@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.
- package/dist/atscript-db.cjs +7 -7
- package/dist/atscript-db.d.cts +9 -11
- package/dist/atscript-db.d.mts +9 -11
- package/dist/atscript-db.mjs +7 -7
- package/dist/{federated-identity-store-CRycYlMj.mjs → federated-identity-store-BZRvw9Pz.mjs} +4 -8
- package/dist/{federated-identity-store-D-LvRXdx.cjs → federated-identity-store-Cbstwl4-.cjs} +4 -8
- package/dist/{federated-identity-store-B_jEkl-a.d.cts → federated-identity-store-Ds7Ye9qJ.d.cts} +12 -12
- package/dist/{federated-identity-store-B_jEkl-a.d.mts → federated-identity-store-Ds7Ye9qJ.d.mts} +12 -12
- package/dist/index.cjs +53 -29
- package/dist/index.d.cts +54 -54
- package/dist/index.d.mts +54 -54
- package/dist/index.mjs +53 -29
- package/package.json +9 -9
package/dist/index.d.cts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import { C as TotpConfig, D as UserCredentials, E as UserAuthErrorType, O as UserServiceConfig, S as PolicyCheckResult, T as TrustedDeviceRecord, _ as PasswordData, a as pickDefinedProfile, b as PasswordPolicyEvalFn, c as AccountData, d as LockoutConfig, f as LoginResult, g as PasswordConfig, h as MfaMethodInfo, i as NewFederatedIdentity, k as UserStoreUpdate, l as DeepPartial, m as MfaMethod, n as FederatedIdentityStore, o as UserStore, p as MfaData, r as FederatedProfileSnapshot, s as WithCasOptions, t as FederatedIdentity, u as LockStatus, v as PasswordPolicyContext, w as TransferablePolicy, x as PasswordPolicyInstance, y as PasswordPolicyDef } from "./federated-identity-store-
|
|
2
|
-
|
|
3
|
-
declare class UserAuthError extends Error {
|
|
1
|
+
import { C as TotpConfig, D as UserCredentials, E as UserAuthErrorType, O as UserServiceConfig, S as PolicyCheckResult, T as TrustedDeviceRecord, _ as PasswordData, a as pickDefinedProfile, b as PasswordPolicyEvalFn, c as AccountData, d as LockoutConfig, f as LoginResult, g as PasswordConfig, h as MfaMethodInfo, i as NewFederatedIdentity, k as UserStoreUpdate, l as DeepPartial, m as MfaMethod, n as FederatedIdentityStore, o as UserStore, p as MfaData, r as FederatedProfileSnapshot, s as WithCasOptions, t as FederatedIdentity, u as LockStatus, v as PasswordPolicyContext, w as TransferablePolicy, x as PasswordPolicyInstance, y as PasswordPolicyDef } from "./federated-identity-store-Ds7Ye9qJ.cjs";
|
|
2
|
+
export declare class UserAuthError extends Error {
|
|
4
3
|
readonly type: UserAuthErrorType;
|
|
5
4
|
readonly details?: Record<string, unknown> | undefined;
|
|
6
5
|
readonly name = "UserAuthError";
|
|
7
6
|
constructor(type: UserAuthErrorType, message?: string, details?: Record<string, unknown> | undefined);
|
|
8
7
|
}
|
|
9
|
-
|
|
10
|
-
//#region src/password/hasher.d.ts
|
|
11
|
-
declare class PasswordHasher {
|
|
8
|
+
export declare class PasswordHasher {
|
|
12
9
|
private readonly pepper;
|
|
13
10
|
private readonly N;
|
|
14
11
|
private readonly r;
|
|
@@ -19,9 +16,7 @@ declare class PasswordHasher {
|
|
|
19
16
|
verify(password: string, encoded: string): Promise<boolean>;
|
|
20
17
|
generatePassword(length?: number): string;
|
|
21
18
|
}
|
|
22
|
-
|
|
23
|
-
//#region src/password/policy.d.ts
|
|
24
|
-
declare function normalizePolicies(policies?: (PasswordPolicyDef | PasswordPolicyInstance)[]): PasswordPolicy[];
|
|
19
|
+
export declare function normalizePolicies(policies?: (PasswordPolicyDef | PasswordPolicyInstance)[]): PasswordPolicy[];
|
|
25
20
|
/**
|
|
26
21
|
* Helper that builds a `PasswordPolicyDef` from a real backend function plus
|
|
27
22
|
* its bound positional arguments. The function runs directly on the server
|
|
@@ -43,13 +38,13 @@ declare function normalizePolicies(policies?: (PasswordPolicyDef | PasswordPolic
|
|
|
43
38
|
* `undefined` and `transferable` is false; frontend pre-validation skips it
|
|
44
39
|
* and only the server check enforces the rule.
|
|
45
40
|
*/
|
|
46
|
-
declare function definePasswordPolicy<A extends readonly unknown[]>(opts: {
|
|
41
|
+
export declare function definePasswordPolicy<A extends readonly unknown[]>(opts: {
|
|
47
42
|
rule: (v: string, ...args: A) => boolean | Promise<boolean>;
|
|
48
43
|
args?: A;
|
|
49
44
|
description?: string;
|
|
50
45
|
errorMessage?: string;
|
|
51
46
|
}): PasswordPolicyDef;
|
|
52
|
-
declare class PasswordPolicy implements PasswordPolicyInstance {
|
|
47
|
+
export declare class PasswordPolicy implements PasswordPolicyInstance {
|
|
53
48
|
readonly rule: PasswordPolicyEvalFn;
|
|
54
49
|
readonly serialized?: string;
|
|
55
50
|
readonly description: string;
|
|
@@ -58,8 +53,6 @@ declare class PasswordPolicy implements PasswordPolicyInstance {
|
|
|
58
53
|
evaluate(password: string, context?: PasswordPolicyContext): boolean | Promise<boolean>;
|
|
59
54
|
get transferable(): boolean;
|
|
60
55
|
}
|
|
61
|
-
//#endregion
|
|
62
|
-
//#region src/user-service.d.ts
|
|
63
56
|
interface ResolvedConfig {
|
|
64
57
|
password: Required<Omit<PasswordConfig, "policies">> & {
|
|
65
58
|
policies: PasswordPolicy[];
|
|
@@ -71,7 +64,7 @@ interface ResolvedConfig {
|
|
|
71
64
|
};
|
|
72
65
|
emailField?: string;
|
|
73
66
|
}
|
|
74
|
-
declare const SEEN_DEVICES_DEFAULT_CAP = 5;
|
|
67
|
+
export declare const SEEN_DEVICES_DEFAULT_CAP = 5;
|
|
75
68
|
/**
|
|
76
69
|
* Orchestrates user credentials over a pluggable {@link UserStore}.
|
|
77
70
|
*
|
|
@@ -81,7 +74,7 @@ declare const SEEN_DEVICES_DEFAULT_CAP = 5;
|
|
|
81
74
|
* handle-driven entry points) take a `username`/`email` login handle, resolved
|
|
82
75
|
* via `UserStore.findByHandle`.
|
|
83
76
|
*/
|
|
84
|
-
declare class UserService<T extends object = object> {
|
|
77
|
+
export declare class UserService<T extends object = object> {
|
|
85
78
|
protected readonly store: UserStore<T>;
|
|
86
79
|
protected readonly config: ResolvedConfig;
|
|
87
80
|
protected readonly hasher: PasswordHasher;
|
|
@@ -113,8 +106,8 @@ declare class UserService<T extends object = object> {
|
|
|
113
106
|
getUserSnapshot(id: string, scope: object): Promise<UserCredentials & T>;
|
|
114
107
|
/**
|
|
115
108
|
* Creates a user with `account.active: false`. The invite workflow relies
|
|
116
|
-
* on this default (
|
|
117
|
-
*
|
|
109
|
+
* on this default (pending invitees stay inactive until they accept the
|
|
110
|
+
* invite). For setup scripts / seeders / tests that
|
|
118
111
|
* don't go through invite, follow up with `activateAccount(id)` or `login()`
|
|
119
112
|
* will throw `UserAuthError("INACTIVE")` — which the login workflow
|
|
120
113
|
* deliberately re-maps to `"Invalid credentials"` to avoid account
|
|
@@ -167,15 +160,15 @@ declare class UserService<T extends object = object> {
|
|
|
167
160
|
setPassword(id: string, newPassword: string): Promise<void>;
|
|
168
161
|
/**
|
|
169
162
|
* Hard-delete the user row by `id`. Returns nothing on success. Throws
|
|
170
|
-
* `UserAuthError("NOT_FOUND")` when no row matches.
|
|
171
|
-
*
|
|
163
|
+
* `UserAuthError("NOT_FOUND")` when no row matches. To revoke a pending
|
|
164
|
+
* invitation use {@link revokeInvitation}, which never deletes an account
|
|
165
|
+
* whose invite was accepted meanwhile.
|
|
172
166
|
*/
|
|
173
167
|
deleteUser(id: string): Promise<void>;
|
|
174
168
|
/**
|
|
175
169
|
* Deep-merge `patch` into the user record (top-level fields are shallow-
|
|
176
170
|
* merged; `account` / `mfa` / `password` are merged per their
|
|
177
171
|
* `@db.patch.strategy 'merge'` declaration). Returns the patched record.
|
|
178
|
-
* Used by the invite workflow's `applyProfile` default fallback.
|
|
179
172
|
*/
|
|
180
173
|
update(id: string, patch: Partial<UserCredentials & T>): Promise<UserCredentials & T>;
|
|
181
174
|
/**
|
|
@@ -187,6 +180,24 @@ declare class UserService<T extends object = object> {
|
|
|
187
180
|
* the same account can never both complete.
|
|
188
181
|
*/
|
|
189
182
|
acceptInvitation(id: string, nonce: string | undefined): Promise<boolean>;
|
|
183
|
+
/**
|
|
184
|
+
* Revoke a pending invitation: delete the invited account, so its invite
|
|
185
|
+
* link stops working (the invite workflow answers it with 410
|
|
186
|
+
* `INVITE_CANCELLED`). Returns `false` and deletes nothing when the account
|
|
187
|
+
* is not pending — the invite was already accepted — or when the row
|
|
188
|
+
* changed while the revoke ran. Throws `UserAuthError("NOT_FOUND")` when no
|
|
189
|
+
* row matches `id`.
|
|
190
|
+
*
|
|
191
|
+
* Safe against a concurrent acceptance or re-invite: one compare-and-set
|
|
192
|
+
* write first rotates `account.inviteNonce` while the row is still pending
|
|
193
|
+
* (an accept racing it fails), and the delete is conditional on the row's
|
|
194
|
+
* `version` still being the one that write produced — any write in between
|
|
195
|
+
* (a re-invite, an acceptance) makes it delete nothing. The bundled stores
|
|
196
|
+
* honour the conditional delete; a custom store whose `delete()` ignores
|
|
197
|
+
* `expectedVersion` leaves a window of one round-trip between the re-check
|
|
198
|
+
* and the delete.
|
|
199
|
+
*/
|
|
200
|
+
revokeInvitation(id: string): Promise<boolean>;
|
|
190
201
|
/**
|
|
191
202
|
* Flip the account active. `opts.verifiedEmail` lets callers that just
|
|
192
203
|
* proved an inbox (invite magic link, signup OTP) record the correspondence
|
|
@@ -386,9 +397,7 @@ declare class UserService<T extends object = object> {
|
|
|
386
397
|
*/
|
|
387
398
|
private incrementAndMaybeLock;
|
|
388
399
|
}
|
|
389
|
-
|
|
390
|
-
//#region src/store/memory.d.ts
|
|
391
|
-
declare class UserStoreMemory<T extends object = object> extends UserStore<T> {
|
|
400
|
+
export declare class UserStoreMemory<T extends object = object> extends UserStore<T> {
|
|
392
401
|
/** Keyed by the stable surrogate `id` (the token subject). */
|
|
393
402
|
private store;
|
|
394
403
|
/**
|
|
@@ -424,11 +433,11 @@ declare class UserStoreMemory<T extends object = object> extends UserStore<T> {
|
|
|
424
433
|
*/
|
|
425
434
|
private assertHandlesUnique;
|
|
426
435
|
update(id: string, update: UserStoreUpdate): Promise<boolean>;
|
|
427
|
-
delete(id: string
|
|
436
|
+
delete(id: string, opts?: {
|
|
437
|
+
expectedVersion?: number;
|
|
438
|
+
}): Promise<boolean>;
|
|
428
439
|
withCas(id: string, mutator: (current: UserCredentials & T) => UserStoreUpdate | null, opts?: WithCasOptions): Promise<void>;
|
|
429
440
|
}
|
|
430
|
-
//#endregion
|
|
431
|
-
//#region src/store/federated-identity-store-memory.d.ts
|
|
432
441
|
interface FederatedIdentityStoreMemoryOptions {
|
|
433
442
|
/** Injectable clock for deterministic `linkedAt` / `lastLoginAt`. Defaults to `Date.now`. */
|
|
434
443
|
clock?: () => number;
|
|
@@ -439,7 +448,7 @@ interface FederatedIdentityStoreMemoryOptions {
|
|
|
439
448
|
* `structuredClone` on every read/write isolates callers from later mutation
|
|
440
449
|
* (mirrors `UserStoreMemory`).
|
|
441
450
|
*/
|
|
442
|
-
declare class FederatedIdentityStoreMemory extends FederatedIdentityStore {
|
|
451
|
+
export declare class FederatedIdentityStoreMemory extends FederatedIdentityStore {
|
|
443
452
|
/** Keyed by `compositeKey(provider, subject)`. */
|
|
444
453
|
private store;
|
|
445
454
|
private clock;
|
|
@@ -451,28 +460,22 @@ declare class FederatedIdentityStoreMemory extends FederatedIdentityStore {
|
|
|
451
460
|
touchLogin(provider: string, subject: string, profile?: FederatedProfileSnapshot): Promise<void>;
|
|
452
461
|
deleteAllForUser(userId: string): Promise<number>;
|
|
453
462
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
declare const
|
|
457
|
-
declare const
|
|
458
|
-
declare const
|
|
459
|
-
declare const
|
|
460
|
-
declare
|
|
461
|
-
declare
|
|
462
|
-
|
|
463
|
-
//#region src/mfa/totp.d.ts
|
|
464
|
-
declare function generateTotpSecret(bytes?: number): string;
|
|
465
|
-
declare function generateTotpUri(secret: string, issuer: string, account: string, config?: Pick<TotpConfig, "period" | "digits">): string;
|
|
466
|
-
declare function generateTotpCode(secret: string, config?: TotpConfig): string;
|
|
463
|
+
export declare const ppHasMinLength: (min?: number) => PasswordPolicyDef;
|
|
464
|
+
export declare const ppHasUpperCase: (n?: number) => PasswordPolicyDef;
|
|
465
|
+
export declare const ppHasLowerCase: (n?: number) => PasswordPolicyDef;
|
|
466
|
+
export declare const ppHasNumber: (n?: number) => PasswordPolicyDef;
|
|
467
|
+
export declare const ppHasSpecialChar: (n?: number) => PasswordPolicyDef;
|
|
468
|
+
export declare const ppMaxRepeatedChars: (maxRepeated?: number) => PasswordPolicyDef;
|
|
469
|
+
export declare function generateTotpSecret(bytes?: number): string;
|
|
470
|
+
export declare function generateTotpUri(secret: string, issuer: string, account: string, config?: Pick<TotpConfig, "period" | "digits">): string;
|
|
471
|
+
export declare function generateTotpCode(secret: string, config?: TotpConfig): string;
|
|
467
472
|
/**
|
|
468
473
|
* Returns the matched HOTP counter when `code` is valid within the verification
|
|
469
474
|
* window, otherwise `null`. Returning the counter (not a bool) lets the caller
|
|
470
475
|
* persist `lastUsedWindow` and reject same-window replays (RFC 6238 §5.2 SHOULD).
|
|
471
476
|
*/
|
|
472
|
-
declare function verifyTotpCode(secret: string, code: string, config?: TotpConfig): number | null;
|
|
473
|
-
declare function generateMfaCode(length?: number): string;
|
|
474
|
-
//#endregion
|
|
475
|
-
//#region src/mfa/codes.d.ts
|
|
477
|
+
export declare function verifyTotpCode(secret: string, code: string, config?: TotpConfig): number | null;
|
|
478
|
+
export declare function generateMfaCode(length?: number): string;
|
|
476
479
|
/**
|
|
477
480
|
* SHA-256 hash of an MFA code (e.g. one-time email/SMS code or backup code).
|
|
478
481
|
*
|
|
@@ -480,7 +483,7 @@ declare function generateMfaCode(length?: number): string;
|
|
|
480
483
|
* Use {@link verifyMfaCode} to compare a submitted plaintext code against the
|
|
481
484
|
* stored hash in constant time.
|
|
482
485
|
*/
|
|
483
|
-
declare function hashMfaCode(code: string): string;
|
|
486
|
+
export declare function hashMfaCode(code: string): string;
|
|
484
487
|
/**
|
|
485
488
|
* Constant-time comparison of a submitted plaintext code against an
|
|
486
489
|
* expected SHA-256 hex hash (as produced by {@link hashMfaCode}).
|
|
@@ -488,12 +491,9 @@ declare function hashMfaCode(code: string): string;
|
|
|
488
491
|
* Returns false for malformed/empty expected hashes (timingSafeEqual
|
|
489
492
|
* requires equal-length, non-empty buffers).
|
|
490
493
|
*/
|
|
491
|
-
declare function verifyMfaCode(submitted: string, expectedHash: string): boolean;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
declare function
|
|
495
|
-
declare function
|
|
496
|
-
|
|
497
|
-
declare function setAtPath(obj: object, path: string, value: unknown): void;
|
|
498
|
-
//#endregion
|
|
499
|
-
export { type AccountData, type DeepPartial, type FederatedIdentity, FederatedIdentityStore, FederatedIdentityStoreMemory, type FederatedIdentityStoreMemoryOptions, type FederatedProfileSnapshot, type LockStatus, type LockoutConfig, type LoginResult, type MfaData, type MfaMethod, type MfaMethodInfo, type NewFederatedIdentity, type PasswordConfig, type PasswordData, PasswordHasher, PasswordPolicy, type PasswordPolicyContext, type PasswordPolicyDef, type PasswordPolicyEvalFn, type PasswordPolicyInstance, type PolicyCheckResult, SEEN_DEVICES_DEFAULT_CAP, type TotpConfig, type TransferablePolicy, type TrustedDeviceRecord, UserAuthError, type UserAuthErrorType, type UserCredentials, UserService, type UserServiceConfig, UserStore, UserStoreMemory, type UserStoreUpdate, definePasswordPolicy, generateMfaCode, generateTotpCode, generateTotpSecret, generateTotpUri, hashMfaCode, maskEmail, maskMfaValue, maskPhone, normalizePolicies, pickDefinedProfile, ppHasLowerCase, ppHasMinLength, ppHasNumber, ppHasSpecialChar, ppHasUpperCase, ppMaxRepeatedChars, setAtPath, verifyMfaCode, verifyTotpCode };
|
|
494
|
+
export declare function verifyMfaCode(submitted: string, expectedHash: string): boolean;
|
|
495
|
+
export declare function maskEmail(email: string): string;
|
|
496
|
+
export declare function maskPhone(phone: string): string;
|
|
497
|
+
export declare function maskMfaValue(method: MfaMethod): string;
|
|
498
|
+
export declare function setAtPath(obj: object, path: string, value: unknown): void;
|
|
499
|
+
export { type AccountData, type DeepPartial, type FederatedIdentity, FederatedIdentityStore, type FederatedIdentityStoreMemoryOptions, type FederatedProfileSnapshot, type LockStatus, type LockoutConfig, type LoginResult, type MfaData, type MfaMethod, type MfaMethodInfo, type NewFederatedIdentity, type PasswordConfig, type PasswordData, type PasswordPolicyContext, type PasswordPolicyDef, type PasswordPolicyEvalFn, type PasswordPolicyInstance, type PolicyCheckResult, type TotpConfig, type TransferablePolicy, type TrustedDeviceRecord, type UserAuthErrorType, type UserCredentials, type UserServiceConfig, UserStore, type UserStoreUpdate, pickDefinedProfile };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import { C as TotpConfig, D as UserCredentials, E as UserAuthErrorType, O as UserServiceConfig, S as PolicyCheckResult, T as TrustedDeviceRecord, _ as PasswordData, a as pickDefinedProfile, b as PasswordPolicyEvalFn, c as AccountData, d as LockoutConfig, f as LoginResult, g as PasswordConfig, h as MfaMethodInfo, i as NewFederatedIdentity, k as UserStoreUpdate, l as DeepPartial, m as MfaMethod, n as FederatedIdentityStore, o as UserStore, p as MfaData, r as FederatedProfileSnapshot, s as WithCasOptions, t as FederatedIdentity, u as LockStatus, v as PasswordPolicyContext, w as TransferablePolicy, x as PasswordPolicyInstance, y as PasswordPolicyDef } from "./federated-identity-store-
|
|
2
|
-
|
|
3
|
-
declare class UserAuthError extends Error {
|
|
1
|
+
import { C as TotpConfig, D as UserCredentials, E as UserAuthErrorType, O as UserServiceConfig, S as PolicyCheckResult, T as TrustedDeviceRecord, _ as PasswordData, a as pickDefinedProfile, b as PasswordPolicyEvalFn, c as AccountData, d as LockoutConfig, f as LoginResult, g as PasswordConfig, h as MfaMethodInfo, i as NewFederatedIdentity, k as UserStoreUpdate, l as DeepPartial, m as MfaMethod, n as FederatedIdentityStore, o as UserStore, p as MfaData, r as FederatedProfileSnapshot, s as WithCasOptions, t as FederatedIdentity, u as LockStatus, v as PasswordPolicyContext, w as TransferablePolicy, x as PasswordPolicyInstance, y as PasswordPolicyDef } from "./federated-identity-store-Ds7Ye9qJ.mjs";
|
|
2
|
+
export declare class UserAuthError extends Error {
|
|
4
3
|
readonly type: UserAuthErrorType;
|
|
5
4
|
readonly details?: Record<string, unknown> | undefined;
|
|
6
5
|
readonly name = "UserAuthError";
|
|
7
6
|
constructor(type: UserAuthErrorType, message?: string, details?: Record<string, unknown> | undefined);
|
|
8
7
|
}
|
|
9
|
-
|
|
10
|
-
//#region src/password/hasher.d.ts
|
|
11
|
-
declare class PasswordHasher {
|
|
8
|
+
export declare class PasswordHasher {
|
|
12
9
|
private readonly pepper;
|
|
13
10
|
private readonly N;
|
|
14
11
|
private readonly r;
|
|
@@ -19,9 +16,7 @@ declare class PasswordHasher {
|
|
|
19
16
|
verify(password: string, encoded: string): Promise<boolean>;
|
|
20
17
|
generatePassword(length?: number): string;
|
|
21
18
|
}
|
|
22
|
-
|
|
23
|
-
//#region src/password/policy.d.ts
|
|
24
|
-
declare function normalizePolicies(policies?: (PasswordPolicyDef | PasswordPolicyInstance)[]): PasswordPolicy[];
|
|
19
|
+
export declare function normalizePolicies(policies?: (PasswordPolicyDef | PasswordPolicyInstance)[]): PasswordPolicy[];
|
|
25
20
|
/**
|
|
26
21
|
* Helper that builds a `PasswordPolicyDef` from a real backend function plus
|
|
27
22
|
* its bound positional arguments. The function runs directly on the server
|
|
@@ -43,13 +38,13 @@ declare function normalizePolicies(policies?: (PasswordPolicyDef | PasswordPolic
|
|
|
43
38
|
* `undefined` and `transferable` is false; frontend pre-validation skips it
|
|
44
39
|
* and only the server check enforces the rule.
|
|
45
40
|
*/
|
|
46
|
-
declare function definePasswordPolicy<A extends readonly unknown[]>(opts: {
|
|
41
|
+
export declare function definePasswordPolicy<A extends readonly unknown[]>(opts: {
|
|
47
42
|
rule: (v: string, ...args: A) => boolean | Promise<boolean>;
|
|
48
43
|
args?: A;
|
|
49
44
|
description?: string;
|
|
50
45
|
errorMessage?: string;
|
|
51
46
|
}): PasswordPolicyDef;
|
|
52
|
-
declare class PasswordPolicy implements PasswordPolicyInstance {
|
|
47
|
+
export declare class PasswordPolicy implements PasswordPolicyInstance {
|
|
53
48
|
readonly rule: PasswordPolicyEvalFn;
|
|
54
49
|
readonly serialized?: string;
|
|
55
50
|
readonly description: string;
|
|
@@ -58,8 +53,6 @@ declare class PasswordPolicy implements PasswordPolicyInstance {
|
|
|
58
53
|
evaluate(password: string, context?: PasswordPolicyContext): boolean | Promise<boolean>;
|
|
59
54
|
get transferable(): boolean;
|
|
60
55
|
}
|
|
61
|
-
//#endregion
|
|
62
|
-
//#region src/user-service.d.ts
|
|
63
56
|
interface ResolvedConfig {
|
|
64
57
|
password: Required<Omit<PasswordConfig, "policies">> & {
|
|
65
58
|
policies: PasswordPolicy[];
|
|
@@ -71,7 +64,7 @@ interface ResolvedConfig {
|
|
|
71
64
|
};
|
|
72
65
|
emailField?: string;
|
|
73
66
|
}
|
|
74
|
-
declare const SEEN_DEVICES_DEFAULT_CAP = 5;
|
|
67
|
+
export declare const SEEN_DEVICES_DEFAULT_CAP = 5;
|
|
75
68
|
/**
|
|
76
69
|
* Orchestrates user credentials over a pluggable {@link UserStore}.
|
|
77
70
|
*
|
|
@@ -81,7 +74,7 @@ declare const SEEN_DEVICES_DEFAULT_CAP = 5;
|
|
|
81
74
|
* handle-driven entry points) take a `username`/`email` login handle, resolved
|
|
82
75
|
* via `UserStore.findByHandle`.
|
|
83
76
|
*/
|
|
84
|
-
declare class UserService<T extends object = object> {
|
|
77
|
+
export declare class UserService<T extends object = object> {
|
|
85
78
|
protected readonly store: UserStore<T>;
|
|
86
79
|
protected readonly config: ResolvedConfig;
|
|
87
80
|
protected readonly hasher: PasswordHasher;
|
|
@@ -113,8 +106,8 @@ declare class UserService<T extends object = object> {
|
|
|
113
106
|
getUserSnapshot(id: string, scope: object): Promise<UserCredentials & T>;
|
|
114
107
|
/**
|
|
115
108
|
* Creates a user with `account.active: false`. The invite workflow relies
|
|
116
|
-
* on this default (
|
|
117
|
-
*
|
|
109
|
+
* on this default (pending invitees stay inactive until they accept the
|
|
110
|
+
* invite). For setup scripts / seeders / tests that
|
|
118
111
|
* don't go through invite, follow up with `activateAccount(id)` or `login()`
|
|
119
112
|
* will throw `UserAuthError("INACTIVE")` — which the login workflow
|
|
120
113
|
* deliberately re-maps to `"Invalid credentials"` to avoid account
|
|
@@ -167,15 +160,15 @@ declare class UserService<T extends object = object> {
|
|
|
167
160
|
setPassword(id: string, newPassword: string): Promise<void>;
|
|
168
161
|
/**
|
|
169
162
|
* Hard-delete the user row by `id`. Returns nothing on success. Throws
|
|
170
|
-
* `UserAuthError("NOT_FOUND")` when no row matches.
|
|
171
|
-
*
|
|
163
|
+
* `UserAuthError("NOT_FOUND")` when no row matches. To revoke a pending
|
|
164
|
+
* invitation use {@link revokeInvitation}, which never deletes an account
|
|
165
|
+
* whose invite was accepted meanwhile.
|
|
172
166
|
*/
|
|
173
167
|
deleteUser(id: string): Promise<void>;
|
|
174
168
|
/**
|
|
175
169
|
* Deep-merge `patch` into the user record (top-level fields are shallow-
|
|
176
170
|
* merged; `account` / `mfa` / `password` are merged per their
|
|
177
171
|
* `@db.patch.strategy 'merge'` declaration). Returns the patched record.
|
|
178
|
-
* Used by the invite workflow's `applyProfile` default fallback.
|
|
179
172
|
*/
|
|
180
173
|
update(id: string, patch: Partial<UserCredentials & T>): Promise<UserCredentials & T>;
|
|
181
174
|
/**
|
|
@@ -187,6 +180,24 @@ declare class UserService<T extends object = object> {
|
|
|
187
180
|
* the same account can never both complete.
|
|
188
181
|
*/
|
|
189
182
|
acceptInvitation(id: string, nonce: string | undefined): Promise<boolean>;
|
|
183
|
+
/**
|
|
184
|
+
* Revoke a pending invitation: delete the invited account, so its invite
|
|
185
|
+
* link stops working (the invite workflow answers it with 410
|
|
186
|
+
* `INVITE_CANCELLED`). Returns `false` and deletes nothing when the account
|
|
187
|
+
* is not pending — the invite was already accepted — or when the row
|
|
188
|
+
* changed while the revoke ran. Throws `UserAuthError("NOT_FOUND")` when no
|
|
189
|
+
* row matches `id`.
|
|
190
|
+
*
|
|
191
|
+
* Safe against a concurrent acceptance or re-invite: one compare-and-set
|
|
192
|
+
* write first rotates `account.inviteNonce` while the row is still pending
|
|
193
|
+
* (an accept racing it fails), and the delete is conditional on the row's
|
|
194
|
+
* `version` still being the one that write produced — any write in between
|
|
195
|
+
* (a re-invite, an acceptance) makes it delete nothing. The bundled stores
|
|
196
|
+
* honour the conditional delete; a custom store whose `delete()` ignores
|
|
197
|
+
* `expectedVersion` leaves a window of one round-trip between the re-check
|
|
198
|
+
* and the delete.
|
|
199
|
+
*/
|
|
200
|
+
revokeInvitation(id: string): Promise<boolean>;
|
|
190
201
|
/**
|
|
191
202
|
* Flip the account active. `opts.verifiedEmail` lets callers that just
|
|
192
203
|
* proved an inbox (invite magic link, signup OTP) record the correspondence
|
|
@@ -386,9 +397,7 @@ declare class UserService<T extends object = object> {
|
|
|
386
397
|
*/
|
|
387
398
|
private incrementAndMaybeLock;
|
|
388
399
|
}
|
|
389
|
-
|
|
390
|
-
//#region src/store/memory.d.ts
|
|
391
|
-
declare class UserStoreMemory<T extends object = object> extends UserStore<T> {
|
|
400
|
+
export declare class UserStoreMemory<T extends object = object> extends UserStore<T> {
|
|
392
401
|
/** Keyed by the stable surrogate `id` (the token subject). */
|
|
393
402
|
private store;
|
|
394
403
|
/**
|
|
@@ -424,11 +433,11 @@ declare class UserStoreMemory<T extends object = object> extends UserStore<T> {
|
|
|
424
433
|
*/
|
|
425
434
|
private assertHandlesUnique;
|
|
426
435
|
update(id: string, update: UserStoreUpdate): Promise<boolean>;
|
|
427
|
-
delete(id: string
|
|
436
|
+
delete(id: string, opts?: {
|
|
437
|
+
expectedVersion?: number;
|
|
438
|
+
}): Promise<boolean>;
|
|
428
439
|
withCas(id: string, mutator: (current: UserCredentials & T) => UserStoreUpdate | null, opts?: WithCasOptions): Promise<void>;
|
|
429
440
|
}
|
|
430
|
-
//#endregion
|
|
431
|
-
//#region src/store/federated-identity-store-memory.d.ts
|
|
432
441
|
interface FederatedIdentityStoreMemoryOptions {
|
|
433
442
|
/** Injectable clock for deterministic `linkedAt` / `lastLoginAt`. Defaults to `Date.now`. */
|
|
434
443
|
clock?: () => number;
|
|
@@ -439,7 +448,7 @@ interface FederatedIdentityStoreMemoryOptions {
|
|
|
439
448
|
* `structuredClone` on every read/write isolates callers from later mutation
|
|
440
449
|
* (mirrors `UserStoreMemory`).
|
|
441
450
|
*/
|
|
442
|
-
declare class FederatedIdentityStoreMemory extends FederatedIdentityStore {
|
|
451
|
+
export declare class FederatedIdentityStoreMemory extends FederatedIdentityStore {
|
|
443
452
|
/** Keyed by `compositeKey(provider, subject)`. */
|
|
444
453
|
private store;
|
|
445
454
|
private clock;
|
|
@@ -451,28 +460,22 @@ declare class FederatedIdentityStoreMemory extends FederatedIdentityStore {
|
|
|
451
460
|
touchLogin(provider: string, subject: string, profile?: FederatedProfileSnapshot): Promise<void>;
|
|
452
461
|
deleteAllForUser(userId: string): Promise<number>;
|
|
453
462
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
declare const
|
|
457
|
-
declare const
|
|
458
|
-
declare const
|
|
459
|
-
declare const
|
|
460
|
-
declare
|
|
461
|
-
declare
|
|
462
|
-
|
|
463
|
-
//#region src/mfa/totp.d.ts
|
|
464
|
-
declare function generateTotpSecret(bytes?: number): string;
|
|
465
|
-
declare function generateTotpUri(secret: string, issuer: string, account: string, config?: Pick<TotpConfig, "period" | "digits">): string;
|
|
466
|
-
declare function generateTotpCode(secret: string, config?: TotpConfig): string;
|
|
463
|
+
export declare const ppHasMinLength: (min?: number) => PasswordPolicyDef;
|
|
464
|
+
export declare const ppHasUpperCase: (n?: number) => PasswordPolicyDef;
|
|
465
|
+
export declare const ppHasLowerCase: (n?: number) => PasswordPolicyDef;
|
|
466
|
+
export declare const ppHasNumber: (n?: number) => PasswordPolicyDef;
|
|
467
|
+
export declare const ppHasSpecialChar: (n?: number) => PasswordPolicyDef;
|
|
468
|
+
export declare const ppMaxRepeatedChars: (maxRepeated?: number) => PasswordPolicyDef;
|
|
469
|
+
export declare function generateTotpSecret(bytes?: number): string;
|
|
470
|
+
export declare function generateTotpUri(secret: string, issuer: string, account: string, config?: Pick<TotpConfig, "period" | "digits">): string;
|
|
471
|
+
export declare function generateTotpCode(secret: string, config?: TotpConfig): string;
|
|
467
472
|
/**
|
|
468
473
|
* Returns the matched HOTP counter when `code` is valid within the verification
|
|
469
474
|
* window, otherwise `null`. Returning the counter (not a bool) lets the caller
|
|
470
475
|
* persist `lastUsedWindow` and reject same-window replays (RFC 6238 §5.2 SHOULD).
|
|
471
476
|
*/
|
|
472
|
-
declare function verifyTotpCode(secret: string, code: string, config?: TotpConfig): number | null;
|
|
473
|
-
declare function generateMfaCode(length?: number): string;
|
|
474
|
-
//#endregion
|
|
475
|
-
//#region src/mfa/codes.d.ts
|
|
477
|
+
export declare function verifyTotpCode(secret: string, code: string, config?: TotpConfig): number | null;
|
|
478
|
+
export declare function generateMfaCode(length?: number): string;
|
|
476
479
|
/**
|
|
477
480
|
* SHA-256 hash of an MFA code (e.g. one-time email/SMS code or backup code).
|
|
478
481
|
*
|
|
@@ -480,7 +483,7 @@ declare function generateMfaCode(length?: number): string;
|
|
|
480
483
|
* Use {@link verifyMfaCode} to compare a submitted plaintext code against the
|
|
481
484
|
* stored hash in constant time.
|
|
482
485
|
*/
|
|
483
|
-
declare function hashMfaCode(code: string): string;
|
|
486
|
+
export declare function hashMfaCode(code: string): string;
|
|
484
487
|
/**
|
|
485
488
|
* Constant-time comparison of a submitted plaintext code against an
|
|
486
489
|
* expected SHA-256 hex hash (as produced by {@link hashMfaCode}).
|
|
@@ -488,12 +491,9 @@ declare function hashMfaCode(code: string): string;
|
|
|
488
491
|
* Returns false for malformed/empty expected hashes (timingSafeEqual
|
|
489
492
|
* requires equal-length, non-empty buffers).
|
|
490
493
|
*/
|
|
491
|
-
declare function verifyMfaCode(submitted: string, expectedHash: string): boolean;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
declare function
|
|
495
|
-
declare function
|
|
496
|
-
|
|
497
|
-
declare function setAtPath(obj: object, path: string, value: unknown): void;
|
|
498
|
-
//#endregion
|
|
499
|
-
export { type AccountData, type DeepPartial, type FederatedIdentity, FederatedIdentityStore, FederatedIdentityStoreMemory, type FederatedIdentityStoreMemoryOptions, type FederatedProfileSnapshot, type LockStatus, type LockoutConfig, type LoginResult, type MfaData, type MfaMethod, type MfaMethodInfo, type NewFederatedIdentity, type PasswordConfig, type PasswordData, PasswordHasher, PasswordPolicy, type PasswordPolicyContext, type PasswordPolicyDef, type PasswordPolicyEvalFn, type PasswordPolicyInstance, type PolicyCheckResult, SEEN_DEVICES_DEFAULT_CAP, type TotpConfig, type TransferablePolicy, type TrustedDeviceRecord, UserAuthError, type UserAuthErrorType, type UserCredentials, UserService, type UserServiceConfig, UserStore, UserStoreMemory, type UserStoreUpdate, definePasswordPolicy, generateMfaCode, generateTotpCode, generateTotpSecret, generateTotpUri, hashMfaCode, maskEmail, maskMfaValue, maskPhone, normalizePolicies, pickDefinedProfile, ppHasLowerCase, ppHasMinLength, ppHasNumber, ppHasSpecialChar, ppHasUpperCase, ppMaxRepeatedChars, setAtPath, verifyMfaCode, verifyTotpCode };
|
|
494
|
+
export declare function verifyMfaCode(submitted: string, expectedHash: string): boolean;
|
|
495
|
+
export declare function maskEmail(email: string): string;
|
|
496
|
+
export declare function maskPhone(phone: string): string;
|
|
497
|
+
export declare function maskMfaValue(method: MfaMethod): string;
|
|
498
|
+
export declare function setAtPath(obj: object, path: string, value: unknown): void;
|
|
499
|
+
export { type AccountData, type DeepPartial, type FederatedIdentity, FederatedIdentityStore, type FederatedIdentityStoreMemoryOptions, type FederatedProfileSnapshot, type LockStatus, type LockoutConfig, type LoginResult, type MfaData, type MfaMethod, type MfaMethodInfo, type NewFederatedIdentity, type PasswordConfig, type PasswordData, type PasswordPolicyContext, type PasswordPolicyDef, type PasswordPolicyEvalFn, type PasswordPolicyInstance, type PolicyCheckResult, type TotpConfig, type TransferablePolicy, type TrustedDeviceRecord, type UserAuthErrorType, type UserCredentials, type UserServiceConfig, UserStore, type UserStoreUpdate, pickDefinedProfile };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as maskEmail, c as setAtPath, i as incrementAtPath, l as UserStore, n as pickDefinedProfile, o as maskMfaValue, r as deepMerge, s as maskPhone, t as FederatedIdentityStore, u as UserAuthError } from "./federated-identity-store-
|
|
1
|
+
import { a as maskEmail, c as setAtPath, i as incrementAtPath, l as UserStore, n as pickDefinedProfile, o as maskMfaValue, r as deepMerge, s as maskPhone, t as FederatedIdentityStore, u as UserAuthError } from "./federated-identity-store-BZRvw9Pz.mjs";
|
|
2
2
|
import { createHash, createHmac, randomBytes, randomInt, randomUUID, scrypt, timingSafeEqual } from "node:crypto";
|
|
3
|
-
|
|
3
|
+
// src/base-x/base32.ts
|
|
4
4
|
/**
|
|
5
5
|
* Partially copied from "thirty-two" library, all credits to Chris Umbel.
|
|
6
6
|
*
|
|
@@ -71,8 +71,7 @@ function decode(input) {
|
|
|
71
71
|
}
|
|
72
72
|
return output.subarray(0, index);
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
//#region src/mfa/totp.ts
|
|
74
|
+
// src/mfa/totp.ts
|
|
76
75
|
function generateTotpSecret(bytes = 20) {
|
|
77
76
|
return encode(randomBytes(bytes)).toString().replace(/=+$/, "").toUpperCase();
|
|
78
77
|
}
|
|
@@ -126,8 +125,7 @@ function hotpCode(key, counter, digits) {
|
|
|
126
125
|
const offset = hmacResult[hmacResult.length - 1] & 15;
|
|
127
126
|
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");
|
|
128
127
|
}
|
|
129
|
-
|
|
130
|
-
//#region src/password/hasher.ts
|
|
128
|
+
// src/password/hasher.ts
|
|
131
129
|
const DEFAULTS = {
|
|
132
130
|
N: 16384,
|
|
133
131
|
r: 8,
|
|
@@ -137,11 +135,11 @@ const DEFAULTS = {
|
|
|
137
135
|
};
|
|
138
136
|
const PREFIX = "$scrypt$";
|
|
139
137
|
/** Node's default scrypt memory cap (32 MiB) — kept as the floor. */
|
|
140
|
-
const DEFAULT_MAXMEM =
|
|
138
|
+
const DEFAULT_MAXMEM = 33554432;
|
|
141
139
|
/** Upper bound for the memory a (possibly stored, untrusted) parameter set may ask for. */
|
|
142
|
-
const MAX_MAXMEM =
|
|
140
|
+
const MAX_MAXMEM = 1073741824;
|
|
143
141
|
function scryptAsync(password, salt, keyLength, options) {
|
|
144
|
-
const maxmem = Math.min(MAX_MAXMEM, Math.max(DEFAULT_MAXMEM, 256 * options.N * options.r +
|
|
142
|
+
const maxmem = Math.min(MAX_MAXMEM, Math.max(DEFAULT_MAXMEM, 256 * options.N * options.r + 1048576));
|
|
145
143
|
return new Promise((resolve, reject) => {
|
|
146
144
|
scrypt(password, salt, keyLength, {
|
|
147
145
|
...options,
|
|
@@ -211,7 +209,7 @@ var PasswordHasher = class {
|
|
|
211
209
|
const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
212
210
|
const digits = "0123456789";
|
|
213
211
|
const special = "!@#$%^&*()-_=+";
|
|
214
|
-
const all =
|
|
212
|
+
const all = lower + upper + digits + special;
|
|
215
213
|
const bytes = randomBytes(minLen);
|
|
216
214
|
const result = Array.from({ length: minLen });
|
|
217
215
|
result[0] = lower[bytes[0] % 26];
|
|
@@ -227,8 +225,7 @@ var PasswordHasher = class {
|
|
|
227
225
|
return result.join("");
|
|
228
226
|
}
|
|
229
227
|
};
|
|
230
|
-
|
|
231
|
-
//#region src/password/policy.ts
|
|
228
|
+
// src/password/policy.ts
|
|
232
229
|
function normalizePolicies(policies) {
|
|
233
230
|
return (policies || []).map((p) => p instanceof PasswordPolicy ? p : new PasswordPolicy(p));
|
|
234
231
|
}
|
|
@@ -282,8 +279,7 @@ var PasswordPolicy = class {
|
|
|
282
279
|
return this.serialized !== void 0;
|
|
283
280
|
}
|
|
284
281
|
};
|
|
285
|
-
|
|
286
|
-
//#region src/user-service.ts
|
|
282
|
+
// src/user-service.ts
|
|
287
283
|
function resolveConfig(config) {
|
|
288
284
|
return {
|
|
289
285
|
password: {
|
|
@@ -413,8 +409,8 @@ var UserService = class {
|
|
|
413
409
|
}
|
|
414
410
|
/**
|
|
415
411
|
* Creates a user with `account.active: false`. The invite workflow relies
|
|
416
|
-
* on this default (
|
|
417
|
-
*
|
|
412
|
+
* on this default (pending invitees stay inactive until they accept the
|
|
413
|
+
* invite). For setup scripts / seeders / tests that
|
|
418
414
|
* don't go through invite, follow up with `activateAccount(id)` or `login()`
|
|
419
415
|
* will throw `UserAuthError("INACTIVE")` — which the login workflow
|
|
420
416
|
* deliberately re-maps to `"Invalid credentials"` to avoid account
|
|
@@ -543,8 +539,9 @@ var UserService = class {
|
|
|
543
539
|
}
|
|
544
540
|
/**
|
|
545
541
|
* Hard-delete the user row by `id`. Returns nothing on success. Throws
|
|
546
|
-
* `UserAuthError("NOT_FOUND")` when no row matches.
|
|
547
|
-
*
|
|
542
|
+
* `UserAuthError("NOT_FOUND")` when no row matches. To revoke a pending
|
|
543
|
+
* invitation use {@link revokeInvitation}, which never deletes an account
|
|
544
|
+
* whose invite was accepted meanwhile.
|
|
548
545
|
*/
|
|
549
546
|
async deleteUser(id) {
|
|
550
547
|
if (!await this.store.delete(id)) throw new UserAuthError("NOT_FOUND");
|
|
@@ -553,7 +550,6 @@ var UserService = class {
|
|
|
553
550
|
* Deep-merge `patch` into the user record (top-level fields are shallow-
|
|
554
551
|
* merged; `account` / `mfa` / `password` are merged per their
|
|
555
552
|
* `@db.patch.strategy 'merge'` declaration). Returns the patched record.
|
|
556
|
-
* Used by the invite workflow's `applyProfile` default fallback.
|
|
557
553
|
*/
|
|
558
554
|
async update(id, patch) {
|
|
559
555
|
if (!await this.store.update(id, { set: patch })) throw new UserAuthError("NOT_FOUND");
|
|
@@ -580,6 +576,36 @@ var UserService = class {
|
|
|
580
576
|
return accepted;
|
|
581
577
|
}
|
|
582
578
|
/**
|
|
579
|
+
* Revoke a pending invitation: delete the invited account, so its invite
|
|
580
|
+
* link stops working (the invite workflow answers it with 410
|
|
581
|
+
* `INVITE_CANCELLED`). Returns `false` and deletes nothing when the account
|
|
582
|
+
* is not pending — the invite was already accepted — or when the row
|
|
583
|
+
* changed while the revoke ran. Throws `UserAuthError("NOT_FOUND")` when no
|
|
584
|
+
* row matches `id`.
|
|
585
|
+
*
|
|
586
|
+
* Safe against a concurrent acceptance or re-invite: one compare-and-set
|
|
587
|
+
* write first rotates `account.inviteNonce` while the row is still pending
|
|
588
|
+
* (an accept racing it fails), and the delete is conditional on the row's
|
|
589
|
+
* `version` still being the one that write produced — any write in between
|
|
590
|
+
* (a re-invite, an acceptance) makes it delete nothing. The bundled stores
|
|
591
|
+
* honour the conditional delete; a custom store whose `delete()` ignores
|
|
592
|
+
* `expectedVersion` leaves a window of one round-trip between the re-check
|
|
593
|
+
* and the delete.
|
|
594
|
+
*/
|
|
595
|
+
async revokeInvitation(id) {
|
|
596
|
+
const nonce = randomBytes(16).toString("base64url");
|
|
597
|
+
let pending = false;
|
|
598
|
+
await this.store.withCas(id, (user) => {
|
|
599
|
+
pending = user.account.pendingInvitation === true;
|
|
600
|
+
if (!pending) return null;
|
|
601
|
+
return { set: { account: { inviteNonce: nonce } } };
|
|
602
|
+
});
|
|
603
|
+
if (!pending) return false;
|
|
604
|
+
const claimed = await this.store.findById(id);
|
|
605
|
+
if (!claimed?.account.pendingInvitation || claimed.account.inviteNonce !== nonce) return false;
|
|
606
|
+
return this.store.delete(id, { expectedVersion: claimed.version ?? 0 });
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
583
609
|
* Flip the account active. `opts.verifiedEmail` lets callers that just
|
|
584
610
|
* proved an inbox (invite magic link, signup OTP) record the correspondence
|
|
585
611
|
* address in the same store write — see {@link setVerifiedEmail}.
|
|
@@ -1107,8 +1133,7 @@ function observeWrites(store, hooks) {
|
|
|
1107
1133
|
return fn;
|
|
1108
1134
|
} });
|
|
1109
1135
|
}
|
|
1110
|
-
|
|
1111
|
-
//#region src/store/memory.ts
|
|
1136
|
+
// src/store/memory.ts
|
|
1112
1137
|
var UserStoreMemory = class extends UserStore {
|
|
1113
1138
|
/** Keyed by the stable surrogate `id` (the token subject). */
|
|
1114
1139
|
store = /* @__PURE__ */ new Map();
|
|
@@ -1204,7 +1229,10 @@ var UserStoreMemory = class extends UserStore {
|
|
|
1204
1229
|
user.version = (user.version ?? 0) + 1;
|
|
1205
1230
|
return true;
|
|
1206
1231
|
}
|
|
1207
|
-
async delete(id) {
|
|
1232
|
+
async delete(id, opts) {
|
|
1233
|
+
const user = this.store.get(id);
|
|
1234
|
+
if (!user) return false;
|
|
1235
|
+
if (opts?.expectedVersion !== void 0 && (user.version ?? 0) !== opts.expectedVersion) return false;
|
|
1208
1236
|
return this.store.delete(id);
|
|
1209
1237
|
}
|
|
1210
1238
|
async withCas(id, mutator, opts) {
|
|
@@ -1222,8 +1250,7 @@ var UserStoreMemory = class extends UserStore {
|
|
|
1222
1250
|
throw new UserAuthError("CAS_EXHAUSTED");
|
|
1223
1251
|
}
|
|
1224
1252
|
};
|
|
1225
|
-
|
|
1226
|
-
//#region src/store/federated-identity-store-memory.ts
|
|
1253
|
+
// src/store/federated-identity-store-memory.ts
|
|
1227
1254
|
/**
|
|
1228
1255
|
* In-memory {@link FederatedIdentityStore} — the offline-testable reference
|
|
1229
1256
|
* impl (RFC IDP.md §9). Keyed by the composite `(provider, subject)`;
|
|
@@ -1281,8 +1308,7 @@ var FederatedIdentityStoreMemory = class extends FederatedIdentityStore {
|
|
|
1281
1308
|
function compositeKey(provider, subject) {
|
|
1282
1309
|
return `${provider}${subject}`;
|
|
1283
1310
|
}
|
|
1284
|
-
|
|
1285
|
-
//#region src/password/policies.ts
|
|
1311
|
+
// src/password/policies.ts
|
|
1286
1312
|
const ppHasMinLength = (min = 8) => definePasswordPolicy({
|
|
1287
1313
|
rule: (v, min) => v.length >= min,
|
|
1288
1314
|
args: [min],
|
|
@@ -1319,8 +1345,7 @@ const ppMaxRepeatedChars = (maxRepeated = 2) => definePasswordPolicy({
|
|
|
1319
1345
|
description: `No more than ${maxRepeated} consecutive repeated characters`,
|
|
1320
1346
|
errorMessage: `Password must not have more than ${maxRepeated} consecutive repeated characters`
|
|
1321
1347
|
});
|
|
1322
|
-
|
|
1323
|
-
//#region src/mfa/codes.ts
|
|
1348
|
+
// src/mfa/codes.ts
|
|
1324
1349
|
/**
|
|
1325
1350
|
* SHA-256 hash of an MFA code (e.g. one-time email/SMS code or backup code).
|
|
1326
1351
|
*
|
|
@@ -1345,5 +1370,4 @@ function verifyMfaCode(submitted, expectedHash) {
|
|
|
1345
1370
|
if (a.length !== b.length) return false;
|
|
1346
1371
|
return timingSafeEqual(a, b);
|
|
1347
1372
|
}
|
|
1348
|
-
//#endregion
|
|
1349
1373
|
export { FederatedIdentityStore, FederatedIdentityStoreMemory, PasswordHasher, PasswordPolicy, SEEN_DEVICES_DEFAULT_CAP, UserAuthError, UserService, UserStore, UserStoreMemory, definePasswordPolicy, generateMfaCode, generateTotpCode, generateTotpSecret, generateTotpUri, hashMfaCode, maskEmail, maskMfaValue, maskPhone, normalizePolicies, pickDefinedProfile, ppHasLowerCase, ppHasMinLength, ppHasNumber, ppHasSpecialChar, ppHasUpperCase, ppMaxRepeatedChars, setAtPath, verifyMfaCode, verifyTotpCode };
|