@aooth/user 0.2.1 → 0.2.3

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,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_federated_identity_store = require("./federated-identity-store-D-LvRXdx.cjs");
2
+ const require_federated_identity_store = require("./federated-identity-store-Cbstwl4-.cjs");
3
3
  let node_crypto = require("node:crypto");
4
- //#region src/atscript-db/federated-identity-store.ts
4
+ // src/atscript-db/federated-identity-store.ts
5
5
  function isConflict$1(err) {
6
6
  return typeof err === "object" && err !== null && err.code === "CONFLICT";
7
7
  }
@@ -71,8 +71,7 @@ var FederatedIdentityStoreAtscriptDb = class extends require_federated_identity_
71
71
  return (await this.table.deleteMany({ userId })).deletedCount;
72
72
  }
73
73
  };
74
- //#endregion
75
- //#region src/atscript-db/index.ts
74
+ // src/atscript-db/index.ts
76
75
  function isConflict(err) {
77
76
  return typeof err === "object" && err !== null && err.code === "CONFLICT";
78
77
  }
@@ -133,8 +132,10 @@ var UsersStoreAtscriptDb = class extends require_federated_identity_store.UserSt
133
132
  throw e;
134
133
  }
135
134
  }
136
- async delete(id) {
137
- return (await this.table.deleteMany({ id })).deletedCount > 0;
135
+ async delete(id, opts) {
136
+ const filter = { id };
137
+ if (opts?.expectedVersion !== void 0) filter.version = opts.expectedVersion;
138
+ return (await this.table.deleteMany(filter)).deletedCount > 0;
138
139
  }
139
140
  /**
140
141
  * Inline retry loop rather than delegating to @atscript/db's
@@ -161,6 +162,5 @@ var UsersStoreAtscriptDb = class extends require_federated_identity_store.UserSt
161
162
  throw new require_federated_identity_store.UserAuthError("CAS_EXHAUSTED");
162
163
  }
163
164
  };
164
- //#endregion
165
165
  exports.FederatedIdentityStoreAtscriptDb = FederatedIdentityStoreAtscriptDb;
166
166
  exports.UsersStoreAtscriptDb = UsersStoreAtscriptDb;
@@ -1,5 +1,4 @@
1
- import { D as UserCredentials, i as NewFederatedIdentity, k as UserStoreUpdate, n as FederatedIdentityStore, o as UserStore, r as FederatedProfileSnapshot, s as WithCasOptions, t as FederatedIdentity } from "./federated-identity-store-B_jEkl-a.cjs";
2
- //#region src/atscript-db/federated-identity-store.d.ts
1
+ import { D as UserCredentials, i as NewFederatedIdentity, k as UserStoreUpdate, n as FederatedIdentityStore, o as UserStore, r as FederatedProfileSnapshot, s as WithCasOptions, t as FederatedIdentity } from "./federated-identity-store-Ds7Ye9qJ.cjs";
3
2
  /**
4
3
  * Structural surface of `AtscriptDbTable` covering exactly the methods this
5
4
  * adapter calls — kept loose so `@aooth/user` doesn't pull `@atscript/db` types
@@ -39,7 +38,7 @@ interface FederatedIdentityStoreAtscriptDbOptions {
39
38
  * is a plain indexed column, so `listForUser` / `deleteAllForUser` scan it
40
39
  * natively.
41
40
  */
42
- declare class FederatedIdentityStoreAtscriptDb extends FederatedIdentityStore {
41
+ export declare class FederatedIdentityStoreAtscriptDb extends FederatedIdentityStore {
43
42
  private readonly table;
44
43
  private readonly clock;
45
44
  constructor(opts: FederatedIdentityStoreAtscriptDbOptions);
@@ -50,15 +49,13 @@ declare class FederatedIdentityStoreAtscriptDb extends FederatedIdentityStore {
50
49
  touchLogin(provider: string, subject: string, profile?: FederatedProfileSnapshot): Promise<void>;
51
50
  deleteAllForUser(userId: string): Promise<number>;
52
51
  }
53
- //#endregion
54
- //#region src/atscript-db/index.d.ts
55
52
  /**
56
53
  * Persisted row shape — `UserCredentials` plus the consumer's custom user
57
54
  * fields. The `.as` model shipped at `@aooth/user/atscript-db/model.as`
58
55
  * (`AoothUserCredentials`) matches by construction; consumers extend it with
59
56
  * their own `.as` interface to add custom columns.
60
57
  */
61
- type UserCredentialsRow<TUserCustom extends object = object> = UserCredentials & TUserCustom;
58
+ export type UserCredentialsRow<TUserCustom extends object = object> = UserCredentials & TUserCustom;
62
59
  /**
63
60
  * Structural surface of `AtscriptDbTable` covering exactly the methods this
64
61
  * adapter calls. Kept loose to avoid pulling `@atscript/db` types into the
@@ -67,7 +64,7 @@ type UserCredentialsRow<TUserCustom extends object = object> = UserCredentials &
67
64
  *
68
65
  * Mirrors `AuthCredentialTable` from `@aooth/auth/atscript-db`.
69
66
  */
70
- interface AuthUserTable<TUserCustom extends object = object> {
67
+ export interface AuthUserTable<TUserCustom extends object = object> {
71
68
  count(query: {
72
69
  filter: Record<string, unknown>;
73
70
  }): Promise<number>;
@@ -105,7 +102,7 @@ interface UsersStoreAtscriptDbOptions<TUserCustom extends object> {
105
102
  * `@meta.id` PK (`id`) and the unique `username` index, plus any
106
103
  * annotation-resolved `handleFields`; writes key on `id`.
107
104
  */
108
- declare class UsersStoreAtscriptDb<TUserCustom extends object = object> extends UserStore<TUserCustom> {
105
+ export declare class UsersStoreAtscriptDb<TUserCustom extends object = object> extends UserStore<TUserCustom> {
109
106
  protected table: AuthUserTable<TUserCustom>;
110
107
  /** Secondary handle fields tried (in order) by `findByHandle` after `username`. */
111
108
  protected readonly handleFields: string[];
@@ -116,7 +113,9 @@ declare class UsersStoreAtscriptDb<TUserCustom extends object = object> extends
116
113
  findByIdentifier(value: string): Promise<(UserCredentials & TUserCustom) | null>;
117
114
  create(data: UserCredentials & TUserCustom): Promise<void>;
118
115
  update(id: string, update: UserStoreUpdate): Promise<boolean>;
119
- delete(id: string): Promise<boolean>;
116
+ delete(id: string, opts?: {
117
+ expectedVersion?: number;
118
+ }): Promise<boolean>;
120
119
  /**
121
120
  * Inline retry loop rather than delegating to @atscript/db's
122
121
  * `withOptimisticRetry`: that helper expects the mutator to always return a
@@ -129,5 +128,4 @@ declare class UsersStoreAtscriptDb<TUserCustom extends object = object> extends
129
128
  */
130
129
  withCas(id: string, mutator: (current: UserCredentials & TUserCustom) => UserStoreUpdate | null, opts?: WithCasOptions): Promise<void>;
131
130
  }
132
- //#endregion
133
- export { AuthUserTable, FederatedIdentityStoreAtscriptDb, type FederatedIdentityTable, UserCredentialsRow, UsersStoreAtscriptDb };
131
+ export type { FederatedIdentityTable };
@@ -1,5 +1,4 @@
1
- import { D as UserCredentials, i as NewFederatedIdentity, k as UserStoreUpdate, n as FederatedIdentityStore, o as UserStore, r as FederatedProfileSnapshot, s as WithCasOptions, t as FederatedIdentity } from "./federated-identity-store-B_jEkl-a.mjs";
2
- //#region src/atscript-db/federated-identity-store.d.ts
1
+ import { D as UserCredentials, i as NewFederatedIdentity, k as UserStoreUpdate, n as FederatedIdentityStore, o as UserStore, r as FederatedProfileSnapshot, s as WithCasOptions, t as FederatedIdentity } from "./federated-identity-store-Ds7Ye9qJ.mjs";
3
2
  /**
4
3
  * Structural surface of `AtscriptDbTable` covering exactly the methods this
5
4
  * adapter calls — kept loose so `@aooth/user` doesn't pull `@atscript/db` types
@@ -39,7 +38,7 @@ interface FederatedIdentityStoreAtscriptDbOptions {
39
38
  * is a plain indexed column, so `listForUser` / `deleteAllForUser` scan it
40
39
  * natively.
41
40
  */
42
- declare class FederatedIdentityStoreAtscriptDb extends FederatedIdentityStore {
41
+ export declare class FederatedIdentityStoreAtscriptDb extends FederatedIdentityStore {
43
42
  private readonly table;
44
43
  private readonly clock;
45
44
  constructor(opts: FederatedIdentityStoreAtscriptDbOptions);
@@ -50,15 +49,13 @@ declare class FederatedIdentityStoreAtscriptDb extends FederatedIdentityStore {
50
49
  touchLogin(provider: string, subject: string, profile?: FederatedProfileSnapshot): Promise<void>;
51
50
  deleteAllForUser(userId: string): Promise<number>;
52
51
  }
53
- //#endregion
54
- //#region src/atscript-db/index.d.ts
55
52
  /**
56
53
  * Persisted row shape — `UserCredentials` plus the consumer's custom user
57
54
  * fields. The `.as` model shipped at `@aooth/user/atscript-db/model.as`
58
55
  * (`AoothUserCredentials`) matches by construction; consumers extend it with
59
56
  * their own `.as` interface to add custom columns.
60
57
  */
61
- type UserCredentialsRow<TUserCustom extends object = object> = UserCredentials & TUserCustom;
58
+ export type UserCredentialsRow<TUserCustom extends object = object> = UserCredentials & TUserCustom;
62
59
  /**
63
60
  * Structural surface of `AtscriptDbTable` covering exactly the methods this
64
61
  * adapter calls. Kept loose to avoid pulling `@atscript/db` types into the
@@ -67,7 +64,7 @@ type UserCredentialsRow<TUserCustom extends object = object> = UserCredentials &
67
64
  *
68
65
  * Mirrors `AuthCredentialTable` from `@aooth/auth/atscript-db`.
69
66
  */
70
- interface AuthUserTable<TUserCustom extends object = object> {
67
+ export interface AuthUserTable<TUserCustom extends object = object> {
71
68
  count(query: {
72
69
  filter: Record<string, unknown>;
73
70
  }): Promise<number>;
@@ -105,7 +102,7 @@ interface UsersStoreAtscriptDbOptions<TUserCustom extends object> {
105
102
  * `@meta.id` PK (`id`) and the unique `username` index, plus any
106
103
  * annotation-resolved `handleFields`; writes key on `id`.
107
104
  */
108
- declare class UsersStoreAtscriptDb<TUserCustom extends object = object> extends UserStore<TUserCustom> {
105
+ export declare class UsersStoreAtscriptDb<TUserCustom extends object = object> extends UserStore<TUserCustom> {
109
106
  protected table: AuthUserTable<TUserCustom>;
110
107
  /** Secondary handle fields tried (in order) by `findByHandle` after `username`. */
111
108
  protected readonly handleFields: string[];
@@ -116,7 +113,9 @@ declare class UsersStoreAtscriptDb<TUserCustom extends object = object> extends
116
113
  findByIdentifier(value: string): Promise<(UserCredentials & TUserCustom) | null>;
117
114
  create(data: UserCredentials & TUserCustom): Promise<void>;
118
115
  update(id: string, update: UserStoreUpdate): Promise<boolean>;
119
- delete(id: string): Promise<boolean>;
116
+ delete(id: string, opts?: {
117
+ expectedVersion?: number;
118
+ }): Promise<boolean>;
120
119
  /**
121
120
  * Inline retry loop rather than delegating to @atscript/db's
122
121
  * `withOptimisticRetry`: that helper expects the mutator to always return a
@@ -129,5 +128,4 @@ declare class UsersStoreAtscriptDb<TUserCustom extends object = object> extends
129
128
  */
130
129
  withCas(id: string, mutator: (current: UserCredentials & TUserCustom) => UserStoreUpdate | null, opts?: WithCasOptions): Promise<void>;
131
130
  }
132
- //#endregion
133
- export { AuthUserTable, FederatedIdentityStoreAtscriptDb, type FederatedIdentityTable, UserCredentialsRow, UsersStoreAtscriptDb };
131
+ export type { FederatedIdentityTable };
@@ -1,6 +1,6 @@
1
- import { c as setAtPath, l as UserStore, n as pickDefinedProfile, t as FederatedIdentityStore, u as UserAuthError } from "./federated-identity-store-CRycYlMj.mjs";
1
+ import { c as setAtPath, l as UserStore, n as pickDefinedProfile, t as FederatedIdentityStore, u as UserAuthError } from "./federated-identity-store-BZRvw9Pz.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
- //#region src/atscript-db/federated-identity-store.ts
3
+ // src/atscript-db/federated-identity-store.ts
4
4
  function isConflict$1(err) {
5
5
  return typeof err === "object" && err !== null && err.code === "CONFLICT";
6
6
  }
@@ -70,8 +70,7 @@ var FederatedIdentityStoreAtscriptDb = class extends FederatedIdentityStore {
70
70
  return (await this.table.deleteMany({ userId })).deletedCount;
71
71
  }
72
72
  };
73
- //#endregion
74
- //#region src/atscript-db/index.ts
73
+ // src/atscript-db/index.ts
75
74
  function isConflict(err) {
76
75
  return typeof err === "object" && err !== null && err.code === "CONFLICT";
77
76
  }
@@ -132,8 +131,10 @@ var UsersStoreAtscriptDb = class extends UserStore {
132
131
  throw e;
133
132
  }
134
133
  }
135
- async delete(id) {
136
- return (await this.table.deleteMany({ id })).deletedCount > 0;
134
+ async delete(id, opts) {
135
+ const filter = { id };
136
+ if (opts?.expectedVersion !== void 0) filter.version = opts.expectedVersion;
137
+ return (await this.table.deleteMany(filter)).deletedCount > 0;
137
138
  }
138
139
  /**
139
140
  * Inline retry loop rather than delegating to @atscript/db's
@@ -160,5 +161,4 @@ var UsersStoreAtscriptDb = class extends UserStore {
160
161
  throw new UserAuthError("CAS_EXHAUSTED");
161
162
  }
162
163
  };
163
- //#endregion
164
164
  export { FederatedIdentityStoreAtscriptDb, UsersStoreAtscriptDb };
@@ -1,5 +1,5 @@
1
1
  import "node:crypto";
2
- //#region src/errors.ts
2
+ // src/errors.ts
3
3
  const defaultMessages = {
4
4
  NOT_FOUND: "User not found",
5
5
  ALREADY_EXISTS: "User already exists",
@@ -24,8 +24,7 @@ var UserAuthError = class extends Error {
24
24
  this.details = details;
25
25
  }
26
26
  };
27
- //#endregion
28
- //#region src/store/user-store.ts
27
+ // src/store/user-store.ts
29
28
  /**
30
29
  * Storage seam for user credentials, keyed by the stable surrogate **`id`**
31
30
  * (the token subject). Reads come in three flavours:
@@ -40,8 +39,7 @@ var UserAuthError = class extends Error {
40
39
  * Writes (`update`/`delete`/`withCas`) all key on the surrogate `id`.
41
40
  */
42
41
  var UserStore = class {};
43
- //#endregion
44
- //#region src/utils.ts
42
+ // src/utils.ts
45
43
  function maskEmail(email) {
46
44
  if (!email) return "";
47
45
  const at = email.indexOf("@");
@@ -101,8 +99,7 @@ function incrementAtPath(obj, path, amount) {
101
99
  const existing = current[leaf];
102
100
  current[leaf] = (typeof existing === "number" ? existing : 0) + amount;
103
101
  }
104
- //#endregion
105
- //#region src/store/federated-identity-store.ts
102
+ // src/store/federated-identity-store.ts
106
103
  /**
107
104
  * Copy only the DEFINED display fields — so a `touchLogin` / `link` with a
108
105
  * partial profile (e.g. Apple omitting the email on a repeat login) never
@@ -126,5 +123,4 @@ function pickDefinedProfile(src) {
126
123
  * keeps the federated-login core (`@aooth/idp`, phase 2) storage-agnostic.
127
124
  */
128
125
  var FederatedIdentityStore = class {};
129
- //#endregion
130
126
  export { maskEmail as a, setAtPath as c, incrementAtPath as i, UserStore as l, pickDefinedProfile as n, maskMfaValue as o, deepMerge as r, maskPhone as s, FederatedIdentityStore as t, UserAuthError as u };
@@ -1,5 +1,5 @@
1
1
  require("node:crypto");
2
- //#region src/errors.ts
2
+ // src/errors.ts
3
3
  const defaultMessages = {
4
4
  NOT_FOUND: "User not found",
5
5
  ALREADY_EXISTS: "User already exists",
@@ -24,8 +24,7 @@ var UserAuthError = class extends Error {
24
24
  this.details = details;
25
25
  }
26
26
  };
27
- //#endregion
28
- //#region src/store/user-store.ts
27
+ // src/store/user-store.ts
29
28
  /**
30
29
  * Storage seam for user credentials, keyed by the stable surrogate **`id`**
31
30
  * (the token subject). Reads come in three flavours:
@@ -40,8 +39,7 @@ var UserAuthError = class extends Error {
40
39
  * Writes (`update`/`delete`/`withCas`) all key on the surrogate `id`.
41
40
  */
42
41
  var UserStore = class {};
43
- //#endregion
44
- //#region src/utils.ts
42
+ // src/utils.ts
45
43
  function maskEmail(email) {
46
44
  if (!email) return "";
47
45
  const at = email.indexOf("@");
@@ -101,8 +99,7 @@ function incrementAtPath(obj, path, amount) {
101
99
  const existing = current[leaf];
102
100
  current[leaf] = (typeof existing === "number" ? existing : 0) + amount;
103
101
  }
104
- //#endregion
105
- //#region src/store/federated-identity-store.ts
102
+ // src/store/federated-identity-store.ts
106
103
  /**
107
104
  * Copy only the DEFINED display fields — so a `touchLogin` / `link` with a
108
105
  * partial profile (e.g. Apple omitting the email on a repeat login) never
@@ -126,7 +123,6 @@ function pickDefinedProfile(src) {
126
123
  * keeps the federated-login core (`@aooth/idp`, phase 2) storage-agnostic.
127
124
  */
128
125
  var FederatedIdentityStore = class {};
129
- //#endregion
130
126
  Object.defineProperty(exports, "FederatedIdentityStore", {
131
127
  enumerable: true,
132
128
  get: function() {
@@ -1,4 +1,3 @@
1
- //#region src/types.d.ts
2
1
  interface UserCredentials {
3
2
  id: string;
4
3
  username: string;
@@ -59,10 +58,11 @@ interface AccountData {
59
58
  lastLogin: number;
60
59
  /**
61
60
  * True while the user record exists from an admin-issued invite but the
62
- * invitee has not yet accepted (set password + activate). Used by
63
- * `InviteWorkflow` to gate the accept tail, reject duplicate invites, and
64
- * power `auth/invite/resend` / `auth/invite/cancel`. Absent / `false` once
65
- * the invite has been accepted.
61
+ * invitee has not yet accepted (set password + activate). The invite
62
+ * workflow uses it to gate the accept tail and to detect re-invites
63
+ * (`'reuse'` refreshes the pending row and sends a new link);
64
+ * `UserService.revokeInvitation()` deletes only rows that still have it.
65
+ * Absent / `false` once the invite has been accepted.
66
66
  */
67
67
  pendingInvitation?: boolean;
68
68
  /** Id of the user who issued the pending invite (written by the invite flow). */
@@ -243,8 +243,6 @@ interface TotpConfig {
243
243
  /** Injectable clock for testability */
244
244
  clock?: () => number;
245
245
  }
246
- //#endregion
247
- //#region src/store/user-store.d.ts
248
246
  interface WithCasOptions {
249
247
  /**
250
248
  * Total attempts (1 initial + retries). Default `2` = one retry. Each
@@ -296,9 +294,14 @@ declare abstract class UserStore<T extends object = object> {
296
294
  abstract update(id: string, update: UserStoreUpdate): Promise<boolean>;
297
295
  /**
298
296
  * Hard-delete the row by `id`. Returns `true` when a row was removed, `false`
299
- * when the id was not found.
297
+ * when the id was not found. With `opts.expectedVersion`, delete only while
298
+ * the row's `version` still equals it (`false` otherwise) — a custom store
299
+ * that ignores it makes `UserService.revokeInvitation()` delete
300
+ * unconditionally after its re-check.
300
301
  */
301
- abstract delete(id: string): Promise<boolean>;
302
+ abstract delete(id: string, opts?: {
303
+ expectedVersion?: number;
304
+ }): Promise<boolean>;
302
305
  /**
303
306
  * Run a read-modify-write cycle under optimistic concurrency, keyed by `id`.
304
307
  * Each attempt re-reads (via `findById`), calls `mutator`, and applies the
@@ -312,8 +315,6 @@ declare abstract class UserStore<T extends object = object> {
312
315
  */
313
316
  abstract withCas(id: string, mutator: (current: UserCredentials & T) => UserStoreUpdate | null, opts?: WithCasOptions): Promise<void>;
314
317
  }
315
- //#endregion
316
- //#region src/store/federated-identity-store.d.ts
317
318
  /**
318
319
  * Display fields snapshotted from an IdP profile onto a federated-identity row.
319
320
  * Refreshed on each login via {@link FederatedIdentityStore.touchLogin}; never
@@ -404,5 +405,4 @@ declare abstract class FederatedIdentityStore {
404
405
  */
405
406
  abstract deleteAllForUser(userId: string): Promise<number>;
406
407
  }
407
- //#endregion
408
408
  export { TotpConfig as C, UserCredentials as D, UserAuthErrorType as E, UserServiceConfig as O, PolicyCheckResult as S, TrustedDeviceRecord as T, PasswordData as _, pickDefinedProfile as a, PasswordPolicyEvalFn as b, AccountData as c, LockoutConfig as d, LoginResult as f, PasswordConfig as g, MfaMethodInfo as h, NewFederatedIdentity as i, UserStoreUpdate as k, DeepPartial as l, MfaMethod as m, FederatedIdentityStore as n, UserStore as o, MfaData as p, FederatedProfileSnapshot as r, WithCasOptions as s, FederatedIdentity as t, LockStatus as u, PasswordPolicyContext as v, TransferablePolicy as w, PasswordPolicyInstance as x, PasswordPolicyDef as y };
@@ -1,4 +1,3 @@
1
- //#region src/types.d.ts
2
1
  interface UserCredentials {
3
2
  id: string;
4
3
  username: string;
@@ -59,10 +58,11 @@ interface AccountData {
59
58
  lastLogin: number;
60
59
  /**
61
60
  * True while the user record exists from an admin-issued invite but the
62
- * invitee has not yet accepted (set password + activate). Used by
63
- * `InviteWorkflow` to gate the accept tail, reject duplicate invites, and
64
- * power `auth/invite/resend` / `auth/invite/cancel`. Absent / `false` once
65
- * the invite has been accepted.
61
+ * invitee has not yet accepted (set password + activate). The invite
62
+ * workflow uses it to gate the accept tail and to detect re-invites
63
+ * (`'reuse'` refreshes the pending row and sends a new link);
64
+ * `UserService.revokeInvitation()` deletes only rows that still have it.
65
+ * Absent / `false` once the invite has been accepted.
66
66
  */
67
67
  pendingInvitation?: boolean;
68
68
  /** Id of the user who issued the pending invite (written by the invite flow). */
@@ -243,8 +243,6 @@ interface TotpConfig {
243
243
  /** Injectable clock for testability */
244
244
  clock?: () => number;
245
245
  }
246
- //#endregion
247
- //#region src/store/user-store.d.ts
248
246
  interface WithCasOptions {
249
247
  /**
250
248
  * Total attempts (1 initial + retries). Default `2` = one retry. Each
@@ -296,9 +294,14 @@ declare abstract class UserStore<T extends object = object> {
296
294
  abstract update(id: string, update: UserStoreUpdate): Promise<boolean>;
297
295
  /**
298
296
  * Hard-delete the row by `id`. Returns `true` when a row was removed, `false`
299
- * when the id was not found.
297
+ * when the id was not found. With `opts.expectedVersion`, delete only while
298
+ * the row's `version` still equals it (`false` otherwise) — a custom store
299
+ * that ignores it makes `UserService.revokeInvitation()` delete
300
+ * unconditionally after its re-check.
300
301
  */
301
- abstract delete(id: string): Promise<boolean>;
302
+ abstract delete(id: string, opts?: {
303
+ expectedVersion?: number;
304
+ }): Promise<boolean>;
302
305
  /**
303
306
  * Run a read-modify-write cycle under optimistic concurrency, keyed by `id`.
304
307
  * Each attempt re-reads (via `findById`), calls `mutator`, and applies the
@@ -312,8 +315,6 @@ declare abstract class UserStore<T extends object = object> {
312
315
  */
313
316
  abstract withCas(id: string, mutator: (current: UserCredentials & T) => UserStoreUpdate | null, opts?: WithCasOptions): Promise<void>;
314
317
  }
315
- //#endregion
316
- //#region src/store/federated-identity-store.d.ts
317
318
  /**
318
319
  * Display fields snapshotted from an IdP profile onto a federated-identity row.
319
320
  * Refreshed on each login via {@link FederatedIdentityStore.touchLogin}; never
@@ -404,5 +405,4 @@ declare abstract class FederatedIdentityStore {
404
405
  */
405
406
  abstract deleteAllForUser(userId: string): Promise<number>;
406
407
  }
407
- //#endregion
408
408
  export { TotpConfig as C, UserCredentials as D, UserAuthErrorType as E, UserServiceConfig as O, PolicyCheckResult as S, TrustedDeviceRecord as T, PasswordData as _, pickDefinedProfile as a, PasswordPolicyEvalFn as b, AccountData as c, LockoutConfig as d, LoginResult as f, PasswordConfig as g, MfaMethodInfo as h, NewFederatedIdentity as i, UserStoreUpdate as k, DeepPartial as l, MfaMethod as m, FederatedIdentityStore as n, UserStore as o, MfaData as p, FederatedProfileSnapshot as r, WithCasOptions as s, FederatedIdentity as t, LockStatus as u, PasswordPolicyContext as v, TransferablePolicy as w, PasswordPolicyInstance as x, PasswordPolicyDef as y };
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_federated_identity_store = require("./federated-identity-store-D-LvRXdx.cjs");
2
+ const require_federated_identity_store = require("./federated-identity-store-Cbstwl4-.cjs");
3
3
  let node_crypto = require("node:crypto");
4
- //#region src/base-x/base32.ts
4
+ // src/base-x/base32.ts
5
5
  /**
6
6
  * Partially copied from "thirty-two" library, all credits to Chris Umbel.
7
7
  *
@@ -72,8 +72,7 @@ function decode(input) {
72
72
  }
73
73
  return output.subarray(0, index);
74
74
  }
75
- //#endregion
76
- //#region src/mfa/totp.ts
75
+ // src/mfa/totp.ts
77
76
  function generateTotpSecret(bytes = 20) {
78
77
  return encode((0, node_crypto.randomBytes)(bytes)).toString().replace(/=+$/, "").toUpperCase();
79
78
  }
@@ -127,8 +126,7 @@ function hotpCode(key, counter, digits) {
127
126
  const offset = hmacResult[hmacResult.length - 1] & 15;
128
127
  return (((hmacResult[offset] & 127) << 24 | (hmacResult[offset + 1] & 255) << 16 | (hmacResult[offset + 2] & 255) << 8 | hmacResult[offset + 3] & 255) % 10 ** digits).toString().padStart(digits, "0");
129
128
  }
130
- //#endregion
131
- //#region src/password/hasher.ts
129
+ // src/password/hasher.ts
132
130
  const DEFAULTS = {
133
131
  N: 16384,
134
132
  r: 8,
@@ -138,11 +136,11 @@ const DEFAULTS = {
138
136
  };
139
137
  const PREFIX = "$scrypt$";
140
138
  /** Node's default scrypt memory cap (32 MiB) — kept as the floor. */
141
- const DEFAULT_MAXMEM = 32 * 1024 * 1024;
139
+ const DEFAULT_MAXMEM = 33554432;
142
140
  /** Upper bound for the memory a (possibly stored, untrusted) parameter set may ask for. */
143
- const MAX_MAXMEM = 1024 * 1024 * 1024;
141
+ const MAX_MAXMEM = 1073741824;
144
142
  function scryptAsync(password, salt, keyLength, options) {
145
- const maxmem = Math.min(MAX_MAXMEM, Math.max(DEFAULT_MAXMEM, 256 * options.N * options.r + 1024 * 1024));
143
+ const maxmem = Math.min(MAX_MAXMEM, Math.max(DEFAULT_MAXMEM, 256 * options.N * options.r + 1048576));
146
144
  return new Promise((resolve, reject) => {
147
145
  (0, node_crypto.scrypt)(password, salt, keyLength, {
148
146
  ...options,
@@ -212,7 +210,7 @@ var PasswordHasher = class {
212
210
  const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
213
211
  const digits = "0123456789";
214
212
  const special = "!@#$%^&*()-_=+";
215
- const all = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+";
213
+ const all = lower + upper + digits + special;
216
214
  const bytes = (0, node_crypto.randomBytes)(minLen);
217
215
  const result = Array.from({ length: minLen });
218
216
  result[0] = lower[bytes[0] % 26];
@@ -228,8 +226,7 @@ var PasswordHasher = class {
228
226
  return result.join("");
229
227
  }
230
228
  };
231
- //#endregion
232
- //#region src/password/policy.ts
229
+ // src/password/policy.ts
233
230
  function normalizePolicies(policies) {
234
231
  return (policies || []).map((p) => p instanceof PasswordPolicy ? p : new PasswordPolicy(p));
235
232
  }
@@ -283,8 +280,7 @@ var PasswordPolicy = class {
283
280
  return this.serialized !== void 0;
284
281
  }
285
282
  };
286
- //#endregion
287
- //#region src/user-service.ts
283
+ // src/user-service.ts
288
284
  function resolveConfig(config) {
289
285
  return {
290
286
  password: {
@@ -414,8 +410,8 @@ var UserService = class {
414
410
  }
415
411
  /**
416
412
  * Creates a user with `account.active: false`. The invite workflow relies
417
- * on this default (see `InviteWorkflow.acceptInvite` — pending invitees stay
418
- * inactive until they accept). For setup scripts / seeders / tests that
413
+ * on this default (pending invitees stay inactive until they accept the
414
+ * invite). For setup scripts / seeders / tests that
419
415
  * don't go through invite, follow up with `activateAccount(id)` or `login()`
420
416
  * will throw `UserAuthError("INACTIVE")` — which the login workflow
421
417
  * deliberately re-maps to `"Invalid credentials"` to avoid account
@@ -544,8 +540,9 @@ var UserService = class {
544
540
  }
545
541
  /**
546
542
  * Hard-delete the user row by `id`. Returns nothing on success. Throws
547
- * `UserAuthError("NOT_FOUND")` when no row matches. Used by the invite
548
- * workflow's `auth/invite/cancel` to revoke a pending invitation.
543
+ * `UserAuthError("NOT_FOUND")` when no row matches. To revoke a pending
544
+ * invitation use {@link revokeInvitation}, which never deletes an account
545
+ * whose invite was accepted meanwhile.
549
546
  */
550
547
  async deleteUser(id) {
551
548
  if (!await this.store.delete(id)) throw new require_federated_identity_store.UserAuthError("NOT_FOUND");
@@ -554,7 +551,6 @@ var UserService = class {
554
551
  * Deep-merge `patch` into the user record (top-level fields are shallow-
555
552
  * merged; `account` / `mfa` / `password` are merged per their
556
553
  * `@db.patch.strategy 'merge'` declaration). Returns the patched record.
557
- * Used by the invite workflow's `applyProfile` default fallback.
558
554
  */
559
555
  async update(id, patch) {
560
556
  if (!await this.store.update(id, { set: patch })) throw new require_federated_identity_store.UserAuthError("NOT_FOUND");
@@ -581,6 +577,36 @@ var UserService = class {
581
577
  return accepted;
582
578
  }
583
579
  /**
580
+ * Revoke a pending invitation: delete the invited account, so its invite
581
+ * link stops working (the invite workflow answers it with 410
582
+ * `INVITE_CANCELLED`). Returns `false` and deletes nothing when the account
583
+ * is not pending — the invite was already accepted — or when the row
584
+ * changed while the revoke ran. Throws `UserAuthError("NOT_FOUND")` when no
585
+ * row matches `id`.
586
+ *
587
+ * Safe against a concurrent acceptance or re-invite: one compare-and-set
588
+ * write first rotates `account.inviteNonce` while the row is still pending
589
+ * (an accept racing it fails), and the delete is conditional on the row's
590
+ * `version` still being the one that write produced — any write in between
591
+ * (a re-invite, an acceptance) makes it delete nothing. The bundled stores
592
+ * honour the conditional delete; a custom store whose `delete()` ignores
593
+ * `expectedVersion` leaves a window of one round-trip between the re-check
594
+ * and the delete.
595
+ */
596
+ async revokeInvitation(id) {
597
+ const nonce = (0, node_crypto.randomBytes)(16).toString("base64url");
598
+ let pending = false;
599
+ await this.store.withCas(id, (user) => {
600
+ pending = user.account.pendingInvitation === true;
601
+ if (!pending) return null;
602
+ return { set: { account: { inviteNonce: nonce } } };
603
+ });
604
+ if (!pending) return false;
605
+ const claimed = await this.store.findById(id);
606
+ if (!claimed?.account.pendingInvitation || claimed.account.inviteNonce !== nonce) return false;
607
+ return this.store.delete(id, { expectedVersion: claimed.version ?? 0 });
608
+ }
609
+ /**
584
610
  * Flip the account active. `opts.verifiedEmail` lets callers that just
585
611
  * proved an inbox (invite magic link, signup OTP) record the correspondence
586
612
  * address in the same store write — see {@link setVerifiedEmail}.
@@ -1108,8 +1134,7 @@ function observeWrites(store, hooks) {
1108
1134
  return fn;
1109
1135
  } });
1110
1136
  }
1111
- //#endregion
1112
- //#region src/store/memory.ts
1137
+ // src/store/memory.ts
1113
1138
  var UserStoreMemory = class extends require_federated_identity_store.UserStore {
1114
1139
  /** Keyed by the stable surrogate `id` (the token subject). */
1115
1140
  store = /* @__PURE__ */ new Map();
@@ -1205,7 +1230,10 @@ var UserStoreMemory = class extends require_federated_identity_store.UserStore {
1205
1230
  user.version = (user.version ?? 0) + 1;
1206
1231
  return true;
1207
1232
  }
1208
- async delete(id) {
1233
+ async delete(id, opts) {
1234
+ const user = this.store.get(id);
1235
+ if (!user) return false;
1236
+ if (opts?.expectedVersion !== void 0 && (user.version ?? 0) !== opts.expectedVersion) return false;
1209
1237
  return this.store.delete(id);
1210
1238
  }
1211
1239
  async withCas(id, mutator, opts) {
@@ -1223,8 +1251,7 @@ var UserStoreMemory = class extends require_federated_identity_store.UserStore {
1223
1251
  throw new require_federated_identity_store.UserAuthError("CAS_EXHAUSTED");
1224
1252
  }
1225
1253
  };
1226
- //#endregion
1227
- //#region src/store/federated-identity-store-memory.ts
1254
+ // src/store/federated-identity-store-memory.ts
1228
1255
  /**
1229
1256
  * In-memory {@link FederatedIdentityStore} — the offline-testable reference
1230
1257
  * impl (RFC IDP.md §9). Keyed by the composite `(provider, subject)`;
@@ -1282,8 +1309,7 @@ var FederatedIdentityStoreMemory = class extends require_federated_identity_stor
1282
1309
  function compositeKey(provider, subject) {
1283
1310
  return `${provider}${subject}`;
1284
1311
  }
1285
- //#endregion
1286
- //#region src/password/policies.ts
1312
+ // src/password/policies.ts
1287
1313
  const ppHasMinLength = (min = 8) => definePasswordPolicy({
1288
1314
  rule: (v, min) => v.length >= min,
1289
1315
  args: [min],
@@ -1320,8 +1346,7 @@ const ppMaxRepeatedChars = (maxRepeated = 2) => definePasswordPolicy({
1320
1346
  description: `No more than ${maxRepeated} consecutive repeated characters`,
1321
1347
  errorMessage: `Password must not have more than ${maxRepeated} consecutive repeated characters`
1322
1348
  });
1323
- //#endregion
1324
- //#region src/mfa/codes.ts
1349
+ // src/mfa/codes.ts
1325
1350
  /**
1326
1351
  * SHA-256 hash of an MFA code (e.g. one-time email/SMS code or backup code).
1327
1352
  *
@@ -1346,7 +1371,6 @@ function verifyMfaCode(submitted, expectedHash) {
1346
1371
  if (a.length !== b.length) return false;
1347
1372
  return (0, node_crypto.timingSafeEqual)(a, b);
1348
1373
  }
1349
- //#endregion
1350
1374
  exports.FederatedIdentityStore = require_federated_identity_store.FederatedIdentityStore;
1351
1375
  exports.FederatedIdentityStoreMemory = FederatedIdentityStoreMemory;
1352
1376
  exports.PasswordHasher = PasswordHasher;