@stapel/auth-react 1.0.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/CHANGELOG.md +56 -0
- package/LICENSE +21 -0
- package/MODULE.md +147 -0
- package/README.md +116 -0
- package/dist/api/authApi.d.ts +68 -0
- package/dist/api/authApi.d.ts.map +1 -0
- package/dist/api/authApi.js +90 -0
- package/dist/api/authApi.js.map +1 -0
- package/dist/api/types.d.ts +238 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +14 -0
- package/dist/api/types.js.map +1 -0
- package/dist/api/urls.d.ts +41 -0
- package/dist/api/urls.d.ts.map +1 -0
- package/dist/api/urls.js +86 -0
- package/dist/api/urls.js.map +1 -0
- package/dist/flows/anonymousFlow.d.ts +33 -0
- package/dist/flows/anonymousFlow.d.ts.map +1 -0
- package/dist/flows/anonymousFlow.js +26 -0
- package/dist/flows/anonymousFlow.js.map +1 -0
- package/dist/flows/authenticatorChangeFlow.d.ts +67 -0
- package/dist/flows/authenticatorChangeFlow.d.ts.map +1 -0
- package/dist/flows/authenticatorChangeFlow.js +79 -0
- package/dist/flows/authenticatorChangeFlow.js.map +1 -0
- package/dist/flows/createFlowMachine.d.ts +55 -0
- package/dist/flows/createFlowMachine.d.ts.map +1 -0
- package/dist/flows/createFlowMachine.js +56 -0
- package/dist/flows/createFlowMachine.js.map +1 -0
- package/dist/flows/errors.d.ts +15 -0
- package/dist/flows/errors.d.ts.map +1 -0
- package/dist/flows/errors.js +17 -0
- package/dist/flows/errors.js.map +1 -0
- package/dist/flows/magicLinkFlow.d.ts +41 -0
- package/dist/flows/magicLinkFlow.d.ts.map +1 -0
- package/dist/flows/magicLinkFlow.js +29 -0
- package/dist/flows/magicLinkFlow.js.map +1 -0
- package/dist/flows/oauthFlow.d.ts +58 -0
- package/dist/flows/oauthFlow.d.ts.map +1 -0
- package/dist/flows/oauthFlow.js +53 -0
- package/dist/flows/oauthFlow.js.map +1 -0
- package/dist/flows/otpFlow.d.ts +74 -0
- package/dist/flows/otpFlow.d.ts.map +1 -0
- package/dist/flows/otpFlow.js +68 -0
- package/dist/flows/otpFlow.js.map +1 -0
- package/dist/flows/passkeyFlow.d.ts +75 -0
- package/dist/flows/passkeyFlow.d.ts.map +1 -0
- package/dist/flows/passkeyFlow.js +100 -0
- package/dist/flows/passkeyFlow.js.map +1 -0
- package/dist/flows/passwordChangeFlow.d.ts +53 -0
- package/dist/flows/passwordChangeFlow.d.ts.map +1 -0
- package/dist/flows/passwordChangeFlow.js +51 -0
- package/dist/flows/passwordChangeFlow.js.map +1 -0
- package/dist/flows/passwordLoginFlow.d.ts +62 -0
- package/dist/flows/passwordLoginFlow.d.ts.map +1 -0
- package/dist/flows/passwordLoginFlow.js +55 -0
- package/dist/flows/passwordLoginFlow.js.map +1 -0
- package/dist/flows/passwordResetFlow.d.ts +56 -0
- package/dist/flows/passwordResetFlow.d.ts.map +1 -0
- package/dist/flows/passwordResetFlow.js +57 -0
- package/dist/flows/passwordResetFlow.js.map +1 -0
- package/dist/flows/qrLoginFlow.d.ts +55 -0
- package/dist/flows/qrLoginFlow.d.ts.map +1 -0
- package/dist/flows/qrLoginFlow.js +91 -0
- package/dist/flows/qrLoginFlow.js.map +1 -0
- package/dist/flows/ssoFlow.d.ts +46 -0
- package/dist/flows/ssoFlow.d.ts.map +1 -0
- package/dist/flows/ssoFlow.js +34 -0
- package/dist/flows/ssoFlow.js.map +1 -0
- package/dist/flows/totpSetupFlow.d.ts +49 -0
- package/dist/flows/totpSetupFlow.d.ts.map +1 -0
- package/dist/flows/totpSetupFlow.js +47 -0
- package/dist/flows/totpSetupFlow.js.map +1 -0
- package/dist/flows/useFlow.d.ts +9 -0
- package/dist/flows/useFlow.d.ts.map +1 -0
- package/dist/flows/useFlow.js +11 -0
- package/dist/flows/useFlow.js.map +1 -0
- package/dist/flows/verificationFlow.d.ts +108 -0
- package/dist/flows/verificationFlow.d.ts.map +1 -0
- package/dist/flows/verificationFlow.js +195 -0
- package/dist/flows/verificationFlow.js.map +1 -0
- package/dist/headless/AuthProvider.d.ts +18 -0
- package/dist/headless/AuthProvider.d.ts.map +1 -0
- package/dist/headless/AuthProvider.js +22 -0
- package/dist/headless/AuthProvider.js.map +1 -0
- package/dist/headless/Passkey.d.ts +31 -0
- package/dist/headless/Passkey.d.ts.map +1 -0
- package/dist/headless/Passkey.js +51 -0
- package/dist/headless/Passkey.js.map +1 -0
- package/dist/headless/PasswordChange.d.ts +20 -0
- package/dist/headless/PasswordChange.d.ts.map +1 -0
- package/dist/headless/PasswordChange.js +30 -0
- package/dist/headless/PasswordChange.js.map +1 -0
- package/dist/headless/PasswordLogin.d.ts +17 -0
- package/dist/headless/PasswordLogin.d.ts.map +1 -0
- package/dist/headless/PasswordLogin.js +31 -0
- package/dist/headless/PasswordLogin.js.map +1 -0
- package/dist/headless/PasswordReset.d.ts +19 -0
- package/dist/headless/PasswordReset.d.ts.map +1 -0
- package/dist/headless/PasswordReset.js +34 -0
- package/dist/headless/PasswordReset.js.map +1 -0
- package/dist/headless/PasswordlessLogin.d.ts +28 -0
- package/dist/headless/PasswordlessLogin.d.ts.map +1 -0
- package/dist/headless/PasswordlessLogin.js +42 -0
- package/dist/headless/PasswordlessLogin.js.map +1 -0
- package/dist/headless/QrLogin.d.ts +19 -0
- package/dist/headless/QrLogin.d.ts.map +1 -0
- package/dist/headless/QrLogin.js +32 -0
- package/dist/headless/QrLogin.js.map +1 -0
- package/dist/headless/TotpSetup.d.ts +17 -0
- package/dist/headless/TotpSetup.d.ts.map +1 -0
- package/dist/headless/TotpSetup.js +26 -0
- package/dist/headless/TotpSetup.js.map +1 -0
- package/dist/headless/VerificationChallenge.d.ts +37 -0
- package/dist/headless/VerificationChallenge.d.ts.map +1 -0
- package/dist/headless/VerificationChallenge.js +40 -0
- package/dist/headless/VerificationChallenge.js.map +1 -0
- package/dist/headless/misc.d.ts +47 -0
- package/dist/headless/misc.d.ts.map +1 -0
- package/dist/headless/misc.js +84 -0
- package/dist/headless/misc.js.map +1 -0
- package/dist/i18n/keys.d.ts +34 -0
- package/dist/i18n/keys.d.ts.map +1 -0
- package/dist/i18n/keys.js +83 -0
- package/dist/i18n/keys.js.map +1 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/model/context.d.ts +22 -0
- package/dist/model/context.d.ts.map +1 -0
- package/dist/model/context.js +34 -0
- package/dist/model/context.js.map +1 -0
- package/dist/model/mutations.d.ts +28 -0
- package/dist/model/mutations.d.ts.map +1 -0
- package/dist/model/mutations.js +108 -0
- package/dist/model/mutations.js.map +1 -0
- package/dist/model/queries.d.ts +30 -0
- package/dist/model/queries.d.ts.map +1 -0
- package/dist/model/queries.js +87 -0
- package/dist/model/queries.js.map +1 -0
- package/dist/model/queryKeys.d.ts +13 -0
- package/dist/model/queryKeys.d.ts.map +1 -0
- package/dist/model/queryKeys.js +21 -0
- package/dist/model/queryKeys.js.map +1 -0
- package/dist/model/runtime.d.ts +39 -0
- package/dist/model/runtime.d.ts.map +1 -0
- package/dist/model/runtime.js +44 -0
- package/dist/model/runtime.js.map +1 -0
- package/dist/model/session.d.ts +50 -0
- package/dist/model/session.d.ts.map +1 -0
- package/dist/model/session.js +124 -0
- package/dist/model/session.js.map +1 -0
- package/package.json +68 -0
- package/src/api/authApi.ts +332 -0
- package/src/api/types.ts +291 -0
- package/src/api/urls.ts +99 -0
- package/src/flows/anonymousFlow.ts +57 -0
- package/src/flows/authenticatorChangeFlow.ts +160 -0
- package/src/flows/createFlowMachine.ts +126 -0
- package/src/flows/errors.ts +29 -0
- package/src/flows/magicLinkFlow.ts +68 -0
- package/src/flows/oauthFlow.ts +114 -0
- package/src/flows/otpFlow.ts +156 -0
- package/src/flows/passkeyFlow.ts +191 -0
- package/src/flows/passwordChangeFlow.ts +114 -0
- package/src/flows/passwordLoginFlow.ts +122 -0
- package/src/flows/passwordResetFlow.ts +123 -0
- package/src/flows/qrLoginFlow.ts +158 -0
- package/src/flows/ssoFlow.ts +84 -0
- package/src/flows/totpSetupFlow.ts +96 -0
- package/src/flows/useFlow.ts +16 -0
- package/src/flows/verificationFlow.ts +341 -0
- package/src/headless/AuthProvider.tsx +30 -0
- package/src/headless/Passkey.tsx +97 -0
- package/src/headless/PasswordChange.tsx +46 -0
- package/src/headless/PasswordLogin.tsx +49 -0
- package/src/headless/PasswordReset.tsx +51 -0
- package/src/headless/PasswordlessLogin.tsx +60 -0
- package/src/headless/QrLogin.tsx +52 -0
- package/src/headless/TotpSetup.tsx +40 -0
- package/src/headless/VerificationChallenge.tsx +54 -0
- package/src/headless/misc.tsx +151 -0
- package/src/i18n/keys.ts +94 -0
- package/src/index.ts +229 -0
- package/src/model/context.tsx +51 -0
- package/src/model/mutations.ts +152 -0
- package/src/model/queries.ts +130 -0
- package/src/model/queryKeys.ts +32 -0
- package/src/model/runtime.ts +93 -0
- package/src/model/session.ts +188 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Analytics } from "@stapel/core";
|
|
2
|
+
/**
|
|
3
|
+
* The shared flow-machine primitive — the FIRST INSTANCE of the pattern every
|
|
4
|
+
* later `@stapel/<module>-react` pair copies (frontend-standard §2, "flows/").
|
|
5
|
+
*
|
|
6
|
+
* A flow is a tiny state container whose state is a discriminated union keyed
|
|
7
|
+
* by a `step` string. Three things earn this primitive its keep across every
|
|
8
|
+
* auth journey:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Typed transitions** — `to(next)` replaces state and notifies React via
|
|
11
|
+
* `useSyncExternalStore` (see `useFlow`).
|
|
12
|
+
* 2. **Human-wait vs async steps** — a step with no pending work is just a
|
|
13
|
+
* resting state the machine waits in for user input (`to`). Async steps go
|
|
14
|
+
* through `run`, which parks the machine in a `pending` step, awaits the
|
|
15
|
+
* task, then transitions to a success/failure step. `run` never throws:
|
|
16
|
+
* rejections are folded into a state, so hosts render errors instead of
|
|
17
|
+
* catching them. `run` is also **staleness-guarded**: if a newer `to`
|
|
18
|
+
* happens while the task is in flight (double-submit, cancel, navigate,
|
|
19
|
+
* challenge expiry), the late result is dropped — it never clobbers the
|
|
20
|
+
* newer state nor runs its resolve/reject side effects.
|
|
21
|
+
* 3. **Auto-instrumentation** — every transition emits
|
|
22
|
+
* `flow.<id>.<step>` (`started`), and every `run` emits `completed` /
|
|
23
|
+
* `failed` for its pending step (analytics-standard §1.2). Funnels exist
|
|
24
|
+
* without hand-written tracking.
|
|
25
|
+
*/
|
|
26
|
+
export interface FlowStateBase {
|
|
27
|
+
readonly step: string;
|
|
28
|
+
}
|
|
29
|
+
export interface FlowMachine<S extends FlowStateBase> {
|
|
30
|
+
/** Analytics flow id — the `<id>` in `flow.<id>.<step>`. */
|
|
31
|
+
readonly id: string;
|
|
32
|
+
getState(): S;
|
|
33
|
+
subscribe(listener: () => void): () => void;
|
|
34
|
+
/** Transition to a resting/human-wait state; emits `<step>` started. */
|
|
35
|
+
to(next: S): void;
|
|
36
|
+
/**
|
|
37
|
+
* Run an async step. Parks in `pending` immediately, awaits `task`, then
|
|
38
|
+
* transitions via `resolve`/`reject`. Emits `completed`/`failed` for the
|
|
39
|
+
* pending step. Resolves once the terminal transition is applied — it does
|
|
40
|
+
* not reject.
|
|
41
|
+
*/
|
|
42
|
+
run<T>(pending: S, task: () => Promise<T>, handlers: {
|
|
43
|
+
readonly resolve: (result: T) => S;
|
|
44
|
+
readonly reject: (error: unknown) => S;
|
|
45
|
+
}): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
export interface FlowMachineOptions<S extends FlowStateBase> {
|
|
48
|
+
/** Analytics flow id (e.g. `"auth.otp"`). */
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly initial: S;
|
|
51
|
+
/** Facade for auto-instrumentation. Omit to disable tracking. */
|
|
52
|
+
readonly analytics?: Analytics | null;
|
|
53
|
+
}
|
|
54
|
+
export declare function createFlowMachine<S extends FlowStateBase>(options: FlowMachineOptions<S>): FlowMachine<S>;
|
|
55
|
+
//# sourceMappingURL=createFlowMachine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createFlowMachine.d.ts","sourceRoot":"","sources":["../../src/flows/createFlowMachine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,aAAa;IAClD,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,IAAI,CAAC,CAAC;IACd,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,wEAAwE;IACxE,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IAClB;;;;;OAKG;IACH,GAAG,CAAC,CAAC,EACH,OAAO,EAAE,CAAC,EACV,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACtB,QAAQ,EAAE;QACR,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;QACnC,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,CAAC;KACxC,GACA,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,aAAa;IACzD,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CACvC;AAED,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,aAAa,EACvD,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAC7B,WAAW,CAAC,CAAC,CAAC,CA6DhB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { trackFlowStep } from "@stapel/core";
|
|
2
|
+
export function createFlowMachine(options) {
|
|
3
|
+
const { id, analytics } = options;
|
|
4
|
+
let state = options.initial;
|
|
5
|
+
const listeners = new Set();
|
|
6
|
+
function emit(step, phase) {
|
|
7
|
+
if (analytics)
|
|
8
|
+
trackFlowStep(analytics, id, step, phase);
|
|
9
|
+
}
|
|
10
|
+
function notify() {
|
|
11
|
+
for (const listener of listeners)
|
|
12
|
+
listener();
|
|
13
|
+
}
|
|
14
|
+
// Monotonic transition counter — the staleness epoch. Every `to` bumps it; a
|
|
15
|
+
// `run` captures it after parking in `pending` and only applies its terminal
|
|
16
|
+
// transition if no newer `to` happened while the task was in flight.
|
|
17
|
+
let generation = 0;
|
|
18
|
+
function to(next) {
|
|
19
|
+
state = next;
|
|
20
|
+
generation += 1;
|
|
21
|
+
emit(next.step, "started");
|
|
22
|
+
notify();
|
|
23
|
+
}
|
|
24
|
+
async function run(pending, task, handlers) {
|
|
25
|
+
to(pending);
|
|
26
|
+
// Epoch of THIS run's pending transition. If it advances before the task
|
|
27
|
+
// settles (double-submit, cancel, navigate, expiry), the late result is
|
|
28
|
+
// stale and must NOT clobber the newer state — nor run its resolve/reject
|
|
29
|
+
// side effects. Analytics still fires so funnels stay honest.
|
|
30
|
+
const epoch = generation;
|
|
31
|
+
try {
|
|
32
|
+
const result = await task();
|
|
33
|
+
emit(pending.step, "completed");
|
|
34
|
+
if (generation === epoch)
|
|
35
|
+
to(handlers.resolve(result));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
emit(pending.step, "failed");
|
|
39
|
+
if (generation === epoch)
|
|
40
|
+
to(handlers.reject(error));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
id,
|
|
45
|
+
getState: () => state,
|
|
46
|
+
subscribe: (listener) => {
|
|
47
|
+
listeners.add(listener);
|
|
48
|
+
return () => {
|
|
49
|
+
listeners.delete(listener);
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
to,
|
|
53
|
+
run,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=createFlowMachine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createFlowMachine.js","sourceRoot":"","sources":["../../src/flows/createFlowMachine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA8D7C,MAAM,UAAU,iBAAiB,CAC/B,OAA8B;IAE9B,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAClC,IAAI,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IAC5B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;IAExC,SAAS,IAAI,CAAC,IAAY,EAAE,KAAyC;QACnE,IAAI,SAAS;YAAE,aAAa,CAAC,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3D,CAAC;IAED,SAAS,MAAM;QACb,KAAK,MAAM,QAAQ,IAAI,SAAS;YAAE,QAAQ,EAAE,CAAC;IAC/C,CAAC;IAED,6EAA6E;IAC7E,6EAA6E;IAC7E,qEAAqE;IACrE,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,SAAS,EAAE,CAAC,IAAO;QACjB,KAAK,GAAG,IAAI,CAAC;QACb,UAAU,IAAI,CAAC,CAAC;QAChB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC3B,MAAM,EAAE,CAAC;IACX,CAAC;IAED,KAAK,UAAU,GAAG,CAChB,OAAU,EACV,IAAsB,EACtB,QAGC;QAED,EAAE,CAAC,OAAO,CAAC,CAAC;QACZ,yEAAyE;QACzE,wEAAwE;QACxE,0EAA0E;QAC1E,8DAA8D;QAC9D,MAAM,KAAK,GAAG,UAAU,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YAChC,IAAI,UAAU,KAAK,KAAK;gBAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QACzD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAC7B,IAAI,UAAU,KAAK,KAAK;gBAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,OAAO;QACL,EAAE;QACF,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK;QACrB,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE;YACtB,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACxB,OAAO,GAAG,EAAE;gBACV,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC7B,CAAC,CAAC;QACJ,CAAC;QACD,EAAE;QACF,GAAG;KACJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalized error shape carried by flow error states. `code` is the backend
|
|
3
|
+
* `localizable_error` i18n key (auth-sa.md "Error reference"); `params` feed
|
|
4
|
+
* `{param}` interpolation (e.g. `retry_after_minutes`, `attempts_remaining`).
|
|
5
|
+
*/
|
|
6
|
+
export interface FlowError {
|
|
7
|
+
readonly code: string;
|
|
8
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
9
|
+
readonly status: number | undefined;
|
|
10
|
+
}
|
|
11
|
+
/** Fold any thrown value into a {@link FlowError} for a flow error state. */
|
|
12
|
+
export declare function toFlowError(error: unknown): FlowError;
|
|
13
|
+
/** Convenience predicate: did this error carry a specific backend code? */
|
|
14
|
+
export declare function isErrorCode(error: FlowError, code: string): boolean;
|
|
15
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/flows/errors.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CASrD;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { StapelApiError } from "@stapel/core";
|
|
2
|
+
/** Fold any thrown value into a {@link FlowError} for a flow error state. */
|
|
3
|
+
export function toFlowError(error) {
|
|
4
|
+
if (error instanceof StapelApiError) {
|
|
5
|
+
return { code: error.code, params: error.params, status: error.status };
|
|
6
|
+
}
|
|
7
|
+
return {
|
|
8
|
+
code: "auth.error.unknown",
|
|
9
|
+
params: {},
|
|
10
|
+
status: undefined,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** Convenience predicate: did this error carry a specific backend code? */
|
|
14
|
+
export function isErrorCode(error, code) {
|
|
15
|
+
return error.code === code;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/flows/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAa9C,6EAA6E;AAC7E,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,KAAK,YAAY,cAAc,EAAE,CAAC;QACpC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IAC1E,CAAC;IACD,OAAO;QACL,IAAI,EAAE,oBAAoB;QAC1B,MAAM,EAAE,EAAE;QACV,MAAM,EAAE,SAAS;KAClB,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,WAAW,CAAC,KAAgB,EAAE,IAAY;IACxD,OAAO,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Analytics } from "@stapel/core";
|
|
2
|
+
import type { AuthApi } from "../api/authApi.js";
|
|
3
|
+
import type { FlowMachine } from "./createFlowMachine.js";
|
|
4
|
+
import type { FlowError } from "./errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* Magic-link request (auth-sa.md §15). Only the *request* is a frontend flow:
|
|
7
|
+
* the email link points directly at the backend (`/auth/api/magic/verify/`),
|
|
8
|
+
* so there is no `/magic-login` page — the browser opens the link and the
|
|
9
|
+
* backend redirects (TOTP / conflict / success handled by the `/login` route
|
|
10
|
+
* consumers, not this flow). Always resolves `sent` on 200 regardless of
|
|
11
|
+
* whether the email exists (enumeration protection); only rate-limit /
|
|
12
|
+
* validation errors surface.
|
|
13
|
+
*/
|
|
14
|
+
export type MagicLinkState = {
|
|
15
|
+
readonly step: "idle";
|
|
16
|
+
} | {
|
|
17
|
+
readonly step: "requesting";
|
|
18
|
+
readonly email: string;
|
|
19
|
+
} | {
|
|
20
|
+
readonly step: "sent";
|
|
21
|
+
readonly email: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly step: "invalidRedirect";
|
|
24
|
+
readonly email: string;
|
|
25
|
+
} | {
|
|
26
|
+
readonly step: "error";
|
|
27
|
+
readonly email: string;
|
|
28
|
+
readonly error: FlowError;
|
|
29
|
+
};
|
|
30
|
+
export interface MagicLinkFlow {
|
|
31
|
+
readonly machine: FlowMachine<MagicLinkState>;
|
|
32
|
+
/** `redirectUrl` must be a relative path (`/...`); rejected client-side otherwise. */
|
|
33
|
+
request(email: string, redirectUrl?: string): Promise<void>;
|
|
34
|
+
reset(): void;
|
|
35
|
+
}
|
|
36
|
+
export interface MagicLinkFlowDeps {
|
|
37
|
+
readonly api: AuthApi;
|
|
38
|
+
readonly analytics?: Analytics | null;
|
|
39
|
+
}
|
|
40
|
+
export declare function createMagicLinkFlow(deps: MagicLinkFlowDeps): MagicLinkFlow;
|
|
41
|
+
//# sourceMappingURL=magicLinkFlow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"magicLinkFlow.d.ts","sourceRoot":"","sources":["../../src/flows/magicLinkFlow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,CAAC;AAElF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;IAC9C,sFAAsF;IACtF,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CACvC;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,aAAa,CA+B1E"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { validRedirectUrl } from "../api/urls.js";
|
|
2
|
+
import { createFlowMachine } from "./createFlowMachine.js";
|
|
3
|
+
import { toFlowError } from "./errors.js";
|
|
4
|
+
export function createMagicLinkFlow(deps) {
|
|
5
|
+
const machine = createFlowMachine({
|
|
6
|
+
id: "auth.magic_link",
|
|
7
|
+
initial: { step: "idle" },
|
|
8
|
+
analytics: deps.analytics ?? null,
|
|
9
|
+
});
|
|
10
|
+
async function request(email, redirectUrl) {
|
|
11
|
+
if (redirectUrl !== undefined && validRedirectUrl(redirectUrl) === null) {
|
|
12
|
+
machine.to({ step: "invalidRedirect", email });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
await machine.run({ step: "requesting", email }, () => deps.api.magicRequest(email, redirectUrl), {
|
|
16
|
+
resolve: () => ({ step: "sent", email }),
|
|
17
|
+
reject: (error) => ({
|
|
18
|
+
step: "error",
|
|
19
|
+
email,
|
|
20
|
+
error: toFlowError(error),
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function reset() {
|
|
25
|
+
machine.to({ step: "idle" });
|
|
26
|
+
}
|
|
27
|
+
return { machine, request, reset };
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=magicLinkFlow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"magicLinkFlow.js","sourceRoot":"","sources":["../../src/flows/magicLinkFlow.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA+B1C,MAAM,UAAU,mBAAmB,CAAC,IAAuB;IACzD,MAAM,OAAO,GAAG,iBAAiB,CAAiB;QAChD,EAAE,EAAE,iBAAiB;QACrB,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI;KAClC,CAAC,CAAC;IAEH,KAAK,UAAU,OAAO,CAAC,KAAa,EAAE,WAAoB;QACxD,IAAI,WAAW,KAAK,SAAS,IAAI,gBAAgB,CAAC,WAAW,CAAC,KAAK,IAAI,EAAE,CAAC;YACxE,OAAO,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,CAAC;YAC/C,OAAO;QACT,CAAC;QACD,MAAM,OAAO,CAAC,GAAG,CACf,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,EAC7B,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,EAAE,WAAW,CAAC,EAC/C;YACE,OAAO,EAAE,GAAmB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;YACxD,MAAM,EAAE,CAAC,KAAK,EAAkB,EAAE,CAAC,CAAC;gBAClC,IAAI,EAAE,OAAO;gBACb,KAAK;gBACL,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC;aAC1B,CAAC;SACH,CACF,CAAC;IACJ,CAAC;IAED,SAAS,KAAK;QACZ,OAAO,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Analytics } from "@stapel/core";
|
|
2
|
+
import type { AuthApi } from "../api/authApi.js";
|
|
3
|
+
import type { AuthResponse } from "../api/types.js";
|
|
4
|
+
import type { FlowMachine } from "./createFlowMachine.js";
|
|
5
|
+
import type { FlowError } from "./errors.js";
|
|
6
|
+
/**
|
|
7
|
+
* OAuth client-side token exchange (auth-sa.md §7 option B). For the
|
|
8
|
+
* server-side redirect (option A, recommended for web) use
|
|
9
|
+
* `authUrls(base).oauthAuthorize(provider, redirectUri)` — no flow needed, the
|
|
10
|
+
* backend sets cookies on return.
|
|
11
|
+
*
|
|
12
|
+
* The exchange can answer with the same TOTP `oneOf` union as password login
|
|
13
|
+
* (only when `OAUTH_STEP_UP` is enabled server-side), so this flow carries the
|
|
14
|
+
* identical `totpRequired` branch — completed via the same
|
|
15
|
+
* `/totp/challenge/verify/` endpoint.
|
|
16
|
+
*/
|
|
17
|
+
export type OAuthState = {
|
|
18
|
+
readonly step: "idle";
|
|
19
|
+
} | {
|
|
20
|
+
readonly step: "exchanging";
|
|
21
|
+
readonly provider: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly step: "totpRequired";
|
|
24
|
+
readonly challengeToken: string;
|
|
25
|
+
readonly expiresIn: number;
|
|
26
|
+
} | {
|
|
27
|
+
readonly step: "verifyingTotp";
|
|
28
|
+
readonly challengeToken: string;
|
|
29
|
+
readonly expiresIn: number;
|
|
30
|
+
} | {
|
|
31
|
+
readonly step: "authenticated";
|
|
32
|
+
readonly result: AuthResponse;
|
|
33
|
+
} | {
|
|
34
|
+
readonly step: "error";
|
|
35
|
+
readonly provider: string;
|
|
36
|
+
readonly error: FlowError;
|
|
37
|
+
} | {
|
|
38
|
+
readonly step: "totpError";
|
|
39
|
+
readonly challengeToken: string;
|
|
40
|
+
readonly expiresIn: number;
|
|
41
|
+
readonly error: FlowError;
|
|
42
|
+
};
|
|
43
|
+
export interface OAuthFlow {
|
|
44
|
+
readonly machine: FlowMachine<OAuthState>;
|
|
45
|
+
exchange(provider: string, accessToken: string): Promise<void>;
|
|
46
|
+
submitTotp(proof: {
|
|
47
|
+
code?: string;
|
|
48
|
+
backup_code?: string;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
reset(): void;
|
|
51
|
+
}
|
|
52
|
+
export interface OAuthFlowDeps {
|
|
53
|
+
readonly api: AuthApi;
|
|
54
|
+
readonly analytics?: Analytics | null;
|
|
55
|
+
readonly onAuthenticated?: (result: AuthResponse) => void;
|
|
56
|
+
}
|
|
57
|
+
export declare function createOAuthFlow(deps: OAuthFlowDeps): OAuthFlow;
|
|
58
|
+
//# sourceMappingURL=oauthFlow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthFlow.d.ts","sourceRoot":"","sources":["../../src/flows/oauthFlow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;;;GAUG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC1D;IACE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/F;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACjE;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GAChF;IACE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC3B,CAAC;AAEN,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,UAAU,CAAC,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;CAC3D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,CA8D9D"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { isTotpChallenge } from "../api/types.js";
|
|
2
|
+
import { createFlowMachine } from "./createFlowMachine.js";
|
|
3
|
+
import { toFlowError } from "./errors.js";
|
|
4
|
+
export function createOAuthFlow(deps) {
|
|
5
|
+
const machine = createFlowMachine({
|
|
6
|
+
id: "auth.oauth",
|
|
7
|
+
initial: { step: "idle" },
|
|
8
|
+
analytics: deps.analytics ?? null,
|
|
9
|
+
});
|
|
10
|
+
async function exchange(provider, accessToken) {
|
|
11
|
+
await machine.run({ step: "exchanging", provider }, () => deps.api.oauthLogin(provider, accessToken), {
|
|
12
|
+
resolve: (r) => {
|
|
13
|
+
if (isTotpChallenge(r)) {
|
|
14
|
+
return {
|
|
15
|
+
step: "totpRequired",
|
|
16
|
+
challengeToken: r.challenge_token,
|
|
17
|
+
expiresIn: r.expires_in,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
deps.onAuthenticated?.(r);
|
|
21
|
+
return { step: "authenticated", result: r };
|
|
22
|
+
},
|
|
23
|
+
reject: (error) => ({
|
|
24
|
+
step: "error",
|
|
25
|
+
provider,
|
|
26
|
+
error: toFlowError(error),
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function submitTotp(proof) {
|
|
31
|
+
const s = machine.getState();
|
|
32
|
+
if (s.step !== "totpRequired" && s.step !== "totpError")
|
|
33
|
+
return;
|
|
34
|
+
const { challengeToken, expiresIn } = s;
|
|
35
|
+
await machine.run({ step: "verifyingTotp", challengeToken, expiresIn }, () => deps.api.totpChallengeVerify(challengeToken, proof), {
|
|
36
|
+
resolve: (result) => {
|
|
37
|
+
deps.onAuthenticated?.(result);
|
|
38
|
+
return { step: "authenticated", result };
|
|
39
|
+
},
|
|
40
|
+
reject: (error) => ({
|
|
41
|
+
step: "totpError",
|
|
42
|
+
challengeToken,
|
|
43
|
+
expiresIn,
|
|
44
|
+
error: toFlowError(error),
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function reset() {
|
|
49
|
+
machine.to({ step: "idle" });
|
|
50
|
+
}
|
|
51
|
+
return { machine, exchange, submitTotp, reset };
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=oauthFlow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthFlow.js","sourceRoot":"","sources":["../../src/flows/oauthFlow.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA6C1C,MAAM,UAAU,eAAe,CAAC,IAAmB;IACjD,MAAM,OAAO,GAAG,iBAAiB,CAAa;QAC5C,EAAE,EAAE,YAAY;QAChB,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI;KAClC,CAAC,CAAC;IAEH,KAAK,UAAU,QAAQ,CAAC,QAAgB,EAAE,WAAmB;QAC3D,MAAM,OAAO,CAAC,GAAG,CACf,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,EAChC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC,EAChD;YACE,OAAO,EAAE,CAAC,CAAC,EAAc,EAAE;gBACzB,IAAI,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;oBACvB,OAAO;wBACL,IAAI,EAAE,cAAc;wBACpB,cAAc,EAAE,CAAC,CAAC,eAAe;wBACjC,SAAS,EAAE,CAAC,CAAC,UAAU;qBACxB,CAAC;gBACJ,CAAC;gBACD,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,CAAC;gBAC1B,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YAC9C,CAAC;YACD,MAAM,EAAE,CAAC,KAAK,EAAc,EAAE,CAAC,CAAC;gBAC9B,IAAI,EAAE,OAAO;gBACb,QAAQ;gBACR,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC;aAC1B,CAAC;SACH,CACF,CAAC;IACJ,CAAC;IAED,KAAK,UAAU,UAAU,CAAC,KAGzB;QACC,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,CAAC,IAAI,KAAK,cAAc,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW;YAAE,OAAO;QAChE,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,OAAO,CAAC,GAAG,CACf,EAAE,IAAI,EAAE,eAAe,EAAE,cAAc,EAAE,SAAS,EAAE,EACpD,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC,cAAc,EAAE,KAAK,CAAC,EACzD;YACE,OAAO,EAAE,CAAC,MAAM,EAAc,EAAE;gBAC9B,IAAI,CAAC,eAAe,EAAE,CAAC,MAAM,CAAC,CAAC;gBAC/B,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC;YAC3C,CAAC;YACD,MAAM,EAAE,CAAC,KAAK,EAAc,EAAE,CAAC,CAAC;gBAC9B,IAAI,EAAE,WAAW;gBACjB,cAAc;gBACd,SAAS;gBACT,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC;aAC1B,CAAC;SACH,CACF,CAAC;IACJ,CAAC;IAED,SAAS,KAAK;QACZ,OAAO,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Analytics } from "@stapel/core";
|
|
2
|
+
import type { AuthApi } from "../api/authApi.js";
|
|
3
|
+
import type { AuthResponse, OtpChannel } from "../api/types.js";
|
|
4
|
+
import type { FlowMachine } from "./createFlowMachine.js";
|
|
5
|
+
import type { FlowError } from "./errors.js";
|
|
6
|
+
/**
|
|
7
|
+
* Email / Phone OTP passwordless login (auth-sa.md §1–2). One machine serves
|
|
8
|
+
* both channels — the only difference is the endpoint, carried in `channel`.
|
|
9
|
+
*
|
|
10
|
+
* Steps:
|
|
11
|
+
* - `idle` → nothing requested yet
|
|
12
|
+
* - `requesting` → POST /<channel>/request/ in flight
|
|
13
|
+
* - `codeSent` → human-wait: enter the code (or resend)
|
|
14
|
+
* - `verifying` → POST /<channel>/verify/ in flight
|
|
15
|
+
* - `authenticated`→ terminal success (AuthResponse)
|
|
16
|
+
* - `requestError` → request failed; retry
|
|
17
|
+
* - `codeError` → wrong/expired code; human-wait, retry or resend
|
|
18
|
+
* - `locked` → 423 lockout with `retry_after_minutes`
|
|
19
|
+
*/
|
|
20
|
+
export type OtpState = {
|
|
21
|
+
readonly step: "idle";
|
|
22
|
+
} | {
|
|
23
|
+
readonly step: "requesting";
|
|
24
|
+
readonly channel: OtpChannel;
|
|
25
|
+
readonly value: string;
|
|
26
|
+
} | {
|
|
27
|
+
readonly step: "codeSent";
|
|
28
|
+
readonly channel: OtpChannel;
|
|
29
|
+
readonly value: string;
|
|
30
|
+
readonly target: string;
|
|
31
|
+
} | {
|
|
32
|
+
readonly step: "verifying";
|
|
33
|
+
readonly channel: OtpChannel;
|
|
34
|
+
readonly value: string;
|
|
35
|
+
readonly target: string;
|
|
36
|
+
} | {
|
|
37
|
+
readonly step: "authenticated";
|
|
38
|
+
readonly result: AuthResponse;
|
|
39
|
+
} | {
|
|
40
|
+
readonly step: "requestError";
|
|
41
|
+
readonly channel: OtpChannel;
|
|
42
|
+
readonly value: string;
|
|
43
|
+
readonly error: FlowError;
|
|
44
|
+
} | {
|
|
45
|
+
readonly step: "codeError";
|
|
46
|
+
readonly channel: OtpChannel;
|
|
47
|
+
readonly value: string;
|
|
48
|
+
readonly target: string;
|
|
49
|
+
readonly error: FlowError;
|
|
50
|
+
} | {
|
|
51
|
+
readonly step: "locked";
|
|
52
|
+
readonly channel: OtpChannel;
|
|
53
|
+
readonly value: string;
|
|
54
|
+
readonly error: FlowError;
|
|
55
|
+
};
|
|
56
|
+
export interface OtpFlow {
|
|
57
|
+
readonly machine: FlowMachine<OtpState>;
|
|
58
|
+
/** Request a code for the identifier. `captchaToken` when the backend needs it. */
|
|
59
|
+
requestCode(channel: OtpChannel, value: string, captchaToken?: string): Promise<void>;
|
|
60
|
+
/** Resend the code for the current identifier (respect the 30 s rate limit). */
|
|
61
|
+
resend(captchaToken?: string): Promise<void>;
|
|
62
|
+
/** Verify the entered code. */
|
|
63
|
+
submitCode(code: string): Promise<void>;
|
|
64
|
+
/** Return to `idle`. */
|
|
65
|
+
reset(): void;
|
|
66
|
+
}
|
|
67
|
+
export interface OtpFlowDeps {
|
|
68
|
+
readonly api: AuthApi;
|
|
69
|
+
readonly analytics?: Analytics | null;
|
|
70
|
+
/** Called with the session-bearing response on success (token persistence). */
|
|
71
|
+
readonly onAuthenticated?: (result: AuthResponse) => void;
|
|
72
|
+
}
|
|
73
|
+
export declare function createOtpFlow(deps: OtpFlowDeps): OtpFlow;
|
|
74
|
+
//# sourceMappingURL=otpFlow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otpFlow.d.ts","sourceRoot":"","sources":["../../src/flows/otpFlow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,QAAQ,GAChB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACrF;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7G;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACjE;IACE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC3B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC3B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC3B,CAAC;AAEN,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IACxC,mFAAmF;IACnF,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtF,gFAAgF;IAChF,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,+BAA+B;IAC/B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,wBAAwB;IACxB,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACtC,+EAA+E;IAC/E,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;CAC3D;AAID,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAiFxD"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createFlowMachine } from "./createFlowMachine.js";
|
|
2
|
+
import { toFlowError } from "./errors.js";
|
|
3
|
+
const LOCKED_STATUS = 423;
|
|
4
|
+
export function createOtpFlow(deps) {
|
|
5
|
+
const machine = createFlowMachine({
|
|
6
|
+
id: "auth.otp",
|
|
7
|
+
initial: { step: "idle" },
|
|
8
|
+
analytics: deps.analytics ?? null,
|
|
9
|
+
});
|
|
10
|
+
async function requestCode(channel, value, captchaToken) {
|
|
11
|
+
await machine.run({ step: "requesting", channel, value }, () => deps.api.otpRequest(channel, value, captchaToken), {
|
|
12
|
+
resolve: (r) => ({
|
|
13
|
+
step: "codeSent",
|
|
14
|
+
channel,
|
|
15
|
+
value,
|
|
16
|
+
target: r.target,
|
|
17
|
+
}),
|
|
18
|
+
reject: (error) => {
|
|
19
|
+
const flowError = toFlowError(error);
|
|
20
|
+
if (flowError.status === LOCKED_STATUS) {
|
|
21
|
+
return { step: "locked", channel, value, error: flowError };
|
|
22
|
+
}
|
|
23
|
+
return { step: "requestError", channel, value, error: flowError };
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function currentIdentifier() {
|
|
28
|
+
const s = machine.getState();
|
|
29
|
+
if (s.step === "codeSent" ||
|
|
30
|
+
s.step === "verifying" ||
|
|
31
|
+
s.step === "codeError" ||
|
|
32
|
+
s.step === "requestError" ||
|
|
33
|
+
s.step === "requesting") {
|
|
34
|
+
return { channel: s.channel, value: s.value };
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
async function resend(captchaToken) {
|
|
39
|
+
const id = currentIdentifier();
|
|
40
|
+
if (id === null)
|
|
41
|
+
return;
|
|
42
|
+
await requestCode(id.channel, id.value, captchaToken);
|
|
43
|
+
}
|
|
44
|
+
async function submitCode(code) {
|
|
45
|
+
const s = machine.getState();
|
|
46
|
+
if (s.step !== "codeSent" && s.step !== "codeError")
|
|
47
|
+
return;
|
|
48
|
+
const { channel, value, target } = s;
|
|
49
|
+
await machine.run({ step: "verifying", channel, value, target }, () => deps.api.otpVerify(channel, value, code), {
|
|
50
|
+
resolve: (result) => {
|
|
51
|
+
deps.onAuthenticated?.(result);
|
|
52
|
+
return { step: "authenticated", result };
|
|
53
|
+
},
|
|
54
|
+
reject: (error) => {
|
|
55
|
+
const flowError = toFlowError(error);
|
|
56
|
+
if (flowError.status === LOCKED_STATUS) {
|
|
57
|
+
return { step: "locked", channel, value, error: flowError };
|
|
58
|
+
}
|
|
59
|
+
return { step: "codeError", channel, value, target, error: flowError };
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function reset() {
|
|
64
|
+
machine.to({ step: "idle" });
|
|
65
|
+
}
|
|
66
|
+
return { machine, requestCode, resend, submitCode, reset };
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=otpFlow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otpFlow.js","sourceRoot":"","sources":["../../src/flows/otpFlow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Analytics } from "@stapel/core";
|
|
2
|
+
import type { AuthApi } from "../api/authApi.js";
|
|
3
|
+
import type { AuthResponse, Passkey } from "../api/types.js";
|
|
4
|
+
import type { FlowMachine } from "./createFlowMachine.js";
|
|
5
|
+
import type { FlowError } from "./errors.js";
|
|
6
|
+
/**
|
|
7
|
+
* Passkeys / WebAuthn (auth-sa.md §17). FLOW-COMPLETE, WEBAUTHN BINDING IS A
|
|
8
|
+
* THIN TODO (see MODULE.md): both machines model the full begin→ceremony→
|
|
9
|
+
* complete journey and surface the server `options`. The single browser step —
|
|
10
|
+
* `navigator.credentials.create()/get()` — is either injected via
|
|
11
|
+
* `webauthn*` deps (auto-driven) or performed by the host, which then calls
|
|
12
|
+
* `submitCredential`. No heuristic "no credentials" probing (auth-sa.md §19.6).
|
|
13
|
+
*/
|
|
14
|
+
export type PasskeyRegisterState = {
|
|
15
|
+
readonly step: "idle";
|
|
16
|
+
} | {
|
|
17
|
+
readonly step: "beginning";
|
|
18
|
+
} | {
|
|
19
|
+
readonly step: "awaitingCredential";
|
|
20
|
+
readonly options: Record<string, unknown>;
|
|
21
|
+
} | {
|
|
22
|
+
readonly step: "completing";
|
|
23
|
+
} | {
|
|
24
|
+
readonly step: "registered";
|
|
25
|
+
readonly passkey: Passkey;
|
|
26
|
+
} | {
|
|
27
|
+
readonly step: "error";
|
|
28
|
+
readonly error: FlowError;
|
|
29
|
+
};
|
|
30
|
+
export interface PasskeyRegistrationFlow {
|
|
31
|
+
readonly machine: FlowMachine<PasskeyRegisterState>;
|
|
32
|
+
begin(deviceName?: string): Promise<void>;
|
|
33
|
+
submitCredential(credential: unknown): Promise<void>;
|
|
34
|
+
reset(): void;
|
|
35
|
+
}
|
|
36
|
+
export interface PasskeyRegistrationFlowDeps {
|
|
37
|
+
readonly api: AuthApi;
|
|
38
|
+
readonly analytics?: Analytics | null;
|
|
39
|
+
/** THIN WebAuthn binding: `navigator.credentials.create({ publicKey })`. */
|
|
40
|
+
readonly webauthnCreate?: (options: Record<string, unknown>) => Promise<unknown>;
|
|
41
|
+
}
|
|
42
|
+
export declare function createPasskeyRegistrationFlow(deps: PasskeyRegistrationFlowDeps): PasskeyRegistrationFlow;
|
|
43
|
+
export type PasskeyLoginState = {
|
|
44
|
+
readonly step: "idle";
|
|
45
|
+
} | {
|
|
46
|
+
readonly step: "beginning";
|
|
47
|
+
} | {
|
|
48
|
+
readonly step: "awaitingAssertion";
|
|
49
|
+
readonly sessionKey: string;
|
|
50
|
+
readonly options: Record<string, unknown>;
|
|
51
|
+
} | {
|
|
52
|
+
readonly step: "completing";
|
|
53
|
+
readonly sessionKey: string;
|
|
54
|
+
} | {
|
|
55
|
+
readonly step: "authenticated";
|
|
56
|
+
readonly result: AuthResponse;
|
|
57
|
+
} | {
|
|
58
|
+
readonly step: "error";
|
|
59
|
+
readonly error: FlowError;
|
|
60
|
+
};
|
|
61
|
+
export interface PasskeyLoginFlow {
|
|
62
|
+
readonly machine: FlowMachine<PasskeyLoginState>;
|
|
63
|
+
begin(email?: string): Promise<void>;
|
|
64
|
+
submitAssertion(credential: unknown): Promise<void>;
|
|
65
|
+
reset(): void;
|
|
66
|
+
}
|
|
67
|
+
export interface PasskeyLoginFlowDeps {
|
|
68
|
+
readonly api: AuthApi;
|
|
69
|
+
readonly analytics?: Analytics | null;
|
|
70
|
+
readonly onAuthenticated?: (result: AuthResponse) => void;
|
|
71
|
+
/** THIN WebAuthn binding: `navigator.credentials.get({ publicKey })`. */
|
|
72
|
+
readonly webauthnGet?: (options: Record<string, unknown>) => Promise<unknown>;
|
|
73
|
+
}
|
|
74
|
+
export declare function createPasskeyLoginFlow(deps: PasskeyLoginFlowDeps): PasskeyLoginFlow;
|
|
75
|
+
//# sourceMappingURL=passkeyFlow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"passkeyFlow.d.ts","sourceRoot":"","sources":["../../src/flows/passkeyFlow.ts"],"names":[],"mappings":"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"}
|