@oxy.so/services 12.3.0 → 12.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/commonjs/index.js +17 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/types.js +14 -8
- package/lib/commonjs/ui/components/authChooser/types.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/OxyAuthenticatorPanel.js +11 -10
- package/lib/commonjs/ui/components/signIn/OxyAuthenticatorPanel.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/OxyDeleteAccountPanel.js +19 -18
- package/lib/commonjs/ui/components/signIn/OxyDeleteAccountPanel.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/OxyEmailPanel.js +251 -0
- package/lib/commonjs/ui/components/signIn/OxyEmailPanel.js.map +1 -0
- package/lib/commonjs/ui/components/signIn/OxyLinkCommonsPanel.js +40 -33
- package/lib/commonjs/ui/components/signIn/OxyLinkCommonsPanel.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/OxyPasswordPanel.js +51 -19
- package/lib/commonjs/ui/components/signIn/OxyPasswordPanel.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/OxySignInSecurityPanel.js +154 -0
- package/lib/commonjs/ui/components/signIn/OxySignInSecurityPanel.js.map +1 -0
- package/lib/commonjs/ui/components/signIn/ReauthStep.js +6 -4
- package/lib/commonjs/ui/components/signIn/ReauthStep.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/SessionsSection.js +200 -0
- package/lib/commonjs/ui/components/signIn/SessionsSection.js.map +1 -0
- package/lib/commonjs/ui/components/signIn/accountFlowParts.js +6 -0
- package/lib/commonjs/ui/components/signIn/accountFlowParts.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/accountSecurityState.js +74 -0
- package/lib/commonjs/ui/components/signIn/accountSecurityState.js.map +1 -0
- package/lib/commonjs/ui/navigation/routes.js +1 -1
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/navigation/screenComponents.js +4 -0
- package/lib/commonjs/ui/navigation/screenComponents.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaceRegistry.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSecurityPanelScreen.js +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +3 -12
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/CreateAccountScreen.js +3 -12
- package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +7 -3
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ManageAccountScreen.js +8 -196
- package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInEmailScreen.js +19 -0
- package/lib/commonjs/ui/screens/SignInEmailScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/SignInSecurityScreen.js +30 -0
- package/lib/commonjs/ui/screens/SignInSecurityScreen.js.map +1 -0
- package/lib/module/index.js +5 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/authChooser/types.js +15 -8
- package/lib/module/ui/components/authChooser/types.js.map +1 -1
- package/lib/module/ui/components/signIn/OxyAuthenticatorPanel.js +12 -11
- package/lib/module/ui/components/signIn/OxyAuthenticatorPanel.js.map +1 -1
- package/lib/module/ui/components/signIn/OxyDeleteAccountPanel.js +20 -19
- package/lib/module/ui/components/signIn/OxyDeleteAccountPanel.js.map +1 -1
- package/lib/module/ui/components/signIn/OxyEmailPanel.js +246 -0
- package/lib/module/ui/components/signIn/OxyEmailPanel.js.map +1 -0
- package/lib/module/ui/components/signIn/OxyLinkCommonsPanel.js +40 -33
- package/lib/module/ui/components/signIn/OxyLinkCommonsPanel.js.map +1 -1
- package/lib/module/ui/components/signIn/OxyPasswordPanel.js +52 -20
- package/lib/module/ui/components/signIn/OxyPasswordPanel.js.map +1 -1
- package/lib/module/ui/components/signIn/OxySignInSecurityPanel.js +149 -0
- package/lib/module/ui/components/signIn/OxySignInSecurityPanel.js.map +1 -0
- package/lib/module/ui/components/signIn/ReauthStep.js +6 -4
- package/lib/module/ui/components/signIn/ReauthStep.js.map +1 -1
- package/lib/module/ui/components/signIn/SessionsSection.js +194 -0
- package/lib/module/ui/components/signIn/SessionsSection.js.map +1 -0
- package/lib/module/ui/components/signIn/accountFlowParts.js +6 -0
- package/lib/module/ui/components/signIn/accountFlowParts.js.map +1 -1
- package/lib/module/ui/components/signIn/accountSecurityState.js +69 -0
- package/lib/module/ui/components/signIn/accountSecurityState.js.map +1 -0
- package/lib/module/ui/navigation/routes.js +1 -1
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/navigation/screenComponents.js +4 -0
- package/lib/module/ui/navigation/screenComponents.js.map +1 -1
- package/lib/module/ui/navigation/surfaceRegistry.js.map +1 -1
- package/lib/module/ui/screens/AccountSecurityPanelScreen.js +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +4 -13
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/CreateAccountScreen.js +3 -13
- package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/EditProfileFieldScreen.js +8 -4
- package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/module/ui/screens/ManageAccountScreen.js +9 -197
- package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInEmailScreen.js +17 -0
- package/lib/module/ui/screens/SignInEmailScreen.js.map +1 -0
- package/lib/module/ui/screens/SignInSecurityScreen.js +26 -0
- package/lib/module/ui/screens/SignInSecurityScreen.js.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +4 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts +1 -3
- package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/OxyAuthenticatorPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/OxyDeleteAccountPanel.d.ts +3 -2
- package/lib/typescript/commonjs/ui/components/signIn/OxyDeleteAccountPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/OxyEmailPanel.d.ts +21 -0
- package/lib/typescript/commonjs/ui/components/signIn/OxyEmailPanel.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/signIn/OxyLinkCommonsPanel.d.ts +2 -1
- package/lib/typescript/commonjs/ui/components/signIn/OxyLinkCommonsPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/OxyPasswordPanel.d.ts +5 -4
- package/lib/typescript/commonjs/ui/components/signIn/OxyPasswordPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/OxySignInSecurityPanel.d.ts +25 -0
- package/lib/typescript/commonjs/ui/components/signIn/OxySignInSecurityPanel.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/signIn/ReauthStep.d.ts +8 -2
- package/lib/typescript/commonjs/ui/components/signIn/ReauthStep.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/SessionsSection.d.ts +9 -0
- package/lib/typescript/commonjs/ui/components/signIn/SessionsSection.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/signIn/accountFlowParts.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/accountSecurityState.d.ts +26 -0
- package/lib/typescript/commonjs/ui/components/signIn/accountSecurityState.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +3 -2
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/screenComponents.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts +2 -0
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/AccountSecurityPanelScreen.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +3 -2
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/SignInEmailScreen.d.ts +6 -0
- package/lib/typescript/commonjs/ui/screens/SignInEmailScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/SignInSecurityScreen.d.ts +6 -0
- package/lib/typescript/commonjs/ui/screens/SignInSecurityScreen.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +4 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/types.d.ts +1 -3
- package/lib/typescript/module/ui/components/authChooser/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/OxyAuthenticatorPanel.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/OxyDeleteAccountPanel.d.ts +3 -2
- package/lib/typescript/module/ui/components/signIn/OxyDeleteAccountPanel.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/OxyEmailPanel.d.ts +21 -0
- package/lib/typescript/module/ui/components/signIn/OxyEmailPanel.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/signIn/OxyLinkCommonsPanel.d.ts +2 -1
- package/lib/typescript/module/ui/components/signIn/OxyLinkCommonsPanel.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/OxyPasswordPanel.d.ts +5 -4
- package/lib/typescript/module/ui/components/signIn/OxyPasswordPanel.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/OxySignInSecurityPanel.d.ts +25 -0
- package/lib/typescript/module/ui/components/signIn/OxySignInSecurityPanel.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/signIn/ReauthStep.d.ts +8 -2
- package/lib/typescript/module/ui/components/signIn/ReauthStep.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/SessionsSection.d.ts +9 -0
- package/lib/typescript/module/ui/components/signIn/SessionsSection.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/signIn/accountFlowParts.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/accountSecurityState.d.ts +26 -0
- package/lib/typescript/module/ui/components/signIn/accountSecurityState.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +3 -2
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/screenComponents.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts +2 -0
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/AccountSecurityPanelScreen.d.ts +1 -1
- package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts +3 -2
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/SignInEmailScreen.d.ts +6 -0
- package/lib/typescript/module/ui/screens/SignInEmailScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/SignInSecurityScreen.d.ts +6 -0
- package/lib/typescript/module/ui/screens/SignInSecurityScreen.d.ts.map +1 -0
- package/package.json +6 -6
- package/src/index.ts +7 -2
- package/src/ui/components/authChooser/__tests__/profileColors.test.ts +13 -0
- package/src/ui/components/authChooser/types.ts +16 -15
- package/src/ui/components/signIn/OxyAuthenticatorPanel.tsx +11 -8
- package/src/ui/components/signIn/OxyDeleteAccountPanel.tsx +15 -16
- package/src/ui/components/signIn/OxyEmailPanel.tsx +258 -0
- package/src/ui/components/signIn/OxyLinkCommonsPanel.tsx +27 -15
- package/src/ui/components/signIn/OxyPasswordPanel.tsx +49 -14
- package/src/ui/components/signIn/OxySignInSecurityPanel.tsx +147 -0
- package/src/ui/components/signIn/ReauthStep.tsx +13 -4
- package/src/ui/components/signIn/SessionsSection.tsx +218 -0
- package/src/ui/components/signIn/accountFlowParts.tsx +6 -0
- package/src/ui/components/signIn/accountSecurityState.tsx +75 -0
- package/src/ui/navigation/routes.ts +5 -1
- package/src/ui/navigation/screenComponents.ts +4 -0
- package/src/ui/navigation/surfaceRegistry.ts +2 -0
- package/src/ui/screens/AccountSecurityPanelScreen.tsx +1 -1
- package/src/ui/screens/AccountSettingsScreen.tsx +10 -29
- package/src/ui/screens/CreateAccountScreen.tsx +9 -27
- package/src/ui/screens/EditProfileFieldScreen.tsx +10 -8
- package/src/ui/screens/ManageAccountScreen.tsx +8 -241
- package/src/ui/screens/SignInEmailScreen.tsx +13 -0
- package/src/ui/screens/SignInSecurityScreen.tsx +19 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { resolveAccentHex } from '../types';
|
|
2
|
+
import { APP_COLOR_PRESETS } from '@oxy.so/bloom/color-presets';
|
|
3
|
+
|
|
4
|
+
jest.mock('@oxy.so/bloom/theme', () => jest.requireActual('@oxy.so/bloom/color-presets'));
|
|
5
|
+
|
|
6
|
+
it('preserves the account identity for every app using the shared chooser', () => {
|
|
7
|
+
expect(resolveAccentHex('grove', '#fff')).toBe(APP_COLOR_PRESETS.grove.hex);
|
|
8
|
+
expect(resolveAccentHex('amber', '#fff')).toBe(APP_COLOR_PRESETS.yellow.hex);
|
|
9
|
+
expect(resolveAccentHex('#ABC', '#fff')).toBe('#aabbcc');
|
|
10
|
+
expect(resolveAccentHex('#123456', '#fff')).toBe('#123456');
|
|
11
|
+
expect(resolveAccentHex(null, '#fff')).toBe('#fff');
|
|
12
|
+
expect(resolveAccentHex('unknown', '#fff')).toBe('#fff');
|
|
13
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { APP_COLOR_NAMES } from '@oxy.so/bloom/color-presets';
|
|
1
2
|
/**
|
|
2
3
|
* Shared types + per-account color resolution for the auth chooser's views.
|
|
3
4
|
*
|
|
@@ -8,12 +9,7 @@
|
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import type { AccountStorageUsageResponse } from '@oxy.so/core';
|
|
11
|
-
import {
|
|
12
|
-
APP_COLOR_NAMES,
|
|
13
|
-
APP_COLOR_PRESETS,
|
|
14
|
-
type AppColorName,
|
|
15
|
-
type useTheme,
|
|
16
|
-
} from '@oxy.so/bloom/theme';
|
|
12
|
+
import { APP_COLOR_PRESETS, type useTheme } from '@oxy.so/bloom/theme';
|
|
17
13
|
import type { AccountDialogMenuSection } from '../../navigation/accountDialogManager';
|
|
18
14
|
import type { useI18n } from '../../hooks/useI18n';
|
|
19
15
|
|
|
@@ -132,14 +128,19 @@ export interface SignInAlternatives {
|
|
|
132
128
|
* renders in EVERY consumer regardless of NativeWind availability.
|
|
133
129
|
*/
|
|
134
130
|
export function resolveAccentHex(color: string | null, fallback: string): string {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
131
|
+
const normalized = color?.trim().toLowerCase();
|
|
132
|
+
if (!normalized) return fallback;
|
|
133
|
+
|
|
134
|
+
if (/^#[0-9a-f]{3}$/i.test(normalized)) {
|
|
135
|
+
return `#${normalized[1]}${normalized[1]}${normalized[2]}${normalized[2]}${normalized[3]}${normalized[3]}`;
|
|
136
|
+
}
|
|
137
|
+
if (/^#[0-9a-f]{6}$/i.test(normalized)) return normalized;
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
// `amber` was the retired name for `yellow`; existing user records are
|
|
140
|
+
// migrated, but inboxes can still render cached account data during rollout.
|
|
141
|
+
const preset = normalized === 'amber' ? 'yellow' : normalized;
|
|
142
|
+
if (APP_COLOR_NAMES.includes(preset as (typeof APP_COLOR_NAMES)[number])) {
|
|
143
|
+
return APP_COLOR_PRESETS[preset as (typeof APP_COLOR_NAMES)[number]].hex;
|
|
144
|
+
}
|
|
145
|
+
return fallback;
|
|
145
146
|
}
|
|
@@ -23,12 +23,11 @@ import type { TotpEnrollResponse } from '@oxy.so/contracts';
|
|
|
23
23
|
import { TOTP_DIGITS } from '@oxy.so/contracts/client';
|
|
24
24
|
import { useQueryClient } from '@tanstack/react-query';
|
|
25
25
|
import { useOxy } from '../../context/oxyRuntimeContext';
|
|
26
|
-
import { useSignInMethods } from '../../hooks/queries/useAuthMethods';
|
|
27
26
|
import { queryKeys } from '../../hooks/queries/queryKeys';
|
|
28
27
|
import { useI18n } from '../../hooks/useI18n';
|
|
29
28
|
import { copyText } from '../../utils/clipboard';
|
|
30
29
|
import { SubtleLink } from '../authChooser/primitives';
|
|
31
|
-
import {
|
|
30
|
+
import { OxyAuthScreen, OxyAuthScreenHeader } from './OxyAuthScreen';
|
|
32
31
|
import { ReauthStep } from './ReauthStep';
|
|
33
32
|
import {
|
|
34
33
|
AccountFlowAction,
|
|
@@ -38,6 +37,7 @@ import {
|
|
|
38
37
|
AccountFlowProgress,
|
|
39
38
|
describeSignInError,
|
|
40
39
|
} from './accountFlowParts';
|
|
40
|
+
import { accountSecurityFallback, useAccountSecurityState } from './accountSecurityState';
|
|
41
41
|
|
|
42
42
|
/** High-contrast, un-themed on purpose: scan reliability. */
|
|
43
43
|
const QR_PLATE_BG = '#FFFFFF';
|
|
@@ -60,10 +60,9 @@ export interface OxyAuthenticatorPanelProps {
|
|
|
60
60
|
export const OxyAuthenticatorPanel: React.FC<OxyAuthenticatorPanelProps> = ({ onDone }) => {
|
|
61
61
|
const theme = useTheme();
|
|
62
62
|
const { t } = useI18n();
|
|
63
|
-
const { oxyServices
|
|
63
|
+
const { oxyServices } = useOxy();
|
|
64
64
|
const queryClient = useQueryClient();
|
|
65
|
-
const
|
|
66
|
-
const methods = useSignInMethods({ enabled: !keyed });
|
|
65
|
+
const security = useAccountSecurityState();
|
|
67
66
|
const [step, setStep] = useState<Step>({ name: 'status' });
|
|
68
67
|
const [code, setCode] = useState('');
|
|
69
68
|
const [pending, setPending] = useState(false);
|
|
@@ -72,7 +71,7 @@ export const OxyAuthenticatorPanel: React.FC<OxyAuthenticatorPanelProps> = ({ on
|
|
|
72
71
|
const refresh = () =>
|
|
73
72
|
void queryClient.invalidateQueries({ queryKey: queryKeys.signInMethods.all });
|
|
74
73
|
|
|
75
|
-
if (keyed) {
|
|
74
|
+
if (security.keyed) {
|
|
76
75
|
return (
|
|
77
76
|
<OxyAuthScreen>
|
|
78
77
|
<OxyAuthScreenHeader
|
|
@@ -82,8 +81,9 @@ export const OxyAuthenticatorPanel: React.FC<OxyAuthenticatorPanelProps> = ({ on
|
|
|
82
81
|
</OxyAuthScreen>
|
|
83
82
|
);
|
|
84
83
|
}
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
const fallback = accountSecurityFallback(security, t('signInSecurity.totp.title'), t);
|
|
85
|
+
if (fallback || !security.methods.data) return fallback;
|
|
86
|
+
const { hasEmail, hasPassword, totpEnabled, backupCodesRemaining } = security.methods.data;
|
|
87
87
|
|
|
88
88
|
const setUp = () => {
|
|
89
89
|
if (pending) return;
|
|
@@ -157,6 +157,7 @@ export const OxyAuthenticatorPanel: React.FC<OxyAuthenticatorPanelProps> = ({ on
|
|
|
157
157
|
description={t('signInSecurity.totp.scan')}
|
|
158
158
|
action="totp"
|
|
159
159
|
allowPassword={hasPassword}
|
|
160
|
+
allowEmail={hasEmail}
|
|
160
161
|
totpEnabled={false}
|
|
161
162
|
submitLabel={t('signInSecurity.totp.enable')}
|
|
162
163
|
validate={() =>
|
|
@@ -213,6 +214,7 @@ export const OxyAuthenticatorPanel: React.FC<OxyAuthenticatorPanelProps> = ({ on
|
|
|
213
214
|
description={t('signInSecurity.totp.regenerateDescription')}
|
|
214
215
|
action="totp"
|
|
215
216
|
allowPassword={hasPassword}
|
|
217
|
+
allowEmail={hasEmail}
|
|
216
218
|
totpEnabled
|
|
217
219
|
submitLabel={t('signInSecurity.totp.regenerate')}
|
|
218
220
|
onSubmit={async (reauth) => {
|
|
@@ -230,6 +232,7 @@ export const OxyAuthenticatorPanel: React.FC<OxyAuthenticatorPanelProps> = ({ on
|
|
|
230
232
|
description={t('signInSecurity.totp.description')}
|
|
231
233
|
action="totp"
|
|
232
234
|
allowPassword={hasPassword}
|
|
235
|
+
allowEmail={hasEmail}
|
|
233
236
|
totpEnabled
|
|
234
237
|
submitLabel={t('signInSecurity.totp.disable')}
|
|
235
238
|
destructive
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Deleting an account that has no key of its own, from the account's settings
|
|
3
3
|
* (the account dialog's "Manage your account", or the Accounts app):
|
|
4
4
|
*
|
|
5
|
-
* type the username → a code sent to the account's email
|
|
6
|
-
* authenticator's code when it has one) →
|
|
5
|
+
* type the username → a code sent to the account's email, or its password
|
|
6
|
+
* when it has no email (+ the authenticator's code when it has one) →
|
|
7
|
+
* deleted, and signed out
|
|
7
8
|
*
|
|
8
9
|
* An account with a Commons key is deleted with that key — in Commons, or in
|
|
9
10
|
* an app that holds it (`ManageAccountScreen`); this panel says so instead.
|
|
@@ -14,11 +15,11 @@ import type React from 'react';
|
|
|
14
15
|
import { useState } from 'react';
|
|
15
16
|
import { surfaces } from '@oxy.so/bloom/surfaces';
|
|
16
17
|
import { useOxy } from '../../context/oxyRuntimeContext';
|
|
17
|
-
import { useSignInMethods } from '../../hooks/queries/useAuthMethods';
|
|
18
18
|
import { useI18n } from '../../hooks/useI18n';
|
|
19
|
-
import {
|
|
19
|
+
import { OxyAuthScreen, OxyAuthScreenHeader } from './OxyAuthScreen';
|
|
20
20
|
import { ReauthStep } from './ReauthStep';
|
|
21
21
|
import { AccountFlowField, AccountFlowNotice } from './accountFlowParts';
|
|
22
|
+
import { accountSecurityFallback, useAccountSecurityState } from './accountSecurityState';
|
|
22
23
|
|
|
23
24
|
export interface OxyDeleteAccountPanelProps {
|
|
24
25
|
/** The account is gone and this origin signed out of it. */
|
|
@@ -33,8 +34,7 @@ export const OxyDeleteAccountPanel: React.FC<OxyDeleteAccountPanelProps> = ({
|
|
|
33
34
|
}) => {
|
|
34
35
|
const { t } = useI18n();
|
|
35
36
|
const { user, oxyServices, logout } = useOxy();
|
|
36
|
-
const
|
|
37
|
-
const methods = useSignInMethods({ enabled: !keyed });
|
|
37
|
+
const security = useAccountSecurityState();
|
|
38
38
|
const [confirmText, setConfirmText] = useState('');
|
|
39
39
|
const [deleted, setDeleted] = useState(false);
|
|
40
40
|
|
|
@@ -49,7 +49,7 @@ export const OxyDeleteAccountPanel: React.FC<OxyDeleteAccountPanelProps> = ({
|
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
if (keyed) {
|
|
52
|
+
if (security.keyed) {
|
|
53
53
|
return (
|
|
54
54
|
<OxyAuthScreen>
|
|
55
55
|
<OxyAuthScreenHeader title={t('deleteAccount.handoff.commonsTitle')} />
|
|
@@ -60,7 +60,9 @@ export const OxyDeleteAccountPanel: React.FC<OxyDeleteAccountPanelProps> = ({
|
|
|
60
60
|
);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
const fallback = accountSecurityFallback(security, t('deleteAccount.title'), t);
|
|
64
|
+
if (fallback || !security.methods.data) return fallback;
|
|
65
|
+
const { hasEmail, totpEnabled } = security.methods.data;
|
|
64
66
|
|
|
65
67
|
const username = user?.username ?? '';
|
|
66
68
|
|
|
@@ -69,14 +71,16 @@ export const OxyDeleteAccountPanel: React.FC<OxyDeleteAccountPanelProps> = ({
|
|
|
69
71
|
title={t('deleteAccount.title')}
|
|
70
72
|
description={t('deleteAccount.keyless.subtitle', { username })}
|
|
71
73
|
action="delete_account"
|
|
72
|
-
|
|
74
|
+
// The mailbox proves it when there is one; the password only without.
|
|
75
|
+
allowPassword={!hasEmail}
|
|
76
|
+
allowEmail={hasEmail}
|
|
77
|
+
totpEnabled={totpEnabled}
|
|
73
78
|
submitLabel={t('deleteAccount.keyless.action')}
|
|
74
79
|
destructive
|
|
75
80
|
validate={() =>
|
|
76
81
|
confirmText.trim() === username ? null : t('deleteAccount.confirmLabel', { username })
|
|
77
82
|
}
|
|
78
83
|
onSubmit={async (proof) => {
|
|
79
|
-
if (!proof.emailCode) throw new Error(t('reauth.errors.invalid'));
|
|
80
84
|
// The last word before it is gone: the proof is ready, and nothing has
|
|
81
85
|
// been sent yet. Cancelling keeps the screen as it is, code and all.
|
|
82
86
|
const confirmed = await surfaces.confirm({
|
|
@@ -87,12 +91,7 @@ export const OxyDeleteAccountPanel: React.FC<OxyDeleteAccountPanelProps> = ({
|
|
|
87
91
|
destructive: true,
|
|
88
92
|
});
|
|
89
93
|
if (!confirmed) return;
|
|
90
|
-
await oxyServices.users.deleteMe(confirmText.trim(), {
|
|
91
|
-
reauth: {
|
|
92
|
-
emailCode: proof.emailCode,
|
|
93
|
-
...(proof.totpCode ? { totpCode: proof.totpCode } : {}),
|
|
94
|
-
},
|
|
95
|
-
});
|
|
94
|
+
await oxyServices.users.deleteMe(confirmText.trim(), { reauth: proof });
|
|
96
95
|
await logout().catch(() => undefined);
|
|
97
96
|
setDeleted(true);
|
|
98
97
|
onDeleted?.();
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The account's email, from its settings: add one, change it, or remove it.
|
|
3
|
+
*
|
|
4
|
+
* add / change: the new address → a code sent to IT, plus the person's
|
|
5
|
+
* proof (the password, or a code to the current email; + the
|
|
6
|
+
* authenticator's) → saved, and every other session signed out
|
|
7
|
+
*
|
|
8
|
+
* remove: only with a password set, confirmed with that password (+ the
|
|
9
|
+
* authenticator's) → the account signs in, and is recovered, with its
|
|
10
|
+
* password alone
|
|
11
|
+
*
|
|
12
|
+
* Sign-in codes, links and security notices go to this address. An account
|
|
13
|
+
* with a Commons key has no email; this panel says so instead.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type React from 'react';
|
|
17
|
+
import { useState } from 'react';
|
|
18
|
+
import { toast } from '@oxy.so/bloom/toast';
|
|
19
|
+
import { EMAIL_CODE_LENGTH, checkContract, emailAddressCheck } from '@oxy.so/contracts/client';
|
|
20
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
21
|
+
import { useOxy } from '../../context/oxyRuntimeContext';
|
|
22
|
+
import { queryKeys } from '../../hooks/queries/queryKeys';
|
|
23
|
+
import { useI18n } from '../../hooks/useI18n';
|
|
24
|
+
import { SubtleLink } from '../authChooser/primitives';
|
|
25
|
+
import { OxyAuthScreen, OxyAuthScreenHeader } from './OxyAuthScreen';
|
|
26
|
+
import { ReauthStep } from './ReauthStep';
|
|
27
|
+
import {
|
|
28
|
+
AccountFlowAction,
|
|
29
|
+
AccountFlowCodeField,
|
|
30
|
+
AccountFlowField,
|
|
31
|
+
AccountFlowNote,
|
|
32
|
+
AccountFlowNotice,
|
|
33
|
+
describeSignInError,
|
|
34
|
+
} from './accountFlowParts';
|
|
35
|
+
import { accountSecurityFallback, useAccountSecurityState } from './accountSecurityState';
|
|
36
|
+
|
|
37
|
+
type Step =
|
|
38
|
+
| { name: 'status' }
|
|
39
|
+
| { name: 'address' }
|
|
40
|
+
| { name: 'confirm'; email: string; verificationId: string }
|
|
41
|
+
| { name: 'remove' };
|
|
42
|
+
|
|
43
|
+
export interface OxyEmailPanelProps {
|
|
44
|
+
/** The email was added, changed or removed. */
|
|
45
|
+
onDone?: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const OxyEmailPanel: React.FC<OxyEmailPanelProps> = ({ onDone }) => {
|
|
49
|
+
const { t } = useI18n();
|
|
50
|
+
const { oxyServices, user } = useOxy();
|
|
51
|
+
const queryClient = useQueryClient();
|
|
52
|
+
const security = useAccountSecurityState();
|
|
53
|
+
const [step, setStep] = useState<Step>({ name: 'status' });
|
|
54
|
+
const [address, setAddress] = useState('');
|
|
55
|
+
const [code, setCode] = useState('');
|
|
56
|
+
const [pending, setPending] = useState(false);
|
|
57
|
+
const [error, setError] = useState<string | null>(null);
|
|
58
|
+
|
|
59
|
+
if (security.keyed) {
|
|
60
|
+
return (
|
|
61
|
+
<OxyAuthScreen>
|
|
62
|
+
<OxyAuthScreenHeader
|
|
63
|
+
title={t('signInSecurity.email.title')}
|
|
64
|
+
description={t('linkCommons.already')}
|
|
65
|
+
/>
|
|
66
|
+
</OxyAuthScreen>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const fallback = accountSecurityFallback(security, t('signInSecurity.email.title'), t);
|
|
70
|
+
if (fallback || !security.methods.data) return fallback;
|
|
71
|
+
const { hasEmail, hasPassword, totpEnabled } = security.methods.data;
|
|
72
|
+
const current = hasEmail ? (user?.email ?? null) : null;
|
|
73
|
+
|
|
74
|
+
const finish = (message: string) => {
|
|
75
|
+
void queryClient.invalidateQueries({ queryKey: queryKeys.signInMethods.all });
|
|
76
|
+
void queryClient.invalidateQueries({ queryKey: queryKeys.accounts.all });
|
|
77
|
+
toast.success(message);
|
|
78
|
+
setStep({ name: 'status' });
|
|
79
|
+
setAddress('');
|
|
80
|
+
setCode('');
|
|
81
|
+
onDone?.();
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const sendCode = (email: string) => {
|
|
85
|
+
if (pending) return;
|
|
86
|
+
setError(null);
|
|
87
|
+
setPending(true);
|
|
88
|
+
oxyServices.auth.email
|
|
89
|
+
.startChange(email)
|
|
90
|
+
.then((started) => {
|
|
91
|
+
setCode('');
|
|
92
|
+
setStep({ name: 'confirm', email, verificationId: started.verificationId });
|
|
93
|
+
})
|
|
94
|
+
.catch((reason: unknown) => setError(describeSignInError(reason, t)))
|
|
95
|
+
.finally(() => setPending(false));
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const submitAddress = () => {
|
|
99
|
+
const email = checkContract(emailAddressCheck, address);
|
|
100
|
+
if (!email) {
|
|
101
|
+
setError(t('signInSecurity.email.invalid'));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (email === current) {
|
|
105
|
+
setError(t('signInSecurity.errors.emailUnchanged'));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
sendCode(email);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
switch (step.name) {
|
|
112
|
+
case 'address':
|
|
113
|
+
return (
|
|
114
|
+
<OxyAuthScreen>
|
|
115
|
+
<OxyAuthScreenHeader
|
|
116
|
+
title={hasEmail ? t('signInSecurity.email.change') : t('signInSecurity.email.add')}
|
|
117
|
+
description={t('signInSecurity.email.newDescription')}
|
|
118
|
+
/>
|
|
119
|
+
<AccountFlowField
|
|
120
|
+
label={t('signInSecurity.email.newLabel')}
|
|
121
|
+
value={address}
|
|
122
|
+
onChange={(value) => {
|
|
123
|
+
setAddress(value);
|
|
124
|
+
if (error) setError(null);
|
|
125
|
+
}}
|
|
126
|
+
onSubmit={submitAddress}
|
|
127
|
+
error={error}
|
|
128
|
+
disabled={pending}
|
|
129
|
+
autoComplete="email"
|
|
130
|
+
keyboardType="email-address"
|
|
131
|
+
testID="email-new"
|
|
132
|
+
/>
|
|
133
|
+
<AccountFlowAction
|
|
134
|
+
label={t('signInSecurity.email.sendCode')}
|
|
135
|
+
onPress={submitAddress}
|
|
136
|
+
pending={pending}
|
|
137
|
+
testID="email-send-code"
|
|
138
|
+
/>
|
|
139
|
+
<SubtleLink
|
|
140
|
+
label={t('common.cancel')}
|
|
141
|
+
onPress={() => {
|
|
142
|
+
setError(null);
|
|
143
|
+
setStep({ name: 'status' });
|
|
144
|
+
}}
|
|
145
|
+
disabled={pending}
|
|
146
|
+
testID="email-cancel"
|
|
147
|
+
/>
|
|
148
|
+
</OxyAuthScreen>
|
|
149
|
+
);
|
|
150
|
+
case 'confirm':
|
|
151
|
+
return (
|
|
152
|
+
<ReauthStep
|
|
153
|
+
title={t('signInSecurity.email.confirmTitle')}
|
|
154
|
+
description={t('signInSecurity.email.confirmDescription', { email: step.email })}
|
|
155
|
+
action="change_email"
|
|
156
|
+
allowPassword={hasPassword}
|
|
157
|
+
allowEmail={hasEmail}
|
|
158
|
+
totpEnabled={totpEnabled}
|
|
159
|
+
submitLabel={t('signInSecurity.email.save')}
|
|
160
|
+
validate={() =>
|
|
161
|
+
code.replace(/\D/g, '').length === EMAIL_CODE_LENGTH
|
|
162
|
+
? null
|
|
163
|
+
: t('emailCode.errors.codeInvalid')
|
|
164
|
+
}
|
|
165
|
+
onSubmit={async (reauth) => {
|
|
166
|
+
await oxyServices.auth.email.change({
|
|
167
|
+
email: step.email,
|
|
168
|
+
verificationId: step.verificationId,
|
|
169
|
+
code: code.replace(/\D/g, ''),
|
|
170
|
+
reauth,
|
|
171
|
+
});
|
|
172
|
+
finish(t('signInSecurity.email.saved'));
|
|
173
|
+
}}
|
|
174
|
+
secondary={{ label: t('common.cancel'), onPress: () => setStep({ name: 'status' }) }}
|
|
175
|
+
>
|
|
176
|
+
<AccountFlowCodeField
|
|
177
|
+
label={t('signInSecurity.email.codeLabel')}
|
|
178
|
+
value={code}
|
|
179
|
+
onChange={setCode}
|
|
180
|
+
error={null}
|
|
181
|
+
length={EMAIL_CODE_LENGTH}
|
|
182
|
+
testID="email-new-code"
|
|
183
|
+
/>
|
|
184
|
+
<SubtleLink
|
|
185
|
+
label={t('emailCode.resend')}
|
|
186
|
+
onPress={() => sendCode(step.email)}
|
|
187
|
+
disabled={pending}
|
|
188
|
+
testID="email-new-code-resend"
|
|
189
|
+
/>
|
|
190
|
+
{error ? (
|
|
191
|
+
<AccountFlowNotice type="error" testID="email-new-code-error">
|
|
192
|
+
{error}
|
|
193
|
+
</AccountFlowNotice>
|
|
194
|
+
) : null}
|
|
195
|
+
</ReauthStep>
|
|
196
|
+
);
|
|
197
|
+
case 'remove':
|
|
198
|
+
return (
|
|
199
|
+
<ReauthStep
|
|
200
|
+
title={t('signInSecurity.email.removeTitle')}
|
|
201
|
+
description={t('signInSecurity.email.removeDescription')}
|
|
202
|
+
action="change_email"
|
|
203
|
+
// Confirmed with the password it will rely on, never the mailbox it loses.
|
|
204
|
+
allowPassword
|
|
205
|
+
allowEmail={false}
|
|
206
|
+
totpEnabled={totpEnabled}
|
|
207
|
+
submitLabel={t('signInSecurity.email.remove')}
|
|
208
|
+
destructive
|
|
209
|
+
onSubmit={async (reauth) => {
|
|
210
|
+
await oxyServices.auth.email.remove(reauth);
|
|
211
|
+
finish(t('signInSecurity.email.removed'));
|
|
212
|
+
}}
|
|
213
|
+
secondary={{ label: t('common.cancel'), onPress: () => setStep({ name: 'status' }) }}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
default:
|
|
217
|
+
return (
|
|
218
|
+
<OxyAuthScreen>
|
|
219
|
+
<OxyAuthScreenHeader
|
|
220
|
+
title={t('signInSecurity.email.title')}
|
|
221
|
+
description={
|
|
222
|
+
hasEmail
|
|
223
|
+
? t('signInSecurity.email.description')
|
|
224
|
+
: t('signInSecurity.email.noneDescription')
|
|
225
|
+
}
|
|
226
|
+
/>
|
|
227
|
+
{current ? <AccountFlowNote testID="email-current">{current}</AccountFlowNote> : null}
|
|
228
|
+
{!hasEmail ? (
|
|
229
|
+
<AccountFlowNotice type="warning" testID="email-none-warning">
|
|
230
|
+
{t('signInSecurity.noEmailWarning')}
|
|
231
|
+
</AccountFlowNotice>
|
|
232
|
+
) : null}
|
|
233
|
+
<AccountFlowAction
|
|
234
|
+
label={hasEmail ? t('signInSecurity.email.change') : t('signInSecurity.email.add')}
|
|
235
|
+
onPress={() => {
|
|
236
|
+
setError(null);
|
|
237
|
+
setAddress('');
|
|
238
|
+
setStep({ name: 'address' });
|
|
239
|
+
}}
|
|
240
|
+
pending={false}
|
|
241
|
+
testID="email-change"
|
|
242
|
+
/>
|
|
243
|
+
{hasEmail && hasPassword ? (
|
|
244
|
+
<SubtleLink
|
|
245
|
+
label={t('signInSecurity.email.remove')}
|
|
246
|
+
onPress={() => setStep({ name: 'remove' })}
|
|
247
|
+
testID="remove-email-link"
|
|
248
|
+
/>
|
|
249
|
+
) : null}
|
|
250
|
+
{hasEmail && !hasPassword ? (
|
|
251
|
+
<AccountFlowNote testID="email-remove-needs-password">
|
|
252
|
+
{t('signInSecurity.email.removeNeedsPassword')}
|
|
253
|
+
</AccountFlowNote>
|
|
254
|
+
) : null}
|
|
255
|
+
</OxyAuthScreen>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* QR → Commons scans and signs with its key → both screens show the same
|
|
6
6
|
* code → the person confirms it's them with a code sent to the account's
|
|
7
|
-
* email (+ the authenticator's code) →
|
|
7
|
+
* email, or its password when it has none (+ the authenticator's code) →
|
|
8
|
+
* linked
|
|
8
9
|
*
|
|
9
10
|
* Afterwards the account is self-custodied: its root is the key Commons holds,
|
|
10
11
|
* its email, password and authenticator are deleted, and its phrase in Commons
|
|
@@ -22,7 +23,6 @@ import { Text } from '@oxy.so/bloom/typography';
|
|
|
22
23
|
import type { IdentityLinkCreateResponse } from '@oxy.so/contracts';
|
|
23
24
|
import { useOxy } from '../../context/oxyRuntimeContext';
|
|
24
25
|
import { loadIdentityCrypto } from '../../utils/identityCrypto';
|
|
25
|
-
import { useSignInMethods } from '../../hooks/queries/useAuthMethods';
|
|
26
26
|
import { useI18n } from '../../hooks/useI18n';
|
|
27
27
|
import { OxyAuthScreen, OxyAuthScreenHeader } from './OxyAuthScreen';
|
|
28
28
|
import { ReauthStep } from './ReauthStep';
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
AccountFlowNote,
|
|
33
33
|
describeSignInError,
|
|
34
34
|
} from './accountFlowParts';
|
|
35
|
+
import { accountSecurityFallback, useAccountSecurityState } from './accountSecurityState';
|
|
35
36
|
|
|
36
37
|
/** How often the page asks whether Commons has signed. */
|
|
37
38
|
export const IDENTITY_LINK_POLL_MS = 2000;
|
|
@@ -57,11 +58,11 @@ export interface OxyLinkCommonsPanelProps {
|
|
|
57
58
|
export const OxyLinkCommonsPanel: React.FC<OxyLinkCommonsPanelProps> = ({ onLinked }) => {
|
|
58
59
|
const theme = useTheme();
|
|
59
60
|
const { t } = useI18n();
|
|
60
|
-
const {
|
|
61
|
+
const { oxyServices } = useOxy();
|
|
61
62
|
const [step, setStep] = useState<Step>({ name: 'opening' });
|
|
62
63
|
const [error, setError] = useState<string | null>(null);
|
|
63
|
-
const
|
|
64
|
-
const
|
|
64
|
+
const security = useAccountSecurityState();
|
|
65
|
+
const alreadyLinked = security.keyed;
|
|
65
66
|
|
|
66
67
|
const open = useCallback(() => {
|
|
67
68
|
setError(null);
|
|
@@ -78,10 +79,10 @@ export const OxyLinkCommonsPanel: React.FC<OxyLinkCommonsPanelProps> = ({ onLink
|
|
|
78
79
|
// One request per visit, opened once the page knows the account has no root.
|
|
79
80
|
const opened = useRef(false);
|
|
80
81
|
useEffect(() => {
|
|
81
|
-
if (alreadyLinked || opened.current) return;
|
|
82
|
+
if (alreadyLinked || !security.personal || opened.current) return;
|
|
82
83
|
opened.current = true;
|
|
83
84
|
open();
|
|
84
|
-
}, [alreadyLinked, open]);
|
|
85
|
+
}, [alreadyLinked, security.personal, open]);
|
|
85
86
|
|
|
86
87
|
// While the QR shows, ask whether Commons has signed; a request only lives a
|
|
87
88
|
// few minutes, and an expired one says so instead of waiting forever.
|
|
@@ -140,27 +141,38 @@ export const OxyLinkCommonsPanel: React.FC<OxyLinkCommonsPanelProps> = ({ onLink
|
|
|
140
141
|
);
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
if (!security.personal) return accountSecurityFallback(security, t('linkCommons.title'), t);
|
|
145
|
+
|
|
143
146
|
switch (step.name) {
|
|
144
|
-
case 'confirm':
|
|
147
|
+
case 'confirm': {
|
|
148
|
+
// The proof depends on what the account has: wait for it rather than
|
|
149
|
+
// guess (an authenticator left out would only fail the confirmation).
|
|
150
|
+
const fallback = accountSecurityFallback(security, t('linkCommons.confirmTitle'), t);
|
|
151
|
+
if (fallback || !security.methods.data) return fallback;
|
|
152
|
+
const { hasEmail, totpEnabled } = security.methods.data;
|
|
145
153
|
return (
|
|
146
154
|
<ReauthStep
|
|
147
155
|
title={t('linkCommons.confirmTitle')}
|
|
148
|
-
description={
|
|
156
|
+
description={
|
|
157
|
+
hasEmail
|
|
158
|
+
? t('linkCommons.confirmDescription')
|
|
159
|
+
: t('linkCommons.confirmPasswordDescription')
|
|
160
|
+
}
|
|
149
161
|
action="link_commons"
|
|
150
|
-
|
|
162
|
+
// The mailbox proves it when there is one; the password only without.
|
|
163
|
+
allowPassword={!hasEmail}
|
|
164
|
+
allowEmail={hasEmail}
|
|
165
|
+
totpEnabled={totpEnabled}
|
|
151
166
|
submitLabel={t('linkCommons.confirm')}
|
|
152
167
|
onSubmit={async (proof) => {
|
|
153
|
-
|
|
154
|
-
await oxyServices.identity.links.complete(step.link.linkId, {
|
|
155
|
-
emailCode: proof.emailCode,
|
|
156
|
-
...(proof.totpCode ? { totpCode: proof.totpCode } : {}),
|
|
157
|
-
});
|
|
168
|
+
await oxyServices.identity.links.complete(step.link.linkId, proof);
|
|
158
169
|
setStep({ name: 'done' });
|
|
159
170
|
onLinked?.();
|
|
160
171
|
}}
|
|
161
172
|
secondary={{ label: t('linkCommons.cancel'), onPress: () => cancel(step.link) }}
|
|
162
173
|
/>
|
|
163
174
|
);
|
|
175
|
+
}
|
|
164
176
|
case 'done':
|
|
165
177
|
return (
|
|
166
178
|
<OxyAuthScreen>
|