@aooth/auth-moost 0.1.6 → 0.1.7

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,2 @@
1
- import { C as TermsBumpForm, S as TenantSelectForm, _ as ProfileCompleteForm, a as EmailIdentifierForm, b as Select2faForm, c as EnrollPickMethodForm, d as InviteSendModeForm, f as LoginCredentialsForm, g as PincodeForm, h as PersonaSelectForm, i as ConcurrencyLimitForm, l as InviteEmailForm, m as MfaCodeForm, n as AskPhoneForm, o as EnrollAddressForm, p as MagicLinkRequestForm, r as BackupCodeForm, s as EnrollConfirmForm, t as AskEmailForm, u as InviteForm, v as RecoveryFactorForm, w as WithInlineConsentForm, x as SetPasswordForm, y as RecoveryModeSelectForm } from "../forms-sF41Fzzn.mjs";
1
+ import { C as TermsBumpForm, S as TenantSelectForm, _ as ProfileCompleteForm, a as EmailIdentifierForm, b as Select2faForm, c as EnrollPickMethodForm, d as InviteSendModeForm, f as LoginCredentialsForm, g as PincodeForm, h as PersonaSelectForm, i as ConcurrencyLimitForm, l as InviteEmailForm, m as MfaCodeForm, n as AskPhoneForm, o as EnrollAddressForm, p as MagicLinkRequestForm, r as BackupCodeForm, s as EnrollConfirmForm, t as AskEmailForm, u as InviteForm, v as RecoveryFactorForm, w as WithInlineConsentForm, x as SetPasswordForm, y as RecoveryModeSelectForm } from "../forms-DtQMdkA_.mjs";
2
2
  export { AskEmailForm, AskPhoneForm, BackupCodeForm, ConcurrencyLimitForm, EmailIdentifierForm, EnrollAddressForm, EnrollConfirmForm, EnrollPickMethodForm, InviteEmailForm, InviteForm, InviteSendModeForm, LoginCredentialsForm, MagicLinkRequestForm, MfaCodeForm, PersonaSelectForm, PincodeForm, ProfileCompleteForm, RecoveryFactorForm, RecoveryModeSelectForm, Select2faForm, SetPasswordForm, TenantSelectForm, TermsBumpForm, WithInlineConsentForm };
@@ -1,2 +1,2 @@
1
- import { C as TermsBumpForm, S as TenantSelectForm, _ as ProfileCompleteForm, a as EmailIdentifierForm, b as Select2faForm, c as EnrollPickMethodForm, d as InviteSendModeForm, f as LoginCredentialsForm, g as PincodeForm, h as PersonaSelectForm, i as ConcurrencyLimitForm, l as InviteEmailForm, m as MfaCodeForm, n as AskPhoneForm, o as EnrollAddressForm, p as MagicLinkRequestForm, r as BackupCodeForm, s as EnrollConfirmForm, t as AskEmailForm, u as InviteForm, v as RecoveryFactorForm, w as WithInlineConsentForm, x as SetPasswordForm, y as RecoveryModeSelectForm } from "../forms-sF41Fzzn.mjs";
1
+ import { C as TermsBumpForm, S as TenantSelectForm, _ as ProfileCompleteForm, a as EmailIdentifierForm, b as Select2faForm, c as EnrollPickMethodForm, d as InviteSendModeForm, f as LoginCredentialsForm, g as PincodeForm, h as PersonaSelectForm, i as ConcurrencyLimitForm, l as InviteEmailForm, m as MfaCodeForm, n as AskPhoneForm, o as EnrollAddressForm, p as MagicLinkRequestForm, r as BackupCodeForm, s as EnrollConfirmForm, t as AskEmailForm, u as InviteForm, v as RecoveryFactorForm, w as WithInlineConsentForm, x as SetPasswordForm, y as RecoveryModeSelectForm } from "../forms-DtQMdkA_.mjs";
2
2
  export { AskEmailForm, AskPhoneForm, BackupCodeForm, ConcurrencyLimitForm, EmailIdentifierForm, EnrollAddressForm, EnrollConfirmForm, EnrollPickMethodForm, InviteEmailForm, InviteForm, InviteSendModeForm, LoginCredentialsForm, MagicLinkRequestForm, MfaCodeForm, PersonaSelectForm, PincodeForm, ProfileCompleteForm, RecoveryFactorForm, RecoveryModeSelectForm, Select2faForm, SetPasswordForm, TenantSelectForm, TermsBumpForm, WithInlineConsentForm };
