@glinr/theauth 0.4.2

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.
@@ -0,0 +1,292 @@
1
+ // src/crypto/web-crypto.ts
2
+ function generateId() {
3
+ return globalThis.crypto.randomUUID();
4
+ }
5
+ new TextEncoder();
6
+
7
+ // src/redirect/chain.ts
8
+ var DEFAULT_COOKIE_NAME = "kavach_redirect";
9
+ var DEFAULT_MAX_AGE = 600;
10
+ var DEFAULT_PATH = "/";
11
+ var DEFAULT_MAX_DEPTH = 10;
12
+ var DEFAULT_EXCLUDE_PATHS = [
13
+ "/sign-in",
14
+ "/sign-up",
15
+ "/forgot-password",
16
+ "/reset-password",
17
+ "/verify-email",
18
+ "/api/"
19
+ ];
20
+ var encoder = new TextEncoder();
21
+ var decoder = new TextDecoder();
22
+ function encodeState(state) {
23
+ const json = JSON.stringify(state);
24
+ const bytes = encoder.encode(json);
25
+ let binary = "";
26
+ for (let i = 0; i < bytes.length; i++) {
27
+ binary += String.fromCharCode(bytes[i]);
28
+ }
29
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
30
+ }
31
+ function decodeState(encoded) {
32
+ try {
33
+ let base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
34
+ while (base64.length % 4 !== 0) {
35
+ base64 += "=";
36
+ }
37
+ const binary = atob(base64);
38
+ const bytes = new Uint8Array(binary.length);
39
+ for (let i = 0; i < binary.length; i++) {
40
+ bytes[i] = binary.charCodeAt(i);
41
+ }
42
+ const json = decoder.decode(bytes);
43
+ const parsed = JSON.parse(json);
44
+ if (!isValidChainState(parsed)) return null;
45
+ return parsed;
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+ function isValidChainState(value) {
51
+ if (typeof value !== "object" || value === null) return false;
52
+ const obj = value;
53
+ if (typeof obj.createdAt !== "number") return false;
54
+ if (!isValidEntry(obj.origin)) return false;
55
+ if (!Array.isArray(obj.steps)) return false;
56
+ for (const step of obj.steps) {
57
+ if (!isValidEntry(step)) return false;
58
+ }
59
+ return true;
60
+ }
61
+ function isValidEntry(value) {
62
+ if (typeof value !== "object" || value === null) return false;
63
+ const obj = value;
64
+ return typeof obj.id === "string" && typeof obj.path === "string" && typeof obj.query === "object" && obj.query !== null && typeof obj.hash === "string" && typeof obj.createdAt === "number";
65
+ }
66
+ function parseCookies(request) {
67
+ const header = request.headers.get("cookie");
68
+ if (!header) return {};
69
+ const cookies = {};
70
+ for (const pair of header.split(";")) {
71
+ const eqIdx = pair.indexOf("=");
72
+ if (eqIdx === -1) continue;
73
+ const key = pair.slice(0, eqIdx).trim();
74
+ const val = pair.slice(eqIdx + 1).trim();
75
+ cookies[key] = val;
76
+ }
77
+ return cookies;
78
+ }
79
+ function isExcluded(path, excludePaths) {
80
+ for (const excluded of excludePaths) {
81
+ if (excluded.endsWith("/")) {
82
+ if (path.startsWith(excluded) || path === excluded.slice(0, -1)) return true;
83
+ } else {
84
+ if (path === excluded) return true;
85
+ }
86
+ }
87
+ return false;
88
+ }
89
+ function createRedirectChain(config) {
90
+ const cookieName = config?.cookieName ?? DEFAULT_COOKIE_NAME;
91
+ const maxAge = config?.maxAge ?? DEFAULT_MAX_AGE;
92
+ const defaultPath = config?.defaultPath ?? DEFAULT_PATH;
93
+ const excludePaths = config?.excludePaths ?? DEFAULT_EXCLUDE_PATHS;
94
+ const preserveQuery = config?.preserveQuery ?? true;
95
+ const preserveHash = config?.preserveHash ?? true;
96
+ const maxDepth = config?.maxDepth ?? DEFAULT_MAX_DEPTH;
97
+ const cookieOpts = {
98
+ httpOnly: config?.cookie?.httpOnly ?? true,
99
+ secure: config?.cookie?.secure ?? true,
100
+ sameSite: config?.cookie?.sameSite ?? "lax",
101
+ path: config?.cookie?.path ?? "/",
102
+ domain: config?.cookie?.domain
103
+ };
104
+ let currentState = null;
105
+ function buildCookieHeader(value, age) {
106
+ const parts = [`${cookieName}=${value}`, `Path=${cookieOpts.path}`, `Max-Age=${age}`];
107
+ if (cookieOpts.httpOnly) parts.push("HttpOnly");
108
+ if (cookieOpts.secure) parts.push("Secure");
109
+ parts.push(
110
+ `SameSite=${cookieOpts.sameSite.charAt(0).toUpperCase()}${cookieOpts.sameSite.slice(1)}`
111
+ );
112
+ if (cookieOpts.domain) parts.push(`Domain=${cookieOpts.domain}`);
113
+ return parts.join("; ");
114
+ }
115
+ function buildClearCookie() {
116
+ return buildCookieHeader("", 0);
117
+ }
118
+ function serializeAndSetCookie(state) {
119
+ currentState = state;
120
+ const encoded = encodeState(state);
121
+ return buildCookieHeader(encoded, maxAge);
122
+ }
123
+ function parseFromRequest(request) {
124
+ const cookies = parseCookies(request);
125
+ const raw = cookies[cookieName];
126
+ if (!raw) return null;
127
+ const state = decodeState(raw);
128
+ if (!state) return null;
129
+ const elapsed = Date.now() - state.createdAt;
130
+ if (elapsed > maxAge * 1e3) return null;
131
+ currentState = state;
132
+ return state;
133
+ }
134
+ function createEntryFromUrl(url, label) {
135
+ let parsed;
136
+ if (typeof url === "string") {
137
+ if (url.startsWith("/")) {
138
+ parsed = new URL(url, "http://localhost");
139
+ } else {
140
+ parsed = new URL(url);
141
+ }
142
+ } else {
143
+ parsed = new URL(url.url);
144
+ }
145
+ const query = {};
146
+ if (preserveQuery) {
147
+ parsed.searchParams.forEach((value, key) => {
148
+ query[key] = value;
149
+ });
150
+ }
151
+ return {
152
+ id: generateId(),
153
+ path: parsed.pathname,
154
+ query,
155
+ hash: preserveHash ? parsed.hash.replace(/^#/, "") : "",
156
+ createdAt: Date.now(),
157
+ label
158
+ };
159
+ }
160
+ function buildUrlFromEntry(entry) {
161
+ let url = entry.path;
162
+ const params = Object.entries(entry.query);
163
+ if (params.length > 0) {
164
+ const searchParams = new URLSearchParams();
165
+ for (const [key, value] of params) {
166
+ searchParams.set(key, value);
167
+ }
168
+ url += `?${searchParams.toString()}`;
169
+ }
170
+ if (entry.hash) {
171
+ url += `#${entry.hash}`;
172
+ }
173
+ return url;
174
+ }
175
+ const manager = {
176
+ capture(request) {
177
+ const existing = parseFromRequest(request);
178
+ if (existing) {
179
+ return serializeAndSetCookie(existing);
180
+ }
181
+ const entry = createEntryFromUrl(request);
182
+ if (isExcluded(entry.path, excludePaths)) {
183
+ entry.path = defaultPath;
184
+ entry.query = {};
185
+ entry.hash = "";
186
+ }
187
+ const state = {
188
+ origin: entry,
189
+ steps: [],
190
+ createdAt: Date.now()
191
+ };
192
+ return serializeAndSetCookie(state);
193
+ },
194
+ push(path, options) {
195
+ if (!currentState) {
196
+ currentState = {
197
+ origin: {
198
+ id: generateId(),
199
+ path: defaultPath,
200
+ query: {},
201
+ hash: "",
202
+ createdAt: Date.now()
203
+ },
204
+ steps: [],
205
+ createdAt: Date.now()
206
+ };
207
+ }
208
+ if (currentState.steps.length >= maxDepth) {
209
+ return serializeAndSetCookie(currentState);
210
+ }
211
+ const entry = {
212
+ id: generateId(),
213
+ path,
214
+ query: options?.query ?? {},
215
+ hash: "",
216
+ createdAt: Date.now(),
217
+ label: options?.label
218
+ };
219
+ currentState.steps.push(entry);
220
+ return serializeAndSetCookie(currentState);
221
+ },
222
+ pop(request) {
223
+ const state = parseFromRequest(request);
224
+ if (!state) {
225
+ return { url: defaultPath, done: true, clearCookie: buildClearCookie() };
226
+ }
227
+ if (state.steps.length > 0) {
228
+ const next = state.steps.shift();
229
+ const url2 = buildUrlFromEntry(next);
230
+ if (state.steps.length === 0) {
231
+ currentState = state;
232
+ return {
233
+ url: url2,
234
+ done: false,
235
+ clearCookie: null
236
+ };
237
+ }
238
+ return {
239
+ url: url2,
240
+ done: false,
241
+ clearCookie: null
242
+ };
243
+ }
244
+ const url = buildUrlFromEntry(state.origin);
245
+ currentState = null;
246
+ return {
247
+ url,
248
+ done: true,
249
+ clearCookie: buildClearCookie()
250
+ };
251
+ },
252
+ peek(request) {
253
+ const state = parseFromRequest(request);
254
+ if (!state) return null;
255
+ if (state.steps.length > 0) {
256
+ const next = state.steps[0];
257
+ return {
258
+ url: buildUrlFromEntry(next),
259
+ remaining: state.steps.length
260
+ // +origin is implicit
261
+ };
262
+ }
263
+ return {
264
+ url: buildUrlFromEntry(state.origin),
265
+ remaining: 0
266
+ };
267
+ },
268
+ getOrigin(request) {
269
+ const state = parseFromRequest(request);
270
+ if (!state) return null;
271
+ return state.origin;
272
+ },
273
+ clear() {
274
+ currentState = null;
275
+ return buildClearCookie();
276
+ },
277
+ parse(request) {
278
+ return parseFromRequest(request);
279
+ },
280
+ buildUrl(entry) {
281
+ return buildUrlFromEntry(entry);
282
+ },
283
+ createEntry(url, label) {
284
+ return createEntryFromUrl(url, label);
285
+ }
286
+ };
287
+ return manager;
288
+ }
289
+
290
+ export { createRedirectChain };
291
+ //# sourceMappingURL=index.js.map
292
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/crypto/web-crypto.ts","../../src/redirect/chain.ts"],"names":["url"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Web Crypto API utilities for TheAuth.\n *\n * This module uses ONLY the Web Crypto API (globalThis.crypto) which is\n * available natively in Cloudflare Workers, Deno, Bun, and Node 20+.\n * No `node:crypto` imports are used, making the core package edge-compatible.\n */\n\n// ---------------------------------------------------------------------------\n// Encoding helpers\n// ---------------------------------------------------------------------------\n\nconst HEX_CHARS = \"0123456789abcdef\";\n\n/** Encode a Uint8Array as a lowercase hex string. */\nexport function toHex(bytes: Uint8Array): string {\n\tlet hex = \"\";\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tconst b = bytes[i] as number;\n\t\thex += HEX_CHARS[b >> 4] as string;\n\t\thex += HEX_CHARS[b & 0x0f] as string;\n\t}\n\treturn hex;\n}\n\n/** Decode a hex string into a Uint8Array. */\nexport function fromHex(hex: string): Uint8Array {\n\tif (hex.length % 2 !== 0) {\n\t\tthrow new Error(\"fromHex: hex string must have even length\");\n\t}\n\tconst bytes = new Uint8Array(hex.length / 2);\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tconst hi = parseInt(hex[i * 2] as string, 16);\n\t\tconst lo = parseInt(hex[i * 2 + 1] as string, 16);\n\t\tif (Number.isNaN(hi) || Number.isNaN(lo)) {\n\t\t\tthrow new Error(`fromHex: invalid hex character at position ${i * 2}`);\n\t\t}\n\t\tbytes[i] = (hi << 4) | lo;\n\t}\n\treturn bytes;\n}\n\n/** Encode a Uint8Array as a base64url string (no padding). */\nexport function toBase64Url(bytes: Uint8Array): string {\n\tlet binary = \"\";\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number);\n\t}\n\treturn btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Decode a base64url string into a Uint8Array. */\nexport function fromBase64Url(b64: string): Uint8Array {\n\t// Restore standard base64\n\tlet base64 = b64.replace(/-/g, \"+\").replace(/_/g, \"/\");\n\t// Add padding\n\twhile (base64.length % 4 !== 0) {\n\t\tbase64 += \"=\";\n\t}\n\tconst binary = atob(base64);\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i);\n\t}\n\treturn bytes;\n}\n\n// ---------------------------------------------------------------------------\n// Random generation\n// ---------------------------------------------------------------------------\n\n/** Generate a v4 UUID using the globally available crypto.randomUUID(). */\nexport function generateId(): string {\n\treturn globalThis.crypto.randomUUID();\n}\n\n/** Generate cryptographically secure random bytes as a Uint8Array. */\nexport function randomBytes(length: number): Uint8Array {\n\tconst bytes = new Uint8Array(length);\n\tglobalThis.crypto.getRandomValues(bytes);\n\treturn bytes;\n}\n\n/** Generate cryptographically secure random bytes as a hex string. */\nexport function randomBytesHex(length: number): string {\n\treturn toHex(randomBytes(length));\n}\n\n// ---------------------------------------------------------------------------\n// Text encoding helper (internal)\n// ---------------------------------------------------------------------------\n\nconst TEXT_ENCODER = new TextEncoder();\n\nfunction toBytes(data: string | Uint8Array): ArrayBuffer {\n\tif (typeof data === \"string\") {\n\t\tconst encoded = TEXT_ENCODER.encode(data);\n\t\treturn (encoded.buffer as ArrayBuffer).slice(\n\t\t\tencoded.byteOffset,\n\t\t\tencoded.byteOffset + encoded.byteLength,\n\t\t);\n\t}\n\treturn (data.buffer as ArrayBuffer).slice(data.byteOffset, data.byteOffset + data.byteLength);\n}\n\n// ---------------------------------------------------------------------------\n// Hashing\n// ---------------------------------------------------------------------------\n\n/** SHA-256 hash, returns hex string. */\nexport async function sha256(data: string | Uint8Array): Promise<string> {\n\tconst digest = await globalThis.crypto.subtle.digest(\"SHA-256\", toBytes(data));\n\treturn toHex(new Uint8Array(digest));\n}\n\n/** SHA-256 hash, returns Uint8Array. */\nexport async function sha256Raw(data: string | Uint8Array): Promise<Uint8Array> {\n\tconst digest = await globalThis.crypto.subtle.digest(\"SHA-256\", toBytes(data));\n\treturn new Uint8Array(digest);\n}\n\n/** SHA-1 hash, returns hex string. Needed for HIBP k-anonymity. */\nexport async function sha1(data: string | Uint8Array): Promise<string> {\n\tconst digest = await globalThis.crypto.subtle.digest(\"SHA-1\", toBytes(data));\n\treturn toHex(new Uint8Array(digest));\n}\n\n// ---------------------------------------------------------------------------\n// HMAC\n// ---------------------------------------------------------------------------\n\n/** Import a secret key for HMAC operations. */\nexport async function importHmacKey(\n\tkey: string | Uint8Array,\n\thash: \"SHA-256\" | \"SHA-1\" = \"SHA-256\",\n): Promise<CryptoKey> {\n\tconst keyData = typeof key === \"string\" ? TEXT_ENCODER.encode(key) : key;\n\treturn globalThis.crypto.subtle.importKey(\n\t\t\"raw\",\n\t\t(keyData.buffer as ArrayBuffer).slice(\n\t\t\tkeyData.byteOffset,\n\t\t\tkeyData.byteOffset + keyData.byteLength,\n\t\t),\n\t\t{ name: \"HMAC\", hash: { name: hash } },\n\t\tfalse,\n\t\t[\"sign\", \"verify\"],\n\t);\n}\n\n/** HMAC-SHA256 sign, returns hex string. */\nexport async function hmacSha256(\n\tkey: string | Uint8Array,\n\tdata: string | Uint8Array,\n): Promise<string> {\n\tconst cryptoKey = await importHmacKey(key, \"SHA-256\");\n\tconst signature = await globalThis.crypto.subtle.sign(\"HMAC\", cryptoKey, toBytes(data));\n\treturn toHex(new Uint8Array(signature));\n}\n\n/** HMAC-SHA256 sign, returns Uint8Array. */\nexport async function hmacSha256Raw(\n\tkey: string | Uint8Array,\n\tdata: string | Uint8Array,\n): Promise<Uint8Array> {\n\tconst cryptoKey = await importHmacKey(key, \"SHA-256\");\n\tconst signature = await globalThis.crypto.subtle.sign(\"HMAC\", cryptoKey, toBytes(data));\n\treturn new Uint8Array(signature);\n}\n\n/** HMAC-SHA1 sign, returns Uint8Array (needed for TOTP per RFC 6238). */\nexport async function hmacSha1Raw(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {\n\tconst cryptoKey = await importHmacKey(key, \"SHA-1\");\n\tconst buf = (data.buffer as ArrayBuffer).slice(\n\t\tdata.byteOffset,\n\t\tdata.byteOffset + data.byteLength,\n\t);\n\tconst signature = await globalThis.crypto.subtle.sign(\"HMAC\", cryptoKey, buf);\n\treturn new Uint8Array(signature);\n}\n\n// ---------------------------------------------------------------------------\n// PBKDF2 password hashing\n// ---------------------------------------------------------------------------\n\nconst PBKDF2_ITERATIONS = 100_000; // CF Workers caps at 100K; OWASP recommends 600K for Node.js\nconst PBKDF2_KEY_LENGTH = 64; // bytes\nconst PBKDF2_SALT_LENGTH = 32; // bytes\n\n/**\n * Hash a password using PBKDF2-SHA256.\n *\n * Returns a string in the format: `pbkdf2:iterations:salt_hex:hash_hex`\n * which is safe to store in the database.\n */\nexport async function pbkdf2Hash(\n\tpassword: string,\n\tsalt?: Uint8Array,\n\titerations?: number,\n): Promise<string> {\n\tconst actualSalt = salt ?? randomBytes(PBKDF2_SALT_LENGTH);\n\tconst actualIterations = iterations ?? PBKDF2_ITERATIONS;\n\n\tconst keyMaterial = await globalThis.crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tTEXT_ENCODER.encode(password),\n\t\t\"PBKDF2\",\n\t\tfalse,\n\t\t[\"deriveBits\"],\n\t);\n\n\tconst saltBuf = (actualSalt.buffer as ArrayBuffer).slice(\n\t\tactualSalt.byteOffset,\n\t\tactualSalt.byteOffset + actualSalt.byteLength,\n\t);\n\tconst derived = await globalThis.crypto.subtle.deriveBits(\n\t\t{\n\t\t\tname: \"PBKDF2\",\n\t\t\tsalt: saltBuf,\n\t\t\titerations: actualIterations,\n\t\t\thash: \"SHA-256\",\n\t\t},\n\t\tkeyMaterial,\n\t\tPBKDF2_KEY_LENGTH * 8,\n\t);\n\n\treturn `pbkdf2:${actualIterations}:${toHex(actualSalt)}:${toHex(new Uint8Array(derived))}`;\n}\n\n/**\n * Verify a password against a stored PBKDF2 hash.\n *\n * Supports the `pbkdf2:iterations:salt:hash` format produced by `pbkdf2Hash`.\n */\nexport async function pbkdf2Verify(password: string, stored: string): Promise<boolean> {\n\tconst parts = stored.split(\":\");\n\tif (parts.length !== 4 || parts[0] !== \"pbkdf2\") {\n\t\treturn false;\n\t}\n\n\tconst iterations = parseInt(parts[1] as string, 10);\n\tconst salt = fromHex(parts[2] as string);\n\tconst storedHash = fromHex(parts[3] as string);\n\n\tif (Number.isNaN(iterations)) return false;\n\n\tconst keyMaterial = await globalThis.crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tTEXT_ENCODER.encode(password),\n\t\t\"PBKDF2\",\n\t\tfalse,\n\t\t[\"deriveBits\"],\n\t);\n\n\tconst saltBuf = (salt.buffer as ArrayBuffer).slice(\n\t\tsalt.byteOffset,\n\t\tsalt.byteOffset + salt.byteLength,\n\t);\n\tconst derived = await globalThis.crypto.subtle.deriveBits(\n\t\t{\n\t\t\tname: \"PBKDF2\",\n\t\t\tsalt: saltBuf,\n\t\t\titerations,\n\t\t\thash: \"SHA-256\",\n\t\t},\n\t\tkeyMaterial,\n\t\tstoredHash.length * 8,\n\t);\n\n\treturn constantTimeEqual(new Uint8Array(derived), storedHash);\n}\n\n// ---------------------------------------------------------------------------\n// Constant-time comparison\n// ---------------------------------------------------------------------------\n\n/**\n * Constant-time comparison of two Uint8Arrays.\n * Returns false immediately if lengths differ (length is not secret).\n */\nexport function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {\n\tif (a.byteLength !== b.byteLength) {\n\t\treturn false;\n\t}\n\tlet diff = 0;\n\tfor (let i = 0; i < a.byteLength; i++) {\n\t\tdiff |= (a[i] as number) ^ (b[i] as number);\n\t}\n\treturn diff === 0;\n}\n","import { generateId } from \"../crypto/web-crypto.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface RedirectConfig {\n\t/** Cookie name for storing the redirect chain (default: \"kavach_redirect\") */\n\tcookieName?: string;\n\t/** Max age in seconds for the redirect cookie (default: 600 = 10 min) */\n\tmaxAge?: number;\n\t/** Default path when no redirect is stored (default: \"/\") */\n\tdefaultPath?: string;\n\t/** Paths that should never be stored as destinations (auth pages, etc.) */\n\texcludePaths?: string[];\n\t/** Whether to preserve query params from the original URL (default: true) */\n\tpreserveQuery?: boolean;\n\t/** Whether to preserve hash fragments (default: true) */\n\tpreserveHash?: boolean;\n\t/** Max chain depth to prevent infinite loops (default: 10) */\n\tmaxDepth?: number;\n\t/** Custom cookie options */\n\tcookie?: {\n\t\thttpOnly?: boolean;\n\t\tsecure?: boolean;\n\t\tsameSite?: \"lax\" | \"strict\" | \"none\";\n\t\tpath?: string;\n\t\tdomain?: string;\n\t};\n}\n\nexport interface RedirectEntry {\n\t/** Unique ID for this entry */\n\tid: string;\n\t/** The URL path (e.g., \"/dashboard/project/abc\") */\n\tpath: string;\n\t/** Query parameters as a record */\n\tquery: Record<string, string>;\n\t/** Hash fragment without # */\n\thash: string;\n\t/** When this entry was created (epoch ms) */\n\tcreatedAt: number;\n\t/** Optional label for debugging (\"onboarding\", \"verify-email\", etc.) */\n\tlabel?: string;\n}\n\nexport interface RedirectChainState {\n\t/** The original destination the user was trying to reach */\n\torigin: RedirectEntry;\n\t/** Stack of intermediate steps (LIFO -- last pushed = first popped) */\n\tsteps: RedirectEntry[];\n\t/** Chain creation time */\n\tcreatedAt: number;\n}\n\nexport interface RedirectChainManager {\n\t/**\n\t * Capture the current request's URL as the origin destination.\n\t * Call this in your auth middleware when redirecting to sign-in.\n\t * Returns the Set-Cookie header value.\n\t *\n\t * If a chain already exists (user refreshed sign-in page), keeps the existing origin.\n\t */\n\tcapture(request: Request): string;\n\n\t/**\n\t * Push an intermediate step onto the chain.\n\t * Use this after sign-up to add onboarding, email verification, etc.\n\t * Returns the updated Set-Cookie header value.\n\t *\n\t * Example: after sign-up, push \"/onboarding\" then \"/complete-profile\"\n\t * When onboarding completes, pop() returns \"/complete-profile\"\n\t * When profile completes, pop() returns the original destination\n\t */\n\tpush(path: string, options?: { label?: string; query?: Record<string, string> }): string;\n\n\t/**\n\t * Pop the next destination from the chain.\n\t * If there are intermediate steps, returns the next step.\n\t * If no steps remain, returns the original destination.\n\t * If chain is empty, returns the defaultPath.\n\t *\n\t * Returns { url, done, clearCookie }\n\t * - url: the full URL path with query params\n\t * - done: true when the chain is fully consumed\n\t * - clearCookie: Set-Cookie header to clear the cookie (only when chain is fully consumed)\n\t */\n\tpop(request: Request): { url: string; done: boolean; clearCookie: string | null };\n\n\t/**\n\t * Peek at the next destination without consuming it.\n\t */\n\tpeek(request: Request): { url: string; remaining: number } | null;\n\n\t/**\n\t * Get the original destination (the URL the user first tried to visit).\n\t * Returns null if no chain exists.\n\t */\n\tgetOrigin(request: Request): RedirectEntry | null;\n\n\t/**\n\t * Clear the entire chain. Returns the Set-Cookie header to clear the cookie.\n\t */\n\tclear(): string;\n\n\t/**\n\t * Parse the chain state from a request's cookies.\n\t * Returns null if no chain exists or if it's expired.\n\t */\n\tparse(request: Request): RedirectChainState | null;\n\n\t/**\n\t * Build a full URL from a RedirectEntry (path + query + hash).\n\t */\n\tbuildUrl(entry: RedirectEntry): string;\n\n\t/**\n\t * Create a RedirectEntry from a URL string or Request.\n\t */\n\tcreateEntry(url: string | Request, label?: string): RedirectEntry;\n}\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_COOKIE_NAME = \"kavach_redirect\";\nconst DEFAULT_MAX_AGE = 600; // 10 minutes\nconst DEFAULT_PATH = \"/\";\nconst DEFAULT_MAX_DEPTH = 10;\nconst DEFAULT_EXCLUDE_PATHS = [\n\t\"/sign-in\",\n\t\"/sign-up\",\n\t\"/forgot-password\",\n\t\"/reset-password\",\n\t\"/verify-email\",\n\t\"/api/\",\n];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nfunction encodeState(state: RedirectChainState): string {\n\tconst json = JSON.stringify(state);\n\tconst bytes = encoder.encode(json);\n\t// base64url encode without Buffer\n\tlet binary = \"\";\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number);\n\t}\n\treturn btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nfunction decodeState(encoded: string): RedirectChainState | null {\n\ttry {\n\t\tlet base64 = encoded.replace(/-/g, \"+\").replace(/_/g, \"/\");\n\t\twhile (base64.length % 4 !== 0) {\n\t\t\tbase64 += \"=\";\n\t\t}\n\t\tconst binary = atob(base64);\n\t\tconst bytes = new Uint8Array(binary.length);\n\t\tfor (let i = 0; i < binary.length; i++) {\n\t\t\tbytes[i] = binary.charCodeAt(i);\n\t\t}\n\t\tconst json = decoder.decode(bytes);\n\t\tconst parsed: unknown = JSON.parse(json);\n\t\tif (!isValidChainState(parsed)) return null;\n\t\treturn parsed;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction isValidChainState(value: unknown): value is RedirectChainState {\n\tif (typeof value !== \"object\" || value === null) return false;\n\tconst obj = value as Record<string, unknown>;\n\tif (typeof obj.createdAt !== \"number\") return false;\n\tif (!isValidEntry(obj.origin)) return false;\n\tif (!Array.isArray(obj.steps)) return false;\n\tfor (const step of obj.steps) {\n\t\tif (!isValidEntry(step)) return false;\n\t}\n\treturn true;\n}\n\nfunction isValidEntry(value: unknown): value is RedirectEntry {\n\tif (typeof value !== \"object\" || value === null) return false;\n\tconst obj = value as Record<string, unknown>;\n\treturn (\n\t\ttypeof obj.id === \"string\" &&\n\t\ttypeof obj.path === \"string\" &&\n\t\ttypeof obj.query === \"object\" &&\n\t\tobj.query !== null &&\n\t\ttypeof obj.hash === \"string\" &&\n\t\ttypeof obj.createdAt === \"number\"\n\t);\n}\n\nfunction parseCookies(request: Request): Record<string, string> {\n\tconst header = request.headers.get(\"cookie\");\n\tif (!header) return {};\n\tconst cookies: Record<string, string> = {};\n\tfor (const pair of header.split(\";\")) {\n\t\tconst eqIdx = pair.indexOf(\"=\");\n\t\tif (eqIdx === -1) continue;\n\t\tconst key = pair.slice(0, eqIdx).trim();\n\t\tconst val = pair.slice(eqIdx + 1).trim();\n\t\tcookies[key] = val;\n\t}\n\treturn cookies;\n}\n\nfunction isExcluded(path: string, excludePaths: string[]): boolean {\n\tfor (const excluded of excludePaths) {\n\t\tif (excluded.endsWith(\"/\")) {\n\t\t\t// Prefix match for paths ending in /\n\t\t\tif (path.startsWith(excluded) || path === excluded.slice(0, -1)) return true;\n\t\t} else {\n\t\t\tif (path === excluded) return true;\n\t\t}\n\t}\n\treturn false;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/** Create a redirect chain manager with the given config. */\nexport function createRedirectChain(config?: RedirectConfig): RedirectChainManager {\n\tconst cookieName = config?.cookieName ?? DEFAULT_COOKIE_NAME;\n\tconst maxAge = config?.maxAge ?? DEFAULT_MAX_AGE;\n\tconst defaultPath = config?.defaultPath ?? DEFAULT_PATH;\n\tconst excludePaths = config?.excludePaths ?? DEFAULT_EXCLUDE_PATHS;\n\tconst preserveQuery = config?.preserveQuery ?? true;\n\tconst preserveHash = config?.preserveHash ?? true;\n\tconst maxDepth = config?.maxDepth ?? DEFAULT_MAX_DEPTH;\n\tconst cookieOpts = {\n\t\thttpOnly: config?.cookie?.httpOnly ?? true,\n\t\tsecure: config?.cookie?.secure ?? true,\n\t\tsameSite: config?.cookie?.sameSite ?? (\"lax\" as const),\n\t\tpath: config?.cookie?.path ?? \"/\",\n\t\tdomain: config?.cookie?.domain,\n\t};\n\n\t// Internal state held in memory for push() which doesn't take a request.\n\t// Updated on every parse/capture/push/pop call.\n\tlet currentState: RedirectChainState | null = null;\n\n\tfunction buildCookieHeader(value: string, age: number): string {\n\t\tconst parts = [`${cookieName}=${value}`, `Path=${cookieOpts.path}`, `Max-Age=${age}`];\n\t\tif (cookieOpts.httpOnly) parts.push(\"HttpOnly\");\n\t\tif (cookieOpts.secure) parts.push(\"Secure\");\n\t\tparts.push(\n\t\t\t`SameSite=${cookieOpts.sameSite.charAt(0).toUpperCase()}${cookieOpts.sameSite.slice(1)}`,\n\t\t);\n\t\tif (cookieOpts.domain) parts.push(`Domain=${cookieOpts.domain}`);\n\t\treturn parts.join(\"; \");\n\t}\n\n\tfunction buildClearCookie(): string {\n\t\treturn buildCookieHeader(\"\", 0);\n\t}\n\n\tfunction serializeAndSetCookie(state: RedirectChainState): string {\n\t\tcurrentState = state;\n\t\tconst encoded = encodeState(state);\n\t\treturn buildCookieHeader(encoded, maxAge);\n\t}\n\n\tfunction parseFromRequest(request: Request): RedirectChainState | null {\n\t\tconst cookies = parseCookies(request);\n\t\tconst raw = cookies[cookieName];\n\t\tif (!raw) return null;\n\t\tconst state = decodeState(raw);\n\t\tif (!state) return null;\n\n\t\t// Check expiration\n\t\tconst elapsed = Date.now() - state.createdAt;\n\t\tif (elapsed > maxAge * 1000) return null;\n\n\t\tcurrentState = state;\n\t\treturn state;\n\t}\n\n\tfunction createEntryFromUrl(url: string | Request, label?: string): RedirectEntry {\n\t\tlet parsed: URL;\n\t\tif (typeof url === \"string\") {\n\t\t\t// Handle relative paths by providing a base\n\t\t\tif (url.startsWith(\"/\")) {\n\t\t\t\tparsed = new URL(url, \"http://localhost\");\n\t\t\t} else {\n\t\t\t\tparsed = new URL(url);\n\t\t\t}\n\t\t} else {\n\t\t\tparsed = new URL(url.url);\n\t\t}\n\n\t\tconst query: Record<string, string> = {};\n\t\tif (preserveQuery) {\n\t\t\tparsed.searchParams.forEach((value, key) => {\n\t\t\t\tquery[key] = value;\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\tid: generateId(),\n\t\t\tpath: parsed.pathname,\n\t\t\tquery,\n\t\t\thash: preserveHash ? parsed.hash.replace(/^#/, \"\") : \"\",\n\t\t\tcreatedAt: Date.now(),\n\t\t\tlabel,\n\t\t};\n\t}\n\n\tfunction buildUrlFromEntry(entry: RedirectEntry): string {\n\t\tlet url = entry.path;\n\t\tconst params = Object.entries(entry.query);\n\t\tif (params.length > 0) {\n\t\t\tconst searchParams = new URLSearchParams();\n\t\t\tfor (const [key, value] of params) {\n\t\t\t\tsearchParams.set(key, value);\n\t\t\t}\n\t\t\turl += `?${searchParams.toString()}`;\n\t\t}\n\t\tif (entry.hash) {\n\t\t\turl += `#${entry.hash}`;\n\t\t}\n\t\treturn url;\n\t}\n\n\tconst manager: RedirectChainManager = {\n\t\tcapture(request: Request): string {\n\t\t\t// If chain already exists, keep the existing origin\n\t\t\tconst existing = parseFromRequest(request);\n\t\t\tif (existing) {\n\t\t\t\treturn serializeAndSetCookie(existing);\n\t\t\t}\n\n\t\t\tconst entry = createEntryFromUrl(request);\n\n\t\t\t// If the captured path is excluded, use defaultPath\n\t\t\tif (isExcluded(entry.path, excludePaths)) {\n\t\t\t\tentry.path = defaultPath;\n\t\t\t\tentry.query = {};\n\t\t\t\tentry.hash = \"\";\n\t\t\t}\n\n\t\t\tconst state: RedirectChainState = {\n\t\t\t\torigin: entry,\n\t\t\t\tsteps: [],\n\t\t\t\tcreatedAt: Date.now(),\n\t\t\t};\n\n\t\t\treturn serializeAndSetCookie(state);\n\t\t},\n\n\t\tpush(path: string, options?: { label?: string; query?: Record<string, string> }): string {\n\t\t\t// If no current state, create one with defaultPath as origin\n\t\t\tif (!currentState) {\n\t\t\t\tcurrentState = {\n\t\t\t\t\torigin: {\n\t\t\t\t\t\tid: generateId(),\n\t\t\t\t\t\tpath: defaultPath,\n\t\t\t\t\t\tquery: {},\n\t\t\t\t\t\thash: \"\",\n\t\t\t\t\t\tcreatedAt: Date.now(),\n\t\t\t\t\t},\n\t\t\t\t\tsteps: [],\n\t\t\t\t\tcreatedAt: Date.now(),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Check max depth\n\t\t\tif (currentState.steps.length >= maxDepth) {\n\t\t\t\t// At max depth, just return current cookie without adding\n\t\t\t\treturn serializeAndSetCookie(currentState);\n\t\t\t}\n\n\t\t\tconst entry: RedirectEntry = {\n\t\t\t\tid: generateId(),\n\t\t\t\tpath,\n\t\t\t\tquery: options?.query ?? {},\n\t\t\t\thash: \"\",\n\t\t\t\tcreatedAt: Date.now(),\n\t\t\t\tlabel: options?.label,\n\t\t\t};\n\n\t\t\tcurrentState.steps.push(entry);\n\t\t\treturn serializeAndSetCookie(currentState);\n\t\t},\n\n\t\tpop(request: Request): { url: string; done: boolean; clearCookie: string | null } {\n\t\t\tconst state = parseFromRequest(request);\n\t\t\tif (!state) {\n\t\t\t\treturn { url: defaultPath, done: true, clearCookie: buildClearCookie() };\n\t\t\t}\n\n\t\t\t// If there are intermediate steps, pop the first one (FIFO order within the stack)\n\t\t\tif (state.steps.length > 0) {\n\t\t\t\tconst next = state.steps.shift() as RedirectEntry;\n\t\t\t\tconst url = buildUrlFromEntry(next);\n\n\t\t\t\tif (state.steps.length === 0) {\n\t\t\t\t\t// Next pop will return origin, but keep the cookie so origin is still available\n\t\t\t\t\tcurrentState = state;\n\t\t\t\t\treturn {\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tdone: false,\n\t\t\t\t\t\tclearCookie: null,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// More steps remain\n\t\t\t\treturn {\n\t\t\t\t\turl,\n\t\t\t\t\tdone: false,\n\t\t\t\t\tclearCookie: null,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// No steps left, return origin and clear the cookie\n\t\t\tconst url = buildUrlFromEntry(state.origin);\n\t\t\tcurrentState = null;\n\t\t\treturn {\n\t\t\t\turl,\n\t\t\t\tdone: true,\n\t\t\t\tclearCookie: buildClearCookie(),\n\t\t\t};\n\t\t},\n\n\t\tpeek(request: Request): { url: string; remaining: number } | null {\n\t\t\tconst state = parseFromRequest(request);\n\t\t\tif (!state) return null;\n\n\t\t\tif (state.steps.length > 0) {\n\t\t\t\tconst next = state.steps[0] as RedirectEntry;\n\t\t\t\treturn {\n\t\t\t\t\turl: buildUrlFromEntry(next),\n\t\t\t\t\tremaining: state.steps.length, // +origin is implicit\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\turl: buildUrlFromEntry(state.origin),\n\t\t\t\tremaining: 0,\n\t\t\t};\n\t\t},\n\n\t\tgetOrigin(request: Request): RedirectEntry | null {\n\t\t\tconst state = parseFromRequest(request);\n\t\t\tif (!state) return null;\n\t\t\treturn state.origin;\n\t\t},\n\n\t\tclear(): string {\n\t\t\tcurrentState = null;\n\t\t\treturn buildClearCookie();\n\t\t},\n\n\t\tparse(request: Request): RedirectChainState | null {\n\t\t\treturn parseFromRequest(request);\n\t\t},\n\n\t\tbuildUrl(entry: RedirectEntry): string {\n\t\t\treturn buildUrlFromEntry(entry);\n\t\t},\n\n\t\tcreateEntry(url: string | Request, label?: string): RedirectEntry {\n\t\t\treturn createEntryFromUrl(url, label);\n\t\t},\n\t};\n\n\treturn manager;\n}\n"]}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * IETF agentic JWT claim name constants.
3
+ *
4
+ * Sources:
5
+ * - draft-goswami-agentic-jwt-00
6
+ * - draft-liu-agent-operation-authorization-01
7
+ *
8
+ * These constants are off by default. Set `emitAgenticJwtClaims: true` in
9
+ * KavachConfig to include them in issued tokens.
10
+ */
11
+ /**
12
+ * Operational mode of an agent within a delegation chain.
13
+ *
14
+ * - `autonomous` — no human-in-the-loop; the agent acts on its own behalf.
15
+ * - `delegated` — the agent is acting under explicit delegation from another principal.
16
+ * - `supervised` — the agent acts autonomously but requires human approval for sensitive ops.
17
+ */
18
+ type AgentType = "autonomous" | "delegated" | "supervised";
19
+ /**
20
+ * Trust tier band assigned at token issuance, derived from the numeric trust
21
+ * score. Matches the five-level model in TheAuth trust scoring.
22
+ *
23
+ * Mapping (inclusive lower bound):
24
+ * score 0–19 → "unverified"
25
+ * score 20–39 → "low"
26
+ * score 40–59 → "standard"
27
+ * score 60–79 → "elevated"
28
+ * score 80+ → "high"
29
+ */
30
+ type TrustTier = "unverified" | "low" | "standard" | "elevated" | "high";
31
+ /**
32
+ * Registered claim names for agentic JWTs.
33
+ *
34
+ * All names are string literals so they can be used directly as JWT payload
35
+ * keys. Consuming code should index into issued token payloads using these
36
+ * constants rather than raw string literals.
37
+ */
38
+ declare const AGENTIC_JWT_CLAIMS: {
39
+ /**
40
+ * Stable identifier of the agent making the call.
41
+ *
42
+ * @see draft-goswami-agentic-jwt-00 §3.1
43
+ */
44
+ readonly AGENT_ID: "agent_id";
45
+ /**
46
+ * Operational mode of the agent: `autonomous`, `delegated`, or `supervised`.
47
+ *
48
+ * @see draft-goswami-agentic-jwt-00 §3.2
49
+ */
50
+ readonly AGENT_TYPE: "agent_type";
51
+ /**
52
+ * Subject principal the agent is acting on behalf of (human user or upstream agent).
53
+ *
54
+ * @see draft-goswami-agentic-jwt-00 §3.3
55
+ */
56
+ readonly ON_BEHALF_OF: "on_behalf_of";
57
+ /**
58
+ * Actor claim per RFC 8693. Identifies the current actor in an
59
+ * impersonation or delegation chain.
60
+ *
61
+ * @see draft-goswami-agentic-jwt-00 §3.4
62
+ * @see RFC 8693
63
+ */
64
+ readonly ACT: "act";
65
+ /**
66
+ * Authorized future actors for delegation chains (RFC 8693 `may_act`).
67
+ *
68
+ * @see draft-goswami-agentic-jwt-00 §3.5
69
+ * @see RFC 8693
70
+ */
71
+ readonly MAY_ACT: "may_act";
72
+ /**
73
+ * Trust score band at token issuance (e.g. `standard`, `elevated`).
74
+ *
75
+ * @see draft-goswami-agentic-jwt-00 §3.6
76
+ */
77
+ readonly TRUST_TIER: "trust_tier";
78
+ /**
79
+ * Correlation id for tracing this token back to an entry in the audit log.
80
+ *
81
+ * @see draft-goswami-agentic-jwt-00 §3.7
82
+ */
83
+ readonly AUDIT_REF: "audit_ref";
84
+ /**
85
+ * Per-tool budget or rate constraints encoded as a structured object.
86
+ *
87
+ * @see draft-goswami-agentic-jwt-00 §3.8
88
+ */
89
+ readonly TOOL_CONSTRAINTS: "tool_constraints";
90
+ /**
91
+ * Workload Identity Token (WIT). Present only when three-layer cryptographic
92
+ * binding is active (draft-liu §4.2). Absent in standard issuance paths.
93
+ *
94
+ * @see draft-liu-agent-operation-authorization-01 §4.2
95
+ * TODO(v3): populate from WIT issuance when three-layer binding is implemented.
96
+ */
97
+ readonly WORKLOAD_BINDING: "wit";
98
+ /**
99
+ * The scoped operation this token authorizes (e.g. `read:documents`).
100
+ *
101
+ * @see draft-liu-agent-operation-authorization-01 §4.3
102
+ */
103
+ readonly OPERATION: "operation";
104
+ };
105
+ /**
106
+ * Optional shape of agentic JWT claims within a token payload.
107
+ *
108
+ * All fields are optional because they are only emitted when
109
+ * `emitAgenticJwtClaims` is enabled and the relevant context is available
110
+ * on the issuance path.
111
+ */
112
+ interface AgenticJwtClaims {
113
+ /** Stable agent identifier. */
114
+ agent_id?: string;
115
+ /** Operational mode of the agent. */
116
+ agent_type?: AgentType;
117
+ /** Principal the agent is acting for. */
118
+ on_behalf_of?: string;
119
+ /** RFC 8693 actor claim (current actor in a delegation chain). */
120
+ act?: Record<string, unknown>;
121
+ /** RFC 8693 may_act claim (authorized future actors). */
122
+ may_act?: Record<string, unknown>;
123
+ /** Trust tier band at issuance time. */
124
+ trust_tier?: TrustTier;
125
+ /** Audit log correlation id. */
126
+ audit_ref?: string;
127
+ /** Per-tool budget or rate constraints. */
128
+ tool_constraints?: Record<string, unknown>;
129
+ /**
130
+ * Workload Identity Token.
131
+ * Absent unless three-layer binding is active.
132
+ * TODO(v3): populate when draft-liu three-layer binding is implemented.
133
+ */
134
+ wit?: string;
135
+ /** Scoped operation this token authorizes. */
136
+ operation?: string;
137
+ }
138
+
139
+ export { AGENTIC_JWT_CLAIMS, type AgentType, type AgenticJwtClaims, type TrustTier };
@@ -0,0 +1,72 @@
1
+ // src/standards/claims.ts
2
+ var AGENTIC_JWT_CLAIMS = {
3
+ /**
4
+ * Stable identifier of the agent making the call.
5
+ *
6
+ * @see draft-goswami-agentic-jwt-00 §3.1
7
+ */
8
+ AGENT_ID: "agent_id",
9
+ /**
10
+ * Operational mode of the agent: `autonomous`, `delegated`, or `supervised`.
11
+ *
12
+ * @see draft-goswami-agentic-jwt-00 §3.2
13
+ */
14
+ AGENT_TYPE: "agent_type",
15
+ /**
16
+ * Subject principal the agent is acting on behalf of (human user or upstream agent).
17
+ *
18
+ * @see draft-goswami-agentic-jwt-00 §3.3
19
+ */
20
+ ON_BEHALF_OF: "on_behalf_of",
21
+ /**
22
+ * Actor claim per RFC 8693. Identifies the current actor in an
23
+ * impersonation or delegation chain.
24
+ *
25
+ * @see draft-goswami-agentic-jwt-00 §3.4
26
+ * @see RFC 8693
27
+ */
28
+ ACT: "act",
29
+ /**
30
+ * Authorized future actors for delegation chains (RFC 8693 `may_act`).
31
+ *
32
+ * @see draft-goswami-agentic-jwt-00 §3.5
33
+ * @see RFC 8693
34
+ */
35
+ MAY_ACT: "may_act",
36
+ /**
37
+ * Trust score band at token issuance (e.g. `standard`, `elevated`).
38
+ *
39
+ * @see draft-goswami-agentic-jwt-00 §3.6
40
+ */
41
+ TRUST_TIER: "trust_tier",
42
+ /**
43
+ * Correlation id for tracing this token back to an entry in the audit log.
44
+ *
45
+ * @see draft-goswami-agentic-jwt-00 §3.7
46
+ */
47
+ AUDIT_REF: "audit_ref",
48
+ /**
49
+ * Per-tool budget or rate constraints encoded as a structured object.
50
+ *
51
+ * @see draft-goswami-agentic-jwt-00 §3.8
52
+ */
53
+ TOOL_CONSTRAINTS: "tool_constraints",
54
+ /**
55
+ * Workload Identity Token (WIT). Present only when three-layer cryptographic
56
+ * binding is active (draft-liu §4.2). Absent in standard issuance paths.
57
+ *
58
+ * @see draft-liu-agent-operation-authorization-01 §4.2
59
+ * TODO(v3): populate from WIT issuance when three-layer binding is implemented.
60
+ */
61
+ WORKLOAD_BINDING: "wit",
62
+ /**
63
+ * The scoped operation this token authorizes (e.g. `read:documents`).
64
+ *
65
+ * @see draft-liu-agent-operation-authorization-01 §4.3
66
+ */
67
+ OPERATION: "operation"
68
+ };
69
+
70
+ export { AGENTIC_JWT_CLAIMS };
71
+ //# sourceMappingURL=index.js.map
72
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/standards/claims.ts"],"names":[],"mappings":";AAgDO,IAAM,kBAAA,GAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjC,QAAA,EAAU,UAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOV,UAAA,EAAY,YAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOZ,YAAA,EAAc,cAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASd,GAAA,EAAK,KAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQL,OAAA,EAAS,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT,UAAA,EAAY,YAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOZ,SAAA,EAAW,WAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOX,gBAAA,EAAkB,kBAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASlB,gBAAA,EAAkB,KAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOlB,SAAA,EAAW;AACZ","file":"index.js","sourcesContent":["/**\n * IETF agentic JWT claim name constants.\n *\n * Sources:\n * - draft-goswami-agentic-jwt-00\n * - draft-liu-agent-operation-authorization-01\n *\n * These constants are off by default. Set `emitAgenticJwtClaims: true` in\n * KavachConfig to include them in issued tokens.\n */\n\n// ---------------------------------------------------------------------------\n// Named types\n// ---------------------------------------------------------------------------\n\n/**\n * Operational mode of an agent within a delegation chain.\n *\n * - `autonomous` — no human-in-the-loop; the agent acts on its own behalf.\n * - `delegated` — the agent is acting under explicit delegation from another principal.\n * - `supervised` — the agent acts autonomously but requires human approval for sensitive ops.\n */\nexport type AgentType = \"autonomous\" | \"delegated\" | \"supervised\";\n\n/**\n * Trust tier band assigned at token issuance, derived from the numeric trust\n * score. Matches the five-level model in TheAuth trust scoring.\n *\n * Mapping (inclusive lower bound):\n * score 0–19 → \"unverified\"\n * score 20–39 → \"low\"\n * score 40–59 → \"standard\"\n * score 60–79 → \"elevated\"\n * score 80+ → \"high\"\n */\nexport type TrustTier = \"unverified\" | \"low\" | \"standard\" | \"elevated\" | \"high\";\n\n// ---------------------------------------------------------------------------\n// Claim name constants\n// ---------------------------------------------------------------------------\n\n/**\n * Registered claim names for agentic JWTs.\n *\n * All names are string literals so they can be used directly as JWT payload\n * keys. Consuming code should index into issued token payloads using these\n * constants rather than raw string literals.\n */\nexport const AGENTIC_JWT_CLAIMS = {\n\t/**\n\t * Stable identifier of the agent making the call.\n\t *\n\t * @see draft-goswami-agentic-jwt-00 §3.1\n\t */\n\tAGENT_ID: \"agent_id\",\n\n\t/**\n\t * Operational mode of the agent: `autonomous`, `delegated`, or `supervised`.\n\t *\n\t * @see draft-goswami-agentic-jwt-00 §3.2\n\t */\n\tAGENT_TYPE: \"agent_type\",\n\n\t/**\n\t * Subject principal the agent is acting on behalf of (human user or upstream agent).\n\t *\n\t * @see draft-goswami-agentic-jwt-00 §3.3\n\t */\n\tON_BEHALF_OF: \"on_behalf_of\",\n\n\t/**\n\t * Actor claim per RFC 8693. Identifies the current actor in an\n\t * impersonation or delegation chain.\n\t *\n\t * @see draft-goswami-agentic-jwt-00 §3.4\n\t * @see RFC 8693\n\t */\n\tACT: \"act\",\n\n\t/**\n\t * Authorized future actors for delegation chains (RFC 8693 `may_act`).\n\t *\n\t * @see draft-goswami-agentic-jwt-00 §3.5\n\t * @see RFC 8693\n\t */\n\tMAY_ACT: \"may_act\",\n\n\t/**\n\t * Trust score band at token issuance (e.g. `standard`, `elevated`).\n\t *\n\t * @see draft-goswami-agentic-jwt-00 §3.6\n\t */\n\tTRUST_TIER: \"trust_tier\",\n\n\t/**\n\t * Correlation id for tracing this token back to an entry in the audit log.\n\t *\n\t * @see draft-goswami-agentic-jwt-00 §3.7\n\t */\n\tAUDIT_REF: \"audit_ref\",\n\n\t/**\n\t * Per-tool budget or rate constraints encoded as a structured object.\n\t *\n\t * @see draft-goswami-agentic-jwt-00 §3.8\n\t */\n\tTOOL_CONSTRAINTS: \"tool_constraints\",\n\n\t/**\n\t * Workload Identity Token (WIT). Present only when three-layer cryptographic\n\t * binding is active (draft-liu §4.2). Absent in standard issuance paths.\n\t *\n\t * @see draft-liu-agent-operation-authorization-01 §4.2\n\t * TODO(v3): populate from WIT issuance when three-layer binding is implemented.\n\t */\n\tWORKLOAD_BINDING: \"wit\",\n\n\t/**\n\t * The scoped operation this token authorizes (e.g. `read:documents`).\n\t *\n\t * @see draft-liu-agent-operation-authorization-01 §4.3\n\t */\n\tOPERATION: \"operation\",\n} as const;\n\n// ---------------------------------------------------------------------------\n// Payload shape\n// ---------------------------------------------------------------------------\n\n/**\n * Optional shape of agentic JWT claims within a token payload.\n *\n * All fields are optional because they are only emitted when\n * `emitAgenticJwtClaims` is enabled and the relevant context is available\n * on the issuance path.\n */\nexport interface AgenticJwtClaims {\n\t/** Stable agent identifier. */\n\tagent_id?: string;\n\t/** Operational mode of the agent. */\n\tagent_type?: AgentType;\n\t/** Principal the agent is acting for. */\n\ton_behalf_of?: string;\n\t/** RFC 8693 actor claim (current actor in a delegation chain). */\n\tact?: Record<string, unknown>;\n\t/** RFC 8693 may_act claim (authorized future actors). */\n\tmay_act?: Record<string, unknown>;\n\t/** Trust tier band at issuance time. */\n\ttrust_tier?: TrustTier;\n\t/** Audit log correlation id. */\n\taudit_ref?: string;\n\t/** Per-tool budget or rate constraints. */\n\ttool_constraints?: Record<string, unknown>;\n\t/**\n\t * Workload Identity Token.\n\t * Absent unless three-layer binding is active.\n\t * TODO(v3): populate when draft-liu three-layer binding is implemented.\n\t */\n\twit?: string;\n\t/** Scoped operation this token authorizes. */\n\toperation?: string;\n}\n"]}