@absolutejs/auth 0.54.7 → 0.54.8
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/agents/index.d.ts +1 -0
- package/dist/agents/index.js +209 -3
- package/dist/agents/index.js.map +7 -4
- package/dist/agents/oidcAdapter.d.ts +4 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +124 -9
- package/dist/index.js.map +9 -8
- package/dist/oidc/clientIdMetadata.d.ts +32 -0
- package/dist/oidc/config.d.ts +1 -0
- package/dist/oidc/dpop.d.ts +2 -1
- package/package.json +1 -1
package/dist/agents/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './config';
|
|
2
2
|
export * from './types';
|
|
3
|
+
export * from '../oidc/clientIdMetadata';
|
|
3
4
|
export { createOidcAgentCredentialVerifier } from './oidcAdapter';
|
|
4
5
|
export { agentHasScopes, resolveAgentPrincipal } from './principal';
|
|
5
6
|
export { agentAuthChallenge, agentAuthPlugin } from './routes';
|
package/dist/agents/index.js
CHANGED
|
@@ -46,6 +46,15 @@ var __export = (target, all) => {
|
|
|
46
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
47
|
var __require = import.meta.require;
|
|
48
48
|
|
|
49
|
+
// src/constants.ts
|
|
50
|
+
var SECONDS_IN_A_MINUTE = 60, MILLISECONDS_IN_A_SECOND = 1000, MILLISECONDS_IN_A_MINUTE, MINUTES_IN_AN_HOUR = 60, HOURS_IN_A_DAY = 24, MILLISECONDS_IN_A_DAY, MILLISECONDS_IN_AN_HOUR, COOKIE_MINUTES = 30, COOKIE_DURATION, DEFAULT_MAX_SESSIONS = 1e4;
|
|
51
|
+
var init_constants = __esm(() => {
|
|
52
|
+
MILLISECONDS_IN_A_MINUTE = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE;
|
|
53
|
+
MILLISECONDS_IN_A_DAY = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE * MINUTES_IN_AN_HOUR * HOURS_IN_A_DAY;
|
|
54
|
+
MILLISECONDS_IN_AN_HOUR = MILLISECONDS_IN_A_MINUTE * MINUTES_IN_AN_HOUR;
|
|
55
|
+
COOKIE_DURATION = SECONDS_IN_A_MINUTE * COOKIE_MINUTES;
|
|
56
|
+
});
|
|
57
|
+
|
|
49
58
|
// src/agents/config.ts
|
|
50
59
|
var DEFAULT_AGENT_RESOURCE_METADATA_ROUTE = "/.well-known/oauth-protected-resource";
|
|
51
60
|
var agentProtectedResourceMetadata = (config) => ({
|
|
@@ -56,6 +65,91 @@ var agentProtectedResourceMetadata = (config) => ({
|
|
|
56
65
|
resource: config.resource,
|
|
57
66
|
scopes_supported: config.scopes
|
|
58
67
|
});
|
|
68
|
+
// src/oidc/clientIdMetadata.ts
|
|
69
|
+
var secureUrl = (value) => {
|
|
70
|
+
try {
|
|
71
|
+
return new URL(value).protocol === "https:";
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
77
|
+
const errors = [];
|
|
78
|
+
if (document.client_id !== expectedClientId)
|
|
79
|
+
errors.push("client_id does not match the metadata document URL");
|
|
80
|
+
if (!secureUrl(document.client_id))
|
|
81
|
+
errors.push("client_id must use HTTPS");
|
|
82
|
+
if (!Array.isArray(document.redirect_uris) || document.redirect_uris.length === 0)
|
|
83
|
+
errors.push("redirect_uris is required");
|
|
84
|
+
for (const uri of document.redirect_uris ?? []) {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = new URL(uri);
|
|
87
|
+
if (parsed.protocol !== "https:" && parsed.hostname !== "localhost")
|
|
88
|
+
errors.push("redirect URIs must use HTTPS or localhost");
|
|
89
|
+
} catch {
|
|
90
|
+
errors.push("redirect URI is invalid");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const [name, value] of [
|
|
94
|
+
["client_uri", document.client_uri],
|
|
95
|
+
["logo_uri", document.logo_uri],
|
|
96
|
+
["policy_uri", document.policy_uri],
|
|
97
|
+
["tos_uri", document.tos_uri],
|
|
98
|
+
["jwks_uri", document.jwks_uri]
|
|
99
|
+
]) {
|
|
100
|
+
if (value !== undefined && !secureUrl(value))
|
|
101
|
+
errors.push(`${name} must use HTTPS`);
|
|
102
|
+
}
|
|
103
|
+
return errors;
|
|
104
|
+
};
|
|
105
|
+
var clientIdMetadataToOAuthClient = (document) => ({
|
|
106
|
+
clientId: document.client_id,
|
|
107
|
+
grantTypes: document.grant_types ?? ["authorization_code", "refresh_token"],
|
|
108
|
+
...document.jwks === undefined ? {} : { jwks: document.jwks.keys },
|
|
109
|
+
...document.jwks_uri === undefined ? {} : { jwksUri: document.jwks_uri },
|
|
110
|
+
name: document.client_name ?? new URL(document.client_id).hostname,
|
|
111
|
+
redirectUris: [...document.redirect_uris],
|
|
112
|
+
scopes: document.scope?.split(" ").filter(Boolean) ?? []
|
|
113
|
+
});
|
|
114
|
+
var createClientIdMetadataResolver = ({
|
|
115
|
+
fetch: fetcher,
|
|
116
|
+
allow = async () => true,
|
|
117
|
+
cacheTtlMs = 5 * 60 * 1000,
|
|
118
|
+
maxBytes = 5 * 1024,
|
|
119
|
+
now = Date.now
|
|
120
|
+
}) => {
|
|
121
|
+
const cache = new Map;
|
|
122
|
+
return async (clientId) => {
|
|
123
|
+
if (!secureUrl(clientId) || !await allow(clientId))
|
|
124
|
+
return;
|
|
125
|
+
const cached = cache.get(clientId);
|
|
126
|
+
if (cached !== undefined && cached.expiresAt > now())
|
|
127
|
+
return cached.client;
|
|
128
|
+
const response = await fetcher(clientId, {
|
|
129
|
+
headers: { accept: "application/json" },
|
|
130
|
+
redirect: "error"
|
|
131
|
+
});
|
|
132
|
+
if (!response.ok)
|
|
133
|
+
return;
|
|
134
|
+
const declaredLength = Number(response.headers.get("content-length") ?? "0");
|
|
135
|
+
if (declaredLength > maxBytes)
|
|
136
|
+
return;
|
|
137
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
138
|
+
if (bytes.byteLength > maxBytes)
|
|
139
|
+
return;
|
|
140
|
+
let document;
|
|
141
|
+
try {
|
|
142
|
+
document = JSON.parse(new TextDecoder().decode(bytes));
|
|
143
|
+
} catch {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (validateClientIdMetadataDocument(document, clientId).length > 0)
|
|
147
|
+
return;
|
|
148
|
+
const client = clientIdMetadataToOAuthClient(document);
|
|
149
|
+
cache.set(clientId, { client, expiresAt: now() + cacheTtlMs });
|
|
150
|
+
return client;
|
|
151
|
+
};
|
|
152
|
+
};
|
|
59
153
|
// src/oidc/keys.ts
|
|
60
154
|
var ENCODER = new TextEncoder;
|
|
61
155
|
var ES256 = { hash: "SHA-256", name: "ECDSA" };
|
|
@@ -112,6 +206,96 @@ var verifyJwt = async (token, publicJwk) => {
|
|
|
112
206
|
};
|
|
113
207
|
};
|
|
114
208
|
|
|
209
|
+
// src/oidc/dpop.ts
|
|
210
|
+
init_constants();
|
|
211
|
+
var DEFAULT_MAX_AGE_MS = 60000;
|
|
212
|
+
var SECONDS_TO_MS = 1000;
|
|
213
|
+
var NONCE_WINDOW_SECONDS = 120;
|
|
214
|
+
var NONCE_WINDOW_MS = NONCE_WINDOW_SECONDS * MILLISECONDS_IN_A_SECOND;
|
|
215
|
+
var NONCE_PREVIOUS_WINDOWS_ACCEPTED = 1;
|
|
216
|
+
var hmacSha256 = async (secret, message) => {
|
|
217
|
+
const encoder = new TextEncoder;
|
|
218
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { hash: "SHA-256", name: "HMAC" }, false, ["sign"]);
|
|
219
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(message));
|
|
220
|
+
return Buffer.from(new Uint8Array(signature)).toString("base64url");
|
|
221
|
+
};
|
|
222
|
+
var extractDpopNonceClaim = (proof) => {
|
|
223
|
+
const [, payloadSegment] = proof.split(".");
|
|
224
|
+
if (payloadSegment === undefined)
|
|
225
|
+
return;
|
|
226
|
+
try {
|
|
227
|
+
const payload = JSON.parse(Buffer.from(payloadSegment, "base64url").toString("utf8"));
|
|
228
|
+
if (typeof payload !== "object" || payload === null)
|
|
229
|
+
return;
|
|
230
|
+
const value = payload.nonce;
|
|
231
|
+
return typeof value === "string" ? value : undefined;
|
|
232
|
+
} catch {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
var mintDpopNonce = async ({
|
|
237
|
+
now = Date.now(),
|
|
238
|
+
secret
|
|
239
|
+
}) => {
|
|
240
|
+
const window = Math.floor(now / NONCE_WINDOW_MS);
|
|
241
|
+
return hmacSha256(secret, String(window));
|
|
242
|
+
};
|
|
243
|
+
var verifyDpopNonce = async ({
|
|
244
|
+
now = Date.now(),
|
|
245
|
+
nonce,
|
|
246
|
+
secret
|
|
247
|
+
}) => {
|
|
248
|
+
const currentWindow = Math.floor(now / NONCE_WINDOW_MS);
|
|
249
|
+
const candidates = await Promise.all(Array.from({ length: NONCE_PREVIOUS_WINDOWS_ACCEPTED + 1 }, (_, offset) => hmacSha256(secret, String(currentWindow - offset))));
|
|
250
|
+
return candidates.some((expected) => expected === nonce);
|
|
251
|
+
};
|
|
252
|
+
var decodeHeader = (segment) => JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
|
|
253
|
+
var normalizeHtu = (value) => {
|
|
254
|
+
try {
|
|
255
|
+
const url = new URL(String(value));
|
|
256
|
+
return `${url.origin}${url.pathname}`;
|
|
257
|
+
} catch {
|
|
258
|
+
return "";
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
var verifyDpopProof = async ({
|
|
262
|
+
accessToken,
|
|
263
|
+
htm,
|
|
264
|
+
htu,
|
|
265
|
+
isUsedJti,
|
|
266
|
+
maxAgeMs = DEFAULT_MAX_AGE_MS,
|
|
267
|
+
now = Date.now(),
|
|
268
|
+
proof
|
|
269
|
+
}) => {
|
|
270
|
+
if (proof === undefined)
|
|
271
|
+
return;
|
|
272
|
+
const [headerSegment] = proof.split(".");
|
|
273
|
+
if (headerSegment === undefined)
|
|
274
|
+
return;
|
|
275
|
+
const header = decodeHeader(headerSegment);
|
|
276
|
+
if (header?.typ !== "dpop+jwt" || header.alg !== "ES256" || header.jwk === undefined) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const verified = await verifyJwt(proof, header.jwk);
|
|
280
|
+
if (verified === undefined)
|
|
281
|
+
return;
|
|
282
|
+
const { payload } = verified;
|
|
283
|
+
if (accessToken !== undefined) {
|
|
284
|
+
const expectedAth = Buffer.from(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(accessToken))).toString("base64url");
|
|
285
|
+
if (payload.ath !== expectedAth)
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const iatMs = typeof payload.iat === "number" ? payload.iat * SECONDS_TO_MS : 0;
|
|
289
|
+
if (payload.htm !== htm || normalizeHtu(payload.htu) !== normalizeHtu(htu) || iatMs === 0 || Math.abs(now - iatMs) > maxAgeMs) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const jti = typeof payload.jti === "string" ? payload.jti : undefined;
|
|
293
|
+
if (jti !== undefined && isUsedJti !== undefined && await isUsedJti(jti)) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
return { jkt: await jwkThumbprint(header.jwk), jti };
|
|
297
|
+
};
|
|
298
|
+
|
|
115
299
|
// src/agents/oidcAdapter.ts
|
|
116
300
|
var BEARER_PREFIX = "Bearer ";
|
|
117
301
|
var MS_PER_SECOND = 1000;
|
|
@@ -125,15 +309,18 @@ var readAudience = (audience) => {
|
|
|
125
309
|
};
|
|
126
310
|
var createOidcAgentCredentialVerifier = ({
|
|
127
311
|
issuer,
|
|
312
|
+
isUsedDpopJti,
|
|
313
|
+
maxDpopAgeMs,
|
|
128
314
|
publicJwk,
|
|
315
|
+
requireDpop = false,
|
|
129
316
|
resource
|
|
130
317
|
}) => {
|
|
131
318
|
const verifier = async (request) => {
|
|
132
319
|
const authorization = request.headers.get("authorization");
|
|
133
|
-
if (authorization === null || !authorization.startsWith(BEARER_PREFIX)) {
|
|
320
|
+
if (authorization === null || !authorization.startsWith(BEARER_PREFIX) && !authorization.startsWith("DPoP ")) {
|
|
134
321
|
return;
|
|
135
322
|
}
|
|
136
|
-
const token = authorization.slice(
|
|
323
|
+
const token = authorization.slice(authorization.indexOf(" ") + 1).trim();
|
|
137
324
|
if (token.length === 0)
|
|
138
325
|
return;
|
|
139
326
|
const verified = await verifyJwt(token, publicJwk);
|
|
@@ -141,6 +328,22 @@ var createOidcAgentCredentialVerifier = ({
|
|
|
141
328
|
if (payload === undefined || payload.iss !== issuer || typeof payload.exp !== "number" || payload.exp <= Math.floor(Date.now() / MS_PER_SECOND) || !readAudience(payload.aud).includes(resource) || typeof payload.client_id !== "string") {
|
|
142
329
|
return;
|
|
143
330
|
}
|
|
331
|
+
const confirmation = payload.cnf;
|
|
332
|
+
const boundJkt = typeof confirmation === "object" && confirmation !== null && typeof confirmation.jkt === "string" ? confirmation.jkt : undefined;
|
|
333
|
+
if (boundJkt !== undefined || requireDpop) {
|
|
334
|
+
if (!authorization.startsWith("DPoP "))
|
|
335
|
+
return;
|
|
336
|
+
const proof = await verifyDpopProof({
|
|
337
|
+
accessToken: token,
|
|
338
|
+
htm: request.method,
|
|
339
|
+
htu: request.url,
|
|
340
|
+
isUsedJti: isUsedDpopJti,
|
|
341
|
+
...maxDpopAgeMs === undefined ? {} : { maxAgeMs: maxDpopAgeMs },
|
|
342
|
+
proof: request.headers.get("dpop") ?? undefined
|
|
343
|
+
});
|
|
344
|
+
if (proof === undefined || boundJkt === undefined || proof.jkt !== boundJkt)
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
144
347
|
return {
|
|
145
348
|
agentId: payload.client_id,
|
|
146
349
|
claims: payload,
|
|
@@ -11278,6 +11481,7 @@ var createPostgresAgentRegistrationStore = (db) => ({
|
|
|
11278
11481
|
}
|
|
11279
11482
|
});
|
|
11280
11483
|
export {
|
|
11484
|
+
validateClientIdMetadataDocument,
|
|
11281
11485
|
resolveAgentPrincipal,
|
|
11282
11486
|
createPostgresAgentRegistrationStore,
|
|
11283
11487
|
createPostgresAgentDelegationStore,
|
|
@@ -11286,6 +11490,8 @@ export {
|
|
|
11286
11490
|
createNeonAgentDelegationStore,
|
|
11287
11491
|
createInMemoryAgentRegistrationStore,
|
|
11288
11492
|
createInMemoryAgentDelegationStore,
|
|
11493
|
+
createClientIdMetadataResolver,
|
|
11494
|
+
clientIdMetadataToOAuthClient,
|
|
11289
11495
|
agentRegistrationsTable,
|
|
11290
11496
|
agentProtectedResourceMetadata,
|
|
11291
11497
|
agentHasScopes,
|
|
@@ -11295,5 +11501,5 @@ export {
|
|
|
11295
11501
|
DEFAULT_AGENT_RESOURCE_METADATA_ROUTE
|
|
11296
11502
|
};
|
|
11297
11503
|
|
|
11298
|
-
//# debugId=
|
|
11504
|
+
//# debugId=BF8C5C40CCB4AFB564756E2164756E21
|
|
11299
11505
|
//# sourceMappingURL=index.js.map
|