@@ -264,7 +264,7 @@ defineAnnotatedType("object", SetPasswordForm).prop("consents", defineAnnotatedT
264
264
  }).optional().$type).prop("backToLogin", defineAnnotatedType().designType("phantom").tags("action", "ui").annotate("ui.form.order", 50).annotate("ui.form.action", {
265
265
  id: "backToLogin",
266
266
  label: "Back to sign-in"
267
- }).optional().$type).annotate("wf.context.pass", "passwordPolicies", true).annotate("wf.context.pass", "pendingConsents", true).annotate("wf.context.pass", "consentsPersisted", true);
267
+ }).optional().$type).annotate("wf.context.pass", "passwordPolicies", true).annotate("wf.context.pass", "passwordChangeReason", true).annotate("wf.context.pass", "pendingConsents", true).annotate("wf.context.pass", "consentsPersisted", true);
268
268
  defineAnnotatedType("object", InviteForm).prop("email", defineAnnotatedType().designType("string").tags("email", "string").annotate("ui.form.type", "text").annotate("meta.label", "Email").annotate("ui.form.autocomplete", "email").annotate("meta.required", {}).annotate("expect.pattern", {
269
269
  pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$",
270
270
  flags: "",
package/dist/index.d.mts CHANGED
@@ -898,6 +898,24 @@ interface LoginWfCtx {
898
898
  /** Legacy alias for `pwReset`; kept until tests migrate. */
899
899
  mfaRequired?: boolean;
900
900
  isPasswordInitial?: boolean;
901
+ /**
902
+ * Set in `credentials` when `guards.passwordExpiry` is true AND
903
+ * `UserService.isPasswordExpired(user)` returns true. Combined with
904
+ * `isPasswordInitial` in the forced-change schema condition — either
905
+ * being truthy routes the user through `prepare-password-rules` +
906
+ * `create-password-form`. Reset after `create-password-form` commits.
907
+ */
908
+ isPasswordExpired?: boolean;
909
+ /**
910
+ * Discriminator for `SetPasswordForm` UX. `'initial'` when the
911
+ * password has never been used (first-set flow); `'expired'` when the
912
+ * password aged past `config.password.maxAgeMs` (rotation flow). When
913
+ * both conditions are true, `'initial'` wins — a never-used password
914
+ * being "expired" is semantically still its initial set. Shipped to
915
+ * the client via `@wf.context.pass` on `SetPasswordForm` so the SPA
916
+ * can swap banner copy; default form labels stay reason-agnostic.
917
+ */
918
+ passwordChangeReason?: "initial" | "expired";
901
919
  usedMagicLink?: boolean;
902
920
  /**
903
921
  * 3-state MFA policy:
@@ -1206,6 +1224,14 @@ declare class LoginWorkflow extends AuthWorkflowBase {
1206
1224
  * Resolve the guards policy (passwordInitial / passwordExpiry /
1207
1225
  * emailVerifiedRequired). Override per-tenant to tighten or loosen the
1208
1226
  * post-credentials gates. Sync/async friendly.
1227
+ *
1228
+ * The `passwordExpiry` flag (default `true`) is the per-tenant escape
1229
+ * hatch for rotation policy: flip to `false` for SSO-only tenants
1230
+ * where the IdP owns rotation, or for service accounts where forced
1231
+ * change would break automation. When `true`, the `credentials` step
1232
+ * consults `UserService.isPasswordExpired(user)` — which is itself
1233
+ * gated on `config.password.maxAgeMs`, so an unset `maxAgeMs` already
1234
+ * disables expiry independent of this flag.
1209
1235
  */
1210
1236
  protected resolveGuards(_ctx: LoginWfCtx): NonNullable<LoginWfCtx["guards"]> | Promise<NonNullable<LoginWfCtx["guards"]>>;
1211
1237
  /**
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { C as TermsBumpForm, S as TenantSelectForm, _ as ProfileCompleteForm, a as EmailIdentifierForm, b as Select2faForm, c as EnrollPickMethodForm, d as InviteSendModeForm, f as LoginCredentialsForm, g as PincodeForm, h as PersonaSelectForm, i as ConcurrencyLimitForm, l as InviteEmailForm, m as MfaCodeForm, n as AskPhoneForm, o as EnrollAddressForm, r as BackupCodeForm, s as EnrollConfirmForm, t as AskEmailForm, u as InviteForm, v as RecoveryFactorForm, x as SetPasswordForm, y as RecoveryModeSelectForm } from "./forms-sF41Fzzn.mjs";
1
+ import { C as TermsBumpForm, S as TenantSelectForm, _ as ProfileCompleteForm, a as EmailIdentifierForm, b as Select2faForm, c as EnrollPickMethodForm, d as InviteSendModeForm, f as LoginCredentialsForm, g as PincodeForm, h as PersonaSelectForm, i as ConcurrencyLimitForm, l as InviteEmailForm, m as MfaCodeForm, n as AskPhoneForm, o as EnrollAddressForm, r as BackupCodeForm, s as EnrollConfirmForm, t as AskEmailForm, u as InviteForm, v as RecoveryFactorForm, x as SetPasswordForm, y as RecoveryModeSelectForm } from "./forms-DtQMdkA_.mjs";
2
2
  import { Controller, Inherit, Injectable, Intercept, Optional, Param, Resolve, TInterceptorPriority, defineAfterInterceptor, defineBeforeInterceptor, getMoostMate, useControllerContext } from "moost";
3
3
  import { AuthCredential, AuthError, generateMagicLinkToken } from "@aooth/auth";
4
4
  import { current, defineWook, eventTypeKey, key } from "@wooksjs/event-core";
@@ -2442,6 +2442,14 @@ let LoginWorkflow = class LoginWorkflow extends AuthWorkflowBase {
2442
2442
  * Resolve the guards policy (passwordInitial / passwordExpiry /
2443
2443
  * emailVerifiedRequired). Override per-tenant to tighten or loosen the
2444
2444
  * post-credentials gates. Sync/async friendly.
2445
+ *
2446
+ * The `passwordExpiry` flag (default `true`) is the per-tenant escape
2447
+ * hatch for rotation policy: flip to `false` for SSO-only tenants
2448
+ * where the IdP owns rotation, or for service accounts where forced
2449
+ * change would break automation. When `true`, the `credentials` step
2450
+ * consults `UserService.isPasswordExpired(user)` — which is itself
2451
+ * gated on `config.password.maxAgeMs`, so an unset `maxAgeMs` already
2452
+ * disables expiry independent of this flag.
2445
2453
  */
2446
2454
  resolveGuards(_ctx) {
2447
2455
  return {
@@ -2653,6 +2661,9 @@ let LoginWorkflow = class LoginWorkflow extends AuthWorkflowBase {
2653
2661
  ctx.username = result.user.username;
2654
2662
  ctx.mfaRequired = result.mfaRequired;
2655
2663
  if (ctx.guards?.passwordInitial && result.user.password.isInitial) ctx.isPasswordInitial = true;
2664
+ if (ctx.guards?.passwordExpiry && this.users.isPasswordExpired(result.user)) ctx.isPasswordExpired = true;
2665
+ if (ctx.isPasswordInitial) ctx.passwordChangeReason = "initial";
2666
+ else if (ctx.isPasswordExpired) ctx.passwordChangeReason = "expired";
2656
2667
  const email = result.user.mfa.methods.find((m) => m.name === "email" && m.confirmed);
2657
2668
  if (email) {
2658
2669
  ctx.email = email.value;
@@ -3099,6 +3110,8 @@ let LoginWorkflow = class LoginWorkflow extends AuthWorkflowBase {
3099
3110
  this.processInlineConsent(ctx, input, wf);
3100
3111
  ctx.passwordChanged = true;
3101
3112
  ctx.isPasswordInitial = false;
3113
+ ctx.isPasswordExpired = false;
3114
+ delete ctx.passwordChangeReason;
3102
3115
  }
3103
3116
  async profileComplete(ctx) {
3104
3117
  const wf = useAtscriptWf(this.opts.forms.profileComplete);
@@ -3475,7 +3488,7 @@ __decorate([
3475
3488
  condition: (ctx) => !!ctx.deviceTrust?.enabled && !!ctx.mfaChecked && !!ctx.newDevice && (!ctx.deviceTrust?.optIn || !!ctx.rememberDevice)
3476
3489
  },
3477
3490
  {
3478
- condition: (ctx) => !!ctx.isPasswordInitial && !ctx.passwordChanged,
3491
+ condition: (ctx) => (!!ctx.isPasswordInitial || !!ctx.isPasswordExpired) && !ctx.passwordChanged,
3479
3492
  steps: [{ id: "prepare-password-rules" }, { id: "create-password-form" }]
3480
3493
  },
3481
3494
  { break: (ctx) => !!ctx.aborted },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aooth/auth-moost",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Moost auth integration for aoothjs — AuthGuard interceptor, useAuth composable, REST endpoints, workflows",
5
5
  "keywords": [
6
6
  "aoothjs",
@@ -51,17 +51,17 @@
51
51
  "access": "public"
52
52
  },
53
53
  "dependencies": {
54
- "@atscript/moost-wf": "^0.1.78",
54
+ "@atscript/moost-wf": "^0.1.79",
55
55
  "@wooksjs/http-body": "^0.7.15",
56
- "@aooth/arbac-moost": "^0.1.6",
57
- "@aooth/auth": "0.1.6",
58
- "@aooth/user": "0.1.6"
56
+ "@aooth/arbac-moost": "^0.1.7",
57
+ "@aooth/auth": "0.1.7",
58
+ "@aooth/user": "0.1.7"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@atscript/core": "^0.1.64",
62
62
  "@atscript/typescript": "^0.1.64",
63
- "@atscript/ui": "^0.1.78",
64
- "@atscript/ui-fns": "^0.1.78",
63
+ "@atscript/ui": "^0.1.79",
64
+ "@atscript/ui-fns": "^0.1.79",
65
65
  "@moostjs/event-http": "^0.6.17",
66
66
  "@moostjs/event-wf": "^0.6.17",
67
67
  "moost": "^0.6.17",
@@ -181,8 +181,16 @@ export interface EmailIdentifierForm {
181
181
  * `data.newPassword` so the rule-fulfillment readout updates live on every
182
182
  * keystroke. `WithInlineConsentForm` continues to supply the inline-consent
183
183
  * `consents: string[]` block via `AsConsentArray` (Phase 5).
184
+ *
185
+ * `@wf.context.pass 'passwordChangeReason'` whitelists the workflow ctx key
186
+ * carrying `'initial' | 'expired'` — set by `LoginWorkflow.credentials` when
187
+ * the forced-change branch fires. Default form labels stay reason-agnostic;
188
+ * downstream SPAs can read the value and override banner copy via a
189
+ * sibling `ui.paragraph` with an `@ui.form.fn.value` expression (mirroring
190
+ * the Phase-3 `EnrollConfirmForm.transportHint` pattern).
184
191
  */
185
192
  @wf.context.pass 'passwordPolicies'
193
+ @wf.context.pass 'passwordChangeReason'
186
194
  @wf.context.pass 'pendingConsents'
187
195
  @wf.context.pass 'consentsPersisted'
188
196
  export interface SetPasswordForm extends WithInlineConsentForm {
@@ -98,7 +98,7 @@ export declare class EmailIdentifierForm {
98
98
 
99
99
  /**
100
100
  * Atscript interface **SetPasswordForm**
101
- * @see {@link ./forms.as:188:18}
101
+ * @see {@link ./forms.as:196:18}
102
102
  */
103
103
  export declare class SetPasswordForm extends WithInlineConsentForm {
104
104
  newPassword: string
@@ -119,7 +119,7 @@ export declare class SetPasswordForm extends WithInlineConsentForm {
119
119
 
120
120
  /**
121
121
  * Atscript interface **InviteForm**
122
- * @see {@link ./forms.as:251:18}
122
+ * @see {@link ./forms.as:259:18}
123
123
  */
124
124
  export declare class InviteForm {
125
125
  email: string /* email */
@@ -139,7 +139,7 @@ export declare class InviteForm {
139
139
 
140
140
  /**
141
141
  * Atscript interface **InviteEmailForm**
142
- * @see {@link ./forms.as:282:18}
142
+ * @see {@link ./forms.as:290:18}
143
143
  */
144
144
  export declare class InviteEmailForm {
145
145
  email: string /* email */
@@ -156,7 +156,7 @@ export declare class InviteEmailForm {
156
156
 
157
157
  /**
158
158
  * Atscript interface **InviteSendModeForm**
159
- * @see {@link ./forms.as:298:18}
159
+ * @see {@link ./forms.as:306:18}
160
160
  */
161
161
  export declare class InviteSendModeForm {
162
162
  mode: string
@@ -173,7 +173,7 @@ export declare class InviteSendModeForm {
173
173
 
174
174
  /**
175
175
  * Atscript interface **Select2faForm**
176
- * @see {@link ./forms.as:322:18}
176
+ * @see {@link ./forms.as:330:18}
177
177
  */
178
178
  export declare class Select2faForm {
179
179
  methodName: string
@@ -191,7 +191,7 @@ export declare class Select2faForm {
191
191
 
192
192
  /**
193
193
  * Atscript interface **PincodeForm**
194
- * @see {@link ./forms.as:357:18}
194
+ * @see {@link ./forms.as:365:18}
195
195
  */
196
196
  export declare class PincodeForm {
197
197
  // transportHint: ui.paragraph
@@ -214,7 +214,7 @@ export declare class PincodeForm {
214
214
 
215
215
  /**
216
216
  * Atscript interface **AskEmailForm**
217
- * @see {@link ./forms.as:401:18}
217
+ * @see {@link ./forms.as:409:18}
218
218
  */
219
219
  export declare class AskEmailForm extends WithInlineConsentForm {
220
220
  email: string /* email */
@@ -230,7 +230,7 @@ export declare class AskEmailForm extends WithInlineConsentForm {
230
230
 
231
231
  /**
232
232
  * Atscript interface **AskPhoneForm**
233
- * @see {@link ./forms.as:417:18}
233
+ * @see {@link ./forms.as:425:18}
234
234
  */
235
235
  export declare class AskPhoneForm extends WithInlineConsentForm {
236
236
  phone: string
@@ -246,7 +246,7 @@ export declare class AskPhoneForm extends WithInlineConsentForm {
246
246
 
247
247
  /**
248
248
  * Atscript interface **EnrollPickMethodForm**
249
- * @see {@link ./forms.as:433:18}
249
+ * @see {@link ./forms.as:441:18}
250
250
  */
251
251
  export declare class EnrollPickMethodForm {
252
252
  method: string
@@ -263,7 +263,7 @@ export declare class EnrollPickMethodForm {
263
263
 
264
264
  /**
265
265
  * Atscript interface **EnrollAddressForm**
266
- * @see {@link ./forms.as:456:18}
266
+ * @see {@link ./forms.as:464:18}
267
267
  */
268
268
  export declare class EnrollAddressForm {
269
269
  address: string
@@ -281,7 +281,7 @@ export declare class EnrollAddressForm {
281
281
 
282
282
  /**
283
283
  * Atscript interface **EnrollConfirmForm**
284
- * @see {@link ./forms.as:484:18}
284
+ * @see {@link ./forms.as:492:18}
285
285
  */
286
286
  export declare class EnrollConfirmForm {
287
287
  // transportHint: ui.paragraph
@@ -301,7 +301,7 @@ export declare class EnrollConfirmForm {
301
301
 
302
302
  /**
303
303
  * Atscript interface **ProfileCompleteForm**
304
- * @see {@link ./forms.as:516:18}
304
+ * @see {@link ./forms.as:524:18}
305
305
  */
306
306
  export declare class ProfileCompleteForm extends WithInlineConsentForm {
307
307
  firstName?: string
@@ -318,7 +318,7 @@ export declare class ProfileCompleteForm extends WithInlineConsentForm {
318
318
 
319
319
  /**
320
320
  * Atscript interface **TermsBumpForm**
321
- * @see {@link ./forms.as:539:18}
321
+ * @see {@link ./forms.as:547:18}
322
322
  */
323
323
  export declare class TermsBumpForm extends WithInlineConsentForm {
324
324
  static __is_atscript_annotated_type: true
@@ -333,7 +333,7 @@ export declare class TermsBumpForm extends WithInlineConsentForm {
333
333
 
334
334
  /**
335
335
  * Atscript interface **TenantSelectForm**
336
- * @see {@link ./forms.as:549:18}
336
+ * @see {@link ./forms.as:557:18}
337
337
  */
338
338
  export declare class TenantSelectForm {
339
339
  tenantId: string
@@ -349,7 +349,7 @@ export declare class TenantSelectForm {
349
349
 
350
350
  /**
351
351
  * Atscript interface **PersonaSelectForm**
352
- * @see {@link ./forms.as:564:18}
352
+ * @see {@link ./forms.as:572:18}
353
353
  */
354
354
  export declare class PersonaSelectForm {
355
355
  personaId: string
@@ -365,7 +365,7 @@ export declare class PersonaSelectForm {
365
365
 
366
366
  /**
367
367
  * Atscript interface **ConcurrencyLimitForm**
368
- * @see {@link ./forms.as:576:18}
368
+ * @see {@link ./forms.as:584:18}
369
369
  */
370
370
  export declare class ConcurrencyLimitForm {
371
371
  action: string
@@ -383,7 +383,7 @@ export declare class ConcurrencyLimitForm {
383
383
 
384
384
  /**
385
385
  * Atscript interface **MagicLinkRequestForm**
386
- * @see {@link ./forms.as:595:18}
386
+ * @see {@link ./forms.as:603:18}
387
387
  */
388
388
  export declare class MagicLinkRequestForm {
389
389
  identifier: string
@@ -399,7 +399,7 @@ export declare class MagicLinkRequestForm {
399
399
 
400
400
  /**
401
401
  * Atscript interface **RecoveryModeSelectForm**
402
- * @see {@link ./forms.as:607:18}
402
+ * @see {@link ./forms.as:615:18}
403
403
  */
404
404
  export declare class RecoveryModeSelectForm {
405
405
  mode: string
@@ -416,7 +416,7 @@ export declare class RecoveryModeSelectForm {
416
416
 
417
417
  /**
418
418
  * Atscript interface **RecoveryFactorForm**
419
- * @see {@link ./forms.as:629:18}
419
+ * @see {@link ./forms.as:637:18}
420
420
  */
421
421
  export declare class RecoveryFactorForm {
422
422
  factor: string