Browse Source

feat(2fa): support for upcoming 2fa backend change (#36345)

* feat(2fa): add option to 2FA interfaces to allow for disallowing of new enrollments
* frontend(tests): tests for 2fa disallow new enrollment rendering
* frontend(tests): test when enrolled in 2fa, but new enrollments are disabled
* backend PR => https://github.com/getsentry/sentry/pull/36344
Matthew 2 years ago
parent
commit
ea35c28993

+ 3 - 0
fixtures/js-stubs/authenticators.js

@@ -12,6 +12,7 @@ export function Authenticators() {
       configureButton: 'Info',
       name: 'Authenticator App',
       allowMultiEnrollment: false,
+      disallowNewEnrollment: false,
       authId: '15',
       canValidateOtp: true,
       isBackupInterface: false,
@@ -27,6 +28,7 @@ export function Authenticators() {
       configureButton: 'Info',
       id: 'sms',
       isBackupInterface: false,
+      disallowNewEnrollment: false,
       description:
         "This authenticator sends you text messages for verification.  It's useful as a backup method or when you do not have a phone that supports an authenticator application.",
       ...params,
@@ -43,6 +45,7 @@ export function Authenticators() {
       configureButton: 'Configure',
       name: 'U2F (Universal 2nd Factor)',
       allowMultiEnrollment: true,
+      disallowNewEnrollment: false,
       authId: '23',
       canValidateOtp: false,
       isBackupInterface: false,

+ 4 - 0
static/app/types/auth.tsx

@@ -32,6 +32,10 @@ export type Authenticator = {
    */
   description: string;
   devices: AuthenticatorDevice[];
+  /**
+   * New enrollments of this 2FA interface are not allowed
+   */
+  disallowNewEnrollment: boolean;
   /**
    * String used to display on button for user as CTA to enroll
    */

+ 4 - 0
static/app/views/settings/account/accountSecurity/index.tsx

@@ -136,9 +136,13 @@ class AccountSecurity extends AsyncView<Props> {
                   description,
                   isBackupInterface,
                   isEnrolled,
+                  disallowNewEnrollment,
                   configureButton,
                   name,
                 } = auth;
+                if (disallowNewEnrollment && !isEnrolled) {
+                  return null;
+                }
                 return (
                   <AuthenticatorPanelItem key={id}>
                     <AuthenticatorHeader>

+ 41 - 0
tests/js/spec/views/accountSecurity.spec.jsx

@@ -303,6 +303,47 @@ describe('AccountSecurity', function () {
     expect(wrapper.find('TwoFactorRequired')).toHaveLength(1);
   });
 
+  it('does not render primary interface that disallows new enrollments', function () {
+    Client.addMockResponse({
+      url: ENDPOINT,
+      body: [
+        TestStubs.Authenticators().Totp({disallowNewEnrollment: false}),
+        TestStubs.Authenticators().U2f({disallowNewEnrollment: null}),
+        TestStubs.Authenticators().Sms({disallowNewEnrollment: true}),
+      ],
+    });
+
+    const wrapper = mountWithTheme(
+      <AccountSecurityWrapper>
+        <AccountSecurity />
+      </AccountSecurityWrapper>,
+      TestStubs.routerContext()
+    );
+
+    // There should only be two authenticators rendered
+    expect(wrapper.find('AuthenticatorName')).toHaveLength(2);
+  });
+
+  it('renders primary interface if new enrollments are disallowed, but we are enrolled', function () {
+    Client.addMockResponse({
+      url: ENDPOINT,
+      body: [
+        TestStubs.Authenticators().Sms({isEnrolled: true, disallowNewEnrollment: true}),
+      ],
+    });
+
+    const wrapper = mountWithTheme(
+      <AccountSecurityWrapper>
+        <AccountSecurity />
+      </AccountSecurityWrapper>,
+      TestStubs.routerContext()
+    );
+
+    // Should still render the authenticator since we are already enrolled
+    expect(wrapper.find('AuthenticatorName')).toHaveLength(1);
+    expect(wrapper.find('AuthenticatorName').prop('children')).toBe('Text Message');
+  });
+
   it('renders a backup interface that is enrolled', function () {
     Client.addMockResponse({
       url: ENDPOINT,