@rayspec/auth-core 1.5.0
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/LICENSE +105 -0
- package/dist/api-key.d.ts +28 -0
- package/dist/api-key.d.ts.map +1 -0
- package/dist/api-key.js +56 -0
- package/dist/api-key.js.map +1 -0
- package/dist/audit.d.ts +26 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +11 -0
- package/dist/audit.js.map +1 -0
- package/dist/authz.d.ts +66 -0
- package/dist/authz.d.ts.map +1 -0
- package/dist/authz.js +112 -0
- package/dist/authz.js.map +1 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +42 -0
- package/dist/config.js.map +1 -0
- package/dist/deferrals.d.ts +33 -0
- package/dist/deferrals.d.ts.map +1 -0
- package/dist/deferrals.js +38 -0
- package/dist/deferrals.js.map +1 -0
- package/dist/dto.d.ts +337 -0
- package/dist/dto.d.ts.map +1 -0
- package/dist/dto.js +221 -0
- package/dist/dto.js.map +1 -0
- package/dist/email.d.ts +24 -0
- package/dist/email.d.ts.map +1 -0
- package/dist/email.js +46 -0
- package/dist/email.js.map +1 -0
- package/dist/errors.d.ts +87 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +104 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/invite.d.ts +18 -0
- package/dist/invite.d.ts.map +1 -0
- package/dist/invite.js +33 -0
- package/dist/invite.js.map +1 -0
- package/dist/password.d.ts +17 -0
- package/dist/password.d.ts.map +1 -0
- package/dist/password.js +46 -0
- package/dist/password.js.map +1 -0
- package/dist/rate-limit.d.ts +93 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +156 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/session.d.ts +18 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +41 -0
- package/dist/session.js.map +1 -0
- package/dist/tokens.d.ts +117 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +150 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, ALv2 Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-ALv2
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 Socialinsiders UG (haftungsbeschränkt)
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the Apache License, Version 2.0 that is effective on the second anniversary of
|
|
91
|
+
the date we make the Software available. On or after that date, you may use the
|
|
92
|
+
Software under the Apache License, Version 2.0, in which case the following
|
|
93
|
+
will apply:
|
|
94
|
+
|
|
95
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
96
|
+
this file except in compliance with the License.
|
|
97
|
+
|
|
98
|
+
You may obtain a copy of the License at
|
|
99
|
+
|
|
100
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
101
|
+
|
|
102
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
|
103
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
|
+
specific language governing permissions and limitations under the License.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the api-key pepper from the environment. THROWS if missing/blank — the boot-required
|
|
3
|
+
* contract (the pepper is in the boot-fails-closed set alongside the JWT signing key).
|
|
4
|
+
*/
|
|
5
|
+
export declare function getApiKeyPepper(): string;
|
|
6
|
+
/** HMAC-SHA256 the secret with the boot-required pepper; returns a lowercase hex digest. */
|
|
7
|
+
export declare function hashApiKey(secret: string, pepper?: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Constant-time verify of a presented secret against a stored HMAC hash. Recomputes the HMAC
|
|
10
|
+
* under the pepper and compares with timingSafeEqual (length-guarded so a wrong-length hash
|
|
11
|
+
* does not throw). Never branches observably on the hash contents.
|
|
12
|
+
*/
|
|
13
|
+
export declare function verifyApiKey(storedHash: string, secret: string, pepper?: string): boolean;
|
|
14
|
+
export interface MintedApiKey {
|
|
15
|
+
/** The full plaintext shown to the user ONCE: `${prefix}.${secret}`. Never stored. */
|
|
16
|
+
plaintext: string;
|
|
17
|
+
/** The public, indexed lookup handle (safe to store + query). */
|
|
18
|
+
prefix: string;
|
|
19
|
+
/** The HMAC hash to persist (key_hash). */
|
|
20
|
+
hash: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Mint a new api-key: a short public prefix + a ≥128-bit secret. Returns the one-time
|
|
24
|
+
* plaintext, the prefix to index, and the HMAC hash to store. The secret is 32 bytes
|
|
25
|
+
* (256-bit) of CSPRNG, base64url-encoded.
|
|
26
|
+
*/
|
|
27
|
+
export declare function mintApiKey(pepper?: string): MintedApiKey;
|
|
28
|
+
//# sourceMappingURL=api-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.d.ts","sourceRoot":"","sources":["../src/api-key.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAQxC;AAED,4FAA4F;AAC5F,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAA0B,GAAG,MAAM,CAErF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAA0B,GACjC,OAAO,CAMT;AAED,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,GAAE,MAA0B,GAAG,YAAY,CAK3E"}
|
package/dist/api-key.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API-key / client-secret hashing primitive — HMAC-SHA256 with a server PEPPER (a deliberate
|
|
3
|
+
* choice: fast on the hot per-request auth path, sound for ≥128-bit machine secrets;
|
|
4
|
+
* argon2id is reserved for low-entropy passwords). Uses node:crypto (stdlib) only.
|
|
5
|
+
*
|
|
6
|
+
* The pepper is REQUIRED-AT-BOOT: `getApiKeyPepper()` throws if RAYSPEC_API_KEY_PEPPER is
|
|
7
|
+
* unset/blank, so a misconfigured deploy fails closed instead of silently hashing keys with
|
|
8
|
+
* an empty pepper. (RAYSPEC_API_KEY_PEPPER is wired into ci.yml + the boot-fails-closed
|
|
9
|
+
* test; here the primitive is the boot-required seam.)
|
|
10
|
+
*
|
|
11
|
+
* Storage model: plaintext shown ONCE at mint; only the public `keyPrefix` (indexed for O(1)
|
|
12
|
+
* lookup) + the HMAC `keyHash` are persisted. Verification recomputes the HMAC and compares
|
|
13
|
+
* in constant time (timingSafeEqual) so a stored-hash comparison cannot be timed.
|
|
14
|
+
*/
|
|
15
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
16
|
+
const PEPPER_ENV = 'RAYSPEC_API_KEY_PEPPER';
|
|
17
|
+
/**
|
|
18
|
+
* Read the api-key pepper from the environment. THROWS if missing/blank — the boot-required
|
|
19
|
+
* contract (the pepper is in the boot-fails-closed set alongside the JWT signing key).
|
|
20
|
+
*/
|
|
21
|
+
export function getApiKeyPepper() {
|
|
22
|
+
const pepper = process.env[PEPPER_ENV];
|
|
23
|
+
if (!pepper || pepper.trim().length === 0) {
|
|
24
|
+
throw new Error(`${PEPPER_ENV} is required at boot (the api-key HMAC pepper). Refusing to start without it.`);
|
|
25
|
+
}
|
|
26
|
+
return pepper;
|
|
27
|
+
}
|
|
28
|
+
/** HMAC-SHA256 the secret with the boot-required pepper; returns a lowercase hex digest. */
|
|
29
|
+
export function hashApiKey(secret, pepper = getApiKeyPepper()) {
|
|
30
|
+
return createHmac('sha256', pepper).update(secret).digest('hex');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Constant-time verify of a presented secret against a stored HMAC hash. Recomputes the HMAC
|
|
34
|
+
* under the pepper and compares with timingSafeEqual (length-guarded so a wrong-length hash
|
|
35
|
+
* does not throw). Never branches observably on the hash contents.
|
|
36
|
+
*/
|
|
37
|
+
export function verifyApiKey(storedHash, secret, pepper = getApiKeyPepper()) {
|
|
38
|
+
const computed = hashApiKey(secret, pepper);
|
|
39
|
+
const a = Buffer.from(computed, 'utf8');
|
|
40
|
+
const b = Buffer.from(storedHash, 'utf8');
|
|
41
|
+
if (a.length !== b.length)
|
|
42
|
+
return false;
|
|
43
|
+
return timingSafeEqual(a, b);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Mint a new api-key: a short public prefix + a ≥128-bit secret. Returns the one-time
|
|
47
|
+
* plaintext, the prefix to index, and the HMAC hash to store. The secret is 32 bytes
|
|
48
|
+
* (256-bit) of CSPRNG, base64url-encoded.
|
|
49
|
+
*/
|
|
50
|
+
export function mintApiKey(pepper = getApiKeyPepper()) {
|
|
51
|
+
const prefix = `rk_${randomBytes(6).toString('base64url')}`;
|
|
52
|
+
const secret = randomBytes(32).toString('base64url'); // 256-bit
|
|
53
|
+
const plaintext = `${prefix}.${secret}`;
|
|
54
|
+
return { plaintext, prefix, hash: hashApiKey(secret, pepper) };
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=api-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.js","sourceRoot":"","sources":["../src/api-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEvE,MAAM,UAAU,GAAG,wBAAwB,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CACb,GAAG,UAAU,+EAA+E,CAC7F,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,SAAiB,eAAe,EAAE;IAC3E,OAAO,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,UAAkB,EAClB,MAAc,EACd,SAAiB,eAAe,EAAE;IAElC,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC1C,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,SAAiB,eAAe,EAAE;IAC3D,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;IAC5D,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU;IAChE,MAAM,SAAS,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;IACxC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;AACjE,CAAC"}
|
package/dist/audit.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neutral auth-audit EVENT shape (the DB write is api-auth's audit-store).
|
|
3
|
+
*
|
|
4
|
+
* Services return a list of AuditEvents describing what happened; the route layer commits them
|
|
5
|
+
* OUT-OF-BAND (their own unit of work) so a 404/rollback of the main request never drops a
|
|
6
|
+
* security event. Cross-tenant-denial events record the ACTOR's resolved tenant
|
|
7
|
+
* authoritatively + the attempted target as an opaque `targetHash` (never a target-org FK the
|
|
8
|
+
* actor does not own). Hashes/metadata only — no raw secrets/PII.
|
|
9
|
+
*/
|
|
10
|
+
/** The closed set of audited auth events. */
|
|
11
|
+
export type AuthEventName = 'register' | 'login' | 'login_failed' | 'refresh' | 'refresh_reuse_detected' | 'logout' | 'org_create' | 'org_switch' | 'org_member_add' | 'org_member_change' | 'org_invite_issued' | 'apikey_mint' | 'apikey_revoke' | 'apikey_auth_failed' | 'cross_tenant_denied' | 'authz_denied';
|
|
12
|
+
/** One audit event a service emits for the route to commit out-of-band. */
|
|
13
|
+
export interface AuditEvent {
|
|
14
|
+
event: AuthEventName;
|
|
15
|
+
/** The actor's resolved tenant (authoritative; null when pre-org). */
|
|
16
|
+
actorOrgId?: string | null;
|
|
17
|
+
/** The actor's user id (null for fully anonymous failures). */
|
|
18
|
+
actorUserId?: string | null;
|
|
19
|
+
/** Refresh family id, when relevant. */
|
|
20
|
+
familyId?: string | null;
|
|
21
|
+
/** Opaque hash of an attempted cross-tenant target (NEVER a target-org FK). */
|
|
22
|
+
targetHash?: string | null;
|
|
23
|
+
/** Extra non-PII metadata (hashes/flags only). */
|
|
24
|
+
meta?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,6CAA6C;AAC7C,MAAM,MAAM,aAAa,GACrB,UAAU,GACV,OAAO,GACP,cAAc,GACd,SAAS,GACT,wBAAwB,GACxB,QAAQ,GACR,YAAY,GACZ,YAAY,GACZ,gBAAgB,GAChB,mBAAmB,GACnB,mBAAmB,GACnB,aAAa,GACb,eAAe,GACf,oBAAoB,GACpB,qBAAqB,GACrB,cAAc,CAAC;AAEnB,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,aAAa,CAAC;IACrB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC"}
|
package/dist/audit.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neutral auth-audit EVENT shape (the DB write is api-auth's audit-store).
|
|
3
|
+
*
|
|
4
|
+
* Services return a list of AuditEvents describing what happened; the route layer commits them
|
|
5
|
+
* OUT-OF-BAND (their own unit of work) so a 404/rollback of the main request never drops a
|
|
6
|
+
* security event. Cross-tenant-denial events record the ACTOR's resolved tenant
|
|
7
|
+
* authoritatively + the attempted target as an opaque `targetHash` (never a target-org FK the
|
|
8
|
+
* actor does not own). Hashes/metadata only — no raw secrets/PII.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
package/dist/authz.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization — the static ROLE_PERMISSIONS table + the authorize() contract.
|
|
3
|
+
* DB-free here; the LIVE membership lookup is injected by the caller (api-auth holds the
|
|
4
|
+
* IdentityStore). A general policy engine is overkill here — a static code constant.
|
|
5
|
+
*
|
|
6
|
+
* THE CRITICAL SPLIT: for SENSITIVE/MUTATING permissions the caller MUST resolve the role from a
|
|
7
|
+
* LIVE membership lookup (TenantDb/IdentityStore), NOT the JWT claim — so a revoked/demoted
|
|
8
|
+
* principal is denied within the ~8-min token TTL (the TTL is a bounded READ-staleness risk, not
|
|
9
|
+
* a write-privilege bypass). `isSensitive(permission)` tells the caller which path to take.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The closed set of permissions.
|
|
13
|
+
*
|
|
14
|
+
* `store:read`/`store:write` are the GENERIC, product-agnostic CRUD permissions a
|
|
15
|
+
* declared `api` route over a materialized store is gated by — read ops (list/get) require
|
|
16
|
+
* `store:read`, mutating ops (create/update/delete) require `store:write`. They are NOT
|
|
17
|
+
* store-specific (no `notebooks:*`): the platform stays product-free and one pair gates every
|
|
18
|
+
* declared store route. `store:write` is SENSITIVE (a live-membership recheck on every mutation,
|
|
19
|
+
* mirroring why a write must never trust a stale JWT claim — see the SENSITIVE set below).
|
|
20
|
+
*/
|
|
21
|
+
export type Permission = 'agent:run' | 'agent:read' | 'store:read' | 'store:write' | 'org:read' | 'org:member:add' | 'org:member:change' | 'org:switch' | 'apikey:read' | 'apikey:mint' | 'apikey:revoke';
|
|
22
|
+
/** Roles → the permissions they grant (static; owner ⊇ admin ⊇ member). */
|
|
23
|
+
export declare const ROLE_PERMISSIONS: Record<string, Permission[]>;
|
|
24
|
+
/** True if `permission` requires a live membership check (never trust the JWT claim). */
|
|
25
|
+
export declare function isSensitive(permission: Permission): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* The CLOSED set of permissions an api-key principal may EVER exercise — its scopes ∩ this set. This
|
|
28
|
+
* is the SINGLE source of truth for both `authorize()` (the runtime gate) and the `ApiKeyScope` DTO
|
|
29
|
+
* enum (the mint-time grammar; dto.ts derives its enum from this so the two can never silently
|
|
30
|
+
* desync). It is purely scope-driven (an api-key is a machine principal with NO membership/role).
|
|
31
|
+
*
|
|
32
|
+
* What is DELIBERATELY ABSENT is the security boundary: the org-MANAGEMENT sensitive ops
|
|
33
|
+
* (`apikey:mint`, `apikey:revoke`, `org:member:change`, `org:switch`) are NOT here, so an api-key can
|
|
34
|
+
* never perform them regardless of scope. `store:write` IS here (the programmatic/agency consumer
|
|
35
|
+
* model — a desktop app / automation POSTing rows authenticates via an org-scoped key); it is also
|
|
36
|
+
* SENSITIVE, but for an api-key the KEY is the live credential, so requirePermission falls api-keys
|
|
37
|
+
* through to authorize(), where this set ∩ scopes is the gate (no stale-claim recheck applies).
|
|
38
|
+
*
|
|
39
|
+
* Frozen so a caller cannot mutate the shared authority; `authorize()` reads it via `.includes`.
|
|
40
|
+
*/
|
|
41
|
+
export declare const API_KEY_GRANTABLE: readonly Permission[];
|
|
42
|
+
/** Does `role` grant `permission` per the static table? */
|
|
43
|
+
export declare function roleGrants(role: string | undefined, permission: Permission): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* The principal an authorize() call evaluates. For sensitive permissions the caller supplies the
|
|
46
|
+
* LIVE role (resolved via the membership store); for read-mostly permissions the JWT-claim role
|
|
47
|
+
* is acceptable.
|
|
48
|
+
*/
|
|
49
|
+
export interface AuthzPrincipal {
|
|
50
|
+
userId?: string;
|
|
51
|
+
/** The role to evaluate against — LIVE for sensitive ops, claim-derived otherwise. */
|
|
52
|
+
role?: string;
|
|
53
|
+
/** API-key scopes (an api-key principal is also gated by its granted scopes). */
|
|
54
|
+
scopes?: string[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Decide whether `principal` may perform `permission` in the (already tenant-resolved) org.
|
|
58
|
+
*
|
|
59
|
+
* Pure decision: the caller has ALREADY (a) asserted the principal belongs to the tenant and
|
|
60
|
+
* (b) for a sensitive permission, set `principal.role` from a LIVE membership lookup. For an
|
|
61
|
+
* api-key principal, the permission must ALSO be within its granted scopes (scope ∩ role).
|
|
62
|
+
*/
|
|
63
|
+
export declare function authorize(principal: AuthzPrincipal, permission: Permission, opts?: {
|
|
64
|
+
isApiKey?: boolean;
|
|
65
|
+
}): boolean;
|
|
66
|
+
//# sourceMappingURL=authz.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz.d.ts","sourceRoot":"","sources":["../src/authz.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAClB,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,aAAa,GACb,UAAU,GACV,gBAAgB,GAChB,mBAAmB,GACnB,YAAY,GACZ,aAAa,GACb,aAAa,GACb,eAAe,CAAC;AAEpB,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CA0BzD,CAAC;AAmBF,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAE3D;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,UAAU,EAOjD,CAAC;AAEH,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAGpF;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,SAAS,EAAE,cAAc,EACzB,UAAU,EAAE,UAAU,EACtB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAChC,OAAO,CAeT"}
|
package/dist/authz.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization — the static ROLE_PERMISSIONS table + the authorize() contract.
|
|
3
|
+
* DB-free here; the LIVE membership lookup is injected by the caller (api-auth holds the
|
|
4
|
+
* IdentityStore). A general policy engine is overkill here — a static code constant.
|
|
5
|
+
*
|
|
6
|
+
* THE CRITICAL SPLIT: for SENSITIVE/MUTATING permissions the caller MUST resolve the role from a
|
|
7
|
+
* LIVE membership lookup (TenantDb/IdentityStore), NOT the JWT claim — so a revoked/demoted
|
|
8
|
+
* principal is denied within the ~8-min token TTL (the TTL is a bounded READ-staleness risk, not
|
|
9
|
+
* a write-privilege bypass). `isSensitive(permission)` tells the caller which path to take.
|
|
10
|
+
*/
|
|
11
|
+
/** Roles → the permissions they grant (static; owner ⊇ admin ⊇ member). */
|
|
12
|
+
export const ROLE_PERMISSIONS = {
|
|
13
|
+
owner: [
|
|
14
|
+
'agent:run',
|
|
15
|
+
'agent:read',
|
|
16
|
+
'store:read',
|
|
17
|
+
'store:write',
|
|
18
|
+
'org:read',
|
|
19
|
+
'org:member:add',
|
|
20
|
+
'org:member:change',
|
|
21
|
+
'org:switch',
|
|
22
|
+
'apikey:read',
|
|
23
|
+
'apikey:mint',
|
|
24
|
+
'apikey:revoke',
|
|
25
|
+
],
|
|
26
|
+
admin: [
|
|
27
|
+
'agent:run',
|
|
28
|
+
'agent:read',
|
|
29
|
+
'store:read',
|
|
30
|
+
'store:write',
|
|
31
|
+
'org:read',
|
|
32
|
+
'org:switch',
|
|
33
|
+
'apikey:read',
|
|
34
|
+
'apikey:mint',
|
|
35
|
+
'apikey:revoke',
|
|
36
|
+
],
|
|
37
|
+
member: ['agent:run', 'agent:read', 'store:read', 'store:write', 'org:read', 'org:switch'],
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* SENSITIVE/MUTATING permissions — for these the caller MUST do a LIVE membership+role lookup and
|
|
41
|
+
* MUST NOT trust the JWT claim. (org switch is included: it re-mints a privileged token, so it
|
|
42
|
+
* re-checks live.)
|
|
43
|
+
*/
|
|
44
|
+
const SENSITIVE = new Set([
|
|
45
|
+
'apikey:mint',
|
|
46
|
+
'apikey:revoke',
|
|
47
|
+
'org:member:add',
|
|
48
|
+
'org:member:change',
|
|
49
|
+
'org:switch',
|
|
50
|
+
// A declared-store MUTATION (create/update/delete) must re-check live membership — a revoked/
|
|
51
|
+
// demoted principal must not write product data on a stale JWT claim (the same write-bypass
|
|
52
|
+
// reasoning that makes the api-key/org-management ops sensitive). store:read stays claim-trusted.
|
|
53
|
+
'store:write',
|
|
54
|
+
]);
|
|
55
|
+
/** True if `permission` requires a live membership check (never trust the JWT claim). */
|
|
56
|
+
export function isSensitive(permission) {
|
|
57
|
+
return SENSITIVE.has(permission);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The CLOSED set of permissions an api-key principal may EVER exercise — its scopes ∩ this set. This
|
|
61
|
+
* is the SINGLE source of truth for both `authorize()` (the runtime gate) and the `ApiKeyScope` DTO
|
|
62
|
+
* enum (the mint-time grammar; dto.ts derives its enum from this so the two can never silently
|
|
63
|
+
* desync). It is purely scope-driven (an api-key is a machine principal with NO membership/role).
|
|
64
|
+
*
|
|
65
|
+
* What is DELIBERATELY ABSENT is the security boundary: the org-MANAGEMENT sensitive ops
|
|
66
|
+
* (`apikey:mint`, `apikey:revoke`, `org:member:change`, `org:switch`) are NOT here, so an api-key can
|
|
67
|
+
* never perform them regardless of scope. `store:write` IS here (the programmatic/agency consumer
|
|
68
|
+
* model — a desktop app / automation POSTing rows authenticates via an org-scoped key); it is also
|
|
69
|
+
* SENSITIVE, but for an api-key the KEY is the live credential, so requirePermission falls api-keys
|
|
70
|
+
* through to authorize(), where this set ∩ scopes is the gate (no stale-claim recheck applies).
|
|
71
|
+
*
|
|
72
|
+
* Frozen so a caller cannot mutate the shared authority; `authorize()` reads it via `.includes`.
|
|
73
|
+
*/
|
|
74
|
+
export const API_KEY_GRANTABLE = Object.freeze([
|
|
75
|
+
'agent:run',
|
|
76
|
+
'agent:read',
|
|
77
|
+
'store:read',
|
|
78
|
+
'store:write',
|
|
79
|
+
'org:read',
|
|
80
|
+
'apikey:read',
|
|
81
|
+
]);
|
|
82
|
+
/** Does `role` grant `permission` per the static table? */
|
|
83
|
+
export function roleGrants(role, permission) {
|
|
84
|
+
if (!role)
|
|
85
|
+
return false;
|
|
86
|
+
return (ROLE_PERMISSIONS[role] ?? []).includes(permission);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Decide whether `principal` may perform `permission` in the (already tenant-resolved) org.
|
|
90
|
+
*
|
|
91
|
+
* Pure decision: the caller has ALREADY (a) asserted the principal belongs to the tenant and
|
|
92
|
+
* (b) for a sensitive permission, set `principal.role` from a LIVE membership lookup. For an
|
|
93
|
+
* api-key principal, the permission must ALSO be within its granted scopes (scope ∩ role).
|
|
94
|
+
*/
|
|
95
|
+
export function authorize(principal, permission, opts = {}) {
|
|
96
|
+
// The permissions an api-key may EVER exercise — purely its scopes, NO role (an api-key is a
|
|
97
|
+
// machine principal with no membership). Sensitive org-management ops are NOT in this set, so
|
|
98
|
+
// an api-key can never mint/revoke keys or change membership/switch org regardless of scope.
|
|
99
|
+
if (opts.isApiKey) {
|
|
100
|
+
// The api-key-grantable SET is the single shared authority (API_KEY_GRANTABLE above): an api-key
|
|
101
|
+
// may exercise ONLY a permission that is BOTH in that set AND in its scopes. The org-MANAGEMENT
|
|
102
|
+
// sensitive ops (apikey:mint/revoke, org:member:change, org:switch) are absent from the set, so
|
|
103
|
+
// an api-key can never perform them regardless of scope. (store:write IS in the set — the
|
|
104
|
+
// programmatic/agency consumer model — see API_KEY_GRANTABLE's rationale.)
|
|
105
|
+
if (!API_KEY_GRANTABLE.includes(permission))
|
|
106
|
+
return false;
|
|
107
|
+
return (principal.scopes ?? []).includes(permission);
|
|
108
|
+
}
|
|
109
|
+
// User principals: the role (LIVE for sensitive ops, claim-derived otherwise) must grant it.
|
|
110
|
+
return roleGrants(principal.role, permission);
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=authz.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz.js","sourceRoot":"","sources":["../src/authz.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAyBH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAAiC;IAC5D,KAAK,EAAE;QACL,WAAW;QACX,YAAY;QACZ,YAAY;QACZ,aAAa;QACb,UAAU;QACV,gBAAgB;QAChB,mBAAmB;QACnB,YAAY;QACZ,aAAa;QACb,aAAa;QACb,eAAe;KAChB;IACD,KAAK,EAAE;QACL,WAAW;QACX,YAAY;QACZ,YAAY;QACZ,aAAa;QACb,UAAU;QACV,YAAY;QACZ,aAAa;QACb,aAAa;QACb,eAAe;KAChB;IACD,MAAM,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,CAAC;CAC3F,CAAC;AAEF;;;;GAIG;AACH,MAAM,SAAS,GAAG,IAAI,GAAG,CAAa;IACpC,aAAa;IACb,eAAe;IACf,gBAAgB;IAChB,mBAAmB;IACnB,YAAY;IACZ,8FAA8F;IAC9F,4FAA4F;IAC5F,kGAAkG;IAClG,aAAa;CACd,CAAC,CAAC;AAEH,yFAAyF;AACzF,MAAM,UAAU,WAAW,CAAC,UAAsB;IAChD,OAAO,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA0B,MAAM,CAAC,MAAM,CAAC;IACpE,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,UAAU;IACV,aAAa;CACd,CAAC,CAAC;AAEH,2DAA2D;AAC3D,MAAM,UAAU,UAAU,CAAC,IAAwB,EAAE,UAAsB;IACzE,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AAC7D,CAAC;AAeD;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CACvB,SAAyB,EACzB,UAAsB,EACtB,OAA+B,EAAE;IAEjC,6FAA6F;IAC7F,8FAA8F;IAC9F,6FAA6F;IAC7F,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,iGAAiG;QACjG,gGAAgG;QAChG,gGAAgG;QAChG,0FAA0F;QAC1F,2EAA2E;QAC3E,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,UAAU,CAAC;YAAE,OAAO,KAAK,CAAC;QAC1D,OAAO,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACvD,CAAC;IACD,6FAA6F;IAC7F,OAAO,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAChD,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot-required secret configuration (no plaintext secrets, fail-closed at boot).
|
|
3
|
+
*
|
|
4
|
+
* Two secrets are REQUIRED-AT-BOOT and live ONLY in env/secret-manager, never in the DB or git:
|
|
5
|
+
* - RAYSPEC_JWT_SIGNING_KEY — the PKCS#8 PEM private key the access-token signer uses.
|
|
6
|
+
* - RAYSPEC_API_KEY_PEPPER — the HMAC pepper for api-key/client-secret hashing (api-key.ts).
|
|
7
|
+
*
|
|
8
|
+
* `assertBootSecrets()` throws if either is missing/blank, so a misconfigured deploy fails to
|
|
9
|
+
* start rather than silently minting unsignable tokens or hashing keys with an empty pepper. The
|
|
10
|
+
* api-auth app calls it at construction; the boot-fails-closed test asserts it throws when a
|
|
11
|
+
* secret is absent.
|
|
12
|
+
*/
|
|
13
|
+
export declare const JWT_SIGNING_KEY_ENV = "RAYSPEC_JWT_SIGNING_KEY";
|
|
14
|
+
export declare const API_KEY_PEPPER_ENV = "RAYSPEC_API_KEY_PEPPER";
|
|
15
|
+
/** Read the JWT signing key PEM; THROWS if unset/blank (boot-required). */
|
|
16
|
+
export declare function getJwtSigningKeyPem(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Assert BOTH boot-required secrets are present. Throws a single combined error listing every
|
|
19
|
+
* missing one. Call this once at app construction (fail-closed boot). Does NOT validate the key
|
|
20
|
+
* material itself — the token module does that lazily on first sign (which also fails closed).
|
|
21
|
+
*/
|
|
22
|
+
export declare function assertBootSecrets(env?: NodeJS.ProcessEnv): void;
|
|
23
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAC7D,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAE3D,2EAA2E;AAC3E,wBAAgB,mBAAmB,IAAI,MAAM,CAS5C;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,IAAI,CAc5E"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot-required secret configuration (no plaintext secrets, fail-closed at boot).
|
|
3
|
+
*
|
|
4
|
+
* Two secrets are REQUIRED-AT-BOOT and live ONLY in env/secret-manager, never in the DB or git:
|
|
5
|
+
* - RAYSPEC_JWT_SIGNING_KEY — the PKCS#8 PEM private key the access-token signer uses.
|
|
6
|
+
* - RAYSPEC_API_KEY_PEPPER — the HMAC pepper for api-key/client-secret hashing (api-key.ts).
|
|
7
|
+
*
|
|
8
|
+
* `assertBootSecrets()` throws if either is missing/blank, so a misconfigured deploy fails to
|
|
9
|
+
* start rather than silently minting unsignable tokens or hashing keys with an empty pepper. The
|
|
10
|
+
* api-auth app calls it at construction; the boot-fails-closed test asserts it throws when a
|
|
11
|
+
* secret is absent.
|
|
12
|
+
*/
|
|
13
|
+
export const JWT_SIGNING_KEY_ENV = 'RAYSPEC_JWT_SIGNING_KEY';
|
|
14
|
+
export const API_KEY_PEPPER_ENV = 'RAYSPEC_API_KEY_PEPPER';
|
|
15
|
+
/** Read the JWT signing key PEM; THROWS if unset/blank (boot-required). */
|
|
16
|
+
export function getJwtSigningKeyPem() {
|
|
17
|
+
const pem = process.env[JWT_SIGNING_KEY_ENV];
|
|
18
|
+
if (!pem || pem.trim().length === 0) {
|
|
19
|
+
throw new Error(`${JWT_SIGNING_KEY_ENV} is required at boot (the JWT signing key, PKCS#8 PEM). ` +
|
|
20
|
+
'Refusing to start without it.');
|
|
21
|
+
}
|
|
22
|
+
return pem;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Assert BOTH boot-required secrets are present. Throws a single combined error listing every
|
|
26
|
+
* missing one. Call this once at app construction (fail-closed boot). Does NOT validate the key
|
|
27
|
+
* material itself — the token module does that lazily on first sign (which also fails closed).
|
|
28
|
+
*/
|
|
29
|
+
export function assertBootSecrets(env = process.env) {
|
|
30
|
+
const missing = [];
|
|
31
|
+
if (!env[JWT_SIGNING_KEY_ENV] || env[JWT_SIGNING_KEY_ENV]?.trim().length === 0) {
|
|
32
|
+
missing.push(JWT_SIGNING_KEY_ENV);
|
|
33
|
+
}
|
|
34
|
+
if (!env[API_KEY_PEPPER_ENV] || env[API_KEY_PEPPER_ENV]?.trim().length === 0) {
|
|
35
|
+
missing.push(API_KEY_PEPPER_ENV);
|
|
36
|
+
}
|
|
37
|
+
if (missing.length > 0) {
|
|
38
|
+
throw new Error(`Boot-required secret(s) missing: ${missing.join(', ')}. ` +
|
|
39
|
+
'These live in env/secret-manager only (never DB/git). Refusing to start (fail-closed).');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAC7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;AAE3D,2EAA2E;AAC3E,MAAM,UAAU,mBAAmB;IACjC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC7C,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,GAAG,mBAAmB,0DAA0D;YAC9E,+BAA+B,CAClC,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACpE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,GAAG,CAAC,mBAAmB,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/E,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,GAAG,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7E,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,oCAAoC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YACxD,wFAAwF,CAC3F,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The before-external-exposure deferral line — EXPLICIT, in code, so the boundary is auditable.
|
|
3
|
+
*
|
|
4
|
+
* The following are NOT shipped yet (they are gated to BEFORE external/SaaS exposure). The
|
|
5
|
+
* platform deliberately ships the forward-compatible HOOKS for each so enabling them is a
|
|
6
|
+
* config/policy change, not a re-architecture:
|
|
7
|
+
*
|
|
8
|
+
* - KMS per-tenant DEK + crypto-shred → hook: orgs.region + ON DELETE CASCADE purge of
|
|
9
|
+
* the at-rest raw-PII columns: the jsonb
|
|
10
|
+
* payloads conversation_items.payload, runs.output,
|
|
11
|
+
* journal_steps.output, run_events.data + the text
|
|
12
|
+
* runs.final_text. (NOT conversation_items.content
|
|
13
|
+
* — deprecated legacy column, part data now lives in
|
|
14
|
+
* payload; see schema.ts.)
|
|
15
|
+
* - Postgres Row-Level Security (RLS) → hook: the TenantDb.transaction() set_config
|
|
16
|
+
* app.current_tenant GUC seam (TENANT_GUC) — RLS
|
|
17
|
+
* policies bind to an already-populated GUC with
|
|
18
|
+
* zero call-site churn.
|
|
19
|
+
* - DPoP-bound access tokens → hook: the jose access-token module (add a cnf
|
|
20
|
+
* claim + DPoP proof verification).
|
|
21
|
+
* - Per-tenant tool/handler sandbox → hook: the neutral Backend boundary + per-tenant
|
|
22
|
+
* CLAUDE_CONFIG_DIR isolation already in the
|
|
23
|
+
* Anthropic adapter.
|
|
24
|
+
* - jti denylist (immediate token revocation) → hook: the jti claim is minted on every token;
|
|
25
|
+
* the opaque session is the revocation point
|
|
26
|
+
* (the ~8-min TTL bounds stateless-read staleness).
|
|
27
|
+
*
|
|
28
|
+
* This constant is exported so a grep for `SECURITY_DEFERRALS_10B` finds the single source of the
|
|
29
|
+
* deferral record; it is referenced by the docs and intentionally never used as runtime logic.
|
|
30
|
+
*/
|
|
31
|
+
export declare const SECURITY_DEFERRALS_10B: readonly ["kms-per-tenant-dek-crypto-shred", "postgres-row-level-security", "dpop-bound-tokens", "per-tenant-tool-sandbox", "jti-denylist"];
|
|
32
|
+
export type SecurityDeferral10B = (typeof SECURITY_DEFERRALS_10B)[number];
|
|
33
|
+
//# sourceMappingURL=deferrals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferrals.d.ts","sourceRoot":"","sources":["../src/deferrals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,sBAAsB,6IAMzB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The before-external-exposure deferral line — EXPLICIT, in code, so the boundary is auditable.
|
|
3
|
+
*
|
|
4
|
+
* The following are NOT shipped yet (they are gated to BEFORE external/SaaS exposure). The
|
|
5
|
+
* platform deliberately ships the forward-compatible HOOKS for each so enabling them is a
|
|
6
|
+
* config/policy change, not a re-architecture:
|
|
7
|
+
*
|
|
8
|
+
* - KMS per-tenant DEK + crypto-shred → hook: orgs.region + ON DELETE CASCADE purge of
|
|
9
|
+
* the at-rest raw-PII columns: the jsonb
|
|
10
|
+
* payloads conversation_items.payload, runs.output,
|
|
11
|
+
* journal_steps.output, run_events.data + the text
|
|
12
|
+
* runs.final_text. (NOT conversation_items.content
|
|
13
|
+
* — deprecated legacy column, part data now lives in
|
|
14
|
+
* payload; see schema.ts.)
|
|
15
|
+
* - Postgres Row-Level Security (RLS) → hook: the TenantDb.transaction() set_config
|
|
16
|
+
* app.current_tenant GUC seam (TENANT_GUC) — RLS
|
|
17
|
+
* policies bind to an already-populated GUC with
|
|
18
|
+
* zero call-site churn.
|
|
19
|
+
* - DPoP-bound access tokens → hook: the jose access-token module (add a cnf
|
|
20
|
+
* claim + DPoP proof verification).
|
|
21
|
+
* - Per-tenant tool/handler sandbox → hook: the neutral Backend boundary + per-tenant
|
|
22
|
+
* CLAUDE_CONFIG_DIR isolation already in the
|
|
23
|
+
* Anthropic adapter.
|
|
24
|
+
* - jti denylist (immediate token revocation) → hook: the jti claim is minted on every token;
|
|
25
|
+
* the opaque session is the revocation point
|
|
26
|
+
* (the ~8-min TTL bounds stateless-read staleness).
|
|
27
|
+
*
|
|
28
|
+
* This constant is exported so a grep for `SECURITY_DEFERRALS_10B` finds the single source of the
|
|
29
|
+
* deferral record; it is referenced by the docs and intentionally never used as runtime logic.
|
|
30
|
+
*/
|
|
31
|
+
export const SECURITY_DEFERRALS_10B = [
|
|
32
|
+
'kms-per-tenant-dek-crypto-shred',
|
|
33
|
+
'postgres-row-level-security',
|
|
34
|
+
'dpop-bound-tokens',
|
|
35
|
+
'per-tenant-tool-sandbox',
|
|
36
|
+
'jti-denylist',
|
|
37
|
+
];
|
|
38
|
+
//# sourceMappingURL=deferrals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferrals.js","sourceRoot":"","sources":["../src/deferrals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,iCAAiC;IACjC,6BAA6B;IAC7B,mBAAmB;IACnB,yBAAyB;IACzB,cAAc;CACN,CAAC"}
|