@keycardai/express 0.1.0

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.
Files changed (44) hide show
  1. package/README.md +77 -0
  2. package/dist/cjs/bearerAuth.d.ts +76 -0
  3. package/dist/cjs/bearerAuth.d.ts.map +1 -0
  4. package/dist/cjs/bearerAuth.js +123 -0
  5. package/dist/cjs/bearerAuth.js.map +1 -0
  6. package/dist/cjs/grant.d.ts +46 -0
  7. package/dist/cjs/grant.d.ts.map +1 -0
  8. package/dist/cjs/grant.js +99 -0
  9. package/dist/cjs/grant.js.map +1 -0
  10. package/dist/cjs/index.d.ts +9 -0
  11. package/dist/cjs/index.d.ts.map +1 -0
  12. package/dist/cjs/index.js +12 -0
  13. package/dist/cjs/index.js.map +1 -0
  14. package/dist/cjs/middleware.d.ts +54 -0
  15. package/dist/cjs/middleware.d.ts.map +1 -0
  16. package/dist/cjs/middleware.js +49 -0
  17. package/dist/cjs/middleware.js.map +1 -0
  18. package/dist/cjs/package.json +1 -0
  19. package/dist/cjs/wellKnown.d.ts +48 -0
  20. package/dist/cjs/wellKnown.d.ts.map +1 -0
  21. package/dist/cjs/wellKnown.js +73 -0
  22. package/dist/cjs/wellKnown.js.map +1 -0
  23. package/dist/esm/bearerAuth.d.ts +76 -0
  24. package/dist/esm/bearerAuth.d.ts.map +1 -0
  25. package/dist/esm/bearerAuth.js +120 -0
  26. package/dist/esm/bearerAuth.js.map +1 -0
  27. package/dist/esm/grant.d.ts +46 -0
  28. package/dist/esm/grant.d.ts.map +1 -0
  29. package/dist/esm/grant.js +96 -0
  30. package/dist/esm/grant.js.map +1 -0
  31. package/dist/esm/index.d.ts +9 -0
  32. package/dist/esm/index.d.ts.map +1 -0
  33. package/dist/esm/index.js +5 -0
  34. package/dist/esm/index.js.map +1 -0
  35. package/dist/esm/middleware.d.ts +54 -0
  36. package/dist/esm/middleware.d.ts.map +1 -0
  37. package/dist/esm/middleware.js +46 -0
  38. package/dist/esm/middleware.js.map +1 -0
  39. package/dist/esm/package.json +1 -0
  40. package/dist/esm/wellKnown.d.ts +48 -0
  41. package/dist/esm/wellKnown.d.ts.map +1 -0
  42. package/dist/esm/wellKnown.js +70 -0
  43. package/dist/esm/wellKnown.js.map +1 -0
  44. package/package.json +53 -0
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.keycardMetadataRouter = keycardMetadataRouter;
4
+ const express_1 = require("express");
5
+ /**
6
+ * Returns an Express Router that serves the two OAuth discovery endpoints
7
+ * required by RFC 9728 and RFC 8414:
8
+ *
9
+ * - `GET /.well-known/oauth-protected-resource` (RFC 9728 §2)
10
+ * - `GET /.well-known/oauth-authorization-server` (RFC 8414 §3, proxied)
11
+ *
12
+ * Mount it at the application root:
13
+ * ```ts
14
+ * import express from "express";
15
+ * import { keycardMetadataRouter } from "@keycardai/express";
16
+ *
17
+ * const app = express();
18
+ * app.use(keycardMetadataRouter({ issuer: "https://zone.keycard.cloud" }));
19
+ * ```
20
+ *
21
+ * These paths must remain publicly accessible (no bearer auth) per their
22
+ * respective specs. Per the security guidance in
23
+ * `@keycardai/starlette` and the feedback_specific_path_bypass rule:
24
+ * only bypass auth for these exact paths, never a broad `/.well-known/` prefix.
25
+ */
26
+ function keycardMetadataRouter(options) {
27
+ const router = (0, express_1.Router)();
28
+ router.get("/.well-known/oauth-protected-resource", protectedResourceHandler(options));
29
+ router.get("/.well-known/oauth-authorization-server", authorizationServerHandler(options.issuer, options.asMetadataTimeoutMs ?? 10_000));
30
+ return router;
31
+ }
32
+ function protectedResourceHandler(options) {
33
+ return (req, res) => {
34
+ const resource = `${req.protocol}://${req.host}`;
35
+ const metadata = {
36
+ resource,
37
+ authorization_servers: [options.issuer],
38
+ };
39
+ if (options.resourceName)
40
+ metadata.resource_name = options.resourceName;
41
+ if (options.scopesSupported)
42
+ metadata.scopes_supported = [...options.scopesSupported];
43
+ if (options.resourceDocumentation)
44
+ metadata.resource_documentation = options.resourceDocumentation;
45
+ res.set("Access-Control-Allow-Origin", "*");
46
+ res.status(200).json(metadata);
47
+ };
48
+ }
49
+ function authorizationServerHandler(issuer, timeoutMs) {
50
+ return async (req, res, next) => {
51
+ try {
52
+ const upstream = await fetch(`${issuer}/.well-known/oauth-authorization-server`, { signal: AbortSignal.timeout(timeoutMs) });
53
+ if (!upstream.ok) {
54
+ res.status(502).json({ error: "Failed to fetch AS metadata from issuer" });
55
+ return;
56
+ }
57
+ const metadata = await upstream.json();
58
+ // Rewrite authorization_endpoint to include a `resource` param pointing
59
+ // at this server's origin so the AS knows which resource is being accessed.
60
+ if (typeof metadata.authorization_endpoint === "string") {
61
+ const authUrl = new URL(metadata.authorization_endpoint);
62
+ authUrl.searchParams.set("resource", `${req.protocol}://${req.host}`);
63
+ metadata.authorization_endpoint = authUrl.toString();
64
+ }
65
+ res.set("Access-Control-Allow-Origin", "*");
66
+ res.status(200).json(metadata);
67
+ }
68
+ catch (e) {
69
+ next(e);
70
+ }
71
+ };
72
+ }
73
+ //# sourceMappingURL=wellKnown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wellKnown.js","sourceRoot":"","sources":["../../src/wellKnown.ts"],"names":[],"mappings":";;AAiDA,sDAcC;AA/DD,qCAAiC;AA4BjC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAgB,qBAAqB,CAAC,OAA6B;IACjE,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;IAExB,MAAM,CAAC,GAAG,CACR,uCAAuC,EACvC,wBAAwB,CAAC,OAAO,CAAC,CAClC,CAAC;IAEF,MAAM,CAAC,GAAG,CACR,yCAAyC,EACzC,0BAA0B,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,mBAAmB,IAAI,MAAM,CAAC,CAClF,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,wBAAwB,CAAC,OAA6B;IAC7D,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAClB,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC,QAAQ,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACjD,MAAM,QAAQ,GAA4B;YACxC,QAAQ;YACR,qBAAqB,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;SACxC,CAAC;QACF,IAAI,OAAO,CAAC,YAAY;YAAE,QAAQ,CAAC,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;QACxE,IAAI,OAAO,CAAC,eAAe;YAAE,QAAQ,CAAC,gBAAgB,GAAG,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;QACtF,IAAI,OAAO,CAAC,qBAAqB;YAAE,QAAQ,CAAC,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC;QAEnG,GAAG,CAAC,GAAG,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;QAC5C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,0BAA0B,CAAC,MAAc,EAAE,SAAiB;IACnE,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC9B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAC1B,GAAG,MAAM,yCAAyC,EAClD,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAC3C,CAAC;YACF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAC,CAAC;gBAC3E,OAAO;YACT,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,EAA6B,CAAC;YAElE,wEAAwE;YACxE,4EAA4E;YAC5E,IAAI,OAAO,QAAQ,CAAC,sBAAsB,KAAK,QAAQ,EAAE,CAAC;gBACxD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC;gBACzD,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,GAAG,CAAC,QAAQ,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;gBACtE,QAAQ,CAAC,sBAAsB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;YACvD,CAAC;YAED,GAAG,CAAC,GAAG,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;YAC5C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,CAAC,CAAC,CAAC;QACV,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,76 @@
1
+ import type { Request, RequestHandler } from "express";
2
+ import { TokenVerifier } from "@keycardai/oauth/server/tokenVerifier";
3
+ import type { TokenVerifierOptions } from "@keycardai/oauth/server/tokenVerifier";
4
+ import type { AccessToken } from "@keycardai/oauth/server/accessToken";
5
+ /**
6
+ * Extends Express `Request` with the verified Keycard `AccessToken`.
7
+ *
8
+ * Cast inside handlers that run after `requireBearerAuth()`:
9
+ * ```ts
10
+ * app.get("/data", (req, res) => {
11
+ * const { auth } = req as AuthenticatedRequest;
12
+ * });
13
+ * ```
14
+ *
15
+ * Alternatively, adopt Express module augmentation so `req.auth` is
16
+ * available without casting across your entire app:
17
+ * ```ts
18
+ * import type { AccessToken } from "@keycardai/oauth/server";
19
+ * declare global {
20
+ * namespace Express {
21
+ * interface Request {
22
+ * auth?: AccessToken;
23
+ * }
24
+ * }
25
+ * }
26
+ * ```
27
+ * We ship the interface-extension form rather than augmenting the global
28
+ * namespace by default. Augmentation makes `req.auth` optional on every
29
+ * request including unauthenticated routes, which weakens the type
30
+ * contract. Use it when you prefer convenience over strictness.
31
+ * See: https://github.com/auth0/express-jwt/issues/311
32
+ */
33
+ export interface AuthenticatedRequest extends Request {
34
+ auth: AccessToken;
35
+ }
36
+ export type BearerAuthOptions = {
37
+ verifier: TokenVerifier;
38
+ requiredScopes?: readonly string[];
39
+ } | {
40
+ /**
41
+ * Keycard zone URL, e.g. "https://zone-id.keycard.cloud".
42
+ * Either `zoneUrl` or `zoneId` is required (consistent with `grant()`).
43
+ */
44
+ zoneUrl?: string;
45
+ /**
46
+ * Keycard zone ID. Constructs the URL as `https://{zoneId}.keycard.cloud`.
47
+ * Either `zoneUrl` or `zoneId` is required (consistent with `grant()`).
48
+ */
49
+ zoneId?: string;
50
+ audience?: string;
51
+ enableMultiZone?: boolean;
52
+ keyring?: TokenVerifierOptions["keyring"];
53
+ requiredScopes?: readonly string[];
54
+ };
55
+ /**
56
+ * Express middleware that validates a Bearer token (RFC 6750) and sets
57
+ * `req.auth` to the verified `AccessToken`.
58
+ *
59
+ * On failure: responds with a `WWW-Authenticate` challenge containing the
60
+ * `resource_metadata` URL per RFC 9728 §3.
61
+ *
62
+ * Usage with a zone URL:
63
+ * ```ts
64
+ * app.use(requireBearerAuth({ zoneUrl: "https://zone.keycard.cloud" }));
65
+ * // or by zone ID
66
+ * app.use(requireBearerAuth({ zoneId: "zone-id" }));
67
+ * ```
68
+ *
69
+ * Usage with a pre-built verifier (shared across routes):
70
+ * ```ts
71
+ * const verifier = new TokenVerifier({ issuer: "https://zone.keycard.cloud" });
72
+ * app.use(requireBearerAuth({ verifier }));
73
+ * ```
74
+ */
75
+ export declare function requireBearerAuth(options: BearerAuthOptions): RequestHandler;
76
+ //# sourceMappingURL=bearerAuth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearerAuth.d.ts","sourceRoot":"","sources":["../../src/bearerAuth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AACtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AASvE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,oBAAqB,SAAQ,OAAO;IACnD,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,EAAE,aAAa,CAAC;IAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC/D;IACE;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAC1C,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC,CAAC;AAEN;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAgG5E"}
@@ -0,0 +1,120 @@
1
+ import { TokenVerifier } from "@keycardai/oauth/server/tokenVerifier";
2
+ import { BadRequestError, UnauthorizedError, InvalidTokenError, InsufficientScopeError, OAuthError, } from "@keycardai/oauth/errors";
3
+ /**
4
+ * Express middleware that validates a Bearer token (RFC 6750) and sets
5
+ * `req.auth` to the verified `AccessToken`.
6
+ *
7
+ * On failure: responds with a `WWW-Authenticate` challenge containing the
8
+ * `resource_metadata` URL per RFC 9728 §3.
9
+ *
10
+ * Usage with a zone URL:
11
+ * ```ts
12
+ * app.use(requireBearerAuth({ zoneUrl: "https://zone.keycard.cloud" }));
13
+ * // or by zone ID
14
+ * app.use(requireBearerAuth({ zoneId: "zone-id" }));
15
+ * ```
16
+ *
17
+ * Usage with a pre-built verifier (shared across routes):
18
+ * ```ts
19
+ * const verifier = new TokenVerifier({ issuer: "https://zone.keycard.cloud" });
20
+ * app.use(requireBearerAuth({ verifier }));
21
+ * ```
22
+ */
23
+ export function requireBearerAuth(options) {
24
+ // Do not pass requiredScopes to TokenVerifier: it returns null on scope
25
+ // failure, which the middleware would interpret as a generic 401. The
26
+ // explicit scope check below produces the correct 403 InsufficientScopeError.
27
+ let verifier;
28
+ if ("verifier" in options) {
29
+ verifier = options.verifier;
30
+ }
31
+ else {
32
+ const issuer = options.zoneUrl ?? buildIssuerFromZoneId(options.zoneId);
33
+ if (!issuer) {
34
+ throw new Error("requireBearerAuth: either `zoneUrl` or `zoneId` is required");
35
+ }
36
+ verifier = new TokenVerifier({
37
+ issuer,
38
+ audience: options.audience,
39
+ enableMultiZone: options.enableMultiZone,
40
+ keyring: options.keyring,
41
+ });
42
+ }
43
+ return async (req, res, next) => {
44
+ const resourceMetadataUrl = getResourceMetadataUrl(req);
45
+ try {
46
+ const authorization = req.headers.authorization;
47
+ if (!authorization) {
48
+ throw new UnauthorizedError("No credentials");
49
+ }
50
+ const [scheme, token] = authorization.split(" ");
51
+ if (!token) {
52
+ throw new BadRequestError("Malformed credentials");
53
+ }
54
+ if (scheme.toLowerCase() !== "bearer") {
55
+ throw new InvalidTokenError("Unsupported authentication scheme");
56
+ }
57
+ const accessToken = await verifier.verifyToken(token);
58
+ if (!accessToken) {
59
+ throw new InvalidTokenError("Token validation failed");
60
+ }
61
+ // Validate resource audience: a token scoped to a different resource
62
+ // server must not be accepted here. Compare origins so path and query
63
+ // string differences are ignored (mirrors Workers auth.ts:88-92).
64
+ if (accessToken.resource) {
65
+ const requestOrigin = `${req.protocol}://${req.host}`;
66
+ try {
67
+ const tokenOrigin = new URL(accessToken.resource).origin;
68
+ if (tokenOrigin !== requestOrigin) {
69
+ throw new InvalidTokenError("Token not intended for resource");
70
+ }
71
+ }
72
+ catch (e) {
73
+ if (e instanceof InvalidTokenError)
74
+ throw e;
75
+ // resource claim is not a URL; opaque audience, skip origin check
76
+ }
77
+ }
78
+ if ("requiredScopes" in options &&
79
+ options.requiredScopes &&
80
+ options.requiredScopes.length > 0) {
81
+ const hasAllScopes = options.requiredScopes.every((scope) => accessToken.scopes.includes(scope));
82
+ if (!hasAllScopes) {
83
+ throw new InsufficientScopeError("Insufficient scope");
84
+ }
85
+ }
86
+ req.auth = accessToken;
87
+ next();
88
+ }
89
+ catch (error) {
90
+ if (error instanceof BadRequestError) {
91
+ res.status(400).end();
92
+ }
93
+ else if (error instanceof UnauthorizedError) {
94
+ res.set("WWW-Authenticate", `Bearer resource_metadata="${resourceMetadataUrl}"`);
95
+ res.status(401).end();
96
+ }
97
+ else if (error instanceof InsufficientScopeError) {
98
+ res.set("WWW-Authenticate", `Bearer error="${error.errorCode}", error_description="${error.message}", resource_metadata="${resourceMetadataUrl}"`);
99
+ res.status(403).end();
100
+ }
101
+ else if (error instanceof OAuthError || error instanceof InvalidTokenError) {
102
+ res.set("WWW-Authenticate", `Bearer error="${error.errorCode}", error_description="${error.message}", resource_metadata="${resourceMetadataUrl}"`);
103
+ res.status(401).end();
104
+ }
105
+ else {
106
+ next(error);
107
+ }
108
+ }
109
+ };
110
+ }
111
+ function getResourceMetadataUrl(req) {
112
+ const origin = `${req.protocol}://${req.host}`;
113
+ return `${origin}/.well-known/oauth-protected-resource`;
114
+ }
115
+ function buildIssuerFromZoneId(zoneId) {
116
+ if (!zoneId)
117
+ return undefined;
118
+ return `https://${zoneId}.keycard.cloud`;
119
+ }
120
+ //# sourceMappingURL=bearerAuth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearerAuth.js","sourceRoot":"","sources":["../../src/bearerAuth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAGtE,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,UAAU,GACX,MAAM,yBAAyB,CAAC;AAqDjC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA0B;IAC1D,wEAAwE;IACxE,sEAAsE;IACtE,8EAA8E;IAC9E,IAAI,QAAuB,CAAC;IAC5B,IAAI,UAAU,IAAI,OAAO,EAAE,CAAC;QAC1B,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAC9B,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,IAAI,qBAAqB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACxE,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QACjF,CAAC;QACD,QAAQ,GAAG,IAAI,aAAa,CAAC;YAC3B,MAAM;YACN,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,OAAO,EAAE,OAAO,CAAC,OAAO;SACzB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/D,MAAM,mBAAmB,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;QAExD,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;YAChD,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,MAAM,IAAI,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;YAChD,CAAC;YAED,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACjD,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,eAAe,CAAC,uBAAuB,CAAC,CAAC;YACrD,CAAC;YACD,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,QAAQ,EAAE,CAAC;gBACtC,MAAM,IAAI,iBAAiB,CAAC,mCAAmC,CAAC,CAAC;YACnE,CAAC;YAED,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACtD,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,MAAM,IAAI,iBAAiB,CAAC,yBAAyB,CAAC,CAAC;YACzD,CAAC;YAED,qEAAqE;YACrE,sEAAsE;YACtE,kEAAkE;YAClE,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC;gBACzB,MAAM,aAAa,GAAG,GAAG,GAAG,CAAC,QAAQ,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;gBACtD,IAAI,CAAC;oBACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;oBACzD,IAAI,WAAW,KAAK,aAAa,EAAE,CAAC;wBAClC,MAAM,IAAI,iBAAiB,CAAC,iCAAiC,CAAC,CAAC;oBACjE,CAAC;gBACH,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,IAAI,CAAC,YAAY,iBAAiB;wBAAE,MAAM,CAAC,CAAC;oBAC5C,kEAAkE;gBACpE,CAAC;YACH,CAAC;YAED,IACE,gBAAgB,IAAI,OAAO;gBAC3B,OAAO,CAAC,cAAc;gBACtB,OAAO,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EACjC,CAAC;gBACD,MAAM,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAC1D,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CACnC,CAAC;gBACF,IAAI,CAAC,YAAY,EAAE,CAAC;oBAClB,MAAM,IAAI,sBAAsB,CAAC,oBAAoB,CAAC,CAAC;gBACzD,CAAC;YACH,CAAC;YAEA,GAA4B,CAAC,IAAI,GAAG,WAAW,CAAC;YACjD,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;gBACrC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;YACxB,CAAC;iBAAM,IAAI,KAAK,YAAY,iBAAiB,EAAE,CAAC;gBAC9C,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,6BAA6B,mBAAmB,GAAG,CAAC,CAAC;gBACjF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;YACxB,CAAC;iBAAM,IAAI,KAAK,YAAY,sBAAsB,EAAE,CAAC;gBACnD,GAAG,CAAC,GAAG,CACL,kBAAkB,EAClB,iBAAkB,KAAoB,CAAC,SAAS,yBAAyB,KAAK,CAAC,OAAO,yBAAyB,mBAAmB,GAAG,CACtI,CAAC;gBACF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;YACxB,CAAC;iBAAM,IAAI,KAAK,YAAY,UAAU,IAAI,KAAK,YAAY,iBAAiB,EAAE,CAAC;gBAC7E,GAAG,CAAC,GAAG,CACL,kBAAkB,EAClB,iBAAkB,KAAoB,CAAC,SAAS,yBAAyB,KAAK,CAAC,OAAO,yBAAyB,mBAAmB,GAAG,CACtI,CAAC;gBACF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,sBAAsB,CAAC,GAAY;IAC1C,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC/C,OAAO,GAAG,MAAM,uCAAuC,CAAC;AAC1D,CAAC;AAED,SAAS,qBAAqB,CAAC,MAAe;IAC5C,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,OAAO,WAAW,MAAM,gBAAgB,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,46 @@
1
+ import type { RequestHandler } from "express";
2
+ import { AccessContext } from "@keycardai/oauth/server/accessContext";
3
+ import type { ApplicationCredential } from "@keycardai/oauth/credentials";
4
+ import type { AuthenticatedRequest } from "./bearerAuth.js";
5
+ export interface GrantedRequest extends AuthenticatedRequest {
6
+ accessContext: AccessContext;
7
+ }
8
+ export interface GrantOptions {
9
+ /**
10
+ * Keycard zone URL, e.g. "https://zone-id.keycard.cloud".
11
+ * Either `zoneUrl` or `zoneId` is required.
12
+ */
13
+ zoneUrl?: string;
14
+ /**
15
+ * Keycard zone ID. Constructs the zone URL as
16
+ * `https://{zoneId}.keycard.cloud`.
17
+ */
18
+ zoneId?: string;
19
+ /**
20
+ * Application credential provider for authenticated token exchange.
21
+ * When omitted, the bearer token is exchanged without client auth.
22
+ */
23
+ applicationCredential?: ApplicationCredential;
24
+ }
25
+ /**
26
+ * Express middleware factory for delegated token exchange (RFC 8693).
27
+ *
28
+ * Must run AFTER `requireBearerAuth()`. Reads the verified bearer token
29
+ * from `req.auth`, exchanges it for per-resource access tokens at the
30
+ * Keycard zone, and stores the results in `req.accessContext`.
31
+ *
32
+ * On success, `req.accessContext.access(resourceUrl)` returns the
33
+ * `TokenResponse` for that resource. On partial failure, some resources
34
+ * may have errors while others succeed.
35
+ *
36
+ * ```ts
37
+ * app.use(requireBearerAuth({ issuer: "https://zone.keycard.cloud" }));
38
+ * app.use(grant(["https://graph.microsoft.com"], { zoneUrl: "https://zone.keycard.cloud" }));
39
+ * app.get("/data", (req, res) => {
40
+ * const token = req.accessContext.access("https://graph.microsoft.com");
41
+ * // ...
42
+ * });
43
+ * ```
44
+ */
45
+ export declare function grant(resources: string | readonly string[], options: GrantOptions): RequestHandler;
46
+ //# sourceMappingURL=grant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grant.d.ts","sourceRoot":"","sources":["../../src/grant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/E,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AACtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAG5D,MAAM,WAAW,cAAe,SAAQ,oBAAoB;IAC1D,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;CAC/C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,KAAK,CACnB,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACrC,OAAO,EAAE,YAAY,GACpB,cAAc,CA2EhB"}
@@ -0,0 +1,96 @@
1
+ import { TokenExchangeClient } from "@keycardai/oauth/tokenExchange";
2
+ import { AccessContext } from "@keycardai/oauth/server/accessContext";
3
+ import { OAuthError, AuthProviderConfigurationError } from "@keycardai/oauth/errors";
4
+ /**
5
+ * Express middleware factory for delegated token exchange (RFC 8693).
6
+ *
7
+ * Must run AFTER `requireBearerAuth()`. Reads the verified bearer token
8
+ * from `req.auth`, exchanges it for per-resource access tokens at the
9
+ * Keycard zone, and stores the results in `req.accessContext`.
10
+ *
11
+ * On success, `req.accessContext.access(resourceUrl)` returns the
12
+ * `TokenResponse` for that resource. On partial failure, some resources
13
+ * may have errors while others succeed.
14
+ *
15
+ * ```ts
16
+ * app.use(requireBearerAuth({ issuer: "https://zone.keycard.cloud" }));
17
+ * app.use(grant(["https://graph.microsoft.com"], { zoneUrl: "https://zone.keycard.cloud" }));
18
+ * app.get("/data", (req, res) => {
19
+ * const token = req.accessContext.access("https://graph.microsoft.com");
20
+ * // ...
21
+ * });
22
+ * ```
23
+ */
24
+ export function grant(resources, options) {
25
+ const zoneUrl = options.zoneUrl ?? buildZoneUrl(options.zoneId);
26
+ if (!zoneUrl) {
27
+ throw new AuthProviderConfigurationError("grant: either `zoneUrl` or `zoneId` is required");
28
+ }
29
+ return async (req, _res, next) => {
30
+ const authReq = req;
31
+ const subjectToken = authReq.auth?.token;
32
+ const accessCtx = new AccessContext();
33
+ if (!subjectToken) {
34
+ accessCtx.setError({
35
+ message: "No authentication token. Ensure requireBearerAuth() runs before grant().",
36
+ });
37
+ req.accessContext = accessCtx;
38
+ return next();
39
+ }
40
+ let client;
41
+ try {
42
+ const auth = options.applicationCredential?.getAuth();
43
+ client = new TokenExchangeClient(zoneUrl, auth ?? undefined);
44
+ }
45
+ catch (e) {
46
+ accessCtx.setError({
47
+ message: "Failed to initialize token exchange client.",
48
+ rawError: String(e),
49
+ });
50
+ req.accessContext = accessCtx;
51
+ return next();
52
+ }
53
+ const resourceList = Array.isArray(resources)
54
+ ? resources
55
+ : [resources];
56
+ const tokens = {};
57
+ for (const resource of resourceList) {
58
+ try {
59
+ let exchangeRequest;
60
+ if (options.applicationCredential) {
61
+ exchangeRequest = await options.applicationCredential.prepareTokenExchangeRequest(subjectToken, resource);
62
+ }
63
+ else {
64
+ exchangeRequest = {
65
+ subjectToken,
66
+ resource,
67
+ subjectTokenType: "urn:ietf:params:oauth:token-type:access_token",
68
+ };
69
+ }
70
+ tokens[resource] = await client.exchangeToken(exchangeRequest);
71
+ }
72
+ catch (e) {
73
+ const detail = {
74
+ message: `Token exchange failed for ${resource}`,
75
+ };
76
+ if (e instanceof OAuthError) {
77
+ detail.code = e.errorCode;
78
+ detail.description = e.message;
79
+ }
80
+ else {
81
+ detail.rawError = String(e);
82
+ }
83
+ accessCtx.setResourceError(resource, detail);
84
+ }
85
+ }
86
+ accessCtx.setBulkTokens(tokens);
87
+ req.accessContext = accessCtx;
88
+ next();
89
+ };
90
+ }
91
+ function buildZoneUrl(zoneId) {
92
+ if (!zoneId)
93
+ return undefined;
94
+ return `https://${zoneId}.keycard.cloud`;
95
+ }
96
+ //# sourceMappingURL=grant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grant.js","sourceRoot":"","sources":["../../src/grant.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export { requireBearerAuth } from "./bearerAuth.js";
2
+ export type { AuthenticatedRequest, BearerAuthOptions } from "./bearerAuth.js";
3
+ export { grant } from "./grant.js";
4
+ export type { GrantedRequest, GrantOptions } from "./grant.js";
5
+ export { keycardMetadataRouter } from "./wellKnown.js";
6
+ export type { KeycardRouterOptions } from "./wellKnown.js";
7
+ export { createKeycardMiddleware } from "./middleware.js";
8
+ export type { KeycardMiddlewareOptions, KeycardMiddleware } from "./middleware.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,YAAY,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { requireBearerAuth } from "./bearerAuth.js";
2
+ export { grant } from "./grant.js";
3
+ export { keycardMetadataRouter } from "./wellKnown.js";
4
+ export { createKeycardMiddleware } from "./middleware.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { RequestHandler } from "express";
2
+ import type { ApplicationCredential } from "@keycardai/oauth/credentials";
3
+ export interface KeycardMiddlewareOptions {
4
+ /**
5
+ * Keycard zone URL, e.g. "https://zone-id.keycard.cloud".
6
+ * Either `zoneUrl` or `zoneId` is required.
7
+ */
8
+ zoneUrl?: string;
9
+ /**
10
+ * Keycard zone ID. Constructs the URL as `https://{zoneId}.keycard.cloud`.
11
+ */
12
+ zoneId?: string;
13
+ /**
14
+ * Application credential for token exchange in `grant()`.
15
+ * Typically a `ClientSecret` from `@keycardai/oauth/server`.
16
+ */
17
+ applicationCredential?: ApplicationCredential;
18
+ }
19
+ export interface KeycardMiddleware {
20
+ /**
21
+ * Express middleware that validates a Bearer token and sets `req.auth`.
22
+ * Accepts optional `requiredScopes` to enforce at the middleware level.
23
+ */
24
+ requireBearerAuth(options?: {
25
+ requiredScopes?: readonly string[];
26
+ }): RequestHandler;
27
+ /**
28
+ * Express middleware for delegated RFC 8693 token exchange.
29
+ * Sets `req.accessContext` with per-resource tokens.
30
+ */
31
+ grant(resources: string | readonly string[], options?: {
32
+ applicationCredential?: ApplicationCredential;
33
+ }): RequestHandler;
34
+ }
35
+ /**
36
+ * Creates a pair of pre-configured Keycard middleware functions sharing a
37
+ * common zone URL. Eliminates the naming mismatch between `requireBearerAuth`
38
+ * (which takes `issuer`) and `grant` (which takes `zoneUrl`/`zoneId`) by
39
+ * accepting a single consistent config.
40
+ *
41
+ * Python equivalent: `AuthProvider(zone_url=..., application_credential=...)`
42
+ *
43
+ * ```ts
44
+ * const keycard = createKeycardMiddleware({
45
+ * zoneUrl: "https://zone.keycard.cloud",
46
+ * applicationCredential: new ClientSecret("client-id", "client-secret"),
47
+ * });
48
+ *
49
+ * app.use(keycard.requireBearerAuth());
50
+ * app.use(keycard.grant(["https://graph.microsoft.com"]));
51
+ * ```
52
+ */
53
+ export declare function createKeycardMiddleware(options: KeycardMiddlewareOptions): KeycardMiddleware;
54
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;CAC/C;AAED,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,iBAAiB,CAAC,OAAO,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAAG,cAAc,CAAC;IACpF;;;OAGG;IACH,KAAK,CACH,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACrC,OAAO,CAAC,EAAE;QACR,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;KAC/C,GACA,cAAc,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CAsB5F"}
@@ -0,0 +1,46 @@
1
+ import { requireBearerAuth } from "./bearerAuth.js";
2
+ import { grant } from "./grant.js";
3
+ /**
4
+ * Creates a pair of pre-configured Keycard middleware functions sharing a
5
+ * common zone URL. Eliminates the naming mismatch between `requireBearerAuth`
6
+ * (which takes `issuer`) and `grant` (which takes `zoneUrl`/`zoneId`) by
7
+ * accepting a single consistent config.
8
+ *
9
+ * Python equivalent: `AuthProvider(zone_url=..., application_credential=...)`
10
+ *
11
+ * ```ts
12
+ * const keycard = createKeycardMiddleware({
13
+ * zoneUrl: "https://zone.keycard.cloud",
14
+ * applicationCredential: new ClientSecret("client-id", "client-secret"),
15
+ * });
16
+ *
17
+ * app.use(keycard.requireBearerAuth());
18
+ * app.use(keycard.grant(["https://graph.microsoft.com"]));
19
+ * ```
20
+ */
21
+ export function createKeycardMiddleware(options) {
22
+ const zoneUrl = options.zoneUrl ?? buildZoneUrl(options.zoneId);
23
+ if (!zoneUrl) {
24
+ throw new Error("createKeycardMiddleware: either `zoneUrl` or `zoneId` is required");
25
+ }
26
+ return {
27
+ requireBearerAuth(localOptions) {
28
+ return requireBearerAuth({
29
+ zoneUrl,
30
+ requiredScopes: localOptions?.requiredScopes,
31
+ });
32
+ },
33
+ grant(resources, localOptions) {
34
+ return grant(resources, {
35
+ zoneUrl,
36
+ applicationCredential: localOptions?.applicationCredential ?? options.applicationCredential,
37
+ });
38
+ },
39
+ };
40
+ }
41
+ function buildZoneUrl(zoneId) {
42
+ if (!zoneId)
43
+ return undefined;
44
+ return `https://${zoneId}.keycard.cloud`;
45
+ }
46
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAsCnC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAiC;IACvE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACvF,CAAC;IAED,OAAO;QACL,iBAAiB,CAAC,YAAqD;YACrE,OAAO,iBAAiB,CAAC;gBACvB,OAAO;gBACP,cAAc,EAAE,YAAY,EAAE,cAAc;aAC7C,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,SAAS,EAAE,YAAY;YAC3B,OAAO,KAAK,CAAC,SAAS,EAAE;gBACtB,OAAO;gBACP,qBAAqB,EACnB,YAAY,EAAE,qBAAqB,IAAI,OAAO,CAAC,qBAAqB;aACvE,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,MAAe;IACnC,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,OAAO,WAAW,MAAM,gBAAgB,CAAC;AAC3C,CAAC"}
@@ -0,0 +1 @@
1
+ {"type": "module"}
@@ -0,0 +1,48 @@
1
+ import { Router } from "express";
2
+ export interface KeycardRouterOptions {
3
+ /**
4
+ * Keycard issuer URL, e.g. "https://zone-id.keycard.cloud".
5
+ * Used to proxy AS metadata from the Keycard authorization server.
6
+ */
7
+ issuer: string;
8
+ /**
9
+ * Human-readable resource name shown in AS metadata.
10
+ */
11
+ resourceName?: string;
12
+ /**
13
+ * Scopes this resource server supports.
14
+ */
15
+ scopesSupported?: readonly string[];
16
+ /**
17
+ * Link to documentation for this resource.
18
+ */
19
+ resourceDocumentation?: string;
20
+ /**
21
+ * Timeout in milliseconds for the upstream AS metadata fetch.
22
+ * Default: 10 000 ms.
23
+ */
24
+ asMetadataTimeoutMs?: number;
25
+ }
26
+ /**
27
+ * Returns an Express Router that serves the two OAuth discovery endpoints
28
+ * required by RFC 9728 and RFC 8414:
29
+ *
30
+ * - `GET /.well-known/oauth-protected-resource` (RFC 9728 §2)
31
+ * - `GET /.well-known/oauth-authorization-server` (RFC 8414 §3, proxied)
32
+ *
33
+ * Mount it at the application root:
34
+ * ```ts
35
+ * import express from "express";
36
+ * import { keycardMetadataRouter } from "@keycardai/express";
37
+ *
38
+ * const app = express();
39
+ * app.use(keycardMetadataRouter({ issuer: "https://zone.keycard.cloud" }));
40
+ * ```
41
+ *
42
+ * These paths must remain publicly accessible (no bearer auth) per their
43
+ * respective specs. Per the security guidance in
44
+ * `@keycardai/starlette` and the feedback_specific_path_bypass rule:
45
+ * only bypass auth for these exact paths, never a broad `/.well-known/` prefix.
46
+ */
47
+ export declare function keycardMetadataRouter(options: KeycardRouterOptions): Router;
48
+ //# sourceMappingURL=wellKnown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wellKnown.d.ts","sourceRoot":"","sources":["../../src/wellKnown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC;;OAEG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAc3E"}