@absolutejs/auth 0.56.13 → 0.56.15
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 +2 -1
- package/dist/agents/index.js +112 -8
- package/dist/agents/index.js.map +7 -6
- package/dist/agents/oauthGuide.d.ts +18 -0
- package/dist/agents/postgresStores.d.ts +2 -0
- package/dist/agents/routes.d.ts +1 -66
- package/dist/cli/migrate.js.map +1 -1
- package/dist/index.d.ts +3 -21
- package/dist/index.js +143 -19
- package/dist/index.js.map +8 -7
- package/dist/server.js +139 -19
- package/dist/server.js.map +8 -7
- 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");
|
|
@@ -27328,10 +27428,10 @@ var identityRegistrationValues = (registration) => ({
|
|
|
27328
27428
|
user_id: registration.userId ?? null,
|
|
27329
27429
|
version: registration.version
|
|
27330
27430
|
});
|
|
27331
|
-
var createNeonAgentDelegationStore = (databaseUrl) =>
|
|
27431
|
+
var createNeonAgentDelegationStore = (databaseUrl) => createDrizzleAgentDelegationStore(createNeonDatabase(databaseUrl));
|
|
27332
27432
|
var createNeonAgentIdentityRegistrationStore = (databaseUrl) => createPostgresAgentIdentityRegistrationStore(createNeonDatabase(databaseUrl));
|
|
27333
27433
|
var createNeonAgentRegistrationStore = (databaseUrl) => createPostgresAgentRegistrationStore(createNeonDatabase(databaseUrl));
|
|
27334
|
-
var
|
|
27434
|
+
var createDrizzleAgentDelegationStore = (db) => ({
|
|
27335
27435
|
findActiveDelegation: async ({
|
|
27336
27436
|
agentId,
|
|
27337
27437
|
now = Date.now(),
|
|
@@ -27370,6 +27470,7 @@ var createPostgresAgentDelegationStore = (db) => ({
|
|
|
27370
27470
|
});
|
|
27371
27471
|
}
|
|
27372
27472
|
});
|
|
27473
|
+
var createPostgresAgentDelegationStore = createDrizzleAgentDelegationStore;
|
|
27373
27474
|
var createPostgresAgentIdentityRegistrationStore = (db) => ({
|
|
27374
27475
|
create: async (registration) => {
|
|
27375
27476
|
const rows = await db.insert(agentIdentityRegistrationsTable).values(identityRegistrationValues(registration)).onConflictDoNothing().returning({ id: agentIdentityRegistrationsTable.registration_id });
|
|
@@ -27638,7 +27739,7 @@ var createPostgresApiKeyStore = (db) => ({
|
|
|
27638
27739
|
}
|
|
27639
27740
|
});
|
|
27640
27741
|
// src/oidc/clientIdMetadata.ts
|
|
27641
|
-
var
|
|
27742
|
+
var secureUrl3 = (value) => {
|
|
27642
27743
|
try {
|
|
27643
27744
|
return new URL(value).protocol === "https:";
|
|
27644
27745
|
} catch {
|
|
@@ -27649,7 +27750,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
|
27649
27750
|
const errors = [];
|
|
27650
27751
|
if (document.client_id !== expectedClientId)
|
|
27651
27752
|
errors.push("client_id does not match the metadata document URL");
|
|
27652
|
-
if (!
|
|
27753
|
+
if (!secureUrl3(document.client_id))
|
|
27653
27754
|
errors.push("client_id must use HTTPS");
|
|
27654
27755
|
if (!Array.isArray(document.redirect_uris) || document.redirect_uris.length === 0)
|
|
27655
27756
|
errors.push("redirect_uris is required");
|
|
@@ -27669,7 +27770,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
|
27669
27770
|
["tos_uri", document.tos_uri],
|
|
27670
27771
|
["jwks_uri", document.jwks_uri]
|
|
27671
27772
|
]) {
|
|
27672
|
-
if (value !== undefined && !
|
|
27773
|
+
if (value !== undefined && !secureUrl3(value))
|
|
27673
27774
|
errors.push(`${name2} must use HTTPS`);
|
|
27674
27775
|
}
|
|
27675
27776
|
return errors;
|
|
@@ -27692,7 +27793,7 @@ var createClientIdMetadataResolver = ({
|
|
|
27692
27793
|
}) => {
|
|
27693
27794
|
const cache = new Map;
|
|
27694
27795
|
return async (clientId) => {
|
|
27695
|
-
if (!
|
|
27796
|
+
if (!secureUrl3(clientId) || !await allow(clientId))
|
|
27696
27797
|
return;
|
|
27697
27798
|
const cached = cache.get(clientId);
|
|
27698
27799
|
if (cached !== undefined && cached.expiresAt > now())
|
|
@@ -30371,6 +30472,22 @@ var buildAuthApplications = async (configuration) => {
|
|
|
30371
30472
|
tokenTtlMs: registration2.tokenTtlMs
|
|
30372
30473
|
});
|
|
30373
30474
|
}
|
|
30475
|
+
if (agentAuth?.oauthGuide !== undefined) {
|
|
30476
|
+
if (oidc === undefined)
|
|
30477
|
+
throw new Error("agentAuth.oauthGuide requires the OIDC provider");
|
|
30478
|
+
if (agentAuth.authorizationServer !== oidc.issuer)
|
|
30479
|
+
throw new Error("agentAuth.authorizationServer must equal oidc.issuer");
|
|
30480
|
+
if (agentAuth.oauthGuide.authorizationServer !== agentAuth.authorizationServer)
|
|
30481
|
+
throw new Error("agentAuth.oauthGuide.authorizationServer must equal agentAuth.authorizationServer");
|
|
30482
|
+
const unknownGuideScopes = agentAuth.oauthGuide.resources.flatMap(({ scopes }) => scopes).filter((scope) => !agentAuth.scopes.includes(scope));
|
|
30483
|
+
if (unknownGuideScopes.length > 0)
|
|
30484
|
+
throw new Error(`Agent OAuth guide uses undeclared scopes: ${[
|
|
30485
|
+
...new Set(unknownGuideScopes)
|
|
30486
|
+
].join(", ")}`);
|
|
30487
|
+
if (agentAuth.agentRegistration !== undefined && (agentAuth.agentRegistration.guideRoute ?? "/auth.md") === (agentAuth.oauthGuide.route ?? "/auth.md"))
|
|
30488
|
+
throw new Error("Agent OAuth and registration guides cannot use the same route");
|
|
30489
|
+
generateAgentOAuthGuide(agentAuth.oauthGuide);
|
|
30490
|
+
}
|
|
30374
30491
|
if (tracing !== undefined)
|
|
30375
30492
|
await initTracing(tracing);
|
|
30376
30493
|
const clientProviders = await buildClientProviders(providersConfiguration, createOAuth2Client, customProviders);
|
|
@@ -30392,6 +30509,9 @@ var buildAuthApplications = async (configuration) => {
|
|
|
30392
30509
|
...oidc,
|
|
30393
30510
|
additionalDiscoveryMetadata: {
|
|
30394
30511
|
...oidc.additionalDiscoveryMetadata,
|
|
30512
|
+
...resolvedAgentAuth?.oauthGuide === undefined ? {} : {
|
|
30513
|
+
service_documentation: agentOAuthGuideUrl(resolvedAgentAuth.oauthGuide)
|
|
30514
|
+
},
|
|
30395
30515
|
...resolvedAgentAuth?.agentRegistration === undefined ? {} : {
|
|
30396
30516
|
agent_auth: agentRegistrationDiscoveryMetadata(resolvedAgentAuth)
|
|
30397
30517
|
}
|
|
@@ -30611,5 +30731,5 @@ export {
|
|
|
30611
30731
|
auth2 as auth
|
|
30612
30732
|
};
|
|
30613
30733
|
|
|
30614
|
-
//# debugId=
|
|
30734
|
+
//# debugId=5695CEA18CB89B4A64756E2164756E21
|
|
30615
30735
|
//# sourceMappingURL=server.js.map
|