@zauso-ai/capstan-auth 1.0.0-beta.4 → 1.0.0-beta.6
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/dpop.d.ts +46 -0
- package/dist/dpop.d.ts.map +1 -0
- package/dist/dpop.js +259 -0
- package/dist/dpop.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +6 -3
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +71 -27
- package/dist/middleware.js.map +1 -1
- package/dist/store.d.ts +25 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +33 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +11 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/workload.d.ts +46 -0
- package/dist/workload.d.ts.map +1 -0
- package/dist/workload.js +227 -0
- package/dist/workload.js.map +1 -0
- package/package.json +3 -2
package/dist/dpop.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { KeyValueStore } from "./store.js";
|
|
2
|
+
/**
|
|
3
|
+
* Result of a successful DPoP proof validation.
|
|
4
|
+
*/
|
|
5
|
+
export interface DpopValidationResult {
|
|
6
|
+
/** Base64url-encoded JWK thumbprint (RFC 7638) of the proof key. */
|
|
7
|
+
thumbprint: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Replace the default in-memory DPoP replay store with a custom implementation.
|
|
11
|
+
*
|
|
12
|
+
* Call this at application startup before any DPoP proofs are validated.
|
|
13
|
+
*/
|
|
14
|
+
export declare function setDpopReplayStore(store: KeyValueStore<true>): void;
|
|
15
|
+
/**
|
|
16
|
+
* Validate a DPoP proof JWT per RFC 9449.
|
|
17
|
+
*
|
|
18
|
+
* The proof is a compact-serialized JWT whose header contains the public key
|
|
19
|
+
* (`jwk`) used to sign the proof. The function:
|
|
20
|
+
*
|
|
21
|
+
* 1. Parses the JOSE header and verifies `typ: "dpop+jwt"` and a supported `alg`.
|
|
22
|
+
* 2. Extracts the embedded `jwk` and imports it via Web Crypto.
|
|
23
|
+
* 3. Verifies the JWT signature using the imported key.
|
|
24
|
+
* 4. Validates required claims:
|
|
25
|
+
* - `htm` — must match the provided HTTP method.
|
|
26
|
+
* - `htu` — must match the provided HTTP URI (scheme + authority + path).
|
|
27
|
+
* - `iat` — must be within the acceptable time window.
|
|
28
|
+
* - `jti` — must be unique (checked against an in-memory replay cache).
|
|
29
|
+
* 5. If an `accessToken` is provided, verifies the `ath` (access token hash)
|
|
30
|
+
* claim matches the SHA-256 hash of the token.
|
|
31
|
+
*
|
|
32
|
+
* Returns the JWK thumbprint on success, or `null` if validation fails.
|
|
33
|
+
*
|
|
34
|
+
* @param proof - The DPoP proof from the `DPoP` request header.
|
|
35
|
+
* @param method - The HTTP method of the request (e.g. "POST").
|
|
36
|
+
* @param url - The full request URL.
|
|
37
|
+
* @param accessToken - Optional access token to verify `ath` binding.
|
|
38
|
+
*/
|
|
39
|
+
export declare function validateDpopProof(proof: string, method: string, url: string, accessToken?: string): Promise<DpopValidationResult | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Clear the DPoP JTI replay cache.
|
|
42
|
+
*
|
|
43
|
+
* Useful for tests so state does not leak between test cases.
|
|
44
|
+
*/
|
|
45
|
+
export declare function clearDpopReplayCache(): Promise<void>;
|
|
46
|
+
//# sourceMappingURL=dpop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dpop.d.ts","sourceRoot":"","sources":["../src/dpop.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAOhD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAC;CACpB;AAoBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,IAAI,CAEnE;AAmGD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA4JtC;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAE1D"}
|
package/dist/dpop.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { MemoryStore } from "./store.js";
|
|
3
|
+
/** Maximum age (in seconds) for a DPoP proof's `iat` claim. */
|
|
4
|
+
const MAX_PROOF_AGE_SECONDS = 300; // 5 minutes
|
|
5
|
+
/** Minimum age (in seconds) — reject proofs from the far future. */
|
|
6
|
+
const MAX_CLOCK_SKEW_SECONDS = 60;
|
|
7
|
+
/** TTL for JTI replay cache entries (proof age + clock skew). */
|
|
8
|
+
const JTI_TTL_MS = (MAX_PROOF_AGE_SECONDS + MAX_CLOCK_SKEW_SECONDS) * 1000;
|
|
9
|
+
/**
|
|
10
|
+
* Pluggable store for tracking recently seen `jti` values to prevent replay.
|
|
11
|
+
*
|
|
12
|
+
* Each entry stores `true` as a sentinel value with a TTL equal to the proof
|
|
13
|
+
* window. The `MemoryStore` default lazily expires entries on access; custom
|
|
14
|
+
* implementations (e.g. Redis) can use native TTL support.
|
|
15
|
+
*/
|
|
16
|
+
let dpopReplayStore = new MemoryStore();
|
|
17
|
+
/**
|
|
18
|
+
* Replace the default in-memory DPoP replay store with a custom implementation.
|
|
19
|
+
*
|
|
20
|
+
* Call this at application startup before any DPoP proofs are validated.
|
|
21
|
+
*/
|
|
22
|
+
export function setDpopReplayStore(store) {
|
|
23
|
+
dpopReplayStore = store;
|
|
24
|
+
}
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Base64url helpers
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
function base64urlDecode(str) {
|
|
29
|
+
// Convert base64url to standard base64.
|
|
30
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
31
|
+
// Pad with '=' to make length a multiple of 4.
|
|
32
|
+
while (base64.length % 4 !== 0) {
|
|
33
|
+
base64 += "=";
|
|
34
|
+
}
|
|
35
|
+
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
36
|
+
}
|
|
37
|
+
function base64urlEncode(buffer) {
|
|
38
|
+
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
39
|
+
let binary = "";
|
|
40
|
+
for (const b of bytes) {
|
|
41
|
+
binary += String.fromCharCode(b);
|
|
42
|
+
}
|
|
43
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// JWK Thumbprint (RFC 7638)
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
/**
|
|
49
|
+
* Compute the JWK thumbprint per RFC 7638.
|
|
50
|
+
*
|
|
51
|
+
* For RSA keys the required members are: e, kty, n
|
|
52
|
+
* For EC keys: crv, kty, x, y
|
|
53
|
+
*/
|
|
54
|
+
function computeJwkThumbprint(jwk) {
|
|
55
|
+
let canonicalMembers;
|
|
56
|
+
if (jwk.kty === "RSA") {
|
|
57
|
+
canonicalMembers = { e: jwk.e, kty: jwk.kty, n: jwk.n };
|
|
58
|
+
}
|
|
59
|
+
else if (jwk.kty === "EC") {
|
|
60
|
+
canonicalMembers = {
|
|
61
|
+
crv: jwk.crv,
|
|
62
|
+
kty: jwk.kty,
|
|
63
|
+
x: jwk.x,
|
|
64
|
+
y: jwk.y,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
throw new Error(`Unsupported key type: ${jwk.kty}`);
|
|
69
|
+
}
|
|
70
|
+
// Lexicographic ordering of keys (JSON Canonicalization is just sorted keys
|
|
71
|
+
// for the required members).
|
|
72
|
+
const sortedKeys = Object.keys(canonicalMembers).sort();
|
|
73
|
+
const json = "{" +
|
|
74
|
+
sortedKeys
|
|
75
|
+
.map((k) => `${JSON.stringify(k)}:${JSON.stringify(canonicalMembers[k])}`)
|
|
76
|
+
.join(",") +
|
|
77
|
+
"}";
|
|
78
|
+
const hash = createHash("sha256").update(json).digest();
|
|
79
|
+
return base64urlEncode(hash);
|
|
80
|
+
}
|
|
81
|
+
function mapAlgorithm(alg) {
|
|
82
|
+
switch (alg) {
|
|
83
|
+
case "RS256":
|
|
84
|
+
return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
|
|
85
|
+
case "RS384":
|
|
86
|
+
return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-384" };
|
|
87
|
+
case "RS512":
|
|
88
|
+
return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-512" };
|
|
89
|
+
case "ES256":
|
|
90
|
+
return { name: "ECDSA", hash: "SHA-256", namedCurve: "P-256" };
|
|
91
|
+
case "ES384":
|
|
92
|
+
return { name: "ECDSA", hash: "SHA-384", namedCurve: "P-384" };
|
|
93
|
+
case "ES512":
|
|
94
|
+
return { name: "ECDSA", hash: "SHA-512", namedCurve: "P-521" };
|
|
95
|
+
default:
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Public API
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
/**
|
|
103
|
+
* Validate a DPoP proof JWT per RFC 9449.
|
|
104
|
+
*
|
|
105
|
+
* The proof is a compact-serialized JWT whose header contains the public key
|
|
106
|
+
* (`jwk`) used to sign the proof. The function:
|
|
107
|
+
*
|
|
108
|
+
* 1. Parses the JOSE header and verifies `typ: "dpop+jwt"` and a supported `alg`.
|
|
109
|
+
* 2. Extracts the embedded `jwk` and imports it via Web Crypto.
|
|
110
|
+
* 3. Verifies the JWT signature using the imported key.
|
|
111
|
+
* 4. Validates required claims:
|
|
112
|
+
* - `htm` — must match the provided HTTP method.
|
|
113
|
+
* - `htu` — must match the provided HTTP URI (scheme + authority + path).
|
|
114
|
+
* - `iat` — must be within the acceptable time window.
|
|
115
|
+
* - `jti` — must be unique (checked against an in-memory replay cache).
|
|
116
|
+
* 5. If an `accessToken` is provided, verifies the `ath` (access token hash)
|
|
117
|
+
* claim matches the SHA-256 hash of the token.
|
|
118
|
+
*
|
|
119
|
+
* Returns the JWK thumbprint on success, or `null` if validation fails.
|
|
120
|
+
*
|
|
121
|
+
* @param proof - The DPoP proof from the `DPoP` request header.
|
|
122
|
+
* @param method - The HTTP method of the request (e.g. "POST").
|
|
123
|
+
* @param url - The full request URL.
|
|
124
|
+
* @param accessToken - Optional access token to verify `ath` binding.
|
|
125
|
+
*/
|
|
126
|
+
export async function validateDpopProof(proof, method, url, accessToken) {
|
|
127
|
+
// ── 1. Split compact serialization ──────────────────────────────
|
|
128
|
+
const parts = proof.split(".");
|
|
129
|
+
if (parts.length !== 3)
|
|
130
|
+
return null;
|
|
131
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
132
|
+
// ── 2. Parse header ─────────────────────────────────────────────
|
|
133
|
+
let header;
|
|
134
|
+
try {
|
|
135
|
+
header = JSON.parse(new TextDecoder().decode(base64urlDecode(headerB64)));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
// Must be typ: dpop+jwt
|
|
141
|
+
if (header.typ !== "dpop+jwt")
|
|
142
|
+
return null;
|
|
143
|
+
// Must have a supported algorithm
|
|
144
|
+
if (!header.alg)
|
|
145
|
+
return null;
|
|
146
|
+
const algMapping = mapAlgorithm(header.alg);
|
|
147
|
+
if (!algMapping)
|
|
148
|
+
return null;
|
|
149
|
+
// Must have an embedded JWK
|
|
150
|
+
if (!header.jwk || typeof header.jwk !== "object")
|
|
151
|
+
return null;
|
|
152
|
+
// The JWK must not contain a private key
|
|
153
|
+
if ("d" in header.jwk)
|
|
154
|
+
return null;
|
|
155
|
+
// ── 3. Import the public key and verify signature ───────────────
|
|
156
|
+
let importAlg;
|
|
157
|
+
if (algMapping.name === "RSASSA-PKCS1-v1_5") {
|
|
158
|
+
importAlg = { name: algMapping.name, hash: algMapping.hash };
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
importAlg = {
|
|
162
|
+
name: algMapping.name,
|
|
163
|
+
namedCurve: algMapping.namedCurve,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
let publicKey;
|
|
167
|
+
try {
|
|
168
|
+
publicKey = await crypto.subtle.importKey("jwk", header.jwk, importAlg, false, ["verify"]);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
// Verify the signature over header.payload
|
|
174
|
+
const signingInput = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
|
|
175
|
+
const signature = base64urlDecode(signatureB64);
|
|
176
|
+
let verifyAlg;
|
|
177
|
+
if (algMapping.name === "ECDSA") {
|
|
178
|
+
verifyAlg = { name: "ECDSA", hash: algMapping.hash };
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
verifyAlg = { name: algMapping.name };
|
|
182
|
+
}
|
|
183
|
+
let valid;
|
|
184
|
+
try {
|
|
185
|
+
valid = await crypto.subtle.verify(verifyAlg, publicKey, signature.buffer, signingInput);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
if (!valid)
|
|
191
|
+
return null;
|
|
192
|
+
// ── 4. Parse and validate payload claims ────────────────────────
|
|
193
|
+
let payload;
|
|
194
|
+
try {
|
|
195
|
+
payload = JSON.parse(new TextDecoder().decode(base64urlDecode(payloadB64)));
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
// htm — HTTP method must match
|
|
201
|
+
if (typeof payload.htm !== "string")
|
|
202
|
+
return null;
|
|
203
|
+
if (payload.htm.toUpperCase() !== method.toUpperCase())
|
|
204
|
+
return null;
|
|
205
|
+
// htu — HTTP URI must match (scheme + authority + path, no query/fragment)
|
|
206
|
+
if (typeof payload.htu !== "string")
|
|
207
|
+
return null;
|
|
208
|
+
try {
|
|
209
|
+
const proofUrl = new URL(payload.htu);
|
|
210
|
+
const requestUrl = new URL(url);
|
|
211
|
+
// Compare origin + pathname (ignoring query string and fragment).
|
|
212
|
+
if (proofUrl.origin !== requestUrl.origin ||
|
|
213
|
+
proofUrl.pathname !== requestUrl.pathname) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
// iat — issued at must be recent
|
|
221
|
+
if (typeof payload.iat !== "number")
|
|
222
|
+
return null;
|
|
223
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
224
|
+
if (payload.iat > nowSeconds + MAX_CLOCK_SKEW_SECONDS)
|
|
225
|
+
return null;
|
|
226
|
+
if (nowSeconds - payload.iat > MAX_PROOF_AGE_SECONDS)
|
|
227
|
+
return null;
|
|
228
|
+
// jti — unique identifier, prevent replay
|
|
229
|
+
if (typeof payload.jti !== "string" || payload.jti.length === 0)
|
|
230
|
+
return null;
|
|
231
|
+
if (await dpopReplayStore.has(payload.jti))
|
|
232
|
+
return null;
|
|
233
|
+
// Record the jti with a TTL matching the proof window.
|
|
234
|
+
await dpopReplayStore.set(payload.jti, true, JTI_TTL_MS);
|
|
235
|
+
// ── 5. Access token hash binding (ath) ──────────────────────────
|
|
236
|
+
if (accessToken !== undefined) {
|
|
237
|
+
const expectedAth = base64urlEncode(createHash("sha256").update(accessToken).digest());
|
|
238
|
+
if (payload.ath !== expectedAth)
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
// ── 6. Compute JWK thumbprint ───────────────────────────────────
|
|
242
|
+
let thumbprint;
|
|
243
|
+
try {
|
|
244
|
+
thumbprint = computeJwkThumbprint(header.jwk);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
return { thumbprint };
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Clear the DPoP JTI replay cache.
|
|
253
|
+
*
|
|
254
|
+
* Useful for tests so state does not leak between test cases.
|
|
255
|
+
*/
|
|
256
|
+
export async function clearDpopReplayCache() {
|
|
257
|
+
await dpopReplayStore.clear();
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=dpop.js.map
|
package/dist/dpop.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dpop.js","sourceRoot":"","sources":["../src/dpop.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,9 @@ export { signSession, verifySession } from "./session.js";
|
|
|
2
2
|
export { generateApiKey, verifyApiKey, extractApiKeyPrefix, } from "./api-key.js";
|
|
3
3
|
export { createAuthMiddleware } from "./middleware.js";
|
|
4
4
|
export { checkPermission, derivePermission } from "./permissions.js";
|
|
5
|
+
export { validateDpopProof, clearDpopReplayCache, setDpopReplayStore } from "./dpop.js";
|
|
6
|
+
export { extractWorkloadIdentity, isValidSpiffeId } from "./workload.js";
|
|
7
|
+
export type { DpopValidationResult } from "./dpop.js";
|
|
8
|
+
export type { WorkloadIdentity } from "./workload.js";
|
|
5
9
|
export type { AuthConfig, SessionPayload, AgentCredential, AuthContext, AuthResolverDeps, } from "./types.js";
|
|
6
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EACL,cAAc,EACd,YAAY,EACZ,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACrE,YAAY,EACV,UAAU,EACV,cAAc,EACd,eAAe,EACf,WAAW,EACX,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EACL,cAAc,EACd,YAAY,EACZ,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACxF,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACzE,YAAY,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EACV,UAAU,EACV,cAAc,EACd,eAAe,EACf,WAAW,EACX,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,4 +2,6 @@ export { signSession, verifySession } from "./session.js";
|
|
|
2
2
|
export { generateApiKey, verifyApiKey, extractApiKeyPrefix, } from "./api-key.js";
|
|
3
3
|
export { createAuthMiddleware } from "./middleware.js";
|
|
4
4
|
export { checkPermission, derivePermission } from "./permissions.js";
|
|
5
|
+
export { validateDpopProof, clearDpopReplayCache, setDpopReplayStore } from "./dpop.js";
|
|
6
|
+
export { extractWorkloadIdentity, isValidSpiffeId } from "./workload.js";
|
|
5
7
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EACL,cAAc,EACd,YAAY,EACZ,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EACL,cAAc,EACd,YAAY,EACZ,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACxF,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -4,11 +4,14 @@ import type { AuthConfig, AuthContext, AuthResolverDeps } from "./types.js";
|
|
|
4
4
|
* incoming `Request`.
|
|
5
5
|
*
|
|
6
6
|
* Resolution order:
|
|
7
|
-
* 1.
|
|
8
|
-
*
|
|
7
|
+
* 1. Client certificate / workload identity (`X-Client-Cert` or
|
|
8
|
+
* `X-Forwarded-Client-Cert` header) — extracts SPIFFE ID, validates
|
|
9
|
+
* trust domain, returns workload context.
|
|
10
|
+
* 2. Session cookie (`capstan_session`) — verifies JWT, returns human context.
|
|
11
|
+
* 3. `Authorization: Bearer <token>` header — if the token matches the
|
|
9
12
|
* configured API key prefix, looks up the agent credential and verifies
|
|
10
13
|
* the key hash.
|
|
11
|
-
*
|
|
14
|
+
* 4. Falls back to `{ type: "anonymous", isAuthenticated: false }`.
|
|
12
15
|
*/
|
|
13
16
|
export declare function createAuthMiddleware(config: AuthConfig, deps: AuthResolverDeps): (request: Request) => Promise<AuthContext>;
|
|
14
17
|
//# sourceMappingURL=middleware.d.ts.map
|
package/dist/middleware.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EACjB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EACjB,MAAM,YAAY,CAAC;AA6BpB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,gBAAgB,GACrB,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,WAAW,CAAC,CAyG5C"}
|
package/dist/middleware.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { verifySession } from "./session.js";
|
|
2
2
|
import { verifyApiKey, extractApiKeyPrefix } from "./api-key.js";
|
|
3
|
+
import { validateDpopProof } from "./dpop.js";
|
|
4
|
+
import { extractWorkloadIdentity } from "./workload.js";
|
|
3
5
|
const SESSION_COOKIE_NAME = "capstan_session";
|
|
4
6
|
const DEFAULT_API_KEY_PREFIX = "cap_ak_";
|
|
5
7
|
const ANONYMOUS_CONTEXT = {
|
|
@@ -25,19 +27,41 @@ function parseCookies(header) {
|
|
|
25
27
|
* incoming `Request`.
|
|
26
28
|
*
|
|
27
29
|
* Resolution order:
|
|
28
|
-
* 1.
|
|
29
|
-
*
|
|
30
|
+
* 1. Client certificate / workload identity (`X-Client-Cert` or
|
|
31
|
+
* `X-Forwarded-Client-Cert` header) — extracts SPIFFE ID, validates
|
|
32
|
+
* trust domain, returns workload context.
|
|
33
|
+
* 2. Session cookie (`capstan_session`) — verifies JWT, returns human context.
|
|
34
|
+
* 3. `Authorization: Bearer <token>` header — if the token matches the
|
|
30
35
|
* configured API key prefix, looks up the agent credential and verifies
|
|
31
36
|
* the key hash.
|
|
32
|
-
*
|
|
37
|
+
* 4. Falls back to `{ type: "anonymous", isAuthenticated: false }`.
|
|
33
38
|
*/
|
|
34
39
|
export function createAuthMiddleware(config, deps) {
|
|
35
40
|
const apiKeyPrefix = config.apiKeys?.prefix ?? DEFAULT_API_KEY_PREFIX;
|
|
36
41
|
const authHeaderName = config.apiKeys?.headerName ?? "Authorization";
|
|
42
|
+
const trustedDomains = config.trustedDomains ?? [];
|
|
37
43
|
return async (request) => {
|
|
38
|
-
|
|
44
|
+
let authCtx;
|
|
45
|
+
let accessToken;
|
|
46
|
+
// ── 1. Workload identity (mTLS / SPIFFE) ─────────────────────
|
|
47
|
+
if (trustedDomains.length > 0) {
|
|
48
|
+
const headers = {};
|
|
49
|
+
for (const [key, value] of request.headers.entries()) {
|
|
50
|
+
headers[key] = value;
|
|
51
|
+
}
|
|
52
|
+
const identity = extractWorkloadIdentity(headers, trustedDomains);
|
|
53
|
+
if (identity) {
|
|
54
|
+
authCtx = {
|
|
55
|
+
isAuthenticated: true,
|
|
56
|
+
type: "workload",
|
|
57
|
+
spiffeId: identity.spiffeId,
|
|
58
|
+
certFingerprint: identity.certFingerprint,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// ── 2. Session cookie ────────────────────────────────────────
|
|
39
63
|
const cookieHeader = request.headers.get("cookie");
|
|
40
|
-
if (cookieHeader) {
|
|
64
|
+
if (!authCtx && cookieHeader) {
|
|
41
65
|
const cookies = parseCookies(cookieHeader);
|
|
42
66
|
const sessionToken = cookies.get(SESSION_COOKIE_NAME);
|
|
43
67
|
if (sessionToken) {
|
|
@@ -52,35 +76,55 @@ export function createAuthMiddleware(config, deps) {
|
|
|
52
76
|
ctx.role = payload.role;
|
|
53
77
|
if (payload.email !== undefined)
|
|
54
78
|
ctx.email = payload.email;
|
|
55
|
-
|
|
79
|
+
authCtx = ctx;
|
|
80
|
+
accessToken = sessionToken;
|
|
56
81
|
}
|
|
57
82
|
}
|
|
58
83
|
}
|
|
59
|
-
// ──
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
// ── 3. API key via Authorization header ──────────────────────
|
|
85
|
+
if (!authCtx) {
|
|
86
|
+
const authHeader = request.headers.get(authHeaderName);
|
|
87
|
+
if (authHeader) {
|
|
88
|
+
const token = authHeader.startsWith("Bearer ")
|
|
89
|
+
? authHeader.slice(7)
|
|
90
|
+
: authHeader.startsWith("DPoP ")
|
|
91
|
+
? authHeader.slice(5)
|
|
92
|
+
: null;
|
|
93
|
+
if (token && token.startsWith(apiKeyPrefix) && deps.findAgentByKeyPrefix) {
|
|
94
|
+
const prefix = extractApiKeyPrefix(token);
|
|
95
|
+
const credential = await deps.findAgentByKeyPrefix(prefix);
|
|
96
|
+
if (credential && !credential.revokedAt) {
|
|
97
|
+
const valid = await verifyApiKey(token, credential.apiKeyHash);
|
|
98
|
+
if (valid) {
|
|
99
|
+
authCtx = {
|
|
100
|
+
isAuthenticated: true,
|
|
101
|
+
type: "agent",
|
|
102
|
+
agentId: credential.id,
|
|
103
|
+
agentName: credential.name,
|
|
104
|
+
permissions: credential.permissions,
|
|
105
|
+
};
|
|
106
|
+
accessToken = token;
|
|
107
|
+
}
|
|
78
108
|
}
|
|
79
109
|
}
|
|
80
110
|
}
|
|
81
111
|
}
|
|
82
|
-
// ──
|
|
83
|
-
|
|
112
|
+
// ── 4. DPoP proof validation ─────────────────────────────────
|
|
113
|
+
// If a DPoP header is present, validate the proof and bind the
|
|
114
|
+
// token to the key thumbprint. A missing or invalid proof when
|
|
115
|
+
// the header is present causes the auth context to be rejected.
|
|
116
|
+
const dpopHeader = request.headers.get("dpop");
|
|
117
|
+
if (dpopHeader && authCtx) {
|
|
118
|
+
const result = await validateDpopProof(dpopHeader, request.method, request.url, accessToken);
|
|
119
|
+
if (!result) {
|
|
120
|
+
// DPoP proof failed validation — treat as unauthenticated.
|
|
121
|
+
return ANONYMOUS_CONTEXT;
|
|
122
|
+
}
|
|
123
|
+
// Bind the DPoP thumbprint to the auth context.
|
|
124
|
+
authCtx.dpopThumbprint = result.thumbprint;
|
|
125
|
+
}
|
|
126
|
+
// ── 5. Anonymous ─────────────────────────────────────────────
|
|
127
|
+
return authCtx ?? ANONYMOUS_CONTEXT;
|
|
84
128
|
};
|
|
85
129
|
}
|
|
86
130
|
//# sourceMappingURL=middleware.js.map
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable key-value store interface.
|
|
3
|
+
*
|
|
4
|
+
* Identical to `@zauso-ai/capstan-core`'s `KeyValueStore` — duplicated here
|
|
5
|
+
* so that `@zauso-ai/capstan-auth` has no hard dependency on the core package.
|
|
6
|
+
*/
|
|
7
|
+
export interface KeyValueStore<T> {
|
|
8
|
+
get(key: string): Promise<T | undefined>;
|
|
9
|
+
set(key: string, value: T, ttlMs?: number): Promise<void>;
|
|
10
|
+
delete(key: string): Promise<boolean>;
|
|
11
|
+
has(key: string): Promise<boolean>;
|
|
12
|
+
clear(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* In-memory implementation of `KeyValueStore` with optional per-entry TTL.
|
|
16
|
+
*/
|
|
17
|
+
export declare class MemoryStore<T> implements KeyValueStore<T> {
|
|
18
|
+
private data;
|
|
19
|
+
get(key: string): Promise<T | undefined>;
|
|
20
|
+
set(key: string, value: T, ttlMs?: number): Promise<void>;
|
|
21
|
+
delete(key: string): Promise<boolean>;
|
|
22
|
+
has(key: string): Promise<boolean>;
|
|
23
|
+
clear(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,WAAW,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IACrD,OAAO,CAAC,IAAI,CAAkE;IAExE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAUxC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory implementation of `KeyValueStore` with optional per-entry TTL.
|
|
3
|
+
*/
|
|
4
|
+
export class MemoryStore {
|
|
5
|
+
data = new Map();
|
|
6
|
+
async get(key) {
|
|
7
|
+
const entry = this.data.get(key);
|
|
8
|
+
if (!entry)
|
|
9
|
+
return undefined;
|
|
10
|
+
if (entry.expiresAt !== undefined && Date.now() > entry.expiresAt) {
|
|
11
|
+
this.data.delete(key);
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return entry.value;
|
|
15
|
+
}
|
|
16
|
+
async set(key, value, ttlMs) {
|
|
17
|
+
this.data.set(key, {
|
|
18
|
+
value,
|
|
19
|
+
expiresAt: ttlMs ? Date.now() + ttlMs : undefined,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async delete(key) {
|
|
23
|
+
return this.data.delete(key);
|
|
24
|
+
}
|
|
25
|
+
async has(key) {
|
|
26
|
+
const val = await this.get(key); // triggers TTL check
|
|
27
|
+
return val !== undefined;
|
|
28
|
+
}
|
|
29
|
+
async clear() {
|
|
30
|
+
this.data.clear();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAcA;;GAEG;AACH,MAAM,OAAO,WAAW;IACd,IAAI,GAAG,IAAI,GAAG,EAAuD,CAAC;IAE9E,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YAClE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACtB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAQ,EAAE,KAAc;QAC7C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE;YACjB,KAAK;YACL,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS;SAClD,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,qBAAqB;QACtD,OAAO,GAAG,KAAK,SAAS,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IACpB,CAAC;CACF"}
|
package/dist/types.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export interface AuthConfig {
|
|
|
7
7
|
prefix?: string;
|
|
8
8
|
headerName?: string;
|
|
9
9
|
};
|
|
10
|
+
/** Trusted SPIFFE trust domains for mTLS workload authentication. */
|
|
11
|
+
trustedDomains?: string[];
|
|
12
|
+
/** Whether to require client certificates (mTLS). */
|
|
13
|
+
mtls?: boolean;
|
|
10
14
|
}
|
|
11
15
|
export interface SessionPayload {
|
|
12
16
|
userId: string;
|
|
@@ -25,13 +29,19 @@ export interface AgentCredential {
|
|
|
25
29
|
}
|
|
26
30
|
export interface AuthContext {
|
|
27
31
|
isAuthenticated: boolean;
|
|
28
|
-
type: "human" | "agent" | "anonymous";
|
|
32
|
+
type: "human" | "agent" | "anonymous" | "workload";
|
|
29
33
|
userId?: string;
|
|
30
34
|
role?: string;
|
|
31
35
|
email?: string;
|
|
32
36
|
agentId?: string;
|
|
33
37
|
agentName?: string;
|
|
34
38
|
permissions?: string[];
|
|
39
|
+
/** DPoP key thumbprint — present when the request included a valid DPoP proof. */
|
|
40
|
+
dpopThumbprint?: string;
|
|
41
|
+
/** SPIFFE ID from client certificate (e.g. "spiffe://example.org/agent/crawler"). */
|
|
42
|
+
spiffeId?: string;
|
|
43
|
+
/** Client certificate fingerprint (SHA-256 hex digest). */
|
|
44
|
+
certFingerprint?: string;
|
|
35
45
|
}
|
|
36
46
|
export interface AuthResolverDeps {
|
|
37
47
|
/** Look up an agent credential by API key prefix */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,qDAAqD;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,WAAW,GAAG,UAAU,CAAC;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,kFAAkF;IAClF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,oBAAoB,CAAC,EAAE,CACrB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;CACtC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsed workload identity extracted from a client certificate.
|
|
3
|
+
*/
|
|
4
|
+
export interface WorkloadIdentity {
|
|
5
|
+
/** Full SPIFFE ID URI (e.g. "spiffe://example.org/agent/crawler"). */
|
|
6
|
+
spiffeId: string;
|
|
7
|
+
/** Trust domain portion of the SPIFFE ID (e.g. "example.org"). */
|
|
8
|
+
trustDomain: string;
|
|
9
|
+
/** Workload path portion of the SPIFFE ID (e.g. "/agent/crawler"). */
|
|
10
|
+
workloadPath: string;
|
|
11
|
+
/** SHA-256 hex digest of the PEM-encoded client certificate. */
|
|
12
|
+
certFingerprint: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Validate that a string is a well-formed SPIFFE ID per the SPIFFE spec:
|
|
16
|
+
*
|
|
17
|
+
* - Scheme must be `spiffe`
|
|
18
|
+
* - Trust domain must be a non-empty hostname-like string (lower-case
|
|
19
|
+
* alphanumerics, hyphens, dots; no leading/trailing hyphens or dots).
|
|
20
|
+
* - Workload path must be non-empty and start with `/`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isValidSpiffeId(id: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Extract a workload identity from client certificate information provided
|
|
25
|
+
* via request headers.
|
|
26
|
+
*
|
|
27
|
+
* In a typical deployment the mTLS termination happens at a reverse proxy
|
|
28
|
+
* (Envoy, Nginx, Istio ingress, etc.) which forwards the client certificate
|
|
29
|
+
* and its SPIFFE ID through HTTP headers.
|
|
30
|
+
*
|
|
31
|
+
* Supported header combinations:
|
|
32
|
+
*
|
|
33
|
+
* | SPIFFE ID source | Cert source |
|
|
34
|
+
* |---------------------------------------|--------------------------|
|
|
35
|
+
* | `X-Client-Cert-Spiffe-Id` header | `X-Client-Cert` |
|
|
36
|
+
* | `URI=` in `X-Forwarded-Client-Cert` | `X-Forwarded-Client-Cert`|
|
|
37
|
+
*
|
|
38
|
+
* @param certOrHeaders PEM-encoded client certificate string, or a
|
|
39
|
+
* header map (e.g. from `Object.fromEntries(request.headers)`).
|
|
40
|
+
* @param trustedDomains Whitelist of SPIFFE trust domains to accept.
|
|
41
|
+
* @returns The parsed `WorkloadIdentity` if the certificate is present, the
|
|
42
|
+
* SPIFFE ID is valid, and the trust domain is in the whitelist.
|
|
43
|
+
* Returns `null` otherwise.
|
|
44
|
+
*/
|
|
45
|
+
export declare function extractWorkloadIdentity(certOrHeaders: string | Record<string, string | undefined>, trustedDomains: string[]): WorkloadIdentity | null;
|
|
46
|
+
//# sourceMappingURL=workload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workload.d.ts","sourceRoot":"","sources":["../src/workload.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,eAAe,EAAE,MAAM,CAAC;CACzB;AAiBD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CA6BnD;AAkID;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,uBAAuB,CACrC,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAC1D,cAAc,EAAE,MAAM,EAAE,GACvB,gBAAgB,GAAG,IAAI,CAyCzB"}
|
package/dist/workload.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Headers that reverse proxies commonly use to forward client certificates.
|
|
4
|
+
*
|
|
5
|
+
* - `x-client-cert` — raw PEM or URL-encoded PEM (Envoy, Nginx, etc.)
|
|
6
|
+
* - `x-forwarded-client-cert` — Envoy XFCC header (may contain key=value pairs)
|
|
7
|
+
*/
|
|
8
|
+
const CLIENT_CERT_HEADERS = [
|
|
9
|
+
"x-client-cert",
|
|
10
|
+
"x-forwarded-client-cert",
|
|
11
|
+
];
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// SPIFFE ID validation
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/**
|
|
16
|
+
* Validate that a string is a well-formed SPIFFE ID per the SPIFFE spec:
|
|
17
|
+
*
|
|
18
|
+
* - Scheme must be `spiffe`
|
|
19
|
+
* - Trust domain must be a non-empty hostname-like string (lower-case
|
|
20
|
+
* alphanumerics, hyphens, dots; no leading/trailing hyphens or dots).
|
|
21
|
+
* - Workload path must be non-empty and start with `/`.
|
|
22
|
+
*/
|
|
23
|
+
export function isValidSpiffeId(id) {
|
|
24
|
+
if (typeof id !== "string" || id.length === 0)
|
|
25
|
+
return false;
|
|
26
|
+
// Must start with the spiffe scheme.
|
|
27
|
+
if (!id.startsWith("spiffe://"))
|
|
28
|
+
return false;
|
|
29
|
+
const rest = id.slice("spiffe://".length);
|
|
30
|
+
// Find the first "/" after the trust domain.
|
|
31
|
+
const slashIndex = rest.indexOf("/");
|
|
32
|
+
if (slashIndex <= 0)
|
|
33
|
+
return false; // no trust domain or no path
|
|
34
|
+
const trustDomain = rest.slice(0, slashIndex);
|
|
35
|
+
const workloadPath = rest.slice(slashIndex);
|
|
36
|
+
// Trust domain: valid DNS-like label(s), no leading/trailing dot or hyphen.
|
|
37
|
+
if (!isValidTrustDomain(trustDomain))
|
|
38
|
+
return false;
|
|
39
|
+
// Workload path must be non-empty (beyond the leading slash).
|
|
40
|
+
if (workloadPath.length < 2)
|
|
41
|
+
return false;
|
|
42
|
+
// Path segments must not be empty (no double slashes) and must not
|
|
43
|
+
// contain `.` or `..` traversals.
|
|
44
|
+
const segments = workloadPath.slice(1).split("/");
|
|
45
|
+
for (const seg of segments) {
|
|
46
|
+
if (seg.length === 0 || seg === "." || seg === "..")
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A trust domain is a valid lower-case DNS name: labels separated by dots,
|
|
53
|
+
* each label consisting of alphanumerics and hyphens, not starting/ending
|
|
54
|
+
* with a hyphen.
|
|
55
|
+
*/
|
|
56
|
+
function isValidTrustDomain(domain) {
|
|
57
|
+
if (domain.length === 0 || domain.length > 255)
|
|
58
|
+
return false;
|
|
59
|
+
const labels = domain.split(".");
|
|
60
|
+
for (const label of labels) {
|
|
61
|
+
if (label.length === 0 || label.length > 63)
|
|
62
|
+
return false;
|
|
63
|
+
if (/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(label) === false)
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Certificate parsing helpers
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
/**
|
|
72
|
+
* Extract the PEM-encoded client certificate from request headers.
|
|
73
|
+
*
|
|
74
|
+
* Supports:
|
|
75
|
+
* - `X-Client-Cert: <PEM or URL-encoded PEM>`
|
|
76
|
+
* - `X-Forwarded-Client-Cert: Cert="<URL-encoded PEM>"` (Envoy XFCC format)
|
|
77
|
+
*/
|
|
78
|
+
function extractPemFromHeaders(headers) {
|
|
79
|
+
// Normalise header names to lower case for case-insensitive lookup.
|
|
80
|
+
const normalised = {};
|
|
81
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
82
|
+
if (v !== undefined) {
|
|
83
|
+
normalised[k.toLowerCase()] = v;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const headerName of CLIENT_CERT_HEADERS) {
|
|
87
|
+
const raw = normalised[headerName];
|
|
88
|
+
if (!raw || raw.length === 0)
|
|
89
|
+
continue;
|
|
90
|
+
// XFCC header from Envoy may look like:
|
|
91
|
+
// By=...;Cert="<url-encoded PEM>";Hash=...
|
|
92
|
+
// Extract the Cert value if present.
|
|
93
|
+
const certMatch = raw.match(/Cert="([^"]+)"/i);
|
|
94
|
+
if (certMatch?.[1]) {
|
|
95
|
+
return decodePem(certMatch[1]);
|
|
96
|
+
}
|
|
97
|
+
// Otherwise treat the entire header value as PEM (possibly URL-encoded).
|
|
98
|
+
return decodePem(raw);
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Decode a PEM string that may be URL-encoded (common in proxy headers).
|
|
104
|
+
*/
|
|
105
|
+
function decodePem(value) {
|
|
106
|
+
// If the value contains %2F or %2B it was URL-encoded.
|
|
107
|
+
if (value.includes("%")) {
|
|
108
|
+
try {
|
|
109
|
+
return decodeURIComponent(value);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Not valid URL encoding — use as-is.
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Compute the SHA-256 hex fingerprint of a PEM-encoded certificate.
|
|
119
|
+
*
|
|
120
|
+
* The fingerprint is computed over the raw PEM text (including header/footer
|
|
121
|
+
* lines) to keep the implementation dependency-free. This is sufficient for
|
|
122
|
+
* identity-binding purposes as each unique cert will produce a unique hash.
|
|
123
|
+
*/
|
|
124
|
+
function computeCertFingerprint(pem) {
|
|
125
|
+
return createHash("sha256").update(pem).digest("hex");
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Extract the SPIFFE URI SAN from a PEM-encoded certificate.
|
|
129
|
+
*
|
|
130
|
+
* Real X.509 parsing requires ASN.1 decoding which would need a dependency.
|
|
131
|
+
* Instead we use a pragmatic approach: the SPIFFE ID is looked up from a
|
|
132
|
+
* companion header (`X-Client-Cert-Spiffe-Id`) that mTLS-terminating proxies
|
|
133
|
+
* (Envoy, Istio, Linkerd) can be configured to set, or it can be embedded
|
|
134
|
+
* in the XFCC header as `URI=spiffe://...`.
|
|
135
|
+
*
|
|
136
|
+
* If neither is available we fall back to scanning the PEM base64 payload
|
|
137
|
+
* for the `spiffe://` URI (works for unencrypted certs where the SAN is
|
|
138
|
+
* visible in the base64 text — a useful heuristic but not a substitute for
|
|
139
|
+
* real ASN.1 parsing).
|
|
140
|
+
*/
|
|
141
|
+
function extractSpiffeIdFromHeaders(headers) {
|
|
142
|
+
const normalised = {};
|
|
143
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
144
|
+
if (v !== undefined) {
|
|
145
|
+
normalised[k.toLowerCase()] = v;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// 1. Explicit header set by the proxy.
|
|
149
|
+
const explicit = normalised["x-client-cert-spiffe-id"];
|
|
150
|
+
if (explicit && isValidSpiffeId(explicit)) {
|
|
151
|
+
return explicit;
|
|
152
|
+
}
|
|
153
|
+
// 2. XFCC URI field (Envoy sets `URI=spiffe://...`).
|
|
154
|
+
const xfcc = normalised["x-forwarded-client-cert"];
|
|
155
|
+
if (xfcc) {
|
|
156
|
+
const uriMatch = xfcc.match(/URI=(spiffe:\/\/[^;,"]+)/i);
|
|
157
|
+
if (uriMatch?.[1] && isValidSpiffeId(uriMatch[1])) {
|
|
158
|
+
return uriMatch[1];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Public API
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
/**
|
|
167
|
+
* Extract a workload identity from client certificate information provided
|
|
168
|
+
* via request headers.
|
|
169
|
+
*
|
|
170
|
+
* In a typical deployment the mTLS termination happens at a reverse proxy
|
|
171
|
+
* (Envoy, Nginx, Istio ingress, etc.) which forwards the client certificate
|
|
172
|
+
* and its SPIFFE ID through HTTP headers.
|
|
173
|
+
*
|
|
174
|
+
* Supported header combinations:
|
|
175
|
+
*
|
|
176
|
+
* | SPIFFE ID source | Cert source |
|
|
177
|
+
* |---------------------------------------|--------------------------|
|
|
178
|
+
* | `X-Client-Cert-Spiffe-Id` header | `X-Client-Cert` |
|
|
179
|
+
* | `URI=` in `X-Forwarded-Client-Cert` | `X-Forwarded-Client-Cert`|
|
|
180
|
+
*
|
|
181
|
+
* @param certOrHeaders PEM-encoded client certificate string, or a
|
|
182
|
+
* header map (e.g. from `Object.fromEntries(request.headers)`).
|
|
183
|
+
* @param trustedDomains Whitelist of SPIFFE trust domains to accept.
|
|
184
|
+
* @returns The parsed `WorkloadIdentity` if the certificate is present, the
|
|
185
|
+
* SPIFFE ID is valid, and the trust domain is in the whitelist.
|
|
186
|
+
* Returns `null` otherwise.
|
|
187
|
+
*/
|
|
188
|
+
export function extractWorkloadIdentity(certOrHeaders, trustedDomains) {
|
|
189
|
+
if (trustedDomains.length === 0)
|
|
190
|
+
return null;
|
|
191
|
+
let pem;
|
|
192
|
+
let spiffeId;
|
|
193
|
+
if (typeof certOrHeaders === "string") {
|
|
194
|
+
// Raw PEM string provided directly — cannot extract SPIFFE ID from
|
|
195
|
+
// headers, so this path is only useful if the caller also provides
|
|
196
|
+
// the SPIFFE ID separately. For header-based flows use the object form.
|
|
197
|
+
pem = certOrHeaders.length > 0 ? certOrHeaders : null;
|
|
198
|
+
spiffeId = null;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
pem = extractPemFromHeaders(certOrHeaders);
|
|
202
|
+
spiffeId = extractSpiffeIdFromHeaders(certOrHeaders);
|
|
203
|
+
}
|
|
204
|
+
// We need both a certificate (for fingerprinting) and a SPIFFE ID.
|
|
205
|
+
if (!pem || !spiffeId)
|
|
206
|
+
return null;
|
|
207
|
+
if (!isValidSpiffeId(spiffeId))
|
|
208
|
+
return null;
|
|
209
|
+
// Parse the trust domain from the SPIFFE ID.
|
|
210
|
+
const rest = spiffeId.slice("spiffe://".length);
|
|
211
|
+
const slashIndex = rest.indexOf("/");
|
|
212
|
+
if (slashIndex <= 0)
|
|
213
|
+
return null;
|
|
214
|
+
const trustDomain = rest.slice(0, slashIndex);
|
|
215
|
+
const workloadPath = rest.slice(slashIndex);
|
|
216
|
+
// Verify the trust domain is in the whitelist.
|
|
217
|
+
if (!trustedDomains.includes(trustDomain))
|
|
218
|
+
return null;
|
|
219
|
+
const certFingerprint = computeCertFingerprint(pem);
|
|
220
|
+
return {
|
|
221
|
+
spiffeId,
|
|
222
|
+
trustDomain,
|
|
223
|
+
workloadPath,
|
|
224
|
+
certFingerprint,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=workload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workload.js","sourceRoot":"","sources":["../src/workload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAoBzC;;;;;GAKG;AACH,MAAM,mBAAmB,GAAG;IAC1B,eAAe;IACf,yBAAyB;CACjB,CAAC;AAEX,8EAA8E;AAC9E,uBAAuB;AACvB,8EAA8E;AAE9E;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,EAAU;IACxC,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAE5D,qCAAqC;IACrC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,KAAK,CAAC;IAE9C,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAE1C,6CAA6C;IAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,UAAU,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,6BAA6B;IAEhE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAE5C,4EAA4E;IAC5E,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC;QAAE,OAAO,KAAK,CAAC;IAEnD,8DAA8D;IAC9D,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAE1C,mEAAmE;IACnE,kCAAkC;IAClC,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClD,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;IACpE,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,MAAc;IACxC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,GAAG;QAAE,OAAO,KAAK,CAAC;IAC7D,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE;YAAE,OAAO,KAAK,CAAC;QAC1D,IAAI,iCAAiC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;IAC5E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8EAA8E;AAC9E,8BAA8B;AAC9B,8EAA8E;AAE9E;;;;;;GAMG;AACH,SAAS,qBAAqB,CAC5B,OAA2C;IAE3C,oEAAoE;IACpE,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACpB,UAAU,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,mBAAmB,EAAE,CAAC;QAC7C,MAAM,GAAG,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;QACnC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAEvC,wCAAwC;QACxC,6CAA6C;QAC7C,qCAAqC;QACrC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAC/C,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnB,OAAO,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QACjC,CAAC;QAED,yEAAyE;QACzE,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAS,SAAS,CAAC,KAAa;IAC9B,uDAAuD;IACvD,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,SAAS,sBAAsB,CAAC,GAAW;IACzC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,0BAA0B,CACjC,OAA2C;IAE3C,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACpB,UAAU,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,uCAAuC;IACvC,MAAM,QAAQ,GAAG,UAAU,CAAC,yBAAyB,CAAC,CAAC;IACvD,IAAI,QAAQ,IAAI,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1C,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,qDAAqD;IACrD,MAAM,IAAI,GAAG,UAAU,CAAC,yBAAyB,CAAC,CAAC;IACnD,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACzD,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAClD,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,uBAAuB,CACrC,aAA0D,EAC1D,cAAwB;IAExB,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE7C,IAAI,GAAkB,CAAC;IACvB,IAAI,QAAuB,CAAC;IAE5B,IAAI,OAAO,aAAa,KAAK,QAAQ,EAAE,CAAC;QACtC,mEAAmE;QACnE,mEAAmE;QACnE,yEAAyE;QACzE,GAAG,GAAG,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;QACtD,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;SAAM,CAAC;QACN,GAAG,GAAG,qBAAqB,CAAC,aAAa,CAAC,CAAC;QAC3C,QAAQ,GAAG,0BAA0B,CAAC,aAAa,CAAC,CAAC;IACvD,CAAC;IAED,mEAAmE;IACnE,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEnC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAE5C,6CAA6C;IAC7C,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,UAAU,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAEjC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAE5C,+CAA+C;IAC/C,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvD,MAAM,eAAe,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAEpD,OAAO;QACL,QAAQ;QACR,WAAW;QACX,YAAY;QACZ,eAAe;KAChB,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zauso-ai/capstan-auth",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc -p tsconfig.json",
|
|
15
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
15
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
16
|
+
"clean": "rm -rf dist"
|
|
16
17
|
},
|
|
17
18
|
"description": "Authentication for Capstan — JWT sessions, API key auth for AI agents, permissions",
|
|
18
19
|
"files": [
|