@absolutejs/auth 0.56.13 → 0.56.14
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/README.md +6 -0
- package/dist/agents/config.d.ts +5 -0
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.js +108 -6
- package/dist/agents/index.js.map +6 -5
- package/dist/agents/oauthGuide.d.ts +18 -0
- package/dist/agents/routes.d.ts +1 -66
- package/dist/index.d.ts +2 -20
- package/dist/index.js +139 -17
- package/dist/index.js.map +7 -6
- package/dist/server.js +136 -17
- package/dist/server.js.map +7 -6
- package/docs/AGENT-AUTH.md +7 -0
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -3206,6 +3206,95 @@ var agentAuthContextPlugin = (config) => new Elysia2({
|
|
|
3206
3206
|
}
|
|
3207
3207
|
})).as("global");
|
|
3208
3208
|
|
|
3209
|
+
// src/agents/oauthGuide.ts
|
|
3210
|
+
var DEFAULT_GUIDE_ROUTE = "/auth.md";
|
|
3211
|
+
var secureUrl = (value, label) => {
|
|
3212
|
+
const url = new URL(value);
|
|
3213
|
+
const loopback = url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1");
|
|
3214
|
+
if (url.protocol !== "https:" && !loopback)
|
|
3215
|
+
throw new Error(`${label} must use HTTPS outside loopback development`);
|
|
3216
|
+
if (url.username || url.password || url.hash)
|
|
3217
|
+
throw new Error(`${label} cannot contain credentials or a fragment`);
|
|
3218
|
+
return url.toString();
|
|
3219
|
+
};
|
|
3220
|
+
var agentOAuthGuideRoute = (config) => config.route ?? DEFAULT_GUIDE_ROUTE;
|
|
3221
|
+
var agentOAuthGuideUrl = (config) => new URL(agentOAuthGuideRoute(config), secureUrl(config.authorizationServer, "OAuth authorization server")).toString();
|
|
3222
|
+
var normalizedResources = (config) => {
|
|
3223
|
+
if (config.resources.length === 0)
|
|
3224
|
+
throw new Error("Agent OAuth guide requires at least one resource");
|
|
3225
|
+
const resources = config.resources.map((resource) => ({
|
|
3226
|
+
metadataUrl: secureUrl(resource.metadataUrl, "Protected-resource metadata URL"),
|
|
3227
|
+
name: resource.name.trim(),
|
|
3228
|
+
resource: secureUrl(resource.resource, "OAuth protected resource"),
|
|
3229
|
+
scopes: [
|
|
3230
|
+
...new Set(resource.scopes.map((scope) => scope.trim()))
|
|
3231
|
+
].filter(Boolean)
|
|
3232
|
+
}));
|
|
3233
|
+
if (resources.some(({ name, scopes }) => !name || scopes.length === 0))
|
|
3234
|
+
throw new Error("Every agent OAuth guide resource requires a name and at least one scope");
|
|
3235
|
+
if (new Set(resources.map(({ resource }) => resource)).size !== resources.length)
|
|
3236
|
+
throw new Error("Agent OAuth guide resources must be unique");
|
|
3237
|
+
return resources;
|
|
3238
|
+
};
|
|
3239
|
+
var resourceSections = (resources) => resources.map((resource) => `### ${resource.name}
|
|
3240
|
+
|
|
3241
|
+
- Protected resource: \`${resource.resource}\`
|
|
3242
|
+
- Metadata: ${resource.metadataUrl}
|
|
3243
|
+
- Allowed scopes: ${resource.scopes.map((scope) => `\`${scope}\``).join(", ")}
|
|
3244
|
+
`).join(`
|
|
3245
|
+
`);
|
|
3246
|
+
var generateAgentOAuthGuide = (config) => {
|
|
3247
|
+
if (!config.serviceName.trim())
|
|
3248
|
+
throw new Error("Agent OAuth guide requires a service name");
|
|
3249
|
+
const authorizationServer = secureUrl(config.authorizationServer, "OAuth authorization server");
|
|
3250
|
+
const resources = normalizedResources(config);
|
|
3251
|
+
const authorizationMetadata = new URL("/.well-known/oauth-authorization-server", authorizationServer).toString();
|
|
3252
|
+
return `# OAuth access for ${config.serviceName.trim()}
|
|
3253
|
+
|
|
3254
|
+
This guide is the agent-readable companion to the service's standards-based
|
|
3255
|
+
OAuth metadata. Discovery metadata is authoritative. Do not infer endpoints,
|
|
3256
|
+
scopes, audiences, or capabilities that are not advertised.
|
|
3257
|
+
|
|
3258
|
+
## 1. Discover
|
|
3259
|
+
|
|
3260
|
+
Fetch the protected-resource metadata for the exact interface you intend to
|
|
3261
|
+
use, then fetch the authorization-server metadata at
|
|
3262
|
+
${authorizationMetadata}.
|
|
3263
|
+
|
|
3264
|
+
${resourceSections(resources)}
|
|
3265
|
+
## 2. Register the OAuth client
|
|
3266
|
+
|
|
3267
|
+
Use an existing registered client or the advertised \`registration_endpoint\`.
|
|
3268
|
+
Request only scopes listed for the selected protected resource. Never register
|
|
3269
|
+
redirect URIs, grant types, or authentication methods the metadata rejects.
|
|
3270
|
+
|
|
3271
|
+
## 3. Obtain user delegation
|
|
3272
|
+
|
|
3273
|
+
For an interactive user, use authorization code with PKCE. For a device or
|
|
3274
|
+
headless client, use the device authorization grant only when
|
|
3275
|
+
\`device_authorization_endpoint\` is advertised. Send the selected protected
|
|
3276
|
+
resource as the OAuth \`resource\` value and show the exact requested scopes to
|
|
3277
|
+
the user before consent.
|
|
3278
|
+
|
|
3279
|
+
## 4. Call the selected interface
|
|
3280
|
+
|
|
3281
|
+
Present the access token in the Authorization header. The token must name the
|
|
3282
|
+
selected resource as its audience and contain the required scope. A token for
|
|
3283
|
+
one resource or transport is not authority for another.
|
|
3284
|
+
|
|
3285
|
+
## Safety
|
|
3286
|
+
|
|
3287
|
+
- Never ask a user to send a password, passkey response, MFA code, device code,
|
|
3288
|
+
authorization code, client secret, refresh token, or access token to the
|
|
3289
|
+
agent or place one in model context.
|
|
3290
|
+
- Stop on issuer, signature, audience, expiry, delegation, or scope failure.
|
|
3291
|
+
- Treat consent denial, revocation, and disabled interfaces as final until the
|
|
3292
|
+
user explicitly starts a new authorization flow.
|
|
3293
|
+
- Discovery is not authorization, and this guide grants no capability by
|
|
3294
|
+
itself.
|
|
3295
|
+
`;
|
|
3296
|
+
};
|
|
3297
|
+
|
|
3209
3298
|
// src/agents/registration.ts
|
|
3210
3299
|
init_constants();
|
|
3211
3300
|
init_crypto();
|
|
@@ -3317,7 +3406,7 @@ var AGENT_IDENTITY_ASSERTION_TYPE = "urn:ietf:params:oauth:token-type:id-jag";
|
|
|
3317
3406
|
var DEFAULT_IDENTITY_ROUTE = "/agent/identity";
|
|
3318
3407
|
var DEFAULT_CLAIM_ROUTE = "/agent/identity/claim";
|
|
3319
3408
|
var DEFAULT_COMPLETE_ROUTE = "/agent/identity/claim/complete";
|
|
3320
|
-
var
|
|
3409
|
+
var DEFAULT_GUIDE_ROUTE2 = "/auth.md";
|
|
3321
3410
|
var DEFAULT_CLAIM_TTL_MS = 24 * 60 * MILLISECONDS_IN_A_MINUTE;
|
|
3322
3411
|
var DEFAULT_ATTEMPT_TTL_MS = 10 * MILLISECONDS_IN_A_MINUTE;
|
|
3323
3412
|
var DEFAULT_ASSERTION_TTL_MS = 60 * MILLISECONDS_IN_A_MINUTE;
|
|
@@ -3352,7 +3441,7 @@ var agentRegistrationEndpoints = (config) => {
|
|
|
3352
3441
|
return {
|
|
3353
3442
|
claimEndpoint: new URL(registration.claimRoute ?? DEFAULT_CLAIM_ROUTE, base).toString(),
|
|
3354
3443
|
completeEndpoint: new URL(registration.completeRoute ?? DEFAULT_COMPLETE_ROUTE, base).toString(),
|
|
3355
|
-
guide: new URL(registration.guideRoute ??
|
|
3444
|
+
guide: new URL(registration.guideRoute ?? DEFAULT_GUIDE_ROUTE2, base).toString(),
|
|
3356
3445
|
identityEndpoint: new URL(registration.identityRoute ?? DEFAULT_IDENTITY_ROUTE, base).toString(),
|
|
3357
3446
|
tokenEndpoint: new URL(`${oidcRoute}/token`, base).toString()
|
|
3358
3447
|
};
|
|
@@ -3980,7 +4069,18 @@ var agentAuthRoutes = (config) => {
|
|
|
3980
4069
|
if (config === undefined)
|
|
3981
4070
|
return plugin.as("global");
|
|
3982
4071
|
if (config.agentRegistration === undefined) {
|
|
3983
|
-
|
|
4072
|
+
plugin.get(config.metadataRoute ?? DEFAULT_AGENT_RESOURCE_METADATA_ROUTE, () => agentProtectedResourceMetadata(config));
|
|
4073
|
+
const { oauthGuide } = config;
|
|
4074
|
+
if (oauthGuide === undefined)
|
|
4075
|
+
return plugin.as("global");
|
|
4076
|
+
const guide = generateAgentOAuthGuide(oauthGuide);
|
|
4077
|
+
plugin.get(agentOAuthGuideRoute(oauthGuide), () => new Response(guide, {
|
|
4078
|
+
headers: {
|
|
4079
|
+
"cache-control": "public, max-age=300",
|
|
4080
|
+
"content-type": "text/markdown; charset=utf-8"
|
|
4081
|
+
}
|
|
4082
|
+
}));
|
|
4083
|
+
return plugin.as("global");
|
|
3984
4084
|
}
|
|
3985
4085
|
const registration = config.agentRegistration;
|
|
3986
4086
|
const identityRoute = registration.identityRoute ?? "/agent/identity";
|
|
@@ -26763,7 +26863,7 @@ var createPostgresScimTokenStore = (db) => ({
|
|
|
26763
26863
|
});
|
|
26764
26864
|
// src/agents/registrationClient.ts
|
|
26765
26865
|
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26766
|
-
var
|
|
26866
|
+
var secureUrl2 = (value, allowLocalhost) => {
|
|
26767
26867
|
if (typeof value !== "string")
|
|
26768
26868
|
return;
|
|
26769
26869
|
try {
|
|
@@ -26853,32 +26953,32 @@ var discoverAgentRegistration = async (resource, options = {}) => {
|
|
|
26853
26953
|
const request = options.request ?? fetch;
|
|
26854
26954
|
const maxBytes = options.maxResponseBytes ?? 256 * 1024;
|
|
26855
26955
|
const allowLocalhost = options.allowInsecureLocalhost === true;
|
|
26856
|
-
const resourceUrl =
|
|
26956
|
+
const resourceUrl = secureUrl2(resource, allowLocalhost);
|
|
26857
26957
|
if (resourceUrl === undefined)
|
|
26858
26958
|
throw new Error("Resource URL must use HTTPS");
|
|
26859
26959
|
const resourceMetadataUrl = new URL("/.well-known/oauth-protected-resource", resourceUrl).toString();
|
|
26860
26960
|
const prm = await requestJson(request, resourceMetadataUrl, {}, maxBytes);
|
|
26861
|
-
const advertisedResource =
|
|
26961
|
+
const advertisedResource = secureUrl2(prm.body.resource, allowLocalhost);
|
|
26862
26962
|
if (advertisedResource === undefined || advertisedResource !== resourceUrl) {
|
|
26863
26963
|
throw new Error("Protected resource metadata identity mismatch");
|
|
26864
26964
|
}
|
|
26865
|
-
const authorizationServer =
|
|
26965
|
+
const authorizationServer = secureUrl2(stringArray(prm.body.authorization_servers)[0], allowLocalhost);
|
|
26866
26966
|
if (authorizationServer === undefined) {
|
|
26867
26967
|
throw new Error("No secure authorization server is advertised");
|
|
26868
26968
|
}
|
|
26869
26969
|
const asUrl = new URL("/.well-known/oauth-authorization-server", authorizationServer).toString();
|
|
26870
26970
|
const metadata = await requestJson(request, asUrl, {}, maxBytes);
|
|
26871
|
-
if (
|
|
26971
|
+
if (secureUrl2(metadata.body.issuer, allowLocalhost) !== authorizationServer) {
|
|
26872
26972
|
throw new Error("Authorization server issuer mismatch");
|
|
26873
26973
|
}
|
|
26874
|
-
const tokenEndpoint =
|
|
26974
|
+
const tokenEndpoint = secureUrl2(metadata.body.token_endpoint, allowLocalhost);
|
|
26875
26975
|
const agentAuth = metadata.body.agent_auth;
|
|
26876
26976
|
if (tokenEndpoint === undefined || !isObject2(agentAuth)) {
|
|
26877
26977
|
throw new Error("Authorization server does not advertise agent registration");
|
|
26878
26978
|
}
|
|
26879
|
-
const identityEndpoint =
|
|
26880
|
-
const claimEndpoint =
|
|
26881
|
-
const skill =
|
|
26979
|
+
const identityEndpoint = secureUrl2(agentAuth.identity_endpoint, allowLocalhost);
|
|
26980
|
+
const claimEndpoint = secureUrl2(agentAuth.claim_endpoint, allowLocalhost);
|
|
26981
|
+
const skill = secureUrl2(agentAuth.skill, allowLocalhost);
|
|
26882
26982
|
const assertionMetadata = agentAuth.identity_assertion;
|
|
26883
26983
|
if (identityEndpoint === undefined || claimEndpoint === undefined || skill === undefined || !isObject2(assertionMetadata)) {
|
|
26884
26984
|
throw new Error("Agent registration metadata is incomplete");
|
|
@@ -27638,7 +27738,7 @@ var createPostgresApiKeyStore = (db) => ({
|
|
|
27638
27738
|
}
|
|
27639
27739
|
});
|
|
27640
27740
|
// src/oidc/clientIdMetadata.ts
|
|
27641
|
-
var
|
|
27741
|
+
var secureUrl3 = (value) => {
|
|
27642
27742
|
try {
|
|
27643
27743
|
return new URL(value).protocol === "https:";
|
|
27644
27744
|
} catch {
|
|
@@ -27649,7 +27749,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
|
27649
27749
|
const errors = [];
|
|
27650
27750
|
if (document.client_id !== expectedClientId)
|
|
27651
27751
|
errors.push("client_id does not match the metadata document URL");
|
|
27652
|
-
if (!
|
|
27752
|
+
if (!secureUrl3(document.client_id))
|
|
27653
27753
|
errors.push("client_id must use HTTPS");
|
|
27654
27754
|
if (!Array.isArray(document.redirect_uris) || document.redirect_uris.length === 0)
|
|
27655
27755
|
errors.push("redirect_uris is required");
|
|
@@ -27669,7 +27769,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
|
27669
27769
|
["tos_uri", document.tos_uri],
|
|
27670
27770
|
["jwks_uri", document.jwks_uri]
|
|
27671
27771
|
]) {
|
|
27672
|
-
if (value !== undefined && !
|
|
27772
|
+
if (value !== undefined && !secureUrl3(value))
|
|
27673
27773
|
errors.push(`${name2} must use HTTPS`);
|
|
27674
27774
|
}
|
|
27675
27775
|
return errors;
|
|
@@ -27692,7 +27792,7 @@ var createClientIdMetadataResolver = ({
|
|
|
27692
27792
|
}) => {
|
|
27693
27793
|
const cache = new Map;
|
|
27694
27794
|
return async (clientId) => {
|
|
27695
|
-
if (!
|
|
27795
|
+
if (!secureUrl3(clientId) || !await allow(clientId))
|
|
27696
27796
|
return;
|
|
27697
27797
|
const cached = cache.get(clientId);
|
|
27698
27798
|
if (cached !== undefined && cached.expiresAt > now())
|
|
@@ -30371,6 +30471,22 @@ var buildAuthApplications = async (configuration) => {
|
|
|
30371
30471
|
tokenTtlMs: registration2.tokenTtlMs
|
|
30372
30472
|
});
|
|
30373
30473
|
}
|
|
30474
|
+
if (agentAuth?.oauthGuide !== undefined) {
|
|
30475
|
+
if (oidc === undefined)
|
|
30476
|
+
throw new Error("agentAuth.oauthGuide requires the OIDC provider");
|
|
30477
|
+
if (agentAuth.authorizationServer !== oidc.issuer)
|
|
30478
|
+
throw new Error("agentAuth.authorizationServer must equal oidc.issuer");
|
|
30479
|
+
if (agentAuth.oauthGuide.authorizationServer !== agentAuth.authorizationServer)
|
|
30480
|
+
throw new Error("agentAuth.oauthGuide.authorizationServer must equal agentAuth.authorizationServer");
|
|
30481
|
+
const unknownGuideScopes = agentAuth.oauthGuide.resources.flatMap(({ scopes }) => scopes).filter((scope) => !agentAuth.scopes.includes(scope));
|
|
30482
|
+
if (unknownGuideScopes.length > 0)
|
|
30483
|
+
throw new Error(`Agent OAuth guide uses undeclared scopes: ${[
|
|
30484
|
+
...new Set(unknownGuideScopes)
|
|
30485
|
+
].join(", ")}`);
|
|
30486
|
+
if (agentAuth.agentRegistration !== undefined && (agentAuth.agentRegistration.guideRoute ?? "/auth.md") === (agentAuth.oauthGuide.route ?? "/auth.md"))
|
|
30487
|
+
throw new Error("Agent OAuth and registration guides cannot use the same route");
|
|
30488
|
+
generateAgentOAuthGuide(agentAuth.oauthGuide);
|
|
30489
|
+
}
|
|
30374
30490
|
if (tracing !== undefined)
|
|
30375
30491
|
await initTracing(tracing);
|
|
30376
30492
|
const clientProviders = await buildClientProviders(providersConfiguration, createOAuth2Client, customProviders);
|
|
@@ -30392,6 +30508,9 @@ var buildAuthApplications = async (configuration) => {
|
|
|
30392
30508
|
...oidc,
|
|
30393
30509
|
additionalDiscoveryMetadata: {
|
|
30394
30510
|
...oidc.additionalDiscoveryMetadata,
|
|
30511
|
+
...resolvedAgentAuth?.oauthGuide === undefined ? {} : {
|
|
30512
|
+
service_documentation: agentOAuthGuideUrl(resolvedAgentAuth.oauthGuide)
|
|
30513
|
+
},
|
|
30395
30514
|
...resolvedAgentAuth?.agentRegistration === undefined ? {} : {
|
|
30396
30515
|
agent_auth: agentRegistrationDiscoveryMetadata(resolvedAgentAuth)
|
|
30397
30516
|
}
|
|
@@ -30611,5 +30730,5 @@ export {
|
|
|
30611
30730
|
auth2 as auth
|
|
30612
30731
|
};
|
|
30613
30732
|
|
|
30614
|
-
//# debugId=
|
|
30733
|
+
//# debugId=E2F70FC52266FF6C64756E2164756E21
|
|
30615
30734
|
//# sourceMappingURL=server.js.map
|