accountSecurityWrapper.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import {cloneElement} from 'react';
  2. import {RouteComponentProps} from 'react-router';
  3. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  4. import AsyncComponent from 'sentry/components/asyncComponent';
  5. import {t} from 'sentry/locale';
  6. import {Authenticator, OrganizationSummary, UserEmail} from 'sentry/types';
  7. import {defined} from 'sentry/utils';
  8. const ENDPOINT = '/users/me/authenticators/';
  9. type Props = {
  10. children: React.ReactElement;
  11. } & RouteComponentProps<{authId: string}, {}> &
  12. AsyncComponent['props'];
  13. type State = {
  14. emails: UserEmail[];
  15. authenticators?: Authenticator[] | null;
  16. organizations?: OrganizationSummary[];
  17. } & AsyncComponent['state'];
  18. class AccountSecurityWrapper extends AsyncComponent<Props, State> {
  19. getEndpoints(): ReturnType<AsyncComponent['getEndpoints']> {
  20. return [
  21. ['authenticators', ENDPOINT],
  22. ['organizations', '/organizations/'],
  23. ['emails', '/users/me/emails/'],
  24. ];
  25. }
  26. handleDisable = async (auth: Authenticator) => {
  27. if (!auth || !auth.authId) {
  28. return;
  29. }
  30. this.setState({loading: true});
  31. try {
  32. await this.api.requestPromise(`${ENDPOINT}${auth.authId}/`, {method: 'DELETE'});
  33. this.remountComponent();
  34. } catch (_err) {
  35. this.setState({loading: false});
  36. addErrorMessage(t('Error disabling %s', auth.name));
  37. }
  38. };
  39. handleRegenerateBackupCodes = async () => {
  40. this.setState({loading: true});
  41. try {
  42. await this.api.requestPromise(`${ENDPOINT}${this.props.params.authId}/`, {
  43. method: 'PUT',
  44. });
  45. this.remountComponent();
  46. } catch (_err) {
  47. this.setState({loading: false});
  48. addErrorMessage(t('Error regenerating backup codes'));
  49. }
  50. };
  51. handleRefresh = () => {
  52. this.fetchData();
  53. };
  54. renderBody() {
  55. const {children} = this.props;
  56. const {authenticators, organizations, emails} = this.state;
  57. const enrolled =
  58. authenticators?.filter(auth => auth.isEnrolled && !auth.isBackupInterface) || [];
  59. const countEnrolled = enrolled.length;
  60. const orgsRequire2fa = organizations?.filter(org => org.require2FA) || [];
  61. const deleteDisabled = orgsRequire2fa.length > 0 && countEnrolled === 1;
  62. const hasVerifiedEmail = !!emails?.find(({isVerified}) => isVerified);
  63. // This happens when you switch between children views and the next child
  64. // view is lazy loaded, it can potentially be `null` while the code split
  65. // package is being fetched
  66. if (!defined(children)) {
  67. return null;
  68. }
  69. return cloneElement(this.props.children, {
  70. onDisable: this.handleDisable,
  71. onRegenerateBackupCodes: this.handleRegenerateBackupCodes,
  72. authenticators,
  73. deleteDisabled,
  74. orgsRequire2fa,
  75. countEnrolled,
  76. hasVerifiedEmail,
  77. handleRefresh: this.handleRefresh,
  78. });
  79. }
  80. }
  81. export default AccountSecurityWrapper;