@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.
Files changed (191) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +21 -0
  3. package/MODULE.md +147 -0
  4. package/README.md +116 -0
  5. package/dist/api/authApi.d.ts +68 -0
  6. package/dist/api/authApi.d.ts.map +1 -0
  7. package/dist/api/authApi.js +90 -0
  8. package/dist/api/authApi.js.map +1 -0
  9. package/dist/api/types.d.ts +238 -0
  10. package/dist/api/types.d.ts.map +1 -0
  11. package/dist/api/types.js +14 -0
  12. package/dist/api/types.js.map +1 -0
  13. package/dist/api/urls.d.ts +41 -0
  14. package/dist/api/urls.d.ts.map +1 -0
  15. package/dist/api/urls.js +86 -0
  16. package/dist/api/urls.js.map +1 -0
  17. package/dist/flows/anonymousFlow.d.ts +33 -0
  18. package/dist/flows/anonymousFlow.d.ts.map +1 -0
  19. package/dist/flows/anonymousFlow.js +26 -0
  20. package/dist/flows/anonymousFlow.js.map +1 -0
  21. package/dist/flows/authenticatorChangeFlow.d.ts +67 -0
  22. package/dist/flows/authenticatorChangeFlow.d.ts.map +1 -0
  23. package/dist/flows/authenticatorChangeFlow.js +79 -0
  24. package/dist/flows/authenticatorChangeFlow.js.map +1 -0
  25. package/dist/flows/createFlowMachine.d.ts +55 -0
  26. package/dist/flows/createFlowMachine.d.ts.map +1 -0
  27. package/dist/flows/createFlowMachine.js +56 -0
  28. package/dist/flows/createFlowMachine.js.map +1 -0
  29. package/dist/flows/errors.d.ts +15 -0
  30. package/dist/flows/errors.d.ts.map +1 -0
  31. package/dist/flows/errors.js +17 -0
  32. package/dist/flows/errors.js.map +1 -0
  33. package/dist/flows/magicLinkFlow.d.ts +41 -0
  34. package/dist/flows/magicLinkFlow.d.ts.map +1 -0
  35. package/dist/flows/magicLinkFlow.js +29 -0
  36. package/dist/flows/magicLinkFlow.js.map +1 -0
  37. package/dist/flows/oauthFlow.d.ts +58 -0
  38. package/dist/flows/oauthFlow.d.ts.map +1 -0
  39. package/dist/flows/oauthFlow.js +53 -0
  40. package/dist/flows/oauthFlow.js.map +1 -0
  41. package/dist/flows/otpFlow.d.ts +74 -0
  42. package/dist/flows/otpFlow.d.ts.map +1 -0
  43. package/dist/flows/otpFlow.js +68 -0
  44. package/dist/flows/otpFlow.js.map +1 -0
  45. package/dist/flows/passkeyFlow.d.ts +75 -0
  46. package/dist/flows/passkeyFlow.d.ts.map +1 -0
  47. package/dist/flows/passkeyFlow.js +100 -0
  48. package/dist/flows/passkeyFlow.js.map +1 -0
  49. package/dist/flows/passwordChangeFlow.d.ts +53 -0
  50. package/dist/flows/passwordChangeFlow.d.ts.map +1 -0
  51. package/dist/flows/passwordChangeFlow.js +51 -0
  52. package/dist/flows/passwordChangeFlow.js.map +1 -0
  53. package/dist/flows/passwordLoginFlow.d.ts +62 -0
  54. package/dist/flows/passwordLoginFlow.d.ts.map +1 -0
  55. package/dist/flows/passwordLoginFlow.js +55 -0
  56. package/dist/flows/passwordLoginFlow.js.map +1 -0
  57. package/dist/flows/passwordResetFlow.d.ts +56 -0
  58. package/dist/flows/passwordResetFlow.d.ts.map +1 -0
  59. package/dist/flows/passwordResetFlow.js +57 -0
  60. package/dist/flows/passwordResetFlow.js.map +1 -0
  61. package/dist/flows/qrLoginFlow.d.ts +55 -0
  62. package/dist/flows/qrLoginFlow.d.ts.map +1 -0
  63. package/dist/flows/qrLoginFlow.js +91 -0
  64. package/dist/flows/qrLoginFlow.js.map +1 -0
  65. package/dist/flows/ssoFlow.d.ts +46 -0
  66. package/dist/flows/ssoFlow.d.ts.map +1 -0
  67. package/dist/flows/ssoFlow.js +34 -0
  68. package/dist/flows/ssoFlow.js.map +1 -0
  69. package/dist/flows/totpSetupFlow.d.ts +49 -0
  70. package/dist/flows/totpSetupFlow.d.ts.map +1 -0
  71. package/dist/flows/totpSetupFlow.js +47 -0
  72. package/dist/flows/totpSetupFlow.js.map +1 -0
  73. package/dist/flows/useFlow.d.ts +9 -0
  74. package/dist/flows/useFlow.d.ts.map +1 -0
  75. package/dist/flows/useFlow.js +11 -0
  76. package/dist/flows/useFlow.js.map +1 -0
  77. package/dist/flows/verificationFlow.d.ts +108 -0
  78. package/dist/flows/verificationFlow.d.ts.map +1 -0
  79. package/dist/flows/verificationFlow.js +195 -0
  80. package/dist/flows/verificationFlow.js.map +1 -0
  81. package/dist/headless/AuthProvider.d.ts +18 -0
  82. package/dist/headless/AuthProvider.d.ts.map +1 -0
  83. package/dist/headless/AuthProvider.js +22 -0
  84. package/dist/headless/AuthProvider.js.map +1 -0
  85. package/dist/headless/Passkey.d.ts +31 -0
  86. package/dist/headless/Passkey.d.ts.map +1 -0
  87. package/dist/headless/Passkey.js +51 -0
  88. package/dist/headless/Passkey.js.map +1 -0
  89. package/dist/headless/PasswordChange.d.ts +20 -0
  90. package/dist/headless/PasswordChange.d.ts.map +1 -0
  91. package/dist/headless/PasswordChange.js +30 -0
  92. package/dist/headless/PasswordChange.js.map +1 -0
  93. package/dist/headless/PasswordLogin.d.ts +17 -0
  94. package/dist/headless/PasswordLogin.d.ts.map +1 -0
  95. package/dist/headless/PasswordLogin.js +31 -0
  96. package/dist/headless/PasswordLogin.js.map +1 -0
  97. package/dist/headless/PasswordReset.d.ts +19 -0
  98. package/dist/headless/PasswordReset.d.ts.map +1 -0
  99. package/dist/headless/PasswordReset.js +34 -0
  100. package/dist/headless/PasswordReset.js.map +1 -0
  101. package/dist/headless/PasswordlessLogin.d.ts +28 -0
  102. package/dist/headless/PasswordlessLogin.d.ts.map +1 -0
  103. package/dist/headless/PasswordlessLogin.js +42 -0
  104. package/dist/headless/PasswordlessLogin.js.map +1 -0
  105. package/dist/headless/QrLogin.d.ts +19 -0
  106. package/dist/headless/QrLogin.d.ts.map +1 -0
  107. package/dist/headless/QrLogin.js +32 -0
  108. package/dist/headless/QrLogin.js.map +1 -0
  109. package/dist/headless/TotpSetup.d.ts +17 -0
  110. package/dist/headless/TotpSetup.d.ts.map +1 -0
  111. package/dist/headless/TotpSetup.js +26 -0
  112. package/dist/headless/TotpSetup.js.map +1 -0
  113. package/dist/headless/VerificationChallenge.d.ts +37 -0
  114. package/dist/headless/VerificationChallenge.d.ts.map +1 -0
  115. package/dist/headless/VerificationChallenge.js +40 -0
  116. package/dist/headless/VerificationChallenge.js.map +1 -0
  117. package/dist/headless/misc.d.ts +47 -0
  118. package/dist/headless/misc.d.ts.map +1 -0
  119. package/dist/headless/misc.js +84 -0
  120. package/dist/headless/misc.js.map +1 -0
  121. package/dist/i18n/keys.d.ts +34 -0
  122. package/dist/i18n/keys.d.ts.map +1 -0
  123. package/dist/i18n/keys.js +83 -0
  124. package/dist/i18n/keys.js.map +1 -0
  125. package/dist/index.d.ts +73 -0
  126. package/dist/index.d.ts.map +1 -0
  127. package/dist/index.js +48 -0
  128. package/dist/index.js.map +1 -0
  129. package/dist/model/context.d.ts +22 -0
  130. package/dist/model/context.d.ts.map +1 -0
  131. package/dist/model/context.js +34 -0
  132. package/dist/model/context.js.map +1 -0
  133. package/dist/model/mutations.d.ts +28 -0
  134. package/dist/model/mutations.d.ts.map +1 -0
  135. package/dist/model/mutations.js +108 -0
  136. package/dist/model/mutations.js.map +1 -0
  137. package/dist/model/queries.d.ts +30 -0
  138. package/dist/model/queries.d.ts.map +1 -0
  139. package/dist/model/queries.js +87 -0
  140. package/dist/model/queries.js.map +1 -0
  141. package/dist/model/queryKeys.d.ts +13 -0
  142. package/dist/model/queryKeys.d.ts.map +1 -0
  143. package/dist/model/queryKeys.js +21 -0
  144. package/dist/model/queryKeys.js.map +1 -0
  145. package/dist/model/runtime.d.ts +39 -0
  146. package/dist/model/runtime.d.ts.map +1 -0
  147. package/dist/model/runtime.js +44 -0
  148. package/dist/model/runtime.js.map +1 -0
  149. package/dist/model/session.d.ts +50 -0
  150. package/dist/model/session.d.ts.map +1 -0
  151. package/dist/model/session.js +124 -0
  152. package/dist/model/session.js.map +1 -0
  153. package/package.json +68 -0
  154. package/src/api/authApi.ts +332 -0
  155. package/src/api/types.ts +291 -0
  156. package/src/api/urls.ts +99 -0
  157. package/src/flows/anonymousFlow.ts +57 -0
  158. package/src/flows/authenticatorChangeFlow.ts +160 -0
  159. package/src/flows/createFlowMachine.ts +126 -0
  160. package/src/flows/errors.ts +29 -0
  161. package/src/flows/magicLinkFlow.ts +68 -0
  162. package/src/flows/oauthFlow.ts +114 -0
  163. package/src/flows/otpFlow.ts +156 -0
  164. package/src/flows/passkeyFlow.ts +191 -0
  165. package/src/flows/passwordChangeFlow.ts +114 -0
  166. package/src/flows/passwordLoginFlow.ts +122 -0
  167. package/src/flows/passwordResetFlow.ts +123 -0
  168. package/src/flows/qrLoginFlow.ts +158 -0
  169. package/src/flows/ssoFlow.ts +84 -0
  170. package/src/flows/totpSetupFlow.ts +96 -0
  171. package/src/flows/useFlow.ts +16 -0
  172. package/src/flows/verificationFlow.ts +341 -0
  173. package/src/headless/AuthProvider.tsx +30 -0
  174. package/src/headless/Passkey.tsx +97 -0
  175. package/src/headless/PasswordChange.tsx +46 -0
  176. package/src/headless/PasswordLogin.tsx +49 -0
  177. package/src/headless/PasswordReset.tsx +51 -0
  178. package/src/headless/PasswordlessLogin.tsx +60 -0
  179. package/src/headless/QrLogin.tsx +52 -0
  180. package/src/headless/TotpSetup.tsx +40 -0
  181. package/src/headless/VerificationChallenge.tsx +54 -0
  182. package/src/headless/misc.tsx +151 -0
  183. package/src/i18n/keys.ts +94 -0
  184. package/src/index.ts +229 -0
  185. package/src/model/context.tsx +51 -0
  186. package/src/model/mutations.ts +152 -0
  187. package/src/model/queries.ts +130 -0
  188. package/src/model/queryKeys.ts +32 -0
  189. package/src/model/runtime.ts +93 -0
  190. package/src/model/session.ts +188 -0
  191. 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":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAmE1C,MAAM,aAAa,GAAG,GAAG,CAAC;AAE1B,MAAM,UAAU,aAAa,CAAC,IAAiB;IAC7C,MAAM,OAAO,GAAG,iBAAiB,CAAW;QAC1C,EAAE,EAAE,UAAU;QACd,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI;KAClC,CAAC,CAAC;IAEH,KAAK,UAAU,WAAW,CACxB,OAAmB,EACnB,KAAa,EACb,YAAqB;QAErB,MAAM,OAAO,CAAC,GAAG,CACf,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,EACtC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,YAAY,CAAC,EACvD;YACE,OAAO,EAAE,CAAC,CAAC,EAAY,EAAE,CAAC,CAAC;gBACzB,IAAI,EAAE,UAAU;gBAChB,OAAO;gBACP,KAAK;gBACL,MAAM,EAAE,CAAC,CAAC,MAAM;aACjB,CAAC;YACF,MAAM,EAAE,CAAC,KAAK,EAAY,EAAE;gBAC1B,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;gBACrC,IAAI,SAAS,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;oBACvC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;gBAC9D,CAAC;gBACD,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;YACpE,CAAC;SACF,CACF,CAAC;IACJ,CAAC;IAED,SAAS,iBAAiB;QACxB,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;QAC7B,IACE,CAAC,CAAC,IAAI,KAAK,UAAU;YACrB,CAAC,CAAC,IAAI,KAAK,WAAW;YACtB,CAAC,CAAC,IAAI,KAAK,WAAW;YACtB,CAAC,CAAC,IAAI,KAAK,cAAc;YACzB,CAAC,CAAC,IAAI,KAAK,YAAY,EACvB,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;QAChD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,UAAU,MAAM,CAAC,YAAqB;QACzC,MAAM,EAAE,GAAG,iBAAiB,EAAE,CAAC;QAC/B,IAAI,EAAE,KAAK,IAAI;YAAE,OAAO;QACxB,MAAM,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,UAAU,UAAU,CAAC,IAAY;QACpC,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW;YAAE,OAAO;QAC5D,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;QACrC,MAAM,OAAO,CAAC,GAAG,CACf,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAC7C,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAC9C;YACE,OAAO,EAAE,CAAC,MAAM,EAAY,EAAE;gBAC5B,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,EAAY,EAAE;gBAC1B,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;gBACrC,IAAI,SAAS,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;oBACvC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;gBAC9D,CAAC;gBACD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;YACzE,CAAC;SACF,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,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;AAC7D,CAAC"}
@@ -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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"}