@hyper.authio/express 0.1.1 → 0.8.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.
- package/README.md +51 -4
- package/dist/index.cjs +77 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -7
- package/dist/index.d.ts +66 -7
- package/dist/index.js +74 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @hyper.authio/express
|
|
2
2
|
|
|
3
|
-
Protect an Express 4 or 5 API with Authio: bearer validation, guards for every form of permission
|
|
4
|
-
|
|
3
|
+
Protect an Express 4 or 5 API with Authio: bearer validation, guards for every form of permission and for the caller
|
|
4
|
+
kind, step-up challenges, protected resource metadata (RFC 9728), a back-channel logout endpoint, and a signed webhook
|
|
5
|
+
route. Built on [`@hyper.authio/server`](../server/README.md).
|
|
5
6
|
|
|
6
7
|
```bash
|
|
7
8
|
npm install @hyper.authio/express
|
|
@@ -46,8 +47,10 @@ app.post("/api/payouts", requireAuthio(requirePermission("payouts", "create"), a
|
|
|
46
47
|
`{ user, token }` on `req.authio`; it answers `401` with `WWW-Authenticate`. With `optional`, a request without a
|
|
47
48
|
token goes through unauthenticated.
|
|
48
49
|
- `requireScope`, `requireRole`, `requirePermission`, `requireGroup`, `requireClaim`, `requireOrganization`,
|
|
49
|
-
`requireAuthenticationLevel`, `requireAuthenticationMethod`
|
|
50
|
-
`insufficient_scope` or `insufficient_user_authentication`
|
|
50
|
+
`requireAuthenticationLevel`, `requireAuthenticationMethod`, `requireRecentAuthentication`, `requireCallerKind` and
|
|
51
|
+
`requireClient` answer `403`, with `insufficient_scope` or `insufficient_user_authentication` (with `acr_values` and
|
|
52
|
+
`max_age`, RFC 9470) in `WWW-Authenticate` when that is the reason.
|
|
53
|
+
- With `requiredScopes` on the verifier, `authioBearer` itself answers `403 insufficient_scope` when a token lacks one.
|
|
51
54
|
- Every guard is also a rule: `requireAuthio(...)`, `allOf(...)` and `anyOf(...)` combine guards of this package and
|
|
52
55
|
rules of `@hyper.authio/server` alike, and are guards themselves.
|
|
53
56
|
- `problemDetails: true` adds an RFC 9457 body; `passErrors: true` hands `AuthioBearerError` / `AuthioForbiddenError`
|
|
@@ -55,6 +58,50 @@ app.post("/api/payouts", requireAuthio(requirePermission("payouts", "create"), a
|
|
|
55
58
|
- `authioWebhooks(options)` reads the raw body itself (or takes `express.raw()`'s buffer), and answers `200` handled
|
|
56
59
|
or duplicate, `400`, `401`, `413` or `500`.
|
|
57
60
|
|
|
61
|
+
## Protected resource metadata (RFC 9728)
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const verifier = createAuthioVerifier({ issuer, resource: "https://orders.example.com/api", resourceMetadata: true });
|
|
65
|
+
|
|
66
|
+
app.use(authioProtectedResourceMetadata({ verifier, resourceName: "Orders" })); // GET /.well-known/oauth-protected-resource/api
|
|
67
|
+
app.use("/api", authioBearer(verifier)); // 401: Bearer resource_metadata="https://orders.example.com/.well-known/oauth-protected-resource/api"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Back-channel logout
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const client = createAuthioClient({ issuer, clientId: "storefront", clientSecret });
|
|
74
|
+
|
|
75
|
+
app.post(
|
|
76
|
+
"/auth/backchannel-logout",
|
|
77
|
+
authioBackchannelLogout({
|
|
78
|
+
client,
|
|
79
|
+
onLogout: async ({ subject, sessionId }) => sessions.destroyWhere({ subject, sessionId }), // your session store
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Register the URL as the client's back-channel logout URI in Authio. The handler reads `logout_token` (with or without
|
|
85
|
+
`express.urlencoded()`), validates it with `client.validateLogoutToken`, answers `200`, `400` (`invalid_request`) for
|
|
86
|
+
a token that does not validate, and always sends `Cache-Control: no-store`.
|
|
87
|
+
|
|
88
|
+
## Front-channel logout
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
app.get(
|
|
92
|
+
"/auth/frontchannel-logout",
|
|
93
|
+
authioFrontchannelLogout({
|
|
94
|
+
client,
|
|
95
|
+
onLogout: async ({ sessionId }, request) => (sessionId ? sessions.destroyWhere({ sessionId }) : sessions.destroy(request)),
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Register the URL as the client's front-channel logout URI in Authio, with the session required so Authio sends `iss`
|
|
101
|
+
and `sid`. Authio loads it in an iframe when a session ends; the handler checks `iss` with
|
|
102
|
+
`client.validateFrontChannelLogout`, answers `200` with an empty page, `400` (`invalid_request`) for another issuer or a
|
|
103
|
+
`sid` without `iss`, and always sends `Cache-Control: no-store`.
|
|
104
|
+
|
|
58
105
|
## License
|
|
59
106
|
|
|
60
107
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var client = require('@hyper.authio/client');
|
|
3
4
|
var server = require('@hyper.authio/server');
|
|
4
5
|
|
|
5
6
|
// src/index.ts
|
|
@@ -15,6 +16,8 @@ function authioBearer(verifier, options = {}) {
|
|
|
15
16
|
}
|
|
16
17
|
resolved.verifyAuthorizationHeader(header).then((user) => {
|
|
17
18
|
request.authio = { user, token: user.token };
|
|
19
|
+
const failure = resolved.requiredScopes.length > 0 ? server.requireScope(resolved.requiredScopes, "all").check(user) : void 0;
|
|
20
|
+
if (failure) return refuse(request, response, next, new server.AuthioForbiddenError(failure), options);
|
|
18
21
|
next();
|
|
19
22
|
}).catch((error) => refuse(request, response, next, error, options));
|
|
20
23
|
};
|
|
@@ -37,8 +40,10 @@ var requirePermission = (resource, method, options) => requireAuthio(server.requ
|
|
|
37
40
|
var requireGroup = (groups, match) => requireAuthio(server.requireGroup(groups, match));
|
|
38
41
|
var requireClaim = (type, ...values) => requireAuthio(server.requireClaim(type, ...values));
|
|
39
42
|
var requireOrganization = (organization) => requireAuthio(server.requireOrganization(organization));
|
|
40
|
-
var requireAuthenticationLevel = (minimumLevel) => requireAuthio(server.requireAuthenticationLevel(minimumLevel));
|
|
41
|
-
var requireAuthenticationMethod = (methods, match) => requireAuthio(server.requireAuthenticationMethod(methods, match));
|
|
43
|
+
var requireAuthenticationLevel = (minimumLevel, options) => requireAuthio(server.requireAuthenticationLevel(minimumLevel, options));
|
|
44
|
+
var requireAuthenticationMethod = (methods, match, options) => requireAuthio(server.requireAuthenticationMethod(methods, match, options));
|
|
45
|
+
var requireRecentAuthentication = (maxAge, options) => requireAuthio(server.requireRecentAuthentication(maxAge, options));
|
|
46
|
+
var requireCallerKind = (kinds) => requireAuthio(server.requireCallerKind(kinds));
|
|
42
47
|
var requireClient = (clientIds) => requireAuthio(server.requireClient(clientIds));
|
|
43
48
|
var allOf = (...rules) => requireAuthio(server.allOf(...rules));
|
|
44
49
|
var anyOf = (...rules) => requireAuthio(server.anyOf(...rules));
|
|
@@ -60,6 +65,60 @@ function authioWebhooks(options) {
|
|
|
60
65
|
});
|
|
61
66
|
return route;
|
|
62
67
|
}
|
|
68
|
+
function authioProtectedResourceMetadata(options) {
|
|
69
|
+
const settings = "verifier" in options ? {
|
|
70
|
+
...options,
|
|
71
|
+
resource: requireResource(options.verifier),
|
|
72
|
+
issuer: options.verifier.connection.issuer,
|
|
73
|
+
scopesSupported: options.scopesSupported ?? (options.verifier.requiredScopes.length > 0 ? options.verifier.requiredScopes : void 0)
|
|
74
|
+
} : options;
|
|
75
|
+
const document = JSON.stringify(server.createProtectedResourceMetadata(settings));
|
|
76
|
+
const path = server.protectedResourceMetadataPath(settings.resource);
|
|
77
|
+
return (request, response, next) => {
|
|
78
|
+
if (request.method !== "GET" && request.method !== "HEAD" || request.path.replace(/\/+$/, "") !== path) return next();
|
|
79
|
+
response.set("Cache-Control", "public, max-age=3600").type("application/json").send(document);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function authioBackchannelLogout(options) {
|
|
83
|
+
return (request, response, next) => {
|
|
84
|
+
response.set("Cache-Control", "no-store");
|
|
85
|
+
readLogoutToken(request).then(async (logoutToken) => {
|
|
86
|
+
if (!logoutToken) {
|
|
87
|
+
response.status(400).json({ error: "invalid_request", error_description: "The request carries no logout_token." });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
let logout;
|
|
91
|
+
try {
|
|
92
|
+
logout = await options.client.validateLogoutToken(logoutToken);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
options.onError?.(error);
|
|
95
|
+
if (!(error instanceof client.AuthioTokenValidationError)) throw error;
|
|
96
|
+
response.status(400).json({ error: "invalid_request", error_description: error.message });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
await options.onLogout(logout, request);
|
|
100
|
+
response.status(200).end();
|
|
101
|
+
}).catch(next);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function authioFrontchannelLogout(options) {
|
|
105
|
+
return (request, response, next) => {
|
|
106
|
+
response.set("Cache-Control", "no-store");
|
|
107
|
+
Promise.resolve().then(async () => {
|
|
108
|
+
let logout;
|
|
109
|
+
try {
|
|
110
|
+
logout = options.client.validateFrontChannelLogout(new URL(request.originalUrl ?? request.url, "http://localhost").searchParams);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
options.onError?.(error);
|
|
113
|
+
if (!(error instanceof client.AuthioCallbackError)) throw error;
|
|
114
|
+
response.status(400).json({ error: "invalid_request", error_description: error.message });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
await options.onLogout(logout, request);
|
|
118
|
+
response.status(200).type("text/html; charset=utf-8").send('<!doctype html><html><head><meta charset="utf-8"><title>Authio</title></head><body></body></html>');
|
|
119
|
+
}).catch(next);
|
|
120
|
+
};
|
|
121
|
+
}
|
|
63
122
|
function sendAuthioError(response, error, options = {}) {
|
|
64
123
|
const challenge = error.wwwAuthenticate;
|
|
65
124
|
if (challenge) response.append("WWW-Authenticate", challenge);
|
|
@@ -104,22 +163,38 @@ function readRawBody(request, maxBodySize) {
|
|
|
104
163
|
request.on("error", reject);
|
|
105
164
|
});
|
|
106
165
|
}
|
|
166
|
+
async function readLogoutToken(request) {
|
|
167
|
+
const parsed = request.body;
|
|
168
|
+
if (parsed && typeof parsed === "object" && !Buffer.isBuffer(parsed) && typeof parsed.logout_token === "string")
|
|
169
|
+
return parsed.logout_token;
|
|
170
|
+
const raw = Buffer.isBuffer(parsed) ? new Uint8Array(parsed) : typeof parsed === "string" ? new TextEncoder().encode(parsed) : request.readableEnded ? void 0 : await readRawBody(request, 64 * 1024);
|
|
171
|
+
return raw ? new URLSearchParams(new TextDecoder().decode(raw)).get("logout_token") ?? void 0 : void 0;
|
|
172
|
+
}
|
|
173
|
+
function requireResource(verifier) {
|
|
174
|
+
if (!verifier.resource) throw new TypeError("authioProtectedResourceMetadata needs a verifier configured with resource.");
|
|
175
|
+
return verifier.resource;
|
|
176
|
+
}
|
|
107
177
|
function isVerifier(value) {
|
|
108
178
|
return typeof value.verify === "function";
|
|
109
179
|
}
|
|
110
180
|
|
|
111
181
|
exports.allOf = allOf;
|
|
112
182
|
exports.anyOf = anyOf;
|
|
183
|
+
exports.authioBackchannelLogout = authioBackchannelLogout;
|
|
113
184
|
exports.authioBearer = authioBearer;
|
|
185
|
+
exports.authioFrontchannelLogout = authioFrontchannelLogout;
|
|
186
|
+
exports.authioProtectedResourceMetadata = authioProtectedResourceMetadata;
|
|
114
187
|
exports.authioWebhooks = authioWebhooks;
|
|
115
188
|
exports.requireAuthenticationLevel = requireAuthenticationLevel;
|
|
116
189
|
exports.requireAuthenticationMethod = requireAuthenticationMethod;
|
|
117
190
|
exports.requireAuthio = requireAuthio;
|
|
191
|
+
exports.requireCallerKind = requireCallerKind;
|
|
118
192
|
exports.requireClaim = requireClaim;
|
|
119
193
|
exports.requireClient = requireClient;
|
|
120
194
|
exports.requireGroup = requireGroup;
|
|
121
195
|
exports.requireOrganization = requireOrganization;
|
|
122
196
|
exports.requirePermission = requirePermission;
|
|
197
|
+
exports.requireRecentAuthentication = requireRecentAuthentication;
|
|
123
198
|
exports.requireRole = requireRole;
|
|
124
199
|
exports.requireScope = requireScope;
|
|
125
200
|
exports.sendAuthioError = sendAuthioError;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["createAuthioVerifier","allOfRule","AuthioBearerError","authorize","AuthioForbiddenError","scopeRule","roleRule","permissionRule","groupRule","claimRule","organizationRule","authenticationLevelRule","authenticationMethodRule","clientRule","anyOfRule","createAuthioWebhooks","toProblemDetails"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from \"express\";\nimport type { AuthioResourceAccessSource, AuthioTypedWebhookEvent, AuthioWebhookDataByType } from \"@hyper.authio/client\";\nimport {\n allOf as allOfRule,\n anyOf as anyOfRule,\n authorize,\n AuthioBearerError,\n AuthioForbiddenError,\n createAuthioVerifier,\n createAuthioWebhooks,\n requireAuthenticationLevel as authenticationLevelRule,\n requireAuthenticationMethod as authenticationMethodRule,\n requireClaim as claimRule,\n requireClient as clientRule,\n requireGroup as groupRule,\n requireOrganization as organizationRule,\n requirePermission as permissionRule,\n requireRole as roleRule,\n requireScope as scopeRule,\n toProblemDetails,\n type AuthioMatch,\n type AuthioRule,\n type AuthioUser,\n type AuthioVerifier,\n type AuthioVerifierOptions,\n type AuthioWebhookHandler,\n type AuthioWebhookOptions,\n} from \"@hyper.authio/server\";\n\n/**\n * What `authioBearer` puts on the request.\n */\nexport interface AuthioRequestContext {\n /** The caller. */\n readonly user: AuthioUser;\n /** The access token the caller was read from. */\n readonly token: string;\n}\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** The Authio caller, once `authioBearer` validated the token. */\n authio?: AuthioRequestContext;\n }\n }\n}\n\n/**\n * How refusals are answered.\n */\nexport interface AuthioResponseOptions {\n /** Answers `401` and `403` with problem details (RFC 9457) as well as the `WWW-Authenticate` header. */\n problemDetails?: boolean;\n /** Passes refusals to `next(error)` instead of answering, for an application's own error handler. */\n passErrors?: boolean;\n}\n\n/**\n * Options of `authioBearer`.\n */\nexport interface AuthioBearerOptions extends AuthioResponseOptions {\n /** Lets a request without a token through, unauthenticated; a token that is present must still be valid. */\n optional?: boolean;\n}\n\nconst responseSettings = Symbol.for(\"authio.express.response\");\n\ntype RequestWithSettings = Request & { [responseSettings]?: AuthioResponseOptions };\n\n/**\n * Validates the `Authorization: Bearer` token and puts the caller on `req.authio`. Answers `401` with the\n * `WWW-Authenticate` challenge when the token is missing or invalid.\n *\n * @param verifier A verifier, or its options.\n */\nexport function authioBearer(verifier: AuthioVerifier | AuthioVerifierOptions, options: AuthioBearerOptions = {}): RequestHandler {\n const resolved = isVerifier(verifier) ? verifier : createAuthioVerifier(verifier);\n\n return (request, response, next) => {\n (request as RequestWithSettings)[responseSettings] = { problemDetails: options.problemDetails, passErrors: options.passErrors };\n\n const header = request.headers.authorization;\n\n if (options.optional && !header) {\n next();\n return;\n }\n\n resolved\n .verifyAuthorizationHeader(header)\n .then((user) => {\n request.authio = { user, token: user.token! };\n next();\n })\n .catch((error: unknown) => refuse(request, response, next, error, options));\n };\n}\n\n/**\n * An Express guard that is also an Authio rule: use it as middleware, or pass it to `requireAuthio`, `allOf` and\n * `anyOf` like any rule of `@hyper.authio/server`.\n */\nexport type AuthioGuard = RequestHandler & AuthioRule;\n\n/**\n * Requires every rule (guards of this package or rules of `@hyper.authio/server`); answers `401` without a caller and\n * `403` with the joined refusal otherwise.\n */\nexport function requireAuthio(...rules: AuthioRule[]): AuthioGuard {\n if (rules.length === 0) throw new TypeError(\"requireAuthio needs at least one rule.\");\n\n const rule = rules.length === 1 ? rules[0]! : allOfRule(...rules);\n const guard: RequestHandler = (request, response, next) => {\n const user = request.authio?.user;\n\n if (!user) return refuse(request, response, next, new AuthioBearerError(undefined, { error: undefined }));\n\n const failure = authorize(user, ...rules);\n\n if (failure) return refuse(request, response, next, new AuthioForbiddenError(failure));\n\n next();\n };\n\n return Object.assign(guard, { description: rule.description, check: (user: AuthioUser) => authorize(user, ...rules) });\n}\n\n/** Requires granted scopes. */\nexport const requireScope = (scopes: string | readonly string[], match?: AuthioMatch): AuthioGuard => requireAuthio(scopeRule(scopes, match));\n/** Requires roles, from any source or only from the given `*_resource_access` claims. */\nexport const requireRole = (\n roles: string | readonly string[],\n options?: { match?: AuthioMatch; sources?: readonly AuthioResourceAccessSource[] },\n): AuthioGuard => requireAuthio(roleRule(roles, options));\n/** Requires a permission (`method` on `resource`). */\nexport const requirePermission = (resource: string, method: string, options?: { sources?: readonly AuthioResourceAccessSource[] }): AuthioGuard =>\n requireAuthio(permissionRule(resource, method, options));\n/** Requires groups. */\nexport const requireGroup = (groups: string | readonly string[], match?: AuthioMatch): AuthioGuard => requireAuthio(groupRule(groups, match));\n/** Requires a claim, or one of its values. */\nexport const requireClaim = (type: string, ...values: string[]): AuthioGuard => requireAuthio(claimRule(type, ...values));\n/** Requires an organization context, or a given organization. */\nexport const requireOrganization = (organization?: string): AuthioGuard => requireAuthio(organizationRule(organization));\n/** Requires a minimum authentication level (`acr`). */\nexport const requireAuthenticationLevel = (minimumLevel: number): AuthioGuard => requireAuthio(authenticationLevelRule(minimumLevel));\n/** Requires authentication methods (`amr`). */\nexport const requireAuthenticationMethod = (methods: string | readonly string[], match?: AuthioMatch): AuthioGuard =>\n requireAuthio(authenticationMethodRule(methods, match));\n/** Requires the token to have been issued to one of the clients. */\nexport const requireClient = (clientIds: string | readonly string[]): AuthioGuard => requireAuthio(clientRule(clientIds));\n/** Requires every rule. */\nexport const allOf = (...rules: AuthioRule[]): AuthioGuard => requireAuthio(allOfRule(...rules));\n/** Requires at least one rule. */\nexport const anyOf = (...rules: AuthioRule[]): AuthioGuard => requireAuthio(anyOfRule(...rules));\n\n/**\n * A webhook route: verifies the signature over the raw body, deduplicates and dispatches. Mount it before any JSON\n * body parser, or behind `express.raw()`.\n */\nexport interface AuthioWebhookRoute extends RequestHandler {\n on<T extends keyof AuthioWebhookDataByType>(type: T, handler: AuthioWebhookHandler<AuthioTypedWebhookEvent<T>>): AuthioWebhookRoute;\n on(type: string, handler: AuthioWebhookHandler): AuthioWebhookRoute;\n}\n\n/**\n * Creates a webhook route: `app.post(\"/webhooks/authio\", authioWebhooks({ secret }).on(\"user_created\", …))`.\n * Answers `200` handled or duplicate, `400` unreadable, `401` bad signature, `413` too large, `500` handler failure.\n */\nexport function authioWebhooks(options: AuthioWebhookOptions): AuthioWebhookRoute {\n const webhooks = createAuthioWebhooks(options);\n\n const route = ((request: Request, response: Response, next: NextFunction) => {\n readRawBody(request, webhooks.maxBodySize)\n .then(async (body) => {\n if (body === undefined) {\n response.status(413).end();\n return;\n }\n\n const outcome = await webhooks.handle({ body, headers: request.headers });\n\n response.status(outcome.status).end();\n })\n .catch(next);\n }) as AuthioWebhookRoute;\n\n route.on = ((type: string, handler: AuthioWebhookHandler) => {\n webhooks.on(type, handler);\n return route;\n }) as AuthioWebhookRoute[\"on\"];\n\n return route;\n}\n\n/**\n * Answers a refusal: `WWW-Authenticate`, the status, and problem details when asked.\n */\nexport function sendAuthioError(response: Response, error: AuthioBearerError | AuthioForbiddenError, options: AuthioResponseOptions = {}): void {\n const challenge = error.wwwAuthenticate;\n\n if (challenge) response.append(\"WWW-Authenticate\", challenge);\n\n response.status(error.status);\n\n if (options.problemDetails) response.type(\"application/problem+json\").send(JSON.stringify(toProblemDetails(error)));\n else response.end();\n}\n\nfunction refuse(request: Request, response: Response, next: NextFunction, error: unknown, options?: AuthioResponseOptions): void {\n const settings = options ?? (request as RequestWithSettings)[responseSettings] ?? {};\n\n if (!(error instanceof AuthioBearerError) && !(error instanceof AuthioForbiddenError)) return next(error);\n if (settings.passErrors) return next(error);\n\n sendAuthioError(response, error, settings);\n}\n\nfunction readRawBody(request: Request, maxBodySize: number): Promise<Uint8Array | undefined> {\n const parsed = (request as Request & { body?: unknown }).body;\n\n if (Buffer.isBuffer(parsed)) return Promise.resolve(parsed.byteLength > maxBodySize ? undefined : new Uint8Array(parsed));\n if (typeof parsed === \"string\") return Promise.resolve(new TextEncoder().encode(parsed));\n\n if (request.readableEnded || (parsed !== undefined && parsed !== null && typeof parsed === \"object\" && Object.keys(parsed).length > 0))\n return Promise.reject(\n new Error(\"The Authio webhook route needs the raw body: mount it before express.json(), or use express.raw({ type: '*/*' }) on it.\"),\n );\n\n const declared = Number(request.headers[\"content-length\"]);\n\n if (Number.isFinite(declared) && declared > maxBodySize) return Promise.resolve(undefined);\n\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n let done = false;\n\n request.on(\"data\", (chunk: Buffer) => {\n if (done) return;\n\n size += chunk.byteLength;\n\n if (size > maxBodySize) {\n done = true;\n request.pause();\n resolve(undefined);\n return;\n }\n\n chunks.push(chunk);\n });\n request.on(\"end\", () => {\n if (!done) resolve(new Uint8Array(Buffer.concat(chunks)));\n });\n request.on(\"error\", reject);\n });\n}\n\nfunction isVerifier(value: AuthioVerifier | AuthioVerifierOptions): value is AuthioVerifier {\n return typeof (value as AuthioVerifier).verify === \"function\";\n}\n\nexport type { AuthioMatch, AuthioRule, AuthioUser, AuthioVerifier, AuthioVerifierOptions } from \"@hyper.authio/server\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["createAuthioVerifier","scopeRule","AuthioForbiddenError","allOfRule","AuthioBearerError","authorize","roleRule","permissionRule","groupRule","claimRule","organizationRule","authenticationLevelRule","authenticationMethodRule","recentAuthenticationRule","callerKindRule","clientRule","anyOfRule","createAuthioWebhooks","createProtectedResourceMetadata","protectedResourceMetadataPath","AuthioTokenValidationError","AuthioCallbackError","toProblemDetails"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from \"express\";\nimport {\n AuthioCallbackError,\n AuthioTokenValidationError,\n type AuthioCallerKind,\n type AuthioClient,\n type AuthioFrontChannelLogout,\n type AuthioLogoutToken,\n type AuthioResourceAccessSource,\n type AuthioTypedWebhookEvent,\n type AuthioWebhookDataByType,\n} from \"@hyper.authio/client\";\nimport {\n allOf as allOfRule,\n anyOf as anyOfRule,\n authorize,\n AuthioBearerError,\n AuthioForbiddenError,\n createAuthioVerifier,\n createAuthioWebhooks,\n createProtectedResourceMetadata,\n protectedResourceMetadataPath,\n requireAuthenticationLevel as authenticationLevelRule,\n requireAuthenticationMethod as authenticationMethodRule,\n requireCallerKind as callerKindRule,\n requireClaim as claimRule,\n requireClient as clientRule,\n requireGroup as groupRule,\n requireOrganization as organizationRule,\n requirePermission as permissionRule,\n requireRecentAuthentication as recentAuthenticationRule,\n requireRole as roleRule,\n requireScope as scopeRule,\n toProblemDetails,\n type AuthioProtectedResourceMetadataOptions,\n type AuthioMatch,\n type AuthioRule,\n type AuthioRuleFailure,\n type AuthioUser,\n type AuthioVerifier,\n type AuthioVerifierOptions,\n type AuthioWebhookHandler,\n type AuthioWebhookOptions,\n} from \"@hyper.authio/server\";\n\n/**\n * What `authioBearer` puts on the request.\n */\nexport interface AuthioRequestContext {\n /** The caller. */\n readonly user: AuthioUser;\n /** The access token the caller was read from. */\n readonly token: string;\n}\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** The Authio caller, once `authioBearer` validated the token. */\n authio?: AuthioRequestContext;\n }\n }\n}\n\n/**\n * How refusals are answered.\n */\nexport interface AuthioResponseOptions {\n /** Answers `401` and `403` with problem details (RFC 9457) as well as the `WWW-Authenticate` header. */\n problemDetails?: boolean;\n /** Passes refusals to `next(error)` instead of answering, for an application's own error handler. */\n passErrors?: boolean;\n}\n\n/**\n * Options of `authioBearer`.\n */\nexport interface AuthioBearerOptions extends AuthioResponseOptions {\n /** Lets a request without a token through, unauthenticated; a token that is present must still be valid. */\n optional?: boolean;\n}\n\nconst responseSettings = Symbol.for(\"authio.express.response\");\n\ntype RequestWithSettings = Request & { [responseSettings]?: AuthioResponseOptions };\n\n/**\n * Validates the `Authorization: Bearer` token and puts the caller on `req.authio`. Answers `401` with the\n * `WWW-Authenticate` challenge when the token is missing or invalid.\n *\n * @param verifier A verifier, or its options.\n */\nexport function authioBearer(verifier: AuthioVerifier | AuthioVerifierOptions, options: AuthioBearerOptions = {}): RequestHandler {\n const resolved = isVerifier(verifier) ? verifier : createAuthioVerifier(verifier);\n\n return (request, response, next) => {\n (request as RequestWithSettings)[responseSettings] = { problemDetails: options.problemDetails, passErrors: options.passErrors };\n\n const header = request.headers.authorization;\n\n if (options.optional && !header) {\n next();\n return;\n }\n\n resolved\n .verifyAuthorizationHeader(header)\n .then((user) => {\n request.authio = { user, token: user.token! };\n\n const failure = resolved.requiredScopes.length > 0 ? scopeRule(resolved.requiredScopes, \"all\").check(user) : undefined;\n\n if (failure) return refuse(request, response, next, new AuthioForbiddenError(failure as AuthioRuleFailure), options);\n\n next();\n })\n .catch((error: unknown) => refuse(request, response, next, error, options));\n };\n}\n\n/**\n * An Express guard that is also an Authio rule: use it as middleware, or pass it to `requireAuthio`, `allOf` and\n * `anyOf` like any rule of `@hyper.authio/server`.\n */\nexport type AuthioGuard = RequestHandler & AuthioRule;\n\n/**\n * Requires every rule (guards of this package or rules of `@hyper.authio/server`); answers `401` without a caller and\n * `403` with the joined refusal otherwise.\n */\nexport function requireAuthio(...rules: AuthioRule[]): AuthioGuard {\n if (rules.length === 0) throw new TypeError(\"requireAuthio needs at least one rule.\");\n\n const rule = rules.length === 1 ? rules[0]! : allOfRule(...rules);\n const guard: RequestHandler = (request, response, next) => {\n const user = request.authio?.user;\n\n if (!user) return refuse(request, response, next, new AuthioBearerError(undefined, { error: undefined }));\n\n const failure = authorize(user, ...rules);\n\n if (failure) return refuse(request, response, next, new AuthioForbiddenError(failure));\n\n next();\n };\n\n return Object.assign(guard, { description: rule.description, check: (user: AuthioUser) => authorize(user, ...rules) });\n}\n\n/** Requires granted scopes. */\nexport const requireScope = (scopes: string | readonly string[], match?: AuthioMatch): AuthioGuard => requireAuthio(scopeRule(scopes, match));\n/** Requires roles, from any source or only from the given `*_resource_access` claims. */\nexport const requireRole = (\n roles: string | readonly string[],\n options?: { match?: AuthioMatch; sources?: readonly AuthioResourceAccessSource[] },\n): AuthioGuard => requireAuthio(roleRule(roles, options));\n/** Requires a permission (`method` on `resource`). */\nexport const requirePermission = (resource: string, method: string, options?: { sources?: readonly AuthioResourceAccessSource[] }): AuthioGuard =>\n requireAuthio(permissionRule(resource, method, options));\n/** Requires groups. */\nexport const requireGroup = (groups: string | readonly string[], match?: AuthioMatch): AuthioGuard => requireAuthio(groupRule(groups, match));\n/** Requires a claim, or one of its values. */\nexport const requireClaim = (type: string, ...values: string[]): AuthioGuard => requireAuthio(claimRule(type, ...values));\n/** Requires an organization context, or a given organization. */\nexport const requireOrganization = (organization?: string): AuthioGuard => requireAuthio(organizationRule(organization));\n/** Requires a minimum authentication level (`acr`), and optionally a sign-in at most `maxAge` seconds old. */\nexport const requireAuthenticationLevel = (minimumLevel: number, options?: Parameters<typeof authenticationLevelRule>[1]): AuthioGuard =>\n requireAuthio(authenticationLevelRule(minimumLevel, options));\n/** Requires authentication methods (`amr`). */\nexport const requireAuthenticationMethod = (methods: string | readonly string[], match?: AuthioMatch, options?: { acrValues?: string }): AuthioGuard =>\n requireAuthio(authenticationMethodRule(methods, match, options));\n/** Requires a sign-in at most `maxAge` seconds old (`auth_time`). */\nexport const requireRecentAuthentication = (maxAge: number, options?: Parameters<typeof recentAuthenticationRule>[1]): AuthioGuard =>\n requireAuthio(recentAuthenticationRule(maxAge, options));\n/** Requires the caller to be of one of the kinds: `user`, `client`, `consumer`, `account_credential`. */\nexport const requireCallerKind = (kinds: AuthioCallerKind | readonly AuthioCallerKind[]): AuthioGuard => requireAuthio(callerKindRule(kinds));\n/** Requires the token to have been issued to one of the clients. */\nexport const requireClient = (clientIds: string | readonly string[]): AuthioGuard => requireAuthio(clientRule(clientIds));\n/** Requires every rule. */\nexport const allOf = (...rules: AuthioRule[]): AuthioGuard => requireAuthio(allOfRule(...rules));\n/** Requires at least one rule. */\nexport const anyOf = (...rules: AuthioRule[]): AuthioGuard => requireAuthio(anyOfRule(...rules));\n\n/**\n * A webhook route: verifies the signature over the raw body, deduplicates and dispatches. Mount it before any JSON\n * body parser, or behind `express.raw()`.\n */\nexport interface AuthioWebhookRoute extends RequestHandler {\n on<T extends keyof AuthioWebhookDataByType>(type: T, handler: AuthioWebhookHandler<AuthioTypedWebhookEvent<T>>): AuthioWebhookRoute;\n on(type: string, handler: AuthioWebhookHandler): AuthioWebhookRoute;\n}\n\n/**\n * Creates a webhook route: `app.post(\"/webhooks/authio\", authioWebhooks({ secret }).on(\"user_created\", …))`.\n * Answers `200` handled or duplicate, `400` unreadable, `401` bad signature, `413` too large, `500` handler failure.\n */\nexport function authioWebhooks(options: AuthioWebhookOptions): AuthioWebhookRoute {\n const webhooks = createAuthioWebhooks(options);\n\n const route = ((request: Request, response: Response, next: NextFunction) => {\n readRawBody(request, webhooks.maxBodySize)\n .then(async (body) => {\n if (body === undefined) {\n response.status(413).end();\n return;\n }\n\n const outcome = await webhooks.handle({ body, headers: request.headers });\n\n response.status(outcome.status).end();\n })\n .catch(next);\n }) as AuthioWebhookRoute;\n\n route.on = ((type: string, handler: AuthioWebhookHandler) => {\n webhooks.on(type, handler);\n return route;\n }) as AuthioWebhookRoute[\"on\"];\n\n return route;\n}\n\n/**\n * Options of `authioProtectedResourceMetadata`: the metadata itself, or a verifier configured with `resource`.\n */\nexport type AuthioProtectedResourceMetadataRouteOptions =\n | AuthioProtectedResourceMetadataOptions\n | (Omit<AuthioProtectedResourceMetadataOptions, \"resource\" | \"issuer\" | \"authority\" | \"realm\"> & { verifier: AuthioVerifier });\n\n/**\n * Serves the API's protected resource metadata (RFC 9728) at `/.well-known/oauth-protected-resource{path of the\n * resource}`: `app.use(authioProtectedResourceMetadata({ verifier }))`. Every other request goes on to `next`.\n */\nexport function authioProtectedResourceMetadata(options: AuthioProtectedResourceMetadataRouteOptions): RequestHandler {\n const settings: AuthioProtectedResourceMetadataOptions =\n \"verifier\" in options\n ? {\n ...options,\n resource: requireResource(options.verifier),\n issuer: options.verifier.connection.issuer,\n scopesSupported: options.scopesSupported ?? (options.verifier.requiredScopes.length > 0 ? options.verifier.requiredScopes : undefined),\n }\n : options;\n const document = JSON.stringify(createProtectedResourceMetadata(settings));\n const path = protectedResourceMetadataPath(settings.resource);\n\n return (request, response, next) => {\n if ((request.method !== \"GET\" && request.method !== \"HEAD\") || request.path.replace(/\\/+$/, \"\") !== path) return next();\n\n response.set(\"Cache-Control\", \"public, max-age=3600\").type(\"application/json\").send(document);\n };\n}\n\n/**\n * Options of `authioBackchannelLogout`.\n */\nexport interface AuthioBackchannelLogoutOptions {\n /** The client the logout tokens are addressed to (its id is their audience). */\n client: AuthioClient;\n /**\n * Ends the sessions the token names: every session of `subject`, or the one `sessionId` names. Session storage is\n * the application's.\n */\n onLogout: (logout: AuthioLogoutToken, request: Request) => void | Promise<void>;\n /** Called when a token is refused. */\n onError?: (error: unknown) => void;\n}\n\n/**\n * The back-channel logout endpoint (OpenID Connect Back-Channel Logout 1.0): reads `logout_token` from the\n * form-encoded body, validates it with the client and calls `onLogout`. Answers `200` on success, `400`\n * (`invalid_request`) for a token that does not validate, and `500` when `onLogout` throws; always `Cache-Control:\n * no-store`. Works with or without `express.urlencoded()` in front of it.\n */\nexport function authioBackchannelLogout(options: AuthioBackchannelLogoutOptions): RequestHandler {\n return (request, response, next) => {\n response.set(\"Cache-Control\", \"no-store\");\n\n readLogoutToken(request)\n .then(async (logoutToken) => {\n if (!logoutToken) {\n response.status(400).json({ error: \"invalid_request\", error_description: \"The request carries no logout_token.\" });\n return;\n }\n\n let logout: AuthioLogoutToken;\n\n try {\n logout = await options.client.validateLogoutToken(logoutToken);\n } catch (error) {\n options.onError?.(error);\n if (!(error instanceof AuthioTokenValidationError)) throw error;\n response.status(400).json({ error: \"invalid_request\", error_description: error.message });\n return;\n }\n\n await options.onLogout(logout, request);\n response.status(200).end();\n })\n .catch(next);\n };\n}\n\n/**\n * Options of `authioFrontchannelLogout`.\n */\nexport interface AuthioFrontchannelLogoutOptions {\n /** The client Authio ends the sessions of (its issuer is checked against `iss`). */\n client: AuthioClient;\n /**\n * Ends the sessions the request names: the Authio session `sessionId`, or the browser's own session (read from the\n * request's cookies) when Authio sent none. Session storage is the application's.\n */\n onLogout: (logout: AuthioFrontChannelLogout, request: Request) => void | Promise<void>;\n /** Called when a request is refused. */\n onError?: (error: unknown) => void;\n}\n\n/**\n * The front-channel logout endpoint (OpenID Connect Front-Channel Logout 1.0), the page Authio loads in an iframe when\n * a session ends: checks `iss` and `sid` with the client and calls `onLogout`. Answers `200` with an empty page, `400`\n * (`invalid_request`) for another issuer or a `sid` without `iss`, and `500` when `onLogout` throws; always\n * `Cache-Control: no-store`. Register its URL as the client's front-channel logout URI, with the session required.\n */\nexport function authioFrontchannelLogout(options: AuthioFrontchannelLogoutOptions): RequestHandler {\n return (request, response, next) => {\n response.set(\"Cache-Control\", \"no-store\");\n\n Promise.resolve()\n .then(async () => {\n let logout: AuthioFrontChannelLogout;\n\n try {\n logout = options.client.validateFrontChannelLogout(new URL(request.originalUrl ?? request.url, \"http://localhost\").searchParams);\n } catch (error) {\n options.onError?.(error);\n if (!(error instanceof AuthioCallbackError)) throw error;\n response.status(400).json({ error: \"invalid_request\", error_description: error.message });\n return;\n }\n\n await options.onLogout(logout, request);\n response.status(200).type(\"text/html; charset=utf-8\").send('<!doctype html><html><head><meta charset=\"utf-8\"><title>Authio</title></head><body></body></html>');\n })\n .catch(next);\n };\n}\n\n/**\n * Answers a refusal: `WWW-Authenticate`, the status, and problem details when asked.\n */\nexport function sendAuthioError(response: Response, error: AuthioBearerError | AuthioForbiddenError, options: AuthioResponseOptions = {}): void {\n const challenge = error.wwwAuthenticate;\n\n if (challenge) response.append(\"WWW-Authenticate\", challenge);\n\n response.status(error.status);\n\n if (options.problemDetails) response.type(\"application/problem+json\").send(JSON.stringify(toProblemDetails(error)));\n else response.end();\n}\n\nfunction refuse(request: Request, response: Response, next: NextFunction, error: unknown, options?: AuthioResponseOptions): void {\n const settings = options ?? (request as RequestWithSettings)[responseSettings] ?? {};\n\n if (!(error instanceof AuthioBearerError) && !(error instanceof AuthioForbiddenError)) return next(error);\n if (settings.passErrors) return next(error);\n\n sendAuthioError(response, error, settings);\n}\n\nfunction readRawBody(request: Request, maxBodySize: number): Promise<Uint8Array | undefined> {\n const parsed = (request as Request & { body?: unknown }).body;\n\n if (Buffer.isBuffer(parsed)) return Promise.resolve(parsed.byteLength > maxBodySize ? undefined : new Uint8Array(parsed));\n if (typeof parsed === \"string\") return Promise.resolve(new TextEncoder().encode(parsed));\n\n if (request.readableEnded || (parsed !== undefined && parsed !== null && typeof parsed === \"object\" && Object.keys(parsed).length > 0))\n return Promise.reject(\n new Error(\"The Authio webhook route needs the raw body: mount it before express.json(), or use express.raw({ type: '*/*' }) on it.\"),\n );\n\n const declared = Number(request.headers[\"content-length\"]);\n\n if (Number.isFinite(declared) && declared > maxBodySize) return Promise.resolve(undefined);\n\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n let done = false;\n\n request.on(\"data\", (chunk: Buffer) => {\n if (done) return;\n\n size += chunk.byteLength;\n\n if (size > maxBodySize) {\n done = true;\n request.pause();\n resolve(undefined);\n return;\n }\n\n chunks.push(chunk);\n });\n request.on(\"end\", () => {\n if (!done) resolve(new Uint8Array(Buffer.concat(chunks)));\n });\n request.on(\"error\", reject);\n });\n}\n\nasync function readLogoutToken(request: Request): Promise<string | undefined> {\n const parsed = (request as Request & { body?: unknown }).body;\n\n if (parsed && typeof parsed === \"object\" && !Buffer.isBuffer(parsed) && typeof (parsed as Record<string, unknown>).logout_token === \"string\")\n return (parsed as Record<string, string>).logout_token;\n\n const raw = Buffer.isBuffer(parsed)\n ? new Uint8Array(parsed)\n : typeof parsed === \"string\"\n ? new TextEncoder().encode(parsed)\n : request.readableEnded\n ? undefined\n : await readRawBody(request, 64 * 1024);\n\n return raw ? (new URLSearchParams(new TextDecoder().decode(raw)).get(\"logout_token\") ?? undefined) : undefined;\n}\n\nfunction requireResource(verifier: AuthioVerifier): string {\n if (!verifier.resource) throw new TypeError(\"authioProtectedResourceMetadata needs a verifier configured with resource.\");\n\n return verifier.resource;\n}\n\nfunction isVerifier(value: AuthioVerifier | AuthioVerifierOptions): value is AuthioVerifier {\n return typeof (value as AuthioVerifier).verify === \"function\";\n}\n\nexport type { AuthioMatch, AuthioRule, AuthioUser, AuthioVerifier, AuthioVerifierOptions } from \"@hyper.authio/server\";\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { RequestHandler, Response } from 'express';
|
|
2
|
-
import { AuthioWebhookDataByType, AuthioTypedWebhookEvent, AuthioResourceAccessSource } from '@hyper.authio/client';
|
|
3
|
-
import { AuthioUser, AuthioRule,
|
|
1
|
+
import { Request, RequestHandler, Response } from 'express';
|
|
2
|
+
import { AuthioClient, AuthioLogoutToken, AuthioFrontChannelLogout, AuthioWebhookDataByType, AuthioTypedWebhookEvent, AuthioCallerKind, AuthioResourceAccessSource } from '@hyper.authio/client';
|
|
3
|
+
import { AuthioUser, AuthioRule, AuthioProtectedResourceMetadataOptions, AuthioVerifier, AuthioWebhookHandler, AuthioVerifierOptions, AuthioWebhookOptions, requireAuthenticationLevel as requireAuthenticationLevel$1, AuthioMatch, requireRecentAuthentication as requireRecentAuthentication$1, AuthioBearerError, AuthioForbiddenError } from '@hyper.authio/server';
|
|
4
4
|
export { AuthioMatch, AuthioRule, AuthioUser, AuthioVerifier, AuthioVerifierOptions } from '@hyper.authio/server';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -70,10 +70,16 @@ declare const requireGroup: (groups: string | readonly string[], match?: AuthioM
|
|
|
70
70
|
declare const requireClaim: (type: string, ...values: string[]) => AuthioGuard;
|
|
71
71
|
/** Requires an organization context, or a given organization. */
|
|
72
72
|
declare const requireOrganization: (organization?: string) => AuthioGuard;
|
|
73
|
-
/** Requires a minimum authentication level (`acr`). */
|
|
74
|
-
declare const requireAuthenticationLevel: (minimumLevel: number) => AuthioGuard;
|
|
73
|
+
/** Requires a minimum authentication level (`acr`), and optionally a sign-in at most `maxAge` seconds old. */
|
|
74
|
+
declare const requireAuthenticationLevel: (minimumLevel: number, options?: Parameters<typeof requireAuthenticationLevel$1>[1]) => AuthioGuard;
|
|
75
75
|
/** Requires authentication methods (`amr`). */
|
|
76
|
-
declare const requireAuthenticationMethod: (methods: string | readonly string[], match?: AuthioMatch
|
|
76
|
+
declare const requireAuthenticationMethod: (methods: string | readonly string[], match?: AuthioMatch, options?: {
|
|
77
|
+
acrValues?: string;
|
|
78
|
+
}) => AuthioGuard;
|
|
79
|
+
/** Requires a sign-in at most `maxAge` seconds old (`auth_time`). */
|
|
80
|
+
declare const requireRecentAuthentication: (maxAge: number, options?: Parameters<typeof requireRecentAuthentication$1>[1]) => AuthioGuard;
|
|
81
|
+
/** Requires the caller to be of one of the kinds: `user`, `client`, `consumer`, `account_credential`. */
|
|
82
|
+
declare const requireCallerKind: (kinds: AuthioCallerKind | readonly AuthioCallerKind[]) => AuthioGuard;
|
|
77
83
|
/** Requires the token to have been issued to one of the clients. */
|
|
78
84
|
declare const requireClient: (clientIds: string | readonly string[]) => AuthioGuard;
|
|
79
85
|
/** Requires every rule. */
|
|
@@ -93,9 +99,62 @@ interface AuthioWebhookRoute extends RequestHandler {
|
|
|
93
99
|
* Answers `200` handled or duplicate, `400` unreadable, `401` bad signature, `413` too large, `500` handler failure.
|
|
94
100
|
*/
|
|
95
101
|
declare function authioWebhooks(options: AuthioWebhookOptions): AuthioWebhookRoute;
|
|
102
|
+
/**
|
|
103
|
+
* Options of `authioProtectedResourceMetadata`: the metadata itself, or a verifier configured with `resource`.
|
|
104
|
+
*/
|
|
105
|
+
type AuthioProtectedResourceMetadataRouteOptions = AuthioProtectedResourceMetadataOptions | (Omit<AuthioProtectedResourceMetadataOptions, "resource" | "issuer" | "authority" | "realm"> & {
|
|
106
|
+
verifier: AuthioVerifier;
|
|
107
|
+
});
|
|
108
|
+
/**
|
|
109
|
+
* Serves the API's protected resource metadata (RFC 9728) at `/.well-known/oauth-protected-resource{path of the
|
|
110
|
+
* resource}`: `app.use(authioProtectedResourceMetadata({ verifier }))`. Every other request goes on to `next`.
|
|
111
|
+
*/
|
|
112
|
+
declare function authioProtectedResourceMetadata(options: AuthioProtectedResourceMetadataRouteOptions): RequestHandler;
|
|
113
|
+
/**
|
|
114
|
+
* Options of `authioBackchannelLogout`.
|
|
115
|
+
*/
|
|
116
|
+
interface AuthioBackchannelLogoutOptions {
|
|
117
|
+
/** The client the logout tokens are addressed to (its id is their audience). */
|
|
118
|
+
client: AuthioClient;
|
|
119
|
+
/**
|
|
120
|
+
* Ends the sessions the token names: every session of `subject`, or the one `sessionId` names. Session storage is
|
|
121
|
+
* the application's.
|
|
122
|
+
*/
|
|
123
|
+
onLogout: (logout: AuthioLogoutToken, request: Request) => void | Promise<void>;
|
|
124
|
+
/** Called when a token is refused. */
|
|
125
|
+
onError?: (error: unknown) => void;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The back-channel logout endpoint (OpenID Connect Back-Channel Logout 1.0): reads `logout_token` from the
|
|
129
|
+
* form-encoded body, validates it with the client and calls `onLogout`. Answers `200` on success, `400`
|
|
130
|
+
* (`invalid_request`) for a token that does not validate, and `500` when `onLogout` throws; always `Cache-Control:
|
|
131
|
+
* no-store`. Works with or without `express.urlencoded()` in front of it.
|
|
132
|
+
*/
|
|
133
|
+
declare function authioBackchannelLogout(options: AuthioBackchannelLogoutOptions): RequestHandler;
|
|
134
|
+
/**
|
|
135
|
+
* Options of `authioFrontchannelLogout`.
|
|
136
|
+
*/
|
|
137
|
+
interface AuthioFrontchannelLogoutOptions {
|
|
138
|
+
/** The client Authio ends the sessions of (its issuer is checked against `iss`). */
|
|
139
|
+
client: AuthioClient;
|
|
140
|
+
/**
|
|
141
|
+
* Ends the sessions the request names: the Authio session `sessionId`, or the browser's own session (read from the
|
|
142
|
+
* request's cookies) when Authio sent none. Session storage is the application's.
|
|
143
|
+
*/
|
|
144
|
+
onLogout: (logout: AuthioFrontChannelLogout, request: Request) => void | Promise<void>;
|
|
145
|
+
/** Called when a request is refused. */
|
|
146
|
+
onError?: (error: unknown) => void;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The front-channel logout endpoint (OpenID Connect Front-Channel Logout 1.0), the page Authio loads in an iframe when
|
|
150
|
+
* a session ends: checks `iss` and `sid` with the client and calls `onLogout`. Answers `200` with an empty page, `400`
|
|
151
|
+
* (`invalid_request`) for another issuer or a `sid` without `iss`, and `500` when `onLogout` throws; always
|
|
152
|
+
* `Cache-Control: no-store`. Register its URL as the client's front-channel logout URI, with the session required.
|
|
153
|
+
*/
|
|
154
|
+
declare function authioFrontchannelLogout(options: AuthioFrontchannelLogoutOptions): RequestHandler;
|
|
96
155
|
/**
|
|
97
156
|
* Answers a refusal: `WWW-Authenticate`, the status, and problem details when asked.
|
|
98
157
|
*/
|
|
99
158
|
declare function sendAuthioError(response: Response, error: AuthioBearerError | AuthioForbiddenError, options?: AuthioResponseOptions): void;
|
|
100
159
|
|
|
101
|
-
export { type AuthioBearerOptions, type AuthioGuard, type AuthioRequestContext, type AuthioResponseOptions, type AuthioWebhookRoute, allOf, anyOf, authioBearer, authioWebhooks, requireAuthenticationLevel, requireAuthenticationMethod, requireAuthio, requireClaim, requireClient, requireGroup, requireOrganization, requirePermission, requireRole, requireScope, sendAuthioError };
|
|
160
|
+
export { type AuthioBackchannelLogoutOptions, type AuthioBearerOptions, type AuthioFrontchannelLogoutOptions, type AuthioGuard, type AuthioProtectedResourceMetadataRouteOptions, type AuthioRequestContext, type AuthioResponseOptions, type AuthioWebhookRoute, allOf, anyOf, authioBackchannelLogout, authioBearer, authioFrontchannelLogout, authioProtectedResourceMetadata, authioWebhooks, requireAuthenticationLevel, requireAuthenticationMethod, requireAuthio, requireCallerKind, requireClaim, requireClient, requireGroup, requireOrganization, requirePermission, requireRecentAuthentication, requireRole, requireScope, sendAuthioError };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { RequestHandler, Response } from 'express';
|
|
2
|
-
import { AuthioWebhookDataByType, AuthioTypedWebhookEvent, AuthioResourceAccessSource } from '@hyper.authio/client';
|
|
3
|
-
import { AuthioUser, AuthioRule,
|
|
1
|
+
import { Request, RequestHandler, Response } from 'express';
|
|
2
|
+
import { AuthioClient, AuthioLogoutToken, AuthioFrontChannelLogout, AuthioWebhookDataByType, AuthioTypedWebhookEvent, AuthioCallerKind, AuthioResourceAccessSource } from '@hyper.authio/client';
|
|
3
|
+
import { AuthioUser, AuthioRule, AuthioProtectedResourceMetadataOptions, AuthioVerifier, AuthioWebhookHandler, AuthioVerifierOptions, AuthioWebhookOptions, requireAuthenticationLevel as requireAuthenticationLevel$1, AuthioMatch, requireRecentAuthentication as requireRecentAuthentication$1, AuthioBearerError, AuthioForbiddenError } from '@hyper.authio/server';
|
|
4
4
|
export { AuthioMatch, AuthioRule, AuthioUser, AuthioVerifier, AuthioVerifierOptions } from '@hyper.authio/server';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -70,10 +70,16 @@ declare const requireGroup: (groups: string | readonly string[], match?: AuthioM
|
|
|
70
70
|
declare const requireClaim: (type: string, ...values: string[]) => AuthioGuard;
|
|
71
71
|
/** Requires an organization context, or a given organization. */
|
|
72
72
|
declare const requireOrganization: (organization?: string) => AuthioGuard;
|
|
73
|
-
/** Requires a minimum authentication level (`acr`). */
|
|
74
|
-
declare const requireAuthenticationLevel: (minimumLevel: number) => AuthioGuard;
|
|
73
|
+
/** Requires a minimum authentication level (`acr`), and optionally a sign-in at most `maxAge` seconds old. */
|
|
74
|
+
declare const requireAuthenticationLevel: (minimumLevel: number, options?: Parameters<typeof requireAuthenticationLevel$1>[1]) => AuthioGuard;
|
|
75
75
|
/** Requires authentication methods (`amr`). */
|
|
76
|
-
declare const requireAuthenticationMethod: (methods: string | readonly string[], match?: AuthioMatch
|
|
76
|
+
declare const requireAuthenticationMethod: (methods: string | readonly string[], match?: AuthioMatch, options?: {
|
|
77
|
+
acrValues?: string;
|
|
78
|
+
}) => AuthioGuard;
|
|
79
|
+
/** Requires a sign-in at most `maxAge` seconds old (`auth_time`). */
|
|
80
|
+
declare const requireRecentAuthentication: (maxAge: number, options?: Parameters<typeof requireRecentAuthentication$1>[1]) => AuthioGuard;
|
|
81
|
+
/** Requires the caller to be of one of the kinds: `user`, `client`, `consumer`, `account_credential`. */
|
|
82
|
+
declare const requireCallerKind: (kinds: AuthioCallerKind | readonly AuthioCallerKind[]) => AuthioGuard;
|
|
77
83
|
/** Requires the token to have been issued to one of the clients. */
|
|
78
84
|
declare const requireClient: (clientIds: string | readonly string[]) => AuthioGuard;
|
|
79
85
|
/** Requires every rule. */
|
|
@@ -93,9 +99,62 @@ interface AuthioWebhookRoute extends RequestHandler {
|
|
|
93
99
|
* Answers `200` handled or duplicate, `400` unreadable, `401` bad signature, `413` too large, `500` handler failure.
|
|
94
100
|
*/
|
|
95
101
|
declare function authioWebhooks(options: AuthioWebhookOptions): AuthioWebhookRoute;
|
|
102
|
+
/**
|
|
103
|
+
* Options of `authioProtectedResourceMetadata`: the metadata itself, or a verifier configured with `resource`.
|
|
104
|
+
*/
|
|
105
|
+
type AuthioProtectedResourceMetadataRouteOptions = AuthioProtectedResourceMetadataOptions | (Omit<AuthioProtectedResourceMetadataOptions, "resource" | "issuer" | "authority" | "realm"> & {
|
|
106
|
+
verifier: AuthioVerifier;
|
|
107
|
+
});
|
|
108
|
+
/**
|
|
109
|
+
* Serves the API's protected resource metadata (RFC 9728) at `/.well-known/oauth-protected-resource{path of the
|
|
110
|
+
* resource}`: `app.use(authioProtectedResourceMetadata({ verifier }))`. Every other request goes on to `next`.
|
|
111
|
+
*/
|
|
112
|
+
declare function authioProtectedResourceMetadata(options: AuthioProtectedResourceMetadataRouteOptions): RequestHandler;
|
|
113
|
+
/**
|
|
114
|
+
* Options of `authioBackchannelLogout`.
|
|
115
|
+
*/
|
|
116
|
+
interface AuthioBackchannelLogoutOptions {
|
|
117
|
+
/** The client the logout tokens are addressed to (its id is their audience). */
|
|
118
|
+
client: AuthioClient;
|
|
119
|
+
/**
|
|
120
|
+
* Ends the sessions the token names: every session of `subject`, or the one `sessionId` names. Session storage is
|
|
121
|
+
* the application's.
|
|
122
|
+
*/
|
|
123
|
+
onLogout: (logout: AuthioLogoutToken, request: Request) => void | Promise<void>;
|
|
124
|
+
/** Called when a token is refused. */
|
|
125
|
+
onError?: (error: unknown) => void;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The back-channel logout endpoint (OpenID Connect Back-Channel Logout 1.0): reads `logout_token` from the
|
|
129
|
+
* form-encoded body, validates it with the client and calls `onLogout`. Answers `200` on success, `400`
|
|
130
|
+
* (`invalid_request`) for a token that does not validate, and `500` when `onLogout` throws; always `Cache-Control:
|
|
131
|
+
* no-store`. Works with or without `express.urlencoded()` in front of it.
|
|
132
|
+
*/
|
|
133
|
+
declare function authioBackchannelLogout(options: AuthioBackchannelLogoutOptions): RequestHandler;
|
|
134
|
+
/**
|
|
135
|
+
* Options of `authioFrontchannelLogout`.
|
|
136
|
+
*/
|
|
137
|
+
interface AuthioFrontchannelLogoutOptions {
|
|
138
|
+
/** The client Authio ends the sessions of (its issuer is checked against `iss`). */
|
|
139
|
+
client: AuthioClient;
|
|
140
|
+
/**
|
|
141
|
+
* Ends the sessions the request names: the Authio session `sessionId`, or the browser's own session (read from the
|
|
142
|
+
* request's cookies) when Authio sent none. Session storage is the application's.
|
|
143
|
+
*/
|
|
144
|
+
onLogout: (logout: AuthioFrontChannelLogout, request: Request) => void | Promise<void>;
|
|
145
|
+
/** Called when a request is refused. */
|
|
146
|
+
onError?: (error: unknown) => void;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The front-channel logout endpoint (OpenID Connect Front-Channel Logout 1.0), the page Authio loads in an iframe when
|
|
150
|
+
* a session ends: checks `iss` and `sid` with the client and calls `onLogout`. Answers `200` with an empty page, `400`
|
|
151
|
+
* (`invalid_request`) for another issuer or a `sid` without `iss`, and `500` when `onLogout` throws; always
|
|
152
|
+
* `Cache-Control: no-store`. Register its URL as the client's front-channel logout URI, with the session required.
|
|
153
|
+
*/
|
|
154
|
+
declare function authioFrontchannelLogout(options: AuthioFrontchannelLogoutOptions): RequestHandler;
|
|
96
155
|
/**
|
|
97
156
|
* Answers a refusal: `WWW-Authenticate`, the status, and problem details when asked.
|
|
98
157
|
*/
|
|
99
158
|
declare function sendAuthioError(response: Response, error: AuthioBearerError | AuthioForbiddenError, options?: AuthioResponseOptions): void;
|
|
100
159
|
|
|
101
|
-
export { type AuthioBearerOptions, type AuthioGuard, type AuthioRequestContext, type AuthioResponseOptions, type AuthioWebhookRoute, allOf, anyOf, authioBearer, authioWebhooks, requireAuthenticationLevel, requireAuthenticationMethod, requireAuthio, requireClaim, requireClient, requireGroup, requireOrganization, requirePermission, requireRole, requireScope, sendAuthioError };
|
|
160
|
+
export { type AuthioBackchannelLogoutOptions, type AuthioBearerOptions, type AuthioFrontchannelLogoutOptions, type AuthioGuard, type AuthioProtectedResourceMetadataRouteOptions, type AuthioRequestContext, type AuthioResponseOptions, type AuthioWebhookRoute, allOf, anyOf, authioBackchannelLogout, authioBearer, authioFrontchannelLogout, authioProtectedResourceMetadata, authioWebhooks, requireAuthenticationLevel, requireAuthenticationMethod, requireAuthio, requireCallerKind, requireClaim, requireClient, requireGroup, requireOrganization, requirePermission, requireRecentAuthentication, requireRole, requireScope, sendAuthioError };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AuthioTokenValidationError, AuthioCallbackError } from '@hyper.authio/client';
|
|
2
|
+
import { createAuthioVerifier, requireScope as requireScope$1, AuthioForbiddenError, allOf as allOf$1, authorize, requireRole as requireRole$1, requirePermission as requirePermission$1, requireGroup as requireGroup$1, requireClaim as requireClaim$1, requireOrganization as requireOrganization$1, requireAuthenticationLevel as requireAuthenticationLevel$1, requireAuthenticationMethod as requireAuthenticationMethod$1, requireRecentAuthentication as requireRecentAuthentication$1, requireCallerKind as requireCallerKind$1, requireClient as requireClient$1, anyOf as anyOf$1, createAuthioWebhooks, createProtectedResourceMetadata, protectedResourceMetadataPath, toProblemDetails, AuthioBearerError } from '@hyper.authio/server';
|
|
2
3
|
|
|
3
4
|
// src/index.ts
|
|
4
5
|
var responseSettings = /* @__PURE__ */ Symbol.for("authio.express.response");
|
|
@@ -13,6 +14,8 @@ function authioBearer(verifier, options = {}) {
|
|
|
13
14
|
}
|
|
14
15
|
resolved.verifyAuthorizationHeader(header).then((user) => {
|
|
15
16
|
request.authio = { user, token: user.token };
|
|
17
|
+
const failure = resolved.requiredScopes.length > 0 ? requireScope$1(resolved.requiredScopes, "all").check(user) : void 0;
|
|
18
|
+
if (failure) return refuse(request, response, next, new AuthioForbiddenError(failure), options);
|
|
16
19
|
next();
|
|
17
20
|
}).catch((error) => refuse(request, response, next, error, options));
|
|
18
21
|
};
|
|
@@ -35,8 +38,10 @@ var requirePermission = (resource, method, options) => requireAuthio(requirePerm
|
|
|
35
38
|
var requireGroup = (groups, match) => requireAuthio(requireGroup$1(groups, match));
|
|
36
39
|
var requireClaim = (type, ...values) => requireAuthio(requireClaim$1(type, ...values));
|
|
37
40
|
var requireOrganization = (organization) => requireAuthio(requireOrganization$1(organization));
|
|
38
|
-
var requireAuthenticationLevel = (minimumLevel) => requireAuthio(requireAuthenticationLevel$1(minimumLevel));
|
|
39
|
-
var requireAuthenticationMethod = (methods, match) => requireAuthio(requireAuthenticationMethod$1(methods, match));
|
|
41
|
+
var requireAuthenticationLevel = (minimumLevel, options) => requireAuthio(requireAuthenticationLevel$1(minimumLevel, options));
|
|
42
|
+
var requireAuthenticationMethod = (methods, match, options) => requireAuthio(requireAuthenticationMethod$1(methods, match, options));
|
|
43
|
+
var requireRecentAuthentication = (maxAge, options) => requireAuthio(requireRecentAuthentication$1(maxAge, options));
|
|
44
|
+
var requireCallerKind = (kinds) => requireAuthio(requireCallerKind$1(kinds));
|
|
40
45
|
var requireClient = (clientIds) => requireAuthio(requireClient$1(clientIds));
|
|
41
46
|
var allOf = (...rules) => requireAuthio(allOf$1(...rules));
|
|
42
47
|
var anyOf = (...rules) => requireAuthio(anyOf$1(...rules));
|
|
@@ -58,6 +63,60 @@ function authioWebhooks(options) {
|
|
|
58
63
|
});
|
|
59
64
|
return route;
|
|
60
65
|
}
|
|
66
|
+
function authioProtectedResourceMetadata(options) {
|
|
67
|
+
const settings = "verifier" in options ? {
|
|
68
|
+
...options,
|
|
69
|
+
resource: requireResource(options.verifier),
|
|
70
|
+
issuer: options.verifier.connection.issuer,
|
|
71
|
+
scopesSupported: options.scopesSupported ?? (options.verifier.requiredScopes.length > 0 ? options.verifier.requiredScopes : void 0)
|
|
72
|
+
} : options;
|
|
73
|
+
const document = JSON.stringify(createProtectedResourceMetadata(settings));
|
|
74
|
+
const path = protectedResourceMetadataPath(settings.resource);
|
|
75
|
+
return (request, response, next) => {
|
|
76
|
+
if (request.method !== "GET" && request.method !== "HEAD" || request.path.replace(/\/+$/, "") !== path) return next();
|
|
77
|
+
response.set("Cache-Control", "public, max-age=3600").type("application/json").send(document);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function authioBackchannelLogout(options) {
|
|
81
|
+
return (request, response, next) => {
|
|
82
|
+
response.set("Cache-Control", "no-store");
|
|
83
|
+
readLogoutToken(request).then(async (logoutToken) => {
|
|
84
|
+
if (!logoutToken) {
|
|
85
|
+
response.status(400).json({ error: "invalid_request", error_description: "The request carries no logout_token." });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
let logout;
|
|
89
|
+
try {
|
|
90
|
+
logout = await options.client.validateLogoutToken(logoutToken);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
options.onError?.(error);
|
|
93
|
+
if (!(error instanceof AuthioTokenValidationError)) throw error;
|
|
94
|
+
response.status(400).json({ error: "invalid_request", error_description: error.message });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
await options.onLogout(logout, request);
|
|
98
|
+
response.status(200).end();
|
|
99
|
+
}).catch(next);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function authioFrontchannelLogout(options) {
|
|
103
|
+
return (request, response, next) => {
|
|
104
|
+
response.set("Cache-Control", "no-store");
|
|
105
|
+
Promise.resolve().then(async () => {
|
|
106
|
+
let logout;
|
|
107
|
+
try {
|
|
108
|
+
logout = options.client.validateFrontChannelLogout(new URL(request.originalUrl ?? request.url, "http://localhost").searchParams);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
options.onError?.(error);
|
|
111
|
+
if (!(error instanceof AuthioCallbackError)) throw error;
|
|
112
|
+
response.status(400).json({ error: "invalid_request", error_description: error.message });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await options.onLogout(logout, request);
|
|
116
|
+
response.status(200).type("text/html; charset=utf-8").send('<!doctype html><html><head><meta charset="utf-8"><title>Authio</title></head><body></body></html>');
|
|
117
|
+
}).catch(next);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
61
120
|
function sendAuthioError(response, error, options = {}) {
|
|
62
121
|
const challenge = error.wwwAuthenticate;
|
|
63
122
|
if (challenge) response.append("WWW-Authenticate", challenge);
|
|
@@ -102,10 +161,21 @@ function readRawBody(request, maxBodySize) {
|
|
|
102
161
|
request.on("error", reject);
|
|
103
162
|
});
|
|
104
163
|
}
|
|
164
|
+
async function readLogoutToken(request) {
|
|
165
|
+
const parsed = request.body;
|
|
166
|
+
if (parsed && typeof parsed === "object" && !Buffer.isBuffer(parsed) && typeof parsed.logout_token === "string")
|
|
167
|
+
return parsed.logout_token;
|
|
168
|
+
const raw = Buffer.isBuffer(parsed) ? new Uint8Array(parsed) : typeof parsed === "string" ? new TextEncoder().encode(parsed) : request.readableEnded ? void 0 : await readRawBody(request, 64 * 1024);
|
|
169
|
+
return raw ? new URLSearchParams(new TextDecoder().decode(raw)).get("logout_token") ?? void 0 : void 0;
|
|
170
|
+
}
|
|
171
|
+
function requireResource(verifier) {
|
|
172
|
+
if (!verifier.resource) throw new TypeError("authioProtectedResourceMetadata needs a verifier configured with resource.");
|
|
173
|
+
return verifier.resource;
|
|
174
|
+
}
|
|
105
175
|
function isVerifier(value) {
|
|
106
176
|
return typeof value.verify === "function";
|
|
107
177
|
}
|
|
108
178
|
|
|
109
|
-
export { allOf, anyOf, authioBearer, authioWebhooks, requireAuthenticationLevel, requireAuthenticationMethod, requireAuthio, requireClaim, requireClient, requireGroup, requireOrganization, requirePermission, requireRole, requireScope, sendAuthioError };
|
|
179
|
+
export { allOf, anyOf, authioBackchannelLogout, authioBearer, authioFrontchannelLogout, authioProtectedResourceMetadata, authioWebhooks, requireAuthenticationLevel, requireAuthenticationMethod, requireAuthio, requireCallerKind, requireClaim, requireClient, requireGroup, requireOrganization, requirePermission, requireRecentAuthentication, requireRole, requireScope, sendAuthioError };
|
|
110
180
|
//# sourceMappingURL=index.js.map
|
|
111
181
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["allOfRule","scopeRule","roleRule","permissionRule","groupRule","claimRule","organizationRule","authenticationLevelRule","authenticationMethodRule","clientRule","anyOfRule"],"mappings":";;;AAmEA,IAAM,gBAAA,mBAAmB,MAAA,CAAO,GAAA,CAAI,yBAAyB,CAAA;AAUtD,SAAS,YAAA,CAAa,QAAA,EAAkD,OAAA,GAA+B,EAAC,EAAmB;AAChI,EAAA,MAAM,WAAW,UAAA,CAAW,QAAQ,CAAA,GAAI,QAAA,GAAW,qBAAqB,QAAQ,CAAA;AAEhF,EAAA,OAAO,CAAC,OAAA,EAAS,QAAA,EAAU,IAAA,KAAS;AAClC,IAAC,OAAA,CAAgC,gBAAgB,CAAA,GAAI,EAAE,gBAAgB,OAAA,CAAQ,cAAA,EAAgB,UAAA,EAAY,OAAA,CAAQ,UAAA,EAAW;AAE9H,IAAA,MAAM,MAAA,GAAS,QAAQ,OAAA,CAAQ,aAAA;AAE/B,IAAA,IAAI,OAAA,CAAQ,QAAA,IAAY,CAAC,MAAA,EAAQ;AAC/B,MAAA,IAAA,EAAK;AACL,MAAA;AAAA,IACF;AAEA,IAAA,QAAA,CACG,yBAAA,CAA0B,MAAM,CAAA,CAChC,IAAA,CAAK,CAAC,IAAA,KAAS;AACd,MAAA,OAAA,CAAQ,MAAA,GAAS,EAAE,IAAA,EAAM,KAAA,EAAO,KAAK,KAAA,EAAO;AAC5C,MAAA,IAAA,EAAK;AAAA,IACP,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,KAAmB,MAAA,CAAO,OAAA,EAAS,QAAA,EAAU,IAAA,EAAM,KAAA,EAAO,OAAO,CAAC,CAAA;AAAA,EAC9E,CAAA;AACF;AAYO,SAAS,iBAAiB,KAAA,EAAkC;AACjE,EAAA,IAAI,MAAM,MAAA,KAAW,CAAA,EAAG,MAAM,IAAI,UAAU,wCAAwC,CAAA;AAEpF,EAAA,MAAM,IAAA,GAAO,MAAM,MAAA,KAAW,CAAA,GAAI,MAAM,CAAC,CAAA,GAAKA,OAAA,CAAU,GAAG,KAAK,CAAA;AAChE,EAAA,MAAM,KAAA,GAAwB,CAAC,OAAA,EAAS,QAAA,EAAU,IAAA,KAAS;AACzD,IAAA,MAAM,IAAA,GAAO,QAAQ,MAAA,EAAQ,IAAA;AAE7B,IAAA,IAAI,CAAC,IAAA,EAAM,OAAO,MAAA,CAAO,SAAS,QAAA,EAAU,IAAA,EAAM,IAAI,iBAAA,CAAkB,MAAA,EAAW,EAAE,KAAA,EAAO,MAAA,EAAW,CAAC,CAAA;AAExG,IAAA,MAAM,OAAA,GAAU,SAAA,CAAU,IAAA,EAAM,GAAG,KAAK,CAAA;AAExC,IAAA,IAAI,OAAA,SAAgB,MAAA,CAAO,OAAA,EAAS,UAAU,IAAA,EAAM,IAAI,oBAAA,CAAqB,OAAO,CAAC,CAAA;AAErF,IAAA,IAAA,EAAK;AAAA,EACP,CAAA;AAEA,EAAA,OAAO,MAAA,CAAO,MAAA,CAAO,KAAA,EAAO,EAAE,aAAa,IAAA,CAAK,WAAA,EAAa,KAAA,EAAO,CAAC,SAAqB,SAAA,CAAU,IAAA,EAAM,GAAG,KAAK,GAAG,CAAA;AACvH;AAGO,IAAM,YAAA,GAAe,CAAC,MAAA,EAAoC,KAAA,KAAqC,cAAcC,cAAA,CAAU,MAAA,EAAQ,KAAK,CAAC;AAErI,IAAM,WAAA,GAAc,CACzB,KAAA,EACA,OAAA,KACgB,cAAcC,aAAA,CAAS,KAAA,EAAO,OAAO,CAAC;AAEjD,IAAM,iBAAA,GAAoB,CAAC,QAAA,EAAkB,MAAA,EAAgB,OAAA,KAClE,cAAcC,mBAAA,CAAe,QAAA,EAAU,MAAA,EAAQ,OAAO,CAAC;AAElD,IAAM,YAAA,GAAe,CAAC,MAAA,EAAoC,KAAA,KAAqC,cAAcC,cAAA,CAAU,MAAA,EAAQ,KAAK,CAAC;AAErI,IAAM,YAAA,GAAe,CAAC,IAAA,EAAA,GAAiB,MAAA,KAAkC,cAAcC,cAAA,CAAU,IAAA,EAAM,GAAG,MAAM,CAAC;AAEjH,IAAM,sBAAsB,CAAC,YAAA,KAAuC,aAAA,CAAcC,qBAAA,CAAiB,YAAY,CAAC;AAEhH,IAAM,6BAA6B,CAAC,YAAA,KAAsC,aAAA,CAAcC,4BAAA,CAAwB,YAAY,CAAC;AAE7H,IAAM,2BAAA,GAA8B,CAAC,OAAA,EAAqC,KAAA,KAC/E,cAAcC,6BAAA,CAAyB,OAAA,EAAS,KAAK,CAAC;AAEjD,IAAM,gBAAgB,CAAC,SAAA,KAAuD,aAAA,CAAcC,eAAA,CAAW,SAAS,CAAC;AAEjH,IAAM,QAAQ,CAAA,GAAI,KAAA,KAAqC,cAAcT,OAAA,CAAU,GAAG,KAAK,CAAC;AAExF,IAAM,QAAQ,CAAA,GAAI,KAAA,KAAqC,cAAcU,OAAA,CAAU,GAAG,KAAK,CAAC;AAexF,SAAS,eAAe,OAAA,EAAmD;AAChF,EAAA,MAAM,QAAA,GAAW,qBAAqB,OAAO,CAAA;AAE7C,EAAA,MAAM,KAAA,IAAS,CAAC,OAAA,EAAkB,QAAA,EAAoB,IAAA,KAAuB;AAC3E,IAAA,WAAA,CAAY,SAAS,QAAA,CAAS,WAAW,CAAA,CACtC,IAAA,CAAK,OAAO,IAAA,KAAS;AACpB,MAAA,IAAI,SAAS,MAAA,EAAW;AACtB,QAAA,QAAA,CAAS,MAAA,CAAO,GAAG,CAAA,CAAE,GAAA,EAAI;AACzB,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAU,MAAM,QAAA,CAAS,MAAA,CAAO,EAAE,IAAA,EAAM,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAS,CAAA;AAExE,MAAA,QAAA,CAAS,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,GAAA,EAAI;AAAA,IACtC,CAAC,CAAA,CACA,KAAA,CAAM,IAAI,CAAA;AAAA,EACf,CAAA,CAAA;AAEA,EAAA,KAAA,CAAM,EAAA,IAAM,CAAC,IAAA,EAAc,OAAA,KAAkC;AAC3D,IAAA,QAAA,CAAS,EAAA,CAAG,MAAM,OAAO,CAAA;AACzB,IAAA,OAAO,KAAA;AAAA,EACT,CAAA,CAAA;AAEA,EAAA,OAAO,KAAA;AACT;AAKO,SAAS,eAAA,CAAgB,QAAA,EAAoB,KAAA,EAAiD,OAAA,GAAiC,EAAC,EAAS;AAC9I,EAAA,MAAM,YAAY,KAAA,CAAM,eAAA;AAExB,EAAA,IAAI,SAAA,EAAW,QAAA,CAAS,MAAA,CAAO,kBAAA,EAAoB,SAAS,CAAA;AAE5D,EAAA,QAAA,CAAS,MAAA,CAAO,MAAM,MAAM,CAAA;AAE5B,EAAA,IAAI,OAAA,CAAQ,cAAA,EAAgB,QAAA,CAAS,IAAA,CAAK,0BAA0B,CAAA,CAAE,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,gBAAA,CAAiB,KAAK,CAAC,CAAC,CAAA;AAAA,gBACpG,GAAA,EAAI;AACpB;AAEA,SAAS,MAAA,CAAO,OAAA,EAAkB,QAAA,EAAoB,IAAA,EAAoB,OAAgB,OAAA,EAAuC;AAC/H,EAAA,MAAM,QAAA,GAAW,OAAA,IAAY,OAAA,CAAgC,gBAAgB,KAAK,EAAC;AAEnF,EAAA,IAAI,EAAE,iBAAiB,iBAAA,CAAA,IAAsB,EAAE,iBAAiB,oBAAA,CAAA,EAAuB,OAAO,KAAK,KAAK,CAAA;AACxG,EAAA,IAAI,QAAA,CAAS,UAAA,EAAY,OAAO,IAAA,CAAK,KAAK,CAAA;AAE1C,EAAA,eAAA,CAAgB,QAAA,EAAU,OAAO,QAAQ,CAAA;AAC3C;AAEA,SAAS,WAAA,CAAY,SAAkB,WAAA,EAAsD;AAC3F,EAAA,MAAM,SAAU,OAAA,CAAyC,IAAA;AAEzD,EAAA,IAAI,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,SAAU,OAAA,CAAQ,OAAA,CAAQ,MAAA,CAAO,UAAA,GAAa,WAAA,GAAc,MAAA,GAAY,IAAI,UAAA,CAAW,MAAM,CAAC,CAAA;AACxH,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,EAAU,OAAO,OAAA,CAAQ,OAAA,CAAQ,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,MAAM,CAAC,CAAA;AAEvF,EAAA,IAAI,OAAA,CAAQ,aAAA,IAAkB,MAAA,KAAW,MAAA,IAAa,MAAA,KAAW,IAAA,IAAQ,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,CAAO,IAAA,CAAK,MAAM,EAAE,MAAA,GAAS,CAAA;AAClI,IAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,MACb,IAAI,MAAM,yHAAyH;AAAA,KACrI;AAEF,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,OAAA,CAAQ,OAAA,CAAQ,gBAAgB,CAAC,CAAA;AAEzD,EAAA,IAAI,MAAA,CAAO,SAAS,QAAQ,CAAA,IAAK,WAAW,WAAA,EAAa,OAAO,OAAA,CAAQ,OAAA,CAAQ,MAAS,CAAA;AAEzF,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,SAAmB,EAAC;AAC1B,IAAA,IAAI,IAAA,GAAO,CAAA;AACX,IAAA,IAAI,IAAA,GAAO,KAAA;AAEX,IAAA,OAAA,CAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,KAAA,KAAkB;AACpC,MAAA,IAAI,IAAA,EAAM;AAEV,MAAA,IAAA,IAAQ,KAAA,CAAM,UAAA;AAEd,MAAA,IAAI,OAAO,WAAA,EAAa;AACtB,QAAA,IAAA,GAAO,IAAA;AACP,QAAA,OAAA,CAAQ,KAAA,EAAM;AACd,QAAA,OAAA,CAAQ,MAAS,CAAA;AACjB,QAAA;AAAA,MACF;AAEA,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAA,IACnB,CAAC,CAAA;AACD,IAAA,OAAA,CAAQ,EAAA,CAAG,OAAO,MAAM;AACtB,MAAA,IAAI,CAAC,MAAM,OAAA,CAAQ,IAAI,WAAW,MAAA,CAAO,MAAA,CAAO,MAAM,CAAC,CAAC,CAAA;AAAA,IAC1D,CAAC,CAAA;AACD,IAAA,OAAA,CAAQ,EAAA,CAAG,SAAS,MAAM,CAAA;AAAA,EAC5B,CAAC,CAAA;AACH;AAEA,SAAS,WAAW,KAAA,EAAwE;AAC1F,EAAA,OAAO,OAAQ,MAAyB,MAAA,KAAW,UAAA;AACrD","file":"index.js","sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from \"express\";\nimport type { AuthioResourceAccessSource, AuthioTypedWebhookEvent, AuthioWebhookDataByType } from \"@hyper.authio/client\";\nimport {\n allOf as allOfRule,\n anyOf as anyOfRule,\n authorize,\n AuthioBearerError,\n AuthioForbiddenError,\n createAuthioVerifier,\n createAuthioWebhooks,\n requireAuthenticationLevel as authenticationLevelRule,\n requireAuthenticationMethod as authenticationMethodRule,\n requireClaim as claimRule,\n requireClient as clientRule,\n requireGroup as groupRule,\n requireOrganization as organizationRule,\n requirePermission as permissionRule,\n requireRole as roleRule,\n requireScope as scopeRule,\n toProblemDetails,\n type AuthioMatch,\n type AuthioRule,\n type AuthioUser,\n type AuthioVerifier,\n type AuthioVerifierOptions,\n type AuthioWebhookHandler,\n type AuthioWebhookOptions,\n} from \"@hyper.authio/server\";\n\n/**\n * What `authioBearer` puts on the request.\n */\nexport interface AuthioRequestContext {\n /** The caller. */\n readonly user: AuthioUser;\n /** The access token the caller was read from. */\n readonly token: string;\n}\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** The Authio caller, once `authioBearer` validated the token. */\n authio?: AuthioRequestContext;\n }\n }\n}\n\n/**\n * How refusals are answered.\n */\nexport interface AuthioResponseOptions {\n /** Answers `401` and `403` with problem details (RFC 9457) as well as the `WWW-Authenticate` header. */\n problemDetails?: boolean;\n /** Passes refusals to `next(error)` instead of answering, for an application's own error handler. */\n passErrors?: boolean;\n}\n\n/**\n * Options of `authioBearer`.\n */\nexport interface AuthioBearerOptions extends AuthioResponseOptions {\n /** Lets a request without a token through, unauthenticated; a token that is present must still be valid. */\n optional?: boolean;\n}\n\nconst responseSettings = Symbol.for(\"authio.express.response\");\n\ntype RequestWithSettings = Request & { [responseSettings]?: AuthioResponseOptions };\n\n/**\n * Validates the `Authorization: Bearer` token and puts the caller on `req.authio`. Answers `401` with the\n * `WWW-Authenticate` challenge when the token is missing or invalid.\n *\n * @param verifier A verifier, or its options.\n */\nexport function authioBearer(verifier: AuthioVerifier | AuthioVerifierOptions, options: AuthioBearerOptions = {}): RequestHandler {\n const resolved = isVerifier(verifier) ? verifier : createAuthioVerifier(verifier);\n\n return (request, response, next) => {\n (request as RequestWithSettings)[responseSettings] = { problemDetails: options.problemDetails, passErrors: options.passErrors };\n\n const header = request.headers.authorization;\n\n if (options.optional && !header) {\n next();\n return;\n }\n\n resolved\n .verifyAuthorizationHeader(header)\n .then((user) => {\n request.authio = { user, token: user.token! };\n next();\n })\n .catch((error: unknown) => refuse(request, response, next, error, options));\n };\n}\n\n/**\n * An Express guard that is also an Authio rule: use it as middleware, or pass it to `requireAuthio`, `allOf` and\n * `anyOf` like any rule of `@hyper.authio/server`.\n */\nexport type AuthioGuard = RequestHandler & AuthioRule;\n\n/**\n * Requires every rule (guards of this package or rules of `@hyper.authio/server`); answers `401` without a caller and\n * `403` with the joined refusal otherwise.\n */\nexport function requireAuthio(...rules: AuthioRule[]): AuthioGuard {\n if (rules.length === 0) throw new TypeError(\"requireAuthio needs at least one rule.\");\n\n const rule = rules.length === 1 ? rules[0]! : allOfRule(...rules);\n const guard: RequestHandler = (request, response, next) => {\n const user = request.authio?.user;\n\n if (!user) return refuse(request, response, next, new AuthioBearerError(undefined, { error: undefined }));\n\n const failure = authorize(user, ...rules);\n\n if (failure) return refuse(request, response, next, new AuthioForbiddenError(failure));\n\n next();\n };\n\n return Object.assign(guard, { description: rule.description, check: (user: AuthioUser) => authorize(user, ...rules) });\n}\n\n/** Requires granted scopes. */\nexport const requireScope = (scopes: string | readonly string[], match?: AuthioMatch): AuthioGuard => requireAuthio(scopeRule(scopes, match));\n/** Requires roles, from any source or only from the given `*_resource_access` claims. */\nexport const requireRole = (\n roles: string | readonly string[],\n options?: { match?: AuthioMatch; sources?: readonly AuthioResourceAccessSource[] },\n): AuthioGuard => requireAuthio(roleRule(roles, options));\n/** Requires a permission (`method` on `resource`). */\nexport const requirePermission = (resource: string, method: string, options?: { sources?: readonly AuthioResourceAccessSource[] }): AuthioGuard =>\n requireAuthio(permissionRule(resource, method, options));\n/** Requires groups. */\nexport const requireGroup = (groups: string | readonly string[], match?: AuthioMatch): AuthioGuard => requireAuthio(groupRule(groups, match));\n/** Requires a claim, or one of its values. */\nexport const requireClaim = (type: string, ...values: string[]): AuthioGuard => requireAuthio(claimRule(type, ...values));\n/** Requires an organization context, or a given organization. */\nexport const requireOrganization = (organization?: string): AuthioGuard => requireAuthio(organizationRule(organization));\n/** Requires a minimum authentication level (`acr`). */\nexport const requireAuthenticationLevel = (minimumLevel: number): AuthioGuard => requireAuthio(authenticationLevelRule(minimumLevel));\n/** Requires authentication methods (`amr`). */\nexport const requireAuthenticationMethod = (methods: string | readonly string[], match?: AuthioMatch): AuthioGuard =>\n requireAuthio(authenticationMethodRule(methods, match));\n/** Requires the token to have been issued to one of the clients. */\nexport const requireClient = (clientIds: string | readonly string[]): AuthioGuard => requireAuthio(clientRule(clientIds));\n/** Requires every rule. */\nexport const allOf = (...rules: AuthioRule[]): AuthioGuard => requireAuthio(allOfRule(...rules));\n/** Requires at least one rule. */\nexport const anyOf = (...rules: AuthioRule[]): AuthioGuard => requireAuthio(anyOfRule(...rules));\n\n/**\n * A webhook route: verifies the signature over the raw body, deduplicates and dispatches. Mount it before any JSON\n * body parser, or behind `express.raw()`.\n */\nexport interface AuthioWebhookRoute extends RequestHandler {\n on<T extends keyof AuthioWebhookDataByType>(type: T, handler: AuthioWebhookHandler<AuthioTypedWebhookEvent<T>>): AuthioWebhookRoute;\n on(type: string, handler: AuthioWebhookHandler): AuthioWebhookRoute;\n}\n\n/**\n * Creates a webhook route: `app.post(\"/webhooks/authio\", authioWebhooks({ secret }).on(\"user_created\", …))`.\n * Answers `200` handled or duplicate, `400` unreadable, `401` bad signature, `413` too large, `500` handler failure.\n */\nexport function authioWebhooks(options: AuthioWebhookOptions): AuthioWebhookRoute {\n const webhooks = createAuthioWebhooks(options);\n\n const route = ((request: Request, response: Response, next: NextFunction) => {\n readRawBody(request, webhooks.maxBodySize)\n .then(async (body) => {\n if (body === undefined) {\n response.status(413).end();\n return;\n }\n\n const outcome = await webhooks.handle({ body, headers: request.headers });\n\n response.status(outcome.status).end();\n })\n .catch(next);\n }) as AuthioWebhookRoute;\n\n route.on = ((type: string, handler: AuthioWebhookHandler) => {\n webhooks.on(type, handler);\n return route;\n }) as AuthioWebhookRoute[\"on\"];\n\n return route;\n}\n\n/**\n * Answers a refusal: `WWW-Authenticate`, the status, and problem details when asked.\n */\nexport function sendAuthioError(response: Response, error: AuthioBearerError | AuthioForbiddenError, options: AuthioResponseOptions = {}): void {\n const challenge = error.wwwAuthenticate;\n\n if (challenge) response.append(\"WWW-Authenticate\", challenge);\n\n response.status(error.status);\n\n if (options.problemDetails) response.type(\"application/problem+json\").send(JSON.stringify(toProblemDetails(error)));\n else response.end();\n}\n\nfunction refuse(request: Request, response: Response, next: NextFunction, error: unknown, options?: AuthioResponseOptions): void {\n const settings = options ?? (request as RequestWithSettings)[responseSettings] ?? {};\n\n if (!(error instanceof AuthioBearerError) && !(error instanceof AuthioForbiddenError)) return next(error);\n if (settings.passErrors) return next(error);\n\n sendAuthioError(response, error, settings);\n}\n\nfunction readRawBody(request: Request, maxBodySize: number): Promise<Uint8Array | undefined> {\n const parsed = (request as Request & { body?: unknown }).body;\n\n if (Buffer.isBuffer(parsed)) return Promise.resolve(parsed.byteLength > maxBodySize ? undefined : new Uint8Array(parsed));\n if (typeof parsed === \"string\") return Promise.resolve(new TextEncoder().encode(parsed));\n\n if (request.readableEnded || (parsed !== undefined && parsed !== null && typeof parsed === \"object\" && Object.keys(parsed).length > 0))\n return Promise.reject(\n new Error(\"The Authio webhook route needs the raw body: mount it before express.json(), or use express.raw({ type: '*/*' }) on it.\"),\n );\n\n const declared = Number(request.headers[\"content-length\"]);\n\n if (Number.isFinite(declared) && declared > maxBodySize) return Promise.resolve(undefined);\n\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n let done = false;\n\n request.on(\"data\", (chunk: Buffer) => {\n if (done) return;\n\n size += chunk.byteLength;\n\n if (size > maxBodySize) {\n done = true;\n request.pause();\n resolve(undefined);\n return;\n }\n\n chunks.push(chunk);\n });\n request.on(\"end\", () => {\n if (!done) resolve(new Uint8Array(Buffer.concat(chunks)));\n });\n request.on(\"error\", reject);\n });\n}\n\nfunction isVerifier(value: AuthioVerifier | AuthioVerifierOptions): value is AuthioVerifier {\n return typeof (value as AuthioVerifier).verify === \"function\";\n}\n\nexport type { AuthioMatch, AuthioRule, AuthioUser, AuthioVerifier, AuthioVerifierOptions } from \"@hyper.authio/server\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["scopeRule","allOfRule","roleRule","permissionRule","groupRule","claimRule","organizationRule","authenticationLevelRule","authenticationMethodRule","recentAuthenticationRule","callerKindRule","clientRule","anyOfRule"],"mappings":";;;;AAmFA,IAAM,gBAAA,mBAAmB,MAAA,CAAO,GAAA,CAAI,yBAAyB,CAAA;AAUtD,SAAS,YAAA,CAAa,QAAA,EAAkD,OAAA,GAA+B,EAAC,EAAmB;AAChI,EAAA,MAAM,WAAW,UAAA,CAAW,QAAQ,CAAA,GAAI,QAAA,GAAW,qBAAqB,QAAQ,CAAA;AAEhF,EAAA,OAAO,CAAC,OAAA,EAAS,QAAA,EAAU,IAAA,KAAS;AAClC,IAAC,OAAA,CAAgC,gBAAgB,CAAA,GAAI,EAAE,gBAAgB,OAAA,CAAQ,cAAA,EAAgB,UAAA,EAAY,OAAA,CAAQ,UAAA,EAAW;AAE9H,IAAA,MAAM,MAAA,GAAS,QAAQ,OAAA,CAAQ,aAAA;AAE/B,IAAA,IAAI,OAAA,CAAQ,QAAA,IAAY,CAAC,MAAA,EAAQ;AAC/B,MAAA,IAAA,EAAK;AACL,MAAA;AAAA,IACF;AAEA,IAAA,QAAA,CACG,yBAAA,CAA0B,MAAM,CAAA,CAChC,IAAA,CAAK,CAAC,IAAA,KAAS;AACd,MAAA,OAAA,CAAQ,MAAA,GAAS,EAAE,IAAA,EAAM,KAAA,EAAO,KAAK,KAAA,EAAO;AAE5C,MAAA,MAAM,OAAA,GAAU,QAAA,CAAS,cAAA,CAAe,MAAA,GAAS,CAAA,GAAIA,cAAA,CAAU,QAAA,CAAS,cAAA,EAAgB,KAAK,CAAA,CAAE,KAAA,CAAM,IAAI,CAAA,GAAI,MAAA;AAE7G,MAAA,IAAI,OAAA,EAAS,OAAO,MAAA,CAAO,OAAA,EAAS,QAAA,EAAU,MAAM,IAAI,oBAAA,CAAqB,OAA4B,CAAA,EAAG,OAAO,CAAA;AAEnH,MAAA,IAAA,EAAK;AAAA,IACP,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,KAAmB,MAAA,CAAO,OAAA,EAAS,QAAA,EAAU,IAAA,EAAM,KAAA,EAAO,OAAO,CAAC,CAAA;AAAA,EAC9E,CAAA;AACF;AAYO,SAAS,iBAAiB,KAAA,EAAkC;AACjE,EAAA,IAAI,MAAM,MAAA,KAAW,CAAA,EAAG,MAAM,IAAI,UAAU,wCAAwC,CAAA;AAEpF,EAAA,MAAM,IAAA,GAAO,MAAM,MAAA,KAAW,CAAA,GAAI,MAAM,CAAC,CAAA,GAAKC,OAAA,CAAU,GAAG,KAAK,CAAA;AAChE,EAAA,MAAM,KAAA,GAAwB,CAAC,OAAA,EAAS,QAAA,EAAU,IAAA,KAAS;AACzD,IAAA,MAAM,IAAA,GAAO,QAAQ,MAAA,EAAQ,IAAA;AAE7B,IAAA,IAAI,CAAC,IAAA,EAAM,OAAO,MAAA,CAAO,SAAS,QAAA,EAAU,IAAA,EAAM,IAAI,iBAAA,CAAkB,MAAA,EAAW,EAAE,KAAA,EAAO,MAAA,EAAW,CAAC,CAAA;AAExG,IAAA,MAAM,OAAA,GAAU,SAAA,CAAU,IAAA,EAAM,GAAG,KAAK,CAAA;AAExC,IAAA,IAAI,OAAA,SAAgB,MAAA,CAAO,OAAA,EAAS,UAAU,IAAA,EAAM,IAAI,oBAAA,CAAqB,OAAO,CAAC,CAAA;AAErF,IAAA,IAAA,EAAK;AAAA,EACP,CAAA;AAEA,EAAA,OAAO,MAAA,CAAO,MAAA,CAAO,KAAA,EAAO,EAAE,aAAa,IAAA,CAAK,WAAA,EAAa,KAAA,EAAO,CAAC,SAAqB,SAAA,CAAU,IAAA,EAAM,GAAG,KAAK,GAAG,CAAA;AACvH;AAGO,IAAM,YAAA,GAAe,CAAC,MAAA,EAAoC,KAAA,KAAqC,cAAcD,cAAA,CAAU,MAAA,EAAQ,KAAK,CAAC;AAErI,IAAM,WAAA,GAAc,CACzB,KAAA,EACA,OAAA,KACgB,cAAcE,aAAA,CAAS,KAAA,EAAO,OAAO,CAAC;AAEjD,IAAM,iBAAA,GAAoB,CAAC,QAAA,EAAkB,MAAA,EAAgB,OAAA,KAClE,cAAcC,mBAAA,CAAe,QAAA,EAAU,MAAA,EAAQ,OAAO,CAAC;AAElD,IAAM,YAAA,GAAe,CAAC,MAAA,EAAoC,KAAA,KAAqC,cAAcC,cAAA,CAAU,MAAA,EAAQ,KAAK,CAAC;AAErI,IAAM,YAAA,GAAe,CAAC,IAAA,EAAA,GAAiB,MAAA,KAAkC,cAAcC,cAAA,CAAU,IAAA,EAAM,GAAG,MAAM,CAAC;AAEjH,IAAM,sBAAsB,CAAC,YAAA,KAAuC,aAAA,CAAcC,qBAAA,CAAiB,YAAY,CAAC;AAEhH,IAAM,0BAAA,GAA6B,CAAC,YAAA,EAAsB,OAAA,KAC/D,cAAcC,4BAAA,CAAwB,YAAA,EAAc,OAAO,CAAC;AAEvD,IAAM,2BAAA,GAA8B,CAAC,OAAA,EAAqC,KAAA,EAAqB,OAAA,KACpG,cAAcC,6BAAA,CAAyB,OAAA,EAAS,KAAA,EAAO,OAAO,CAAC;AAE1D,IAAM,2BAAA,GAA8B,CAAC,MAAA,EAAgB,OAAA,KAC1D,cAAcC,6BAAA,CAAyB,MAAA,EAAQ,OAAO,CAAC;AAElD,IAAM,oBAAoB,CAAC,KAAA,KAAuE,aAAA,CAAcC,mBAAA,CAAe,KAAK,CAAC;AAErI,IAAM,gBAAgB,CAAC,SAAA,KAAuD,aAAA,CAAcC,eAAA,CAAW,SAAS,CAAC;AAEjH,IAAM,QAAQ,CAAA,GAAI,KAAA,KAAqC,cAAcV,OAAA,CAAU,GAAG,KAAK,CAAC;AAExF,IAAM,QAAQ,CAAA,GAAI,KAAA,KAAqC,cAAcW,OAAA,CAAU,GAAG,KAAK,CAAC;AAexF,SAAS,eAAe,OAAA,EAAmD;AAChF,EAAA,MAAM,QAAA,GAAW,qBAAqB,OAAO,CAAA;AAE7C,EAAA,MAAM,KAAA,IAAS,CAAC,OAAA,EAAkB,QAAA,EAAoB,IAAA,KAAuB;AAC3E,IAAA,WAAA,CAAY,SAAS,QAAA,CAAS,WAAW,CAAA,CACtC,IAAA,CAAK,OAAO,IAAA,KAAS;AACpB,MAAA,IAAI,SAAS,MAAA,EAAW;AACtB,QAAA,QAAA,CAAS,MAAA,CAAO,GAAG,CAAA,CAAE,GAAA,EAAI;AACzB,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAU,MAAM,QAAA,CAAS,MAAA,CAAO,EAAE,IAAA,EAAM,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAS,CAAA;AAExE,MAAA,QAAA,CAAS,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,GAAA,EAAI;AAAA,IACtC,CAAC,CAAA,CACA,KAAA,CAAM,IAAI,CAAA;AAAA,EACf,CAAA,CAAA;AAEA,EAAA,KAAA,CAAM,EAAA,IAAM,CAAC,IAAA,EAAc,OAAA,KAAkC;AAC3D,IAAA,QAAA,CAAS,EAAA,CAAG,MAAM,OAAO,CAAA;AACzB,IAAA,OAAO,KAAA;AAAA,EACT,CAAA,CAAA;AAEA,EAAA,OAAO,KAAA;AACT;AAaO,SAAS,gCAAgC,OAAA,EAAsE;AACpH,EAAA,MAAM,QAAA,GACJ,cAAc,OAAA,GACV;AAAA,IACE,GAAG,OAAA;AAAA,IACH,QAAA,EAAU,eAAA,CAAgB,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAC1C,MAAA,EAAQ,OAAA,CAAQ,QAAA,CAAS,UAAA,CAAW,MAAA;AAAA,IACpC,eAAA,EAAiB,OAAA,CAAQ,eAAA,KAAoB,OAAA,CAAQ,QAAA,CAAS,eAAe,MAAA,GAAS,CAAA,GAAI,OAAA,CAAQ,QAAA,CAAS,cAAA,GAAiB,MAAA;AAAA,GAC9H,GACA,OAAA;AACN,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,SAAA,CAAU,+BAAA,CAAgC,QAAQ,CAAC,CAAA;AACzE,EAAA,MAAM,IAAA,GAAO,6BAAA,CAA8B,QAAA,CAAS,QAAQ,CAAA;AAE5D,EAAA,OAAO,CAAC,OAAA,EAAS,QAAA,EAAU,IAAA,KAAS;AAClC,IAAA,IAAK,OAAA,CAAQ,MAAA,KAAW,KAAA,IAAS,OAAA,CAAQ,WAAW,MAAA,IAAW,OAAA,CAAQ,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA,KAAM,IAAA,SAAa,IAAA,EAAK;AAEtH,IAAA,QAAA,CAAS,GAAA,CAAI,iBAAiB,sBAAsB,CAAA,CAAE,KAAK,kBAAkB,CAAA,CAAE,KAAK,QAAQ,CAAA;AAAA,EAC9F,CAAA;AACF;AAuBO,SAAS,wBAAwB,OAAA,EAAyD;AAC/F,EAAA,OAAO,CAAC,OAAA,EAAS,QAAA,EAAU,IAAA,KAAS;AAClC,IAAA,QAAA,CAAS,GAAA,CAAI,iBAAiB,UAAU,CAAA;AAExC,IAAA,eAAA,CAAgB,OAAO,CAAA,CACpB,IAAA,CAAK,OAAO,WAAA,KAAgB;AAC3B,MAAA,IAAI,CAAC,WAAA,EAAa;AAChB,QAAA,QAAA,CAAS,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,EAAE,KAAA,EAAO,iBAAA,EAAmB,iBAAA,EAAmB,sCAAA,EAAwC,CAAA;AACjH,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,MAAA;AAEJ,MAAA,IAAI;AACF,QAAA,MAAA,GAAS,MAAM,OAAA,CAAQ,MAAA,CAAO,mBAAA,CAAoB,WAAW,CAAA;AAAA,MAC/D,SAAS,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,UAAU,KAAK,CAAA;AACvB,QAAA,IAAI,EAAE,KAAA,YAAiB,0BAAA,CAAA,EAA6B,MAAM,KAAA;AAC1D,QAAA,QAAA,CAAS,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,EAAE,OAAO,iBAAA,EAAmB,iBAAA,EAAmB,KAAA,CAAM,OAAA,EAAS,CAAA;AACxF,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,CAAQ,QAAA,CAAS,MAAA,EAAQ,OAAO,CAAA;AACtC,MAAA,QAAA,CAAS,MAAA,CAAO,GAAG,CAAA,CAAE,GAAA,EAAI;AAAA,IAC3B,CAAC,CAAA,CACA,KAAA,CAAM,IAAI,CAAA;AAAA,EACf,CAAA;AACF;AAuBO,SAAS,yBAAyB,OAAA,EAA0D;AACjG,EAAA,OAAO,CAAC,OAAA,EAAS,QAAA,EAAU,IAAA,KAAS;AAClC,IAAA,QAAA,CAAS,GAAA,CAAI,iBAAiB,UAAU,CAAA;AAExC,IAAA,OAAA,CAAQ,OAAA,EAAQ,CACb,IAAA,CAAK,YAAY;AAChB,MAAA,IAAI,MAAA;AAEJ,MAAA,IAAI;AACF,QAAA,MAAA,GAAS,OAAA,CAAQ,MAAA,CAAO,0BAAA,CAA2B,IAAI,GAAA,CAAI,OAAA,CAAQ,WAAA,IAAe,OAAA,CAAQ,GAAA,EAAK,kBAAkB,CAAA,CAAE,YAAY,CAAA;AAAA,MACjI,SAAS,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,UAAU,KAAK,CAAA;AACvB,QAAA,IAAI,EAAE,KAAA,YAAiB,mBAAA,CAAA,EAAsB,MAAM,KAAA;AACnD,QAAA,QAAA,CAAS,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,EAAE,OAAO,iBAAA,EAAmB,iBAAA,EAAmB,KAAA,CAAM,OAAA,EAAS,CAAA;AACxF,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,CAAQ,QAAA,CAAS,MAAA,EAAQ,OAAO,CAAA;AACtC,MAAA,QAAA,CAAS,OAAO,GAAG,CAAA,CAAE,KAAK,0BAA0B,CAAA,CAAE,KAAK,mGAAmG,CAAA;AAAA,IAChK,CAAC,CAAA,CACA,KAAA,CAAM,IAAI,CAAA;AAAA,EACf,CAAA;AACF;AAKO,SAAS,eAAA,CAAgB,QAAA,EAAoB,KAAA,EAAiD,OAAA,GAAiC,EAAC,EAAS;AAC9I,EAAA,MAAM,YAAY,KAAA,CAAM,eAAA;AAExB,EAAA,IAAI,SAAA,EAAW,QAAA,CAAS,MAAA,CAAO,kBAAA,EAAoB,SAAS,CAAA;AAE5D,EAAA,QAAA,CAAS,MAAA,CAAO,MAAM,MAAM,CAAA;AAE5B,EAAA,IAAI,OAAA,CAAQ,cAAA,EAAgB,QAAA,CAAS,IAAA,CAAK,0BAA0B,CAAA,CAAE,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,gBAAA,CAAiB,KAAK,CAAC,CAAC,CAAA;AAAA,gBACpG,GAAA,EAAI;AACpB;AAEA,SAAS,MAAA,CAAO,OAAA,EAAkB,QAAA,EAAoB,IAAA,EAAoB,OAAgB,OAAA,EAAuC;AAC/H,EAAA,MAAM,QAAA,GAAW,OAAA,IAAY,OAAA,CAAgC,gBAAgB,KAAK,EAAC;AAEnF,EAAA,IAAI,EAAE,iBAAiB,iBAAA,CAAA,IAAsB,EAAE,iBAAiB,oBAAA,CAAA,EAAuB,OAAO,KAAK,KAAK,CAAA;AACxG,EAAA,IAAI,QAAA,CAAS,UAAA,EAAY,OAAO,IAAA,CAAK,KAAK,CAAA;AAE1C,EAAA,eAAA,CAAgB,QAAA,EAAU,OAAO,QAAQ,CAAA;AAC3C;AAEA,SAAS,WAAA,CAAY,SAAkB,WAAA,EAAsD;AAC3F,EAAA,MAAM,SAAU,OAAA,CAAyC,IAAA;AAEzD,EAAA,IAAI,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,SAAU,OAAA,CAAQ,OAAA,CAAQ,MAAA,CAAO,UAAA,GAAa,WAAA,GAAc,MAAA,GAAY,IAAI,UAAA,CAAW,MAAM,CAAC,CAAA;AACxH,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,EAAU,OAAO,OAAA,CAAQ,OAAA,CAAQ,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,MAAM,CAAC,CAAA;AAEvF,EAAA,IAAI,OAAA,CAAQ,aAAA,IAAkB,MAAA,KAAW,MAAA,IAAa,MAAA,KAAW,IAAA,IAAQ,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,CAAO,IAAA,CAAK,MAAM,EAAE,MAAA,GAAS,CAAA;AAClI,IAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,MACb,IAAI,MAAM,yHAAyH;AAAA,KACrI;AAEF,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,OAAA,CAAQ,OAAA,CAAQ,gBAAgB,CAAC,CAAA;AAEzD,EAAA,IAAI,MAAA,CAAO,SAAS,QAAQ,CAAA,IAAK,WAAW,WAAA,EAAa,OAAO,OAAA,CAAQ,OAAA,CAAQ,MAAS,CAAA;AAEzF,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,SAAmB,EAAC;AAC1B,IAAA,IAAI,IAAA,GAAO,CAAA;AACX,IAAA,IAAI,IAAA,GAAO,KAAA;AAEX,IAAA,OAAA,CAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,KAAA,KAAkB;AACpC,MAAA,IAAI,IAAA,EAAM;AAEV,MAAA,IAAA,IAAQ,KAAA,CAAM,UAAA;AAEd,MAAA,IAAI,OAAO,WAAA,EAAa;AACtB,QAAA,IAAA,GAAO,IAAA;AACP,QAAA,OAAA,CAAQ,KAAA,EAAM;AACd,QAAA,OAAA,CAAQ,MAAS,CAAA;AACjB,QAAA;AAAA,MACF;AAEA,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAA,IACnB,CAAC,CAAA;AACD,IAAA,OAAA,CAAQ,EAAA,CAAG,OAAO,MAAM;AACtB,MAAA,IAAI,CAAC,MAAM,OAAA,CAAQ,IAAI,WAAW,MAAA,CAAO,MAAA,CAAO,MAAM,CAAC,CAAC,CAAA;AAAA,IAC1D,CAAC,CAAA;AACD,IAAA,OAAA,CAAQ,EAAA,CAAG,SAAS,MAAM,CAAA;AAAA,EAC5B,CAAC,CAAA;AACH;AAEA,eAAe,gBAAgB,OAAA,EAA+C;AAC5E,EAAA,MAAM,SAAU,OAAA,CAAyC,IAAA;AAEzD,EAAA,IAAI,MAAA,IAAU,OAAO,MAAA,KAAW,QAAA,IAAY,CAAC,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,IAAK,OAAQ,MAAA,CAAmC,YAAA,KAAiB,QAAA;AAClI,IAAA,OAAQ,MAAA,CAAkC,YAAA;AAE5C,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,GAC9B,IAAI,UAAA,CAAW,MAAM,CAAA,GACrB,OAAO,MAAA,KAAW,QAAA,GAChB,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,MAAM,CAAA,GAC/B,OAAA,CAAQ,aAAA,GACN,MAAA,GACA,MAAM,WAAA,CAAY,OAAA,EAAS,EAAA,GAAK,IAAI,CAAA;AAE5C,EAAA,OAAO,GAAA,GAAO,IAAI,eAAA,CAAgB,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,GAAG,CAAC,CAAA,CAAE,GAAA,CAAI,cAAc,KAAK,MAAA,GAAa,MAAA;AACvG;AAEA,SAAS,gBAAgB,QAAA,EAAkC;AACzD,EAAA,IAAI,CAAC,QAAA,CAAS,QAAA,EAAU,MAAM,IAAI,UAAU,4EAA4E,CAAA;AAExH,EAAA,OAAO,QAAA,CAAS,QAAA;AAClB;AAEA,SAAS,WAAW,KAAA,EAAwE;AAC1F,EAAA,OAAO,OAAQ,MAAyB,MAAA,KAAW,UAAA;AACrD","file":"index.js","sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from \"express\";\nimport {\n AuthioCallbackError,\n AuthioTokenValidationError,\n type AuthioCallerKind,\n type AuthioClient,\n type AuthioFrontChannelLogout,\n type AuthioLogoutToken,\n type AuthioResourceAccessSource,\n type AuthioTypedWebhookEvent,\n type AuthioWebhookDataByType,\n} from \"@hyper.authio/client\";\nimport {\n allOf as allOfRule,\n anyOf as anyOfRule,\n authorize,\n AuthioBearerError,\n AuthioForbiddenError,\n createAuthioVerifier,\n createAuthioWebhooks,\n createProtectedResourceMetadata,\n protectedResourceMetadataPath,\n requireAuthenticationLevel as authenticationLevelRule,\n requireAuthenticationMethod as authenticationMethodRule,\n requireCallerKind as callerKindRule,\n requireClaim as claimRule,\n requireClient as clientRule,\n requireGroup as groupRule,\n requireOrganization as organizationRule,\n requirePermission as permissionRule,\n requireRecentAuthentication as recentAuthenticationRule,\n requireRole as roleRule,\n requireScope as scopeRule,\n toProblemDetails,\n type AuthioProtectedResourceMetadataOptions,\n type AuthioMatch,\n type AuthioRule,\n type AuthioRuleFailure,\n type AuthioUser,\n type AuthioVerifier,\n type AuthioVerifierOptions,\n type AuthioWebhookHandler,\n type AuthioWebhookOptions,\n} from \"@hyper.authio/server\";\n\n/**\n * What `authioBearer` puts on the request.\n */\nexport interface AuthioRequestContext {\n /** The caller. */\n readonly user: AuthioUser;\n /** The access token the caller was read from. */\n readonly token: string;\n}\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** The Authio caller, once `authioBearer` validated the token. */\n authio?: AuthioRequestContext;\n }\n }\n}\n\n/**\n * How refusals are answered.\n */\nexport interface AuthioResponseOptions {\n /** Answers `401` and `403` with problem details (RFC 9457) as well as the `WWW-Authenticate` header. */\n problemDetails?: boolean;\n /** Passes refusals to `next(error)` instead of answering, for an application's own error handler. */\n passErrors?: boolean;\n}\n\n/**\n * Options of `authioBearer`.\n */\nexport interface AuthioBearerOptions extends AuthioResponseOptions {\n /** Lets a request without a token through, unauthenticated; a token that is present must still be valid. */\n optional?: boolean;\n}\n\nconst responseSettings = Symbol.for(\"authio.express.response\");\n\ntype RequestWithSettings = Request & { [responseSettings]?: AuthioResponseOptions };\n\n/**\n * Validates the `Authorization: Bearer` token and puts the caller on `req.authio`. Answers `401` with the\n * `WWW-Authenticate` challenge when the token is missing or invalid.\n *\n * @param verifier A verifier, or its options.\n */\nexport function authioBearer(verifier: AuthioVerifier | AuthioVerifierOptions, options: AuthioBearerOptions = {}): RequestHandler {\n const resolved = isVerifier(verifier) ? verifier : createAuthioVerifier(verifier);\n\n return (request, response, next) => {\n (request as RequestWithSettings)[responseSettings] = { problemDetails: options.problemDetails, passErrors: options.passErrors };\n\n const header = request.headers.authorization;\n\n if (options.optional && !header) {\n next();\n return;\n }\n\n resolved\n .verifyAuthorizationHeader(header)\n .then((user) => {\n request.authio = { user, token: user.token! };\n\n const failure = resolved.requiredScopes.length > 0 ? scopeRule(resolved.requiredScopes, \"all\").check(user) : undefined;\n\n if (failure) return refuse(request, response, next, new AuthioForbiddenError(failure as AuthioRuleFailure), options);\n\n next();\n })\n .catch((error: unknown) => refuse(request, response, next, error, options));\n };\n}\n\n/**\n * An Express guard that is also an Authio rule: use it as middleware, or pass it to `requireAuthio`, `allOf` and\n * `anyOf` like any rule of `@hyper.authio/server`.\n */\nexport type AuthioGuard = RequestHandler & AuthioRule;\n\n/**\n * Requires every rule (guards of this package or rules of `@hyper.authio/server`); answers `401` without a caller and\n * `403` with the joined refusal otherwise.\n */\nexport function requireAuthio(...rules: AuthioRule[]): AuthioGuard {\n if (rules.length === 0) throw new TypeError(\"requireAuthio needs at least one rule.\");\n\n const rule = rules.length === 1 ? rules[0]! : allOfRule(...rules);\n const guard: RequestHandler = (request, response, next) => {\n const user = request.authio?.user;\n\n if (!user) return refuse(request, response, next, new AuthioBearerError(undefined, { error: undefined }));\n\n const failure = authorize(user, ...rules);\n\n if (failure) return refuse(request, response, next, new AuthioForbiddenError(failure));\n\n next();\n };\n\n return Object.assign(guard, { description: rule.description, check: (user: AuthioUser) => authorize(user, ...rules) });\n}\n\n/** Requires granted scopes. */\nexport const requireScope = (scopes: string | readonly string[], match?: AuthioMatch): AuthioGuard => requireAuthio(scopeRule(scopes, match));\n/** Requires roles, from any source or only from the given `*_resource_access` claims. */\nexport const requireRole = (\n roles: string | readonly string[],\n options?: { match?: AuthioMatch; sources?: readonly AuthioResourceAccessSource[] },\n): AuthioGuard => requireAuthio(roleRule(roles, options));\n/** Requires a permission (`method` on `resource`). */\nexport const requirePermission = (resource: string, method: string, options?: { sources?: readonly AuthioResourceAccessSource[] }): AuthioGuard =>\n requireAuthio(permissionRule(resource, method, options));\n/** Requires groups. */\nexport const requireGroup = (groups: string | readonly string[], match?: AuthioMatch): AuthioGuard => requireAuthio(groupRule(groups, match));\n/** Requires a claim, or one of its values. */\nexport const requireClaim = (type: string, ...values: string[]): AuthioGuard => requireAuthio(claimRule(type, ...values));\n/** Requires an organization context, or a given organization. */\nexport const requireOrganization = (organization?: string): AuthioGuard => requireAuthio(organizationRule(organization));\n/** Requires a minimum authentication level (`acr`), and optionally a sign-in at most `maxAge` seconds old. */\nexport const requireAuthenticationLevel = (minimumLevel: number, options?: Parameters<typeof authenticationLevelRule>[1]): AuthioGuard =>\n requireAuthio(authenticationLevelRule(minimumLevel, options));\n/** Requires authentication methods (`amr`). */\nexport const requireAuthenticationMethod = (methods: string | readonly string[], match?: AuthioMatch, options?: { acrValues?: string }): AuthioGuard =>\n requireAuthio(authenticationMethodRule(methods, match, options));\n/** Requires a sign-in at most `maxAge` seconds old (`auth_time`). */\nexport const requireRecentAuthentication = (maxAge: number, options?: Parameters<typeof recentAuthenticationRule>[1]): AuthioGuard =>\n requireAuthio(recentAuthenticationRule(maxAge, options));\n/** Requires the caller to be of one of the kinds: `user`, `client`, `consumer`, `account_credential`. */\nexport const requireCallerKind = (kinds: AuthioCallerKind | readonly AuthioCallerKind[]): AuthioGuard => requireAuthio(callerKindRule(kinds));\n/** Requires the token to have been issued to one of the clients. */\nexport const requireClient = (clientIds: string | readonly string[]): AuthioGuard => requireAuthio(clientRule(clientIds));\n/** Requires every rule. */\nexport const allOf = (...rules: AuthioRule[]): AuthioGuard => requireAuthio(allOfRule(...rules));\n/** Requires at least one rule. */\nexport const anyOf = (...rules: AuthioRule[]): AuthioGuard => requireAuthio(anyOfRule(...rules));\n\n/**\n * A webhook route: verifies the signature over the raw body, deduplicates and dispatches. Mount it before any JSON\n * body parser, or behind `express.raw()`.\n */\nexport interface AuthioWebhookRoute extends RequestHandler {\n on<T extends keyof AuthioWebhookDataByType>(type: T, handler: AuthioWebhookHandler<AuthioTypedWebhookEvent<T>>): AuthioWebhookRoute;\n on(type: string, handler: AuthioWebhookHandler): AuthioWebhookRoute;\n}\n\n/**\n * Creates a webhook route: `app.post(\"/webhooks/authio\", authioWebhooks({ secret }).on(\"user_created\", …))`.\n * Answers `200` handled or duplicate, `400` unreadable, `401` bad signature, `413` too large, `500` handler failure.\n */\nexport function authioWebhooks(options: AuthioWebhookOptions): AuthioWebhookRoute {\n const webhooks = createAuthioWebhooks(options);\n\n const route = ((request: Request, response: Response, next: NextFunction) => {\n readRawBody(request, webhooks.maxBodySize)\n .then(async (body) => {\n if (body === undefined) {\n response.status(413).end();\n return;\n }\n\n const outcome = await webhooks.handle({ body, headers: request.headers });\n\n response.status(outcome.status).end();\n })\n .catch(next);\n }) as AuthioWebhookRoute;\n\n route.on = ((type: string, handler: AuthioWebhookHandler) => {\n webhooks.on(type, handler);\n return route;\n }) as AuthioWebhookRoute[\"on\"];\n\n return route;\n}\n\n/**\n * Options of `authioProtectedResourceMetadata`: the metadata itself, or a verifier configured with `resource`.\n */\nexport type AuthioProtectedResourceMetadataRouteOptions =\n | AuthioProtectedResourceMetadataOptions\n | (Omit<AuthioProtectedResourceMetadataOptions, \"resource\" | \"issuer\" | \"authority\" | \"realm\"> & { verifier: AuthioVerifier });\n\n/**\n * Serves the API's protected resource metadata (RFC 9728) at `/.well-known/oauth-protected-resource{path of the\n * resource}`: `app.use(authioProtectedResourceMetadata({ verifier }))`. Every other request goes on to `next`.\n */\nexport function authioProtectedResourceMetadata(options: AuthioProtectedResourceMetadataRouteOptions): RequestHandler {\n const settings: AuthioProtectedResourceMetadataOptions =\n \"verifier\" in options\n ? {\n ...options,\n resource: requireResource(options.verifier),\n issuer: options.verifier.connection.issuer,\n scopesSupported: options.scopesSupported ?? (options.verifier.requiredScopes.length > 0 ? options.verifier.requiredScopes : undefined),\n }\n : options;\n const document = JSON.stringify(createProtectedResourceMetadata(settings));\n const path = protectedResourceMetadataPath(settings.resource);\n\n return (request, response, next) => {\n if ((request.method !== \"GET\" && request.method !== \"HEAD\") || request.path.replace(/\\/+$/, \"\") !== path) return next();\n\n response.set(\"Cache-Control\", \"public, max-age=3600\").type(\"application/json\").send(document);\n };\n}\n\n/**\n * Options of `authioBackchannelLogout`.\n */\nexport interface AuthioBackchannelLogoutOptions {\n /** The client the logout tokens are addressed to (its id is their audience). */\n client: AuthioClient;\n /**\n * Ends the sessions the token names: every session of `subject`, or the one `sessionId` names. Session storage is\n * the application's.\n */\n onLogout: (logout: AuthioLogoutToken, request: Request) => void | Promise<void>;\n /** Called when a token is refused. */\n onError?: (error: unknown) => void;\n}\n\n/**\n * The back-channel logout endpoint (OpenID Connect Back-Channel Logout 1.0): reads `logout_token` from the\n * form-encoded body, validates it with the client and calls `onLogout`. Answers `200` on success, `400`\n * (`invalid_request`) for a token that does not validate, and `500` when `onLogout` throws; always `Cache-Control:\n * no-store`. Works with or without `express.urlencoded()` in front of it.\n */\nexport function authioBackchannelLogout(options: AuthioBackchannelLogoutOptions): RequestHandler {\n return (request, response, next) => {\n response.set(\"Cache-Control\", \"no-store\");\n\n readLogoutToken(request)\n .then(async (logoutToken) => {\n if (!logoutToken) {\n response.status(400).json({ error: \"invalid_request\", error_description: \"The request carries no logout_token.\" });\n return;\n }\n\n let logout: AuthioLogoutToken;\n\n try {\n logout = await options.client.validateLogoutToken(logoutToken);\n } catch (error) {\n options.onError?.(error);\n if (!(error instanceof AuthioTokenValidationError)) throw error;\n response.status(400).json({ error: \"invalid_request\", error_description: error.message });\n return;\n }\n\n await options.onLogout(logout, request);\n response.status(200).end();\n })\n .catch(next);\n };\n}\n\n/**\n * Options of `authioFrontchannelLogout`.\n */\nexport interface AuthioFrontchannelLogoutOptions {\n /** The client Authio ends the sessions of (its issuer is checked against `iss`). */\n client: AuthioClient;\n /**\n * Ends the sessions the request names: the Authio session `sessionId`, or the browser's own session (read from the\n * request's cookies) when Authio sent none. Session storage is the application's.\n */\n onLogout: (logout: AuthioFrontChannelLogout, request: Request) => void | Promise<void>;\n /** Called when a request is refused. */\n onError?: (error: unknown) => void;\n}\n\n/**\n * The front-channel logout endpoint (OpenID Connect Front-Channel Logout 1.0), the page Authio loads in an iframe when\n * a session ends: checks `iss` and `sid` with the client and calls `onLogout`. Answers `200` with an empty page, `400`\n * (`invalid_request`) for another issuer or a `sid` without `iss`, and `500` when `onLogout` throws; always\n * `Cache-Control: no-store`. Register its URL as the client's front-channel logout URI, with the session required.\n */\nexport function authioFrontchannelLogout(options: AuthioFrontchannelLogoutOptions): RequestHandler {\n return (request, response, next) => {\n response.set(\"Cache-Control\", \"no-store\");\n\n Promise.resolve()\n .then(async () => {\n let logout: AuthioFrontChannelLogout;\n\n try {\n logout = options.client.validateFrontChannelLogout(new URL(request.originalUrl ?? request.url, \"http://localhost\").searchParams);\n } catch (error) {\n options.onError?.(error);\n if (!(error instanceof AuthioCallbackError)) throw error;\n response.status(400).json({ error: \"invalid_request\", error_description: error.message });\n return;\n }\n\n await options.onLogout(logout, request);\n response.status(200).type(\"text/html; charset=utf-8\").send('<!doctype html><html><head><meta charset=\"utf-8\"><title>Authio</title></head><body></body></html>');\n })\n .catch(next);\n };\n}\n\n/**\n * Answers a refusal: `WWW-Authenticate`, the status, and problem details when asked.\n */\nexport function sendAuthioError(response: Response, error: AuthioBearerError | AuthioForbiddenError, options: AuthioResponseOptions = {}): void {\n const challenge = error.wwwAuthenticate;\n\n if (challenge) response.append(\"WWW-Authenticate\", challenge);\n\n response.status(error.status);\n\n if (options.problemDetails) response.type(\"application/problem+json\").send(JSON.stringify(toProblemDetails(error)));\n else response.end();\n}\n\nfunction refuse(request: Request, response: Response, next: NextFunction, error: unknown, options?: AuthioResponseOptions): void {\n const settings = options ?? (request as RequestWithSettings)[responseSettings] ?? {};\n\n if (!(error instanceof AuthioBearerError) && !(error instanceof AuthioForbiddenError)) return next(error);\n if (settings.passErrors) return next(error);\n\n sendAuthioError(response, error, settings);\n}\n\nfunction readRawBody(request: Request, maxBodySize: number): Promise<Uint8Array | undefined> {\n const parsed = (request as Request & { body?: unknown }).body;\n\n if (Buffer.isBuffer(parsed)) return Promise.resolve(parsed.byteLength > maxBodySize ? undefined : new Uint8Array(parsed));\n if (typeof parsed === \"string\") return Promise.resolve(new TextEncoder().encode(parsed));\n\n if (request.readableEnded || (parsed !== undefined && parsed !== null && typeof parsed === \"object\" && Object.keys(parsed).length > 0))\n return Promise.reject(\n new Error(\"The Authio webhook route needs the raw body: mount it before express.json(), or use express.raw({ type: '*/*' }) on it.\"),\n );\n\n const declared = Number(request.headers[\"content-length\"]);\n\n if (Number.isFinite(declared) && declared > maxBodySize) return Promise.resolve(undefined);\n\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n let done = false;\n\n request.on(\"data\", (chunk: Buffer) => {\n if (done) return;\n\n size += chunk.byteLength;\n\n if (size > maxBodySize) {\n done = true;\n request.pause();\n resolve(undefined);\n return;\n }\n\n chunks.push(chunk);\n });\n request.on(\"end\", () => {\n if (!done) resolve(new Uint8Array(Buffer.concat(chunks)));\n });\n request.on(\"error\", reject);\n });\n}\n\nasync function readLogoutToken(request: Request): Promise<string | undefined> {\n const parsed = (request as Request & { body?: unknown }).body;\n\n if (parsed && typeof parsed === \"object\" && !Buffer.isBuffer(parsed) && typeof (parsed as Record<string, unknown>).logout_token === \"string\")\n return (parsed as Record<string, string>).logout_token;\n\n const raw = Buffer.isBuffer(parsed)\n ? new Uint8Array(parsed)\n : typeof parsed === \"string\"\n ? new TextEncoder().encode(parsed)\n : request.readableEnded\n ? undefined\n : await readRawBody(request, 64 * 1024);\n\n return raw ? (new URLSearchParams(new TextDecoder().decode(raw)).get(\"logout_token\") ?? undefined) : undefined;\n}\n\nfunction requireResource(verifier: AuthioVerifier): string {\n if (!verifier.resource) throw new TypeError(\"authioProtectedResourceMetadata needs a verifier configured with resource.\");\n\n return verifier.resource;\n}\n\nfunction isVerifier(value: AuthioVerifier | AuthioVerifierOptions): value is AuthioVerifier {\n return typeof (value as AuthioVerifier).verify === \"function\";\n}\n\nexport type { AuthioMatch, AuthioRule, AuthioUser, AuthioVerifier, AuthioVerifierOptions } from \"@hyper.authio/server\";\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyper.authio/express",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Express middleware for Authio: bearer validation, scope/role/permission/group/claim/organization/acr/amr/client guards, and signed webhook routes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"build": "tsup"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@hyper.authio/client": "0.
|
|
36
|
-
"@hyper.authio/server": "0.
|
|
35
|
+
"@hyper.authio/client": "0.8.0",
|
|
36
|
+
"@hyper.authio/server": "0.8.0"
|
|
37
37
|
},
|
|
38
38
|
"keywords": [
|
|
39
39
|
"authio",
|