import React from 'react'; import {mountWithTheme} from 'sentry-test/enzyme'; import {Client} from 'app/api'; import AccountSecurity from 'app/views/settings/account/accountSecurity'; import AccountSecurityWrapper from 'app/views/settings/account/accountSecurity/accountSecurityWrapper'; const ENDPOINT = '/users/me/authenticators/'; const ORG_ENDPOINT = '/organizations/'; const AUTH_ENDPOINT = '/auth/'; describe('AccountSecurity', function() { beforeEach(function() { Client.clearMockResponses(); Client.addMockResponse({ url: ORG_ENDPOINT, body: TestStubs.Organizations(), }); }); it('renders empty', function() { Client.addMockResponse({ url: ENDPOINT, body: [], }); const wrapper = mountWithTheme( , TestStubs.routerContext() ); expect(wrapper.find('EmptyMessage')).toHaveLength(1); expect(wrapper.find('TwoFactorRequired')).toHaveLength(0); }); it('renders a primary interface that is enrolled', function() { Client.addMockResponse({ url: ENDPOINT, body: [TestStubs.Authenticators().Totp({configureButton: 'Info'})], }); const wrapper = mountWithTheme( , TestStubs.routerContext() ); expect(wrapper.find('AuthenticatorName').prop('children')).toBe('Authenticator App'); // There should be an "Info" button expect( wrapper .find('Button[className="details-button"]') .first() .prop('children') ).toBe('Info'); // Remove button expect(wrapper.find('Button[icon="icon-trash"]')).toHaveLength(1); expect(wrapper.find('CircleIndicator').prop('enabled')).toBe(true); expect(wrapper.find('TwoFactorRequired')).toHaveLength(0); }); it('can delete enrolled authenticator', function() { Client.addMockResponse({ url: ENDPOINT, body: [ TestStubs.Authenticators().Totp({ authId: '15', configureButton: 'Info', }), ], }); const deleteMock = Client.addMockResponse({ url: `${ENDPOINT}15/`, method: 'DELETE', }); expect(deleteMock).not.toHaveBeenCalled(); const wrapper = mountWithTheme( , TestStubs.routerContext() ); expect(wrapper.find('CircleIndicator').prop('enabled')).toBe(true); // This will open confirm modal wrapper.find('Button[icon="icon-trash"]').simulate('click'); // Confirm wrapper .find('Modal Button') .last() .simulate('click'); expect(deleteMock).toHaveBeenCalled(); setTimeout(() => { wrapper.update(); expect(wrapper.find('CircleIndicator').prop('enabled')).toBe(false); }, 1); // still has another 2fa method expect(wrapper.find('TwoFactorRequired')).toHaveLength(0); }); it('can remove one of multiple 2fa methods when org requires 2fa', function() { Client.addMockResponse({ url: ENDPOINT, body: [ TestStubs.Authenticators().Totp({ authId: '15', configureButton: 'Info', }), TestStubs.Authenticators().U2f(), ], }); Client.addMockResponse({ url: ORG_ENDPOINT, body: TestStubs.Organizations({require2FA: true}), }); const deleteMock = Client.addMockResponse({ url: `${ENDPOINT}15/`, method: 'DELETE', }); expect(deleteMock).not.toHaveBeenCalled(); const wrapper = mountWithTheme( , TestStubs.routerContext() ); expect( wrapper .find('CircleIndicator') .first() .prop('enabled') ).toBe(true); expect( wrapper .find('RemoveConfirm') .first() .prop('disabled') ).toBe(false); expect( wrapper .find('Tooltip') .first() .prop('disabled') ).toBe(true); // This will open confirm modal wrapper .find('Button[icon="icon-trash"]') .first() .simulate('click'); // Confirm wrapper .find('Modal Button') .last() .simulate('click'); expect(deleteMock).toHaveBeenCalled(); }); it('can not remove last 2fa method when org requires 2fa', function() { Client.addMockResponse({ url: ENDPOINT, body: [ TestStubs.Authenticators().Totp({ authId: '15', configureButton: 'Info', }), ], }); Client.addMockResponse({ url: ORG_ENDPOINT, body: TestStubs.Organizations({require2FA: true}), }); const deleteMock = Client.addMockResponse({ url: `${ENDPOINT}15/`, method: 'DELETE', }); expect(deleteMock).not.toHaveBeenCalled(); const wrapper = mountWithTheme( , TestStubs.routerContext() ); expect(wrapper.find('CircleIndicator').prop('enabled')).toBe(true); expect(wrapper.find('RemoveConfirm').prop('disabled')).toBe(true); expect(wrapper.find('Tooltip').prop('disabled')).toBe(false); expect(wrapper.find('Tooltip').prop('title')).toContain('test 1 and test 2'); // This will open confirm modal wrapper.find('Button[icon="icon-trash"]').simulate('click'); // Confirm expect(wrapper.find('Modal Button')).toHaveLength(0); expect(deleteMock).not.toHaveBeenCalled(); }); it('renders a primary interface that is not enrolled', function() { Client.addMockResponse({ url: ENDPOINT, body: [TestStubs.Authenticators().Totp({isEnrolled: false})], }); const wrapper = mountWithTheme( , TestStubs.routerContext() ); expect(wrapper.find('AuthenticatorName').prop('children')).toBe('Authenticator App'); // There should be an "Add" button expect( wrapper .find('Button[className="enroll-button"]') .first() .prop('children') ).toBe('Add'); expect(wrapper.find('CircleIndicator').prop('enabled')).toBe(false); // user is not 2fa enrolled expect(wrapper.find('TwoFactorRequired')).toHaveLength(1); }); it('renders a backup interface that is not enrolled', function() { Client.addMockResponse({ url: ENDPOINT, body: [TestStubs.Authenticators().Recovery({isEnrolled: false})], }); const wrapper = mountWithTheme( , TestStubs.routerContext() ); expect(wrapper.find('AuthenticatorName').prop('children')).toBe('Recovery Codes'); // There should be an View Codes button expect(wrapper.find('Button[className="details-button"]')).toHaveLength(0); expect(wrapper.find('CircleIndicator').prop('enabled')).toBe(false); // user is not 2fa enrolled expect(wrapper.find('TwoFactorRequired')).toHaveLength(1); }); it('renders a backup interface that is enrolled', function() { Client.addMockResponse({ url: ENDPOINT, body: [TestStubs.Authenticators().Recovery({isEnrolled: true})], }); const wrapper = mountWithTheme( , TestStubs.routerContext() ); expect(wrapper.find('AuthenticatorName').prop('children')).toBe('Recovery Codes'); // There should be an View Codes button expect( wrapper .find('Button[className="details-button"]') .first() .prop('children') ).toBe('View Codes'); expect(wrapper.find('CircleIndicator').prop('enabled')).toBe(true); }); it('can change password', function() { Client.addMockResponse({ url: ENDPOINT, body: [TestStubs.Authenticators().Recovery({isEnrolled: false})], }); const url = '/users/me/password/'; const mock = Client.addMockResponse({ url, method: 'PUT', }); const wrapper = mountWithTheme( , TestStubs.routerContext() ); wrapper .find('PasswordForm input[name="password"]') .simulate('change', {target: {value: 'oldpassword'}}); wrapper .find('PasswordForm input[name="passwordNew"]') .simulate('change', {target: {value: 'newpassword'}}); wrapper .find('PasswordForm input[name="passwordVerify"]') .simulate('change', {target: {value: 'newpassword'}}); wrapper.find('PasswordForm form').simulate('submit'); expect(mock).toHaveBeenCalledWith( url, expect.objectContaining({ method: 'PUT', data: { password: 'oldpassword', passwordNew: 'newpassword', passwordVerify: 'newpassword', }, }) ); // user is not 2fa enrolled expect(wrapper.find('TwoFactorRequired')).toHaveLength(1); }); it('requires current password to be entered', function() { Client.addMockResponse({ url: ENDPOINT, body: [TestStubs.Authenticators().Recovery({isEnrolled: false})], }); const url = '/users/me/password/'; const mock = Client.addMockResponse({ url, method: 'PUT', }); const wrapper = mountWithTheme( , TestStubs.routerContext() ); wrapper .find('PasswordForm input[name="passwordNew"]') .simulate('change', {target: {value: 'newpassword'}}); wrapper .find('PasswordForm input[name="passwordVerify"]') .simulate('change', {target: {value: 'newpassword'}}); wrapper.find('PasswordForm form').simulate('submit'); expect(mock).not.toHaveBeenCalled(); // user is not 2fa enrolled expect(wrapper.find('TwoFactorRequired')).toHaveLength(1); }); it('can expire all sessions', function() { Client.addMockResponse({ url: ENDPOINT, body: [TestStubs.Authenticators().Recovery({isEnrolled: false})], }); const mock = Client.addMockResponse({ url: AUTH_ENDPOINT, body: {all: true}, method: 'DELETE', status: 204, }); const wrapper = mountWithTheme( , TestStubs.routerContext() ); wrapper.find('Button[data-test-id="signoutAll"]').simulate('click'); expect(mock).toHaveBeenCalled(); }); });