@aooth/user 0.1.77 → 0.1.79
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/index.cjs +105 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.mts +25 -0
- package/dist/index.mjs +105 -1
- package/package.json +9 -9
package/dist/index.cjs
CHANGED
|
@@ -137,9 +137,17 @@ const DEFAULTS = {
|
|
|
137
137
|
saltLength: 32
|
|
138
138
|
};
|
|
139
139
|
const PREFIX = "$scrypt$";
|
|
140
|
+
/** Node's default scrypt memory cap (32 MiB) — kept as the floor. */
|
|
141
|
+
const DEFAULT_MAXMEM = 32 * 1024 * 1024;
|
|
142
|
+
/** Upper bound for the memory a (possibly stored, untrusted) parameter set may ask for. */
|
|
143
|
+
const MAX_MAXMEM = 1024 * 1024 * 1024;
|
|
140
144
|
function scryptAsync(password, salt, keyLength, options) {
|
|
145
|
+
const maxmem = Math.min(MAX_MAXMEM, Math.max(DEFAULT_MAXMEM, 256 * options.N * options.r + 1024 * 1024));
|
|
141
146
|
return new Promise((resolve, reject) => {
|
|
142
|
-
(0, node_crypto.scrypt)(password, salt, keyLength,
|
|
147
|
+
(0, node_crypto.scrypt)(password, salt, keyLength, {
|
|
148
|
+
...options,
|
|
149
|
+
maxmem
|
|
150
|
+
}, (err, derived) => {
|
|
143
151
|
if (err) reject(err);
|
|
144
152
|
else resolve(derived);
|
|
145
153
|
});
|
|
@@ -337,10 +345,72 @@ var UserService = class {
|
|
|
337
345
|
store;
|
|
338
346
|
config;
|
|
339
347
|
hasher;
|
|
348
|
+
/** Moves on every store write through this service (start and end). */
|
|
349
|
+
writeEpoch = 0;
|
|
350
|
+
writeListeners = /* @__PURE__ */ new Set();
|
|
351
|
+
snapshots = /* @__PURE__ */ new WeakMap();
|
|
340
352
|
constructor(store, config) {
|
|
341
353
|
this.store = store;
|
|
342
354
|
this.config = resolveConfig(config);
|
|
343
355
|
this.hasher = new PasswordHasher(this.config.password);
|
|
356
|
+
this.store = observeWrites(store, {
|
|
357
|
+
start: () => {
|
|
358
|
+
this.writeEpoch++;
|
|
359
|
+
},
|
|
360
|
+
end: (id) => {
|
|
361
|
+
this.writeEpoch++;
|
|
362
|
+
let failure;
|
|
363
|
+
for (const listener of this.writeListeners) try {
|
|
364
|
+
listener(id);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
failure ??= { error };
|
|
367
|
+
}
|
|
368
|
+
if (failure) throw failure.error;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Subscribe to user writes made through this service (or its store): the
|
|
374
|
+
* listener gets the written user's id once the write settles (`undefined`
|
|
375
|
+
* when it cannot be told). E.g. evict a cross-request cache:
|
|
376
|
+
* `users.onUserWrite((id) => arbacUserCache.invalidate(id))`.
|
|
377
|
+
*
|
|
378
|
+
* @returns unsubscribe
|
|
379
|
+
* @since 0.1.78
|
|
380
|
+
*/
|
|
381
|
+
onUserWrite(listener) {
|
|
382
|
+
this.writeListeners.add(listener);
|
|
383
|
+
return () => {
|
|
384
|
+
this.writeListeners.delete(listener);
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* {@link getUser} memoized per `scope` — an object that lives as long as
|
|
389
|
+
* one unit of work (typically the current event context): repeated reads of
|
|
390
|
+
* the same user in that scope share one store read, until any write goes
|
|
391
|
+
* through this service (then the next read is fresh). Rejections are not
|
|
392
|
+
* kept. Writes made around this service are not seen — use `getUser` when
|
|
393
|
+
* that matters.
|
|
394
|
+
*
|
|
395
|
+
* @since 0.1.78
|
|
396
|
+
*/
|
|
397
|
+
getUserSnapshot(id, scope) {
|
|
398
|
+
let perScope = this.snapshots.get(scope);
|
|
399
|
+
if (!perScope) {
|
|
400
|
+
perScope = /* @__PURE__ */ new Map();
|
|
401
|
+
this.snapshots.set(scope, perScope);
|
|
402
|
+
}
|
|
403
|
+
const hit = perScope.get(id);
|
|
404
|
+
if (hit && hit.epoch === this.writeEpoch) return hit.user;
|
|
405
|
+
const entry = {
|
|
406
|
+
epoch: this.writeEpoch,
|
|
407
|
+
user: this.getUser(id)
|
|
408
|
+
};
|
|
409
|
+
perScope.set(id, entry);
|
|
410
|
+
entry.user.catch(() => {
|
|
411
|
+
if (perScope.get(id) === entry) perScope.delete(id);
|
|
412
|
+
});
|
|
413
|
+
return entry.user;
|
|
344
414
|
}
|
|
345
415
|
/**
|
|
346
416
|
* Creates a user with `account.active: false`. The invite workflow relies
|
|
@@ -964,6 +1034,40 @@ var UserService = class {
|
|
|
964
1034
|
throw new require_federated_identity_store.UserAuthError(errorCode);
|
|
965
1035
|
}
|
|
966
1036
|
};
|
|
1037
|
+
/** The store methods that write (keyed by the user id, or `create`'s row). */
|
|
1038
|
+
const WRITE_METHODS = /* @__PURE__ */ new Set([
|
|
1039
|
+
"create",
|
|
1040
|
+
"update",
|
|
1041
|
+
"delete",
|
|
1042
|
+
"withCas"
|
|
1043
|
+
]);
|
|
1044
|
+
/**
|
|
1045
|
+
* `store` with its write methods reporting to `hooks` (before the call and
|
|
1046
|
+
* once it settles, with the user id); everything else passes through, bound
|
|
1047
|
+
* to the store itself.
|
|
1048
|
+
*/
|
|
1049
|
+
function observeWrites(store, hooks) {
|
|
1050
|
+
const bound = /* @__PURE__ */ new Map();
|
|
1051
|
+
return new Proxy(store, { get(target, prop) {
|
|
1052
|
+
const value = Reflect.get(target, prop, target);
|
|
1053
|
+
if (typeof value !== "function") return value;
|
|
1054
|
+
let fn = bound.get(prop);
|
|
1055
|
+
if (fn === void 0 || fn.__of !== value) {
|
|
1056
|
+
const method = value;
|
|
1057
|
+
fn = WRITE_METHODS.has(prop) ? Object.assign(async (...args) => {
|
|
1058
|
+
const id = typeof args[0] === "string" ? args[0] : args[0]?.id;
|
|
1059
|
+
hooks.start();
|
|
1060
|
+
try {
|
|
1061
|
+
return await method.apply(target, args);
|
|
1062
|
+
} finally {
|
|
1063
|
+
hooks.end(typeof id === "string" ? id : void 0);
|
|
1064
|
+
}
|
|
1065
|
+
}, { __of: value }) : Object.assign(method.bind(target), { __of: value });
|
|
1066
|
+
bound.set(prop, fn);
|
|
1067
|
+
}
|
|
1068
|
+
return fn;
|
|
1069
|
+
} });
|
|
1070
|
+
}
|
|
967
1071
|
//#endregion
|
|
968
1072
|
//#region src/store/memory.ts
|
|
969
1073
|
var UserStoreMemory = class extends require_federated_identity_store.UserStore {
|
package/dist/index.d.cts
CHANGED
|
@@ -85,7 +85,32 @@ declare class UserService<T extends object = object> {
|
|
|
85
85
|
protected readonly store: UserStore<T>;
|
|
86
86
|
protected readonly config: ResolvedConfig;
|
|
87
87
|
protected readonly hasher: PasswordHasher;
|
|
88
|
+
/** Moves on every store write through this service (start and end). */
|
|
89
|
+
private writeEpoch;
|
|
90
|
+
private readonly writeListeners;
|
|
91
|
+
private readonly snapshots;
|
|
88
92
|
constructor(store: UserStore<T>, config?: UserServiceConfig);
|
|
93
|
+
/**
|
|
94
|
+
* Subscribe to user writes made through this service (or its store): the
|
|
95
|
+
* listener gets the written user's id once the write settles (`undefined`
|
|
96
|
+
* when it cannot be told). E.g. evict a cross-request cache:
|
|
97
|
+
* `users.onUserWrite((id) => arbacUserCache.invalidate(id))`.
|
|
98
|
+
*
|
|
99
|
+
* @returns unsubscribe
|
|
100
|
+
* @since 0.1.78
|
|
101
|
+
*/
|
|
102
|
+
onUserWrite(listener: (id: string | undefined) => void): () => void;
|
|
103
|
+
/**
|
|
104
|
+
* {@link getUser} memoized per `scope` — an object that lives as long as
|
|
105
|
+
* one unit of work (typically the current event context): repeated reads of
|
|
106
|
+
* the same user in that scope share one store read, until any write goes
|
|
107
|
+
* through this service (then the next read is fresh). Rejections are not
|
|
108
|
+
* kept. Writes made around this service are not seen — use `getUser` when
|
|
109
|
+
* that matters.
|
|
110
|
+
*
|
|
111
|
+
* @since 0.1.78
|
|
112
|
+
*/
|
|
113
|
+
getUserSnapshot(id: string, scope: object): Promise<UserCredentials & T>;
|
|
89
114
|
/**
|
|
90
115
|
* Creates a user with `account.active: false`. The invite workflow relies
|
|
91
116
|
* on this default (see `InviteWorkflow.acceptInvite` — pending invitees stay
|
package/dist/index.d.mts
CHANGED
|
@@ -85,7 +85,32 @@ declare class UserService<T extends object = object> {
|
|
|
85
85
|
protected readonly store: UserStore<T>;
|
|
86
86
|
protected readonly config: ResolvedConfig;
|
|
87
87
|
protected readonly hasher: PasswordHasher;
|
|
88
|
+
/** Moves on every store write through this service (start and end). */
|
|
89
|
+
private writeEpoch;
|
|
90
|
+
private readonly writeListeners;
|
|
91
|
+
private readonly snapshots;
|
|
88
92
|
constructor(store: UserStore<T>, config?: UserServiceConfig);
|
|
93
|
+
/**
|
|
94
|
+
* Subscribe to user writes made through this service (or its store): the
|
|
95
|
+
* listener gets the written user's id once the write settles (`undefined`
|
|
96
|
+
* when it cannot be told). E.g. evict a cross-request cache:
|
|
97
|
+
* `users.onUserWrite((id) => arbacUserCache.invalidate(id))`.
|
|
98
|
+
*
|
|
99
|
+
* @returns unsubscribe
|
|
100
|
+
* @since 0.1.78
|
|
101
|
+
*/
|
|
102
|
+
onUserWrite(listener: (id: string | undefined) => void): () => void;
|
|
103
|
+
/**
|
|
104
|
+
* {@link getUser} memoized per `scope` — an object that lives as long as
|
|
105
|
+
* one unit of work (typically the current event context): repeated reads of
|
|
106
|
+
* the same user in that scope share one store read, until any write goes
|
|
107
|
+
* through this service (then the next read is fresh). Rejections are not
|
|
108
|
+
* kept. Writes made around this service are not seen — use `getUser` when
|
|
109
|
+
* that matters.
|
|
110
|
+
*
|
|
111
|
+
* @since 0.1.78
|
|
112
|
+
*/
|
|
113
|
+
getUserSnapshot(id: string, scope: object): Promise<UserCredentials & T>;
|
|
89
114
|
/**
|
|
90
115
|
* Creates a user with `account.active: false`. The invite workflow relies
|
|
91
116
|
* on this default (see `InviteWorkflow.acceptInvite` — pending invitees stay
|
package/dist/index.mjs
CHANGED
|
@@ -136,9 +136,17 @@ const DEFAULTS = {
|
|
|
136
136
|
saltLength: 32
|
|
137
137
|
};
|
|
138
138
|
const PREFIX = "$scrypt$";
|
|
139
|
+
/** Node's default scrypt memory cap (32 MiB) — kept as the floor. */
|
|
140
|
+
const DEFAULT_MAXMEM = 32 * 1024 * 1024;
|
|
141
|
+
/** Upper bound for the memory a (possibly stored, untrusted) parameter set may ask for. */
|
|
142
|
+
const MAX_MAXMEM = 1024 * 1024 * 1024;
|
|
139
143
|
function scryptAsync(password, salt, keyLength, options) {
|
|
144
|
+
const maxmem = Math.min(MAX_MAXMEM, Math.max(DEFAULT_MAXMEM, 256 * options.N * options.r + 1024 * 1024));
|
|
140
145
|
return new Promise((resolve, reject) => {
|
|
141
|
-
scrypt(password, salt, keyLength,
|
|
146
|
+
scrypt(password, salt, keyLength, {
|
|
147
|
+
...options,
|
|
148
|
+
maxmem
|
|
149
|
+
}, (err, derived) => {
|
|
142
150
|
if (err) reject(err);
|
|
143
151
|
else resolve(derived);
|
|
144
152
|
});
|
|
@@ -336,10 +344,72 @@ var UserService = class {
|
|
|
336
344
|
store;
|
|
337
345
|
config;
|
|
338
346
|
hasher;
|
|
347
|
+
/** Moves on every store write through this service (start and end). */
|
|
348
|
+
writeEpoch = 0;
|
|
349
|
+
writeListeners = /* @__PURE__ */ new Set();
|
|
350
|
+
snapshots = /* @__PURE__ */ new WeakMap();
|
|
339
351
|
constructor(store, config) {
|
|
340
352
|
this.store = store;
|
|
341
353
|
this.config = resolveConfig(config);
|
|
342
354
|
this.hasher = new PasswordHasher(this.config.password);
|
|
355
|
+
this.store = observeWrites(store, {
|
|
356
|
+
start: () => {
|
|
357
|
+
this.writeEpoch++;
|
|
358
|
+
},
|
|
359
|
+
end: (id) => {
|
|
360
|
+
this.writeEpoch++;
|
|
361
|
+
let failure;
|
|
362
|
+
for (const listener of this.writeListeners) try {
|
|
363
|
+
listener(id);
|
|
364
|
+
} catch (error) {
|
|
365
|
+
failure ??= { error };
|
|
366
|
+
}
|
|
367
|
+
if (failure) throw failure.error;
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Subscribe to user writes made through this service (or its store): the
|
|
373
|
+
* listener gets the written user's id once the write settles (`undefined`
|
|
374
|
+
* when it cannot be told). E.g. evict a cross-request cache:
|
|
375
|
+
* `users.onUserWrite((id) => arbacUserCache.invalidate(id))`.
|
|
376
|
+
*
|
|
377
|
+
* @returns unsubscribe
|
|
378
|
+
* @since 0.1.78
|
|
379
|
+
*/
|
|
380
|
+
onUserWrite(listener) {
|
|
381
|
+
this.writeListeners.add(listener);
|
|
382
|
+
return () => {
|
|
383
|
+
this.writeListeners.delete(listener);
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* {@link getUser} memoized per `scope` — an object that lives as long as
|
|
388
|
+
* one unit of work (typically the current event context): repeated reads of
|
|
389
|
+
* the same user in that scope share one store read, until any write goes
|
|
390
|
+
* through this service (then the next read is fresh). Rejections are not
|
|
391
|
+
* kept. Writes made around this service are not seen — use `getUser` when
|
|
392
|
+
* that matters.
|
|
393
|
+
*
|
|
394
|
+
* @since 0.1.78
|
|
395
|
+
*/
|
|
396
|
+
getUserSnapshot(id, scope) {
|
|
397
|
+
let perScope = this.snapshots.get(scope);
|
|
398
|
+
if (!perScope) {
|
|
399
|
+
perScope = /* @__PURE__ */ new Map();
|
|
400
|
+
this.snapshots.set(scope, perScope);
|
|
401
|
+
}
|
|
402
|
+
const hit = perScope.get(id);
|
|
403
|
+
if (hit && hit.epoch === this.writeEpoch) return hit.user;
|
|
404
|
+
const entry = {
|
|
405
|
+
epoch: this.writeEpoch,
|
|
406
|
+
user: this.getUser(id)
|
|
407
|
+
};
|
|
408
|
+
perScope.set(id, entry);
|
|
409
|
+
entry.user.catch(() => {
|
|
410
|
+
if (perScope.get(id) === entry) perScope.delete(id);
|
|
411
|
+
});
|
|
412
|
+
return entry.user;
|
|
343
413
|
}
|
|
344
414
|
/**
|
|
345
415
|
* Creates a user with `account.active: false`. The invite workflow relies
|
|
@@ -963,6 +1033,40 @@ var UserService = class {
|
|
|
963
1033
|
throw new UserAuthError(errorCode);
|
|
964
1034
|
}
|
|
965
1035
|
};
|
|
1036
|
+
/** The store methods that write (keyed by the user id, or `create`'s row). */
|
|
1037
|
+
const WRITE_METHODS = /* @__PURE__ */ new Set([
|
|
1038
|
+
"create",
|
|
1039
|
+
"update",
|
|
1040
|
+
"delete",
|
|
1041
|
+
"withCas"
|
|
1042
|
+
]);
|
|
1043
|
+
/**
|
|
1044
|
+
* `store` with its write methods reporting to `hooks` (before the call and
|
|
1045
|
+
* once it settles, with the user id); everything else passes through, bound
|
|
1046
|
+
* to the store itself.
|
|
1047
|
+
*/
|
|
1048
|
+
function observeWrites(store, hooks) {
|
|
1049
|
+
const bound = /* @__PURE__ */ new Map();
|
|
1050
|
+
return new Proxy(store, { get(target, prop) {
|
|
1051
|
+
const value = Reflect.get(target, prop, target);
|
|
1052
|
+
if (typeof value !== "function") return value;
|
|
1053
|
+
let fn = bound.get(prop);
|
|
1054
|
+
if (fn === void 0 || fn.__of !== value) {
|
|
1055
|
+
const method = value;
|
|
1056
|
+
fn = WRITE_METHODS.has(prop) ? Object.assign(async (...args) => {
|
|
1057
|
+
const id = typeof args[0] === "string" ? args[0] : args[0]?.id;
|
|
1058
|
+
hooks.start();
|
|
1059
|
+
try {
|
|
1060
|
+
return await method.apply(target, args);
|
|
1061
|
+
} finally {
|
|
1062
|
+
hooks.end(typeof id === "string" ? id : void 0);
|
|
1063
|
+
}
|
|
1064
|
+
}, { __of: value }) : Object.assign(method.bind(target), { __of: value });
|
|
1065
|
+
bound.set(prop, fn);
|
|
1066
|
+
}
|
|
1067
|
+
return fn;
|
|
1068
|
+
} });
|
|
1069
|
+
}
|
|
966
1070
|
//#endregion
|
|
967
1071
|
//#region src/store/memory.ts
|
|
968
1072
|
var UserStoreMemory = class extends UserStore {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aooth/user",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.79",
|
|
4
4
|
"description": "User credential primitives for aoothjs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aoothjs",
|
|
@@ -60,18 +60,18 @@
|
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@atscript/core": "^0.1.
|
|
64
|
-
"@atscript/db": "^0.1.
|
|
65
|
-
"@atscript/db-sql-tools": "^0.1.
|
|
66
|
-
"@atscript/db-sqlite": "^0.1.
|
|
67
|
-
"@atscript/typescript": "^0.1.
|
|
63
|
+
"@atscript/core": "^0.1.103",
|
|
64
|
+
"@atscript/db": "^0.1.153",
|
|
65
|
+
"@atscript/db-sql-tools": "^0.1.153",
|
|
66
|
+
"@atscript/db-sqlite": "^0.1.153",
|
|
67
|
+
"@atscript/typescript": "^0.1.103",
|
|
68
68
|
"@types/better-sqlite3": "^7.6.13",
|
|
69
|
-
"@uniqu/core": "^0.1.
|
|
69
|
+
"@uniqu/core": "^0.1.15",
|
|
70
70
|
"better-sqlite3": "^12.6.2",
|
|
71
|
-
"unplugin-atscript": "^0.1.
|
|
71
|
+
"unplugin-atscript": "^0.1.103"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
|
-
"@atscript/db": "^0.1.
|
|
74
|
+
"@atscript/db": "^0.1.153"
|
|
75
75
|
},
|
|
76
76
|
"peerDependenciesMeta": {
|
|
77
77
|
"@atscript/db": {
|