@hyper.authio/server 0.0.0-stage → 0.1.1
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 +114 -2
- package/dist/index.cjs +739 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +439 -0
- package/dist/index.d.ts +439 -0
- package/dist/index.js +714 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -4
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var client = require('@hyper.authio/client');
|
|
4
|
+
var jose = require('jose');
|
|
5
|
+
|
|
6
|
+
// src/user.ts
|
|
7
|
+
var AuthioUser = class {
|
|
8
|
+
/** `sub`: the person's id, or the credential's for a machine caller. */
|
|
9
|
+
subject;
|
|
10
|
+
/** The client the token was issued to (`azp`, else `client_id`). */
|
|
11
|
+
clientId;
|
|
12
|
+
/** `sid`. */
|
|
13
|
+
sessionId;
|
|
14
|
+
/** `jti`. */
|
|
15
|
+
tokenId;
|
|
16
|
+
/** `aud`. */
|
|
17
|
+
audiences;
|
|
18
|
+
/** The granted scopes. */
|
|
19
|
+
scopes;
|
|
20
|
+
/** The roles of the seven `*_resource_access` claims, with their source and permissions. */
|
|
21
|
+
resourceAccess;
|
|
22
|
+
/** Every role name: the `*_resource_access` roles, then the flat role claims. */
|
|
23
|
+
roles;
|
|
24
|
+
/** Resource → every method any role allows on it. */
|
|
25
|
+
permissions;
|
|
26
|
+
/** The group names of the group claims. */
|
|
27
|
+
groups;
|
|
28
|
+
/** `org_id`. */
|
|
29
|
+
organizationId;
|
|
30
|
+
/** `org_alias`. */
|
|
31
|
+
organizationAlias;
|
|
32
|
+
/** `account_id`. */
|
|
33
|
+
accountId;
|
|
34
|
+
/** `acr`. */
|
|
35
|
+
acr;
|
|
36
|
+
/** `acr` as a number: 1 one factor, 2 several, 3 one of them phishing-resistant. */
|
|
37
|
+
authenticationLevel;
|
|
38
|
+
/** `amr`. */
|
|
39
|
+
authenticationMethods;
|
|
40
|
+
/** `idp`. */
|
|
41
|
+
identityProvider;
|
|
42
|
+
/** `auth_time`. */
|
|
43
|
+
authenticatedAt;
|
|
44
|
+
/** `act`. */
|
|
45
|
+
actor;
|
|
46
|
+
/** `delegation_id`. */
|
|
47
|
+
delegationId;
|
|
48
|
+
email;
|
|
49
|
+
emailVerified;
|
|
50
|
+
name;
|
|
51
|
+
preferredUsername;
|
|
52
|
+
/** `credential_type`, on a token issued to an account credential or a consumer. */
|
|
53
|
+
credentialType;
|
|
54
|
+
/** `credential_id`. */
|
|
55
|
+
credentialId;
|
|
56
|
+
/** Every claim, as the token or the introspection answer carried it. */
|
|
57
|
+
claims;
|
|
58
|
+
flatRoles;
|
|
59
|
+
/**
|
|
60
|
+
* @param claims The token's payload, or the introspection answer's members.
|
|
61
|
+
* @param settings Where flat roles and groups are read from.
|
|
62
|
+
* @param token The access token, kept for forwarding and on-behalf-of.
|
|
63
|
+
*/
|
|
64
|
+
constructor(claims, settings = {}, token) {
|
|
65
|
+
const text = (name) => typeof claims[name] === "string" && claims[name] ? claims[name] : void 0;
|
|
66
|
+
this.claims = Object.freeze({ ...claims });
|
|
67
|
+
this.subject = text(client.AuthioClaimTypes.Subject) ?? "";
|
|
68
|
+
this.clientId = text(client.AuthioClaimTypes.AuthorizedParty) ?? text(client.AuthioClaimTypes.ClientId);
|
|
69
|
+
this.sessionId = text(client.AuthioClaimTypes.SessionId);
|
|
70
|
+
this.tokenId = text(client.AuthioClaimTypes.JwtId);
|
|
71
|
+
this.audiences = Object.freeze(distinct(valuesOf(claims[client.AuthioClaimTypes.Audience])));
|
|
72
|
+
this.scopes = Object.freeze(distinct(valuesOf(claims[client.AuthioClaimTypes.Scope]).flatMap((scope) => scope.split(/\s+/)).filter(Boolean)));
|
|
73
|
+
this.resourceAccess = client.parseResourceAccess(claims);
|
|
74
|
+
this.flatRoles = Object.freeze((settings.roleClaims ?? ["role"]).flatMap((name) => valuesOf(claims[name])));
|
|
75
|
+
this.roles = Object.freeze(distinctIgnoringCase([...this.resourceAccess.roles, ...this.flatRoles]));
|
|
76
|
+
this.permissions = this.resourceAccess.permissions;
|
|
77
|
+
this.groups = Object.freeze(distinct((settings.groupClaims ?? ["groups"]).flatMap((name) => valuesOf(claims[name]))));
|
|
78
|
+
this.organizationId = text(client.AuthioClaimTypes.OrganizationId);
|
|
79
|
+
this.organizationAlias = text(client.AuthioClaimTypes.OrganizationAlias);
|
|
80
|
+
this.accountId = text(client.AuthioClaimTypes.AccountId);
|
|
81
|
+
this.acr = valuesOf(claims[client.AuthioClaimTypes.AuthenticationContextClass])[0];
|
|
82
|
+
this.authenticationLevel = this.acr !== void 0 && /^\d+$/.test(this.acr) ? Number(this.acr) : void 0;
|
|
83
|
+
this.authenticationMethods = Object.freeze(distinctIgnoringCase(valuesOf(claims[client.AuthioClaimTypes.AuthenticationMethods])));
|
|
84
|
+
this.identityProvider = text(client.AuthioClaimTypes.IdentityProvider);
|
|
85
|
+
this.authenticatedAt = toDate(claims[client.AuthioClaimTypes.AuthTime]);
|
|
86
|
+
this.actor = readActor(claims[client.AuthioClaimTypes.Actor]);
|
|
87
|
+
this.delegationId = text(client.AuthioClaimTypes.DelegationId);
|
|
88
|
+
this.email = text(client.AuthioClaimTypes.Email);
|
|
89
|
+
this.emailVerified = toBoolean(claims[client.AuthioClaimTypes.EmailVerified]);
|
|
90
|
+
this.name = text(client.AuthioClaimTypes.Name);
|
|
91
|
+
this.preferredUsername = text(client.AuthioClaimTypes.PreferredUsername);
|
|
92
|
+
this.credentialType = text(client.AuthioClaimTypes.CredentialType);
|
|
93
|
+
this.credentialId = text(client.AuthioClaimTypes.CredentialId);
|
|
94
|
+
Object.defineProperty(this, "token", { value: token, enumerable: false });
|
|
95
|
+
}
|
|
96
|
+
/** Whether an actor exchanged the token on the subject's behalf. */
|
|
97
|
+
get isDelegated() {
|
|
98
|
+
return this.actor !== void 0;
|
|
99
|
+
}
|
|
100
|
+
/** Whether the scope was granted. */
|
|
101
|
+
hasScope(scope) {
|
|
102
|
+
return this.scopes.includes(scope);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Whether the user holds the role. Without sources, from any `*_resource_access` claim or a flat role claim; with
|
|
106
|
+
* sources, only from those `*_resource_access` claims.
|
|
107
|
+
*/
|
|
108
|
+
hasRole(role, options = {}) {
|
|
109
|
+
if (!role) return false;
|
|
110
|
+
if (options.sources && options.sources.length > 0) return this.resourceAccess.hasRole(role, options.sources);
|
|
111
|
+
const wanted = role.toLowerCase();
|
|
112
|
+
return this.resourceAccess.hasRole(role) || this.flatRoles.some((name) => name.toLowerCase() === wanted);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Authio's grant rule: whether a role (from any source, or only from the given ones) allows `method` on
|
|
116
|
+
* `resource`. Case-insensitive.
|
|
117
|
+
*/
|
|
118
|
+
hasPermission(resource, method, options = {}) {
|
|
119
|
+
return this.resourceAccess.grants(resource, method, options.sources);
|
|
120
|
+
}
|
|
121
|
+
/** Whether the user is in the group (exact match). */
|
|
122
|
+
hasGroup(group) {
|
|
123
|
+
return this.groups.includes(group);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Whether the claim is present (not `null`), and, given values, whether it holds one of them (exact match; an
|
|
127
|
+
* array claim matches when any item does).
|
|
128
|
+
*/
|
|
129
|
+
hasClaim(type, ...values) {
|
|
130
|
+
const present = this.claimValues(type);
|
|
131
|
+
return values.length === 0 ? present.length > 0 : present.some((value) => values.includes(value));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The claim's values as text: one for a scalar, one per item for an array, JSON text for an object.
|
|
135
|
+
*/
|
|
136
|
+
claimValues(type) {
|
|
137
|
+
return valuesOf(this.claims[type]);
|
|
138
|
+
}
|
|
139
|
+
/** Every member but the raw token. */
|
|
140
|
+
toJSON() {
|
|
141
|
+
const { flatRoles: _flatRoles, ...members } = this;
|
|
142
|
+
return { ...members, resourceAccess: this.resourceAccess.entries, isDelegated: this.isDelegated };
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
function valuesOf(value) {
|
|
146
|
+
if (value === void 0 || value === null) return [];
|
|
147
|
+
if (Array.isArray(value)) return value.flatMap((item) => item === null || item === void 0 ? [] : [toText(item)]);
|
|
148
|
+
if (typeof value === "string" && value.startsWith("[")) {
|
|
149
|
+
try {
|
|
150
|
+
const parsed = JSON.parse(value);
|
|
151
|
+
if (Array.isArray(parsed)) return valuesOf(parsed);
|
|
152
|
+
} catch {
|
|
153
|
+
return [value];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return [toText(value)];
|
|
157
|
+
}
|
|
158
|
+
function toText(value) {
|
|
159
|
+
return typeof value === "string" ? value : typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
160
|
+
}
|
|
161
|
+
function readActor(value, depth = 0) {
|
|
162
|
+
let actor = value;
|
|
163
|
+
if (typeof actor === "string") {
|
|
164
|
+
try {
|
|
165
|
+
actor = JSON.parse(actor);
|
|
166
|
+
} catch {
|
|
167
|
+
return void 0;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!actor || typeof actor !== "object" || Array.isArray(actor) || depth > 16) return void 0;
|
|
171
|
+
const record = actor;
|
|
172
|
+
return Object.freeze({
|
|
173
|
+
clientId: typeof record.client_id === "string" ? record.client_id : void 0,
|
|
174
|
+
subject: typeof record.sub === "string" ? record.sub : void 0,
|
|
175
|
+
actor: readActor(record.act, depth + 1)
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function toDate(value) {
|
|
179
|
+
const seconds3 = typeof value === "number" ? value : typeof value === "string" && /^\d+$/.test(value) ? Number(value) : void 0;
|
|
180
|
+
return seconds3 === void 0 ? void 0 : new Date(seconds3 * 1e3);
|
|
181
|
+
}
|
|
182
|
+
function toBoolean(value) {
|
|
183
|
+
if (typeof value === "boolean") return value;
|
|
184
|
+
if (value === "true") return true;
|
|
185
|
+
if (value === "false") return false;
|
|
186
|
+
return void 0;
|
|
187
|
+
}
|
|
188
|
+
function distinct(values) {
|
|
189
|
+
return [...new Set(values)];
|
|
190
|
+
}
|
|
191
|
+
function distinctIgnoringCase(values) {
|
|
192
|
+
const seen = /* @__PURE__ */ new Set();
|
|
193
|
+
return values.filter((value) => {
|
|
194
|
+
const key = value.toLowerCase();
|
|
195
|
+
if (seen.has(key)) return false;
|
|
196
|
+
seen.add(key);
|
|
197
|
+
return true;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/rules.ts
|
|
202
|
+
function authorize(user, ...rules) {
|
|
203
|
+
const failures = rules.map((rule) => rule.check(user)).filter((failure) => failure !== void 0);
|
|
204
|
+
if (failures.length === 0) return void 0;
|
|
205
|
+
const bearer = failures.find((failure) => failure.error === "insufficient_user_authentication") ?? failures.find((failure) => failure.error);
|
|
206
|
+
return {
|
|
207
|
+
message: failures.map((failure) => failure.message).join(" "),
|
|
208
|
+
...bearer?.error ? { error: bearer.error } : {},
|
|
209
|
+
...bearer?.scope ? { scope: bearer.scope } : {}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function requireScope(scopes, match = "any") {
|
|
213
|
+
const required = listOf(scopes, "scope");
|
|
214
|
+
return {
|
|
215
|
+
description: `Authio scope (${match}): ${required.join(" ")}`,
|
|
216
|
+
check: (user) => satisfies(required, match, (scope) => user.hasScope(scope)) ? void 0 : {
|
|
217
|
+
message: `The token lacks the required scope (${match}): ${required.join(" ")}.`,
|
|
218
|
+
error: "insufficient_scope",
|
|
219
|
+
scope: required.join(" ")
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function requireRole(roles, options = {}) {
|
|
224
|
+
const required = listOf(roles, "role");
|
|
225
|
+
const match = options.match ?? "any";
|
|
226
|
+
const from = describeSources(options.sources);
|
|
227
|
+
return {
|
|
228
|
+
description: `Authio role (${match}): ${required.join(", ")}${from}`,
|
|
229
|
+
check: (user) => satisfies(required, match, (role) => user.hasRole(role, { sources: options.sources })) ? void 0 : { message: `The caller lacks the required role (${match}): ${required.join(", ")}${from}.` }
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function requirePermission(resource, method, options = {}) {
|
|
233
|
+
if (!resource?.trim() || !method?.trim()) throw new TypeError("A permission needs a resource and a method.");
|
|
234
|
+
const from = describeSources(options.sources);
|
|
235
|
+
return {
|
|
236
|
+
description: `Authio permission: ${resource}:${method}${from}`,
|
|
237
|
+
check: (user) => user.hasPermission(resource, method, { sources: options.sources }) ? void 0 : { message: `The caller lacks the permission ${resource}:${method}${from}.` }
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function requireGroup(groups, match = "any") {
|
|
241
|
+
const required = listOf(groups, "group");
|
|
242
|
+
return {
|
|
243
|
+
description: `Authio group (${match}): ${required.join(", ")}`,
|
|
244
|
+
check: (user) => satisfies(required, match, (group) => user.hasGroup(group)) ? void 0 : { message: `The caller is not in the required group (${match}): ${required.join(", ")}.` }
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function requireClaim(type, ...values) {
|
|
248
|
+
if (!type?.trim()) throw new TypeError("A claim rule needs a claim type.");
|
|
249
|
+
return {
|
|
250
|
+
description: values.length === 0 ? `Authio claim: ${type}` : `Authio claim: ${type} in ${values.join(", ")}`,
|
|
251
|
+
check: (user) => user.hasClaim(type, ...values) ? void 0 : { message: values.length === 0 ? `The token lacks the claim ${type}.` : `The token's claim ${type} is not one of: ${values.join(", ")}.` }
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function requireOrganization(organization) {
|
|
255
|
+
const wanted = organization?.trim() || void 0;
|
|
256
|
+
return {
|
|
257
|
+
description: `Authio organization: ${wanted ?? "any"}`,
|
|
258
|
+
check: (user) => {
|
|
259
|
+
const satisfied = !!user.organizationId && (!wanted || user.organizationAlias?.toLowerCase() === wanted.toLowerCase() || user.organizationId.toLowerCase() === wanted.toLowerCase());
|
|
260
|
+
return satisfied ? void 0 : { message: wanted ? `The token is not in the organization ${wanted}.` : "The token has no organization context." };
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function requireAuthenticationLevel(minimumLevel) {
|
|
265
|
+
if (!Number.isInteger(minimumLevel) || minimumLevel < 0) throw new RangeError("The minimum level must be a non-negative integer.");
|
|
266
|
+
return {
|
|
267
|
+
description: `Authio authentication level >= ${minimumLevel}`,
|
|
268
|
+
check: (user) => user.authenticationLevel !== void 0 && user.authenticationLevel >= minimumLevel ? void 0 : { message: `The authentication level must be at least ${minimumLevel}.`, error: "insufficient_user_authentication" }
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function requireAuthenticationMethod(methods, match = "any") {
|
|
272
|
+
const required = listOf(methods, "authentication method");
|
|
273
|
+
return {
|
|
274
|
+
description: `Authio authentication method (${match}): ${required.join(", ")}`,
|
|
275
|
+
check: (user) => {
|
|
276
|
+
const used = user.authenticationMethods.map((method) => method.toLowerCase());
|
|
277
|
+
return satisfies(required, match, (method) => used.includes(method.toLowerCase())) ? void 0 : { message: `The user must authenticate with (${match}): ${required.join(", ")}.`, error: "insufficient_user_authentication" };
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function requireClient(clientIds) {
|
|
282
|
+
const required = listOf(clientIds, "client id");
|
|
283
|
+
return {
|
|
284
|
+
description: `Authio client: ${required.join(", ")}`,
|
|
285
|
+
check: (user) => user.clientId !== void 0 && required.includes(user.clientId) ? void 0 : { message: `The token was not issued to an accepted client (${required.join(", ")}).` }
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function allOf(...rules) {
|
|
289
|
+
return {
|
|
290
|
+
description: rules.map((rule) => rule.description).join(" and "),
|
|
291
|
+
check: (user) => authorize(user, ...rules)
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function anyOf(...rules) {
|
|
295
|
+
if (rules.length === 0) throw new TypeError("anyOf needs at least one rule.");
|
|
296
|
+
return {
|
|
297
|
+
description: rules.map((rule) => rule.description).join(" or "),
|
|
298
|
+
check: (user) => {
|
|
299
|
+
const failures = rules.map((rule) => rule.check(user));
|
|
300
|
+
return failures.some((failure) => failure === void 0) ? void 0 : failures[0];
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function listOf(values, name) {
|
|
305
|
+
const list = [...new Set((typeof values === "string" ? [values] : [...values]).filter((value) => value?.trim()))];
|
|
306
|
+
if (list.length === 0) throw new TypeError(`At least one ${name} is required.`);
|
|
307
|
+
return list;
|
|
308
|
+
}
|
|
309
|
+
function satisfies(required, match, holds) {
|
|
310
|
+
return match === "all" ? required.every(holds) : required.some(holds);
|
|
311
|
+
}
|
|
312
|
+
function describeSources(sources) {
|
|
313
|
+
return sources && sources.length > 0 ? ` from ${[...new Set(sources)].join(", ")}` : "";
|
|
314
|
+
}
|
|
315
|
+
var AuthioBearerError = class extends client.AuthioError {
|
|
316
|
+
status = 401;
|
|
317
|
+
/** `invalid_token`, or `undefined` when the request had no token at all (RFC 6750 §3.1). */
|
|
318
|
+
error;
|
|
319
|
+
/** Why the token was refused, safe to send to the caller. */
|
|
320
|
+
description;
|
|
321
|
+
constructor(description, options = {}) {
|
|
322
|
+
super(description ?? "A valid Authio access token is required.", { cause: options.cause });
|
|
323
|
+
this.error = "error" in options ? options.error : "invalid_token";
|
|
324
|
+
this.description = description === void 0 ? void 0 : toHeaderText(description);
|
|
325
|
+
}
|
|
326
|
+
/** The `WWW-Authenticate` value. */
|
|
327
|
+
get wwwAuthenticate() {
|
|
328
|
+
if (!this.error) return "Bearer";
|
|
329
|
+
return this.description ? `Bearer error="${this.error}", error_description="${this.description}"` : `Bearer error="${this.error}"`;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
var AuthioForbiddenError = class extends client.AuthioError {
|
|
333
|
+
status = 403;
|
|
334
|
+
failure;
|
|
335
|
+
constructor(failure) {
|
|
336
|
+
super(failure.message);
|
|
337
|
+
this.failure = failure;
|
|
338
|
+
}
|
|
339
|
+
/** The `WWW-Authenticate` value, or `undefined` when the refusal is not about scopes or the sign-in. */
|
|
340
|
+
get wwwAuthenticate() {
|
|
341
|
+
return toForbiddenChallenge(this.failure);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
function toForbiddenChallenge(failure) {
|
|
345
|
+
if (!failure.error) return void 0;
|
|
346
|
+
const value = `Bearer error="${failure.error}", error_description="${escapeQuoted(failure.message)}"`;
|
|
347
|
+
return failure.scope ? `${value}, scope="${escapeQuoted(failure.scope)}"` : value;
|
|
348
|
+
}
|
|
349
|
+
function toProblemDetails(error) {
|
|
350
|
+
if (error instanceof AuthioBearerError)
|
|
351
|
+
return {
|
|
352
|
+
type: "https://tools.ietf.org/html/rfc9110#section-15.5.2",
|
|
353
|
+
title: "Unauthorized",
|
|
354
|
+
status: 401,
|
|
355
|
+
detail: "A valid Authio access token is required."
|
|
356
|
+
};
|
|
357
|
+
return {
|
|
358
|
+
type: "https://tools.ietf.org/html/rfc9110#section-15.5.4",
|
|
359
|
+
title: "Forbidden",
|
|
360
|
+
status: 403,
|
|
361
|
+
detail: error.failure.message || "The caller is not allowed to access this resource.",
|
|
362
|
+
...error.failure.error ? { error: error.failure.error } : {},
|
|
363
|
+
...error.failure.scope ? { scope: error.failure.scope } : {}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function toHeaderText(text) {
|
|
367
|
+
return [...text].map((character) => character >= " " && character <= "~" && character !== '"' && character !== "\\" ? character : "?").join("");
|
|
368
|
+
}
|
|
369
|
+
function escapeQuoted(value) {
|
|
370
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
371
|
+
}
|
|
372
|
+
var AuthioBearerErrorDescriptions = {
|
|
373
|
+
Inactive: "Authio reports the token as not active to this API's client. It was revoked or has expired or its session ended; or this API's client is not the introspection client of the resource server the token is for.",
|
|
374
|
+
Unconfirmed: "Authio could not be reached to confirm that the token is active.",
|
|
375
|
+
Empty: "The token is empty.",
|
|
376
|
+
Encrypted: "The token is encrypted; validate it with introspection ('introspectionOnly').",
|
|
377
|
+
SigningKeysUnavailable: "Authio could not be reached to read its signing keys."
|
|
378
|
+
};
|
|
379
|
+
var maxCachedAnswers = 1e4;
|
|
380
|
+
function createAuthioVerifier(options) {
|
|
381
|
+
const audiences = [...new Set((typeof options.audiences === "string" ? [options.audiences] : [...options.audiences ?? []]).filter((value) => value?.trim()))];
|
|
382
|
+
if (audiences.length === 0) throw new client.AuthioConfigurationError("The Authio verifier needs at least one audience: the API's resource identifier or client id.");
|
|
383
|
+
const connection = client.resolveConnection(options);
|
|
384
|
+
const discovery = client.createAuthioDiscoveryClient(connection);
|
|
385
|
+
const introspection = options.introspection ?? "none";
|
|
386
|
+
const client$1 = resolveClient(options);
|
|
387
|
+
if (introspection !== "none" && !client$1)
|
|
388
|
+
throw new client.AuthioConfigurationError(
|
|
389
|
+
`Authio introspection ('${introspection}') needs the API's confidential client: pass options.client with its clientId and clientSecret or private key.`
|
|
390
|
+
);
|
|
391
|
+
const cacheDurationMs = client.seconds(options.introspectionCacheDuration, 30, "introspectionCacheDuration");
|
|
392
|
+
const clockSkew = client.seconds(options.clockSkew, 30, "clockSkew") / 1e3;
|
|
393
|
+
const algorithms = options.validAlgorithms && options.validAlgorithms.length > 0 ? [...options.validAlgorithms] : [...client.AuthioAsymmetricAlgorithms];
|
|
394
|
+
const rejectNonAccessTokens = options.rejectNonAccessTokens ?? true;
|
|
395
|
+
const claimSettings = { roleClaims: options.roleClaims, groupClaims: options.groupClaims };
|
|
396
|
+
const cache = /* @__PURE__ */ new Map();
|
|
397
|
+
let keys;
|
|
398
|
+
const introspect = async (token, key, signal) => {
|
|
399
|
+
const now = connection.clock.now();
|
|
400
|
+
const cached = cache.get(key);
|
|
401
|
+
if (cached && cached.until > now) return cached.result;
|
|
402
|
+
cache.delete(key);
|
|
403
|
+
const result = await client$1.introspect(token, { tokenTypeHint: client.AuthioTokenTypes.AccessToken, signal });
|
|
404
|
+
if (!result.active) return void 0;
|
|
405
|
+
const until = Math.min(now + cacheDurationMs, typeof result.exp === "number" ? result.exp * 1e3 : Number.POSITIVE_INFINITY);
|
|
406
|
+
if (until > now) {
|
|
407
|
+
if (cache.size >= maxCachedAnswers) cache.delete(cache.keys().next().value);
|
|
408
|
+
cache.set(key, { result, until });
|
|
409
|
+
}
|
|
410
|
+
return result;
|
|
411
|
+
};
|
|
412
|
+
const signingKeys = async (signal) => {
|
|
413
|
+
const document = await discovery.get(signal);
|
|
414
|
+
const uri = document.jwks_uri;
|
|
415
|
+
if (!uri) throw new client.AuthioError("The Authio discovery document has no jwks_uri.");
|
|
416
|
+
if (!keys || keys.uri !== uri) keys = { uri, set: jose.createRemoteJWKSet(new URL(uri), { [jose.customFetch]: connection.fetch, timeoutDuration: connection.httpTimeoutMs }) };
|
|
417
|
+
return keys.set;
|
|
418
|
+
};
|
|
419
|
+
const verifyJwt = async (token, signal) => {
|
|
420
|
+
if (client.isAuthioTokenEncrypted(token)) throw new AuthioBearerError(AuthioBearerErrorDescriptions.Encrypted);
|
|
421
|
+
let keySet;
|
|
422
|
+
try {
|
|
423
|
+
keySet = await signingKeys(signal);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
throw new AuthioBearerError(AuthioBearerErrorDescriptions.SigningKeysUnavailable, { cause: error });
|
|
426
|
+
}
|
|
427
|
+
let payload;
|
|
428
|
+
try {
|
|
429
|
+
({ payload } = await jose.jwtVerify(token, keySet, {
|
|
430
|
+
issuer: connection.issuer,
|
|
431
|
+
audience: audiences,
|
|
432
|
+
algorithms,
|
|
433
|
+
clockTolerance: clockSkew,
|
|
434
|
+
requiredClaims: ["exp"],
|
|
435
|
+
currentDate: new Date(connection.clock.now())
|
|
436
|
+
}));
|
|
437
|
+
} catch (error) {
|
|
438
|
+
throw new AuthioBearerError(describeJwtFailure(error, audiences, token), { cause: error });
|
|
439
|
+
}
|
|
440
|
+
const type = typeof payload.typ === "string" ? payload.typ : void 0;
|
|
441
|
+
if (rejectNonAccessTokens && type !== void 0 && type.toLowerCase() !== "bearer")
|
|
442
|
+
throw new AuthioBearerError(`Only access tokens are accepted; this token's typ is '${type}'.`);
|
|
443
|
+
if (introspection === "activeCheck") await confirmActive(token, payload, signal);
|
|
444
|
+
return new AuthioUser(payload, claimSettings, token);
|
|
445
|
+
};
|
|
446
|
+
const confirmActive = async (token, payload, signal) => {
|
|
447
|
+
const key = payload.jti ? `jti:${payload.jti}` : await keyForToken(token);
|
|
448
|
+
let result;
|
|
449
|
+
try {
|
|
450
|
+
result = await introspect(token, key, signal);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
if (signal?.aborted) throw error;
|
|
453
|
+
throw new AuthioBearerError(AuthioBearerErrorDescriptions.Unconfirmed, { cause: error });
|
|
454
|
+
}
|
|
455
|
+
if (!result) throw new AuthioBearerError(AuthioBearerErrorDescriptions.Inactive);
|
|
456
|
+
};
|
|
457
|
+
const verifyByIntrospection = async (token, signal) => {
|
|
458
|
+
let result;
|
|
459
|
+
try {
|
|
460
|
+
result = await introspect(token, await keyForToken(token), signal);
|
|
461
|
+
} catch (error) {
|
|
462
|
+
if (signal?.aborted) throw error;
|
|
463
|
+
throw new AuthioBearerError(AuthioBearerErrorDescriptions.Unconfirmed, { cause: error });
|
|
464
|
+
}
|
|
465
|
+
if (!result) throw new AuthioBearerError(AuthioBearerErrorDescriptions.Inactive);
|
|
466
|
+
if (result.iss && result.iss !== connection.issuer) throw new AuthioBearerError(`The issuer '${result.iss}' is invalid`);
|
|
467
|
+
const tokenAudiences = [result.aud ?? []].flat();
|
|
468
|
+
if (!tokenAudiences.some((audience) => audiences.includes(audience))) throw new AuthioBearerError(describeAudience(tokenAudiences, audiences));
|
|
469
|
+
if (typeof result.exp === "number" && (result.exp + clockSkew) * 1e3 <= connection.clock.now())
|
|
470
|
+
throw new AuthioBearerError(`The token expired at '${new Date(result.exp * 1e3).toISOString()}'`);
|
|
471
|
+
const { active: _active, token_type: tokenType, username, ...members } = result;
|
|
472
|
+
const claims = { ...members };
|
|
473
|
+
if (typeof tokenType === "string" && claims.typ === void 0) claims.typ = tokenType;
|
|
474
|
+
if (typeof username === "string" && claims.preferred_username === void 0) claims.preferred_username = username;
|
|
475
|
+
if (typeof result.client_id === "string" && claims.azp === void 0) claims.azp = result.client_id;
|
|
476
|
+
const type = typeof claims.typ === "string" ? claims.typ : void 0;
|
|
477
|
+
if (rejectNonAccessTokens && type !== void 0 && type.toLowerCase() !== "bearer")
|
|
478
|
+
throw new AuthioBearerError(`Only access tokens are accepted; this token's typ is '${type}'.`);
|
|
479
|
+
return new AuthioUser(claims, claimSettings, token);
|
|
480
|
+
};
|
|
481
|
+
const verify = async (token, signal) => {
|
|
482
|
+
if (typeof token !== "string" || !token.trim()) throw new AuthioBearerError(AuthioBearerErrorDescriptions.Empty);
|
|
483
|
+
return introspection === "introspectionOnly" ? verifyByIntrospection(token.trim(), signal) : verifyJwt(token.trim(), signal);
|
|
484
|
+
};
|
|
485
|
+
return {
|
|
486
|
+
connection,
|
|
487
|
+
discovery,
|
|
488
|
+
audiences,
|
|
489
|
+
introspection,
|
|
490
|
+
client: client$1,
|
|
491
|
+
verify,
|
|
492
|
+
async verifyAuthorizationHeader(header, signal) {
|
|
493
|
+
const token = readBearerToken(header);
|
|
494
|
+
if (token === void 0) throw new AuthioBearerError(void 0, { error: void 0 });
|
|
495
|
+
return verify(token, signal);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function readBearerToken(header) {
|
|
500
|
+
const match = /^\s*Bearer\s+(\S+)\s*$/i.exec(header ?? "");
|
|
501
|
+
return match?.[1];
|
|
502
|
+
}
|
|
503
|
+
async function keyForToken(token) {
|
|
504
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token)));
|
|
505
|
+
return `token:${[...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
506
|
+
}
|
|
507
|
+
function resolveClient(options) {
|
|
508
|
+
const given = options.client;
|
|
509
|
+
if (!given) return void 0;
|
|
510
|
+
if (isClient(given)) return given;
|
|
511
|
+
return client.createAuthioClient({
|
|
512
|
+
...given,
|
|
513
|
+
issuer: options.issuer,
|
|
514
|
+
authority: options.authority,
|
|
515
|
+
realm: options.realm,
|
|
516
|
+
requireHttps: options.requireHttps,
|
|
517
|
+
discoveryCacheDuration: options.discoveryCacheDuration,
|
|
518
|
+
httpTimeout: options.httpTimeout,
|
|
519
|
+
fetch: options.fetch,
|
|
520
|
+
clock: options.clock
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
function isClient(value) {
|
|
524
|
+
return typeof value.introspect === "function" && typeof value.tokens === "object";
|
|
525
|
+
}
|
|
526
|
+
function describeJwtFailure(error, audiences, token) {
|
|
527
|
+
if (error instanceof jose.errors.JWTExpired) {
|
|
528
|
+
const exp = error.payload.exp;
|
|
529
|
+
return typeof exp === "number" ? `The token expired at '${new Date(exp * 1e3).toISOString()}'` : "The token has expired";
|
|
530
|
+
}
|
|
531
|
+
if (error instanceof jose.errors.JWTClaimValidationFailed) {
|
|
532
|
+
switch (error.claim) {
|
|
533
|
+
case "aud":
|
|
534
|
+
return describeAudience([error.payload.aud ?? []].flat(), audiences);
|
|
535
|
+
case "iss":
|
|
536
|
+
return `The issuer '${error.payload.iss ?? ""}' is invalid`;
|
|
537
|
+
case "nbf":
|
|
538
|
+
return typeof error.payload.nbf === "number" ? `The token is not valid before '${new Date(error.payload.nbf * 1e3).toISOString()}'` : "The token is not valid yet";
|
|
539
|
+
case "exp":
|
|
540
|
+
return "The token has no expiration";
|
|
541
|
+
default:
|
|
542
|
+
return `The token's ${error.claim} claim is invalid`;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (error instanceof jose.errors.JWSSignatureVerificationFailed) return "The signature is invalid";
|
|
546
|
+
if (error instanceof jose.errors.JWKSNoMatchingKey) return "The signature key was not found";
|
|
547
|
+
if (error instanceof jose.errors.JOSEAlgNotAllowed) return `The algorithm '${algorithmOf(token)}' is not available`;
|
|
548
|
+
if (error instanceof jose.errors.JWKSTimeout || error instanceof jose.errors.JWKSInvalid) return AuthioBearerErrorDescriptions.SigningKeysUnavailable;
|
|
549
|
+
if (error instanceof jose.errors.JWTInvalid || error instanceof jose.errors.JWSInvalid) return "The token is malformed";
|
|
550
|
+
return void 0;
|
|
551
|
+
}
|
|
552
|
+
function describeAudience(tokenAudiences, audiences) {
|
|
553
|
+
const known = tokenAudiences.length === 0 ? "" : ` ${tokenAudiences.map((audience) => `'${audience}'`).join(" and ")}`;
|
|
554
|
+
const accepted = audiences.map((audience) => `'${audience}'`).join(" or ");
|
|
555
|
+
return audiences.length === 1 ? `The token is not addressed to this API: its audience${known} is not ${accepted}. Request the token for that resource (RFC 8707).` : `The token is not addressed to this API: its audience${known} is not ${accepted}. Request the token for one of those resources (RFC 8707).`;
|
|
556
|
+
}
|
|
557
|
+
function algorithmOf(token) {
|
|
558
|
+
try {
|
|
559
|
+
return String(jose.decodeProtectedHeader(token).alg ?? "");
|
|
560
|
+
} catch {
|
|
561
|
+
return "";
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function createAuthioWebhooks(options) {
|
|
565
|
+
const secrets = [...new Set([options.secret, ...options.secrets ?? []].filter((secret) => !!secret?.trim()))];
|
|
566
|
+
if (secrets.length === 0) throw new client.AuthioConfigurationError("Authio webhooks need the webhook's secret.");
|
|
567
|
+
const clock = options.clock ?? client.systemClock;
|
|
568
|
+
const tolerance = options.tolerance ?? 300;
|
|
569
|
+
const windowMs = (options.deduplicationWindow ?? 86400) * 1e3;
|
|
570
|
+
const maxBodySize = options.maxBodySize ?? 1024 * 1024;
|
|
571
|
+
const store = resolveStore(options.deduplication ?? "memory", clock);
|
|
572
|
+
const handlers = [];
|
|
573
|
+
const webhooks = {
|
|
574
|
+
maxBodySize,
|
|
575
|
+
on(type, handler) {
|
|
576
|
+
if (!type) throw new TypeError("An event type (or '*') is required.");
|
|
577
|
+
handlers.push({ type, handler });
|
|
578
|
+
return webhooks;
|
|
579
|
+
},
|
|
580
|
+
async handle(delivery) {
|
|
581
|
+
const body = typeof delivery.body === "string" ? new TextEncoder().encode(delivery.body) : delivery.body;
|
|
582
|
+
if (body.byteLength > maxBodySize) return { status: 413, reason: "too_large" };
|
|
583
|
+
const signature = headerOf(delivery.headers, client.AuthioWebhookHeaders.Signature);
|
|
584
|
+
let refusal = "no webhook secret matched";
|
|
585
|
+
for (const secret of secrets) {
|
|
586
|
+
const result = await client.verifyWebhookSignature(signature, body, secret, { tolerance, now: clock.now() });
|
|
587
|
+
if (result.valid) {
|
|
588
|
+
refusal = "";
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
refusal = result.reason;
|
|
592
|
+
}
|
|
593
|
+
if (refusal) {
|
|
594
|
+
options.onError?.(new Error(refusal), { reason: "unsigned" });
|
|
595
|
+
return { status: 401, reason: "unsigned" };
|
|
596
|
+
}
|
|
597
|
+
const event = client.tryParseWebhookEvent(body);
|
|
598
|
+
if (!event) return { status: 400, reason: "unreadable" };
|
|
599
|
+
const eventId = headerOf(delivery.headers, client.AuthioWebhookHeaders.EventId)?.trim() || String(event.id ?? "");
|
|
600
|
+
if (eventId && !await store.tryBegin(eventId, windowMs)) return { status: 200, reason: "duplicate", event };
|
|
601
|
+
try {
|
|
602
|
+
for (const { type, handler } of handlers) if (type === "*" || type === event.type) await handler(event);
|
|
603
|
+
} catch (error) {
|
|
604
|
+
if (eventId) await store.release(eventId);
|
|
605
|
+
options.onError?.(error, { reason: "handler_failed", eventId, eventType: event.type });
|
|
606
|
+
return { status: 500, reason: "handler_failed", event };
|
|
607
|
+
}
|
|
608
|
+
return { status: 200, reason: "handled", event };
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
return webhooks;
|
|
612
|
+
}
|
|
613
|
+
function createMemoryDeduplicationStore(clock = client.systemClock) {
|
|
614
|
+
const seen = /* @__PURE__ */ new Map();
|
|
615
|
+
return {
|
|
616
|
+
tryBegin(eventId, windowMs) {
|
|
617
|
+
const now = clock.now();
|
|
618
|
+
for (const [id, until2] of seen) {
|
|
619
|
+
if (until2 > now) break;
|
|
620
|
+
seen.delete(id);
|
|
621
|
+
}
|
|
622
|
+
const until = seen.get(eventId);
|
|
623
|
+
if (until !== void 0 && until > now) return false;
|
|
624
|
+
seen.delete(eventId);
|
|
625
|
+
seen.set(eventId, now + windowMs);
|
|
626
|
+
return true;
|
|
627
|
+
},
|
|
628
|
+
release(eventId) {
|
|
629
|
+
seen.delete(eventId);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
function resolveStore(deduplication, clock) {
|
|
634
|
+
if (deduplication === "none") return { tryBegin: () => true, release: () => void 0 };
|
|
635
|
+
if (deduplication === "memory") return createMemoryDeduplicationStore(clock);
|
|
636
|
+
return deduplication;
|
|
637
|
+
}
|
|
638
|
+
function headerOf(headers, name) {
|
|
639
|
+
if (typeof headers.get === "function") return headers.get(name) ?? void 0;
|
|
640
|
+
const wanted = name.toLowerCase();
|
|
641
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
642
|
+
if (key.toLowerCase() === wanted) return Array.isArray(value) ? value[0] : value;
|
|
643
|
+
}
|
|
644
|
+
return void 0;
|
|
645
|
+
}
|
|
646
|
+
function createForwardingFetch(subject, inner = globalThis.fetch) {
|
|
647
|
+
const token = tokenOf(subject);
|
|
648
|
+
return (input, init) => {
|
|
649
|
+
if (!token) return inner(input, init);
|
|
650
|
+
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
651
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
652
|
+
return inner(input, { ...init, headers });
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
function createOnBehalfOf(options) {
|
|
656
|
+
const { client: client$1 } = options;
|
|
657
|
+
const scope = client.toScopeString(options.scopes);
|
|
658
|
+
const sortedScope = (scope ?? "").split(" ").filter(Boolean).sort().join(" ");
|
|
659
|
+
const skewMs = client.seconds(options.tokenRefreshSkew, client.AuthioDefaults.TokenRefreshSkew, "tokenRefreshSkew");
|
|
660
|
+
const entries = /* @__PURE__ */ new Map();
|
|
661
|
+
const pending = /* @__PURE__ */ new Map();
|
|
662
|
+
const keyOf = async (subject) => {
|
|
663
|
+
const token = tokenOf(subject);
|
|
664
|
+
const jti = typeof subject === "string" ? void 0 : subject.tokenId;
|
|
665
|
+
return `${jti ? `jti:${jti}` : await keyForToken(token)}|${options.resource ?? ""}|${sortedScope}`;
|
|
666
|
+
};
|
|
667
|
+
const getToken = async (subject, signal) => {
|
|
668
|
+
const incoming = tokenOf(subject);
|
|
669
|
+
if (!incoming) throw new client.AuthioError("On-behalf-of needs the caller's access token, and the request has none.");
|
|
670
|
+
const key = await keyOf(subject);
|
|
671
|
+
const cached = entries.get(key);
|
|
672
|
+
if (cached && client$1.connection.clock.now() < cached.refreshAt) return cached;
|
|
673
|
+
let request = pending.get(key);
|
|
674
|
+
if (!request) {
|
|
675
|
+
request = client$1.tokens.exchange({ subjectToken: incoming, scopes: scope, resource: options.resource, signal }).then((response) => {
|
|
676
|
+
const token = client.toClientCredentialsToken(response, client$1.connection.clock.now(), skewMs);
|
|
677
|
+
entries.set(key, token);
|
|
678
|
+
return token;
|
|
679
|
+
}).finally(() => pending.delete(key));
|
|
680
|
+
pending.set(key, request);
|
|
681
|
+
}
|
|
682
|
+
return request;
|
|
683
|
+
};
|
|
684
|
+
const invalidate = async (subject, accessToken) => {
|
|
685
|
+
const key = await keyOf(subject);
|
|
686
|
+
if (accessToken === void 0 || entries.get(key)?.accessToken === accessToken) entries.delete(key);
|
|
687
|
+
};
|
|
688
|
+
return {
|
|
689
|
+
getToken,
|
|
690
|
+
invalidate,
|
|
691
|
+
fetch(subject, inner = client$1.connection.fetch) {
|
|
692
|
+
return async (input, init) => {
|
|
693
|
+
const request = new Request(input, init);
|
|
694
|
+
const body = request.body ? await request.arrayBuffer() : void 0;
|
|
695
|
+
const attempt = (token2) => {
|
|
696
|
+
const headers = new Headers(request.headers);
|
|
697
|
+
headers.set("authorization", `${token2.tokenType} ${token2.accessToken}`);
|
|
698
|
+
return inner(request.url, { method: request.method, headers, body, signal: request.signal, redirect: request.redirect });
|
|
699
|
+
};
|
|
700
|
+
const token = await getToken(subject, request.signal);
|
|
701
|
+
const response = await attempt(token);
|
|
702
|
+
if (response.status !== 401) return response;
|
|
703
|
+
await invalidate(subject, token.accessToken);
|
|
704
|
+
return attempt(await getToken(subject, request.signal));
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
function tokenOf(subject) {
|
|
710
|
+
if (!subject) return void 0;
|
|
711
|
+
return typeof subject === "string" ? subject : subject.token;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
exports.AuthioBearerError = AuthioBearerError;
|
|
715
|
+
exports.AuthioBearerErrorDescriptions = AuthioBearerErrorDescriptions;
|
|
716
|
+
exports.AuthioForbiddenError = AuthioForbiddenError;
|
|
717
|
+
exports.AuthioUser = AuthioUser;
|
|
718
|
+
exports.allOf = allOf;
|
|
719
|
+
exports.anyOf = anyOf;
|
|
720
|
+
exports.authorize = authorize;
|
|
721
|
+
exports.createAuthioVerifier = createAuthioVerifier;
|
|
722
|
+
exports.createAuthioWebhooks = createAuthioWebhooks;
|
|
723
|
+
exports.createForwardingFetch = createForwardingFetch;
|
|
724
|
+
exports.createMemoryDeduplicationStore = createMemoryDeduplicationStore;
|
|
725
|
+
exports.createOnBehalfOf = createOnBehalfOf;
|
|
726
|
+
exports.readBearerToken = readBearerToken;
|
|
727
|
+
exports.requireAuthenticationLevel = requireAuthenticationLevel;
|
|
728
|
+
exports.requireAuthenticationMethod = requireAuthenticationMethod;
|
|
729
|
+
exports.requireClaim = requireClaim;
|
|
730
|
+
exports.requireClient = requireClient;
|
|
731
|
+
exports.requireGroup = requireGroup;
|
|
732
|
+
exports.requireOrganization = requireOrganization;
|
|
733
|
+
exports.requirePermission = requirePermission;
|
|
734
|
+
exports.requireRole = requireRole;
|
|
735
|
+
exports.requireScope = requireScope;
|
|
736
|
+
exports.toForbiddenChallenge = toForbiddenChallenge;
|
|
737
|
+
exports.toProblemDetails = toProblemDetails;
|
|
738
|
+
//# sourceMappingURL=index.cjs.map
|
|
739
|
+
//# sourceMappingURL=index.cjs.map
|