@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,332 @@
1
+ import type { StapelClient, StapelRequestOptions } from "@stapel/core";
2
+ import type {
3
+ AuthResponse,
4
+ AuditPage,
5
+ AuthSession,
6
+ Capabilities,
7
+ ChangeOldVerifiedResponse,
8
+ DelayedChangeInitiatedResponse,
9
+ DelayedChangeStatus,
10
+ LoginResponse,
11
+ OtpChannel,
12
+ OtpRequestResponse,
13
+ Passkey,
14
+ PasskeyAuthenticateBeginResponse,
15
+ PasskeyRegisterBeginResponse,
16
+ PasswordMethods,
17
+ QrGenerateResponse,
18
+ QrStatusResponse,
19
+ QrType,
20
+ RefreshResponse,
21
+ SecurityStatus,
22
+ SsoLookupResponse,
23
+ StapelUser,
24
+ StatusResponse,
25
+ TotpDisableRequest,
26
+ TotpSetupConfirmResponse,
27
+ TotpSetupResponse,
28
+ VerificationCompleteResponse,
29
+ VerificationEnvelope,
30
+ VerificationFactorId,
31
+ VerificationInitiateResponse,
32
+ } from "./types.js";
33
+
34
+ /**
35
+ * CSRF rule for cookie-authenticated browser clients (auth-sa.md §"CSRF"):
36
+ * the simplest SPA rule is to always send `X-Requested-With: XMLHttpRequest`
37
+ * on mutating requests. Header-token clients are exempt but it is harmless
38
+ * for them, so we send it on every mutation.
39
+ */
40
+ const CSRF_HEADERS: Record<string, string> = {
41
+ "X-Requested-With": "XMLHttpRequest",
42
+ };
43
+
44
+ function mutating(
45
+ options?: Omit<StapelRequestOptions, "method" | "body">
46
+ ): Omit<StapelRequestOptions, "method" | "body"> {
47
+ return {
48
+ ...options,
49
+ headers: { ...CSRF_HEADERS, ...options?.headers },
50
+ };
51
+ }
52
+
53
+ /**
54
+ * The typed auth surface. One method per auth-sa.md endpoint that a JS client
55
+ * may call. Browser-redirect endpoints (OAuth authorize, SSO login, QR scan,
56
+ * magic-link verify) are intentionally absent — see `authUrls`.
57
+ */
58
+ export interface AuthApi {
59
+ readonly client: StapelClient;
60
+
61
+ // Capabilities & identity
62
+ capabilities(): Promise<Capabilities>;
63
+ me(): Promise<StapelUser>;
64
+ logout(): Promise<StatusResponse>;
65
+
66
+ // Email / Phone OTP (auth-sa.md §1–2)
67
+ otpRequest(channel: OtpChannel, value: string, captchaToken?: string): Promise<OtpRequestResponse>;
68
+ otpVerify(channel: OtpChannel, value: string, code: string): Promise<AuthResponse>;
69
+
70
+ // Password (auth-sa.md §3–5)
71
+ passwordLogin(login: string, password: string): Promise<LoginResponse>;
72
+ passwordMethods(): Promise<PasswordMethods>;
73
+ passwordChange(oldPassword: string, newPassword: string): Promise<StatusResponse>;
74
+ passwordChangeOtpRequest(method: OtpChannel): Promise<OtpRequestResponse>;
75
+ passwordChangeOtpVerify(method: OtpChannel, code: string, newPassword: string): Promise<StatusResponse>;
76
+ passwordResetRequest(channel: OtpChannel, value: string): Promise<OtpRequestResponse>;
77
+ passwordResetVerify(channel: OtpChannel, value: string, code: string, newPassword: string): Promise<AuthResponse>;
78
+
79
+ // Anonymous (auth-sa.md §6)
80
+ anonymous(deviceId?: string): Promise<AuthResponse>;
81
+
82
+ // OAuth (auth-sa.md §7, option B)
83
+ oauthLogin(provider: string, accessToken: string): Promise<LoginResponse>;
84
+
85
+ // TOTP (auth-sa.md §11)
86
+ totpChallengeVerify(challengeToken: string, proof: { code?: string; backup_code?: string }): Promise<AuthResponse>;
87
+ totpSetup(): Promise<TotpSetupResponse>;
88
+ totpSetupConfirm(code: string): Promise<TotpSetupConfirmResponse>;
89
+ totpDisable(request: TotpDisableRequest): Promise<StatusResponse>;
90
+ totpDisableOtpRequest(): Promise<OtpRequestResponse>;
91
+
92
+ // Verification / step-up factor flow (auth-sa.md §11)
93
+ verificationGet(challengeId: string): Promise<VerificationEnvelope>;
94
+ verificationInitiate(challengeId: string, factor: VerificationFactorId): Promise<VerificationInitiateResponse>;
95
+ verificationComplete(challengeId: string, body: Record<string, unknown>): Promise<VerificationCompleteResponse>;
96
+
97
+ // Security status (auth-sa.md §10)
98
+ securityStatus(): Promise<SecurityStatus>;
99
+
100
+ // Sessions (auth-sa.md §12)
101
+ sessions(): Promise<readonly AuthSession[]>;
102
+ confirmSession(id: string): Promise<StatusResponse>;
103
+ revokeSession(id: string): Promise<StatusResponse>;
104
+ revokeOtherSessions(): Promise<StatusResponse>;
105
+
106
+ // Token refresh (auth-sa.md §13)
107
+ tokenRefresh(refresh?: string): Promise<RefreshResponse>;
108
+
109
+ // QR (auth-sa.md §8)
110
+ qrGenerate(type: QrType, redirectUrl: string, allowUnauthenticatedScanner?: boolean): Promise<QrGenerateResponse>;
111
+ qrStatus(key: string): Promise<QrStatusResponse>;
112
+ qrConfirm(key: string): Promise<StatusResponse>;
113
+ qrReject(key: string): Promise<StatusResponse>;
114
+
115
+ // Magic links (auth-sa.md §15)
116
+ magicRequest(email: string, redirectUrl?: string): Promise<StatusResponse>;
117
+
118
+ // Passkeys (auth-sa.md §17)
119
+ passkeys(): Promise<readonly Passkey[]>;
120
+ passkeyRegisterBegin(): Promise<PasskeyRegisterBeginResponse>;
121
+ passkeyRegisterComplete(credential: unknown, deviceName?: string): Promise<Passkey>;
122
+ passkeyAuthenticateBegin(email?: string): Promise<PasskeyAuthenticateBeginResponse>;
123
+ passkeyAuthenticateComplete(sessionKey: string, credential: unknown): Promise<AuthResponse>;
124
+ passkeyRemove(id: string): Promise<void>;
125
+
126
+ // Authenticator change (auth-sa.md §9)
127
+ changeInstantRequestOld(channel: OtpChannel): Promise<OtpRequestResponse>;
128
+ changeInstantVerifyOld(channel: OtpChannel, code: string): Promise<ChangeOldVerifiedResponse>;
129
+ changeInstantRequestNew(channel: OtpChannel, value: string, changeToken: string): Promise<OtpRequestResponse>;
130
+ changeInstantVerifyNew(channel: OtpChannel, value: string, code: string, changeToken: string): Promise<AuthResponse>;
131
+ changeDelayedInitiate(channel: OtpChannel, value: string): Promise<DelayedChangeInitiatedResponse>;
132
+ changeDelayedStatus(channel: OtpChannel): Promise<DelayedChangeStatus>;
133
+ changeDelayedCancel(channel: OtpChannel, changeRequestId: string): Promise<StatusResponse>;
134
+
135
+ // SSO (auth-sa.md §18)
136
+ ssoLookup(domain: string): Promise<SsoLookupResponse>;
137
+
138
+ // Audit log (auth-sa.md §16)
139
+ auditLog(page?: number): Promise<AuditPage>;
140
+ }
141
+
142
+ /**
143
+ * Build the auth API bound to an injected {@link StapelClient} (per-module
144
+ * override from `StapelProvider`, the fork-resolution seam of §7.2). All
145
+ * mutations carry the CSRF header.
146
+ */
147
+ export function createAuthApi(client: StapelClient): AuthApi {
148
+ return {
149
+ client,
150
+
151
+ capabilities: () => client.get("/capabilities/"),
152
+ me: () => client.get("/me/"),
153
+ logout: () => client.post("/logout/", undefined, mutating()),
154
+
155
+ otpRequest: (channel, value, captchaToken) =>
156
+ client.post(
157
+ `/${channel}/request/`,
158
+ captchaToken === undefined
159
+ ? { [channel]: value }
160
+ : { [channel]: value, captcha_token: captchaToken },
161
+ mutating()
162
+ ),
163
+ otpVerify: (channel, value, code) =>
164
+ client.post(`/${channel}/verify/`, { [channel]: value, code }, mutating()),
165
+
166
+ passwordLogin: (login, password) =>
167
+ client.post("/password/login/", { login, password }, mutating()),
168
+ passwordMethods: () => client.get("/password/methods/"),
169
+ passwordChange: (oldPassword, newPassword) =>
170
+ client.post(
171
+ "/password/change/",
172
+ { old_password: oldPassword, new_password: newPassword },
173
+ mutating()
174
+ ),
175
+ passwordChangeOtpRequest: (method) =>
176
+ client.post("/password/change/otp/request/", { method }, mutating()),
177
+ passwordChangeOtpVerify: (method, code, newPassword) =>
178
+ client.post(
179
+ "/password/change/otp/verify/",
180
+ { method, code, new_password: newPassword },
181
+ mutating()
182
+ ),
183
+ passwordResetRequest: (channel, value) =>
184
+ client.post(
185
+ `/password/reset/${channel}/request/`,
186
+ { [channel]: value },
187
+ mutating()
188
+ ),
189
+ passwordResetVerify: (channel, value, code, newPassword) =>
190
+ client.post(
191
+ `/password/reset/${channel}/verify/`,
192
+ { [channel]: value, code, new_password: newPassword },
193
+ mutating()
194
+ ),
195
+
196
+ anonymous: (deviceId) =>
197
+ client.post(
198
+ "/anonymous/",
199
+ deviceId === undefined ? {} : { device_id: deviceId },
200
+ mutating()
201
+ ),
202
+
203
+ oauthLogin: (provider, accessToken) =>
204
+ client.post(
205
+ "/oauth/login/",
206
+ { provider, access_token: accessToken },
207
+ mutating()
208
+ ),
209
+
210
+ totpChallengeVerify: (challengeToken, proof) =>
211
+ client.post(
212
+ "/totp/challenge/verify/",
213
+ { challenge_token: challengeToken, ...proof },
214
+ mutating()
215
+ ),
216
+ totpSetup: () => client.post("/totp/setup/", undefined, mutating()),
217
+ totpSetupConfirm: (code) =>
218
+ client.post("/totp/setup/confirm/", { code }, mutating()),
219
+ totpDisable: (request) => client.post("/totp/disable/", request, mutating()),
220
+ totpDisableOtpRequest: () =>
221
+ client.post("/totp/disable-otp/request/", undefined, mutating()),
222
+
223
+ verificationGet: (challengeId) =>
224
+ client.get(`/verification/${challengeId}/`),
225
+ verificationInitiate: (challengeId, factor) =>
226
+ client.post(`/verification/${challengeId}/initiate/`, { factor }, mutating()),
227
+ verificationComplete: (challengeId, body) =>
228
+ client.post(`/verification/${challengeId}/complete/`, body, mutating()),
229
+
230
+ securityStatus: () => client.get("/security/status/"),
231
+
232
+ sessions: () => client.get("/sessions/"),
233
+ confirmSession: (id) =>
234
+ client.post(`/sessions/${id}/confirm/`, undefined, mutating()),
235
+ revokeSession: (id) => client.delete(`/sessions/${id}/`, mutating()),
236
+ revokeOtherSessions: () => client.delete("/sessions/", mutating()),
237
+
238
+ tokenRefresh: (refresh) =>
239
+ refresh === undefined
240
+ ? client.get("/token/refresh/")
241
+ : client.post("/token/refresh/", { refresh }, mutating()),
242
+
243
+ qrGenerate: (type, redirectUrl, allowUnauthenticatedScanner) =>
244
+ client.post(
245
+ "/qr/generate/",
246
+ allowUnauthenticatedScanner === undefined
247
+ ? { type, redirect_url: redirectUrl }
248
+ : {
249
+ type,
250
+ redirect_url: redirectUrl,
251
+ allow_unauthenticated_scanner: allowUnauthenticatedScanner,
252
+ },
253
+ mutating()
254
+ ),
255
+ qrStatus: (key) => client.get(`/qr/${key}/status/`),
256
+ qrConfirm: (key) => client.post(`/qr/${key}/confirm/`, undefined, mutating()),
257
+ qrReject: (key) => client.post(`/qr/${key}/reject/`, undefined, mutating()),
258
+
259
+ magicRequest: (email, redirectUrl) =>
260
+ client.post(
261
+ "/magic/request/",
262
+ redirectUrl === undefined ? { email } : { email, redirect_url: redirectUrl },
263
+ mutating()
264
+ ),
265
+
266
+ passkeys: () =>
267
+ client
268
+ .get<{ passkeys: readonly Passkey[] }>("/passkey/")
269
+ .then((r) => r.passkeys),
270
+ passkeyRegisterBegin: () =>
271
+ client.post("/passkey/register/begin/", undefined, mutating()),
272
+ passkeyRegisterComplete: (credential, deviceName) =>
273
+ client.post(
274
+ "/passkey/register/complete/",
275
+ deviceName === undefined
276
+ ? { credential }
277
+ : { credential, device_name: deviceName },
278
+ mutating()
279
+ ),
280
+ passkeyAuthenticateBegin: (email) =>
281
+ client.post(
282
+ "/passkey/authenticate/begin/",
283
+ email === undefined ? {} : { email },
284
+ mutating()
285
+ ),
286
+ passkeyAuthenticateComplete: (sessionKey, credential) =>
287
+ client.post(
288
+ "/passkey/authenticate/complete/",
289
+ { session_key: sessionKey, credential },
290
+ mutating()
291
+ ),
292
+ passkeyRemove: (id) => client.delete(`/passkey/${id}/`, mutating()),
293
+
294
+ changeInstantRequestOld: (channel) =>
295
+ client.post(`/${channel}/change/instant/request-old/`, undefined, mutating()),
296
+ changeInstantVerifyOld: (channel, code) =>
297
+ client.post(`/${channel}/change/instant/verify-old/`, { code }, mutating()),
298
+ changeInstantRequestNew: (channel, value, changeToken) =>
299
+ client.post(
300
+ `/${channel}/change/instant/request-new/`,
301
+ { [channel]: value, change_token: changeToken },
302
+ mutating()
303
+ ),
304
+ changeInstantVerifyNew: (channel, value, code, changeToken) =>
305
+ client.post(
306
+ `/${channel}/change/instant/verify-new/`,
307
+ { [channel]: value, code, change_token: changeToken },
308
+ mutating()
309
+ ),
310
+ changeDelayedInitiate: (channel, value) =>
311
+ client.post(
312
+ `/${channel}/change/delayed/initiate/`,
313
+ { [channel]: value },
314
+ mutating()
315
+ ),
316
+ changeDelayedStatus: (channel) =>
317
+ client.get(`/${channel}/change/delayed/status/`),
318
+ changeDelayedCancel: (channel, changeRequestId) =>
319
+ client.post(
320
+ `/${channel}/change/delayed/cancel/`,
321
+ { change_request_id: changeRequestId },
322
+ mutating()
323
+ ),
324
+
325
+ ssoLookup: (domain) => client.get("/sso/lookup/", { query: { domain } }),
326
+
327
+ auditLog: (page) =>
328
+ client.get("/security/audit/", {
329
+ query: page === undefined ? {} : { page },
330
+ }),
331
+ };
332
+ }
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Wire types for the stapel-auth HTTP contract (auth-sa.md §1–18).
3
+ *
4
+ * These are hand-authored here because the auth backend does not yet publish
5
+ * an `openapi.json` artifact to this monorepo. When it does, this file is the
6
+ * codegen target (frontend-standard §2/§3): the shapes below are transcribed
7
+ * verbatim from auth-sa.md's documented request/response bodies so a future
8
+ * `regen` produces a compatible surface.
9
+ */
10
+
11
+ /** Result status of any flow that ends in a session (auth-sa.md §"AuthResponse"). */
12
+ export type AuthStatus = "LOGGED_IN" | "REGISTERED" | "MERGED" | "MODIFIED";
13
+
14
+ /** The authenticated principal. Backend adds fields freely; kept open. */
15
+ export interface StapelUser {
16
+ readonly id: string;
17
+ readonly username?: string;
18
+ readonly email?: string | null;
19
+ readonly phone?: string | null;
20
+ readonly is_anonymous?: boolean;
21
+ readonly [extra: string]: unknown;
22
+ }
23
+
24
+ /** Access/refresh JWT pair returned in the body (also set as cookies). */
25
+ export interface AuthTokens {
26
+ readonly access: string;
27
+ readonly refresh: string;
28
+ }
29
+
30
+ /** Returned by every flow that results in a session. */
31
+ export interface AuthResponse {
32
+ readonly status: AuthStatus;
33
+ readonly user: StapelUser;
34
+ readonly tokens: AuthTokens;
35
+ }
36
+
37
+ /** Returned instead of `AuthResponse` when the account has TOTP enabled. */
38
+ export interface TOTPChallengeResponse {
39
+ readonly status: "TOTP_REQUIRED";
40
+ readonly challenge_token: string;
41
+ readonly expires_in: number;
42
+ }
43
+
44
+ /**
45
+ * The `oneOf` login union (discriminator: `status`). Narrow with
46
+ * `if (r.status === "TOTP_REQUIRED")`.
47
+ */
48
+ export type LoginResponse = AuthResponse | TOTPChallengeResponse;
49
+
50
+ /** Narrowing helper for the login union. */
51
+ export function isTotpChallenge(r: LoginResponse): r is TOTPChallengeResponse {
52
+ return r.status === "TOTP_REQUIRED";
53
+ }
54
+
55
+ /** `{ message, target }` — target is the masked destination to display. */
56
+ export interface OtpRequestResponse {
57
+ readonly message: string;
58
+ readonly target: string;
59
+ }
60
+
61
+ /** Simple status envelopes returned by several mutations. */
62
+ export interface StatusResponse {
63
+ readonly status: string;
64
+ }
65
+
66
+ // ── Capabilities (auth-sa.md §"Which login methods to render") ──────────────
67
+
68
+ export interface OAuthProviderInfo {
69
+ readonly id: string;
70
+ readonly name: string;
71
+ }
72
+
73
+ export interface RegistrationCapabilities {
74
+ readonly phone: boolean;
75
+ readonly email: boolean;
76
+ readonly password: boolean;
77
+ readonly oauth: readonly OAuthProviderInfo[];
78
+ readonly sso: boolean;
79
+ readonly anonymous: boolean;
80
+ }
81
+
82
+ export interface LoginCapabilities {
83
+ readonly phone: boolean;
84
+ readonly email: boolean;
85
+ readonly password: boolean;
86
+ readonly oauth: readonly OAuthProviderInfo[];
87
+ readonly sso: boolean;
88
+ readonly qr: boolean;
89
+ readonly passkey: boolean;
90
+ readonly magic_link: boolean;
91
+ }
92
+
93
+ export interface Capabilities {
94
+ readonly registration: RegistrationCapabilities;
95
+ readonly login: LoginCapabilities;
96
+ }
97
+
98
+ // ── Password change methods (auth-sa.md §4) ─────────────────────────────────
99
+
100
+ export type PasswordChangeMethod = "password" | "email" | "phone";
101
+
102
+ export interface PasswordMethodEntry {
103
+ readonly method: PasswordChangeMethod;
104
+ readonly target?: string;
105
+ }
106
+
107
+ export interface PasswordMethods {
108
+ readonly has_password: boolean;
109
+ readonly methods: readonly PasswordMethodEntry[];
110
+ }
111
+
112
+ // ── Security status (auth-sa.md §10) ────────────────────────────────────────
113
+
114
+ export interface SecurityStatus {
115
+ readonly password: { readonly is_set: boolean };
116
+ readonly totp: {
117
+ readonly is_enabled: boolean;
118
+ readonly backup_codes_remaining: number;
119
+ };
120
+ readonly email: { readonly value: string | null; readonly is_verified: boolean };
121
+ readonly phone: { readonly value: string | null; readonly is_verified: boolean };
122
+ readonly oauth: { readonly connected_providers: readonly string[] };
123
+ readonly sessions: { readonly active_count: number };
124
+ readonly passkeys: { readonly count: number };
125
+ }
126
+
127
+ // ── Sessions (auth-sa.md §12) ───────────────────────────────────────────────
128
+
129
+ export type SessionDeviceType =
130
+ | "phone"
131
+ | "tablet"
132
+ | "desktop"
133
+ | "api"
134
+ | "unknown";
135
+
136
+ export interface AuthSession {
137
+ readonly id: string;
138
+ readonly device_type: SessionDeviceType;
139
+ readonly device_name: string;
140
+ readonly device_details: string;
141
+ readonly ip_address: string;
142
+ readonly created_at: string;
143
+ readonly last_used_at: string;
144
+ readonly is_current: boolean;
145
+ readonly is_suspicious: boolean;
146
+ }
147
+
148
+ // ── TOTP (auth-sa.md §11) ───────────────────────────────────────────────────
149
+
150
+ export interface TotpSetupResponse {
151
+ readonly secret: string;
152
+ readonly qr_uri: string;
153
+ readonly expires_in: number;
154
+ }
155
+
156
+ export interface TotpSetupConfirmResponse {
157
+ readonly backup_codes: readonly string[];
158
+ }
159
+
160
+ /** `oneOf` discriminated by `method` (auth-sa.md §"Disable TOTP"). */
161
+ export type TotpDisableRequest =
162
+ | { readonly method: "totp"; readonly code: string }
163
+ | { readonly method: "backup"; readonly backup_code: string }
164
+ | { readonly method: "otp"; readonly otp_code: string };
165
+
166
+ // ── Verification / step-up (auth-sa.md §11 "verification challenges") ────────
167
+
168
+ export type VerificationFactorId =
169
+ | "otp_email"
170
+ | "otp_phone"
171
+ | "totp"
172
+ | "passkey";
173
+
174
+ /** The `verification` object inside the 403 envelope. */
175
+ export interface VerificationEnvelope {
176
+ readonly challenge_id: string;
177
+ readonly scope: string;
178
+ readonly factors: readonly VerificationFactorId[];
179
+ readonly expires_at: number;
180
+ }
181
+
182
+ export interface VerificationInitiateResponse {
183
+ readonly factor: VerificationFactorId;
184
+ /** For otp_* — `{ target }`; for passkey — `{ session_key, options }`. */
185
+ readonly data: Record<string, unknown>;
186
+ }
187
+
188
+ export interface VerificationCompleteResponse {
189
+ readonly verified: boolean;
190
+ readonly verification_token: string;
191
+ }
192
+
193
+ // ── QR (auth-sa.md §8) ──────────────────────────────────────────────────────
194
+
195
+ export type QrType = "session_share" | "login_request";
196
+
197
+ export interface QrGenerateResponse {
198
+ readonly key: string;
199
+ readonly type: QrType;
200
+ readonly expires_in: number;
201
+ readonly scan_url: string;
202
+ }
203
+
204
+ export type QrStatusValue = "pending" | "fulfilled" | "expired" | "rejected";
205
+
206
+ export interface QrStatusResponse {
207
+ readonly status: QrStatusValue;
208
+ /** Present only on `login_request` fulfilment. */
209
+ readonly access_token?: string;
210
+ readonly refresh_token?: string;
211
+ }
212
+
213
+ // ── Passkeys (auth-sa.md §17) ───────────────────────────────────────────────
214
+
215
+ export interface Passkey {
216
+ readonly id: string;
217
+ readonly device_name: string;
218
+ readonly aaguid: string;
219
+ readonly transports: readonly string[];
220
+ readonly created_at: string;
221
+ readonly last_used_at: string | null;
222
+ }
223
+
224
+ export interface PasskeyRegisterBeginResponse {
225
+ /** PublicKeyCredentialCreationOptions (JSON form). */
226
+ readonly options: Record<string, unknown>;
227
+ }
228
+
229
+ export interface PasskeyAuthenticateBeginResponse {
230
+ readonly session_key: string;
231
+ /** PublicKeyCredentialRequestOptions (JSON form). */
232
+ readonly options: Record<string, unknown>;
233
+ }
234
+
235
+ // ── Authenticator change (auth-sa.md §9) ────────────────────────────────────
236
+
237
+ export interface ChangeOldVerifiedResponse {
238
+ readonly status: "OLD_VERIFIED";
239
+ readonly change_token: string;
240
+ readonly expires_at: string;
241
+ }
242
+
243
+ export interface DelayedChangeInitiatedResponse {
244
+ readonly status: "PENDING";
245
+ readonly change_request_id: string;
246
+ readonly scheduled_at: string;
247
+ readonly can_cancel_until: string;
248
+ }
249
+
250
+ export type DelayedChangeStatus =
251
+ | { readonly has_pending_change: false }
252
+ | {
253
+ readonly has_pending_change?: true;
254
+ readonly change_request_id: string;
255
+ readonly scheduled_at: string;
256
+ readonly can_cancel_until: string;
257
+ };
258
+
259
+ // ── SSO (auth-sa.md §18) ────────────────────────────────────────────────────
260
+
261
+ export interface SsoLookupResponse {
262
+ readonly sso_required: boolean;
263
+ readonly org_slug: string | null;
264
+ readonly protocol?: "saml" | "oidc";
265
+ }
266
+
267
+ // ── Audit log (auth-sa.md §16) ──────────────────────────────────────────────
268
+
269
+ export interface AuditEvent {
270
+ readonly id: string;
271
+ readonly event_type: string;
272
+ readonly ip_address: string;
273
+ readonly user_agent: string;
274
+ readonly metadata: Record<string, unknown>;
275
+ readonly created_at: string;
276
+ }
277
+
278
+ export interface AuditPage {
279
+ readonly results: readonly AuditEvent[];
280
+ readonly count: number;
281
+ readonly next: number | null;
282
+ }
283
+
284
+ /** `{ access, refresh }` from POST/GET /token/refresh/. */
285
+ export interface RefreshResponse {
286
+ readonly access: string;
287
+ readonly refresh: string;
288
+ }
289
+
290
+ /** Which identifier channel an OTP flow uses. */
291
+ export type OtpChannel = "email" | "phone";