@oxy.so/services 11.3.1 → 11.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/lib/commonjs/ui/components/signIn/OxyEmailAccountChoice.js +113 -0
- package/lib/commonjs/ui/components/signIn/OxyEmailAccountChoice.js.map +1 -0
- package/lib/commonjs/ui/components/signIn/OxySignInPanel.js +70 -3
- package/lib/commonjs/ui/components/signIn/OxySignInPanel.js.map +1 -1
- package/lib/commonjs/ui/components/signIn/accountFlowParts.js +2 -0
- package/lib/commonjs/ui/components/signIn/accountFlowParts.js.map +1 -1
- package/lib/module/ui/components/signIn/OxyEmailAccountChoice.js +110 -0
- package/lib/module/ui/components/signIn/OxyEmailAccountChoice.js.map +1 -0
- package/lib/module/ui/components/signIn/OxySignInPanel.js +71 -4
- package/lib/module/ui/components/signIn/OxySignInPanel.js.map +1 -1
- package/lib/module/ui/components/signIn/accountFlowParts.js +2 -0
- package/lib/module/ui/components/signIn/accountFlowParts.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/OxyEmailAccountChoice.d.ts +20 -0
- package/lib/typescript/commonjs/ui/components/signIn/OxyEmailAccountChoice.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/signIn/OxySignInPanel.d.ts +2 -0
- package/lib/typescript/commonjs/ui/components/signIn/OxySignInPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/signIn/accountFlowParts.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/OxyEmailAccountChoice.d.ts +20 -0
- package/lib/typescript/module/ui/components/signIn/OxyEmailAccountChoice.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/signIn/OxySignInPanel.d.ts +2 -0
- package/lib/typescript/module/ui/components/signIn/OxySignInPanel.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/signIn/accountFlowParts.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ui/components/signIn/OxyEmailAccountChoice.tsx +116 -0
- package/src/ui/components/signIn/OxySignInPanel.tsx +89 -4
- package/src/ui/components/signIn/accountFlowParts.tsx +2 -0
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* (the start response does not say which, by design); meanwhile
|
|
17
17
|
* the screen asks whether the link was opened in this browser,
|
|
18
18
|
* and signs in when it was.
|
|
19
|
+
* choose the email holds several accounts, and the code or the link just
|
|
20
|
+
* proved it: which one signs in (a username never gets here).
|
|
19
21
|
* password the alternative for an account that has one.
|
|
20
22
|
* second-factor the authenticator's code, or a backup code, when the account has one.
|
|
21
23
|
*
|
|
@@ -39,9 +41,11 @@ import {
|
|
|
39
41
|
EMAIL_SIGNIN_LONG_CODE_LENGTH,
|
|
40
42
|
SIGN_IN_ERROR_CODES,
|
|
41
43
|
TOTP_DIGITS,
|
|
44
|
+
isEmailSignInChooseAccount,
|
|
42
45
|
isSecondFactorRequired,
|
|
46
|
+
type EmailSignInAccountOption,
|
|
47
|
+
type EmailSignInStepResult,
|
|
43
48
|
type LoginResult,
|
|
44
|
-
type SignInStepResult,
|
|
45
49
|
} from '@oxy.so/contracts';
|
|
46
50
|
import { useQueryClient } from '@tanstack/react-query';
|
|
47
51
|
import { useOxy } from '../../context/OxyContext';
|
|
@@ -56,6 +60,7 @@ import { SIGN_IN_SLIDES } from './artwork';
|
|
|
56
60
|
import { InlineCommonsQr } from './InlineCommonsQr';
|
|
57
61
|
import { OxyAccountPicker } from './OxyAccountPicker';
|
|
58
62
|
import { OxyAuthScreen, OxyAuthScreenHeader, OxyAuthSplit, OxyAuthTerms } from './OxyAuthScreen';
|
|
63
|
+
import { OxyEmailAccountChoice } from './OxyEmailAccountChoice';
|
|
59
64
|
import {
|
|
60
65
|
AccountFlowAction,
|
|
61
66
|
AccountFlowCodeField,
|
|
@@ -94,6 +99,13 @@ const SIGN_IN_FLOW_KEY = 'signin';
|
|
|
94
99
|
type Step =
|
|
95
100
|
| { name: 'start' }
|
|
96
101
|
| { name: 'check-email'; identifier: string; requestId: string; requestSecret: string }
|
|
102
|
+
| {
|
|
103
|
+
name: 'choose';
|
|
104
|
+
identifier: string;
|
|
105
|
+
requestId: string;
|
|
106
|
+
requestSecret: string;
|
|
107
|
+
accounts: EmailSignInAccountOption[];
|
|
108
|
+
}
|
|
97
109
|
| { name: 'password'; identifier: string }
|
|
98
110
|
| { name: 'second-factor'; challengeId: string; identifier: string };
|
|
99
111
|
|
|
@@ -153,6 +165,7 @@ export const OxySignInPanel: React.FC<OxySignInPanelProps> = ({
|
|
|
153
165
|
const [useBackupCode, setUseBackupCode] = useState(saved?.useBackupCode ?? false);
|
|
154
166
|
const [longCode, setLongCode] = useState(saved?.longCode ?? false);
|
|
155
167
|
const [pending, setPending] = useState(false);
|
|
168
|
+
const [choosingId, setChoosingId] = useState<string | null>(null);
|
|
156
169
|
const [error, setError] = useState<string | null>(null);
|
|
157
170
|
const [notice, setNotice] = useState<string | null>(null);
|
|
158
171
|
const [rateLimitSeconds, setRateLimitSeconds] = useState(0);
|
|
@@ -225,8 +238,25 @@ export const OxySignInPanel: React.FC<OxySignInPanelProps> = ({
|
|
|
225
238
|
// One sign-in ends once: the typed code and the opened link can race.
|
|
226
239
|
const finishingRef = useRef(false);
|
|
227
240
|
const finish = useCallback(
|
|
228
|
-
async (
|
|
241
|
+
async (
|
|
242
|
+
result: EmailSignInStepResult,
|
|
243
|
+
from: string,
|
|
244
|
+
request?: { requestId: string; requestSecret: string },
|
|
245
|
+
): Promise<void> => {
|
|
229
246
|
if (finishingRef.current) return;
|
|
247
|
+
if (isEmailSignInChooseAccount(result)) {
|
|
248
|
+
if (!request) return;
|
|
249
|
+
setError(null);
|
|
250
|
+
setNotice(null);
|
|
251
|
+
setStep({
|
|
252
|
+
name: 'choose',
|
|
253
|
+
identifier: from,
|
|
254
|
+
requestId: request.requestId,
|
|
255
|
+
requestSecret: request.requestSecret,
|
|
256
|
+
accounts: result.accounts,
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
230
260
|
if (isSecondFactorRequired(result)) {
|
|
231
261
|
setError(null);
|
|
232
262
|
setSecondFactorCode('');
|
|
@@ -301,7 +331,7 @@ export const OxySignInPanel: React.FC<OxySignInPanelProps> = ({
|
|
|
301
331
|
requestSecret: request.requestSecret,
|
|
302
332
|
code: typed.trim(),
|
|
303
333
|
})
|
|
304
|
-
.then((result) => finish(result, request.identifier))
|
|
334
|
+
.then((result) => finish(result, request.identifier, request))
|
|
305
335
|
.catch((reason: unknown) => {
|
|
306
336
|
setCode('');
|
|
307
337
|
fail(reason);
|
|
@@ -309,6 +339,27 @@ export const OxySignInPanel: React.FC<OxySignInPanelProps> = ({
|
|
|
309
339
|
.finally(() => setPending(false));
|
|
310
340
|
};
|
|
311
341
|
|
|
342
|
+
const chooseAccount = (account: EmailSignInAccountOption) => {
|
|
343
|
+
if (step.name !== 'choose' || blocked) return;
|
|
344
|
+
const request = step;
|
|
345
|
+
const name = account.username ?? request.identifier;
|
|
346
|
+
setError(null);
|
|
347
|
+
setPending(true);
|
|
348
|
+
setChoosingId(account.id);
|
|
349
|
+
oxyServices.auth.email
|
|
350
|
+
.choose({
|
|
351
|
+
requestId: request.requestId,
|
|
352
|
+
requestSecret: request.requestSecret,
|
|
353
|
+
userId: account.id,
|
|
354
|
+
})
|
|
355
|
+
.then((result) => finish(result, name))
|
|
356
|
+
.catch(fail)
|
|
357
|
+
.finally(() => {
|
|
358
|
+
setPending(false);
|
|
359
|
+
setChoosingId(null);
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
|
|
312
363
|
const submitPassword = () => {
|
|
313
364
|
if (step.name !== 'password' || blocked) return;
|
|
314
365
|
if (!password) {
|
|
@@ -369,7 +420,7 @@ export const OxySignInPanel: React.FC<OxySignInPanelProps> = ({
|
|
|
369
420
|
try {
|
|
370
421
|
const result = await oxyServices.auth.email.collect({ requestId, requestSecret });
|
|
371
422
|
if (!('status' in result)) {
|
|
372
|
-
await finishRef.current(result, requestIdentifier);
|
|
423
|
+
await finishRef.current(result, requestIdentifier, { requestId, requestSecret });
|
|
373
424
|
return;
|
|
374
425
|
}
|
|
375
426
|
if (stopped) return;
|
|
@@ -550,6 +601,40 @@ export const OxySignInPanel: React.FC<OxySignInPanelProps> = ({
|
|
|
550
601
|
);
|
|
551
602
|
break;
|
|
552
603
|
}
|
|
604
|
+
case 'choose': {
|
|
605
|
+
const request = step;
|
|
606
|
+
form = (
|
|
607
|
+
<OxyAuthScreen className={splits ? 'md:max-w-none' : undefined}>
|
|
608
|
+
<OxyAuthScreenHeader
|
|
609
|
+
title={t('signin.emailAccounts.title')}
|
|
610
|
+
description={t('signin.emailAccounts.description', {
|
|
611
|
+
identifier: request.identifier,
|
|
612
|
+
})}
|
|
613
|
+
/>
|
|
614
|
+
<OxyEmailAccountChoice
|
|
615
|
+
accounts={request.accounts}
|
|
616
|
+
onChoose={chooseAccount}
|
|
617
|
+
pendingId={choosingId}
|
|
618
|
+
disabled={blocked}
|
|
619
|
+
/>
|
|
620
|
+
{error ? (
|
|
621
|
+
<AccountFlowNotice type="error" testID="signin-choose-error">
|
|
622
|
+
{error}
|
|
623
|
+
</AccountFlowNotice>
|
|
624
|
+
) : null}
|
|
625
|
+
{rateLimitNotice}
|
|
626
|
+
<View style={styles.links}>
|
|
627
|
+
<SubtleLink
|
|
628
|
+
label={t('signin.checkEmail.differentAccount')}
|
|
629
|
+
onPress={backToIdentifier}
|
|
630
|
+
disabled={pending}
|
|
631
|
+
testID="signin-different-account"
|
|
632
|
+
/>
|
|
633
|
+
</View>
|
|
634
|
+
</OxyAuthScreen>
|
|
635
|
+
);
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
553
638
|
case 'password': {
|
|
554
639
|
const request = step;
|
|
555
640
|
form = (
|
|
@@ -85,6 +85,8 @@ export function describeSignInError(error: unknown, t: Translate): string {
|
|
|
85
85
|
return t('signin.errors.originNotAllowed');
|
|
86
86
|
case SIGN_IN_ERROR_CODES.usernameTaken:
|
|
87
87
|
return t('signup.username.taken');
|
|
88
|
+
case SIGN_IN_ERROR_CODES.emailAccountsFull:
|
|
89
|
+
return t('signin.errors.emailAccountsFull');
|
|
88
90
|
case EMAIL_VERIFICATION_ERROR_CODES.codeInvalid:
|
|
89
91
|
return t('emailCode.errors.codeInvalid');
|
|
90
92
|
case EMAIL_VERIFICATION_ERROR_CODES.tooManyAttempts:
|