accountSecurityWrapper.tsx 3.5 KB

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