@aooth/auth-moost 0.1.19 → 0.1.21

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.
@@ -1,2 +1,450 @@
1
- import { C as Select2faForm, D as WithInlineConsentForm, E as TermsBumpForm, S as RemoveMfaConfirmForm, T as SignupForm, _ as PincodeForm, a as ConcurrencyLimitForm, b as RecoveryFactorForm, c as EnrollConfirmForm, d as InviteForm, f as LoginCredentialsForm, g as PasswordReauthForm, h as MfaCodeForm, i as ChangePasswordForm, l as EnrollPickMethodForm, m as ManageMfaForm, n as AskPhoneForm, o as EmailIdentifierForm, p as MagicLinkRequestForm, r as AuthorizeConsentForm, s as EnrollAddressForm, t as AskEmailForm, u as EnrollTotpQrForm, v as ProveControlForm, w as SetPasswordForm, x as RecoveryModeSelectForm, y as ProveControlOtpForm } from "../forms-uqegc32h.mjs";
1
+ import { TAtscriptAnnotatedType, TAtscriptTypeObject, TMetadataMap, TValidatorOptions, Validator } from "@atscript/typescript/utils";
2
+
3
+ //#region src/atscript/models/forms.as.d.ts
4
+ /**
5
+ * Atscript interface **WithInlineConsentForm**
6
+ * @see {@link ./forms.as:34:18}
7
+ */
8
+ declare class WithInlineConsentForm {
9
+ consents: string[];
10
+ static __is_atscript_annotated_type: true;
11
+ static type: TAtscriptTypeObject<keyof WithInlineConsentForm, WithInlineConsentForm>;
12
+ static metadata: TMetadataMap<AtscriptMetadata>;
13
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof WithInlineConsentForm>;
14
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
15
+ static toJsonSchema: () => any;
16
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
17
+ static toExampleData?: () => any;
18
+ }
19
+ /**
20
+ * Atscript interface **LoginCredentialsForm**
21
+ * @see {@link ./forms.as:61:18}
22
+ */
23
+ declare class LoginCredentialsForm {
24
+ username: string;
25
+ password: string; // signup: ui.action
26
+ // magicLink: ui.action
27
+ ssoProvider?: string;
28
+ static __is_atscript_annotated_type: true;
29
+ static type: TAtscriptTypeObject<keyof LoginCredentialsForm, LoginCredentialsForm>;
30
+ static metadata: TMetadataMap<AtscriptMetadata>;
31
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof LoginCredentialsForm>;
32
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
33
+ static toJsonSchema: () => any;
34
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
35
+ static toExampleData?: () => any;
36
+ }
37
+ /**
38
+ * Atscript interface **MfaCodeForm**
39
+ * @see {@link ./forms.as:136:18}
40
+ */
41
+ declare class MfaCodeForm {
42
+ // transportHint: ui.paragraph
43
+ code: string; // useDifferentMethod: ui.action
44
+ rememberDevice: boolean;
45
+ static __is_atscript_annotated_type: true;
46
+ static type: TAtscriptTypeObject<keyof MfaCodeForm, MfaCodeForm>;
47
+ static metadata: TMetadataMap<AtscriptMetadata>;
48
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof MfaCodeForm>;
49
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
50
+ static toJsonSchema: () => any;
51
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
52
+ static toExampleData?: () => any;
53
+ }
54
+ /**
55
+ * Atscript interface **EmailIdentifierForm**
56
+ * @see {@link ./forms.as:173:18}
57
+ */
58
+ declare class EmailIdentifierForm {
59
+ email: string;
60
+ /* email */
61
+ // backToLogin: ui.action
62
+ static __is_atscript_annotated_type: true;
63
+ static type: TAtscriptTypeObject<keyof EmailIdentifierForm, EmailIdentifierForm>;
64
+ static metadata: TMetadataMap<AtscriptMetadata>;
65
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof EmailIdentifierForm>;
66
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
67
+ static toJsonSchema: () => any;
68
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
69
+ static toExampleData?: () => any;
70
+ }
71
+ /**
72
+ * Atscript interface **SignupForm**
73
+ * @see {@link ./forms.as:205:18}
74
+ */
75
+ declare class SignupForm {
76
+ email: string;
77
+ /* email */
78
+ // backToLogin: ui.action
79
+ static __is_atscript_annotated_type: true;
80
+ static type: TAtscriptTypeObject<keyof SignupForm, SignupForm>;
81
+ static metadata: TMetadataMap<AtscriptMetadata>;
82
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof SignupForm>;
83
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
84
+ static toJsonSchema: () => any;
85
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
86
+ static toExampleData?: () => any;
87
+ }
88
+ /**
89
+ * Atscript interface **SetPasswordForm**
90
+ * @see {@link ./forms.as:263:18}
91
+ */
92
+ declare class SetPasswordForm extends WithInlineConsentForm {
93
+ // intro: ui.paragraph
94
+ newPassword: string;
95
+ confirmPassword: string; // passwordRules: ui.paragraph
96
+ static __is_atscript_annotated_type: true;
97
+ static type: TAtscriptTypeObject<keyof SetPasswordForm, SetPasswordForm>;
98
+ static metadata: TMetadataMap<AtscriptMetadata>;
99
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof SetPasswordForm>;
100
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
101
+ static toJsonSchema: () => any;
102
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
103
+ static toExampleData?: () => any;
104
+ }
105
+ /**
106
+ * Atscript interface **InviteForm**
107
+ * @see {@link ./forms.as:341:18}
108
+ */
109
+ declare class InviteForm {
110
+ email: string;
111
+ /* email */
112
+ roles: string[];
113
+ static __is_atscript_annotated_type: true;
114
+ static type: TAtscriptTypeObject<keyof InviteForm, InviteForm>;
115
+ static metadata: TMetadataMap<AtscriptMetadata>;
116
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof InviteForm>;
117
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
118
+ static toJsonSchema: () => any;
119
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
120
+ static toExampleData?: () => any;
121
+ }
122
+ /**
123
+ * Atscript interface **Select2faForm**
124
+ * @see {@link ./forms.as:371:18}
125
+ */
126
+ declare class Select2faForm {
127
+ methodName: string;
128
+ saveAsDefault: boolean;
129
+ rememberDevice: boolean;
130
+ static __is_atscript_annotated_type: true;
131
+ static type: TAtscriptTypeObject<keyof Select2faForm, Select2faForm>;
132
+ static metadata: TMetadataMap<AtscriptMetadata>;
133
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof Select2faForm>;
134
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
135
+ static toJsonSchema: () => any;
136
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
137
+ static toExampleData?: () => any;
138
+ }
139
+ /**
140
+ * Atscript interface **PincodeForm**
141
+ * @see {@link ./forms.as:417:18}
142
+ */
143
+ declare class PincodeForm {
144
+ // transportHint: ui.paragraph
145
+ code: string;
146
+ rememberDevice: boolean; // resend: ui.action
147
+ // useDifferentMethod: ui.action
148
+ // useDifferentTransport: ui.action
149
+ static __is_atscript_annotated_type: true;
150
+ static type: TAtscriptTypeObject<keyof PincodeForm, PincodeForm>;
151
+ static metadata: TMetadataMap<AtscriptMetadata>;
152
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof PincodeForm>;
153
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
154
+ static toJsonSchema: () => any;
155
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
156
+ static toExampleData?: () => any;
157
+ }
158
+ /**
159
+ * Atscript interface **AskEmailForm**
160
+ * @see {@link ./forms.as:456:18}
161
+ */
162
+ declare class AskEmailForm extends WithInlineConsentForm {
163
+ // disclosure: ui.paragraph
164
+ email: string;
165
+ /* email */
166
+ static __is_atscript_annotated_type: true;
167
+ static type: TAtscriptTypeObject<keyof AskEmailForm, AskEmailForm>;
168
+ static metadata: TMetadataMap<AtscriptMetadata>;
169
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof AskEmailForm>;
170
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
171
+ static toJsonSchema: () => any;
172
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
173
+ static toExampleData?: () => any;
174
+ }
175
+ /**
176
+ * Atscript interface **AskPhoneForm**
177
+ * @see {@link ./forms.as:486:18}
178
+ */
179
+ declare class AskPhoneForm extends WithInlineConsentForm {
180
+ // disclosure: ui.paragraph
181
+ phone: string;
182
+ static __is_atscript_annotated_type: true;
183
+ static type: TAtscriptTypeObject<keyof AskPhoneForm, AskPhoneForm>;
184
+ static metadata: TMetadataMap<AtscriptMetadata>;
185
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof AskPhoneForm>;
186
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
187
+ static toJsonSchema: () => any;
188
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
189
+ static toExampleData?: () => any;
190
+ }
191
+ /**
192
+ * Atscript interface **EnrollPickMethodForm**
193
+ * @see {@link ./forms.as:510:18}
194
+ */
195
+ declare class EnrollPickMethodForm {
196
+ method: string; // skip: ui.action
197
+ // cancel: ui.action
198
+ static __is_atscript_annotated_type: true;
199
+ static type: TAtscriptTypeObject<keyof EnrollPickMethodForm, EnrollPickMethodForm>;
200
+ static metadata: TMetadataMap<AtscriptMetadata>;
201
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof EnrollPickMethodForm>;
202
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
203
+ static toJsonSchema: () => any;
204
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
205
+ static toExampleData?: () => any;
206
+ }
207
+ /**
208
+ * Atscript interface **EnrollAddressForm**
209
+ * @see {@link ./forms.as:544:18}
210
+ */
211
+ declare class EnrollAddressForm {
212
+ address: string; // skip: ui.action
213
+ // cancel: ui.action
214
+ // useDifferentMethod: ui.action
215
+ static __is_atscript_annotated_type: true;
216
+ static type: TAtscriptTypeObject<keyof EnrollAddressForm, EnrollAddressForm>;
217
+ static metadata: TMetadataMap<AtscriptMetadata>;
218
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof EnrollAddressForm>;
219
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
220
+ static toJsonSchema: () => any;
221
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
222
+ static toExampleData?: () => any;
223
+ }
224
+ /**
225
+ * Atscript interface **EnrollConfirmForm**
226
+ * @see {@link ./forms.as:590:18}
227
+ */
228
+ declare class EnrollConfirmForm {
229
+ // transportHint: ui.paragraph
230
+ code: string; // resend: ui.action
231
+ // useDifferentMethod: ui.action
232
+ // cancel: ui.action
233
+ // skip: ui.action
234
+ static __is_atscript_annotated_type: true;
235
+ static type: TAtscriptTypeObject<keyof EnrollConfirmForm, EnrollConfirmForm>;
236
+ static metadata: TMetadataMap<AtscriptMetadata>;
237
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof EnrollConfirmForm>;
238
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
239
+ static toJsonSchema: () => any;
240
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
241
+ static toExampleData?: () => any;
242
+ }
243
+ /**
244
+ * Atscript interface **EnrollTotpQrForm**
245
+ * @see {@link ./forms.as:640:18}
246
+ */
247
+ declare class EnrollTotpQrForm {
248
+ // qrCode: ui.paragraph
249
+ // useDifferentMethod: ui.action
250
+ // cancel: ui.action
251
+ // skip: ui.action
252
+ static __is_atscript_annotated_type: true;
253
+ static type: TAtscriptTypeObject<keyof EnrollTotpQrForm, EnrollTotpQrForm>;
254
+ static metadata: TMetadataMap<AtscriptMetadata>;
255
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof EnrollTotpQrForm>;
256
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
257
+ static toJsonSchema: () => any;
258
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
259
+ static toExampleData?: () => any;
260
+ }
261
+ /**
262
+ * Atscript interface **ManageMfaForm**
263
+ * @see {@link ./forms.as:679:18}
264
+ */
265
+ declare class ManageMfaForm {
266
+ // lockedNote: ui.paragraph
267
+ operation: string; // cancel: ui.action
268
+ static __is_atscript_annotated_type: true;
269
+ static type: TAtscriptTypeObject<keyof ManageMfaForm, ManageMfaForm>;
270
+ static metadata: TMetadataMap<AtscriptMetadata>;
271
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ManageMfaForm>;
272
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
273
+ static toJsonSchema: () => any;
274
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
275
+ static toExampleData?: () => any;
276
+ }
277
+ /**
278
+ * Atscript interface **RemoveMfaConfirmForm**
279
+ * @see {@link ./forms.as:708:18}
280
+ */
281
+ declare class RemoveMfaConfirmForm {
282
+ // notice: ui.paragraph
283
+ // cancel: ui.action
284
+ static __is_atscript_annotated_type: true;
285
+ static type: TAtscriptTypeObject<keyof RemoveMfaConfirmForm, RemoveMfaConfirmForm>;
286
+ static metadata: TMetadataMap<AtscriptMetadata>;
287
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof RemoveMfaConfirmForm>;
288
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
289
+ static toJsonSchema: () => any;
290
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
291
+ static toExampleData?: () => any;
292
+ }
293
+ /**
294
+ * Atscript interface **PasswordReauthForm**
295
+ * @see {@link ./forms.as:731:18}
296
+ */
297
+ declare class PasswordReauthForm {
298
+ password: string; // cancel: ui.action
299
+ static __is_atscript_annotated_type: true;
300
+ static type: TAtscriptTypeObject<keyof PasswordReauthForm, PasswordReauthForm>;
301
+ static metadata: TMetadataMap<AtscriptMetadata>;
302
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof PasswordReauthForm>;
303
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
304
+ static toJsonSchema: () => any;
305
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
306
+ static toExampleData?: () => any;
307
+ }
308
+ /**
309
+ * Atscript interface **TermsBumpForm**
310
+ * @see {@link ./forms.as:760:18}
311
+ */
312
+ declare class TermsBumpForm extends WithInlineConsentForm {
313
+ static __is_atscript_annotated_type: true;
314
+ static type: TAtscriptTypeObject<keyof TermsBumpForm, TermsBumpForm>;
315
+ static metadata: TMetadataMap<AtscriptMetadata>;
316
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof TermsBumpForm>;
317
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
318
+ static toJsonSchema: () => any;
319
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
320
+ static toExampleData?: () => any;
321
+ }
322
+ /**
323
+ * Atscript interface **ConcurrencyLimitForm**
324
+ * @see {@link ./forms.as:773:18}
325
+ */
326
+ declare class ConcurrencyLimitForm {
327
+ static __is_atscript_annotated_type: true;
328
+ static type: TAtscriptTypeObject<keyof ConcurrencyLimitForm, ConcurrencyLimitForm>;
329
+ static metadata: TMetadataMap<AtscriptMetadata>;
330
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ConcurrencyLimitForm>;
331
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
332
+ static toJsonSchema: () => any;
333
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
334
+ static toExampleData?: () => any;
335
+ }
336
+ /**
337
+ * Atscript interface **MagicLinkRequestForm**
338
+ * @see {@link ./forms.as:783:18}
339
+ */
340
+ declare class MagicLinkRequestForm {
341
+ identifier: string;
342
+ static __is_atscript_annotated_type: true;
343
+ static type: TAtscriptTypeObject<keyof MagicLinkRequestForm, MagicLinkRequestForm>;
344
+ static metadata: TMetadataMap<AtscriptMetadata>;
345
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof MagicLinkRequestForm>;
346
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
347
+ static toJsonSchema: () => any;
348
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
349
+ static toExampleData?: () => any;
350
+ }
351
+ /**
352
+ * Atscript interface **RecoveryModeSelectForm**
353
+ * @see {@link ./forms.as:798:18}
354
+ */
355
+ declare class RecoveryModeSelectForm {
356
+ mode: string;
357
+ static __is_atscript_annotated_type: true;
358
+ static type: TAtscriptTypeObject<keyof RecoveryModeSelectForm, RecoveryModeSelectForm>;
359
+ static metadata: TMetadataMap<AtscriptMetadata>;
360
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof RecoveryModeSelectForm>;
361
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
362
+ static toJsonSchema: () => any;
363
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
364
+ static toExampleData?: () => any;
365
+ }
366
+ /**
367
+ * Atscript interface **RecoveryFactorForm**
368
+ * @see {@link ./forms.as:821:18}
369
+ */
370
+ declare class RecoveryFactorForm {
371
+ factor: string;
372
+ value: string;
373
+ static __is_atscript_annotated_type: true;
374
+ static type: TAtscriptTypeObject<keyof RecoveryFactorForm, RecoveryFactorForm>;
375
+ static metadata: TMetadataMap<AtscriptMetadata>;
376
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof RecoveryFactorForm>;
377
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
378
+ static toJsonSchema: () => any;
379
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
380
+ static toExampleData?: () => any;
381
+ }
382
+ /**
383
+ * Atscript interface **ChangePasswordForm**
384
+ * @see {@link ./forms.as:855:18}
385
+ */
386
+ declare class ChangePasswordForm {
387
+ // intro: ui.paragraph
388
+ currentPassword: string;
389
+ newPassword: string;
390
+ confirmPassword: string; // passwordRules: ui.paragraph
391
+ static __is_atscript_annotated_type: true;
392
+ static type: TAtscriptTypeObject<keyof ChangePasswordForm, ChangePasswordForm>;
393
+ static metadata: TMetadataMap<AtscriptMetadata>;
394
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ChangePasswordForm>;
395
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
396
+ static toJsonSchema: () => any;
397
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
398
+ static toExampleData?: () => any;
399
+ }
400
+ /**
401
+ * Atscript interface **ProveControlForm**
402
+ * @see {@link ./forms.as:920:18}
403
+ */
404
+ declare class ProveControlForm {
405
+ // intro: ui.paragraph
406
+ password: string; // cancel: ui.action
407
+ static __is_atscript_annotated_type: true;
408
+ static type: TAtscriptTypeObject<keyof ProveControlForm, ProveControlForm>;
409
+ static metadata: TMetadataMap<AtscriptMetadata>;
410
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ProveControlForm>;
411
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
412
+ static toJsonSchema: () => any;
413
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
414
+ static toExampleData?: () => any;
415
+ }
416
+ /**
417
+ * Atscript interface **ProveControlOtpForm**
418
+ * @see {@link ./forms.as:955:18}
419
+ */
420
+ declare class ProveControlOtpForm {
421
+ // intro: ui.paragraph
422
+ code: string; // resend: ui.action
423
+ // cancel: ui.action
424
+ static __is_atscript_annotated_type: true;
425
+ static type: TAtscriptTypeObject<keyof ProveControlOtpForm, ProveControlOtpForm>;
426
+ static metadata: TMetadataMap<AtscriptMetadata>;
427
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ProveControlOtpForm>;
428
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
429
+ static toJsonSchema: () => any;
430
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
431
+ static toExampleData?: () => any;
432
+ }
433
+ /**
434
+ * Atscript interface **AuthorizeConsentForm**
435
+ * @see {@link ./forms.as:1005:18}
436
+ */
437
+ declare class AuthorizeConsentForm {
438
+ // notice: ui.paragraph
439
+ // deny: ui.action
440
+ static __is_atscript_annotated_type: true;
441
+ static type: TAtscriptTypeObject<keyof AuthorizeConsentForm, AuthorizeConsentForm>;
442
+ static metadata: TMetadataMap<AtscriptMetadata>;
443
+ static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof AuthorizeConsentForm>;
444
+ /** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
445
+ static toJsonSchema: () => any;
446
+ /** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
447
+ static toExampleData?: () => any;
448
+ }
449
+ //#endregion
2
450
  export { AskEmailForm, AskPhoneForm, AuthorizeConsentForm, ChangePasswordForm, ConcurrencyLimitForm, EmailIdentifierForm, EnrollAddressForm, EnrollConfirmForm, EnrollPickMethodForm, EnrollTotpQrForm, InviteForm, LoginCredentialsForm, MagicLinkRequestForm, ManageMfaForm, MfaCodeForm, PasswordReauthForm, PincodeForm, ProveControlForm, ProveControlOtpForm, RecoveryFactorForm, RecoveryModeSelectForm, RemoveMfaConfirmForm, Select2faForm, SetPasswordForm, SignupForm, TermsBumpForm, WithInlineConsentForm };
package/dist/index.d.mts CHANGED
@@ -1230,17 +1230,52 @@ interface AuthWfMfaState {
1230
1230
  }
1231
1231
  /** Channel-onboarding state (login Phase 3). */
1232
1232
  interface AuthWfChannelState {
1233
+ /**
1234
+ * The email address `ask/email` collected and sent the enrollment code to —
1235
+ * the sole enrollment ask→verify target, mirroring `phone`. The enrollment
1236
+ * gates key on it. Deliberately SEPARATE from `notice.email` (the
1237
+ * security-notice recipient): that slot may be seeded from
1238
+ * `getCorrespondenceEmail` / a federated profile without any code ever
1239
+ * being sent, so the enrollment gates must never key on it.
1240
+ */
1241
+ email?: string;
1233
1242
  emailConfirmed?: boolean;
1234
1243
  phone?: string;
1235
1244
  phoneConfirmed?: boolean;
1236
1245
  otpDisclosure?: string;
1237
1246
  }
1247
+ /**
1248
+ * Security-notice recipient state (login flow). `email` is the address
1249
+ * `notify-new-device` (and any future security notice) delivers to — owned
1250
+ * by the `credentials` / `seedChannelState` seeding (confirmed email-MFA →
1251
+ * `getCorrespondenceEmail` → provider display email) and refreshed by
1252
+ * `verify/email` once a freshly-proven inbox confirms. Server-only — never
1253
+ * `@wf.context.pass`'d, not mirrored into `ctx.public`. Deliberately
1254
+ * distinct from `channel.email` (the enrollment ask→verify target) and from
1255
+ * the flow-subject `ctx.email` that recovery / invite / signup use.
1256
+ */
1257
+ interface AuthWfNoticeState {
1258
+ email?: string;
1259
+ }
1238
1260
  /** Device-trust state (login). */
1239
1261
  interface AuthWfTrustState {
1240
1262
  deviceTrustToken?: string;
1241
1263
  newDevice?: boolean;
1242
1264
  rememberDevice?: boolean;
1243
1265
  optIn?: boolean;
1266
+ /**
1267
+ * The ARRIVING request presented a valid recognition cookie (stamped by
1268
+ * `device-recognition` BEFORE any mint — pre-mint arrival state). This is
1269
+ * what the notify-new-device gate reads: recognition suppresses the
1270
+ * notification; it never affects MFA (that's `newDevice` / trust).
1271
+ */
1272
+ recognized?: boolean;
1273
+ /**
1274
+ * Recognition token `issue` must set as a cookie on the finish envelope —
1275
+ * either the re-validated arriving cookie (re-issued with a fresh maxAge)
1276
+ * or a freshly minted one.
1277
+ */
1278
+ seenDeviceToken?: string;
1244
1279
  }
1245
1280
  /** Session-policy state (login). */
1246
1281
  interface AuthWfSessionState {
@@ -1372,6 +1407,16 @@ interface AuthWfRecoveryAltActions {
1372
1407
  */
1373
1408
  interface AuthWfOtpState {
1374
1409
  verified?: boolean;
1410
+ /**
1411
+ * The address the last `pincode-send` ACTUALLY delivered to — login MFA
1412
+ * email/SMS challenge, recovery M1 (typed identifier) / M2 (registered
1413
+ * method), and signup's reuse of the recovery pair. Server-only — never
1414
+ * `@wf.context.pass`'d. The email-channel case is consumed by
1415
+ * `pincode-check` as inbox proof (`users.setVerifiedEmail`).
1416
+ */
1417
+ deliveredTo?: string;
1418
+ /** Wire channel of that delivery — only `email` constitutes an inbox proof. */
1419
+ deliveredChannel?: "email" | "sms";
1375
1420
  }
1376
1421
  /** Invite admin-side (Phase A) state. */
1377
1422
  interface AuthWfAdminState {
@@ -1684,6 +1729,12 @@ interface AuthWfPublicState {
1684
1729
  /** Unified workflow context shape — one type for all three flows. */
1685
1730
  interface AuthWfCtx {
1686
1731
  subject?: string;
1732
+ /**
1733
+ * Flow-subject address — the typed recovery identifier, the invite target,
1734
+ * or the signup email. The LOGIN flow does NOT use this slot: its
1735
+ * security-notice recipient is `notice.email` and its enrollment
1736
+ * ask→verify target is `channel.email`.
1737
+ */
1687
1738
  email?: string;
1688
1739
  defaults?: AuthWfDefaults;
1689
1740
  pin?: string;
@@ -1725,6 +1776,7 @@ interface AuthWfCtx {
1725
1776
  recoveryAltActions?: AuthWfRecoveryAltActions;
1726
1777
  mfa?: AuthWfMfaState;
1727
1778
  channel?: AuthWfChannelState;
1779
+ notice?: AuthWfNoticeState;
1728
1780
  trust?: AuthWfTrustState;
1729
1781
  session?: AuthWfSessionState;
1730
1782
  altActions?: AuthWfAltActionsState;
@@ -1796,6 +1848,24 @@ interface AuthWorkflowOpts {
1796
1848
  ttlMs?: number;
1797
1849
  bindsTo?: "cookie" | "cookie+ip";
1798
1850
  };
1851
+ /**
1852
+ * Device RECOGNITION infra — the always-on `seenDevices` ledger + cookie
1853
+ * that suppress the "new sign-in" notification on devices the user has
1854
+ * already logged in from. Strictly a notification suppressor, NOT an MFA
1855
+ * bypass (that is `deviceTrust`, which stays opt-in and strict).
1856
+ * Cookie-only binding by design — recognition is pure noise control, so it
1857
+ * never binds to IP (IP churn must not re-trigger the email).
1858
+ *
1859
+ * `cookieName` defaults to `<deviceTrust.cookieName>_seen` so a consumer
1860
+ * renaming the trust cookie gets a matching recognition name for free.
1861
+ * No policy flags here — the on/off gate is the existing
1862
+ * `resolveFinalize().notifyNewDevice` policy.
1863
+ */
1864
+ deviceRecognition?: {
1865
+ cookieName?: string;
1866
+ ttlMs?: number; /** Cap on the per-user `seenDevices` ledger — LRU-evicted beyond it. */
1867
+ maxDevices?: number;
1868
+ };
1799
1869
  forms?: {
1800
1870
  loginCredentials?: TAtscriptAnnotatedType;
1801
1871
  invite?: TAtscriptAnnotatedType;
@@ -1846,6 +1916,11 @@ interface ResolvedAuthWorkflowOpts {
1846
1916
  ttlMs: number;
1847
1917
  bindsTo: "cookie" | "cookie+ip";
1848
1918
  };
1919
+ deviceRecognition: {
1920
+ cookieName: string;
1921
+ ttlMs: number;
1922
+ maxDevices: number;
1923
+ };
1849
1924
  forms: {
1850
1925
  loginCredentials: TAtscriptAnnotatedType;
1851
1926
  invite: TAtscriptAnnotatedType;
@@ -1917,6 +1992,22 @@ type AuthDeliveryPayload = {
1917
1992
  recipient: string;
1918
1993
  deviceLabel?: string;
1919
1994
  loginAt: number;
1995
+ }
1996
+ /**
1997
+ * Consumer-triggered security notice (e.g. impossible-travel detected by a
1998
+ * `resolveRiskStepUp` override) — routed through the same `deliver()` as
1999
+ * every other notice. `reason` is the machine-readable trigger
2000
+ * (e.g. `"impossible-travel"`); `context` is free-form template data
2001
+ * (distances, cities). NEVER auto-sent by the base class — only a consumer
2002
+ * call to `sendSecurityAlert` emits it.
2003
+ */
2004
+ | {
2005
+ kind: "security-alert";
2006
+ channel: "email";
2007
+ recipient: string;
2008
+ reason: string;
2009
+ loginAt: number;
2010
+ context?: Record<string, unknown>;
1920
2011
  };
1921
2012
  /**
1922
2013
  * Top-level `UserCredentials` keys that workflow-collected profile payloads
@@ -1932,6 +2023,22 @@ declare const RESERVED_USER_KEYS: ReadonlySet<string>;
1932
2023
  * Does not mutate the input.
1933
2024
  */
1934
2025
  declare function stripReservedUserKeys(profile: Record<string, unknown>): Record<string, unknown>;
2026
+ /**
2027
+ * Best-effort "Browser on OS" label from a raw User-Agent string — feeds the
2028
+ * `name` field of `seenDevices` records so a device list reads "Chrome on
2029
+ * Windows" instead of a token. Deliberately tiny (no UA-parser dependency);
2030
+ * detection order lives in the `UA_BROWSERS` / `UA_OSES` tables above.
2031
+ * Returns just the browser or just the OS when only one side is detected;
2032
+ * `undefined` for empty / fully unrecognized input.
2033
+ */
2034
+ declare function humanizeUserAgent(ua: string | undefined): string | undefined;
2035
+ declare function haversineKm(a: {
2036
+ lat: number;
2037
+ lon: number;
2038
+ }, b: {
2039
+ lat: number;
2040
+ lon: number;
2041
+ }): number;
1935
2042
  /** Trim + de-duplicate role identifiers submitted via the admin invite form. */
1936
2043
  declare function parseInviteRoles(input?: string[]): string[];
1937
2044
  /**
@@ -1964,6 +2071,16 @@ declare class AuthWorkflow {
1964
2071
  * sync-friendly: the default `void` preserves the engine's sync fast path.
1965
2072
  */
1966
2073
  protected deliver(_payload: AuthDeliveryPayload): void | Promise<void>;
2074
+ /**
2075
+ * The blessed one-call alert path for risk overrides — emit a
2076
+ * `security-alert` delivery (e.g. from an impossible-travel
2077
+ * `resolveRiskStepUp` override). Recipient comes from `ctx.notice.email`,
2078
+ * the proven-first correspondence chain seeded by `credentials` /
2079
+ * `seedChannelState` and refreshed by `verify/email`. No recipient →
2080
+ * SILENT no-op (a user with no provable inbox simply can't be alerted —
2081
+ * mirrors `notifyNewDevice`'s posture). Never called by the base class.
2082
+ */
2083
+ protected sendSecurityAlert(ctx: AuthWfCtx, reason: string, context?: Record<string, unknown>): Promise<void>;
1967
2084
  /**
1968
2085
  * Return the list of selectable role identifiers for the admin invite form.
1969
2086
  * Mirrors the prior `InviteWorkflow.getAvailableRoles()` consumer hook —
@@ -2306,6 +2423,17 @@ declare class AuthWorkflow {
2306
2423
  * matching `resolveOtpDisclosure` / the MFA transport.
2307
2424
  */
2308
2425
  protected resolvePromoteHandleField(_ctx: AuthWfCtx, _channel: "email" | "sms"): string | undefined | Promise<string | undefined>;
2426
+ /**
2427
+ * Decide whether a verified federated profile's email claim counts as inbox
2428
+ * proof for the CORRESPONDENCE address (`users.setVerifiedEmail`). Default
2429
+ * trusts the provider's `email_verified` claim — a provider trusted to
2430
+ * AUTHENTICATE the user is strictly more trusted than its email claim. The
2431
+ * capture is correspondence-only: it never promotes the address to a login
2432
+ * handle and never resolves accounts by it. Override to exclude providers
2433
+ * whose claim should not be taken at face value (e.g. an internal OIDC
2434
+ * issuer that stamps `email_verified` on unverified directory entries).
2435
+ */
2436
+ protected resolveFederatedEmailTrust(_ctx: AuthWfCtx, profile: FederatedProfileSnapshot): boolean | Promise<boolean>;
2309
2437
  /**
2310
2438
  * Route a form alt-action click to a canonical outcome. Defaults match the
2311
2439
  * action ids the bundled `PincodeForm` declares; customers override per
@@ -2908,6 +3036,29 @@ declare class AuthWorkflow {
2908
3036
  * the MFA-form `hidden` expression on `rememberDevice`).
2909
3037
  */
2910
3038
  deviceTrust(ctx: AuthWfCtx): Promise<undefined>;
3039
+ /**
3040
+ * Always-on device RECOGNITION — verify-or-mint the long-lived recognition
3041
+ * cookie against the `seenDevices` ledger. Recognition is a notification
3042
+ * suppressor ONLY (the notify-new-device gate reads `trust.recognized`); it
3043
+ * never skips MFA — that is `deviceTrust`, which stays opt-in and strict.
3044
+ *
3045
+ * Deliberately a SEPARATE step from `check-trusted-device`: that step is
3046
+ * schema-gated on `deviceTrust.enabled && skipsMfa`, so recognition must
3047
+ * not piggyback on it or recognition dies whenever trust is disabled —
3048
+ * exactly the consumers who get the noisiest notify behaviour today.
3049
+ * Verify-or-mint lives in ONE step so `trust.recognized` captures the
3050
+ * PRE-MINT arrival state the notify gate needs (a freshly minted token
3051
+ * must not mark the current login as recognized).
3052
+ *
3053
+ * A valid arriving cookie is verified with `slideTtlMs` (LRU bump) and
3054
+ * re-stashed on `trust.seenDeviceToken` so `issue` re-sets it with a fresh
3055
+ * maxAge. An unrecognized arrival mints + persists a new record (capped at
3056
+ * `deviceRecognition.maxDevices`) and stashes the new token — `recognized`
3057
+ * stays unset so the notification still fires for this login. Degrades
3058
+ * gracefully to a no-op when no `deviceTrust.secret` is configured,
3059
+ * preserving the legacy notify behaviour for those consumers.
3060
+ */
3061
+ deviceRecognition(ctx: AuthWfCtx): Promise<undefined>;
2911
3062
  /**
2912
3063
  * Standalone terms-bump prompt for returning users whose accepted terms
2913
3064
  * version is stale and no carrier form ran. Delegates to
@@ -2968,7 +3119,16 @@ declare class AuthWorkflow {
2968
3119
  /**
2969
3120
  * Notify the user of a login from a new device via the unified `deliver`
2970
3121
  * hook. Gated upstream by
2971
- * `!ctx.isFirstLogin && !!ctx.finalize.notifyNewDevice && !!ctx.trust.newDevice`.
3122
+ * `!ctx.isFirstLogin && !!ctx.finalize.notifyNewDevice && !ctx.trust.recognized`
3123
+ * — "not recognized" (no valid recognition cookie on arrival), NOT "no
3124
+ * valid trust cookie": users who decline remember-me, or whose strict trust
3125
+ * cookie expired / failed IP binding, must not get the email on every
3126
+ * login. Recognition is the loose always-on ledger minted by
3127
+ * `device-recognition`; trust stays strict and drives MFA skip only.
3128
+ *
3129
+ * Recipient is `notice.email` — the security-notice slot owned by the
3130
+ * `credentials` / `seedChannelState` seeding and refreshed by
3131
+ * `verify/email`. No recipient seeded → silently skips.
2972
3132
  */
2973
3133
  notifyNewDevice(ctx: AuthWfCtx): Promise<undefined>;
2974
3134
  /**
@@ -3132,14 +3292,31 @@ declare class AuthWorkflow {
3132
3292
  */
3133
3293
  private finishOAuth;
3134
3294
  /**
3135
- * Seed `ctx.email` / `ctx.channel` from a resolved user's confirmed channels —
3295
+ * Seed `ctx.notice.email` / `ctx.channel` from a resolved user's confirmed channels —
3136
3296
  * shared by `ssoCallback` (linked / created / auto-linked) and `proveControl`
3137
3297
  * (interactively-linked) so the post-success channel shape can't drift between
3138
- * the two federated entry points. Mirrors `credentials`' post-login seeding.
3139
- * `fallbackEmail` (the provider / snapshot email) is a DISPLAY fallback only —
3140
- * never promoted to the unique login handle (a gated, later-phase concern).
3298
+ * the two federated entry points. Mirrors `credentials`' post-login seeding,
3299
+ * including the correspondence fallback (confirmed email-MFA
3300
+ * `users.getCorrespondenceEmail` provider display email).
3301
+ *
3302
+ * Also the single federated capture point for `users.setVerifiedEmail`: a
3303
+ * trusted `profile.email` (per `resolveFederatedEmailTrust`) is recorded as
3304
+ * the proven correspondence address on EVERY federated login — first-time
3305
+ * create, returning link, and interactive link alike (the store write is
3306
+ * skipped when the capture is already current). The profile email is
3307
+ * otherwise a DISPLAY fallback only — never promoted to the unique login
3308
+ * handle (a gated, later-phase concern).
3141
3309
  */
3142
3310
  private seedChannelState;
3311
+ /**
3312
+ * Correspondence tail of the `ctx.notice.email` seeding — shared by
3313
+ * `credentials` (post-login) and `seedChannelState` (federated) so the
3314
+ * fallback chain (`users.getCorrespondenceEmail` → optional display email)
3315
+ * can't drift between the two. Does NOT set `channel.emailConfirmed` — that
3316
+ * flag means "confirmed email-MFA channel" and gates enrolment; a
3317
+ * correspondence address is a notice recipient, not a proven OTP channel.
3318
+ */
3319
+ private seedCorrespondenceEmail;
3143
3320
  /**
3144
3321
  * `needs-link` setup (decision A — password, OTP fallback). Decide how the
3145
3322
  * user will prove control of the matched account, stash the pending-link
@@ -3304,4 +3481,4 @@ interface AuditEmitter {
3304
3481
  emit(event: AuditEvent): Promise<void> | void;
3305
3482
  }
3306
3483
  //#endregion
3307
- export { ADD_MFA_WORKFLOW, AUTHZ_BINDING_COOKIE, AUTH_CODE_STORE_TOKEN, type AuditEmitter, type AuditEvent, type AuthBindings, type AuthContext, AuthController, type AuthDeliveryPayload, type AuthEmailEvent, type AuthEmailKind, type AuthEmailOutletDeps, AuthGuarded, type AuthLoginResponse, type AuthLogoutBody, type AuthOkResponse, type AuthOptions, type AuthRefreshBody, type AuthSmsEvent, type AuthSmsKind, type AuthWfCompletionState, type AuthWfConsentsState, type AuthWfCtx, type AuthWfMfaEnrollState, type AuthWfOAuthState, type AuthWfPasswordUiState, type AuthWfPincodeUiState, AuthWorkflow, type AuthWorkflowOpts, type AuthorizationServerMetadata, AuthorizeController, AuthorizeRuntime, type BuildAuthorizationServerMetadataOptions, type BuildMagicLinkUrl, type BuildProtectedResourceMetadataOptions, CHANGE_PASSWORD_WORKFLOW, CLIENT_REDIRECT_POLICY_TOKEN, type ConcurrencyLimitOptions, type ConnectedAccount, type ConsentDescriptor, type ConsentDescriptorLike, type ConsentEvent, ConsentStore, DEFAULT_AUTH_WORKFLOWS, DYNAMIC_CLIENT_STORE_TOKEN, DynamicClientRegistration, type DynamicClientRegistrationOptions, type EmailSender, type EnrichedSession, FEDERATED_IDENTITY_STORE_TOKEN, type IssueResult, type LoginRedirect, type MfaSummary, type MfaTransport, OAUTH_CSRF_COOKIE, OAuthController, OAuthRuntime, PENDING_AUTHORIZATION_STORE_TOKEN, type ProtectedResourceMetadata, Public, RESERVED_USER_KEYS, type ResolvedAuthCookieConfig, type ResolvedAuthOptions, type ResolvedAuthWorkflowOpts, type SessionEnricher, SessionEnricherProvider, type SessionInfo, SessionsController, type SmsSender, type SsoProvider, type TAuthMeta, UserId, WfTrigger, type WfTriggerOpts, WfTriggerProvider, type WwwAuthenticateBearerChallengeOptions, authGuardInterceptor, authzBindingCookieAttrs, buildAuthorizationServerMetadata, buildInviteAlreadyAcceptedEnvelope, buildProtectedResourceMetadata, buildWwwAuthenticateBearerChallenge, canonicalizeIssuer, createAuthEmailOutlet, deriveWfStateSecret, generateMagicLinkToken, getAuthMate, isSafeRelativeRedirect, oauthCsrfCookieAttrs, parseInviteRoles, resolveOAuthRedirect, stripReservedUserKeys, useAuth };
3484
+ export { ADD_MFA_WORKFLOW, AUTHZ_BINDING_COOKIE, AUTH_CODE_STORE_TOKEN, type AuditEmitter, type AuditEvent, type AuthBindings, type AuthContext, AuthController, type AuthDeliveryPayload, type AuthEmailEvent, type AuthEmailKind, type AuthEmailOutletDeps, AuthGuarded, type AuthLoginResponse, type AuthLogoutBody, type AuthOkResponse, type AuthOptions, type AuthRefreshBody, type AuthSmsEvent, type AuthSmsKind, type AuthWfCompletionState, type AuthWfConsentsState, type AuthWfCtx, type AuthWfMfaEnrollState, type AuthWfOAuthState, type AuthWfPasswordUiState, type AuthWfPincodeUiState, AuthWorkflow, type AuthWorkflowOpts, type AuthorizationServerMetadata, AuthorizeController, AuthorizeRuntime, type BuildAuthorizationServerMetadataOptions, type BuildMagicLinkUrl, type BuildProtectedResourceMetadataOptions, CHANGE_PASSWORD_WORKFLOW, CLIENT_REDIRECT_POLICY_TOKEN, type ConcurrencyLimitOptions, type ConnectedAccount, type ConsentDescriptor, type ConsentDescriptorLike, type ConsentEvent, ConsentStore, DEFAULT_AUTH_WORKFLOWS, DYNAMIC_CLIENT_STORE_TOKEN, DynamicClientRegistration, type DynamicClientRegistrationOptions, type EmailSender, type EnrichedSession, FEDERATED_IDENTITY_STORE_TOKEN, type IssueResult, type LoginRedirect, type MfaSummary, type MfaTransport, OAUTH_CSRF_COOKIE, OAuthController, OAuthRuntime, PENDING_AUTHORIZATION_STORE_TOKEN, type ProtectedResourceMetadata, Public, RESERVED_USER_KEYS, type ResolvedAuthCookieConfig, type ResolvedAuthOptions, type ResolvedAuthWorkflowOpts, type SessionEnricher, SessionEnricherProvider, type SessionInfo, SessionsController, type SmsSender, type SsoProvider, type TAuthMeta, UserId, WfTrigger, type WfTriggerOpts, WfTriggerProvider, type WwwAuthenticateBearerChallengeOptions, authGuardInterceptor, authzBindingCookieAttrs, buildAuthorizationServerMetadata, buildInviteAlreadyAcceptedEnvelope, buildProtectedResourceMetadata, buildWwwAuthenticateBearerChallenge, canonicalizeIssuer, createAuthEmailOutlet, deriveWfStateSecret, generateMagicLinkToken, getAuthMate, haversineKm, humanizeUserAgent, isSafeRelativeRedirect, oauthCsrfCookieAttrs, parseInviteRoles, resolveOAuthRedirect, stripReservedUserKeys, useAuth };
package/dist/index.mjs CHANGED
@@ -4,7 +4,7 @@ import { AuthCredential, AuthError, generateMagicLinkToken } from "@aooth/auth";
4
4
  import { current, defineWook, eventTypeKey, key } from "@wooksjs/event-core";
5
5
  import { HttpError, useAuthorization, useCookies, useHeaders, useRequest, useResponse, useUrlParams } from "@wooksjs/event-http";
6
6
  import { ArbacAction, ArbacResource, getArbacMate } from "@aooth/arbac-moost";
7
- import { FederatedIdentityStore, UserAuthError, UserService, generateMfaCode, generateTotpSecret, generateTotpUri, maskEmail, maskPhone, pickDefinedProfile, verifyTotpCode } from "@aooth/user";
7
+ import { FederatedIdentityStore, SEEN_DEVICES_DEFAULT_CAP, UserAuthError, UserService, generateMfaCode, generateTotpSecret, generateTotpUri, maskEmail, maskPhone, pickDefinedProfile, verifyTotpCode } from "@aooth/user";
8
8
  import { Body, Delete, Get, HttpError as HttpError$1, Post, Query } from "@moostjs/event-http";
9
9
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
10
10
  import { createAsHttpOutlet, finishWf, handleAsOutletRequest, useAtscriptWf } from "@atscript/moost-wf";
@@ -964,6 +964,12 @@ const DEFAULT_FORMS = {
964
964
  authzConsent: AuthorizeConsentForm
965
965
  };
966
966
  function mergeAuthWorkflowOpts(opts) {
967
+ const deviceTrust = {
968
+ cookieName: "aooth_trusted_device",
969
+ ttlMs: 1440 * 6e4,
970
+ bindsTo: "cookie",
971
+ ...opts.deviceTrust
972
+ };
967
973
  return {
968
974
  autoLoginOnInvite: opts.autoLoginOnInvite ?? true,
969
975
  autoLoginOnRecover: opts.autoLoginOnRecover ?? false,
@@ -976,11 +982,11 @@ function mergeAuthWorkflowOpts(opts) {
976
982
  recoveryStateTtlMs: opts.recoveryStateTtlMs ?? 3600 * 1e3,
977
983
  loginUrl: opts.loginUrl ?? "/login",
978
984
  totpIssuer: opts.totpIssuer ?? "aooth",
979
- deviceTrust: {
980
- cookieName: "aooth_trusted_device",
981
- ttlMs: 1440 * 6e4,
982
- bindsTo: "cookie",
983
- ...opts.deviceTrust
985
+ deviceTrust,
986
+ deviceRecognition: {
987
+ cookieName: opts.deviceRecognition?.cookieName ?? `${deviceTrust.cookieName}_seen`,
988
+ ttlMs: opts.deviceRecognition?.ttlMs ?? 4320 * 60 * 6e4,
989
+ maxDevices: opts.deviceRecognition?.maxDevices ?? SEEN_DEVICES_DEFAULT_CAP
984
990
  },
985
991
  forms: {
986
992
  ...DEFAULT_FORMS,
@@ -1006,6 +1012,7 @@ const RESERVED_USER_KEYS = new Set([
1006
1012
  "passwordHistory",
1007
1013
  "mfa",
1008
1014
  "trustedDevices",
1015
+ "seenDevices",
1009
1016
  "pendingInvitation"
1010
1017
  ]);
1011
1018
  /**
@@ -1017,6 +1024,50 @@ function stripReservedUserKeys(profile) {
1017
1024
  for (const key of Object.keys(profile)) if (!RESERVED_USER_KEYS.has(key)) out[key] = profile[key];
1018
1025
  return out;
1019
1026
  }
1027
+ const UA_BROWSERS = [
1028
+ ["Edg", "Edge"],
1029
+ ["Chrome", "Chrome"],
1030
+ ["Firefox", "Firefox"],
1031
+ ["Safari", "Safari"]
1032
+ ];
1033
+ const UA_OSES = [
1034
+ [/iPhone|iPad|iPod/, "iOS"],
1035
+ [/Android/, "Android"],
1036
+ [/Mac OS X|Macintosh/, "macOS"],
1037
+ [/Windows/, "Windows"],
1038
+ [/Linux/, "Linux"]
1039
+ ];
1040
+ /**
1041
+ * Best-effort "Browser on OS" label from a raw User-Agent string — feeds the
1042
+ * `name` field of `seenDevices` records so a device list reads "Chrome on
1043
+ * Windows" instead of a token. Deliberately tiny (no UA-parser dependency);
1044
+ * detection order lives in the `UA_BROWSERS` / `UA_OSES` tables above.
1045
+ * Returns just the browser or just the OS when only one side is detected;
1046
+ * `undefined` for empty / fully unrecognized input.
1047
+ */
1048
+ function humanizeUserAgent(ua) {
1049
+ if (!ua) return void 0;
1050
+ const browser = UA_BROWSERS.find(([token]) => ua.includes(token))?.[1];
1051
+ const os = UA_OSES.find(([pattern]) => pattern.test(ua))?.[1];
1052
+ if (browser && os) return `${browser} on ${os}`;
1053
+ return browser ?? os;
1054
+ }
1055
+ /**
1056
+ * Great-circle distance in kilometres between two coordinates (haversine,
1057
+ * WGS84 mean radius 6371 km) — for impossible-travel thresholds against
1058
+ * per-session geo metadata captured via `resolveIssueMetadata`. Pure and
1059
+ * dependency-free; aooth ships no geo resolution or default thresholds —
1060
+ * feeding coordinates in (and deciding what distance is "impossible") is
1061
+ * consumer policy.
1062
+ */
1063
+ const toRad = (deg) => deg * Math.PI / 180;
1064
+ function haversineKm(a, b) {
1065
+ const R = 6371;
1066
+ const dLat = toRad(b.lat - a.lat);
1067
+ const dLon = toRad(b.lon - a.lon);
1068
+ const h = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(a.lat)) * Math.cos(toRad(b.lat)) * Math.sin(dLon / 2) ** 2;
1069
+ return 2 * R * Math.asin(Math.sqrt(h));
1070
+ }
1020
1071
  /** Trim + de-duplicate role identifiers submitted via the admin invite form. */
1021
1072
  function parseInviteRoles(input) {
1022
1073
  if (!Array.isArray(input)) return [];
@@ -1153,6 +1204,27 @@ let AuthWorkflow = class AuthWorkflow {
1153
1204
  */
1154
1205
  deliver(_payload) {}
1155
1206
  /**
1207
+ * The blessed one-call alert path for risk overrides — emit a
1208
+ * `security-alert` delivery (e.g. from an impossible-travel
1209
+ * `resolveRiskStepUp` override). Recipient comes from `ctx.notice.email`,
1210
+ * the proven-first correspondence chain seeded by `credentials` /
1211
+ * `seedChannelState` and refreshed by `verify/email`. No recipient →
1212
+ * SILENT no-op (a user with no provable inbox simply can't be alerted —
1213
+ * mirrors `notifyNewDevice`'s posture). Never called by the base class.
1214
+ */
1215
+ async sendSecurityAlert(ctx, reason, context) {
1216
+ const recipient = ctx.notice?.email;
1217
+ if (!recipient) return;
1218
+ await this.deliver({
1219
+ kind: "security-alert",
1220
+ channel: "email",
1221
+ recipient,
1222
+ reason,
1223
+ loginAt: Date.now(),
1224
+ ...context && { context }
1225
+ });
1226
+ }
1227
+ /**
1156
1228
  * Return the list of selectable role identifiers for the admin invite form.
1157
1229
  * Mirrors the prior `InviteWorkflow.getAvailableRoles()` consumer hook —
1158
1230
  * `undefined` (default) means no whitelist is enforced. Read by
@@ -1661,6 +1733,19 @@ let AuthWorkflow = class AuthWorkflow {
1661
1733
  */
1662
1734
  resolvePromoteHandleField(_ctx, _channel) {}
1663
1735
  /**
1736
+ * Decide whether a verified federated profile's email claim counts as inbox
1737
+ * proof for the CORRESPONDENCE address (`users.setVerifiedEmail`). Default
1738
+ * trusts the provider's `email_verified` claim — a provider trusted to
1739
+ * AUTHENTICATE the user is strictly more trusted than its email claim. The
1740
+ * capture is correspondence-only: it never promotes the address to a login
1741
+ * handle and never resolves accounts by it. Override to exclude providers
1742
+ * whose claim should not be taken at face value (e.g. an internal OIDC
1743
+ * issuer that stamps `email_verified` on unverified directory entries).
1744
+ */
1745
+ resolveFederatedEmailTrust(_ctx, profile) {
1746
+ return profile.emailVerified === true;
1747
+ }
1748
+ /**
1664
1749
  * Route a form alt-action click to a canonical outcome. Defaults match the
1665
1750
  * action ids the bundled `PincodeForm` declares; customers override per
1666
1751
  * form when adding new actions or remapping the canonical ones.
@@ -2116,7 +2201,7 @@ let AuthWorkflow = class AuthWorkflow {
2116
2201
  else if (ctx.isPasswordExpired) (ctx.password ??= {}).changeReason = "expired";
2117
2202
  const email = result.user.mfa.methods.find((m) => m.name === "email" && m.confirmed);
2118
2203
  if (email) {
2119
- ctx.email = email.value;
2204
+ (ctx.notice ??= {}).email = email.value;
2120
2205
  (ctx.channel ??= {}).emailConfirmed = true;
2121
2206
  }
2122
2207
  const phone = result.user.mfa.methods.find((m) => m.name === "sms" && m.confirmed);
@@ -2125,6 +2210,7 @@ let AuthWorkflow = class AuthWorkflow {
2125
2210
  channel.phone = phone.value;
2126
2211
  channel.phoneConfirmed = true;
2127
2212
  }
2213
+ if (!ctx.notice?.email) await this.seedCorrespondenceEmail(ctx, result.user);
2128
2214
  } catch (err) {
2129
2215
  if (err instanceof UserAuthError) {
2130
2216
  if (err.type === "LOCKED") throw this.throwPublic(ctx, wf, { formMessage: "Account locked, please try again later" });
@@ -2602,7 +2688,7 @@ let AuthWorkflow = class AuthWorkflow {
2602
2688
  /** Activate the invited user account (flips the account status flag). */
2603
2689
  async activateUser(ctx) {
2604
2690
  this.requireSubject(ctx);
2605
- await this.users.activateAccount(ctx.subject);
2691
+ await this.users.activateAccount(ctx.subject, ctx.email ? { verifiedEmail: ctx.email } : {});
2606
2692
  }
2607
2693
  /**
2608
2694
  * Emit the success-confirmation envelope. The downstream `finalize-auto-
@@ -2959,7 +3045,7 @@ let AuthWorkflow = class AuthWorkflow {
2959
3045
  value,
2960
3046
  confirmed: false
2961
3047
  }));
2962
- if (isEmail) ctx.email = value;
3048
+ if (isEmail) (ctx.channel ??= {}).email = value;
2963
3049
  else (ctx.channel ??= {}).phone = value;
2964
3050
  const code = this.mintPin(ctx, this.opts.mfa.pincodeLength, this.opts.mfa.pincodeTtlMs);
2965
3051
  await this.deliver({
@@ -2986,7 +3072,7 @@ let AuthWorkflow = class AuthWorkflow {
2986
3072
  const remainingSec = Math.ceil((ctx.pincode.resendAllowedAt - Date.now()) / 1e3);
2987
3073
  throw this.throwPublic(ctx, pincodeWf, { formMessage: `Please wait ${remainingSec}s before requesting a new code.` });
2988
3074
  }
2989
- const recipient = isEmail ? ctx.email : ctx.channel?.phone;
3075
+ const recipient = isEmail ? ctx.channel?.email : ctx.channel?.phone;
2990
3076
  const code = this.mintPin(ctx, this.opts.mfa.pincodeLength, this.opts.mfa.pincodeTtlMs);
2991
3077
  await this.deliver({
2992
3078
  kind: "enroll-pincode",
@@ -3006,11 +3092,16 @@ let AuthWorkflow = class AuthWorkflow {
3006
3092
  if (pinErr) throw this.throwPublic(ctx, pincodeWf, { errors: pinErr });
3007
3093
  await this.withStoreErrorTranslation(() => this.users.confirmMfaMethod(ctx.subject, isEmail ? "email" : "sms"));
3008
3094
  const channelState = ctx.channel ??= {};
3009
- if (isEmail) channelState.emailConfirmed = true;
3010
- else channelState.phoneConfirmed = true;
3095
+ if (isEmail) {
3096
+ channelState.emailConfirmed = true;
3097
+ if (channelState.email) {
3098
+ await this.users.setVerifiedEmail(ctx.subject, channelState.email);
3099
+ (ctx.notice ??= {}).email = channelState.email;
3100
+ }
3101
+ } else channelState.phoneConfirmed = true;
3011
3102
  if (channelState.otpDisclosure) {
3012
3103
  const channelArg = isEmail ? "email" : "sms";
3013
- const target = isEmail ? ctx.email : channelState.phone;
3104
+ const target = isEmail ? channelState.email : channelState.phone;
3014
3105
  await this.consentStore.recordOtpChannelConsent(ctx.subject, channelArg, target, channelState.otpDisclosure);
3015
3106
  }
3016
3107
  delete ctx.pin;
@@ -3199,6 +3290,9 @@ let AuthWorkflow = class AuthWorkflow {
3199
3290
  code,
3200
3291
  expiresInMs: this.opts.mfa.pincodeTtlMs
3201
3292
  });
3293
+ const otp = ctx.otp ??= {};
3294
+ otp.deliveredTo = target.address;
3295
+ otp.deliveredChannel = target.channel;
3202
3296
  const pincode = ctx.pincode ??= {};
3203
3297
  pincode.sentTo = this.maskAddress(target.address, target.channel);
3204
3298
  pincode.codeLength = this.opts.mfa.pincodeLength;
@@ -3249,7 +3343,9 @@ let AuthWorkflow = class AuthWorkflow {
3249
3343
  }
3250
3344
  const pinErr = this.verifyPin(ctx, input.code);
3251
3345
  if (pinErr) throw this.throwPublic(ctx, wf, { errors: pinErr });
3252
- (ctx.otp ??= {}).verified = true;
3346
+ const otp = ctx.otp ??= {};
3347
+ otp.verified = true;
3348
+ if (ctx.subject && otp.deliveredChannel === "email" && otp.deliveredTo) await this.users.setVerifiedEmail(ctx.subject, otp.deliveredTo);
3253
3349
  (ctx.session ??= {}).riskStepUpEvaluated = false;
3254
3350
  if (ctx.deviceTrust?.enabled && ctx.deviceTrust?.optIn) (ctx.trust ??= {}).rememberDevice = Boolean(input.rememberDevice);
3255
3351
  delete ctx.pin;
@@ -3449,6 +3545,7 @@ let AuthWorkflow = class AuthWorkflow {
3449
3545
  value,
3450
3546
  confirmed: true
3451
3547
  }));
3548
+ if (methodName === "email") await this.users.setVerifiedEmail(username, value);
3452
3549
  if (!m.keepExistingDefault) await this.users.setDefaultMfaMethod(username, methodName);
3453
3550
  m.done = true;
3454
3551
  (ctx.otp ??= {}).verified = true;
@@ -3545,6 +3642,47 @@ let AuthWorkflow = class AuthWorkflow {
3545
3642
  (ctx.trust ??= {}).deviceTrustToken = record.token;
3546
3643
  }
3547
3644
  /**
3645
+ * Always-on device RECOGNITION — verify-or-mint the long-lived recognition
3646
+ * cookie against the `seenDevices` ledger. Recognition is a notification
3647
+ * suppressor ONLY (the notify-new-device gate reads `trust.recognized`); it
3648
+ * never skips MFA — that is `deviceTrust`, which stays opt-in and strict.
3649
+ *
3650
+ * Deliberately a SEPARATE step from `check-trusted-device`: that step is
3651
+ * schema-gated on `deviceTrust.enabled && skipsMfa`, so recognition must
3652
+ * not piggyback on it or recognition dies whenever trust is disabled —
3653
+ * exactly the consumers who get the noisiest notify behaviour today.
3654
+ * Verify-or-mint lives in ONE step so `trust.recognized` captures the
3655
+ * PRE-MINT arrival state the notify gate needs (a freshly minted token
3656
+ * must not mark the current login as recognized).
3657
+ *
3658
+ * A valid arriving cookie is verified with `slideTtlMs` (LRU bump) and
3659
+ * re-stashed on `trust.seenDeviceToken` so `issue` re-sets it with a fresh
3660
+ * maxAge. An unrecognized arrival mints + persists a new record (capped at
3661
+ * `deviceRecognition.maxDevices`) and stashes the new token — `recognized`
3662
+ * stays unset so the notification still fires for this login. Degrades
3663
+ * gracefully to a no-op when no `deviceTrust.secret` is configured,
3664
+ * preserving the legacy notify behaviour for those consumers.
3665
+ */
3666
+ async deviceRecognition(ctx) {
3667
+ if (!ctx.subject) return void 0;
3668
+ if (!this.users.hasDeviceTrustSecret()) return void 0;
3669
+ const cookieValue = useCookies(current()).getCookie(this.opts.deviceRecognition.cookieName);
3670
+ const trust = ctx.trust ??= {};
3671
+ if (cookieValue) {
3672
+ if (await this.users.verifySeenDevice(ctx.subject, cookieValue, { slideTtlMs: this.opts.deviceRecognition.ttlMs })) {
3673
+ trust.recognized = true;
3674
+ trust.seenDeviceToken = cookieValue;
3675
+ return;
3676
+ }
3677
+ }
3678
+ const record = this.users.issueSeenDevice(ctx.subject, {
3679
+ ttlMs: this.opts.deviceRecognition.ttlMs,
3680
+ name: humanizeUserAgent(this.resolveUserAgent())
3681
+ });
3682
+ await this.users.addSeenDevice(ctx.subject, record, { cap: this.opts.deviceRecognition.maxDevices });
3683
+ trust.seenDeviceToken = record.token;
3684
+ }
3685
+ /**
3548
3686
  * Standalone terms-bump prompt for returning users whose accepted terms
3549
3687
  * version is stale and no carrier form ran. Delegates to
3550
3688
  * `processInlineConsent` for validation + ctx writes.
@@ -3620,14 +3758,8 @@ let AuthWorkflow = class AuthWorkflow {
3620
3758
  */
3621
3759
  async revokeSessions(ctx) {
3622
3760
  if (!ctx.subject) return void 0;
3623
- if (ctx.changePassword?.revokeOtherSessions) {
3624
- const currentSessionId = useAuth().getSessionId();
3625
- if (currentSessionId) {
3626
- await this.auth.revokeOtherSessions(ctx.subject, currentSessionId);
3627
- return;
3628
- }
3629
- }
3630
- await this.auth.revokeAllForUser(ctx.subject);
3761
+ const currentSessionId = ctx.changePassword?.revokeOtherSessions ? useAuth().getSessionId() : void 0;
3762
+ await Promise.all([currentSessionId ? this.auth.revokeOtherSessions(ctx.subject, currentSessionId) : this.auth.revokeAllForUser(ctx.subject), this.users.revokeSeenDevices(ctx.subject)]);
3631
3763
  }
3632
3764
  /**
3633
3765
  * Lift a failed-login lockout after a successful password reset. Recovery
@@ -3653,14 +3785,19 @@ let AuthWorkflow = class AuthWorkflow {
3653
3785
  finished: true,
3654
3786
  data: auth.buildLoginResponse(ctx.subject, issue)
3655
3787
  };
3656
- const sessionCookies = auth.buildFinishedCookies(issue);
3657
- const cookies = ctx.trust?.deviceTrustToken ? {
3658
- ...sessionCookies,
3659
- [this.opts.deviceTrust.cookieName]: {
3660
- value: ctx.trust.deviceTrustToken,
3661
- options: auth.cookieAttrs({ maxAge: this.opts.deviceTrust.ttlMs / 1e3 })
3662
- }
3663
- } : sessionCookies;
3788
+ let cookies = auth.buildFinishedCookies(issue);
3789
+ const attachDeviceCookie = (name, value, ttlMs) => {
3790
+ if (!value) return;
3791
+ cookies = {
3792
+ ...cookies,
3793
+ [name]: {
3794
+ value,
3795
+ options: auth.cookieAttrs({ maxAge: ttlMs / 1e3 })
3796
+ }
3797
+ };
3798
+ };
3799
+ attachDeviceCookie(this.opts.deviceTrust.cookieName, ctx.trust?.deviceTrustToken, this.opts.deviceTrust.ttlMs);
3800
+ attachDeviceCookie(this.opts.deviceRecognition.cookieName, ctx.trust?.seenDeviceToken, this.opts.deviceRecognition.ttlMs);
3664
3801
  useWfFinished().set({
3665
3802
  type: "data",
3666
3803
  value: envelope,
@@ -3670,14 +3807,24 @@ let AuthWorkflow = class AuthWorkflow {
3670
3807
  /**
3671
3808
  * Notify the user of a login from a new device via the unified `deliver`
3672
3809
  * hook. Gated upstream by
3673
- * `!ctx.isFirstLogin && !!ctx.finalize.notifyNewDevice && !!ctx.trust.newDevice`.
3810
+ * `!ctx.isFirstLogin && !!ctx.finalize.notifyNewDevice && !ctx.trust.recognized`
3811
+ * — "not recognized" (no valid recognition cookie on arrival), NOT "no
3812
+ * valid trust cookie": users who decline remember-me, or whose strict trust
3813
+ * cookie expired / failed IP binding, must not get the email on every
3814
+ * login. Recognition is the loose always-on ledger minted by
3815
+ * `device-recognition`; trust stays strict and drives MFA skip only.
3816
+ *
3817
+ * Recipient is `notice.email` — the security-notice slot owned by the
3818
+ * `credentials` / `seedChannelState` seeding and refreshed by
3819
+ * `verify/email`. No recipient seeded → silently skips.
3674
3820
  */
3675
3821
  async notifyNewDevice(ctx) {
3676
- if (!ctx.email) return void 0;
3822
+ const recipient = ctx.notice?.email;
3823
+ if (!recipient) return void 0;
3677
3824
  await this.deliver({
3678
3825
  kind: "new-device-notice",
3679
3826
  channel: "email",
3680
- recipient: ctx.email,
3827
+ recipient,
3681
3828
  loginAt: Date.now()
3682
3829
  });
3683
3830
  }
@@ -4149,7 +4296,7 @@ let AuthWorkflow = class AuthWorkflow {
4149
4296
  });
4150
4297
  if (Object.keys(extras).length > 0) await this.users.update(outcome.userId, extras);
4151
4298
  }
4152
- this.seedChannelState(ctx, user, profile.email);
4299
+ await this.seedChannelState(ctx, user, profile);
4153
4300
  swapStrategy("store");
4154
4301
  }
4155
4302
  /**
@@ -4176,19 +4323,31 @@ let AuthWorkflow = class AuthWorkflow {
4176
4323
  } });
4177
4324
  }
4178
4325
  /**
4179
- * Seed `ctx.email` / `ctx.channel` from a resolved user's confirmed channels —
4326
+ * Seed `ctx.notice.email` / `ctx.channel` from a resolved user's confirmed channels —
4180
4327
  * shared by `ssoCallback` (linked / created / auto-linked) and `proveControl`
4181
4328
  * (interactively-linked) so the post-success channel shape can't drift between
4182
- * the two federated entry points. Mirrors `credentials`' post-login seeding.
4183
- * `fallbackEmail` (the provider / snapshot email) is a DISPLAY fallback only —
4184
- * never promoted to the unique login handle (a gated, later-phase concern).
4185
- */
4186
- seedChannelState(ctx, user, fallbackEmail) {
4329
+ * the two federated entry points. Mirrors `credentials`' post-login seeding,
4330
+ * including the correspondence fallback (confirmed email-MFA
4331
+ * `users.getCorrespondenceEmail` provider display email).
4332
+ *
4333
+ * Also the single federated capture point for `users.setVerifiedEmail`: a
4334
+ * trusted `profile.email` (per `resolveFederatedEmailTrust`) is recorded as
4335
+ * the proven correspondence address on EVERY federated login — first-time
4336
+ * create, returning link, and interactive link alike (the store write is
4337
+ * skipped when the capture is already current). The profile email is
4338
+ * otherwise a DISPLAY fallback only — never promoted to the unique login
4339
+ * handle (a gated, later-phase concern).
4340
+ */
4341
+ async seedChannelState(ctx, user, profile) {
4342
+ if (profile?.email && user.account.verifiedEmail !== profile.email && await this.resolveFederatedEmailTrust(ctx, profile)) {
4343
+ await this.users.setVerifiedEmail(user.id, profile.email);
4344
+ user.account.verifiedEmail = profile.email;
4345
+ }
4187
4346
  const email = user.mfa.methods.find((m) => m.name === "email" && m.confirmed);
4188
4347
  if (email) {
4189
- ctx.email = email.value;
4348
+ (ctx.notice ??= {}).email = email.value;
4190
4349
  (ctx.channel ??= {}).emailConfirmed = true;
4191
- } else if (fallbackEmail) ctx.email = fallbackEmail;
4350
+ } else await this.seedCorrespondenceEmail(ctx, user, profile?.email);
4192
4351
  const phone = user.mfa.methods.find((m) => m.name === "sms" && m.confirmed);
4193
4352
  if (phone) {
4194
4353
  const channel = ctx.channel ??= {};
@@ -4197,6 +4356,19 @@ let AuthWorkflow = class AuthWorkflow {
4197
4356
  }
4198
4357
  }
4199
4358
  /**
4359
+ * Correspondence tail of the `ctx.notice.email` seeding — shared by
4360
+ * `credentials` (post-login) and `seedChannelState` (federated) so the
4361
+ * fallback chain (`users.getCorrespondenceEmail` → optional display email)
4362
+ * can't drift between the two. Does NOT set `channel.emailConfirmed` — that
4363
+ * flag means "confirmed email-MFA channel" and gates enrolment; a
4364
+ * correspondence address is a notice recipient, not a proven OTP channel.
4365
+ */
4366
+ async seedCorrespondenceEmail(ctx, user, displayEmail) {
4367
+ const correspondence = await this.users.getCorrespondenceEmail(user);
4368
+ if (correspondence) (ctx.notice ??= {}).email = correspondence;
4369
+ else if (displayEmail) (ctx.notice ??= {}).email = displayEmail;
4370
+ }
4371
+ /**
4200
4372
  * `needs-link` setup (decision A — password, OTP fallback). Decide how the
4201
4373
  * user will prove control of the matched account, stash the pending-link
4202
4374
  * state, and return so the `prove-control` @Step (gated on `ctx.pendingLink`)
@@ -4351,7 +4523,7 @@ let AuthWorkflow = class AuthWorkflow {
4351
4523
  isNew: false,
4352
4524
  ...pending.redirect ? { redirect: pending.redirect } : {}
4353
4525
  };
4354
- this.seedChannelState(ctx, candidate, pending.snapshot?.email);
4526
+ await this.seedChannelState(ctx, candidate, pending.snapshot);
4355
4527
  delete ctx.pendingLink;
4356
4528
  swapStrategy("store");
4357
4529
  }
@@ -4947,6 +5119,14 @@ __decorate([
4947
5119
  __decorateMetadata("design:paramtypes", [Object]),
4948
5120
  __decorateMetadata("design:returntype", Promise)
4949
5121
  ], AuthWorkflow.prototype, "deviceTrust", null);
5122
+ __decorate([
5123
+ Step("device-recognition"),
5124
+ Public(),
5125
+ __decorateParam(0, WorkflowParam("context")),
5126
+ __decorateMetadata("design:type", Function),
5127
+ __decorateMetadata("design:paramtypes", [Object]),
5128
+ __decorateMetadata("design:returntype", Promise)
5129
+ ], AuthWorkflow.prototype, "deviceRecognition", null);
4950
5130
  __decorate([
4951
5131
  Step("terms-bump-prompt"),
4952
5132
  Public(),
@@ -5179,11 +5359,11 @@ __decorate([
5179
5359
  },
5180
5360
  {
5181
5361
  id: "ask/email",
5182
- condition: (ctx) => (!!ctx.enrollment?.ensureEmail || !!ctx.guards?.emailVerifiedRequired) && !ctx.email
5362
+ condition: (ctx) => (!!ctx.enrollment?.ensureEmail || !!ctx.guards?.emailVerifiedRequired) && !ctx.channel?.email && !ctx.channel?.emailConfirmed
5183
5363
  },
5184
5364
  {
5185
5365
  id: "verify/email",
5186
- condition: (ctx) => (!!ctx.enrollment?.ensureEmail || !!ctx.guards?.emailVerifiedRequired) && !!ctx.email && !ctx.channel?.emailConfirmed
5366
+ condition: (ctx) => (!!ctx.enrollment?.ensureEmail || !!ctx.guards?.emailVerifiedRequired) && !!ctx.channel?.email && !ctx.channel?.emailConfirmed
5187
5367
  },
5188
5368
  {
5189
5369
  id: "ask/phone",
@@ -5218,10 +5398,11 @@ __decorate([
5218
5398
  {
5219
5399
  condition: (ctx) => !ctx.authz,
5220
5400
  steps: [
5401
+ { id: "device-recognition" },
5221
5402
  { id: "issue" },
5222
5403
  {
5223
5404
  id: "notify-new-device",
5224
- condition: (ctx) => !ctx.isFirstLogin && !!ctx.finalize?.notifyNewDevice && !!ctx.trust?.newDevice
5405
+ condition: (ctx) => !ctx.isFirstLogin && !!ctx.finalize?.notifyNewDevice && !ctx.trust?.recognized
5225
5406
  },
5226
5407
  { id: "redirect" }
5227
5408
  ]
@@ -6437,4 +6618,4 @@ function createAuthEmailOutlet(deps) {
6437
6618
  });
6438
6619
  }
6439
6620
  //#endregion
6440
- export { ADD_MFA_WORKFLOW, AUTHZ_BINDING_COOKIE, AUTH_CODE_STORE_TOKEN, AuthController, AuthGuarded, AuthWorkflow, AuthorizeController, AuthorizeRuntime, CHANGE_PASSWORD_WORKFLOW, CLIENT_REDIRECT_POLICY_TOKEN, ConsentStore, DEFAULT_AUTH_WORKFLOWS, DYNAMIC_CLIENT_STORE_TOKEN, DynamicClientRegistration, FEDERATED_IDENTITY_STORE_TOKEN, OAUTH_CSRF_COOKIE, OAuthController, OAuthRuntime, PENDING_AUTHORIZATION_STORE_TOKEN, Public, RESERVED_USER_KEYS, SessionEnricherProvider, SessionsController, UserId, WfTrigger, WfTriggerProvider, authGuardInterceptor, authzBindingCookieAttrs, buildAuthorizationServerMetadata, buildInviteAlreadyAcceptedEnvelope, buildProtectedResourceMetadata, buildWwwAuthenticateBearerChallenge, canonicalizeIssuer, createAuthEmailOutlet, deriveWfStateSecret, generateMagicLinkToken, getAuthMate, isSafeRelativeRedirect, oauthCsrfCookieAttrs, parseInviteRoles, resolveOAuthRedirect, stripReservedUserKeys, useAuth };
6621
+ export { ADD_MFA_WORKFLOW, AUTHZ_BINDING_COOKIE, AUTH_CODE_STORE_TOKEN, AuthController, AuthGuarded, AuthWorkflow, AuthorizeController, AuthorizeRuntime, CHANGE_PASSWORD_WORKFLOW, CLIENT_REDIRECT_POLICY_TOKEN, ConsentStore, DEFAULT_AUTH_WORKFLOWS, DYNAMIC_CLIENT_STORE_TOKEN, DynamicClientRegistration, FEDERATED_IDENTITY_STORE_TOKEN, OAUTH_CSRF_COOKIE, OAuthController, OAuthRuntime, PENDING_AUTHORIZATION_STORE_TOKEN, Public, RESERVED_USER_KEYS, SessionEnricherProvider, SessionsController, UserId, WfTrigger, WfTriggerProvider, authGuardInterceptor, authzBindingCookieAttrs, buildAuthorizationServerMetadata, buildInviteAlreadyAcceptedEnvelope, buildProtectedResourceMetadata, buildWwwAuthenticateBearerChallenge, canonicalizeIssuer, createAuthEmailOutlet, deriveWfStateSecret, generateMagicLinkToken, getAuthMate, haversineKm, humanizeUserAgent, isSafeRelativeRedirect, oauthCsrfCookieAttrs, parseInviteRoles, resolveOAuthRedirect, stripReservedUserKeys, useAuth };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aooth/auth-moost",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Moost auth integration for aoothjs — AuthGuard interceptor, useAuth composable, REST endpoints, workflows",
5
5
  "keywords": [
6
6
  "aoothjs",
@@ -57,33 +57,33 @@
57
57
  "access": "public"
58
58
  },
59
59
  "dependencies": {
60
- "@atscript/moost-wf": "^0.1.98",
60
+ "@atscript/moost-wf": "^0.1.100",
61
61
  "@wooksjs/http-body": "^0.7.19",
62
- "@aooth/auth": "0.1.19",
63
- "@aooth/arbac-moost": "^0.1.19",
64
- "@aooth/idp": "0.1.19",
65
- "@aooth/user": "0.1.19"
62
+ "@aooth/arbac-moost": "^0.1.21",
63
+ "@aooth/auth": "0.1.21",
64
+ "@aooth/idp": "0.1.21",
65
+ "@aooth/user": "0.1.21"
66
66
  },
67
67
  "devDependencies": {
68
- "@atscript/core": "^0.1.75",
69
- "@atscript/typescript": "^0.1.75",
70
- "@atscript/ui": "^0.1.98",
71
- "@atscript/ui-fns": "^0.1.98",
72
- "@moostjs/event-http": "^0.6.26",
73
- "@moostjs/event-wf": "^0.6.26",
74
- "moost": "^0.6.26",
75
- "unplugin-atscript": "^0.1.75",
68
+ "@atscript/core": "^0.1.76",
69
+ "@atscript/typescript": "^0.1.76",
70
+ "@atscript/ui": "^0.1.100",
71
+ "@atscript/ui-fns": "^0.1.100",
72
+ "@moostjs/event-http": "^0.6.27",
73
+ "@moostjs/event-wf": "^0.6.27",
74
+ "moost": "^0.6.27",
75
+ "unplugin-atscript": "^0.1.76",
76
76
  "wooks": "^0.7.19"
77
77
  },
78
78
  "peerDependencies": {
79
- "@atscript/moost-wf": "^0.1.98",
80
- "@atscript/typescript": "^0.1.75",
81
- "@moostjs/event-http": "^0.6.26",
82
- "@moostjs/event-wf": "^0.6.26",
79
+ "@atscript/moost-wf": "^0.1.100",
80
+ "@atscript/typescript": "^0.1.76",
81
+ "@moostjs/event-http": "^0.6.27",
82
+ "@moostjs/event-wf": "^0.6.27",
83
83
  "@wooksjs/event-core": "^0.7.19",
84
84
  "@wooksjs/event-http": "^0.7.19",
85
85
  "@wooksjs/http-body": "^0.7.19",
86
- "moost": "^0.6.26"
86
+ "moost": "^0.6.27"
87
87
  },
88
88
  "peerDependenciesMeta": {
89
89
  "@atscript/typescript": {