@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/atscript-db.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-
|
|
2
|
+
const require_federated_identity_store = require("./federated-identity-store-Cbstwl4-.cjs");
|
|
3
3
|
let node_crypto = require("node:crypto");
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/atscript-db.d.cts
CHANGED
|
@@ -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-
|
|
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
|
|
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
|
-
|
|
133
|
-
export { AuthUserTable, FederatedIdentityStoreAtscriptDb, type FederatedIdentityTable, UserCredentialsRow, UsersStoreAtscriptDb };
|
|
131
|
+
export type { FederatedIdentityTable };
|
package/dist/atscript-db.d.mts
CHANGED
|
@@ -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-
|
|
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
|
|
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
|
-
|
|
133
|
-
export { AuthUserTable, FederatedIdentityStoreAtscriptDb, type FederatedIdentityTable, UserCredentialsRow, UsersStoreAtscriptDb };
|
|
131
|
+
export type { FederatedIdentityTable };
|
package/dist/atscript-db.mjs
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|
package/dist/{federated-identity-store-CRycYlMj.mjs → federated-identity-store-BZRvw9Pz.mjs}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "node:crypto";
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|
package/dist/{federated-identity-store-D-LvRXdx.cjs → federated-identity-store-Cbstwl4-.cjs}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require("node:crypto");
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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() {
|
package/dist/{federated-identity-store-B_jEkl-a.d.cts → federated-identity-store-Ds7Ye9qJ.d.cts}
RENAMED
|
@@ -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).
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
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
|
|
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/{federated-identity-store-B_jEkl-a.d.mts → federated-identity-store-Ds7Ye9qJ.d.mts}
RENAMED
|
@@ -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).
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
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
|
|
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-
|
|
2
|
+
const require_federated_identity_store = require("./federated-identity-store-Cbstwl4-.cjs");
|
|
3
3
|
let node_crypto = require("node:crypto");
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 +
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
418
|
-
*
|
|
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.
|
|
548
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|