@aooth/user 0.1.6 → 0.1.8
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 +108 -22
- package/dist/atscript-db.d.cts +69 -12
- package/dist/atscript-db.d.mts +69 -12
- package/dist/atscript-db.mjs +103 -18
- package/dist/{user-store-BaBmH13V.mjs → federated-identity-store-Cmc7jBrw.mjs} +40 -1
- package/dist/federated-identity-store-DEEed8lA.d.cts +378 -0
- package/dist/federated-identity-store-DEEed8lA.d.mts +378 -0
- package/dist/{user-store-BPZVAboN.cjs → federated-identity-store-oRjhnR5l.cjs} +51 -0
- package/dist/index.cjs +301 -220
- package/dist/index.d.cts +114 -72
- package/dist/index.d.mts +114 -72
- package/dist/index.mjs +268 -189
- package/package.json +23 -9
- package/src/atscript-db/federated-identity.as +44 -0
- package/src/atscript-db/federated-identity.as.d.ts +62 -0
- package/src/atscript-db/user-credentials.as +7 -2
- package/src/atscript-db/user-credentials.as.d.ts +62 -0
- package/dist/user-store-B3EStUfT.d.cts +0 -246
- package/dist/user-store-C1lxahSB.d.mts +0 -246
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aooth/user",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "User credential primitives for aoothjs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aoothjs",
|
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|
|
24
|
-
"src/atscript-db/user-credentials.as"
|
|
24
|
+
"src/atscript-db/user-credentials.as",
|
|
25
|
+
"src/atscript-db/user-credentials.as.d.ts",
|
|
26
|
+
"src/atscript-db/federated-identity.as",
|
|
27
|
+
"src/atscript-db/federated-identity.as.d.ts"
|
|
25
28
|
],
|
|
26
29
|
"type": "module",
|
|
27
30
|
"sideEffects": false,
|
|
@@ -39,21 +42,32 @@
|
|
|
39
42
|
"import": "./dist/atscript-db.mjs",
|
|
40
43
|
"require": "./dist/atscript-db.cjs"
|
|
41
44
|
},
|
|
42
|
-
"./atscript-db/model.as":
|
|
45
|
+
"./atscript-db/model.as": {
|
|
46
|
+
"types": "./src/atscript-db/user-credentials.as.d.ts",
|
|
47
|
+
"default": "./src/atscript-db/user-credentials.as"
|
|
48
|
+
},
|
|
49
|
+
"./atscript-db/federated-model": {
|
|
50
|
+
"types": "./src/atscript-db/federated-identity.as.d.ts",
|
|
51
|
+
"default": "./src/atscript-db/federated-identity.as"
|
|
52
|
+
},
|
|
53
|
+
"./atscript-db/federated-model.as": {
|
|
54
|
+
"types": "./src/atscript-db/federated-identity.as.d.ts",
|
|
55
|
+
"default": "./src/atscript-db/federated-identity.as"
|
|
56
|
+
},
|
|
43
57
|
"./package.json": "./package.json"
|
|
44
58
|
},
|
|
45
59
|
"publishConfig": {
|
|
46
60
|
"access": "public"
|
|
47
61
|
},
|
|
48
62
|
"devDependencies": {
|
|
49
|
-
"@atscript/core": "^0.1.
|
|
50
|
-
"@atscript/db": "^0.1.
|
|
51
|
-
"@atscript/db-sql-tools": "^0.1.
|
|
52
|
-
"@atscript/db-sqlite": "^0.1.
|
|
53
|
-
"@atscript/typescript": "^0.1.
|
|
63
|
+
"@atscript/core": "^0.1.69",
|
|
64
|
+
"@atscript/db": "^0.1.96",
|
|
65
|
+
"@atscript/db-sql-tools": "^0.1.96",
|
|
66
|
+
"@atscript/db-sqlite": "^0.1.96",
|
|
67
|
+
"@atscript/typescript": "^0.1.69",
|
|
54
68
|
"@types/better-sqlite3": "^7.6.13",
|
|
55
69
|
"better-sqlite3": "^12.6.2",
|
|
56
|
-
"unplugin-atscript": "^0.1.
|
|
70
|
+
"unplugin-atscript": "^0.1.69"
|
|
57
71
|
},
|
|
58
72
|
"peerDependencies": {
|
|
59
73
|
"@atscript/db": ">=0.1.79"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Account-linking table: one external-provider account → exactly one aooth
|
|
2
|
+
// user. The genuinely new piece of persistent state for federated login —
|
|
3
|
+
// the `(provider, subject) → userId` map (RFC IDP.md §3.3). Shipped concrete
|
|
4
|
+
// (own `@db.table`), like `AoothAuthCredential`; consumers can extend it with
|
|
5
|
+
// `extends AoothFederatedIdentity {}` to re-own the table name, exactly as
|
|
6
|
+
// `DemoAuthCredential` does.
|
|
7
|
+
@db.table 'aooth_federated_identities'
|
|
8
|
+
@db.depth.limit 0
|
|
9
|
+
export interface AoothFederatedIdentity {
|
|
10
|
+
// Surrogate PK — lets a row be addressed (unlink-by-id) / extended.
|
|
11
|
+
@meta.id
|
|
12
|
+
@db.default.uuid
|
|
13
|
+
id: string
|
|
14
|
+
|
|
15
|
+
// Composite identity key. The SAME index name on both fields collapses
|
|
16
|
+
// into ONE compound UNIQUE index (atscript-db groups index fields by
|
|
17
|
+
// (type, name)) — so a provider account maps to at most one row, which is
|
|
18
|
+
// the anti-account-takeover guarantee (RFC §1 note #4, §4).
|
|
19
|
+
@db.index.unique 'provider_subject_idx'
|
|
20
|
+
provider: string // 'google' | 'github' | 'oidc:<issuer>' ...
|
|
21
|
+
@db.index.unique 'provider_subject_idx'
|
|
22
|
+
subject: string // the IdP's stable subject id (`sub`)
|
|
23
|
+
|
|
24
|
+
// Owner — the user's stable surrogate `id`. A PLAIN indexed string, NOT a
|
|
25
|
+
// `@db.rel.FK`: `@aooth/user` cannot know the consumer's concrete user
|
|
26
|
+
// table (`AoothUserCredentials` is an abstract, table-less base), so this
|
|
27
|
+
// mirrors `AoothAuthCredential.userId`. Cross-row cleanup is the explicit
|
|
28
|
+
// `FederatedIdentityStore.deleteAllForUser` (GDPR), not a DB cascade.
|
|
29
|
+
// Consumers wanting a hard FK + cascade re-declare it in their subclass.
|
|
30
|
+
@db.index.plain
|
|
31
|
+
userId: string
|
|
32
|
+
|
|
33
|
+
// Display snapshots — refreshed by `touchLogin` on each federated login;
|
|
34
|
+
// NOT join keys (the stable join is always `(provider, subject)`). A
|
|
35
|
+
// provider's snapshot email (e.g. Apple Private Relay) may differ from the
|
|
36
|
+
// user-row `email` handle, so these live here per-identity.
|
|
37
|
+
email?: string
|
|
38
|
+
emailVerified?: boolean
|
|
39
|
+
displayName?: string
|
|
40
|
+
avatarUrl?: string
|
|
41
|
+
|
|
42
|
+
linkedAt: number.timestamp
|
|
43
|
+
lastLoginAt?: number.timestamp
|
|
44
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// prettier-ignore-start
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/* oxlint-disable */
|
|
4
|
+
/// <reference path="./federated-identity.as" />
|
|
5
|
+
/**
|
|
6
|
+
* 🪄 This file was generated by Atscript
|
|
7
|
+
* Do not edit this file!
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { TAtscriptTypeObject, TAtscriptTypeComplex, TAtscriptTypeFinal, TAtscriptTypeArray, TAtscriptAnnotatedType, TMetadataMap, Validator, TValidatorOptions } from "@atscript/typescript/utils"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Atscript interface **AoothFederatedIdentity**
|
|
14
|
+
* @see {@link ./federated-identity.as:9:18}
|
|
15
|
+
*/
|
|
16
|
+
export declare class AoothFederatedIdentity {
|
|
17
|
+
id: string
|
|
18
|
+
provider: string
|
|
19
|
+
subject: string
|
|
20
|
+
userId: string
|
|
21
|
+
email?: string
|
|
22
|
+
emailVerified?: boolean
|
|
23
|
+
displayName?: string
|
|
24
|
+
avatarUrl?: string
|
|
25
|
+
linkedAt: number /* timestamp */
|
|
26
|
+
lastLoginAt?: number /* timestamp */
|
|
27
|
+
static __is_atscript_annotated_type: true
|
|
28
|
+
static type: TAtscriptTypeObject<keyof AoothFederatedIdentity, AoothFederatedIdentity>
|
|
29
|
+
static metadata: TMetadataMap<AtscriptMetadata>
|
|
30
|
+
static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof AoothFederatedIdentity>
|
|
31
|
+
/** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
|
|
32
|
+
static toJsonSchema: () => any
|
|
33
|
+
/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
|
|
34
|
+
static toExampleData?: () => any
|
|
35
|
+
static __flat: {
|
|
36
|
+
"id": string
|
|
37
|
+
"provider": string
|
|
38
|
+
"subject": string
|
|
39
|
+
"userId": string
|
|
40
|
+
"email"?: string
|
|
41
|
+
"emailVerified"?: boolean
|
|
42
|
+
"displayName"?: string
|
|
43
|
+
"avatarUrl"?: string
|
|
44
|
+
"linkedAt": number /* timestamp */
|
|
45
|
+
"lastLoginAt"?: number /* timestamp */
|
|
46
|
+
}
|
|
47
|
+
static __ownProps: {
|
|
48
|
+
"id": string
|
|
49
|
+
"provider": string
|
|
50
|
+
"subject": string
|
|
51
|
+
"userId": string
|
|
52
|
+
"email"?: string
|
|
53
|
+
"emailVerified"?: boolean
|
|
54
|
+
"displayName"?: string
|
|
55
|
+
"avatarUrl"?: string
|
|
56
|
+
"linkedAt": number /* timestamp */
|
|
57
|
+
"lastLoginAt"?: number /* timestamp */
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static __pk: string
|
|
61
|
+
}
|
|
62
|
+
// prettier-ignore-end
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export interface AoothUserCredentials {
|
|
2
|
+
@meta.id
|
|
3
|
+
@db.default.uuid
|
|
4
|
+
id: string
|
|
5
|
+
|
|
2
6
|
@db.index.unique 'username_idx'
|
|
3
7
|
username: string
|
|
4
8
|
|
|
9
|
+
@db.index.unique 'email_idx'
|
|
10
|
+
email?: string
|
|
11
|
+
|
|
5
12
|
@db.column.version
|
|
6
13
|
version: number.int
|
|
7
14
|
|
|
@@ -42,6 +49,4 @@ export interface AoothUserCredentials {
|
|
|
42
49
|
expiresAt: number.timestamp
|
|
43
50
|
name?: string
|
|
44
51
|
}[]
|
|
45
|
-
|
|
46
|
-
backupCodes?: string[]
|
|
47
52
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// prettier-ignore-start
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/* oxlint-disable */
|
|
4
|
+
/// <reference path="./user-credentials.as" />
|
|
5
|
+
/**
|
|
6
|
+
* 🪄 This file was generated by Atscript
|
|
7
|
+
* Do not edit this file!
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { TAtscriptTypeObject, TAtscriptTypeComplex, TAtscriptTypeFinal, TAtscriptTypeArray, TAtscriptAnnotatedType, TMetadataMap, Validator, TValidatorOptions } from "@atscript/typescript/utils"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Atscript interface **AoothUserCredentials**
|
|
14
|
+
* @see {@link ./user-credentials.as:1:18}
|
|
15
|
+
*/
|
|
16
|
+
export declare class AoothUserCredentials {
|
|
17
|
+
id: string
|
|
18
|
+
username: string
|
|
19
|
+
email?: string
|
|
20
|
+
version: number /* int */
|
|
21
|
+
password: {
|
|
22
|
+
hash: string
|
|
23
|
+
history: string[]
|
|
24
|
+
lastChanged: number /* timestamp */
|
|
25
|
+
isInitial: boolean
|
|
26
|
+
}
|
|
27
|
+
account: {
|
|
28
|
+
active: boolean
|
|
29
|
+
locked: boolean
|
|
30
|
+
lockReason: string
|
|
31
|
+
lockEnds: number /* timestamp */
|
|
32
|
+
failedLoginAttempts: number
|
|
33
|
+
lastLogin: number /* timestamp */
|
|
34
|
+
pendingInvitation?: boolean
|
|
35
|
+
}
|
|
36
|
+
mfa: {
|
|
37
|
+
methods: {
|
|
38
|
+
name: string
|
|
39
|
+
confirmed: boolean
|
|
40
|
+
value: string
|
|
41
|
+
lastUsedWindow?: number /* int */
|
|
42
|
+
}[]
|
|
43
|
+
defaultMethod: string
|
|
44
|
+
autoSend: boolean
|
|
45
|
+
}
|
|
46
|
+
trustedDevices?: {
|
|
47
|
+
token: string
|
|
48
|
+
ip?: string
|
|
49
|
+
issuedAt: number /* timestamp */
|
|
50
|
+
expiresAt: number /* timestamp */
|
|
51
|
+
name?: string
|
|
52
|
+
}[]
|
|
53
|
+
static __is_atscript_annotated_type: true
|
|
54
|
+
static type: TAtscriptTypeObject<keyof AoothUserCredentials, AoothUserCredentials>
|
|
55
|
+
static metadata: TMetadataMap<AtscriptMetadata>
|
|
56
|
+
static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof AoothUserCredentials>
|
|
57
|
+
/** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
|
|
58
|
+
static toJsonSchema: () => any
|
|
59
|
+
/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
|
|
60
|
+
static toExampleData?: () => any
|
|
61
|
+
}
|
|
62
|
+
// prettier-ignore-end
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
//#region src/types.d.ts
|
|
2
|
-
interface UserCredentials {
|
|
3
|
-
id: string;
|
|
4
|
-
username: string;
|
|
5
|
-
/**
|
|
6
|
-
* Server-managed optimistic-concurrency counter. Bumped by `UserStore.update`
|
|
7
|
-
* on every successful write; checked against `UserStoreUpdate.expectedVersion`
|
|
8
|
-
* for CAS. Callers MUST NOT write it directly — atscript-db rejects direct
|
|
9
|
-
* writes with `DbError("VERSION_COLUMN_WRITE")`. Optional in TS so pre-OCC
|
|
10
|
-
* fixtures keep compiling; the store seeds `0` on insert.
|
|
11
|
-
*/
|
|
12
|
-
version?: number;
|
|
13
|
-
password: PasswordData;
|
|
14
|
-
account: AccountData;
|
|
15
|
-
mfa: MfaData;
|
|
16
|
-
/**
|
|
17
|
-
* Hashed backup codes (SHA-256, hex-encoded). Generated via
|
|
18
|
-
* `UserService.generateBackupCodes`. Undefined when the user has not
|
|
19
|
-
* enrolled backup codes; an empty array means all codes were consumed.
|
|
20
|
-
*/
|
|
21
|
-
backupCodes?: string[];
|
|
22
|
-
/**
|
|
23
|
-
* Persisted device-trust records ("remember this device, skip MFA next
|
|
24
|
-
* time"). Managed by `UserService.{issue,add,verify,revoke,list}TrustedDevice`.
|
|
25
|
-
* Absent when the user has never opted in.
|
|
26
|
-
*/
|
|
27
|
-
trustedDevices?: TrustedDeviceRecord[];
|
|
28
|
-
}
|
|
29
|
-
interface TrustedDeviceRecord {
|
|
30
|
-
/** `<raw>.<sig>` — what we hand back to the consumer and what they round-trip. */
|
|
31
|
-
token: string;
|
|
32
|
-
/** Bound IP — set when `deviceTrust.bindsTo === 'cookie+ip'`. */
|
|
33
|
-
ip?: string;
|
|
34
|
-
issuedAt: number;
|
|
35
|
-
expiresAt: number;
|
|
36
|
-
/** Optional human-readable label (e.g. user-agent summary). */
|
|
37
|
-
name?: string;
|
|
38
|
-
}
|
|
39
|
-
interface PasswordData {
|
|
40
|
-
/** Self-describing scrypt hash: $scrypt$N=...,r=...,p=...,l=...$salt$hash */
|
|
41
|
-
hash: string;
|
|
42
|
-
/** Previous password hashes (self-describing strings) */
|
|
43
|
-
history: string[];
|
|
44
|
-
lastChanged: number;
|
|
45
|
-
/** True when password was system-generated and user hasn't set their own */
|
|
46
|
-
isInitial: boolean;
|
|
47
|
-
}
|
|
48
|
-
interface AccountData {
|
|
49
|
-
active: boolean;
|
|
50
|
-
locked: boolean;
|
|
51
|
-
lockReason: string;
|
|
52
|
-
/** 0 = permanent lock, >0 = timestamp (ms) when lock expires */
|
|
53
|
-
lockEnds: number;
|
|
54
|
-
failedLoginAttempts: number;
|
|
55
|
-
lastLogin: number;
|
|
56
|
-
/**
|
|
57
|
-
* True while the user record exists from an admin-issued invite but the
|
|
58
|
-
* invitee has not yet accepted (set password + activate). Used by
|
|
59
|
-
* `InviteWorkflow` to gate the accept tail, reject duplicate invites, and
|
|
60
|
-
* power `auth/invite/resend` / `auth/invite/cancel`. Absent / `false` once
|
|
61
|
-
* the invite has been accepted.
|
|
62
|
-
*/
|
|
63
|
-
pendingInvitation?: boolean;
|
|
64
|
-
}
|
|
65
|
-
interface MfaData {
|
|
66
|
-
/** Registered MFA methods */
|
|
67
|
-
methods: MfaMethod[];
|
|
68
|
-
/** Name of the default MFA method */
|
|
69
|
-
defaultMethod: string;
|
|
70
|
-
/** Auto-send MFA challenge on login */
|
|
71
|
-
autoSend: boolean;
|
|
72
|
-
}
|
|
73
|
-
interface MfaMethod {
|
|
74
|
-
/** Method name: 'email', 'sms', 'totp' */
|
|
75
|
-
name: string;
|
|
76
|
-
/** Whether this method has been verified/confirmed */
|
|
77
|
-
confirmed: boolean;
|
|
78
|
-
/** The method's value: email address, phone number, or TOTP secret */
|
|
79
|
-
value: string;
|
|
80
|
-
/**
|
|
81
|
-
* Last HOTP counter accepted for this method (TOTP only). Server-managed
|
|
82
|
-
* replay guard — `verifyMfa` rejects any code whose matched counter is
|
|
83
|
-
* `<= lastUsedWindow`. Never written from user-facing input.
|
|
84
|
-
*/
|
|
85
|
-
lastUsedWindow?: number;
|
|
86
|
-
}
|
|
87
|
-
interface UserServiceConfig {
|
|
88
|
-
password?: PasswordConfig;
|
|
89
|
-
lockout?: LockoutConfig;
|
|
90
|
-
/** Injectable clock for testability. Defaults to Date.now */
|
|
91
|
-
clock?: () => number;
|
|
92
|
-
/**
|
|
93
|
-
* Device-trust config. Required (with a non-empty `secret`) when any
|
|
94
|
-
* `issueTrustedDevice` / `verifyTrustedDevice` API is called; the methods
|
|
95
|
-
* throw clearly when invoked without it.
|
|
96
|
-
*/
|
|
97
|
-
deviceTrust?: {
|
|
98
|
-
/** HMAC-SHA256 signing secret for trust-device tokens. */secret: string;
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
interface PasswordConfig {
|
|
102
|
-
/** Pepper string prepended to password before hashing */
|
|
103
|
-
pepper?: string;
|
|
104
|
-
/** Number of historical hashes to retain (0 = disabled) */
|
|
105
|
-
historyLength?: number;
|
|
106
|
-
/** scrypt cost parameter N (default 16384) */
|
|
107
|
-
scryptN?: number;
|
|
108
|
-
/** scrypt block size r (default 8) */
|
|
109
|
-
scryptR?: number;
|
|
110
|
-
/** scrypt parallelism p (default 1) */
|
|
111
|
-
scryptP?: number;
|
|
112
|
-
/** Hash output length in bytes (default 64) */
|
|
113
|
-
keyLength?: number;
|
|
114
|
-
/** Password policy rules */
|
|
115
|
-
policies?: (PasswordPolicyDef | PasswordPolicyInstance)[];
|
|
116
|
-
}
|
|
117
|
-
interface LockoutConfig {
|
|
118
|
-
/** Lock after this many failed attempts (0 = disabled) */
|
|
119
|
-
threshold?: number;
|
|
120
|
-
/** Lock duration in ms (0 = permanent) */
|
|
121
|
-
duration?: number;
|
|
122
|
-
}
|
|
123
|
-
interface PasswordPolicyDef {
|
|
124
|
-
/**
|
|
125
|
-
* Backend evaluator. Function only — executed directly with no sandbox.
|
|
126
|
-
* String rules were removed: `@prostojs/ftring`'s sandbox does NOT block
|
|
127
|
-
* prototype-chain escapes (`constructor.constructor("return process")()`,
|
|
128
|
-
* `__proto__.x = ...`), so accepting strings was an RCE vector. Authors
|
|
129
|
-
* use `definePasswordPolicy({ rule, args })` to get both this fn AND the
|
|
130
|
-
* serialized form for free.
|
|
131
|
-
*/
|
|
132
|
-
rule: PasswordPolicyEvalFn;
|
|
133
|
-
/**
|
|
134
|
-
* Pre-baked function-literal text shipped to clients for cross-tier
|
|
135
|
-
* validation via `getTransferablePolicies()`. Authored as
|
|
136
|
-
* `(v) => (${ruleSource})(v, ${args.map(JSON.stringify).join(', ')})` by
|
|
137
|
-
* `definePasswordPolicy`. Absent → the policy is backend-only (frontend
|
|
138
|
-
* skips it; server-side check remains authoritative).
|
|
139
|
-
*/
|
|
140
|
-
serialized?: string;
|
|
141
|
-
description?: string;
|
|
142
|
-
errorMessage?: string;
|
|
143
|
-
}
|
|
144
|
-
type PasswordPolicyEvalFn = (password: string, context?: PasswordPolicyContext) => boolean | Promise<boolean>;
|
|
145
|
-
interface PasswordPolicyContext {
|
|
146
|
-
passwordData?: PasswordData;
|
|
147
|
-
passwordConfig?: PasswordConfig;
|
|
148
|
-
}
|
|
149
|
-
/** Interface satisfied by the PasswordPolicy class (avoids circular import) */
|
|
150
|
-
interface PasswordPolicyInstance extends PasswordPolicyDef {
|
|
151
|
-
evaluate(password: string, context?: PasswordPolicyContext): boolean | Promise<boolean>;
|
|
152
|
-
transferable: boolean;
|
|
153
|
-
}
|
|
154
|
-
type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P] };
|
|
155
|
-
interface UserStoreUpdate {
|
|
156
|
-
/** Partial object with fields to set (deep-merged) */
|
|
157
|
-
set?: DeepPartial<UserCredentials>;
|
|
158
|
-
/** Dot-paths to atomically increment: e.g. {'account.failedLoginAttempts': 1} */
|
|
159
|
-
inc?: Record<string, number>;
|
|
160
|
-
/**
|
|
161
|
-
* Optimistic concurrency control: when supplied, the store applies the
|
|
162
|
-
* update iff the row's current `version` equals this value. On mismatch
|
|
163
|
-
* the store returns `false` (same shape as "not found") and does NOT
|
|
164
|
-
* mutate. Service callers treat both states as "stale read, retry".
|
|
165
|
-
*/
|
|
166
|
-
expectedVersion?: number;
|
|
167
|
-
}
|
|
168
|
-
type UserAuthErrorType = "NOT_FOUND" | "ALREADY_EXISTS" | "LOCKED" | "INACTIVE" | "INVALID_CREDENTIALS" | "POLICY_VIOLATION" | "PASSWORDS_MISMATCH" | "PASSWORD_IN_HISTORY" | "MFA_REQUIRED" | "MFA_INVALID" | "MFA_NOT_CONFIGURED" | "CAS_EXHAUSTED";
|
|
169
|
-
interface LoginResult<T extends object = object> {
|
|
170
|
-
user: UserCredentials & T;
|
|
171
|
-
/** Whether MFA verification is required before granting full access */
|
|
172
|
-
mfaRequired: boolean;
|
|
173
|
-
}
|
|
174
|
-
interface LockStatus {
|
|
175
|
-
locked: boolean;
|
|
176
|
-
/** True when lock has a non-zero lockEnds that is in the past */
|
|
177
|
-
expired: boolean;
|
|
178
|
-
reason: string;
|
|
179
|
-
lockEnds: number;
|
|
180
|
-
}
|
|
181
|
-
interface PolicyCheckResult {
|
|
182
|
-
passed: boolean;
|
|
183
|
-
policies: {
|
|
184
|
-
description: string;
|
|
185
|
-
passed: boolean;
|
|
186
|
-
}[];
|
|
187
|
-
errors: string[];
|
|
188
|
-
}
|
|
189
|
-
interface TransferablePolicy {
|
|
190
|
-
rule: string;
|
|
191
|
-
description?: string;
|
|
192
|
-
errorMessage?: string;
|
|
193
|
-
}
|
|
194
|
-
interface MfaMethodInfo {
|
|
195
|
-
name: string;
|
|
196
|
-
isDefault: boolean;
|
|
197
|
-
masked: string;
|
|
198
|
-
}
|
|
199
|
-
interface TotpConfig {
|
|
200
|
-
/** Time step in seconds (default 30) */
|
|
201
|
-
period?: number;
|
|
202
|
-
/** Number of digits in the code (default 6) */
|
|
203
|
-
digits?: number;
|
|
204
|
-
/** Verification window — number of steps to check on each side (default 1) */
|
|
205
|
-
window?: number;
|
|
206
|
-
/** Injectable clock for testability */
|
|
207
|
-
clock?: () => number;
|
|
208
|
-
}
|
|
209
|
-
//#endregion
|
|
210
|
-
//#region src/store/user-store.d.ts
|
|
211
|
-
interface WithCasOptions {
|
|
212
|
-
/**
|
|
213
|
-
* Total attempts (1 initial + retries). Default `2` = one retry. Each
|
|
214
|
-
* attempt re-reads the row so the mutator runs against fresh state — that
|
|
215
|
-
* is the whole point of retry under OCC. Bump for high-contention writers
|
|
216
|
-
* (bulk admin scripts); leave at default for normal per-user request flow.
|
|
217
|
-
*/
|
|
218
|
-
maxAttempts?: number;
|
|
219
|
-
}
|
|
220
|
-
declare abstract class UserStore<T extends object = object> {
|
|
221
|
-
abstract exists(username: string): Promise<boolean>;
|
|
222
|
-
abstract findByUsername(username: string): Promise<(UserCredentials & T) | null>;
|
|
223
|
-
abstract create(data: UserCredentials & T): Promise<void>;
|
|
224
|
-
abstract update(username: string, update: UserStoreUpdate): Promise<boolean>;
|
|
225
|
-
/**
|
|
226
|
-
* Hard-delete the row. Returns `true` when a row was removed, `false` when
|
|
227
|
-
* the username was not found. Used by `UserService.deleteUser` (and in turn
|
|
228
|
-
* by the invite workflow's `auth/invite/cancel` step).
|
|
229
|
-
*/
|
|
230
|
-
abstract delete(username: string): Promise<boolean>;
|
|
231
|
-
/**
|
|
232
|
-
* Run a read-modify-write cycle under optimistic concurrency. Each attempt
|
|
233
|
-
* fetches the current row, calls `mutator` with it, and applies the returned
|
|
234
|
-
* patch under CAS (`expectedVersion = current.version`). On CAS miss the
|
|
235
|
-
* cycle repeats up to `opts.maxAttempts`. The mutator MAY return `null` to
|
|
236
|
-
* exit early without writing — used for "race-loser detects nothing left to
|
|
237
|
-
* do" paths (e.g. the backup code was already consumed by the winner).
|
|
238
|
-
*
|
|
239
|
-
* Throws `UserAuthError("NOT_FOUND")` when no row matches `username`, or
|
|
240
|
-
* `UserAuthError("CAS_EXHAUSTED")` when retries are saturated. Errors
|
|
241
|
-
* thrown from inside `mutator` propagate immediately without retry.
|
|
242
|
-
*/
|
|
243
|
-
abstract withCas(username: string, mutator: (current: UserCredentials & T) => UserStoreUpdate | null, opts?: WithCasOptions): Promise<void>;
|
|
244
|
-
}
|
|
245
|
-
//#endregion
|
|
246
|
-
export { UserServiceConfig as C, UserCredentials as S, PolicyCheckResult as _, LockStatus as a, TrustedDeviceRecord as b, MfaData as c, PasswordConfig as d, PasswordData as f, PasswordPolicyInstance as g, PasswordPolicyEvalFn as h, DeepPartial as i, MfaMethod as l, PasswordPolicyDef as m, WithCasOptions as n, LockoutConfig as o, PasswordPolicyContext as p, AccountData as r, LoginResult as s, UserStore as t, MfaMethodInfo as u, TotpConfig as v, UserStoreUpdate as w, UserAuthErrorType as x, TransferablePolicy as y };
|