@spinajs/authn-orm 0.0.0-stage → 3.0.1
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/README.md +84 -2
- package/lib/cjs/audit/db-audit-sink.d.ts +11 -0
- package/lib/cjs/audit/db-audit-sink.d.ts.map +1 -0
- package/lib/cjs/audit/db-audit-sink.js +73 -0
- package/lib/cjs/audit/db-audit-sink.js.map +1 -0
- package/lib/cjs/audit/index.d.ts +2 -0
- package/lib/cjs/audit/index.d.ts.map +1 -0
- package/lib/cjs/audit/index.js +6 -0
- package/lib/cjs/audit/index.js.map +1 -0
- package/lib/cjs/identity.d.ts +6 -0
- package/lib/cjs/identity.d.ts.map +1 -0
- package/lib/cjs/identity.js +27 -0
- package/lib/cjs/identity.js.map +1 -0
- package/lib/cjs/index.d.ts +10 -0
- package/lib/cjs/index.d.ts.map +1 -0
- package/lib/cjs/index.js +26 -0
- package/lib/cjs/index.js.map +1 -0
- package/lib/cjs/links/db-link-store.d.ts +13 -0
- package/lib/cjs/links/db-link-store.d.ts.map +1 -0
- package/lib/cjs/links/db-link-store.js +88 -0
- package/lib/cjs/links/db-link-store.js.map +1 -0
- package/lib/cjs/links/index.d.ts +2 -0
- package/lib/cjs/links/index.d.ts.map +1 -0
- package/lib/cjs/links/index.js +6 -0
- package/lib/cjs/links/index.js.map +1 -0
- package/lib/cjs/mfa/cipher.d.ts +18 -0
- package/lib/cjs/mfa/cipher.d.ts.map +1 -0
- package/lib/cjs/mfa/cipher.js +81 -0
- package/lib/cjs/mfa/cipher.js.map +1 -0
- package/lib/cjs/mfa/db-mfa-enrolments.d.ts +14 -0
- package/lib/cjs/mfa/db-mfa-enrolments.d.ts.map +1 -0
- package/lib/cjs/mfa/db-mfa-enrolments.js +43 -0
- package/lib/cjs/mfa/db-mfa-enrolments.js.map +1 -0
- package/lib/cjs/mfa/db-mfa-store.d.ts +51 -0
- package/lib/cjs/mfa/db-mfa-store.d.ts.map +1 -0
- package/lib/cjs/mfa/db-mfa-store.js +190 -0
- package/lib/cjs/mfa/db-mfa-store.js.map +1 -0
- package/lib/cjs/mfa/index.d.ts +4 -0
- package/lib/cjs/mfa/index.d.ts.map +1 -0
- package/lib/cjs/mfa/index.js +20 -0
- package/lib/cjs/mfa/index.js.map +1 -0
- package/lib/cjs/migrations/AuthnInitial_2026_10_02_00_00_00.d.ts +10 -0
- package/lib/cjs/migrations/AuthnInitial_2026_10_02_00_00_00.d.ts.map +1 -0
- package/lib/cjs/migrations/AuthnInitial_2026_10_02_00_00_00.js +98 -0
- package/lib/cjs/migrations/AuthnInitial_2026_10_02_00_00_00.js.map +1 -0
- package/lib/cjs/migrations/index.d.ts +2 -0
- package/lib/cjs/migrations/index.d.ts.map +1 -0
- package/lib/cjs/migrations/index.js +18 -0
- package/lib/cjs/migrations/index.js.map +1 -0
- package/lib/cjs/models/AuthnAudit.d.ts +18 -0
- package/lib/cjs/models/AuthnAudit.d.ts.map +1 -0
- package/lib/cjs/models/AuthnAudit.js +30 -0
- package/lib/cjs/models/AuthnAudit.js.map +1 -0
- package/lib/cjs/models/AuthnIdentity.d.ts +16 -0
- package/lib/cjs/models/AuthnIdentity.d.ts.map +1 -0
- package/lib/cjs/models/AuthnIdentity.js +39 -0
- package/lib/cjs/models/AuthnIdentity.js.map +1 -0
- package/lib/cjs/models/AuthnMfa.d.ts +16 -0
- package/lib/cjs/models/AuthnMfa.d.ts.map +1 -0
- package/lib/cjs/models/AuthnMfa.js +39 -0
- package/lib/cjs/models/AuthnMfa.js.map +1 -0
- package/lib/cjs/models/AuthnNonce.d.ts +7 -0
- package/lib/cjs/models/AuthnNonce.d.ts.map +1 -0
- package/lib/cjs/models/AuthnNonce.js +30 -0
- package/lib/cjs/models/AuthnNonce.js.map +1 -0
- package/lib/cjs/models/AuthnRecoveryCode.d.ts +10 -0
- package/lib/cjs/models/AuthnRecoveryCode.d.ts.map +1 -0
- package/lib/cjs/models/AuthnRecoveryCode.js +30 -0
- package/lib/cjs/models/AuthnRecoveryCode.js.map +1 -0
- package/lib/cjs/models/AuthnSecret.d.ts +13 -0
- package/lib/cjs/models/AuthnSecret.d.ts.map +1 -0
- package/lib/cjs/models/AuthnSecret.js +38 -0
- package/lib/cjs/models/AuthnSecret.js.map +1 -0
- package/lib/cjs/models/AuthnThrottle.d.ts +10 -0
- package/lib/cjs/models/AuthnThrottle.d.ts.map +1 -0
- package/lib/cjs/models/AuthnThrottle.js +38 -0
- package/lib/cjs/models/AuthnThrottle.js.map +1 -0
- package/lib/cjs/models/index.d.ts +9 -0
- package/lib/cjs/models/index.d.ts.map +1 -0
- package/lib/cjs/models/index.js +25 -0
- package/lib/cjs/models/index.js.map +1 -0
- package/lib/cjs/models/time.d.ts +4 -0
- package/lib/cjs/models/time.d.ts.map +1 -0
- package/lib/cjs/models/time.js +19 -0
- package/lib/cjs/models/time.js.map +1 -0
- package/lib/cjs/nonces/index.d.ts +2 -0
- package/lib/cjs/nonces/index.d.ts.map +1 -0
- package/lib/cjs/nonces/index.js +18 -0
- package/lib/cjs/nonces/index.js.map +1 -0
- package/lib/cjs/nonces/nonce-store.d.ts +11 -0
- package/lib/cjs/nonces/nonce-store.d.ts.map +1 -0
- package/lib/cjs/nonces/nonce-store.js +77 -0
- package/lib/cjs/nonces/nonce-store.js.map +1 -0
- package/lib/cjs/package.json +1 -0
- package/lib/cjs/secrets/db-secret-store.d.ts +25 -0
- package/lib/cjs/secrets/db-secret-store.d.ts.map +1 -0
- package/lib/cjs/secrets/db-secret-store.js +99 -0
- package/lib/cjs/secrets/db-secret-store.js.map +1 -0
- package/lib/cjs/secrets/index.d.ts +2 -0
- package/lib/cjs/secrets/index.d.ts.map +1 -0
- package/lib/cjs/secrets/index.js +6 -0
- package/lib/cjs/secrets/index.js.map +1 -0
- package/lib/cjs/support/sql.d.ts +16 -0
- package/lib/cjs/support/sql.d.ts.map +1 -0
- package/lib/cjs/support/sql.js +50 -0
- package/lib/cjs/support/sql.js.map +1 -0
- package/lib/cjs/support/values.d.ts +7 -0
- package/lib/cjs/support/values.d.ts.map +1 -0
- package/lib/cjs/support/values.js +8 -0
- package/lib/cjs/support/values.js.map +1 -0
- package/lib/cjs/throttle/db-throttle-store.d.ts +79 -0
- package/lib/cjs/throttle/db-throttle-store.d.ts.map +1 -0
- package/lib/cjs/throttle/db-throttle-store.js +231 -0
- package/lib/cjs/throttle/db-throttle-store.js.map +1 -0
- package/lib/cjs/throttle/db-throttle.d.ts +77 -0
- package/lib/cjs/throttle/db-throttle.d.ts.map +1 -0
- package/lib/cjs/throttle/db-throttle.js +192 -0
- package/lib/cjs/throttle/db-throttle.js.map +1 -0
- package/lib/cjs/throttle/index.d.ts +6 -0
- package/lib/cjs/throttle/index.d.ts.map +1 -0
- package/lib/cjs/throttle/index.js +8 -0
- package/lib/cjs/throttle/index.js.map +1 -0
- package/lib/mjs/audit/db-audit-sink.d.ts +11 -0
- package/lib/mjs/audit/db-audit-sink.d.ts.map +1 -0
- package/lib/mjs/audit/db-audit-sink.js +70 -0
- package/lib/mjs/audit/db-audit-sink.js.map +1 -0
- package/lib/mjs/audit/index.d.ts +2 -0
- package/lib/mjs/audit/index.d.ts.map +1 -0
- package/lib/mjs/audit/index.js +2 -0
- package/lib/mjs/audit/index.js.map +1 -0
- package/lib/mjs/identity.d.ts +6 -0
- package/lib/mjs/identity.d.ts.map +1 -0
- package/lib/mjs/identity.js +23 -0
- package/lib/mjs/identity.js.map +1 -0
- package/lib/mjs/index.d.ts +10 -0
- package/lib/mjs/index.d.ts.map +1 -0
- package/lib/mjs/index.js +10 -0
- package/lib/mjs/index.js.map +1 -0
- package/lib/mjs/links/db-link-store.d.ts +13 -0
- package/lib/mjs/links/db-link-store.d.ts.map +1 -0
- package/lib/mjs/links/db-link-store.js +85 -0
- package/lib/mjs/links/db-link-store.js.map +1 -0
- package/lib/mjs/links/index.d.ts +2 -0
- package/lib/mjs/links/index.d.ts.map +1 -0
- package/lib/mjs/links/index.js +2 -0
- package/lib/mjs/links/index.js.map +1 -0
- package/lib/mjs/mfa/cipher.d.ts +18 -0
- package/lib/mjs/mfa/cipher.d.ts.map +1 -0
- package/lib/mjs/mfa/cipher.js +76 -0
- package/lib/mjs/mfa/cipher.js.map +1 -0
- package/lib/mjs/mfa/db-mfa-enrolments.d.ts +14 -0
- package/lib/mjs/mfa/db-mfa-enrolments.d.ts.map +1 -0
- package/lib/mjs/mfa/db-mfa-enrolments.js +40 -0
- package/lib/mjs/mfa/db-mfa-enrolments.js.map +1 -0
- package/lib/mjs/mfa/db-mfa-store.d.ts +51 -0
- package/lib/mjs/mfa/db-mfa-store.d.ts.map +1 -0
- package/lib/mjs/mfa/db-mfa-store.js +187 -0
- package/lib/mjs/mfa/db-mfa-store.js.map +1 -0
- package/lib/mjs/mfa/index.d.ts +4 -0
- package/lib/mjs/mfa/index.d.ts.map +1 -0
- package/lib/mjs/mfa/index.js +4 -0
- package/lib/mjs/mfa/index.js.map +1 -0
- package/lib/mjs/migrations/AuthnInitial_2026_10_02_00_00_00.d.ts +10 -0
- package/lib/mjs/migrations/AuthnInitial_2026_10_02_00_00_00.d.ts.map +1 -0
- package/lib/mjs/migrations/AuthnInitial_2026_10_02_00_00_00.js +95 -0
- package/lib/mjs/migrations/AuthnInitial_2026_10_02_00_00_00.js.map +1 -0
- package/lib/mjs/migrations/index.d.ts +2 -0
- package/lib/mjs/migrations/index.d.ts.map +1 -0
- package/lib/mjs/migrations/index.js +2 -0
- package/lib/mjs/migrations/index.js.map +1 -0
- package/lib/mjs/models/AuthnAudit.d.ts +18 -0
- package/lib/mjs/models/AuthnAudit.d.ts.map +1 -0
- package/lib/mjs/models/AuthnAudit.js +27 -0
- package/lib/mjs/models/AuthnAudit.js.map +1 -0
- package/lib/mjs/models/AuthnIdentity.d.ts +16 -0
- package/lib/mjs/models/AuthnIdentity.d.ts.map +1 -0
- package/lib/mjs/models/AuthnIdentity.js +36 -0
- package/lib/mjs/models/AuthnIdentity.js.map +1 -0
- package/lib/mjs/models/AuthnMfa.d.ts +16 -0
- package/lib/mjs/models/AuthnMfa.d.ts.map +1 -0
- package/lib/mjs/models/AuthnMfa.js +36 -0
- package/lib/mjs/models/AuthnMfa.js.map +1 -0
- package/lib/mjs/models/AuthnNonce.d.ts +7 -0
- package/lib/mjs/models/AuthnNonce.d.ts.map +1 -0
- package/lib/mjs/models/AuthnNonce.js +27 -0
- package/lib/mjs/models/AuthnNonce.js.map +1 -0
- package/lib/mjs/models/AuthnRecoveryCode.d.ts +10 -0
- package/lib/mjs/models/AuthnRecoveryCode.d.ts.map +1 -0
- package/lib/mjs/models/AuthnRecoveryCode.js +27 -0
- package/lib/mjs/models/AuthnRecoveryCode.js.map +1 -0
- package/lib/mjs/models/AuthnSecret.d.ts +13 -0
- package/lib/mjs/models/AuthnSecret.d.ts.map +1 -0
- package/lib/mjs/models/AuthnSecret.js +35 -0
- package/lib/mjs/models/AuthnSecret.js.map +1 -0
- package/lib/mjs/models/AuthnThrottle.d.ts +10 -0
- package/lib/mjs/models/AuthnThrottle.d.ts.map +1 -0
- package/lib/mjs/models/AuthnThrottle.js +35 -0
- package/lib/mjs/models/AuthnThrottle.js.map +1 -0
- package/lib/mjs/models/index.d.ts +9 -0
- package/lib/mjs/models/index.d.ts.map +1 -0
- package/lib/mjs/models/index.js +9 -0
- package/lib/mjs/models/index.js.map +1 -0
- package/lib/mjs/models/time.d.ts +4 -0
- package/lib/mjs/models/time.d.ts.map +1 -0
- package/lib/mjs/models/time.js +16 -0
- package/lib/mjs/models/time.js.map +1 -0
- package/lib/mjs/nonces/index.d.ts +2 -0
- package/lib/mjs/nonces/index.d.ts.map +1 -0
- package/lib/mjs/nonces/index.js +2 -0
- package/lib/mjs/nonces/index.js.map +1 -0
- package/lib/mjs/nonces/nonce-store.d.ts +11 -0
- package/lib/mjs/nonces/nonce-store.d.ts.map +1 -0
- package/lib/mjs/nonces/nonce-store.js +72 -0
- package/lib/mjs/nonces/nonce-store.js.map +1 -0
- package/lib/mjs/package.json +1 -0
- package/lib/mjs/secrets/db-secret-store.d.ts +25 -0
- package/lib/mjs/secrets/db-secret-store.d.ts.map +1 -0
- package/lib/mjs/secrets/db-secret-store.js +96 -0
- package/lib/mjs/secrets/db-secret-store.js.map +1 -0
- package/lib/mjs/secrets/index.d.ts +2 -0
- package/lib/mjs/secrets/index.d.ts.map +1 -0
- package/lib/mjs/secrets/index.js +2 -0
- package/lib/mjs/secrets/index.js.map +1 -0
- package/lib/mjs/support/sql.d.ts +16 -0
- package/lib/mjs/support/sql.d.ts.map +1 -0
- package/lib/mjs/support/sql.js +41 -0
- package/lib/mjs/support/sql.js.map +1 -0
- package/lib/mjs/support/values.d.ts +7 -0
- package/lib/mjs/support/values.d.ts.map +1 -0
- package/lib/mjs/support/values.js +5 -0
- package/lib/mjs/support/values.js.map +1 -0
- package/lib/mjs/throttle/db-throttle-store.d.ts +79 -0
- package/lib/mjs/throttle/db-throttle-store.d.ts.map +1 -0
- package/lib/mjs/throttle/db-throttle-store.js +227 -0
- package/lib/mjs/throttle/db-throttle-store.js.map +1 -0
- package/lib/mjs/throttle/db-throttle.d.ts +77 -0
- package/lib/mjs/throttle/db-throttle.d.ts.map +1 -0
- package/lib/mjs/throttle/db-throttle.js +189 -0
- package/lib/mjs/throttle/db-throttle.js.map +1 -0
- package/lib/mjs/throttle/index.d.ts +6 -0
- package/lib/mjs/throttle/index.d.ts.map +1 -0
- package/lib/mjs/throttle/index.js +3 -0
- package/lib/mjs/throttle/index.js.map +1 -0
- package/lib/tsconfig.cjs.tsbuildinfo +1 -0
- package/lib/tsconfig.mjs.tsbuildinfo +1 -0
- package/package.json +89 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/secrets/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DbSecretStore = void 0;
|
|
4
|
+
var db_secret_store_js_1 = require("./db-secret-store.js");
|
|
5
|
+
Object.defineProperty(exports, "DbSecretStore", { enumerable: true, get: function () { return db_secret_store_js_1.DbSecretStore; } });
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/secrets/index.ts"],"names":[],"mappings":";;;AAAA,2DAAqD;AAA5C,mHAAA,aAAa,OAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DateTime } from 'luxon';
|
|
2
|
+
export declare const MIN_MS: number;
|
|
3
|
+
export declare const MAX_MS: number;
|
|
4
|
+
/** @internal The instant as a datetime column value, clamped to four-digit years. */
|
|
5
|
+
export declare function instant(ms: number): DateTime;
|
|
6
|
+
/** @internal A hydrated datetime column as a Date; an invalid or missing value is an invalid Date. */
|
|
7
|
+
export declare function dateOf(value: DateTime | null | undefined): Date;
|
|
8
|
+
/** @internal The time of a valid Date within four-digit years; anything else throws `InvalidArgument` naming `label`. */
|
|
9
|
+
export declare function checkedMs(value: unknown, label: string): number;
|
|
10
|
+
/** @internal Hex SHA-256 of the UTF-8 bytes. */
|
|
11
|
+
export declare const sha256hex: (value: string) => string;
|
|
12
|
+
/** @internal A string of at most `max` characters (code points), or `InvalidArgument`; never truncated. */
|
|
13
|
+
export declare function checkedText(value: unknown, max: number, label: string, allowEmpty?: boolean): string;
|
|
14
|
+
/** @internal Cuts to `max` code points, so no surrogate pair is split. */
|
|
15
|
+
export declare function cutText(value: string | undefined | null, max: number): string | null;
|
|
16
|
+
//# sourceMappingURL=sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../../src/support/sql.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGjC,eAAO,MAAM,MAAM,QAAuB,CAAC;AAC3C,eAAO,MAAM,MAAM,QAA0C,CAAC;AAE9D,qFAAqF;AACrF,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,CAE5C;AAED,sGAAsG;AACtG,wBAAgB,MAAM,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAE/D;AAED,yHAAyH;AACzH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/D;AAED,gDAAgD;AAChD,eAAO,MAAM,SAAS,UAAW,MAAM,KAAG,MAAkE,CAAC;AAE7G,2GAA2G;AAC3G,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,UAAQ,GAAG,MAAM,CAOlG;AAED,0EAA0E;AAC1E,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGpF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sha256hex = exports.MAX_MS = exports.MIN_MS = void 0;
|
|
4
|
+
exports.instant = instant;
|
|
5
|
+
exports.dateOf = dateOf;
|
|
6
|
+
exports.checkedMs = checkedMs;
|
|
7
|
+
exports.checkedText = checkedText;
|
|
8
|
+
exports.cutText = cutText;
|
|
9
|
+
const node_crypto_1 = require("node:crypto");
|
|
10
|
+
const exceptions_1 = require("@spinajs/exceptions");
|
|
11
|
+
const util_1 = require("@spinajs/util");
|
|
12
|
+
const luxon_1 = require("luxon");
|
|
13
|
+
// Four-digit years keep the text form of a datetime ordered on sqlite, where the column holds text.
|
|
14
|
+
exports.MIN_MS = Date.UTC(1000, 0, 1);
|
|
15
|
+
exports.MAX_MS = Date.UTC(9999, 11, 31, 23, 59, 59, 999);
|
|
16
|
+
/** @internal The instant as a datetime column value, clamped to four-digit years. */
|
|
17
|
+
function instant(ms) {
|
|
18
|
+
return luxon_1.DateTime.fromMillis(Math.min(Math.max(ms, exports.MIN_MS), exports.MAX_MS), { zone: 'utc' });
|
|
19
|
+
}
|
|
20
|
+
/** @internal A hydrated datetime column as a Date; an invalid or missing value is an invalid Date. */
|
|
21
|
+
function dateOf(value) {
|
|
22
|
+
return value ? value.toJSDate() : new Date(NaN);
|
|
23
|
+
}
|
|
24
|
+
/** @internal The time of a valid Date within four-digit years; anything else throws `InvalidArgument` naming `label`. */
|
|
25
|
+
function checkedMs(value, label) {
|
|
26
|
+
const ms = value instanceof Date ? value.getTime() : NaN;
|
|
27
|
+
if (!Number.isFinite(ms) || ms < exports.MIN_MS || ms > exports.MAX_MS) {
|
|
28
|
+
throw new exceptions_1.InvalidArgument(`${label} must be a valid Date`);
|
|
29
|
+
}
|
|
30
|
+
return ms;
|
|
31
|
+
}
|
|
32
|
+
/** @internal Hex SHA-256 of the UTF-8 bytes. */
|
|
33
|
+
const sha256hex = (value) => (0, node_crypto_1.createHash)('sha256').update(value, 'utf8').digest('hex');
|
|
34
|
+
exports.sha256hex = sha256hex;
|
|
35
|
+
/** @internal A string of at most `max` characters (code points), or `InvalidArgument`; never truncated. */
|
|
36
|
+
function checkedText(value, max, label, allowEmpty = false) {
|
|
37
|
+
const text = (0, util_1._check_arg)((0, util_1._is_string)(...(allowEmpty ? [] : [(0, util_1._non_empty)()])))(value, label);
|
|
38
|
+
const length = [...text].length;
|
|
39
|
+
if (length > max) {
|
|
40
|
+
throw new exceptions_1.InvalidArgument(`${label} is ${length} characters long, the column holds ${max}`);
|
|
41
|
+
}
|
|
42
|
+
return text;
|
|
43
|
+
}
|
|
44
|
+
/** @internal Cuts to `max` code points, so no surrogate pair is split. */
|
|
45
|
+
function cutText(value, max) {
|
|
46
|
+
if (value === undefined || value === null)
|
|
47
|
+
return null;
|
|
48
|
+
return (0, util_1.truncate)(String(value), max, '', { codePoints: true });
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=sql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.js","sourceRoot":"","sources":["../../../src/support/sql.ts"],"names":[],"mappings":";;;;;;;;AAAA,6CAAyC;AAEzC,oDAAsD;AACtD,wCAA6E;AAC7E,iCAAiC;AAEjC,oGAAoG;AACvF,QAAA,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC9B,QAAA,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;AAE9D,qFAAqF;AACrF,iBAAwB,EAAU;IAChC,OAAO,gBAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,QAAA,MAAM,CAAC,EAAE,QAAA,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtF,CAAC;AAED,sGAAsG;AACtG,gBAAuB,KAAkC;IACvD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,CAAC;AAED,yHAAyH;AACzH,mBAA0B,KAAc,EAAE,KAAa;IACrD,MAAM,EAAE,GAAG,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IACzD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,QAAA,MAAM,IAAI,EAAE,GAAG,QAAA,MAAM,EAAE,CAAC;QACvD,MAAM,IAAI,4BAAe,CAAC,GAAG,KAAK,uBAAuB,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,gDAAgD;AACzC,MAAM,SAAS,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAAhG,QAAA,SAAS,GAAT,SAAS,CAAuF;AAE7G,2GAA2G;AAC3G,qBAA4B,KAAc,EAAE,GAAW,EAAE,KAAa,EAAE,UAAU,GAAG,KAAK;IACxF,MAAM,IAAI,GAAW,IAAA,iBAAU,EAAC,IAAA,iBAAU,EAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAA,iBAAU,GAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACjG,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC;IAChC,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;QACjB,MAAM,IAAI,4BAAe,CAAC,GAAG,KAAK,OAAO,MAAM,sCAAsC,GAAG,EAAE,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,0EAA0E;AAC1E,iBAAwB,KAAgC,EAAE,GAAW;IACnE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACvD,OAAO,IAAA,eAAQ,EAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** @internal What a write passes for a model's columns: a nullable column takes `null`, which its property type leaves out. */
|
|
2
|
+
export type ColumnValues<M> = {
|
|
3
|
+
[K in keyof M]?: M[K] | null;
|
|
4
|
+
};
|
|
5
|
+
/** @internal `values` typed as the ORM's write methods take them. */
|
|
6
|
+
export declare function columnValues<M>(values: ColumnValues<M>): Partial<M>;
|
|
7
|
+
//# sourceMappingURL=values.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values.d.ts","sourceRoot":"","sources":["../../../src/support/values.ts"],"names":[],"mappings":"AAAA,+HAA+H;AAC/H,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;CAAE,CAAC;AAE/D,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAEnE"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.columnValues = columnValues;
|
|
4
|
+
/** @internal `values` typed as the ORM's write methods take them. */
|
|
5
|
+
function columnValues(values) {
|
|
6
|
+
return values;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=values.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values.js","sourceRoot":"","sources":["../../../src/support/values.ts"],"names":[],"mappings":";;;AAGA,qEAAqE;AACrE,sBAAgC,MAAuB;IACrD,OAAO,MAAoB,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { KeyedBackoffVerdict, ThrottleGate, ThrottleLower, ThrottleStore } from '@spinajs/util';
|
|
2
|
+
import type { DateTime } from 'luxon';
|
|
3
|
+
/** @internal "now" and the staleness cutoff of one call. */
|
|
4
|
+
export interface StoreTimes {
|
|
5
|
+
readonly nowMs: number;
|
|
6
|
+
readonly now: DateTime;
|
|
7
|
+
readonly cutoff: DateTime;
|
|
8
|
+
}
|
|
9
|
+
/** @internal A live row as stored: its datetimes as read back, so a write can compare against them. */
|
|
10
|
+
export interface ClaimState {
|
|
11
|
+
readonly attempts: number;
|
|
12
|
+
readonly last: DateTime;
|
|
13
|
+
readonly next: DateTime;
|
|
14
|
+
}
|
|
15
|
+
/** @internal What a claim wrote. `prev` is the live row it replaced; without one (insert or stale row) undo deletes the row. */
|
|
16
|
+
export interface ClaimWrite {
|
|
17
|
+
readonly k: string;
|
|
18
|
+
readonly attempts: number;
|
|
19
|
+
readonly last: DateTime;
|
|
20
|
+
readonly next: DateTime;
|
|
21
|
+
readonly prev?: ClaimState;
|
|
22
|
+
}
|
|
23
|
+
/** @internal */
|
|
24
|
+
export type Claimed = {
|
|
25
|
+
readonly ok: true;
|
|
26
|
+
readonly write: ClaimWrite;
|
|
27
|
+
} | {
|
|
28
|
+
readonly ok: false;
|
|
29
|
+
readonly waitMs: number;
|
|
30
|
+
};
|
|
31
|
+
export interface DbThrottleStoreOptions {
|
|
32
|
+
/** A row idle this long that delays nobody counts as zero and may be purged. */
|
|
33
|
+
readonly resetAfterMs: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The rows of `DbAuthnThrottle` in `authn_throttle`, shared by every app instance. Each attempt claims its slot on
|
|
37
|
+
* every gating key with a compare-and-set; a later key that refuses undoes the earlier claims in reverse. No
|
|
38
|
+
* transactions: the sqlite driver shares one handle, so a transaction would take in the statements of concurrent
|
|
39
|
+
* attempts. `succeed` is several compare-and-set statements too, where a lost race only throttles more.
|
|
40
|
+
*
|
|
41
|
+
* Every time decision compares in SQL against the `nowMs` passed in, never SQL `now()`, and never against a datetime
|
|
42
|
+
* read back (lane note D10). Flows need no column: the ids already differ per flow.
|
|
43
|
+
*/
|
|
44
|
+
export declare class DbThrottleStore implements ThrottleStore {
|
|
45
|
+
private readonly resetAfterMs;
|
|
46
|
+
private readonly purgeDue;
|
|
47
|
+
constructor(options: DbThrottleStoreOptions);
|
|
48
|
+
attempt(_flow: string, gates: readonly ThrottleGate[], counted: readonly string[], nowMs: number): Promise<KeyedBackoffVerdict>;
|
|
49
|
+
peek(_flow: string, gates: readonly ThrottleGate[], nowMs: number): Promise<KeyedBackoffVerdict>;
|
|
50
|
+
/** Lowers by the largest count it consumed: rows carry no run, so a `by` row older than its target is taken off too. */
|
|
51
|
+
succeed(_flow: string, lower: readonly ThrottleLower[], clear: readonly string[], nowMs: number): Promise<void>;
|
|
52
|
+
/** Deletes up to one batch of stale rows (idle for resetAfterMs and delaying nobody); returns how many went. */
|
|
53
|
+
purge(nowMs: number): Promise<number>;
|
|
54
|
+
/**
|
|
55
|
+
* @internal Claims one gating key: counts attempt n + 1 and sets its next allowed time, or reports the wait. A miss
|
|
56
|
+
* re-reads this key and the keys still to claim (`rest`): an attempt that will be refused further on stops here,
|
|
57
|
+
* before it adds counts that its undo may no longer take back.
|
|
58
|
+
*/
|
|
59
|
+
protected claimKey(k: string, delay: (n: number) => number, state: ClaimState | undefined, t: StoreTimes, rest?: readonly string[]): Promise<Claimed>;
|
|
60
|
+
/** @internal CAS on a live row that delays nobody now. */
|
|
61
|
+
protected writeLive(w: ClaimWrite, n: number, t: StoreTimes): Promise<boolean>;
|
|
62
|
+
/** @internal Inserts the first count; a row already there may be taken over only when stale (it counts as zero). */
|
|
63
|
+
protected insertOrTakeStale(w: ClaimWrite, t: StoreTimes): Promise<boolean>;
|
|
64
|
+
/** @internal Puts back what each claim replaced, only where the row still holds exactly what it wrote; a miss keeps the count. */
|
|
65
|
+
protected undo(claims: readonly ClaimWrite[], t: StoreTimes): Promise<void>;
|
|
66
|
+
/** A counted row gates nothing, so a lost race here only leaves less to subtract on success (more throttling). */
|
|
67
|
+
private countBook;
|
|
68
|
+
/** Deletes a live counted row; the caller that deleted it gets its count. */
|
|
69
|
+
private takeBook;
|
|
70
|
+
private subtract;
|
|
71
|
+
/** The longest wait any of `ks` imposes now, 0 when none delays. */
|
|
72
|
+
private blockedWait;
|
|
73
|
+
/** Rows of `ks` that are not stale; stale and missing rows both count as zero. */
|
|
74
|
+
private readLive;
|
|
75
|
+
private isLive;
|
|
76
|
+
private refuse;
|
|
77
|
+
private times;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=db-throttle-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-throttle-store.d.ts","sourceRoot":"","sources":["../../../src/throttle/db-throttle-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DbThrottleStore = void 0;
|
|
4
|
+
const contracts_1 = require("@spinajs/authn/contracts");
|
|
5
|
+
const orm_1 = require("@spinajs/orm");
|
|
6
|
+
const AuthnThrottle_js_1 = require("../models/AuthnThrottle.js");
|
|
7
|
+
const sql_js_1 = require("../support/sql.js");
|
|
8
|
+
/** Each miss means another attempt won that key, so this bounds the parallel attempts one call waits behind. */
|
|
9
|
+
const MAX_TRIES = 64;
|
|
10
|
+
const PURGE_EVERY = 64;
|
|
11
|
+
const PURGE_BATCH = 500;
|
|
12
|
+
const FAIL_CLOSED_MS = 1000;
|
|
13
|
+
/**
|
|
14
|
+
* The rows of `DbAuthnThrottle` in `authn_throttle`, shared by every app instance. Each attempt claims its slot on
|
|
15
|
+
* every gating key with a compare-and-set; a later key that refuses undoes the earlier claims in reverse. No
|
|
16
|
+
* transactions: the sqlite driver shares one handle, so a transaction would take in the statements of concurrent
|
|
17
|
+
* attempts. `succeed` is several compare-and-set statements too, where a lost race only throttles more.
|
|
18
|
+
*
|
|
19
|
+
* Every time decision compares in SQL against the `nowMs` passed in, never SQL `now()`, and never against a datetime
|
|
20
|
+
* read back (lane note D10). Flows need no column: the ids already differ per flow.
|
|
21
|
+
*/
|
|
22
|
+
class DbThrottleStore {
|
|
23
|
+
constructor(options) {
|
|
24
|
+
this.purgeDue = (0, orm_1.everyNthCall)(PURGE_EVERY);
|
|
25
|
+
this.resetAfterMs = options.resetAfterMs;
|
|
26
|
+
}
|
|
27
|
+
async attempt(_flow, gates, counted, nowMs) {
|
|
28
|
+
await (0, orm_1.ormReady)();
|
|
29
|
+
if (this.purgeDue()) {
|
|
30
|
+
await this.purge(nowMs);
|
|
31
|
+
}
|
|
32
|
+
const t = this.times(nowMs);
|
|
33
|
+
const backoff = new Map(gates.map((g) => [g.id, g.backoff]));
|
|
34
|
+
const order = [...backoff.keys()].sort();
|
|
35
|
+
if (order.length > 0) {
|
|
36
|
+
const waitMs = await this.blockedWait(order, t);
|
|
37
|
+
if (waitMs > 0)
|
|
38
|
+
return this.refuse(waitMs);
|
|
39
|
+
}
|
|
40
|
+
const live = await this.readLive([...order, ...counted], t);
|
|
41
|
+
const claims = [];
|
|
42
|
+
for (const [i, k] of order.entries()) {
|
|
43
|
+
const p = backoff.get(k);
|
|
44
|
+
const r = await this.claimKey(k, (n) => (0, contracts_1.backoffDelayMs)(p, n), live.get(k), t, order.slice(i + 1));
|
|
45
|
+
if (!r.ok) {
|
|
46
|
+
await this.undo(claims, t);
|
|
47
|
+
return this.refuse(r.waitMs);
|
|
48
|
+
}
|
|
49
|
+
claims.push(r.write);
|
|
50
|
+
}
|
|
51
|
+
for (const k of counted) {
|
|
52
|
+
await this.countBook(k, live.get(k), t);
|
|
53
|
+
}
|
|
54
|
+
return { allowed: true };
|
|
55
|
+
}
|
|
56
|
+
async peek(_flow, gates, nowMs) {
|
|
57
|
+
if (gates.length === 0)
|
|
58
|
+
return { allowed: true };
|
|
59
|
+
await (0, orm_1.ormReady)();
|
|
60
|
+
const waitMs = await this.blockedWait(gates.map((g) => g.id).sort(), this.times(nowMs));
|
|
61
|
+
return waitMs > 0 ? this.refuse(waitMs) : { allowed: true };
|
|
62
|
+
}
|
|
63
|
+
/** Lowers by the largest count it consumed: rows carry no run, so a `by` row older than its target is taken off too. */
|
|
64
|
+
async succeed(_flow, lower, clear, nowMs) {
|
|
65
|
+
await (0, orm_1.ormReady)();
|
|
66
|
+
const t = this.times(nowMs);
|
|
67
|
+
const consumed = new Set();
|
|
68
|
+
for (const { targets, by } of lower) {
|
|
69
|
+
let spent = 0;
|
|
70
|
+
for (const k of by) {
|
|
71
|
+
consumed.add(k);
|
|
72
|
+
spent = Math.max(spent, await this.takeBook(k, t));
|
|
73
|
+
}
|
|
74
|
+
if (spent === 0)
|
|
75
|
+
continue;
|
|
76
|
+
for (const { id, backoff } of targets) {
|
|
77
|
+
await this.subtract(id, backoff, spent, t);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// A consumed row is not deleted again: a count an attempt added to it since stays for the next success.
|
|
81
|
+
for (const k of clear) {
|
|
82
|
+
if (!consumed.has(k))
|
|
83
|
+
await AuthnThrottle_js_1.AuthnThrottle.destroy().where('k', k);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Deletes up to one batch of stale rows (idle for resetAfterMs and delaying nobody); returns how many went. */
|
|
87
|
+
async purge(nowMs) {
|
|
88
|
+
await (0, orm_1.ormReady)();
|
|
89
|
+
const t = this.times(nowMs);
|
|
90
|
+
const stale = await AuthnThrottle_js_1.AuthnThrottle.query().columns(['k']).where('last_attempt_at', '<=', t.cutoff).where('next_allowed_at', '<=', t.now).orderBy('k').take(PURGE_BATCH);
|
|
91
|
+
if (stale.length === 0)
|
|
92
|
+
return 0;
|
|
93
|
+
const { RowsAffected } = await AuthnThrottle_js_1.AuthnThrottle.destroy()
|
|
94
|
+
.whereIn('k', stale.map((r) => r.k))
|
|
95
|
+
.where('last_attempt_at', '<=', t.cutoff)
|
|
96
|
+
.where('next_allowed_at', '<=', t.now);
|
|
97
|
+
return RowsAffected;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* @internal Claims one gating key: counts attempt n + 1 and sets its next allowed time, or reports the wait. A miss
|
|
101
|
+
* re-reads this key and the keys still to claim (`rest`): an attempt that will be refused further on stops here,
|
|
102
|
+
* before it adds counts that its undo may no longer take back.
|
|
103
|
+
*/
|
|
104
|
+
async claimKey(k, delay, state, t, rest = []) {
|
|
105
|
+
for (let i = 0; i < MAX_TRIES; i++) {
|
|
106
|
+
const attempts = (state?.attempts ?? 0) + 1;
|
|
107
|
+
const write = { k, attempts, last: t.now, next: (0, sql_js_1.instant)(t.nowMs + delay(attempts)), prev: state };
|
|
108
|
+
if (state ? await this.writeLive(write, state.attempts, t) : await this.insertOrTakeStale(write, t)) {
|
|
109
|
+
return { ok: true, write };
|
|
110
|
+
}
|
|
111
|
+
const waitMs = await this.blockedWait([k, ...rest], t);
|
|
112
|
+
if (waitMs > 0)
|
|
113
|
+
return { ok: false, waitMs };
|
|
114
|
+
state = (await this.readLive([k], t)).get(k);
|
|
115
|
+
}
|
|
116
|
+
return { ok: false, waitMs: FAIL_CLOSED_MS };
|
|
117
|
+
}
|
|
118
|
+
/** @internal CAS on a live row that delays nobody now. */
|
|
119
|
+
async writeLive(w, n, t) {
|
|
120
|
+
const r = await AuthnThrottle_js_1.AuthnThrottle.update({ attempts: w.attempts, last_attempt_at: w.last, next_allowed_at: w.next, updated_at: t.now }).where('k', w.k).where('attempts', n).where('next_allowed_at', '<=', t.now).where('last_attempt_at', '>', t.cutoff);
|
|
121
|
+
return r.RowsAffected === 1;
|
|
122
|
+
}
|
|
123
|
+
/** @internal Inserts the first count; a row already there may be taken over only when stale (it counts as zero). */
|
|
124
|
+
async insertOrTakeStale(w, t) {
|
|
125
|
+
try {
|
|
126
|
+
await AuthnThrottle_js_1.AuthnThrottle.insert({ k: w.k, attempts: w.attempts, last_attempt_at: w.last, next_allowed_at: w.next, updated_at: t.now });
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
if (!(err instanceof orm_1.UniqueViolation))
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
const r = await AuthnThrottle_js_1.AuthnThrottle.update({ attempts: w.attempts, last_attempt_at: w.last, next_allowed_at: w.next, updated_at: t.now }).where('k', w.k).where('next_allowed_at', '<=', t.now).where('last_attempt_at', '<=', t.cutoff);
|
|
134
|
+
return r.RowsAffected === 1;
|
|
135
|
+
}
|
|
136
|
+
/** @internal Puts back what each claim replaced, only where the row still holds exactly what it wrote; a miss keeps the count. */
|
|
137
|
+
async undo(claims, t) {
|
|
138
|
+
for (const w of [...claims].reverse()) {
|
|
139
|
+
if (w.prev) {
|
|
140
|
+
await AuthnThrottle_js_1.AuthnThrottle.update({ attempts: w.prev.attempts, last_attempt_at: w.prev.last, next_allowed_at: w.prev.next, updated_at: t.now }).where('k', w.k).where('attempts', w.attempts).where('last_attempt_at', w.last).where('next_allowed_at', w.next);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
await AuthnThrottle_js_1.AuthnThrottle.destroy().where('k', w.k).where('attempts', w.attempts).where('last_attempt_at', w.last).where('next_allowed_at', w.next);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** A counted row gates nothing, so a lost race here only leaves less to subtract on success (more throttling). */
|
|
148
|
+
async countBook(k, state, t) {
|
|
149
|
+
for (let i = 0; i < MAX_TRIES; i++) {
|
|
150
|
+
const write = { k, attempts: (state?.attempts ?? 0) + 1, last: t.now, next: t.now };
|
|
151
|
+
if (state ? await this.writeLive(write, state.attempts, t) : await this.insertOrTakeStale(write, t))
|
|
152
|
+
return;
|
|
153
|
+
state = (await this.readLive([k], t)).get(k);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Deletes a live counted row; the caller that deleted it gets its count. */
|
|
157
|
+
async takeBook(k, t) {
|
|
158
|
+
for (let i = 0; i < MAX_TRIES; i++) {
|
|
159
|
+
const state = (await this.readLive([k], t)).get(k);
|
|
160
|
+
if (!state)
|
|
161
|
+
return 0;
|
|
162
|
+
const r = await AuthnThrottle_js_1.AuthnThrottle.destroy().where('k', k).where('attempts', state.attempts).where(this.isLive(t));
|
|
163
|
+
if (r.RowsAffected === 1)
|
|
164
|
+
return state.attempts;
|
|
165
|
+
}
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
async subtract(k, p, spent, t) {
|
|
169
|
+
for (let i = 0; i < MAX_TRIES; i++) {
|
|
170
|
+
const state = (await this.readLive([k], t)).get(k);
|
|
171
|
+
if (!state)
|
|
172
|
+
return;
|
|
173
|
+
const attempts = Math.max(0, state.attempts - spent);
|
|
174
|
+
let r;
|
|
175
|
+
if (attempts === 0) {
|
|
176
|
+
r = await AuthnThrottle_js_1.AuthnThrottle.destroy().where('k', k).where('attempts', state.attempts).where(this.isLive(t));
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// The schedule runs from the row's last counted attempt.
|
|
180
|
+
const lastMs = state.last?.toMillis() ?? NaN;
|
|
181
|
+
const next = Number.isFinite(lastMs) ? (0, sql_js_1.instant)(lastMs + (0, contracts_1.backoffDelayMs)(p, attempts)) : state.next;
|
|
182
|
+
r = await AuthnThrottle_js_1.AuthnThrottle.update({ attempts, next_allowed_at: next, updated_at: t.now }).where('k', k).where('attempts', state.attempts).where(this.isLive(t));
|
|
183
|
+
}
|
|
184
|
+
if (r.RowsAffected === 1)
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** The longest wait any of `ks` imposes now, 0 when none delays. */
|
|
189
|
+
async blockedWait(ks, t) {
|
|
190
|
+
const rows = await AuthnThrottle_js_1.AuthnThrottle.query()
|
|
191
|
+
.columns(['k', 'next_allowed_at'])
|
|
192
|
+
.whereIn('k', [...ks])
|
|
193
|
+
.where('next_allowed_at', '>', t.now);
|
|
194
|
+
let waitMs = 0;
|
|
195
|
+
for (const r of rows) {
|
|
196
|
+
const ms = (r.next_allowed_at?.toMillis() ?? NaN) - t.nowMs;
|
|
197
|
+
// A delaying row whose datetime does not read still delays.
|
|
198
|
+
waitMs = Math.max(waitMs, Number.isFinite(ms) && ms > 0 ? ms : FAIL_CLOSED_MS);
|
|
199
|
+
}
|
|
200
|
+
return waitMs;
|
|
201
|
+
}
|
|
202
|
+
/** Rows of `ks` that are not stale; stale and missing rows both count as zero. */
|
|
203
|
+
async readLive(ks, t) {
|
|
204
|
+
const out = new Map();
|
|
205
|
+
if (ks.length === 0)
|
|
206
|
+
return out;
|
|
207
|
+
const rows = await AuthnThrottle_js_1.AuthnThrottle.query()
|
|
208
|
+
.columns(['k', 'attempts', 'last_attempt_at', 'next_allowed_at'])
|
|
209
|
+
.whereIn('k', [...ks])
|
|
210
|
+
.where(this.isLive(t));
|
|
211
|
+
for (const r of rows) {
|
|
212
|
+
out.set(r.k, { attempts: Number(r.attempts), last: r.last_attempt_at, next: r.next_allowed_at });
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
// Stale: idle for resetAfterMs and delaying nobody; a delay longer than that still runs to its end.
|
|
217
|
+
isLive(t) {
|
|
218
|
+
return function () {
|
|
219
|
+
this.where('last_attempt_at', '>', t.cutoff);
|
|
220
|
+
this.orWhere('next_allowed_at', '>', t.now);
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
refuse(waitMs) {
|
|
224
|
+
return { allowed: false, retryAfterSeconds: Math.max(1, Math.ceil(waitMs / 1000)) };
|
|
225
|
+
}
|
|
226
|
+
times(nowMs) {
|
|
227
|
+
return { nowMs, now: (0, sql_js_1.instant)(nowMs), cutoff: (0, sql_js_1.instant)(nowMs - this.resetAfterMs) };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
exports.DbThrottleStore = DbThrottleStore;
|
|
231
|
+
//# sourceMappingURL=db-throttle-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-throttle-store.js","sourceRoot":"","sources":["../../../src/throttle/db-throttle-store.ts"],"names":[],"mappings":";;;AAAA,wDAA0D;AAE1D,sCAAuE;AAKvE,iEAA2D;AAC3D,8CAA4C;AAE5C,gHAAgH;AAChH,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,WAAW,GAAG,GAAG,CAAC;AACxB,MAAM,cAAc,GAAG,IAAI,CAAC;AAiC5B;;;;;;;;GAQG;AACH;IAIE,YAAY,OAA+B;QAF1B,aAAQ,GAAG,IAAA,kBAAY,EAAC,WAAW,CAAC,CAAC;QAGpD,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAC3C,CAAC;IAEM,KAAK,CAAC,OAAO,CAAC,KAAa,EAAE,KAA8B,EAAE,OAA0B,EAAE,KAAa;QAC3G,MAAM,IAAA,cAAQ,GAAE,CAAC;QACjB,IAAI,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;YACpB,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;QAED,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC7D,MAAM,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAChD,IAAI,MAAM,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7C,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAiB,EAAE,CAAC;QAChC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;YACrC,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC;YAC1B,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAA,0BAAc,EAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAClG,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACV,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;gBAC3B,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAC/B,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,KAAa,EAAE,KAA8B,EAAE,KAAa;QAC5E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACjD,MAAM,IAAA,cAAQ,GAAE,CAAC;QACjB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACxF,OAAO,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC9D,CAAC;IAED,wHAAwH;IACjH,KAAK,CAAC,OAAO,CAAC,KAAa,EAAE,KAA+B,EAAE,KAAwB,EAAE,KAAa;QAC1G,MAAM,IAAA,cAAQ,GAAE,CAAC;QACjB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;QACnC,KAAK,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,KAAK,EAAE,CAAC;YACpC,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;gBACnB,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAChB,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACrD,CAAC;YACD,IAAI,KAAK,KAAK,CAAC;gBAAE,SAAS;YAC1B,KAAK,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,CAAC;gBACtC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;QACD,wGAAwG;QACxG,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,MAAM,gCAAa,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,gHAAgH;IACzG,KAAK,CAAC,KAAK,CAAC,KAAa;QAC9B,MAAM,IAAA,cAAQ,GAAE,CAAC;QACjB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG,MAAM,gCAAa,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACvK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;QACjC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,gCAAa,CAAC,OAAO,EAAE;aACnD,OAAO,CACN,GAAG,EACH,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CACtB;aACA,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;aACxC,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;QACzC,OAAO,YAAY,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACO,KAAK,CAAC,QAAQ,CAAC,CAAS,EAAE,KAA4B,EAAE,KAA6B,EAAE,CAAa,EAAE,IAAI,GAAsB,EAAE;QAC1I,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YAC5C,MAAM,KAAK,GAAe,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,IAAA,gBAAO,EAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;YAC9G,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC;gBACpG,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;YAC7B,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACvD,IAAI,MAAM,GAAG,CAAC;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;YAC7C,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAC/C,CAAC;IAED,0DAA0D;IAChD,KAAK,CAAC,SAAS,CAAC,CAAa,EAAE,CAAS,EAAE,CAAa;QAC/D,MAAM,CAAC,GAAG,MAAM,gCAAa,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;QACvP,OAAO,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,oHAAoH;IAC1G,KAAK,CAAC,iBAAiB,CAAC,CAAa,EAAE,CAAa;QAC5D,IAAI,CAAC;YACH,MAAM,gCAAa,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;YAClI,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,CAAC,GAAG,YAAY,qBAAe,CAAC;gBAAE,MAAM,GAAG,CAAC;QACnD,CAAC;QACD,MAAM,CAAC,GAAG,MAAM,gCAAa,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;QACnO,OAAO,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,kIAAkI;IACxH,KAAK,CAAC,IAAI,CAAC,MAA6B,EAAE,CAAa;QAC/D,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;YACtC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;gBACX,MAAM,gCAAa,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YAC3P,CAAC;iBAAM,CAAC;gBACN,MAAM,gCAAa,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YAChJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,kHAAkH;IAC1G,KAAK,CAAC,SAAS,CAAC,CAAS,EAAE,KAA6B,EAAE,CAAa;QAC7E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,KAAK,GAAe,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;YAChG,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC,CAAC;gBAAE,OAAO;YAC5G,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,6EAA6E;IACrE,KAAK,CAAC,QAAQ,CAAC,CAAS,EAAE,CAAa;QAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACnD,IAAI,CAAC,KAAK;gBAAE,OAAO,CAAC,CAAC;YACrB,MAAM,CAAC,GAAG,MAAM,gCAAa,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9G,IAAI,CAAC,CAAC,YAAY,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC,QAAQ,CAAC;QAClD,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,CAAS,EAAE,CAAgB,EAAE,KAAa,EAAE,CAAa;QAC9E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACnD,IAAI,CAAC,KAAK;gBAAE,OAAO;YACnB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,CAAC;YACrD,IAAI,CAA2B,CAAC;YAChC,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;gBACnB,CAAC,GAAG,MAAM,gCAAa,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1G,CAAC;iBAAM,CAAC;gBACN,yDAAyD;gBACzD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,CAAC;gBAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAA,gBAAO,EAAC,MAAM,GAAG,IAAA,0BAAc,EAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;gBAClG,CAAC,GAAG,MAAM,gCAAa,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/J,CAAC;YACD,IAAI,CAAC,CAAC,YAAY,KAAK,CAAC;gBAAE,OAAO;QACnC,CAAC;IACH,CAAC;IAED,oEAAoE;IAC5D,KAAK,CAAC,WAAW,CAAC,EAAqB,EAAE,CAAa;QAC5D,MAAM,IAAI,GAAG,MAAM,gCAAa,CAAC,KAAK,EAAE;aACrC,OAAO,CAAC,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;aACjC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;aACrB,KAAK,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,eAAe,EAAE,QAAQ,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;YAC5D,4DAA4D;YAC5D,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;QACjF,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,kFAAkF;IAC1E,KAAK,CAAC,QAAQ,CAAC,EAAqB,EAAE,CAAa;QACzD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC1C,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,GAAG,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,gCAAa,CAAC,KAAK,EAAE;aACrC,OAAO,CAAC,CAAC,GAAG,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,CAAC,CAAC;aAChE,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;aACrB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;QACnG,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,oGAAoG;IAC5F,MAAM,CAAC,CAAa;QAC1B,OAAO;YACL,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;YAC7C,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;QAC9C,CAAC,CAAC;IACJ,CAAC;IAEO,MAAM,CAAC,MAAc;QAC3B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC;IACtF,CAAC;IAEO,KAAK,CAAC,KAAa;QACzB,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAA,gBAAO,EAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAA,gBAAO,EAAC,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;IACpF,CAAC;CACF"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { AuthnConfigSource } from '@spinajs/authn/env';
|
|
2
|
+
import { StoreAuthnThrottle } from '@spinajs/authn/store-throttle';
|
|
3
|
+
import type { AuthnContext, ThrottleFlow, ThrottleKey, ThrottleParams } from '@spinajs/authn/contracts';
|
|
4
|
+
import '@spinajs/authn/register-schema';
|
|
5
|
+
import { IContainer } from '@spinajs/di';
|
|
6
|
+
import { Log } from '@spinajs/log';
|
|
7
|
+
import type { ThrottleStore } from '@spinajs/util';
|
|
8
|
+
import { DbThrottleStore } from './db-throttle-store.js';
|
|
9
|
+
import type { DbThrottleStoreOptions } from './db-throttle-store.js';
|
|
10
|
+
export interface DbThrottleOptions {
|
|
11
|
+
/** Overrides `authn.audit.hashKey.secret`; null: a random key of this process. */
|
|
12
|
+
readonly hashKey?: string | null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Backoff per key, never a lock (A05, A06, H31), in `authn_throttle`, shared by every app instance. The rows, their
|
|
16
|
+
* compare-and-set claims and the purge are a `DbThrottleStore`.
|
|
17
|
+
*
|
|
18
|
+
* Every time decision compares in SQL against values computed in Node (`ctx.now`, or the clock in `succeed`),
|
|
19
|
+
* never SQL `now()`, and never against a datetime read back (lane note D10).
|
|
20
|
+
*
|
|
21
|
+
* Tested on sqlite and postgres. On MySQL and MSSQL the migration's `datetime` keeps whole seconds (MySQL) or
|
|
22
|
+
* about 3 ms (MSSQL): undo may then miss, which keeps the extra count (more throttling), and a `baseDelayMs`
|
|
23
|
+
* below one second is not supported there.
|
|
24
|
+
*
|
|
25
|
+
* `last_attempt_at` takes the caller's now, not the later of the two: with clocks skewed across instances, an
|
|
26
|
+
* account-ip row can outlive the ip run it counted in by that skew, and a later success subtracts it from the new run.
|
|
27
|
+
*
|
|
28
|
+
* The hash key check (`checkConfiguration`) runs when AuthnService resolves. A process that resolves this throttle
|
|
29
|
+
* without it (a worker, a CLI command) keys rows with a random key of the process when the key is unset.
|
|
30
|
+
*/
|
|
31
|
+
export declare class DbAuthnThrottle extends StoreAuthnThrottle {
|
|
32
|
+
private readonly options;
|
|
33
|
+
protected ConfiguredParams: ThrottleParams;
|
|
34
|
+
protected ConfiguredHashSecret: string | null | undefined;
|
|
35
|
+
protected Log: Log;
|
|
36
|
+
protected Container: IContainer;
|
|
37
|
+
/** Complete params given to the constructor. */
|
|
38
|
+
private readonly fixedParams;
|
|
39
|
+
private loadedParams;
|
|
40
|
+
private readonly fromConfiguration;
|
|
41
|
+
private hmacKey;
|
|
42
|
+
private loading;
|
|
43
|
+
private rows;
|
|
44
|
+
/**
|
|
45
|
+
* DI passes nothing: `authn.throttle` and `authn.audit.hashKey.secret` come from the configuration of the resolving
|
|
46
|
+
* container on first use (`init`). With `params` nothing is read from the configuration: the hash key is
|
|
47
|
+
* `options.hashKey`. The rows live on the connection of the `AuthnThrottle` model.
|
|
48
|
+
*/
|
|
49
|
+
constructor(params?: ThrottleParams, options?: DbThrottleOptions);
|
|
50
|
+
get params(): ThrottleParams;
|
|
51
|
+
protected get throttleName(): string;
|
|
52
|
+
/**
|
|
53
|
+
* The authn service calls it at boot with its Configuration. Without the hash key this throttle uses (`options.hashKey`
|
|
54
|
+
* or the `authn.audit.hashKey.secret` it reads) the rows are keyed with a random key of this process, which only a
|
|
55
|
+
* development or test env of the booting configuration accepts.
|
|
56
|
+
*/
|
|
57
|
+
checkConfiguration(booting: AuthnConfigSource): Promise<void>;
|
|
58
|
+
/** Resolves the configuration; the first `attempt`, `peek` or `succeed` and every `purge` call it. */
|
|
59
|
+
init(): Promise<void>;
|
|
60
|
+
/** The row key: base64url(HMAC-SHA256(hash key, `flow|kind|value`)); logins and addresses are personal data (SR-19). */
|
|
61
|
+
rowKey(flow: ThrottleFlow, key: ThrottleKey): string;
|
|
62
|
+
/** Deletes up to one batch of stale rows (idle for resetAfterSeconds and delaying nobody); returns how many went. */
|
|
63
|
+
purge(now?: Date): Promise<number>;
|
|
64
|
+
protected store(): Promise<ThrottleStore>;
|
|
65
|
+
protected keyId(flow: ThrottleFlow, key: ThrottleKey): string;
|
|
66
|
+
protected checkKeys(keys: readonly ThrottleKey[]): void;
|
|
67
|
+
protected nowMs(ctx: AuthnContext, method: string): number;
|
|
68
|
+
/** Protected seam: override to wrap or replace the store (tests use it to interleave or fail statements). */
|
|
69
|
+
protected createStore(options: DbThrottleStoreOptions): DbThrottleStore;
|
|
70
|
+
private dbStore;
|
|
71
|
+
private snapshotConfigured;
|
|
72
|
+
/** The key `init` uses: `options.hashKey` when given, else the configured one when there is a configuration. */
|
|
73
|
+
private hashSecret;
|
|
74
|
+
private configurationContainer;
|
|
75
|
+
private load;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=db-throttle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-throttle.d.ts","sourceRoot":"","sources":["../../../src/throttle/db-throttle.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAExG,OAAO,gCAAgC,CAAC;AACxC,OAAO,EAAyB,UAAU,EAAgC,MAAM,aAAa,CAAC;AAE9F,OAAO,EAAE,GAAG,EAAU,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAKrE,MAAM,WAAW,iBAAiB;IAChC,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAEa,eAAgB,SAAQ,kBAAkB;IA0BhB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAxB7D,SAAS,CAAC,gBAAgB,EAAE,cAAc,CAAC;IAG3C,SAAS,CAAC,oBAAoB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAG1D,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;IAGnB,SAAS,CAAC,SAAS,EAAE,UAAU,CAAC;IAEhC,gDAAgD;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAC5C,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,IAAI,CAA8B;IAE1C;;;;OAIG;IACH,YAAY,MAAM,CAAC,EAAE,cAAc,EAAmB,OAAO,GAAE,iBAAsB,EASpF;IAED,IAAW,MAAM,IAAI,cAAc,CAGlC;IAED,cAAuB,YAAY,IAAI,MAAM,CAE5C;IAED;;;;OAIG;IACU,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzE;IAED,sGAAsG;IACzF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAMjC;IAED,wHAAwH;IACjH,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,WAAW,GAAG,MAAM,CAM1D;IAED,qHAAqH;IACxG,KAAK,CAAC,GAAG,GAAE,IAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAG1D;IAED,SAAS,CAAC,KAAK,IAAI,OAAO,CAAC,aAAa,CAAC,CAExC;IAED,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,WAAW,GAAG,MAAM,CAE5D;IAGD,UAAmB,SAAS,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,GAAG,IAAI,CAE/D;IAGD,UAAmB,KAAK,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAElE;IAED,6GAA6G;IAC7G,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,sBAAsB,GAAG,eAAe,CAEtE;YAEa,OAAO;IAOrB,OAAO,CAAC,kBAAkB;IAO1B,gHAAgH;IAChH,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,sBAAsB;YAKhB,IAAI;CAYnB"}
|