@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,96 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
8
|
+
import { SecretStore } from '@spinajs/authn/contracts';
|
|
9
|
+
import { Injectable, PerChildInstance } from '@spinajs/di';
|
|
10
|
+
import { everyNthCall, ormReady } from '@spinajs/orm';
|
|
11
|
+
import { _check_arg, _finite, _gt, _is_number, _is_string, _max_length, _non_empty, _try_check_arg } from '@spinajs/util';
|
|
12
|
+
import { identityKey } from '../identity.js';
|
|
13
|
+
import { AuthnSecret } from '../models/AuthnSecret.js';
|
|
14
|
+
import { checkedMs, checkedText, instant, sha256hex } from '../support/sql.js';
|
|
15
|
+
import { columnValues } from '../support/values.js';
|
|
16
|
+
const PURPOSE_MAX = 32;
|
|
17
|
+
const PURGE_EVERY = 64;
|
|
18
|
+
const PURGE_BATCH = 500;
|
|
19
|
+
/** Far above any secret this store issues; longer input is refused before hashing. */
|
|
20
|
+
const RAW_MAX = 1024;
|
|
21
|
+
/**
|
|
22
|
+
* `authn_secret`: single-use secrets stored as the hex SHA-256 of the raw value only (SR-09). The raw secret is
|
|
23
|
+
* 32 random bytes as base64url (43 chars). Times are taken in Node at call time (F7); the claim of `redeem`
|
|
24
|
+
* decides expiry and single use in SQL against that time (D10).
|
|
25
|
+
*
|
|
26
|
+
* Two parallel `issue` calls for one identity and purpose may both leave a live row: each deletes, then inserts.
|
|
27
|
+
*/
|
|
28
|
+
let DbSecretStore = class DbSecretStore extends SecretStore {
|
|
29
|
+
constructor() {
|
|
30
|
+
super(...arguments);
|
|
31
|
+
this.purgeDue = everyNthCall(PURGE_EVERY);
|
|
32
|
+
}
|
|
33
|
+
async issue(identity, purpose, ttlSeconds) {
|
|
34
|
+
const key = identityKey(identity);
|
|
35
|
+
checkedText(purpose, PURPOSE_MAX, 'DbSecretStore: purpose');
|
|
36
|
+
_check_arg(_is_number(_finite(), _gt(0)))(ttlSeconds, 'ttlSeconds');
|
|
37
|
+
const nowMs = checkedMs(new Date(), 'DbSecretStore.issue: the clock');
|
|
38
|
+
await ormReady();
|
|
39
|
+
await this.purgeNowAndThen(nowMs);
|
|
40
|
+
const raw = randomBytes(32).toString('base64url');
|
|
41
|
+
await AuthnSecret.destroy().where('identity', key).where('purpose', purpose);
|
|
42
|
+
await AuthnSecret.insert(columnValues({ id: randomUUID(), identity: key, purpose, secret_hash: sha256hex(raw), expires_at: instant(nowMs + ttlSeconds * 1000), used_at: null, created_at: instant(nowMs) }));
|
|
43
|
+
return raw;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Looks the row up by the hash of `raw`, so no stored value is compared with the input in Node. The lookup
|
|
47
|
+
* also matches identity and purpose; the claim succeeds for exactly one caller.
|
|
48
|
+
*/
|
|
49
|
+
async redeem(identity, purpose, raw) {
|
|
50
|
+
const key = identityKey(identity);
|
|
51
|
+
checkedText(purpose, PURPOSE_MAX, 'DbSecretStore: purpose');
|
|
52
|
+
if (!_try_check_arg(_is_string(_non_empty(), _max_length(RAW_MAX)))(raw, 'raw')[0])
|
|
53
|
+
return false;
|
|
54
|
+
const nowMs = checkedMs(new Date(), 'DbSecretStore.redeem: the clock');
|
|
55
|
+
await ormReady();
|
|
56
|
+
await this.purgeNowAndThen(nowMs);
|
|
57
|
+
const rows = await AuthnSecret.query().columns(['id', 'identity', 'purpose', 'expires_at']).where('secret_hash', sha256hex(raw)).where('identity', key).where('purpose', purpose).whereNull('used_at');
|
|
58
|
+
// MySQL and MSSQL default collations ignore case: the binding to identity and purpose is checked exactly here.
|
|
59
|
+
const row = rows.find((r) => r.identity === key && r.purpose === purpose);
|
|
60
|
+
if (!row || !this.liveInNode(row.expires_at, nowMs))
|
|
61
|
+
return false;
|
|
62
|
+
const now = instant(nowMs);
|
|
63
|
+
const { RowsAffected } = await AuthnSecret.update({ used_at: now }).where('id', row.id).whereNull('used_at').where('expires_at', '>', now);
|
|
64
|
+
return RowsAffected === 1;
|
|
65
|
+
}
|
|
66
|
+
/** Deletes up to one batch of spent and expired rows; returns how many went. */
|
|
67
|
+
async purge(now = new Date()) {
|
|
68
|
+
const ms = checkedMs(now, 'DbSecretStore.purge: now');
|
|
69
|
+
await ormReady();
|
|
70
|
+
const t = instant(ms);
|
|
71
|
+
const done = function () {
|
|
72
|
+
this.whereNotNull('used_at');
|
|
73
|
+
this.orWhere('expires_at', '<=', t);
|
|
74
|
+
};
|
|
75
|
+
const ids = (await AuthnSecret.query().columns(['id']).where(done).orderBy('id').take(PURGE_BATCH)).map((r) => r.id);
|
|
76
|
+
if (ids.length === 0)
|
|
77
|
+
return 0;
|
|
78
|
+
return (await AuthnSecret.destroy().whereIn('id', ids).where(done)).RowsAffected;
|
|
79
|
+
}
|
|
80
|
+
/** @internal An early exit only: the claim's SQL guard decides. */
|
|
81
|
+
liveInNode(expiresAt, nowMs) {
|
|
82
|
+
const ms = expiresAt ? expiresAt.toMillis() : NaN;
|
|
83
|
+
return !Number.isFinite(ms) || ms > nowMs;
|
|
84
|
+
}
|
|
85
|
+
async purgeNowAndThen(nowMs) {
|
|
86
|
+
if (this.purgeDue()) {
|
|
87
|
+
await this.purge(new Date(nowMs));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
DbSecretStore = __decorate([
|
|
92
|
+
PerChildInstance(),
|
|
93
|
+
Injectable(SecretStore)
|
|
94
|
+
], DbSecretStore);
|
|
95
|
+
export { DbSecretStore };
|
|
96
|
+
//# sourceMappingURL=db-secret-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-secret-store.js","sourceRoot":"","sources":["../../../src/secrets/db-secret-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/secrets/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -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,41 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { InvalidArgument } from '@spinajs/exceptions';
|
|
3
|
+
import { _check_arg, _is_string, _non_empty, truncate } from '@spinajs/util';
|
|
4
|
+
import { DateTime } from 'luxon';
|
|
5
|
+
// Four-digit years keep the text form of a datetime ordered on sqlite, where the column holds text.
|
|
6
|
+
export const MIN_MS = Date.UTC(1000, 0, 1);
|
|
7
|
+
export const MAX_MS = Date.UTC(9999, 11, 31, 23, 59, 59, 999);
|
|
8
|
+
/** @internal The instant as a datetime column value, clamped to four-digit years. */
|
|
9
|
+
export function instant(ms) {
|
|
10
|
+
return DateTime.fromMillis(Math.min(Math.max(ms, MIN_MS), MAX_MS), { zone: 'utc' });
|
|
11
|
+
}
|
|
12
|
+
/** @internal A hydrated datetime column as a Date; an invalid or missing value is an invalid Date. */
|
|
13
|
+
export function dateOf(value) {
|
|
14
|
+
return value ? value.toJSDate() : new Date(NaN);
|
|
15
|
+
}
|
|
16
|
+
/** @internal The time of a valid Date within four-digit years; anything else throws `InvalidArgument` naming `label`. */
|
|
17
|
+
export function checkedMs(value, label) {
|
|
18
|
+
const ms = value instanceof Date ? value.getTime() : NaN;
|
|
19
|
+
if (!Number.isFinite(ms) || ms < MIN_MS || ms > MAX_MS) {
|
|
20
|
+
throw new InvalidArgument(`${label} must be a valid Date`);
|
|
21
|
+
}
|
|
22
|
+
return ms;
|
|
23
|
+
}
|
|
24
|
+
/** @internal Hex SHA-256 of the UTF-8 bytes. */
|
|
25
|
+
export const sha256hex = (value) => createHash('sha256').update(value, 'utf8').digest('hex');
|
|
26
|
+
/** @internal A string of at most `max` characters (code points), or `InvalidArgument`; never truncated. */
|
|
27
|
+
export function checkedText(value, max, label, allowEmpty = false) {
|
|
28
|
+
const text = _check_arg(_is_string(...(allowEmpty ? [] : [_non_empty()])))(value, label);
|
|
29
|
+
const length = [...text].length;
|
|
30
|
+
if (length > max) {
|
|
31
|
+
throw new InvalidArgument(`${label} is ${length} characters long, the column holds ${max}`);
|
|
32
|
+
}
|
|
33
|
+
return text;
|
|
34
|
+
}
|
|
35
|
+
/** @internal Cuts to `max` code points, so no surrogate pair is split. */
|
|
36
|
+
export function cutText(value, max) {
|
|
37
|
+
if (value === undefined || value === null)
|
|
38
|
+
return null;
|
|
39
|
+
return truncate(String(value), max, '', { codePoints: true });
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=sql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.js","sourceRoot":"","sources":["../../../src/support/sql.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjC,oGAAoG;AACpG,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3C,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;AAE9D,qFAAqF;AACrF,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,OAAO,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtF,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,MAAM,CAAC,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,MAAM,UAAU,SAAS,CAAC,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,MAAM,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC;QACvD,MAAM,IAAI,eAAe,CAAC,GAAG,KAAK,uBAAuB,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,gDAAgD;AAChD,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAE7G,2GAA2G;AAC3G,MAAM,UAAU,WAAW,CAAC,KAAc,EAAE,GAAW,EAAE,KAAa,EAAE,UAAU,GAAG,KAAK;IACxF,MAAM,IAAI,GAAW,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,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,eAAe,CAAC,GAAG,KAAK,OAAO,MAAM,sCAAsC,GAAG,EAAE,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,OAAO,CAAC,KAAgC,EAAE,GAAW;IACnE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACvD,OAAO,QAAQ,CAAC,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 @@
|
|
|
1
|
+
{"version":3,"file":"values.js","sourceRoot":"","sources":["../../../src/support/values.ts"],"names":[],"mappings":"AAGA,qEAAqE;AACrE,MAAM,UAAU,YAAY,CAAI,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,227 @@
|
|
|
1
|
+
import { backoffDelayMs } from '@spinajs/authn/contracts';
|
|
2
|
+
import { UniqueViolation, everyNthCall, ormReady } from '@spinajs/orm';
|
|
3
|
+
import { AuthnThrottle } from '../models/AuthnThrottle.js';
|
|
4
|
+
import { instant } from '../support/sql.js';
|
|
5
|
+
/** Each miss means another attempt won that key, so this bounds the parallel attempts one call waits behind. */
|
|
6
|
+
const MAX_TRIES = 64;
|
|
7
|
+
const PURGE_EVERY = 64;
|
|
8
|
+
const PURGE_BATCH = 500;
|
|
9
|
+
const FAIL_CLOSED_MS = 1000;
|
|
10
|
+
/**
|
|
11
|
+
* The rows of `DbAuthnThrottle` in `authn_throttle`, shared by every app instance. Each attempt claims its slot on
|
|
12
|
+
* every gating key with a compare-and-set; a later key that refuses undoes the earlier claims in reverse. No
|
|
13
|
+
* transactions: the sqlite driver shares one handle, so a transaction would take in the statements of concurrent
|
|
14
|
+
* attempts. `succeed` is several compare-and-set statements too, where a lost race only throttles more.
|
|
15
|
+
*
|
|
16
|
+
* Every time decision compares in SQL against the `nowMs` passed in, never SQL `now()`, and never against a datetime
|
|
17
|
+
* read back (lane note D10). Flows need no column: the ids already differ per flow.
|
|
18
|
+
*/
|
|
19
|
+
export class DbThrottleStore {
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.purgeDue = everyNthCall(PURGE_EVERY);
|
|
22
|
+
this.resetAfterMs = options.resetAfterMs;
|
|
23
|
+
}
|
|
24
|
+
async attempt(_flow, gates, counted, nowMs) {
|
|
25
|
+
await ormReady();
|
|
26
|
+
if (this.purgeDue()) {
|
|
27
|
+
await this.purge(nowMs);
|
|
28
|
+
}
|
|
29
|
+
const t = this.times(nowMs);
|
|
30
|
+
const backoff = new Map(gates.map((g) => [g.id, g.backoff]));
|
|
31
|
+
const order = [...backoff.keys()].sort();
|
|
32
|
+
if (order.length > 0) {
|
|
33
|
+
const waitMs = await this.blockedWait(order, t);
|
|
34
|
+
if (waitMs > 0)
|
|
35
|
+
return this.refuse(waitMs);
|
|
36
|
+
}
|
|
37
|
+
const live = await this.readLive([...order, ...counted], t);
|
|
38
|
+
const claims = [];
|
|
39
|
+
for (const [i, k] of order.entries()) {
|
|
40
|
+
const p = backoff.get(k);
|
|
41
|
+
const r = await this.claimKey(k, (n) => backoffDelayMs(p, n), live.get(k), t, order.slice(i + 1));
|
|
42
|
+
if (!r.ok) {
|
|
43
|
+
await this.undo(claims, t);
|
|
44
|
+
return this.refuse(r.waitMs);
|
|
45
|
+
}
|
|
46
|
+
claims.push(r.write);
|
|
47
|
+
}
|
|
48
|
+
for (const k of counted) {
|
|
49
|
+
await this.countBook(k, live.get(k), t);
|
|
50
|
+
}
|
|
51
|
+
return { allowed: true };
|
|
52
|
+
}
|
|
53
|
+
async peek(_flow, gates, nowMs) {
|
|
54
|
+
if (gates.length === 0)
|
|
55
|
+
return { allowed: true };
|
|
56
|
+
await ormReady();
|
|
57
|
+
const waitMs = await this.blockedWait(gates.map((g) => g.id).sort(), this.times(nowMs));
|
|
58
|
+
return waitMs > 0 ? this.refuse(waitMs) : { allowed: true };
|
|
59
|
+
}
|
|
60
|
+
/** Lowers by the largest count it consumed: rows carry no run, so a `by` row older than its target is taken off too. */
|
|
61
|
+
async succeed(_flow, lower, clear, nowMs) {
|
|
62
|
+
await ormReady();
|
|
63
|
+
const t = this.times(nowMs);
|
|
64
|
+
const consumed = new Set();
|
|
65
|
+
for (const { targets, by } of lower) {
|
|
66
|
+
let spent = 0;
|
|
67
|
+
for (const k of by) {
|
|
68
|
+
consumed.add(k);
|
|
69
|
+
spent = Math.max(spent, await this.takeBook(k, t));
|
|
70
|
+
}
|
|
71
|
+
if (spent === 0)
|
|
72
|
+
continue;
|
|
73
|
+
for (const { id, backoff } of targets) {
|
|
74
|
+
await this.subtract(id, backoff, spent, t);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// A consumed row is not deleted again: a count an attempt added to it since stays for the next success.
|
|
78
|
+
for (const k of clear) {
|
|
79
|
+
if (!consumed.has(k))
|
|
80
|
+
await AuthnThrottle.destroy().where('k', k);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Deletes up to one batch of stale rows (idle for resetAfterMs and delaying nobody); returns how many went. */
|
|
84
|
+
async purge(nowMs) {
|
|
85
|
+
await ormReady();
|
|
86
|
+
const t = this.times(nowMs);
|
|
87
|
+
const stale = await AuthnThrottle.query().columns(['k']).where('last_attempt_at', '<=', t.cutoff).where('next_allowed_at', '<=', t.now).orderBy('k').take(PURGE_BATCH);
|
|
88
|
+
if (stale.length === 0)
|
|
89
|
+
return 0;
|
|
90
|
+
const { RowsAffected } = await AuthnThrottle.destroy()
|
|
91
|
+
.whereIn('k', stale.map((r) => r.k))
|
|
92
|
+
.where('last_attempt_at', '<=', t.cutoff)
|
|
93
|
+
.where('next_allowed_at', '<=', t.now);
|
|
94
|
+
return RowsAffected;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* @internal Claims one gating key: counts attempt n + 1 and sets its next allowed time, or reports the wait. A miss
|
|
98
|
+
* re-reads this key and the keys still to claim (`rest`): an attempt that will be refused further on stops here,
|
|
99
|
+
* before it adds counts that its undo may no longer take back.
|
|
100
|
+
*/
|
|
101
|
+
async claimKey(k, delay, state, t, rest = []) {
|
|
102
|
+
for (let i = 0; i < MAX_TRIES; i++) {
|
|
103
|
+
const attempts = (state?.attempts ?? 0) + 1;
|
|
104
|
+
const write = { k, attempts, last: t.now, next: instant(t.nowMs + delay(attempts)), prev: state };
|
|
105
|
+
if (state ? await this.writeLive(write, state.attempts, t) : await this.insertOrTakeStale(write, t)) {
|
|
106
|
+
return { ok: true, write };
|
|
107
|
+
}
|
|
108
|
+
const waitMs = await this.blockedWait([k, ...rest], t);
|
|
109
|
+
if (waitMs > 0)
|
|
110
|
+
return { ok: false, waitMs };
|
|
111
|
+
state = (await this.readLive([k], t)).get(k);
|
|
112
|
+
}
|
|
113
|
+
return { ok: false, waitMs: FAIL_CLOSED_MS };
|
|
114
|
+
}
|
|
115
|
+
/** @internal CAS on a live row that delays nobody now. */
|
|
116
|
+
async writeLive(w, n, t) {
|
|
117
|
+
const r = await 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);
|
|
118
|
+
return r.RowsAffected === 1;
|
|
119
|
+
}
|
|
120
|
+
/** @internal Inserts the first count; a row already there may be taken over only when stale (it counts as zero). */
|
|
121
|
+
async insertOrTakeStale(w, t) {
|
|
122
|
+
try {
|
|
123
|
+
await AuthnThrottle.insert({ k: w.k, attempts: w.attempts, last_attempt_at: w.last, next_allowed_at: w.next, updated_at: t.now });
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
if (!(err instanceof UniqueViolation))
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
const r = await 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);
|
|
131
|
+
return r.RowsAffected === 1;
|
|
132
|
+
}
|
|
133
|
+
/** @internal Puts back what each claim replaced, only where the row still holds exactly what it wrote; a miss keeps the count. */
|
|
134
|
+
async undo(claims, t) {
|
|
135
|
+
for (const w of [...claims].reverse()) {
|
|
136
|
+
if (w.prev) {
|
|
137
|
+
await 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);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
await AuthnThrottle.destroy().where('k', w.k).where('attempts', w.attempts).where('last_attempt_at', w.last).where('next_allowed_at', w.next);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** A counted row gates nothing, so a lost race here only leaves less to subtract on success (more throttling). */
|
|
145
|
+
async countBook(k, state, t) {
|
|
146
|
+
for (let i = 0; i < MAX_TRIES; i++) {
|
|
147
|
+
const write = { k, attempts: (state?.attempts ?? 0) + 1, last: t.now, next: t.now };
|
|
148
|
+
if (state ? await this.writeLive(write, state.attempts, t) : await this.insertOrTakeStale(write, t))
|
|
149
|
+
return;
|
|
150
|
+
state = (await this.readLive([k], t)).get(k);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Deletes a live counted row; the caller that deleted it gets its count. */
|
|
154
|
+
async takeBook(k, t) {
|
|
155
|
+
for (let i = 0; i < MAX_TRIES; i++) {
|
|
156
|
+
const state = (await this.readLive([k], t)).get(k);
|
|
157
|
+
if (!state)
|
|
158
|
+
return 0;
|
|
159
|
+
const r = await AuthnThrottle.destroy().where('k', k).where('attempts', state.attempts).where(this.isLive(t));
|
|
160
|
+
if (r.RowsAffected === 1)
|
|
161
|
+
return state.attempts;
|
|
162
|
+
}
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
async subtract(k, p, spent, t) {
|
|
166
|
+
for (let i = 0; i < MAX_TRIES; i++) {
|
|
167
|
+
const state = (await this.readLive([k], t)).get(k);
|
|
168
|
+
if (!state)
|
|
169
|
+
return;
|
|
170
|
+
const attempts = Math.max(0, state.attempts - spent);
|
|
171
|
+
let r;
|
|
172
|
+
if (attempts === 0) {
|
|
173
|
+
r = await AuthnThrottle.destroy().where('k', k).where('attempts', state.attempts).where(this.isLive(t));
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
// The schedule runs from the row's last counted attempt.
|
|
177
|
+
const lastMs = state.last?.toMillis() ?? NaN;
|
|
178
|
+
const next = Number.isFinite(lastMs) ? instant(lastMs + backoffDelayMs(p, attempts)) : state.next;
|
|
179
|
+
r = await AuthnThrottle.update({ attempts, next_allowed_at: next, updated_at: t.now }).where('k', k).where('attempts', state.attempts).where(this.isLive(t));
|
|
180
|
+
}
|
|
181
|
+
if (r.RowsAffected === 1)
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** The longest wait any of `ks` imposes now, 0 when none delays. */
|
|
186
|
+
async blockedWait(ks, t) {
|
|
187
|
+
const rows = await AuthnThrottle.query()
|
|
188
|
+
.columns(['k', 'next_allowed_at'])
|
|
189
|
+
.whereIn('k', [...ks])
|
|
190
|
+
.where('next_allowed_at', '>', t.now);
|
|
191
|
+
let waitMs = 0;
|
|
192
|
+
for (const r of rows) {
|
|
193
|
+
const ms = (r.next_allowed_at?.toMillis() ?? NaN) - t.nowMs;
|
|
194
|
+
// A delaying row whose datetime does not read still delays.
|
|
195
|
+
waitMs = Math.max(waitMs, Number.isFinite(ms) && ms > 0 ? ms : FAIL_CLOSED_MS);
|
|
196
|
+
}
|
|
197
|
+
return waitMs;
|
|
198
|
+
}
|
|
199
|
+
/** Rows of `ks` that are not stale; stale and missing rows both count as zero. */
|
|
200
|
+
async readLive(ks, t) {
|
|
201
|
+
const out = new Map();
|
|
202
|
+
if (ks.length === 0)
|
|
203
|
+
return out;
|
|
204
|
+
const rows = await AuthnThrottle.query()
|
|
205
|
+
.columns(['k', 'attempts', 'last_attempt_at', 'next_allowed_at'])
|
|
206
|
+
.whereIn('k', [...ks])
|
|
207
|
+
.where(this.isLive(t));
|
|
208
|
+
for (const r of rows) {
|
|
209
|
+
out.set(r.k, { attempts: Number(r.attempts), last: r.last_attempt_at, next: r.next_allowed_at });
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
// Stale: idle for resetAfterMs and delaying nobody; a delay longer than that still runs to its end.
|
|
214
|
+
isLive(t) {
|
|
215
|
+
return function () {
|
|
216
|
+
this.where('last_attempt_at', '>', t.cutoff);
|
|
217
|
+
this.orWhere('next_allowed_at', '>', t.now);
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
refuse(waitMs) {
|
|
221
|
+
return { allowed: false, retryAfterSeconds: Math.max(1, Math.ceil(waitMs / 1000)) };
|
|
222
|
+
}
|
|
223
|
+
times(nowMs) {
|
|
224
|
+
return { nowMs, now: instant(nowMs), cutoff: instant(nowMs - this.resetAfterMs) };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
//# 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":"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"}
|
|
@@ -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
|