accountSecurityWrapper.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import {cloneElement, useCallback} from 'react';
  2. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  3. import {fetchOrganizations} from 'sentry/actionCreators/organizations';
  4. import LoadingError from 'sentry/components/loadingError';
  5. import LoadingIndicator from 'sentry/components/loadingIndicator';
  6. import {t} from 'sentry/locale';
  7. import type {Authenticator} from 'sentry/types/auth';
  8. import type {OrganizationSummary} from 'sentry/types/organization';
  9. import type {UserEmail} from 'sentry/types/user';
  10. import {defined} from 'sentry/utils';
  11. import {useApiQuery, useMutation, useQuery} from 'sentry/utils/queryClient';
  12. import useApi from 'sentry/utils/useApi';
  13. import {useParams} from 'sentry/utils/useParams';
  14. const ENDPOINT = '/users/me/authenticators/';
  15. interface Props {
  16. children: React.ReactElement;
  17. }
  18. function AccountSecurityWrapper({children}: Props) {
  19. const api = useApi();
  20. const {authId} = useParams<{authId?: string}>();
  21. const orgRequest = useQuery<OrganizationSummary[]>({
  22. queryKey: ['organizations'],
  23. queryFn: () => fetchOrganizations(api),
  24. staleTime: 0,
  25. });
  26. const emailsRequest = useApiQuery<UserEmail[]>(['/users/me/emails/'], {staleTime: 0});
  27. const authenticatorsRequest = useApiQuery<Authenticator[]>([ENDPOINT], {staleTime: 0});
  28. const handleRefresh = useCallback(() => {
  29. orgRequest.refetch();
  30. authenticatorsRequest.refetch();
  31. emailsRequest.refetch();
  32. }, [orgRequest, authenticatorsRequest, emailsRequest]);
  33. const disableAuthenticatorMutation = useMutation({
  34. mutationFn: async (auth: Authenticator) => {
  35. if (!auth || !auth.authId) {
  36. return;
  37. }
  38. await api.requestPromise(`${ENDPOINT}${auth.authId}/`, {method: 'DELETE'});
  39. },
  40. onSuccess: () => {
  41. handleRefresh();
  42. },
  43. onError: (_, auth) => {
  44. addErrorMessage(t('Error disabling %s', auth.name));
  45. },
  46. });
  47. const regenerateBackupCodesMutation = useMutation({
  48. mutationFn: async () => {
  49. if (!authId) {
  50. return;
  51. }
  52. await api.requestPromise(`${ENDPOINT}${authId}/`, {
  53. method: 'PUT',
  54. });
  55. },
  56. onSuccess: () => {
  57. handleRefresh();
  58. },
  59. onError: () => {
  60. addErrorMessage(t('Error regenerating backup codes'));
  61. },
  62. });
  63. if (
  64. orgRequest.isPending ||
  65. emailsRequest.isPending ||
  66. authenticatorsRequest.isPending ||
  67. disableAuthenticatorMutation.isPending ||
  68. regenerateBackupCodesMutation.isPending
  69. ) {
  70. return <LoadingIndicator />;
  71. }
  72. if (authenticatorsRequest.isError || emailsRequest.isError || orgRequest.isError) {
  73. return <LoadingError onRetry={handleRefresh} />;
  74. }
  75. const authenticators = authenticatorsRequest.data;
  76. const emails = emailsRequest.data;
  77. const organizations = orgRequest.data;
  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(children, {
  91. onDisable: disableAuthenticatorMutation.mutate,
  92. onRegenerateBackupCodes: regenerateBackupCodesMutation.mutate,
  93. authenticators,
  94. deleteDisabled,
  95. orgsRequire2fa,
  96. countEnrolled,
  97. hasVerifiedEmail,
  98. handleRefresh,
  99. });
  100. }
  101. export default AccountSecurityWrapper;