@cogenta/auth 0.3.0 → 0.5.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/dist/api-keys.d.ts +93 -4
- package/dist/api-keys.d.ts.map +1 -1
- package/dist/api-keys.js +260 -27
- package/dist/api-keys.js.map +1 -1
- package/dist/audit-integrity.d.ts +61 -0
- package/dist/audit-integrity.d.ts.map +1 -0
- package/dist/audit-integrity.js +130 -0
- package/dist/audit-integrity.js.map +1 -0
- package/dist/audit.d.ts +78 -2
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +168 -24
- package/dist/audit.js.map +1 -1
- package/dist/credentials.d.ts +27 -0
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +61 -0
- package/dist/credentials.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/login.d.ts +63 -6
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +90 -10
- package/dist/login.js.map +1 -1
- package/dist/mfa.d.ts +1 -1
- package/dist/mfa.d.ts.map +1 -1
- package/dist/mfa.js +3 -1
- package/dist/mfa.js.map +1 -1
- package/dist/recovery-codes.d.ts +25 -0
- package/dist/recovery-codes.d.ts.map +1 -0
- package/dist/recovery-codes.js +56 -0
- package/dist/recovery-codes.js.map +1 -0
- package/dist/resets.d.ts +15 -1
- package/dist/resets.d.ts.map +1 -1
- package/dist/resets.js +10 -0
- package/dist/resets.js.map +1 -1
- package/dist/sessions.d.ts +24 -0
- package/dist/sessions.d.ts.map +1 -1
- package/dist/sessions.js +25 -4
- package/dist/sessions.js.map +1 -1
- package/dist/store.d.ts +3 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +4 -1
- package/dist/store.js.map +1 -1
- package/dist/tables.d.ts +17 -0
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +129 -1
- package/dist/tables.js.map +1 -1
- package/dist/types.d.ts +89 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -1
- package/dist/types.js.map +1 -1
- package/dist/user-agent.d.ts +18 -0
- package/dist/user-agent.d.ts.map +1 -0
- package/dist/user-agent.js +39 -0
- package/dist/user-agent.js.map +1 -0
- package/dist/users.d.ts +24 -1
- package/dist/users.d.ts.map +1 -1
- package/dist/users.js +72 -3
- package/dist/users.js.map +1 -1
- package/package.json +3 -3
package/dist/api-keys.d.ts
CHANGED
|
@@ -1,19 +1,108 @@
|
|
|
1
1
|
import { type DatabaseHandle } from '@cogenta/core';
|
|
2
|
-
import type { ApiKey, CreateApiKeyInput, IssuedApiKey } from './types.js';
|
|
2
|
+
import type { ApiKey, ApiKeyRotationResult, ApiKeyUsage, CreateApiKeyInput, IssuedApiKey } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A generous but real per-key request quota (fiche 20, "décisions à
|
|
5
|
+
* prendre"): high enough that no legitimate integration ever notices it,
|
|
6
|
+
* low enough that a leaked key cannot read the whole site as fast as the
|
|
7
|
+
* network allows. Applied whenever a key is minted — by `create` or by
|
|
8
|
+
* `rotate` — without an explicit quota of its own.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_RATE_LIMIT_PER_MINUTE = 600;
|
|
11
|
+
/**
|
|
12
|
+
* Fiche 62 task 2: how long a revoked key has to sit before it can be
|
|
13
|
+
* purged — the same reasoning and the same default as content's own
|
|
14
|
+
* `DEFAULT_TRASH_RETAIN_DAYS` (`@cogenta/schema`), applied here because a
|
|
15
|
+
* revoked key is not stored anywhere else: purging too early is the one
|
|
16
|
+
* mistake `purge()` must never make, since it is a real `DELETE`, not a
|
|
17
|
+
* second layer of soft state on top of `revoked_at`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const MIN_PURGE_AFTER_REVOKED_DAYS = 30;
|
|
20
|
+
/**
|
|
21
|
+
* Fiche 62 task 3 (decision "(b)", recommended by the fiche): a key revoked
|
|
22
|
+
* by mistake is not reactivated — `revoked_at` never goes back to `null` —
|
|
23
|
+
* but a short window after the mistake, `recover()` mints a replacement the
|
|
24
|
+
* same way `rotate()` does for an active key. Past this window the only way
|
|
25
|
+
* back is minting a brand new key from scratch, on purpose: the longer a key
|
|
26
|
+
* has been dead, the less likely "revoked by mistake" is the true story.
|
|
27
|
+
*/
|
|
28
|
+
export declare const RECOVERY_WINDOW_MS: number;
|
|
3
29
|
/** A raw bearer token is an API key only if it has this shape — checked before any DB lookup. */
|
|
4
30
|
export declare function looksLikeApiKey(token: string): boolean;
|
|
31
|
+
export interface RotateApiKeyOptions {
|
|
32
|
+
/** How long the replaced key keeps working alongside the new one. */
|
|
33
|
+
readonly graceMs: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Fiche 67 task 5. Both omitted (the historical call shape, `mcp.tsx`'s
|
|
37
|
+
* picker among others) still returns every key, unpaginated, byte for byte —
|
|
38
|
+
* this is additive, not a breaking change to an existing publishable
|
|
39
|
+
* interface.
|
|
40
|
+
*/
|
|
41
|
+
export interface ListApiKeysOptions {
|
|
42
|
+
readonly limit?: number;
|
|
43
|
+
readonly offset?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Fiche feedback: a key's name/scope/quota had no edit path at all after
|
|
47
|
+
* creation — only `rotate()` existed, and it reissues the secret under the
|
|
48
|
+
* *same* name/scope, never a changed one. Every field absent from the patch
|
|
49
|
+
* is left exactly as saved; `rateLimitPerMinute: null` clears an explicit
|
|
50
|
+
* quota back to `DEFAULT_RATE_LIMIT_PER_MINUTE`, matching how omitting it at
|
|
51
|
+
* `create()` time already behaves.
|
|
52
|
+
*/
|
|
53
|
+
export interface UpdateApiKeyInput {
|
|
54
|
+
readonly name?: string;
|
|
55
|
+
readonly scope?: readonly string[];
|
|
56
|
+
readonly rateLimitPerMinute?: number | null;
|
|
57
|
+
}
|
|
5
58
|
export interface ApiKeyStore {
|
|
6
59
|
/** Mints a key and returns the raw secret once. It is never stored or returned again. */
|
|
7
60
|
create(input: CreateApiKeyInput): Promise<IssuedApiKey>;
|
|
8
|
-
/**
|
|
9
|
-
|
|
61
|
+
/** Tri-state patch — see `UpdateApiKeyInput`. Never touches the key's secret, prefix, or lifecycle fields. */
|
|
62
|
+
update(id: string, patch: UpdateApiKeyInput): Promise<ApiKey>;
|
|
63
|
+
/** Never the raw key or its hash — a prefix is all a list ever shows. Newest first; `limit`/`offset` page it (fiche 67 task 5), both absent means "every key". */
|
|
64
|
+
list(options?: ListApiKeysOptions): Promise<readonly ApiKey[]>;
|
|
65
|
+
getById(id: string): Promise<ApiKey | null>;
|
|
10
66
|
revoke(id: string): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Fiche 62 task 2: a real, permanent `DELETE` — the row and its usage
|
|
69
|
+
* history are gone, never just hidden. Refuses anything but a key that is
|
|
70
|
+
* both revoked and has been for at least `MIN_PURGE_AFTER_REVOKED_DAYS`:
|
|
71
|
+
* an active key (even one technically past its `expiresAt`) is never
|
|
72
|
+
* purgeable, and neither is a key revoked too recently — see the piège
|
|
73
|
+
* connu in fiche 62 §5.
|
|
74
|
+
*/
|
|
75
|
+
purge(id: string): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Fiche 62 task 3, decision (b): recovers from a key revoked by mistake
|
|
78
|
+
* *without* lifting `revoked_at` — the revoked row stays revoked forever,
|
|
79
|
+
* exactly as `statusOf` in the admin already assumes. Instead, within
|
|
80
|
+
* `RECOVERY_WINDOW_MS` of the revocation, this mints a fresh replacement
|
|
81
|
+
* carrying the same name, scope and quota, the same way `rotate` does for
|
|
82
|
+
* a live key. Refuses a key that was never revoked (nothing to recover
|
|
83
|
+
* from — rotate it instead) and a key revoked longer ago than the window.
|
|
84
|
+
*/
|
|
85
|
+
recover(id: string): Promise<IssuedApiKey>;
|
|
11
86
|
/**
|
|
12
87
|
* Resolves a raw bearer token to the key it names, or `null` if it does not
|
|
13
88
|
* exist, is revoked, or has expired. Touches `lastUsedAt` on success, the
|
|
14
|
-
* same sliding-window courtesy a session gets
|
|
89
|
+
* same sliding-window courtesy a session gets, and counts the call toward
|
|
90
|
+
* this key's aggregated daily usage (fiche 20 task 4).
|
|
15
91
|
*/
|
|
16
92
|
verify(rawKey: string): Promise<ApiKey | null>;
|
|
93
|
+
/**
|
|
94
|
+
* "Faire tourner cette clé" (fiche 20 task 2): mints a replacement carrying
|
|
95
|
+
* the same name, scope and quota, and leaves the original valid for
|
|
96
|
+
* `graceMs` longer rather than revoking it outright — a rotation with no
|
|
97
|
+
* window where nothing authenticates.
|
|
98
|
+
*
|
|
99
|
+
* Refuses a key that is already revoked or expired: there is nothing left
|
|
100
|
+
* to hand a grace period to, and calling that "rotation" would hide that
|
|
101
|
+
* the integration already stopped working.
|
|
102
|
+
*/
|
|
103
|
+
rotate(id: string, options: RotateApiKeyOptions): Promise<ApiKeyRotationResult>;
|
|
104
|
+
/** Aggregated call counts over the last 7 and 30 days — never a line per call. */
|
|
105
|
+
usage(id: string): Promise<ApiKeyUsage>;
|
|
17
106
|
}
|
|
18
107
|
export declare function createApiKeyStore(db: DatabaseHandle, now?: () => number): ApiKeyStore;
|
|
19
108
|
//# sourceMappingURL=api-keys.d.ts.map
|
package/dist/api-keys.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-keys.d.ts","sourceRoot":"","sources":["../src/api-keys.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"api-keys.d.ts","sourceRoot":"","sources":["../src/api-keys.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,cAAc,EAKpB,MAAM,eAAe,CAAA;AAEtB,OAAO,KAAK,EACV,MAAM,EACN,oBAAoB,EACpB,WAAW,EACX,iBAAiB,EACjB,YAAY,EACb,MAAM,YAAY,CAAA;AA0BnB;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,MAAM,CAAA;AAEhD;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAA;AAE9C;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,QAAsB,CAAA;AAWrD,iGAAiG;AACjG,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEtD;AAuDD,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5C;AAED,MAAM,WAAW,WAAW;IAC1B,yFAAyF;IACzF,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IACvD,8GAA8G;IAC9G,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7D,kKAAkK;IAClK,IAAI,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAA;IAC9D,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAC3C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChC;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAC1C;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAC9C;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAA;IAC/E,kFAAkF;IAClF,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CACxC;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,GAAE,MAAM,MAAiB,GAAG,WAAW,CA6S/F"}
|
package/dist/api-keys.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
-
import { CogentaError, identifier, newId, sql } from '@cogenta/core';
|
|
2
|
+
import { CogentaError, identifier, newId, sql, limit as sqlLimit, } from '@cogenta/core';
|
|
3
3
|
import { TABLES } from './tables.js';
|
|
4
4
|
/**
|
|
5
|
-
* Machine-to-machine bearer credentials (L13 task 8
|
|
5
|
+
* Machine-to-machine bearer credentials (L13 task 8; lifecycle, quota and
|
|
6
|
+
* usage added by fiche 20).
|
|
6
7
|
*
|
|
7
8
|
* `resolveActor` in `@cogenta/api` already recognises one bearer-token shape:
|
|
8
9
|
* a session, minted at sign-in for a human. This store mints the other
|
|
@@ -22,6 +23,32 @@ const KEY_BYTES = 32;
|
|
|
22
23
|
const KEY_PREFIX = 'cogenta_sk_';
|
|
23
24
|
/** How much of the raw key stays visible forever, so a list of keys is recognisable. */
|
|
24
25
|
const VISIBLE_PREFIX_LENGTH = 12;
|
|
26
|
+
/**
|
|
27
|
+
* A generous but real per-key request quota (fiche 20, "décisions à
|
|
28
|
+
* prendre"): high enough that no legitimate integration ever notices it,
|
|
29
|
+
* low enough that a leaked key cannot read the whole site as fast as the
|
|
30
|
+
* network allows. Applied whenever a key is minted — by `create` or by
|
|
31
|
+
* `rotate` — without an explicit quota of its own.
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_RATE_LIMIT_PER_MINUTE = 600;
|
|
34
|
+
/**
|
|
35
|
+
* Fiche 62 task 2: how long a revoked key has to sit before it can be
|
|
36
|
+
* purged — the same reasoning and the same default as content's own
|
|
37
|
+
* `DEFAULT_TRASH_RETAIN_DAYS` (`@cogenta/schema`), applied here because a
|
|
38
|
+
* revoked key is not stored anywhere else: purging too early is the one
|
|
39
|
+
* mistake `purge()` must never make, since it is a real `DELETE`, not a
|
|
40
|
+
* second layer of soft state on top of `revoked_at`.
|
|
41
|
+
*/
|
|
42
|
+
export const MIN_PURGE_AFTER_REVOKED_DAYS = 30;
|
|
43
|
+
/**
|
|
44
|
+
* Fiche 62 task 3 (decision "(b)", recommended by the fiche): a key revoked
|
|
45
|
+
* by mistake is not reactivated — `revoked_at` never goes back to `null` —
|
|
46
|
+
* but a short window after the mistake, `recover()` mints a replacement the
|
|
47
|
+
* same way `rotate()` does for an active key. Past this window the only way
|
|
48
|
+
* back is minting a brand new key from scratch, on purpose: the longer a key
|
|
49
|
+
* has been dead, the less likely "revoked by mistake" is the true story.
|
|
50
|
+
*/
|
|
51
|
+
export const RECOVERY_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
25
52
|
function issueKey() {
|
|
26
53
|
return `${KEY_PREFIX}${randomBytes(KEY_BYTES).toString('base64url')}`;
|
|
27
54
|
}
|
|
@@ -33,6 +60,10 @@ function hashKey(key) {
|
|
|
33
60
|
export function looksLikeApiKey(token) {
|
|
34
61
|
return token.startsWith(KEY_PREFIX) && token.length > KEY_PREFIX.length;
|
|
35
62
|
}
|
|
63
|
+
/** `YYYY-MM-DD`, UTC — the same day boundary everywhere, regardless of server timezone. */
|
|
64
|
+
function dayKey(ms) {
|
|
65
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
66
|
+
}
|
|
36
67
|
function fromRow(row) {
|
|
37
68
|
return {
|
|
38
69
|
id: row.id,
|
|
@@ -44,6 +75,8 @@ function fromRow(row) {
|
|
|
44
75
|
expiresAt: row.expires_at ?? undefined,
|
|
45
76
|
revokedAt: row.revoked_at ?? undefined,
|
|
46
77
|
lastUsedAt: row.last_used_at ?? undefined,
|
|
78
|
+
rateLimitPerMinute: row.rate_limit_per_minute ?? DEFAULT_RATE_LIMIT_PER_MINUTE,
|
|
79
|
+
supersededBy: row.superseded_by ?? undefined,
|
|
47
80
|
};
|
|
48
81
|
}
|
|
49
82
|
function assertScope(scope) {
|
|
@@ -55,42 +88,172 @@ function assertScope(scope) {
|
|
|
55
88
|
});
|
|
56
89
|
}
|
|
57
90
|
}
|
|
91
|
+
function keyNotFound() {
|
|
92
|
+
return new CogentaError({
|
|
93
|
+
code: 'API_KEY_NOT_FOUND',
|
|
94
|
+
message: 'No API key with that id.',
|
|
95
|
+
hint: 'It may already have been revoked and removed from the list, or the id may be mistyped.',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
58
98
|
export function createApiKeyStore(db, now = Date.now) {
|
|
59
99
|
const table = identifier(TABLES.apiKeys, db.dialect);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
100
|
+
const usageTable = identifier(TABLES.apiKeyUsage, db.dialect);
|
|
101
|
+
async function create(input) {
|
|
102
|
+
assertScope(input.scope);
|
|
103
|
+
const key = issueKey();
|
|
104
|
+
const id = newId(now);
|
|
105
|
+
const created = new Date(now()).toISOString();
|
|
106
|
+
const prefix = key.slice(0, VISIBLE_PREFIX_LENGTH);
|
|
107
|
+
const rateLimitPerMinute = input.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT_PER_MINUTE;
|
|
108
|
+
await db.query(sql `
|
|
68
109
|
insert into ${table}
|
|
69
|
-
(id, name, key_hash, key_prefix, scope, created_by, created_at, expires_at, revoked_at,
|
|
110
|
+
(id, name, key_hash, key_prefix, scope, created_by, created_at, expires_at, revoked_at,
|
|
111
|
+
last_used_at, rate_limit_per_minute, superseded_by)
|
|
70
112
|
values
|
|
71
113
|
(${id}, ${input.name}, ${hashKey(key)}, ${prefix}, ${JSON.stringify(input.scope)},
|
|
72
|
-
${input.createdBy}, ${created}, ${input.expiresAt ?? null}, ${null},
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
${input.createdBy}, ${created}, ${input.expiresAt ?? null}, ${null},
|
|
115
|
+
${null}, ${rateLimitPerMinute}, ${null})`);
|
|
116
|
+
return {
|
|
117
|
+
id,
|
|
118
|
+
name: input.name,
|
|
119
|
+
prefix,
|
|
120
|
+
scope: input.scope,
|
|
121
|
+
createdBy: input.createdBy,
|
|
122
|
+
createdAt: created,
|
|
123
|
+
expiresAt: input.expiresAt,
|
|
124
|
+
revokedAt: undefined,
|
|
125
|
+
lastUsedAt: undefined,
|
|
126
|
+
rateLimitPerMinute,
|
|
127
|
+
supersededBy: undefined,
|
|
128
|
+
key,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function recordUsage(id) {
|
|
132
|
+
const day = dayKey(now());
|
|
133
|
+
const updated = await db.query(sql `update ${usageTable} set count = count + 1 where key_id = ${id} and day = ${day}`);
|
|
134
|
+
if (updated.rowsAffected > 0)
|
|
135
|
+
return;
|
|
136
|
+
// First call of the day for this key: insert the row. A concurrent first
|
|
137
|
+
// call could lose this race — the unique index on (key_id, day) then
|
|
138
|
+
// refuses the second insert, and the fallback update picks up the count
|
|
139
|
+
// that insert would have started at 1.
|
|
140
|
+
await db
|
|
141
|
+
.query(sql `insert into ${usageTable} (id, key_id, day, count) values (${newId(now)}, ${id}, ${day}, ${1})`)
|
|
142
|
+
.catch(() => db.query(sql `update ${usageTable} set count = count + 1 where key_id = ${id} and day = ${day}`));
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
create,
|
|
146
|
+
list: async (options) => {
|
|
147
|
+
let statement = sql `select * from ${table} order by created_at desc, id desc`;
|
|
148
|
+
if (options?.limit !== undefined) {
|
|
149
|
+
statement = sql `${statement} limit ${sqlLimit(options.limit)} offset ${sqlLimit(options.offset ?? 0)}`;
|
|
150
|
+
}
|
|
151
|
+
const result = await db.query(statement);
|
|
88
152
|
return result.rows.map(fromRow);
|
|
89
153
|
},
|
|
154
|
+
getById: async (id) => {
|
|
155
|
+
const result = await db.query(sql `select * from ${table} where id = ${id}`);
|
|
156
|
+
const row = result.rows[0];
|
|
157
|
+
return row === undefined ? null : fromRow(row);
|
|
158
|
+
},
|
|
159
|
+
update: async (id, patch) => {
|
|
160
|
+
const result = await db.query(sql `select * from ${table} where id = ${id}`);
|
|
161
|
+
const row = result.rows[0];
|
|
162
|
+
if (row === undefined)
|
|
163
|
+
throw keyNotFound();
|
|
164
|
+
if (patch.scope !== undefined)
|
|
165
|
+
assertScope(patch.scope);
|
|
166
|
+
const name = patch.name ?? row.name;
|
|
167
|
+
const scopeJson = patch.scope === undefined ? row.scope : JSON.stringify(patch.scope);
|
|
168
|
+
const rateLimitPerMinute = patch.rateLimitPerMinute === undefined
|
|
169
|
+
? row.rate_limit_per_minute
|
|
170
|
+
: patch.rateLimitPerMinute;
|
|
171
|
+
await db.query(sql `
|
|
172
|
+
update ${table}
|
|
173
|
+
set name = ${name}, scope = ${scopeJson}, rate_limit_per_minute = ${rateLimitPerMinute}
|
|
174
|
+
where id = ${id}`);
|
|
175
|
+
return fromRow({
|
|
176
|
+
...row,
|
|
177
|
+
name,
|
|
178
|
+
scope: scopeJson,
|
|
179
|
+
rate_limit_per_minute: rateLimitPerMinute,
|
|
180
|
+
});
|
|
181
|
+
},
|
|
90
182
|
revoke: async (id) => {
|
|
91
183
|
const revokedAt = new Date(now()).toISOString();
|
|
92
184
|
await db.query(sql `update ${table} set revoked_at = ${revokedAt} where id = ${id} and revoked_at is null`);
|
|
93
185
|
},
|
|
186
|
+
purge: async (id) => {
|
|
187
|
+
const result = await db.query(sql `select * from ${table} where id = ${id}`);
|
|
188
|
+
const row = result.rows[0];
|
|
189
|
+
if (row === undefined)
|
|
190
|
+
throw keyNotFound();
|
|
191
|
+
if (row.revoked_at === null) {
|
|
192
|
+
throw new CogentaError({
|
|
193
|
+
code: 'API_KEY_PURGE_INVALID',
|
|
194
|
+
message: 'Only a revoked key can be purged.',
|
|
195
|
+
hint: 'Revoke this key first if it should no longer exist at all.',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const revokedAgoMs = now() - new Date(row.revoked_at).getTime();
|
|
199
|
+
const minAgeMs = MIN_PURGE_AFTER_REVOKED_DAYS * 24 * 60 * 60 * 1000;
|
|
200
|
+
if (revokedAgoMs < minAgeMs) {
|
|
201
|
+
throw new CogentaError({
|
|
202
|
+
code: 'API_KEY_PURGE_INVALID',
|
|
203
|
+
message: `A revoked key can only be purged after ${MIN_PURGE_AFTER_REVOKED_DAYS} days.`,
|
|
204
|
+
hint: 'Wait for the retention window to pass, or leave it revoked — a revoked key can never authenticate again.',
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
// Its usage history first: nothing references it by foreign key, but a
|
|
208
|
+
// dangling row for a key id that no longer exists would only ever be
|
|
209
|
+
// dead weight, never something `usage()` is asked about again.
|
|
210
|
+
await db.query(sql `delete from ${usageTable} where key_id = ${id}`);
|
|
211
|
+
await db.query(sql `delete from ${table} where id = ${id}`);
|
|
212
|
+
},
|
|
213
|
+
recover: async (id) => {
|
|
214
|
+
const result = await db.query(sql `select * from ${table} where id = ${id}`);
|
|
215
|
+
const row = result.rows[0];
|
|
216
|
+
if (row === undefined)
|
|
217
|
+
throw keyNotFound();
|
|
218
|
+
if (row.revoked_at === null) {
|
|
219
|
+
throw new CogentaError({
|
|
220
|
+
code: 'API_KEY_RECOVERY_INVALID',
|
|
221
|
+
message: 'Only a revoked key can be recovered — an active key should be rotated instead.',
|
|
222
|
+
hint: 'Use rotate() for a key that is still active.',
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
const revokedAgoMs = now() - new Date(row.revoked_at).getTime();
|
|
226
|
+
if (revokedAgoMs > RECOVERY_WINDOW_MS) {
|
|
227
|
+
throw new CogentaError({
|
|
228
|
+
code: 'API_KEY_RECOVERY_INVALID',
|
|
229
|
+
message: `This key was revoked more than ${RECOVERY_WINDOW_MS / (60 * 60 * 1000)} hours ago — recovery is only for a very recent, mistaken revocation.`,
|
|
230
|
+
hint: 'Create a brand new key instead. The revoked key stays revoked either way.',
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const scope = JSON.parse(row.scope);
|
|
234
|
+
const rateLimitPerMinute = row.rate_limit_per_minute ?? DEFAULT_RATE_LIMIT_PER_MINUTE;
|
|
235
|
+
const currentTime = now();
|
|
236
|
+
// Same duration-preserving policy as rotate(): a replacement for a key
|
|
237
|
+
// that never expired keeps not expiring, and one that had a fixed
|
|
238
|
+
// lifetime gets that same duration, restarted from now.
|
|
239
|
+
const expiresAt = row.expires_at === null
|
|
240
|
+
? undefined
|
|
241
|
+
: new Date(currentTime +
|
|
242
|
+
Math.max(new Date(row.expires_at).getTime() - new Date(row.created_at).getTime(), 0)).toISOString();
|
|
243
|
+
const issued = await create({
|
|
244
|
+
name: row.name,
|
|
245
|
+
scope,
|
|
246
|
+
createdBy: row.created_by,
|
|
247
|
+
rateLimitPerMinute,
|
|
248
|
+
...(expiresAt === undefined ? {} : { expiresAt }),
|
|
249
|
+
});
|
|
250
|
+
// `revoked_at` is deliberately never touched: the row this recovers
|
|
251
|
+
// from stays revoked forever, exactly as decision (b) in fiche 62 §3
|
|
252
|
+
// requires. `superseded_by` only records what replaced it, the same
|
|
253
|
+
// way rotation already links a live key to what came after it.
|
|
254
|
+
await db.query(sql `update ${table} set superseded_by = ${issued.id} where id = ${id}`);
|
|
255
|
+
return issued;
|
|
256
|
+
},
|
|
94
257
|
verify: async (rawKey) => {
|
|
95
258
|
if (!looksLikeApiKey(rawKey))
|
|
96
259
|
return null;
|
|
@@ -105,8 +268,78 @@ export function createApiKeyStore(db, now = Date.now) {
|
|
|
105
268
|
return null;
|
|
106
269
|
const lastUsedAt = new Date(now()).toISOString();
|
|
107
270
|
await db.query(sql `update ${table} set last_used_at = ${lastUsedAt} where id = ${row.id}`);
|
|
271
|
+
await recordUsage(row.id);
|
|
108
272
|
return fromRow({ ...row, last_used_at: lastUsedAt });
|
|
109
273
|
},
|
|
274
|
+
rotate: async (id, options) => {
|
|
275
|
+
const result = await db.query(sql `select * from ${table} where id = ${id}`);
|
|
276
|
+
const row = result.rows[0];
|
|
277
|
+
if (row === undefined)
|
|
278
|
+
throw keyNotFound();
|
|
279
|
+
if (row.revoked_at !== null) {
|
|
280
|
+
throw new CogentaError({
|
|
281
|
+
code: 'API_KEY_ROTATION_INVALID',
|
|
282
|
+
message: 'A revoked key cannot be rotated.',
|
|
283
|
+
hint: 'Create a new key instead — there is nothing left to hand a grace period to.',
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
const currentTime = now();
|
|
287
|
+
if (row.expires_at !== null && new Date(row.expires_at).getTime() <= currentTime) {
|
|
288
|
+
throw new CogentaError({
|
|
289
|
+
code: 'API_KEY_ROTATION_INVALID',
|
|
290
|
+
message: 'An expired key cannot be rotated.',
|
|
291
|
+
hint: 'Create a new key instead — there is nothing left to hand a grace period to.',
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
const scope = JSON.parse(row.scope);
|
|
295
|
+
const rateLimitPerMinute = row.rate_limit_per_minute ?? DEFAULT_RATE_LIMIT_PER_MINUTE;
|
|
296
|
+
// The new key's own expiry policy preserves the *duration* the old key
|
|
297
|
+
// was valid for, restarted from now — never its literal calendar date,
|
|
298
|
+
// which could already be imminent. A key that never expired keeps not
|
|
299
|
+
// expiring; nothing here invents a default the caller did not ask for.
|
|
300
|
+
const expiresAt = row.expires_at === null
|
|
301
|
+
? undefined
|
|
302
|
+
: new Date(currentTime +
|
|
303
|
+
Math.max(new Date(row.expires_at).getTime() - new Date(row.created_at).getTime(), 0)).toISOString();
|
|
304
|
+
const issued = await create({
|
|
305
|
+
name: row.name,
|
|
306
|
+
scope,
|
|
307
|
+
createdBy: row.created_by,
|
|
308
|
+
rateLimitPerMinute,
|
|
309
|
+
...(expiresAt === undefined ? {} : { expiresAt }),
|
|
310
|
+
});
|
|
311
|
+
// The old key's grace window: it keeps working until whichever comes
|
|
312
|
+
// first, its own existing expiry or the chosen grace period — rotating
|
|
313
|
+
// a key that was about to expire tomorrow must not grant it another
|
|
314
|
+
// week.
|
|
315
|
+
const graceExpiry = new Date(currentTime + options.graceMs).toISOString();
|
|
316
|
+
const previousExpiresAt = row.expires_at !== null && row.expires_at < graceExpiry ? row.expires_at : graceExpiry;
|
|
317
|
+
await db.query(sql `
|
|
318
|
+
update ${table} set expires_at = ${previousExpiresAt}, superseded_by = ${issued.id}
|
|
319
|
+
where id = ${id}`);
|
|
320
|
+
return {
|
|
321
|
+
issued,
|
|
322
|
+
previous: fromRow({
|
|
323
|
+
...row,
|
|
324
|
+
expires_at: previousExpiresAt,
|
|
325
|
+
superseded_by: issued.id,
|
|
326
|
+
}),
|
|
327
|
+
};
|
|
328
|
+
},
|
|
329
|
+
usage: async (id) => {
|
|
330
|
+
const since30 = dayKey(now() - 29 * 24 * 60 * 60 * 1000);
|
|
331
|
+
const since7 = dayKey(now() - 6 * 24 * 60 * 60 * 1000);
|
|
332
|
+
const result = await db.query(sql `select day, count from ${usageTable} where key_id = ${id} and day >= ${since30}`);
|
|
333
|
+
let last7Days = 0;
|
|
334
|
+
let last30Days = 0;
|
|
335
|
+
for (const row of result.rows) {
|
|
336
|
+
const count = Number(row.count);
|
|
337
|
+
last30Days += count;
|
|
338
|
+
if (row.day >= since7)
|
|
339
|
+
last7Days += count;
|
|
340
|
+
}
|
|
341
|
+
return { last7Days, last30Days };
|
|
342
|
+
},
|
|
110
343
|
};
|
|
111
344
|
}
|
|
112
345
|
//# sourceMappingURL=api-keys.js.map
|
package/dist/api-keys.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../src/api-keys.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../src/api-keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type DatabaseHandle } from '@cogenta/core';
|
|
2
|
+
import type { AuditChainPoint, AuditLog } from './audit.js';
|
|
3
|
+
/**
|
|
4
|
+
* Fiche 21 task 3: "un bouton qu'on ne presse jamais ne protège de rien" — a
|
|
5
|
+
* scheduled counterpart to the manual "verify now" button, with somewhere to
|
|
6
|
+
* remember what it last found so the admin screen can say "checked 40
|
|
7
|
+
* seconds ago, chain intact" without replaying the whole log to answer that.
|
|
8
|
+
*
|
|
9
|
+
* Two things this store is careful about:
|
|
10
|
+
* - **Bounded.** Most runs are `audit.verifyRange(checkpoint)`, not
|
|
11
|
+
* `audit.verify()` — a million-entry log cannot be replayed on every
|
|
12
|
+
* tick. A full replay still happens periodically (`fullCheckIntervalMs`,
|
|
13
|
+
* a week by default) as the "vérification complète plus rare en secours"
|
|
14
|
+
* the fiche asks for, because an incremental check can only prove the
|
|
15
|
+
* *suffix* since the last checkpoint is intact — it does not re-read
|
|
16
|
+
* entries before it, so tampering with already-checkpointed history is
|
|
17
|
+
* only ever caught by a full run.
|
|
18
|
+
* - **Sticky once broken.** A break is not quietly retried away: once
|
|
19
|
+
* `state` is `'broken'`, only an explicit `check({ full: true })` (the
|
|
20
|
+
* admin's own "verify now", or an operator who has since pruned the bad
|
|
21
|
+
* prefix) attempts to move past it. Every other tick just re-stamps
|
|
22
|
+
* `lastCheckedAt` so "still checking" is visible without pretending the
|
|
23
|
+
* problem went away on its own.
|
|
24
|
+
*/
|
|
25
|
+
export type AuditIntegrityMode = 'incremental' | 'full';
|
|
26
|
+
export type AuditIntegrityState = 'never-run' | 'ok' | 'broken';
|
|
27
|
+
export interface AuditIntegrityStatus {
|
|
28
|
+
readonly state: AuditIntegrityState;
|
|
29
|
+
readonly checkpoint: AuditChainPoint | null;
|
|
30
|
+
readonly entriesChecked: number;
|
|
31
|
+
readonly lastCheckedAt: string | null;
|
|
32
|
+
readonly lastMode: AuditIntegrityMode | null;
|
|
33
|
+
readonly lastFullCheckedAt: string | null;
|
|
34
|
+
readonly brokenAt: string | null;
|
|
35
|
+
readonly brokenEntryId: string | null;
|
|
36
|
+
readonly brokenMessage: string | null;
|
|
37
|
+
}
|
|
38
|
+
export interface AuditIntegrityCheckResult {
|
|
39
|
+
readonly status: AuditIntegrityStatus;
|
|
40
|
+
/**
|
|
41
|
+
* `true` only on the run that first found the chain broken — the signal
|
|
42
|
+
* an alert-sending caller should use to actually send one, rather than
|
|
43
|
+
* re-alerting on every subsequent tick while the same break stands.
|
|
44
|
+
*/
|
|
45
|
+
readonly newlyBroken: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface AuditIntegrityStore {
|
|
48
|
+
/** Reads the last outcome without running a new check — cheap, one row. */
|
|
49
|
+
status(): Promise<AuditIntegrityStatus>;
|
|
50
|
+
/** Runs a check now. `full: true` forces a full replay instead of the usual incremental one — also what re-attempts past a `'broken'` state. */
|
|
51
|
+
check(options?: {
|
|
52
|
+
readonly full?: boolean;
|
|
53
|
+
}): Promise<AuditIntegrityCheckResult>;
|
|
54
|
+
}
|
|
55
|
+
export interface AuditIntegrityOptions {
|
|
56
|
+
readonly now?: () => number;
|
|
57
|
+
/** How rarely a full replay runs on its own. Default: 7 days. */
|
|
58
|
+
readonly fullCheckIntervalMs?: number;
|
|
59
|
+
}
|
|
60
|
+
export declare function createAuditIntegrityStore(db: DatabaseHandle, audit: Pick<AuditLog, 'verifyRange'>, options?: AuditIntegrityOptions): AuditIntegrityStore;
|
|
61
|
+
//# sourceMappingURL=audit-integrity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-integrity.d.ts","sourceRoot":"","sources":["../src/audit-integrity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAmC,MAAM,eAAe,CAAA;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAG3D;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,MAAM,CAAA;AACvD,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,IAAI,GAAG,QAAQ,CAAA;AAE/D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAA;IACnC,QAAQ,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;IAC3C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAC5C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CACtC;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAA;IACrC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,2EAA2E;IAC3E,MAAM,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAA;IACvC,gJAAgJ;IAChJ,KAAK,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAA;CACjF;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;CACtC;AA4DD,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,EACpC,OAAO,GAAE,qBAA0B,GAClC,mBAAmB,CAiGrB"}
|