recoveryOptionsModal.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import {Fragment, useState} from 'react';
  2. import type {ModalRenderProps} from 'sentry/actionCreators/modal';
  3. import {Alert} from 'sentry/components/alert';
  4. import {Button} from 'sentry/components/button';
  5. import LoadingError from 'sentry/components/loadingError';
  6. import LoadingIndicator from 'sentry/components/loadingIndicator';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import type {Authenticator} from 'sentry/types';
  10. import {useApiQuery} from 'sentry/utils/queryClient';
  11. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  12. type Props = ModalRenderProps & {
  13. authenticatorName: string;
  14. };
  15. function RecoveryOptionsModal({
  16. authenticatorName,
  17. closeModal,
  18. Body,
  19. Header,
  20. Footer,
  21. }: Props) {
  22. const {
  23. isLoading,
  24. isError,
  25. refetch: refetchAuthenticators,
  26. data: authenticators = [],
  27. } = useApiQuery<Authenticator[]>(['/users/me/authenticators/'], {
  28. staleTime: 5000, // expire after 5 seconds
  29. });
  30. const [skipSms, setSkipSms] = useState<boolean>(false);
  31. const {recovery, sms} = authenticators.reduce<{[key: string]: Authenticator}>(
  32. (obj, item) => {
  33. obj[item.id] = item;
  34. return obj;
  35. },
  36. {}
  37. );
  38. const recoveryEnrolled = recovery?.isEnrolled;
  39. const displaySmsPrompt =
  40. sms && !sms.isEnrolled && !skipSms && !sms.disallowNewEnrollment;
  41. const handleSkipSms = () => {
  42. setSkipSms(true);
  43. };
  44. if (isLoading) return <LoadingIndicator />;
  45. if (isError) {
  46. return (
  47. <LoadingError
  48. message={t('There was an error loading authenticators.')}
  49. onRetry={refetchAuthenticators}
  50. />
  51. );
  52. }
  53. return (
  54. <Fragment>
  55. <Header closeButton>{t('Two-Factor Authentication Enabled')}</Header>
  56. <Body>
  57. <TextBlock>
  58. {t('Two-factor authentication via %s has been enabled.', authenticatorName)}
  59. </TextBlock>
  60. <TextBlock>
  61. {t('You should now set up recovery options to secure your account.')}
  62. </TextBlock>
  63. {displaySmsPrompt ? (
  64. // set up backup phone number
  65. <Alert type="warning">
  66. {t('We recommend adding a phone number as a backup 2FA method.')}
  67. </Alert>
  68. ) : (
  69. // get recovery codes
  70. <Alert type="warning">
  71. {t(
  72. `Recovery codes are the only way to access your account if you lose
  73. your device and cannot receive two-factor authentication codes.`
  74. )}
  75. </Alert>
  76. )}
  77. </Body>
  78. {displaySmsPrompt ? (
  79. // set up backup phone number
  80. <Footer>
  81. <Button onClick={handleSkipSms} name="skipStep" autoFocus>
  82. {t('Skip this step')}
  83. </Button>
  84. <Button
  85. priority="primary"
  86. onClick={closeModal}
  87. to={`/settings/account/security/mfa/${sms.id}/enroll/`}
  88. name="addPhone"
  89. css={{marginLeft: space(1)}}
  90. autoFocus
  91. >
  92. {t('Add a Phone Number')}
  93. </Button>
  94. </Footer>
  95. ) : (
  96. // get recovery codes
  97. <Footer>
  98. <Button
  99. priority="primary"
  100. onClick={closeModal}
  101. to={
  102. recoveryEnrolled
  103. ? `/settings/account/security/mfa/${recovery.authId}/`
  104. : '/settings/account/security/'
  105. }
  106. name="getCodes"
  107. autoFocus
  108. >
  109. {t('Get Recovery Codes')}
  110. </Button>
  111. </Footer>
  112. )}
  113. </Fragment>
  114. );
  115. }
  116. export default RecoveryOptionsModal;