@authyon/server 0.2.0-beta.2 → 0.2.0-beta.20
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 +267 -2
- package/dist/authorization-DcA5oKZT.d.cts +129 -0
- package/dist/authorization-DfFCoDQ6.d.ts +129 -0
- package/dist/authyonServerClient-UA7HdjtD.d.cts +1232 -0
- package/dist/authyonServerClient-UA7HdjtD.d.ts +1232 -0
- package/dist/bff.cjs +1174 -0
- package/dist/bff.d.cts +149 -0
- package/dist/bff.d.ts +149 -0
- package/dist/bff.js +808 -0
- package/dist/chunk-77J4NURK.js +13 -0
- package/dist/chunk-NKWQMYB3.js +485 -0
- package/dist/chunk-TDJNBD3Z.js +354 -0
- package/dist/index.cjs +591 -64
- package/dist/index.d.cts +47 -756
- package/dist/index.d.ts +47 -756
- package/dist/index.js +537 -798
- package/dist/next.cjs +328 -0
- package/dist/next.d.cts +27 -0
- package/dist/next.d.ts +27 -0
- package/dist/next.js +48 -0
- package/package.json +28 -5
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __typeError = (msg) => {
|
|
7
|
+
throw TypeError(msg);
|
|
8
|
+
};
|
|
6
9
|
var __export = (target, all) => {
|
|
7
10
|
for (var name in all)
|
|
8
11
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -16,6 +19,10 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
19
|
return to;
|
|
17
20
|
};
|
|
18
21
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
23
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
24
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
25
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
19
26
|
|
|
20
27
|
// src/index.ts
|
|
21
28
|
var index_exports = {};
|
|
@@ -32,6 +39,7 @@ __export(index_exports, {
|
|
|
32
39
|
LoggingHttpAdapter: () => LoggingHttpAdapter,
|
|
33
40
|
TenantScopedClient: () => TenantScopedClient,
|
|
34
41
|
TokenVerificationError: () => TokenVerificationError,
|
|
42
|
+
UserScopedClient: () => UserScopedClient,
|
|
35
43
|
authorizeRequest: () => authorizeRequest,
|
|
36
44
|
authorizeToken: () => authorizeToken,
|
|
37
45
|
createAuthorizationErrorResponse: () => createAuthorizationErrorResponse,
|
|
@@ -40,39 +48,250 @@ __export(index_exports, {
|
|
|
40
48
|
createClient: () => createClient,
|
|
41
49
|
createExpressAuthorizationMiddleware: () => createExpressAuthorizationMiddleware,
|
|
42
50
|
hasPermission: () => hasPermission,
|
|
51
|
+
hasPermissionGroup: () => hasPermissionGroup,
|
|
43
52
|
normalizeClientIp: () => normalizeClientIp
|
|
44
53
|
});
|
|
45
54
|
module.exports = __toCommonJS(index_exports);
|
|
46
55
|
|
|
56
|
+
// src/client/credentialManagement.ts
|
|
57
|
+
function segment(value) {
|
|
58
|
+
if (typeof value !== "string" || !value.trim() || value === "." || value === ".." || [...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)) {
|
|
59
|
+
throw new TypeError("Authyon: invalid route identifier.");
|
|
60
|
+
}
|
|
61
|
+
return encodeURIComponent(value);
|
|
62
|
+
}
|
|
63
|
+
function permissionsBody(input) {
|
|
64
|
+
if (input.permissions !== void 0 && input.scopes !== void 0) {
|
|
65
|
+
throw new TypeError("Authyon: supply permissions or scopes, not both.");
|
|
66
|
+
}
|
|
67
|
+
const values = input.permissions ?? input.scopes;
|
|
68
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
69
|
+
throw new TypeError("Authyon: explicit credential permissions are required.");
|
|
70
|
+
}
|
|
71
|
+
const permissions = values.map((value) => {
|
|
72
|
+
if (typeof value !== "string") throw new TypeError("Authyon: invalid permission.");
|
|
73
|
+
const normalized = value.trim().toLowerCase();
|
|
74
|
+
if (!/^(?:\*|[a-z][a-z0-9_-]*):(?:\*|[a-z][a-z0-9_-]*):(?:\*|[a-z][a-z0-9_-]*)$/u.test(normalized)) {
|
|
75
|
+
throw new TypeError("Authyon: permissions must have namespace:resource:action format.");
|
|
76
|
+
}
|
|
77
|
+
return normalized;
|
|
78
|
+
});
|
|
79
|
+
return { permissions: [...new Set(permissions)] };
|
|
80
|
+
}
|
|
81
|
+
function lifetimeBody(input) {
|
|
82
|
+
if (input.lifetimeDays === void 0 || input.lifetimeDays === null) return {};
|
|
83
|
+
if (!Number.isInteger(input.lifetimeDays) || input.lifetimeDays < 1 || input.lifetimeDays > 3650) {
|
|
84
|
+
throw new RangeError("Authyon: lifetimeDays must be between 1 and 3650.");
|
|
85
|
+
}
|
|
86
|
+
return { lifetimeDays: input.lifetimeDays };
|
|
87
|
+
}
|
|
88
|
+
function credentialQuery(options = {}) {
|
|
89
|
+
const { skip, take, search } = options;
|
|
90
|
+
if (skip !== void 0 && (!Number.isInteger(skip) || skip < 0 || skip > 2147483647))
|
|
91
|
+
throw new RangeError("Authyon: invalid skip.");
|
|
92
|
+
if (take !== void 0 && (!Number.isInteger(take) || take < 1 || take > 100))
|
|
93
|
+
throw new RangeError("Authyon: take must be between 1 and 100.");
|
|
94
|
+
if (search !== void 0 && (typeof search !== "string" || search.trim().length > 200))
|
|
95
|
+
throw new RangeError("Authyon: search must contain at most 200 characters.");
|
|
96
|
+
return { search: search?.trim() || void 0, skip, take };
|
|
97
|
+
}
|
|
98
|
+
function credentialSummary(value) {
|
|
99
|
+
return {
|
|
100
|
+
id: value.id,
|
|
101
|
+
clientId: value.clientId,
|
|
102
|
+
description: value.description,
|
|
103
|
+
createdAt: value.createdAt,
|
|
104
|
+
lastUsedAt: value.lastUsedAt,
|
|
105
|
+
revokedAt: value.revokedAt,
|
|
106
|
+
expiresAt: value.expiresAt ?? null,
|
|
107
|
+
isActive: value.isActive,
|
|
108
|
+
permissions: [...value.permissions]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function credentialDetail(value) {
|
|
112
|
+
return {
|
|
113
|
+
...credentialSummary(value),
|
|
114
|
+
createdBy: value.createdBy == null ? null : {
|
|
115
|
+
id: value.createdBy.id,
|
|
116
|
+
type: value.createdBy.type,
|
|
117
|
+
displayName: value.createdBy.displayName
|
|
118
|
+
},
|
|
119
|
+
updatedAt: value.updatedAt,
|
|
120
|
+
secretRotatedAt: value.secretRotatedAt,
|
|
121
|
+
expiresAt: value.expiresAt ?? null,
|
|
122
|
+
ageSeconds: value.ageSeconds,
|
|
123
|
+
lifetimeSeconds: value.lifetimeSeconds,
|
|
124
|
+
accessTokenLifetimeSeconds: value.accessTokenLifetimeSeconds
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function credentialPage(request, path2, options = {}) {
|
|
128
|
+
const query = credentialQuery(options);
|
|
129
|
+
const page = await request(path2, { query });
|
|
130
|
+
if (!page || !Array.isArray(page.data))
|
|
131
|
+
throw new TypeError("Authyon: expected a paginated credential response.");
|
|
132
|
+
return {
|
|
133
|
+
data: page.data.map(credentialSummary),
|
|
134
|
+
page: page.page,
|
|
135
|
+
perPage: page.perPage,
|
|
136
|
+
total: page.total,
|
|
137
|
+
pages: page.pages,
|
|
138
|
+
hasNext: page.hasNext,
|
|
139
|
+
hasPrev: page.hasPrev
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async function allCredentials(request, path2) {
|
|
143
|
+
const result = [];
|
|
144
|
+
const seen = /* @__PURE__ */ new Set();
|
|
145
|
+
let skip = 0;
|
|
146
|
+
for (let iteration = 0; iteration < 1e3; iteration++) {
|
|
147
|
+
const page = await request(
|
|
148
|
+
path2,
|
|
149
|
+
iteration === 0 ? {} : { query: { skip, take: 100 } }
|
|
150
|
+
);
|
|
151
|
+
if (Array.isArray(page)) {
|
|
152
|
+
if (iteration !== 0)
|
|
153
|
+
throw new Error("Authyon: credential pagination changed during listing.");
|
|
154
|
+
return page.map(credentialSummary);
|
|
155
|
+
}
|
|
156
|
+
if (!page || !Array.isArray(page.data))
|
|
157
|
+
throw new TypeError("Authyon: invalid credential page.");
|
|
158
|
+
for (const row of page.data) {
|
|
159
|
+
if (seen.has(row.id))
|
|
160
|
+
throw new Error("Authyon: credential pages overlapped; retry the listing.");
|
|
161
|
+
seen.add(row.id);
|
|
162
|
+
result.push(credentialSummary(row));
|
|
163
|
+
}
|
|
164
|
+
if (!page.hasNext) return result;
|
|
165
|
+
if (page.data.length === 0) throw new Error("Authyon: credential pagination made no progress.");
|
|
166
|
+
skip += page.data.length;
|
|
167
|
+
}
|
|
168
|
+
throw new Error("Authyon: use listPage for more than 1000 credential pages.");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ../../internal/core/security/approvals.ts
|
|
172
|
+
function path(id, suffix = "") {
|
|
173
|
+
if (typeof id !== "string" || !id.trim())
|
|
174
|
+
throw new TypeError("Authyon: an approval id is required.");
|
|
175
|
+
return `/auth/authorizations/${encodeURIComponent(id)}${suffix}`;
|
|
176
|
+
}
|
|
177
|
+
var NO_METHODS = {
|
|
178
|
+
password: false,
|
|
179
|
+
authenticator: false,
|
|
180
|
+
emailCode: false,
|
|
181
|
+
passkey: false,
|
|
182
|
+
passkeyCount: 0,
|
|
183
|
+
recoveryCodes: 0,
|
|
184
|
+
sso: [],
|
|
185
|
+
twoFactor: false
|
|
186
|
+
};
|
|
187
|
+
function subjectSecurity(request) {
|
|
188
|
+
return {
|
|
189
|
+
methods: async () => {
|
|
190
|
+
const me = await request("/auth/me");
|
|
191
|
+
return me.security ?? NO_METHODS;
|
|
192
|
+
},
|
|
193
|
+
approvals: {
|
|
194
|
+
get: (id) => request(path(id)),
|
|
195
|
+
webauthnOptions: (id) => request(path(id, "/webauthn/options"), { method: "POST" }),
|
|
196
|
+
confirm: (id, input) => request(path(id, "/confirm"), { method: "POST", body: input }),
|
|
197
|
+
reject: (id) => request(path(id, "/reject"), { method: "POST" })
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/client/scopedManagementClient.ts
|
|
203
|
+
function scopedRequest(request, source) {
|
|
204
|
+
return async (path2, options = {}) => {
|
|
205
|
+
const token = typeof source === "function" ? await source() : source;
|
|
206
|
+
if (typeof token !== "string" || !token.trim() || /\s/u.test(token))
|
|
207
|
+
throw new TypeError("Authyon: a valid access token is required.");
|
|
208
|
+
return request(path2, {
|
|
209
|
+
...options,
|
|
210
|
+
headers: { ...options.headers, Authorization: `Bearer ${token}` }
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
var _request;
|
|
215
|
+
var UserScopedClient = class {
|
|
216
|
+
/** @hidden */
|
|
217
|
+
constructor(request, token) {
|
|
218
|
+
__privateAdd(this, _request);
|
|
219
|
+
this.tenants = {
|
|
220
|
+
members: {
|
|
221
|
+
list: (tenantId, options = {}) => __privateGet(this, _request).call(this, `/auth/tenants/${segment(tenantId)}/members`, { query: options }),
|
|
222
|
+
/** Adds an existing environment user by email; does not send an invitation email. */
|
|
223
|
+
invite: (tenantId, input) => __privateGet(this, _request).call(this, `/auth/tenants/${segment(tenantId)}/members`, {
|
|
224
|
+
method: "POST",
|
|
225
|
+
body: { email: input.email, roles: input.roles }
|
|
226
|
+
}),
|
|
227
|
+
remove: (tenantId, userId) => __privateGet(this, _request).call(this, `/auth/tenants/${segment(tenantId)}/members/${segment(userId)}`, {
|
|
228
|
+
method: "DELETE"
|
|
229
|
+
})
|
|
230
|
+
},
|
|
231
|
+
/**
|
|
232
|
+
* Invites by e-mail or by link, acting as the user. Requires the
|
|
233
|
+
* `tenants:members:invite` permission on the tenant.
|
|
234
|
+
*/
|
|
235
|
+
invites: {
|
|
236
|
+
list: (tenantId) => __privateGet(this, _request).call(this, `/auth/tenants/${segment(tenantId)}/invites`),
|
|
237
|
+
/** Always returns `acceptUrl`; `sendEmail: false` skips the e-mail. */
|
|
238
|
+
create: (tenantId, input) => __privateGet(this, _request).call(this, `/auth/tenants/${segment(tenantId)}/invites`, {
|
|
239
|
+
method: "POST",
|
|
240
|
+
body: {
|
|
241
|
+
email: input.email,
|
|
242
|
+
roles: input.roles,
|
|
243
|
+
sendEmail: input.sendEmail ?? true,
|
|
244
|
+
redirectUri: input.redirectUri
|
|
245
|
+
}
|
|
246
|
+
}),
|
|
247
|
+
/** New link (the previous one stops working), mailed unless `sendEmail` is `false`. */
|
|
248
|
+
resend: (tenantId, inviteId, input = {}) => __privateGet(this, _request).call(this, `/auth/tenants/${segment(tenantId)}/invites/${segment(inviteId)}/resend`, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
body: { sendEmail: input.sendEmail ?? true }
|
|
251
|
+
}),
|
|
252
|
+
revoke: (tenantId, inviteId) => __privateGet(this, _request).call(this, `/auth/tenants/${segment(tenantId)}/invites/${segment(inviteId)}`, {
|
|
253
|
+
method: "DELETE"
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
__privateSet(this, _request, scopedRequest(request, token));
|
|
258
|
+
this.security = subjectSecurity(__privateGet(this, _request));
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
_request = new WeakMap();
|
|
262
|
+
|
|
47
263
|
// ../../internal/core/config/defaults.ts
|
|
48
264
|
var DEFAULT_BASE_URL = "https://api.authyon.com";
|
|
49
265
|
var DEFAULT_EXPIRY_SKEW_MS = 3e4;
|
|
50
266
|
|
|
51
267
|
// ../../internal/core/auth/expiringTokenProvider.ts
|
|
268
|
+
var _token, _inFlight;
|
|
52
269
|
var ExpiringTokenProvider = class {
|
|
53
270
|
constructor(acquire, expirySkewMs = DEFAULT_EXPIRY_SKEW_MS) {
|
|
54
271
|
this.acquire = acquire;
|
|
55
272
|
this.expirySkewMs = expirySkewMs;
|
|
56
|
-
this
|
|
57
|
-
this
|
|
273
|
+
__privateAdd(this, _token, null);
|
|
274
|
+
__privateAdd(this, _inFlight, null);
|
|
58
275
|
}
|
|
59
276
|
async getAccessToken() {
|
|
60
|
-
if (this
|
|
61
|
-
return this.
|
|
277
|
+
if (__privateGet(this, _token) && Date.now() < __privateGet(this, _token).expiresAt - this.expirySkewMs) {
|
|
278
|
+
return __privateGet(this, _token).accessToken;
|
|
62
279
|
}
|
|
63
|
-
if (this
|
|
64
|
-
this
|
|
65
|
-
this
|
|
280
|
+
if (__privateGet(this, _inFlight)) return __privateGet(this, _inFlight);
|
|
281
|
+
__privateSet(this, _inFlight, this.acquire().then((token) => {
|
|
282
|
+
__privateSet(this, _token, token);
|
|
66
283
|
return token.accessToken;
|
|
67
284
|
}).finally(() => {
|
|
68
|
-
this
|
|
69
|
-
});
|
|
70
|
-
return this
|
|
285
|
+
__privateSet(this, _inFlight, null);
|
|
286
|
+
}));
|
|
287
|
+
return __privateGet(this, _inFlight);
|
|
71
288
|
}
|
|
72
289
|
clear() {
|
|
73
|
-
this
|
|
290
|
+
__privateSet(this, _token, null);
|
|
74
291
|
}
|
|
75
292
|
};
|
|
293
|
+
_token = new WeakMap();
|
|
294
|
+
_inFlight = new WeakMap();
|
|
76
295
|
|
|
77
296
|
// ../../internal/core/errors/authyonError.ts
|
|
78
297
|
var ErrorCodes = {
|
|
@@ -86,16 +305,45 @@ var ErrorCodes = {
|
|
|
86
305
|
EmailTaken: "user.email_taken",
|
|
87
306
|
PasswordWeak: "user.password_weak",
|
|
88
307
|
PasswordPwned: "user.password_pwned",
|
|
89
|
-
RateLimited: "rate_limited"
|
|
308
|
+
RateLimited: "rate_limited",
|
|
309
|
+
StepUpRequired: "step_up_required",
|
|
310
|
+
InvalidSecondFactorCode: "invalid_code",
|
|
311
|
+
VerificationAttemptsExhausted: "verification_attempts_exhausted",
|
|
312
|
+
MethodNotEnrolled: "method_not_enrolled",
|
|
313
|
+
InvalidApprovalState: "invalid_authorization_state",
|
|
314
|
+
PayloadMismatch: "payload_mismatch",
|
|
315
|
+
ApprovalNotConsumable: "authorization_not_consumable",
|
|
316
|
+
IdempotencyConflict: "idempotency_conflict",
|
|
317
|
+
UserDisabled: "user_disabled",
|
|
318
|
+
UserNotFound: "user_not_found",
|
|
319
|
+
TemporarilyUnavailable: "temporarily_unavailable",
|
|
320
|
+
InsufficientScope: "insufficient_scope",
|
|
321
|
+
CredentialThrottled: "credential_throttled",
|
|
322
|
+
ApprovalNotFound: "authorization_not_found",
|
|
323
|
+
SessionRevoked: "session_revoked",
|
|
324
|
+
TwoFactorTooManyFailures: "user.2fa.too_many_failures"
|
|
90
325
|
};
|
|
326
|
+
var STANDARD_ERROR_FIELDS = /* @__PURE__ */ new Set([
|
|
327
|
+
"type",
|
|
328
|
+
"status",
|
|
329
|
+
"title",
|
|
330
|
+
"detail",
|
|
331
|
+
"code",
|
|
332
|
+
"error",
|
|
333
|
+
"error_description"
|
|
334
|
+
]);
|
|
91
335
|
var AuthyonError = class extends Error {
|
|
92
336
|
constructor(status, body, options = {}) {
|
|
93
|
-
|
|
337
|
+
const detail = body.detail ?? body.error_description;
|
|
338
|
+
super(detail ?? body.title ?? `Authyon request failed with status ${status}`);
|
|
94
339
|
this.name = "AuthyonError";
|
|
95
340
|
this.status = status;
|
|
96
|
-
this.code = body.code ?? "unknown";
|
|
341
|
+
this.code = body.code ?? body.error ?? "unknown";
|
|
97
342
|
this.title = body.title ?? "Error";
|
|
98
|
-
this.detail =
|
|
343
|
+
this.detail = detail;
|
|
344
|
+
this.extensions = Object.fromEntries(
|
|
345
|
+
Object.entries(body).filter(([key]) => !STANDARD_ERROR_FIELDS.has(key))
|
|
346
|
+
);
|
|
99
347
|
this.requestId = options.requestId;
|
|
100
348
|
this.retryAfter = options.retryAfter;
|
|
101
349
|
this.cause = options.cause;
|
|
@@ -145,6 +393,7 @@ var AuthyonError = class extends Error {
|
|
|
145
393
|
};
|
|
146
394
|
function classifyError(status, code) {
|
|
147
395
|
if (code === ErrorCodes.NetworkError) return "network";
|
|
396
|
+
if (code === ErrorCodes.StepUpRequired) return "authentication";
|
|
148
397
|
if (code === ErrorCodes.Timeout || status === 408) return "timeout";
|
|
149
398
|
if (code === ErrorCodes.RateLimited || status === 429) return "rate_limit";
|
|
150
399
|
if (code === ErrorCodes.EmailTaken || status === 409) return "conflict";
|
|
@@ -183,14 +432,14 @@ function isRetryable(category) {
|
|
|
183
432
|
}
|
|
184
433
|
|
|
185
434
|
// ../../internal/core/http/query.ts
|
|
186
|
-
function appendQuery(
|
|
187
|
-
if (!params) return
|
|
435
|
+
function appendQuery(path2, params) {
|
|
436
|
+
if (!params) return path2;
|
|
188
437
|
const query = new URLSearchParams();
|
|
189
438
|
for (const [key, value2] of Object.entries(params)) {
|
|
190
439
|
if (value2 !== void 0) query.set(key, String(value2));
|
|
191
440
|
}
|
|
192
441
|
const value = query.toString();
|
|
193
|
-
return value ? `${
|
|
442
|
+
return value ? `${path2}${path2.includes("?") ? "&" : "?"}${value}` : path2;
|
|
194
443
|
}
|
|
195
444
|
|
|
196
445
|
// ../../internal/core/http/httpAdapter.ts
|
|
@@ -203,7 +452,8 @@ var FetchHttpAdapter = class {
|
|
|
203
452
|
method: request.method,
|
|
204
453
|
headers: request.headers,
|
|
205
454
|
body: request.body,
|
|
206
|
-
signal: request.signal
|
|
455
|
+
signal: request.signal,
|
|
456
|
+
redirect: "error"
|
|
207
457
|
});
|
|
208
458
|
}
|
|
209
459
|
};
|
|
@@ -255,6 +505,9 @@ function defaultHttpLogger(event) {
|
|
|
255
505
|
}
|
|
256
506
|
function sanitizeUrl(value) {
|
|
257
507
|
const url = new URL(value);
|
|
508
|
+
url.username = "";
|
|
509
|
+
url.password = "";
|
|
510
|
+
url.hash = "";
|
|
258
511
|
const queryKeys = /* @__PURE__ */ new Set();
|
|
259
512
|
url.searchParams.forEach((_value, key) => queryKeys.add(key));
|
|
260
513
|
url.search = queryKeys.size > 0 ? [...queryKeys].map((key) => `${encodeURIComponent(key)}=REDACTED`).join("&") : "";
|
|
@@ -284,12 +537,12 @@ function createSharedTransport(options) {
|
|
|
284
537
|
const httpAdapter = options.httpLogger ? new LoggingHttpAdapter(baseAdapter, options.httpLogger) : baseAdapter;
|
|
285
538
|
return {
|
|
286
539
|
baseUrl,
|
|
287
|
-
async request(
|
|
540
|
+
async request(path2, init) {
|
|
288
541
|
const controller = new AbortController();
|
|
289
542
|
const timeout = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
|
|
290
543
|
try {
|
|
291
544
|
return await httpAdapter.request({
|
|
292
|
-
url: `${baseUrl}${
|
|
545
|
+
url: `${baseUrl}${path2}`,
|
|
293
546
|
method: init.method ?? "GET",
|
|
294
547
|
headers: normalizeHeaders(init.headers),
|
|
295
548
|
body: init.body,
|
|
@@ -347,11 +600,11 @@ var JsonHttpClient = class {
|
|
|
347
600
|
constructor(transport) {
|
|
348
601
|
this.transport = transport;
|
|
349
602
|
}
|
|
350
|
-
async send(
|
|
603
|
+
async send(path2, options = {}) {
|
|
351
604
|
const headers = { ...options.headers };
|
|
352
605
|
if (options.body !== void 0) headers["Content-Type"] = "application/json";
|
|
353
606
|
try {
|
|
354
|
-
return await this.transport.request(appendQuery(
|
|
607
|
+
return await this.transport.request(appendQuery(path2, options.query), {
|
|
355
608
|
method: options.method ?? "GET",
|
|
356
609
|
headers,
|
|
357
610
|
body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
|
|
@@ -365,7 +618,9 @@ var JsonHttpClient = class {
|
|
|
365
618
|
if (!response.ok) {
|
|
366
619
|
let body = {};
|
|
367
620
|
try {
|
|
368
|
-
|
|
621
|
+
const parsed = await response.json();
|
|
622
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
623
|
+
body = parsed;
|
|
369
624
|
} catch {
|
|
370
625
|
}
|
|
371
626
|
throw new AuthyonError(response.status, body, responseMetadata(response));
|
|
@@ -373,8 +628,8 @@ var JsonHttpClient = class {
|
|
|
373
628
|
if (response.status === 204) return void 0;
|
|
374
629
|
return await response.json();
|
|
375
630
|
}
|
|
376
|
-
async request(
|
|
377
|
-
return this.parse(await this.send(
|
|
631
|
+
async request(path2, options = {}) {
|
|
632
|
+
return this.parse(await this.send(path2, options));
|
|
378
633
|
}
|
|
379
634
|
};
|
|
380
635
|
|
|
@@ -536,8 +791,10 @@ function isRecord(value) {
|
|
|
536
791
|
}
|
|
537
792
|
|
|
538
793
|
// src/client/authyonServerClient.ts
|
|
794
|
+
var _clientSecret;
|
|
539
795
|
var AuthyonServerClient = class {
|
|
540
796
|
constructor(options = {}) {
|
|
797
|
+
__privateAdd(this, _clientSecret);
|
|
541
798
|
// ── Discovery ──────────────────────────────────────────────────────────────
|
|
542
799
|
this.discovery = {
|
|
543
800
|
/** GET /.well-known/jwks.json — the environment's public signing keys. */
|
|
@@ -571,6 +828,14 @@ var AuthyonServerClient = class {
|
|
|
571
828
|
client_id: credentials.clientId,
|
|
572
829
|
client_secret: credentials.clientSecret
|
|
573
830
|
}
|
|
831
|
+
}),
|
|
832
|
+
/** POST /tenant/auth/validate — validates an already-minted tenant-client bearer token. */
|
|
833
|
+
validate: (accessToken, context) => this.request("/tenant/auth/validate", {
|
|
834
|
+
method: "POST",
|
|
835
|
+
headers: {
|
|
836
|
+
...clientIpHeaders(context),
|
|
837
|
+
Authorization: `Bearer ${accessToken}`
|
|
838
|
+
}
|
|
574
839
|
})
|
|
575
840
|
};
|
|
576
841
|
// ── Environment management (users, tenants, roles, permissions, audit) ───
|
|
@@ -616,6 +881,39 @@ var AuthyonServerClient = class {
|
|
|
616
881
|
envBearer: true,
|
|
617
882
|
body: { newPassword, reason }
|
|
618
883
|
}),
|
|
884
|
+
/**
|
|
885
|
+
* POST /env/password-resets — issues a password-reset token for the
|
|
886
|
+
* account with this address and returns it, so your application can
|
|
887
|
+
* deliver it in its own e-mail instead of Authyon's. Needs the
|
|
888
|
+
* `authyon:users:password` scope. Unlike the public forgot-password
|
|
889
|
+
* request, an unknown address rejects with `user.not_found` (404).
|
|
890
|
+
* A previous live token for the same account stops working.
|
|
891
|
+
*/
|
|
892
|
+
issuePasswordReset: (input) => this.request("/env/password-resets", {
|
|
893
|
+
method: "POST",
|
|
894
|
+
envBearer: true,
|
|
895
|
+
body: { ...input, sendEmail: input.sendEmail ?? false }
|
|
896
|
+
}),
|
|
897
|
+
/**
|
|
898
|
+
* POST /auth/password-reset/validate — for a backend hosting the reset
|
|
899
|
+
* page: checks a token without spending it. Public, needs only the
|
|
900
|
+
* `envKey`. Rejects with `user.password_reset.invalid` (401) when the
|
|
901
|
+
* token is unknown, used or expired.
|
|
902
|
+
*/
|
|
903
|
+
validatePasswordReset: (token) => this.request("/auth/password-reset/validate", { method: "POST", body: { token } }),
|
|
904
|
+
/**
|
|
905
|
+
* POST /auth/password-reset/confirm — redeems the token and sets the new
|
|
906
|
+
* password (every session of the account is signed out). Public, needs
|
|
907
|
+
* only the `envKey`. Resolves with the `redirectUri` the reset was issued
|
|
908
|
+
* with, or `null`.
|
|
909
|
+
*/
|
|
910
|
+
confirmPasswordReset: async (token, newPassword) => {
|
|
911
|
+
const done = await this.request(
|
|
912
|
+
"/auth/password-reset/confirm",
|
|
913
|
+
{ method: "POST", body: { token, newPassword } }
|
|
914
|
+
);
|
|
915
|
+
return { redirectUri: done?.redirectUri ?? null };
|
|
916
|
+
},
|
|
619
917
|
/** POST /env/users/{userId}/sessions/revoke-all — revokes every session the user has. */
|
|
620
918
|
revokeSessions: (userId, reason) => this.request(`/env/users/${encodeURIComponent(userId)}/sessions/revoke-all`, {
|
|
621
919
|
method: "POST",
|
|
@@ -664,7 +962,63 @@ var AuthyonServerClient = class {
|
|
|
664
962
|
)
|
|
665
963
|
},
|
|
666
964
|
tenants: {
|
|
667
|
-
|
|
965
|
+
credentials: {
|
|
966
|
+
/** GET /env/tenants/{tenantId}/credentials. Returns metadata only, including revoked credentials. */
|
|
967
|
+
list: (tenantId) => allCredentials(
|
|
968
|
+
(path2, options) => this.request(path2, { ...options, envBearer: true }),
|
|
969
|
+
`/env/tenants/${segment(tenantId)}/credentials`
|
|
970
|
+
),
|
|
971
|
+
/** Paginated searchable listing. Prefer this over loading every credential. */
|
|
972
|
+
listPage: (tenantId, options = {}) => credentialPage(
|
|
973
|
+
(path2, requestOptions) => this.request(path2, { ...requestOptions, envBearer: true }),
|
|
974
|
+
`/env/tenants/${segment(tenantId)}/credentials`,
|
|
975
|
+
options
|
|
976
|
+
),
|
|
977
|
+
get: async (tenantId, credentialId) => credentialDetail(
|
|
978
|
+
await this.request(
|
|
979
|
+
`/env/tenants/${segment(tenantId)}/credentials/${segment(credentialId)}`,
|
|
980
|
+
{ envBearer: true }
|
|
981
|
+
)
|
|
982
|
+
),
|
|
983
|
+
/** Explicit scopes/permissions; only issuance returns the client secret. */
|
|
984
|
+
create: (tenantId, input) => this.request(`/env/tenants/${segment(tenantId)}/credentials`, {
|
|
985
|
+
method: "POST",
|
|
986
|
+
envBearer: true,
|
|
987
|
+
body: {
|
|
988
|
+
description: input.description,
|
|
989
|
+
...lifetimeBody(input),
|
|
990
|
+
...permissionsBody(input)
|
|
991
|
+
}
|
|
992
|
+
}),
|
|
993
|
+
/** Returns a new secret once while keeping the same client id. */
|
|
994
|
+
rotate: (tenantId, credentialId) => this.request(
|
|
995
|
+
`/env/tenants/${segment(tenantId)}/credentials/${segment(credentialId)}/rotate`,
|
|
996
|
+
{ method: "POST", envBearer: true }
|
|
997
|
+
),
|
|
998
|
+
updatePermissions: (tenantId, credentialId, input) => this.request(
|
|
999
|
+
`/env/tenants/${segment(tenantId)}/credentials/${segment(credentialId)}/permissions`,
|
|
1000
|
+
{ method: "PUT", envBearer: true, body: permissionsBody(input) }
|
|
1001
|
+
),
|
|
1002
|
+
updateScopes: (tenantId, credentialId, scopes) => this.environment.tenants.credentials.updatePermissions(tenantId, credentialId, {
|
|
1003
|
+
scopes
|
|
1004
|
+
}),
|
|
1005
|
+
revoke: (tenantId, credentialId) => this.request(`/env/tenants/${segment(tenantId)}/credentials/${segment(credentialId)}`, {
|
|
1006
|
+
method: "DELETE",
|
|
1007
|
+
envBearer: true
|
|
1008
|
+
})
|
|
1009
|
+
},
|
|
1010
|
+
/**
|
|
1011
|
+
* GET /env/tenants — one page of the environment's tenants.
|
|
1012
|
+
*
|
|
1013
|
+
* `search` matches, case-insensitively and anywhere in the value, the
|
|
1014
|
+
* name, the slug, the description and the raw text of both metadata
|
|
1015
|
+
* documents — so a tenant filed with `{"cnpj":"00.000.000/0001-00"}`
|
|
1016
|
+
* is found by the number, and `cnpj` alone finds every tenant carrying
|
|
1017
|
+
* that key. `take` defaults to 25 and is capped at 100.
|
|
1018
|
+
*
|
|
1019
|
+
* Against an Authyon API older than 2026-10-21 this route ignored
|
|
1020
|
+
* skip/take/search and answered with a bare array.
|
|
1021
|
+
*/
|
|
668
1022
|
list: (params = {}) => this.request("/env/tenants", { envBearer: true, query: params }),
|
|
669
1023
|
/** POST /env/tenants — create a tenant in the environment. */
|
|
670
1024
|
create: (input) => this.request("/env/tenants", { method: "POST", envBearer: true, body: input }),
|
|
@@ -676,7 +1030,18 @@ var AuthyonServerClient = class {
|
|
|
676
1030
|
envBearer: true,
|
|
677
1031
|
body: input
|
|
678
1032
|
}),
|
|
679
|
-
/**
|
|
1033
|
+
/** PUT /env/tenants/{tenantId}/metadata - replace public and private metadata. */
|
|
1034
|
+
updateMetadata: (tenantId, input) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/metadata`, {
|
|
1035
|
+
method: "PUT",
|
|
1036
|
+
envBearer: true,
|
|
1037
|
+
body: input
|
|
1038
|
+
}),
|
|
1039
|
+
/** POST /env/tenants/{tenantId}/disable — disable without removing tenant data. */
|
|
1040
|
+
disable: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/disable`, {
|
|
1041
|
+
method: "POST",
|
|
1042
|
+
envBearer: true
|
|
1043
|
+
}),
|
|
1044
|
+
/** DELETE /env/tenants/{tenantId} — logically delete (disable) a tenant. */
|
|
680
1045
|
delete: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}`, {
|
|
681
1046
|
method: "DELETE",
|
|
682
1047
|
envBearer: true
|
|
@@ -684,10 +1049,16 @@ var AuthyonServerClient = class {
|
|
|
684
1049
|
members: {
|
|
685
1050
|
/**
|
|
686
1051
|
* GET /env/tenants/{tenantId}/members — a tenant's members as full
|
|
687
|
-
* user records
|
|
688
|
-
* `
|
|
689
|
-
*
|
|
690
|
-
*
|
|
1052
|
+
* user records, with the same shape `environment.users.list()` items
|
|
1053
|
+
* have, plus `tenantRoles` and `tenantPermissions` scoped to this
|
|
1054
|
+
* tenant.
|
|
1055
|
+
*
|
|
1056
|
+
* Paged like every other list: the `Paged<T>` envelope carries
|
|
1057
|
+
* `total` and `hasNext`, `take` defaults to 50 and is capped at 100.
|
|
1058
|
+
* Up to the Authyon API of 2026-10-21 this one route answered with a
|
|
1059
|
+
* bare array, so a caller had to page until a short page came back;
|
|
1060
|
+
* against such an API `data` is undefined and the array is the body
|
|
1061
|
+
* itself.
|
|
691
1062
|
*/
|
|
692
1063
|
list: (tenantId, params = {}) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/members`, {
|
|
693
1064
|
envBearer: true,
|
|
@@ -725,6 +1096,61 @@ var AuthyonServerClient = class {
|
|
|
725
1096
|
{ method: "DELETE", envBearer: true }
|
|
726
1097
|
)
|
|
727
1098
|
},
|
|
1099
|
+
/**
|
|
1100
|
+
* Invites by e-mail or by link — for addresses that may not have an
|
|
1101
|
+
* account yet; accepting creates it. Managing them needs the
|
|
1102
|
+
* credential's `authyon:users:write` scope (listing, `authyon:tenants:read`).
|
|
1103
|
+
*/
|
|
1104
|
+
invites: {
|
|
1105
|
+
/** GET /env/tenants/{tenantId}/invites — every invite with its status, newest first. */
|
|
1106
|
+
list: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/invites`, { envBearer: true }),
|
|
1107
|
+
/**
|
|
1108
|
+
* POST /env/tenants/{tenantId}/invites — invite an address. Always
|
|
1109
|
+
* returns `acceptUrl`; with `sendEmail: false` Authyon does not mail
|
|
1110
|
+
* it and you deliver the link yourself. Valid 7 days, single use, and
|
|
1111
|
+
* not readable again later.
|
|
1112
|
+
*/
|
|
1113
|
+
create: (tenantId, input) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/invites`, {
|
|
1114
|
+
method: "POST",
|
|
1115
|
+
envBearer: true,
|
|
1116
|
+
body: {
|
|
1117
|
+
email: input.email,
|
|
1118
|
+
roles: input.roles,
|
|
1119
|
+
sendEmail: input.sendEmail ?? true,
|
|
1120
|
+
redirectUri: input.redirectUri
|
|
1121
|
+
}
|
|
1122
|
+
}),
|
|
1123
|
+
/** POST .../invites/{inviteId}/resend — new link (the previous one stops working), mailed unless `sendEmail` is `false`. */
|
|
1124
|
+
resend: (tenantId, inviteId, input = {}) => this.request(
|
|
1125
|
+
`/env/tenants/${encodeURIComponent(tenantId)}/invites/${encodeURIComponent(inviteId)}/resend`,
|
|
1126
|
+
{ method: "POST", envBearer: true, body: { sendEmail: input.sendEmail ?? true } }
|
|
1127
|
+
),
|
|
1128
|
+
/** DELETE /env/tenants/{tenantId}/invites/{inviteId} — revoke a pending invite. */
|
|
1129
|
+
revoke: (tenantId, inviteId) => this.request(
|
|
1130
|
+
`/env/tenants/${encodeURIComponent(tenantId)}/invites/${encodeURIComponent(inviteId)}`,
|
|
1131
|
+
{ method: "DELETE", envBearer: true }
|
|
1132
|
+
),
|
|
1133
|
+
/**
|
|
1134
|
+
* POST /auth/tenant-invites/preview — for a backend hosting the accept
|
|
1135
|
+
* page. Public: needs only `envKey`, the token is the credential.
|
|
1136
|
+
*/
|
|
1137
|
+
preview: (token) => this.request("/auth/tenant-invites/preview", { method: "POST", body: { token } }),
|
|
1138
|
+
/**
|
|
1139
|
+
* Joins the tenant; for an address with no account, also creates it
|
|
1140
|
+
* (password required) with the e-mail already confirmed. Does not
|
|
1141
|
+
* sign the user in.
|
|
1142
|
+
*
|
|
1143
|
+
* Without metadata: POST /auth/tenant-invites/accept — public, needs
|
|
1144
|
+
* only `envKey`. With `publicMetadata`/`privateMetadata`: POST
|
|
1145
|
+
* /env/tenant-invites/accept with the environment credential
|
|
1146
|
+
* (`authyon:users:write`), since only your backend may set them.
|
|
1147
|
+
*/
|
|
1148
|
+
accept: (input) => input.publicMetadata !== void 0 || input.privateMetadata !== void 0 ? this.request("/env/tenant-invites/accept", {
|
|
1149
|
+
method: "POST",
|
|
1150
|
+
envBearer: true,
|
|
1151
|
+
body: input
|
|
1152
|
+
}) : this.request("/auth/tenant-invites/accept", { method: "POST", body: input })
|
|
1153
|
+
},
|
|
728
1154
|
roles: {
|
|
729
1155
|
/** GET /env/tenants/{tenantId}/roles — list a tenant's roles. */
|
|
730
1156
|
list: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/roles`, { envBearer: true }),
|
|
@@ -794,6 +1220,60 @@ var AuthyonServerClient = class {
|
|
|
794
1220
|
loginActivity: (params = {}) => this.request("/env/audit/login-activity", { envBearer: true, query: params })
|
|
795
1221
|
}
|
|
796
1222
|
};
|
|
1223
|
+
// ── Security: one-time codes and approvals ───────────────────────────────
|
|
1224
|
+
//
|
|
1225
|
+
// Everything here runs with the environment credential: `otp` needs the
|
|
1226
|
+
// `authyon:otp:verify` scope, `approvals` needs `authyon:financial:authorize`.
|
|
1227
|
+
// Keep those credentials on the server.
|
|
1228
|
+
this.security = {
|
|
1229
|
+
otp: {
|
|
1230
|
+
/**
|
|
1231
|
+
* POST /env/users/{userId}/otp/verify — checks the user's authenticator
|
|
1232
|
+
* app (TOTP) code. A wrong code returns `{ valid: false }` instead of
|
|
1233
|
+
* throwing. Each code is accepted once; 3 wrong codes in a row sign the
|
|
1234
|
+
* user out everywhere (`sessionsRevoked: true`); 5 lock the check for 15
|
|
1235
|
+
* minutes (`rate_limited`). More than 50 wrong codes in 10 minutes from the
|
|
1236
|
+
* same credential pause it (`credential_throttled`). Needs `authyon:otp:verify`.
|
|
1237
|
+
*/
|
|
1238
|
+
check: (userId, code) => this.request(`/env/users/${segment(userId)}/otp/verify`, {
|
|
1239
|
+
method: "POST",
|
|
1240
|
+
envBearer: true,
|
|
1241
|
+
body: { code }
|
|
1242
|
+
})
|
|
1243
|
+
},
|
|
1244
|
+
/**
|
|
1245
|
+
* The customer approves a payload with their authenticator code or
|
|
1246
|
+
* passkey, once.
|
|
1247
|
+
*
|
|
1248
|
+
* 1. `create()` the approval with the payload and the customer.
|
|
1249
|
+
* 2. The customer confirms it (`@authyon/auth` → `security.approvals.confirm`).
|
|
1250
|
+
* 3. `consume()` it right before executing, and execute the returned payload.
|
|
1251
|
+
*/
|
|
1252
|
+
approvals: {
|
|
1253
|
+
/** POST /env/authorizations — registers a payload awaiting the customer's approval. */
|
|
1254
|
+
create: (input) => {
|
|
1255
|
+
const { idempotencyKey, ...body } = input;
|
|
1256
|
+
return this.request("/env/authorizations", {
|
|
1257
|
+
method: "POST",
|
|
1258
|
+
envBearer: true,
|
|
1259
|
+
headers: idempotencyKey ? { "Idempotency-Key": idempotencyKey } : void 0,
|
|
1260
|
+
body
|
|
1261
|
+
});
|
|
1262
|
+
},
|
|
1263
|
+
/** GET /env/authorizations/{id} — current status and, once approved, the assurance evidence. */
|
|
1264
|
+
get: (id) => this.request(`/env/authorizations/${segment(id)}`, { envBearer: true }),
|
|
1265
|
+
/**
|
|
1266
|
+
* POST /env/authorizations/{id}/consume — redeems an approved approval
|
|
1267
|
+
* exactly once and returns the approved payload. Pass `payload` to have
|
|
1268
|
+
* the API check it matches (`payload_mismatch` otherwise).
|
|
1269
|
+
*/
|
|
1270
|
+
consume: (id, payload) => this.request(`/env/authorizations/${segment(id)}/consume`, {
|
|
1271
|
+
method: "POST",
|
|
1272
|
+
envBearer: true,
|
|
1273
|
+
body: payload === void 0 ? void 0 : { payload }
|
|
1274
|
+
})
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
797
1277
|
// ── Generic permission grants ─────────────────────────────────────────────
|
|
798
1278
|
this.permissions = {
|
|
799
1279
|
/** POST /users/{userId}/permissions — grant a permission directly to a user. */
|
|
@@ -810,7 +1290,8 @@ var AuthyonServerClient = class {
|
|
|
810
1290
|
};
|
|
811
1291
|
this.envKey = options.envKey;
|
|
812
1292
|
this.clientId = options.clientId;
|
|
813
|
-
this
|
|
1293
|
+
__privateSet(this, _clientSecret, options.clientSecret);
|
|
1294
|
+
this.clientIp = options.clientIp;
|
|
814
1295
|
this.transport = createSharedTransport({
|
|
815
1296
|
baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
|
|
816
1297
|
allowInsecureHttp: options.allowInsecureHttp,
|
|
@@ -821,14 +1302,14 @@ var AuthyonServerClient = class {
|
|
|
821
1302
|
});
|
|
822
1303
|
this.http = new JsonHttpClient(this.transport);
|
|
823
1304
|
this.environmentTokenProvider = new ExpiringTokenProvider(async () => {
|
|
824
|
-
if (!this.clientId || !this
|
|
1305
|
+
if (!this.clientId || !__privateGet(this, _clientSecret)) {
|
|
825
1306
|
throw new Error(
|
|
826
1307
|
"Authyon: `clientId`/`clientSecret` are required for environment management calls (mint a pair in the console, under the environment's OAuth clients)."
|
|
827
1308
|
);
|
|
828
1309
|
}
|
|
829
1310
|
const token = await this.environmentAuth.token({
|
|
830
1311
|
clientId: this.clientId,
|
|
831
|
-
clientSecret: this
|
|
1312
|
+
clientSecret: __privateGet(this, _clientSecret)
|
|
832
1313
|
});
|
|
833
1314
|
return {
|
|
834
1315
|
accessToken: token.access_token,
|
|
@@ -836,13 +1317,16 @@ var AuthyonServerClient = class {
|
|
|
836
1317
|
};
|
|
837
1318
|
});
|
|
838
1319
|
}
|
|
839
|
-
async request(
|
|
840
|
-
const headers = {
|
|
1320
|
+
async request(path2, options = {}) {
|
|
1321
|
+
const headers = {
|
|
1322
|
+
...clientIpHeaders({ clientIp: this.clientIp }),
|
|
1323
|
+
...options.headers
|
|
1324
|
+
};
|
|
841
1325
|
if (options.envBearer) {
|
|
842
1326
|
headers.Authorization = `Bearer ${await this.getEnvironmentAccessToken()}`;
|
|
843
1327
|
}
|
|
844
1328
|
if (this.envKey) headers["X-Authyon-Environment"] = this.envKey;
|
|
845
|
-
return this.http.request(
|
|
1329
|
+
return this.http.request(path2, { ...options, headers });
|
|
846
1330
|
}
|
|
847
1331
|
/** Mints (and caches) an environment access token from `clientId`/`clientSecret`. */
|
|
848
1332
|
async getEnvironmentAccessToken() {
|
|
@@ -897,26 +1381,52 @@ var AuthyonServerClient = class {
|
|
|
897
1381
|
tenant(credentials) {
|
|
898
1382
|
return new TenantScopedClient(this, credentials);
|
|
899
1383
|
}
|
|
1384
|
+
/** Uses an end-user token; the configured environment key selects the environment. */
|
|
1385
|
+
user(token) {
|
|
1386
|
+
return new UserScopedClient((path2, options) => this.request(path2, options), token);
|
|
1387
|
+
}
|
|
900
1388
|
/** @internal used by {@link TenantScopedClient} to share the base URL/fetch/error handling. */
|
|
901
|
-
_requestAsTenant(accessToken,
|
|
902
|
-
return this.request(
|
|
1389
|
+
_requestAsTenant(accessToken, path2, options = {}) {
|
|
1390
|
+
return this.request(path2, {
|
|
903
1391
|
...options,
|
|
904
1392
|
headers: { ...options.headers, Authorization: `Bearer ${accessToken}` },
|
|
905
1393
|
envBearer: false
|
|
906
1394
|
});
|
|
907
1395
|
}
|
|
908
1396
|
};
|
|
1397
|
+
_clientSecret = new WeakMap();
|
|
909
1398
|
var TenantScopedClient = class {
|
|
910
1399
|
constructor(server, credentials) {
|
|
1400
|
+
/** Invites to the token's tenant, by e-mail or by link. */
|
|
1401
|
+
this.invites = {
|
|
1402
|
+
/** GET /tenant/invites — every invite with its status, newest first. */
|
|
1403
|
+
list: () => this.request("/tenant/invites"),
|
|
1404
|
+
/** POST /tenant/invites — always returns `acceptUrl`; `sendEmail: false` skips the e-mail. */
|
|
1405
|
+
create: (input) => this.request("/tenant/invites", {
|
|
1406
|
+
method: "POST",
|
|
1407
|
+
body: {
|
|
1408
|
+
email: input.email,
|
|
1409
|
+
roles: input.roles,
|
|
1410
|
+
sendEmail: input.sendEmail ?? true,
|
|
1411
|
+
redirectUri: input.redirectUri
|
|
1412
|
+
}
|
|
1413
|
+
}),
|
|
1414
|
+
/** POST /tenant/invites/{inviteId}/resend — new link (the previous one stops working), mailed unless `sendEmail` is `false`. */
|
|
1415
|
+
resend: (inviteId, input = {}) => this.request(`/tenant/invites/${encodeURIComponent(inviteId)}/resend`, {
|
|
1416
|
+
method: "POST",
|
|
1417
|
+
body: { sendEmail: input.sendEmail ?? true }
|
|
1418
|
+
}),
|
|
1419
|
+
/** DELETE /tenant/invites/{inviteId} — revoke a pending invite. */
|
|
1420
|
+
revoke: (inviteId) => this.request(`/tenant/invites/${encodeURIComponent(inviteId)}`, { method: "DELETE" })
|
|
1421
|
+
};
|
|
911
1422
|
this.members = {
|
|
912
1423
|
/**
|
|
913
1424
|
* GET /tenant/members — members of the token's tenant.
|
|
914
1425
|
*
|
|
915
|
-
*
|
|
916
|
-
*
|
|
917
|
-
*
|
|
918
|
-
*
|
|
919
|
-
* to `/auth/tenants/{id}/members`'s paginated, lightweight one.
|
|
1426
|
+
* Same handler as `/env/tenants/{id}/members` (the tenant comes from the
|
|
1427
|
+
* token instead of the path), so the same `Paged<T>` envelope of full
|
|
1428
|
+
* user records, `take` defaulting to 50 and capped at 100 — as opposed
|
|
1429
|
+
* to `/auth/tenants/{id}/members`'s lightweight member shape.
|
|
920
1430
|
*/
|
|
921
1431
|
list: (params = {}) => this.request("/tenant/members", { query: params }),
|
|
922
1432
|
/** POST /tenant/members — add a member to the token's tenant. */
|
|
@@ -975,8 +1485,12 @@ var TenantScopedClient = class {
|
|
|
975
1485
|
async getAccessToken() {
|
|
976
1486
|
return this.tokenProvider.getAccessToken();
|
|
977
1487
|
}
|
|
978
|
-
async request(
|
|
979
|
-
return this.server._requestAsTenant(await this.getAccessToken(),
|
|
1488
|
+
async request(path2, options = {}) {
|
|
1489
|
+
return this.server._requestAsTenant(await this.getAccessToken(), path2, options);
|
|
1490
|
+
}
|
|
1491
|
+
/** POST /tenant/auth/validate — validates this tenant client's current bearer token. */
|
|
1492
|
+
async validate(context) {
|
|
1493
|
+
return this.server.tenantAuth.validate(await this.getAccessToken(), context);
|
|
980
1494
|
}
|
|
981
1495
|
};
|
|
982
1496
|
function createClient(options = {}) {
|
|
@@ -984,40 +1498,46 @@ function createClient(options = {}) {
|
|
|
984
1498
|
}
|
|
985
1499
|
|
|
986
1500
|
// src/client/authyonServerClientBuilder.ts
|
|
1501
|
+
var _options;
|
|
987
1502
|
var AuthyonServerClientBuilder = class {
|
|
988
1503
|
constructor() {
|
|
989
|
-
this
|
|
1504
|
+
__privateAdd(this, _options, {});
|
|
990
1505
|
}
|
|
991
1506
|
withEnvironmentKey(envKey) {
|
|
992
|
-
this.
|
|
1507
|
+
__privateGet(this, _options).envKey = envKey;
|
|
993
1508
|
return this;
|
|
994
1509
|
}
|
|
995
1510
|
withEnvironmentCredentials(clientId, clientSecret) {
|
|
996
|
-
this.
|
|
997
|
-
this.
|
|
1511
|
+
__privateGet(this, _options).clientId = clientId;
|
|
1512
|
+
__privateGet(this, _options).clientSecret = clientSecret;
|
|
1513
|
+
return this;
|
|
1514
|
+
}
|
|
1515
|
+
withClientIp(clientIp) {
|
|
1516
|
+
__privateGet(this, _options).clientIp = clientIp;
|
|
998
1517
|
return this;
|
|
999
1518
|
}
|
|
1000
1519
|
withBaseUrl(baseUrl, allowInsecureHttp = false) {
|
|
1001
|
-
this.
|
|
1002
|
-
this.
|
|
1520
|
+
__privateGet(this, _options).baseUrl = baseUrl;
|
|
1521
|
+
__privateGet(this, _options).allowInsecureHttp = allowInsecureHttp;
|
|
1003
1522
|
return this;
|
|
1004
1523
|
}
|
|
1005
1524
|
withTimeout(timeoutMs) {
|
|
1006
|
-
this.
|
|
1525
|
+
__privateGet(this, _options).timeoutMs = timeoutMs;
|
|
1007
1526
|
return this;
|
|
1008
1527
|
}
|
|
1009
1528
|
withHttpAdapter(httpAdapter) {
|
|
1010
|
-
this.
|
|
1529
|
+
__privateGet(this, _options).httpAdapter = httpAdapter;
|
|
1011
1530
|
return this;
|
|
1012
1531
|
}
|
|
1013
1532
|
withHttpLogger(httpLogger) {
|
|
1014
|
-
this.
|
|
1533
|
+
__privateGet(this, _options).httpLogger = httpLogger;
|
|
1015
1534
|
return this;
|
|
1016
1535
|
}
|
|
1017
1536
|
build() {
|
|
1018
|
-
return new AuthyonServerClient({ ...this
|
|
1537
|
+
return new AuthyonServerClient({ ...__privateGet(this, _options) });
|
|
1019
1538
|
}
|
|
1020
1539
|
};
|
|
1540
|
+
_options = new WeakMap();
|
|
1021
1541
|
|
|
1022
1542
|
// ../../internal/core/authorization/ability.ts
|
|
1023
1543
|
var AuthyonAbility = class {
|
|
@@ -1089,6 +1609,11 @@ function hasPermission(source, requiredPermission) {
|
|
|
1089
1609
|
const permissionSource = isPermissionList(source) ? { permissions: source } : source;
|
|
1090
1610
|
return createAuthyonAbility(permissionSource).can(requirement.action, requirement.subject);
|
|
1091
1611
|
}
|
|
1612
|
+
function hasPermissionGroup(source, group) {
|
|
1613
|
+
const hasAll = (group.allOf ?? []).every((permission) => hasPermission(source, permission));
|
|
1614
|
+
const hasAny = !group.anyOf?.length || group.anyOf.some((permission) => hasPermission(source, permission));
|
|
1615
|
+
return hasAll && hasAny;
|
|
1616
|
+
}
|
|
1092
1617
|
function isPermissionList(source) {
|
|
1093
1618
|
return Array.isArray(source);
|
|
1094
1619
|
}
|
|
@@ -1127,25 +1652,25 @@ function matchesToken(value, expected, wildcard) {
|
|
|
1127
1652
|
function matchesSegments(pattern, value) {
|
|
1128
1653
|
const patternSegments = pattern.split(":");
|
|
1129
1654
|
const valueSegments = value.split(":");
|
|
1130
|
-
return patternSegments.length === valueSegments.length && patternSegments.every((
|
|
1655
|
+
return patternSegments.length === valueSegments.length && patternSegments.every((segment2, index) => segment2 === "*" || segment2 === valueSegments[index]);
|
|
1131
1656
|
}
|
|
1132
1657
|
function matchesField(pattern, field) {
|
|
1133
1658
|
if (pattern === "*" || pattern === field) return true;
|
|
1134
1659
|
return pattern.endsWith(".*") && field.startsWith(pattern.slice(0, -1));
|
|
1135
1660
|
}
|
|
1136
1661
|
function matchesConditions(subject, conditions) {
|
|
1137
|
-
return Object.entries(conditions).every(([
|
|
1138
|
-
if (
|
|
1662
|
+
return Object.entries(conditions).every(([path2, expected]) => {
|
|
1663
|
+
if (path2 === "$and" && Array.isArray(expected)) {
|
|
1139
1664
|
return expected.every(
|
|
1140
1665
|
(condition) => matchesConditions(subject, condition)
|
|
1141
1666
|
);
|
|
1142
1667
|
}
|
|
1143
|
-
if (
|
|
1668
|
+
if (path2 === "$or" && Array.isArray(expected)) {
|
|
1144
1669
|
return expected.some(
|
|
1145
1670
|
(condition) => matchesConditions(subject, condition)
|
|
1146
1671
|
);
|
|
1147
1672
|
}
|
|
1148
|
-
return matchesValue(readPath(subject,
|
|
1673
|
+
return matchesValue(readPath(subject, path2), expected);
|
|
1149
1674
|
});
|
|
1150
1675
|
}
|
|
1151
1676
|
function matchesValue(actual, expected) {
|
|
@@ -1177,8 +1702,8 @@ function matchesValue(actual, expected) {
|
|
|
1177
1702
|
}
|
|
1178
1703
|
});
|
|
1179
1704
|
}
|
|
1180
|
-
function readPath(value,
|
|
1181
|
-
return
|
|
1705
|
+
function readPath(value, path2) {
|
|
1706
|
+
return path2.split(".").reduce((current, part) => isRecord2(current) ? current[part] : void 0, value);
|
|
1182
1707
|
}
|
|
1183
1708
|
function defaultSubjectType(subject) {
|
|
1184
1709
|
const explicit = subject.__type ?? subject.type ?? subject.kind;
|
|
@@ -1334,6 +1859,7 @@ function unauthenticated() {
|
|
|
1334
1859
|
LoggingHttpAdapter,
|
|
1335
1860
|
TenantScopedClient,
|
|
1336
1861
|
TokenVerificationError,
|
|
1862
|
+
UserScopedClient,
|
|
1337
1863
|
authorizeRequest,
|
|
1338
1864
|
authorizeToken,
|
|
1339
1865
|
createAuthorizationErrorResponse,
|
|
@@ -1342,5 +1868,6 @@ function unauthenticated() {
|
|
|
1342
1868
|
createClient,
|
|
1343
1869
|
createExpressAuthorizationMiddleware,
|
|
1344
1870
|
hasPermission,
|
|
1871
|
+
hasPermissionGroup,
|
|
1345
1872
|
normalizeClientIp
|
|
1346
1873
|
});
|