@authyon/server 0.2.0-beta.0 → 0.2.0-beta.11
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/LICENSE +21 -0
- package/README.md +321 -2
- package/dist/authorization-CeP_GqT7.d.cts +129 -0
- package/dist/authorization-D_A6L9Ie.d.ts +129 -0
- package/dist/authyonServerClient-D-DxoL30.d.cts +898 -0
- package/dist/authyonServerClient-D-DxoL30.d.ts +898 -0
- package/dist/bff.cjs +1126 -0
- package/dist/bff.d.cts +142 -0
- package/dist/bff.d.ts +142 -0
- package/dist/bff.js +791 -0
- package/dist/browser.cjs +21 -0
- package/dist/browser.d.cts +2 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +4 -0
- package/dist/chunk-56H66KLG.js +323 -0
- package/dist/chunk-77J4NURK.js +13 -0
- package/dist/chunk-NKWQMYB3.js +485 -0
- package/dist/index.cjs +1276 -73
- package/dist/index.d.cts +58 -506
- package/dist/index.d.ts +58 -506
- package/dist/index.js +516 -85
- 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 +81 -36
package/dist/index.js
CHANGED
|
@@ -1,33 +1,314 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import {
|
|
2
|
+
AuthyonError,
|
|
3
|
+
DEFAULT_BASE_URL,
|
|
4
|
+
DEFAULT_EXPIRY_SKEW_MS,
|
|
5
|
+
ErrorCodes,
|
|
6
|
+
FetchHttpAdapter,
|
|
7
|
+
JsonHttpClient,
|
|
8
|
+
LoggingHttpAdapter,
|
|
9
|
+
createSharedTransport
|
|
10
|
+
} from "./chunk-56H66KLG.js";
|
|
11
|
+
import {
|
|
12
|
+
AuthorizationError,
|
|
13
|
+
AuthyonAbility,
|
|
14
|
+
AuthyonAbilityBuilder,
|
|
15
|
+
JwksTokenVerifier,
|
|
16
|
+
TokenVerificationError,
|
|
17
|
+
authorizeRequest,
|
|
18
|
+
authorizeToken,
|
|
19
|
+
clientIpHeaders,
|
|
20
|
+
createAuthorizationErrorResponse,
|
|
21
|
+
createAuthyonAbility,
|
|
22
|
+
createAuthyonRules,
|
|
23
|
+
createExpressAuthorizationMiddleware,
|
|
24
|
+
hasPermission,
|
|
25
|
+
hasPermissionGroup,
|
|
26
|
+
normalizeClientIp
|
|
27
|
+
} from "./chunk-NKWQMYB3.js";
|
|
28
|
+
import {
|
|
29
|
+
__privateAdd,
|
|
30
|
+
__privateGet,
|
|
31
|
+
__privateSet
|
|
32
|
+
} from "./chunk-77J4NURK.js";
|
|
33
|
+
|
|
34
|
+
// src/client/credentialManagement.ts
|
|
35
|
+
function segment(value) {
|
|
36
|
+
if (typeof value !== "string" || !value.trim() || value === "." || value === ".." || [...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)) {
|
|
37
|
+
throw new TypeError("Authyon: invalid route identifier.");
|
|
13
38
|
}
|
|
14
|
-
|
|
39
|
+
return encodeURIComponent(value);
|
|
40
|
+
}
|
|
41
|
+
function permissionsBody(input) {
|
|
42
|
+
if (input.permissions !== void 0 && input.scopes !== void 0) {
|
|
43
|
+
throw new TypeError("Authyon: supply permissions or scopes, not both.");
|
|
44
|
+
}
|
|
45
|
+
const values = input.permissions ?? input.scopes;
|
|
46
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
47
|
+
throw new TypeError("Authyon: explicit credential permissions are required.");
|
|
48
|
+
}
|
|
49
|
+
const permissions = values.map((value) => {
|
|
50
|
+
if (typeof value !== "string") throw new TypeError("Authyon: invalid permission.");
|
|
51
|
+
const normalized = value.trim().toLowerCase();
|
|
52
|
+
if (!/^(?:\*|[a-z][a-z0-9_-]*):(?:\*|[a-z][a-z0-9_-]*):(?:\*|[a-z][a-z0-9_-]*)$/u.test(normalized)) {
|
|
53
|
+
throw new TypeError("Authyon: permissions must have namespace:resource:action format.");
|
|
54
|
+
}
|
|
55
|
+
return normalized;
|
|
56
|
+
});
|
|
57
|
+
return { permissions: [...new Set(permissions)] };
|
|
58
|
+
}
|
|
59
|
+
function lifetimeBody(input) {
|
|
60
|
+
if (input.lifetimeDays === void 0 || input.lifetimeDays === null) return {};
|
|
61
|
+
if (!Number.isInteger(input.lifetimeDays) || input.lifetimeDays < 1 || input.lifetimeDays > 3650) {
|
|
62
|
+
throw new RangeError("Authyon: lifetimeDays must be between 1 and 3650.");
|
|
63
|
+
}
|
|
64
|
+
return { lifetimeDays: input.lifetimeDays };
|
|
65
|
+
}
|
|
66
|
+
function credentialQuery(options = {}) {
|
|
67
|
+
const { skip, take, search } = options;
|
|
68
|
+
if (skip !== void 0 && (!Number.isInteger(skip) || skip < 0 || skip > 2147483647))
|
|
69
|
+
throw new RangeError("Authyon: invalid skip.");
|
|
70
|
+
if (take !== void 0 && (!Number.isInteger(take) || take < 1 || take > 100))
|
|
71
|
+
throw new RangeError("Authyon: take must be between 1 and 100.");
|
|
72
|
+
if (search !== void 0 && (typeof search !== "string" || search.trim().length > 200))
|
|
73
|
+
throw new RangeError("Authyon: search must contain at most 200 characters.");
|
|
74
|
+
return { search: search?.trim() || void 0, skip, take };
|
|
75
|
+
}
|
|
76
|
+
function credentialSummary(value) {
|
|
77
|
+
return {
|
|
78
|
+
id: value.id,
|
|
79
|
+
clientId: value.clientId,
|
|
80
|
+
description: value.description,
|
|
81
|
+
createdAt: value.createdAt,
|
|
82
|
+
lastUsedAt: value.lastUsedAt,
|
|
83
|
+
revokedAt: value.revokedAt,
|
|
84
|
+
expiresAt: value.expiresAt ?? null,
|
|
85
|
+
isActive: value.isActive,
|
|
86
|
+
permissions: [...value.permissions]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function credentialDetail(value) {
|
|
90
|
+
return {
|
|
91
|
+
...credentialSummary(value),
|
|
92
|
+
createdBy: value.createdBy == null ? null : {
|
|
93
|
+
id: value.createdBy.id,
|
|
94
|
+
type: value.createdBy.type,
|
|
95
|
+
displayName: value.createdBy.displayName
|
|
96
|
+
},
|
|
97
|
+
updatedAt: value.updatedAt,
|
|
98
|
+
secretRotatedAt: value.secretRotatedAt,
|
|
99
|
+
expiresAt: value.expiresAt ?? null,
|
|
100
|
+
ageSeconds: value.ageSeconds,
|
|
101
|
+
lifetimeSeconds: value.lifetimeSeconds,
|
|
102
|
+
accessTokenLifetimeSeconds: value.accessTokenLifetimeSeconds
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function credentialPage(request, path, options = {}) {
|
|
106
|
+
const query = credentialQuery(options);
|
|
107
|
+
const page = await request(path, { query });
|
|
108
|
+
if (!page || !Array.isArray(page.data))
|
|
109
|
+
throw new TypeError("Authyon: expected a paginated credential response.");
|
|
110
|
+
return {
|
|
111
|
+
data: page.data.map(credentialSummary),
|
|
112
|
+
...page.perPage === void 0 ? {} : { perPage: page.perPage },
|
|
113
|
+
pageSize: page.pageSize,
|
|
114
|
+
total: page.total,
|
|
115
|
+
pages: page.pages,
|
|
116
|
+
hasNext: page.hasNext,
|
|
117
|
+
hasPrev: page.hasPrev
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async function allCredentials(request, path) {
|
|
121
|
+
const result = [];
|
|
122
|
+
const seen = /* @__PURE__ */ new Set();
|
|
123
|
+
let skip = 0;
|
|
124
|
+
for (let iteration = 0; iteration < 1e3; iteration++) {
|
|
125
|
+
const page = await request(
|
|
126
|
+
path,
|
|
127
|
+
iteration === 0 ? {} : { query: { skip, take: 100 } }
|
|
128
|
+
);
|
|
129
|
+
if (Array.isArray(page)) {
|
|
130
|
+
if (iteration !== 0)
|
|
131
|
+
throw new Error("Authyon: credential pagination changed during listing.");
|
|
132
|
+
return page.map(credentialSummary);
|
|
133
|
+
}
|
|
134
|
+
if (!page || !Array.isArray(page.data))
|
|
135
|
+
throw new TypeError("Authyon: invalid credential page.");
|
|
136
|
+
for (const row of page.data) {
|
|
137
|
+
if (seen.has(row.id))
|
|
138
|
+
throw new Error("Authyon: credential pages overlapped; retry the listing.");
|
|
139
|
+
seen.add(row.id);
|
|
140
|
+
result.push(credentialSummary(row));
|
|
141
|
+
}
|
|
142
|
+
if (!page.hasNext) return result;
|
|
143
|
+
if (page.data.length === 0) throw new Error("Authyon: credential pagination made no progress.");
|
|
144
|
+
skip += page.data.length;
|
|
145
|
+
}
|
|
146
|
+
throw new Error("Authyon: use listPage for more than 1000 credential pages.");
|
|
147
|
+
}
|
|
15
148
|
|
|
16
|
-
// src/client.ts
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
149
|
+
// src/client/scopedManagementClient.ts
|
|
150
|
+
function scopedRequest(request, source) {
|
|
151
|
+
return async (path, options = {}) => {
|
|
152
|
+
const token = typeof source === "function" ? await source() : source;
|
|
153
|
+
if (typeof token !== "string" || !token.trim() || /\s/u.test(token))
|
|
154
|
+
throw new TypeError("Authyon: a valid access token is required.");
|
|
155
|
+
return request(path, {
|
|
156
|
+
...options,
|
|
157
|
+
headers: { ...options.headers, Authorization: `Bearer ${token}` }
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function environmentPath(scope) {
|
|
162
|
+
return `/platform/workspaces/${segment(scope.workspaceId)}/environments/${segment(scope.environmentId)}`;
|
|
163
|
+
}
|
|
164
|
+
function tenantPath(scope) {
|
|
165
|
+
return `${environmentPath(scope)}/tenants/${segment(scope.tenantId)}`;
|
|
166
|
+
}
|
|
167
|
+
function credentialPath(scope) {
|
|
168
|
+
return `${scope.tenantId === void 0 ? environmentPath(scope) : tenantPath(scope)}/credentials`;
|
|
169
|
+
}
|
|
170
|
+
function workspacePath(id) {
|
|
171
|
+
return `/platform/workspaces/${segment(id)}`;
|
|
26
172
|
}
|
|
173
|
+
function workspaceRole(role) {
|
|
174
|
+
if (role !== "admin" && role !== "auditor")
|
|
175
|
+
throw new TypeError("Authyon: workspace role must be admin or auditor.");
|
|
176
|
+
return role;
|
|
177
|
+
}
|
|
178
|
+
var _request;
|
|
179
|
+
var PlatformScopedClient = class {
|
|
180
|
+
/** @hidden */
|
|
181
|
+
constructor(request, token) {
|
|
182
|
+
__privateAdd(this, _request);
|
|
183
|
+
this.credentials = {
|
|
184
|
+
list: (scope, options = {}) => credentialPage(__privateGet(this, _request), credentialPath(scope), options),
|
|
185
|
+
get: async (scope, credentialId) => credentialDetail(await __privateGet(this, _request).call(this, `${credentialPath(scope)}/${segment(credentialId)}`)),
|
|
186
|
+
create: (scope, input) => __privateGet(this, _request).call(this, credentialPath(scope), {
|
|
187
|
+
method: "POST",
|
|
188
|
+
body: { description: input.description, ...lifetimeBody(input), ...permissionsBody(input) }
|
|
189
|
+
}),
|
|
190
|
+
updatePermissions: (scope, credentialId, input) => __privateGet(this, _request).call(this, `${credentialPath(scope)}/${segment(credentialId)}/permissions`, {
|
|
191
|
+
method: "PUT",
|
|
192
|
+
body: permissionsBody(input)
|
|
193
|
+
}),
|
|
194
|
+
updateScopes: (scope, credentialId, scopes) => this.credentials.updatePermissions(scope, credentialId, { scopes }),
|
|
195
|
+
/** Returns a new secret once. API may require an explicit step-up first. */
|
|
196
|
+
rotate: (scope, credentialId) => __privateGet(this, _request).call(this, `${credentialPath(scope)}/${segment(credentialId)}/rotate`, { method: "POST" }),
|
|
197
|
+
revoke: (scope, credentialId) => __privateGet(this, _request).call(this, `${credentialPath(scope)}/${segment(credentialId)}`, { method: "DELETE" })
|
|
198
|
+
};
|
|
199
|
+
this.workspaces = {
|
|
200
|
+
team: (workspaceId) => __privateGet(this, _request).call(this, `${workspacePath(workspaceId)}/team`),
|
|
201
|
+
members: {
|
|
202
|
+
invite: (workspaceId, input) => __privateGet(this, _request).call(this, `${workspacePath(workspaceId)}/invites`, {
|
|
203
|
+
method: "POST",
|
|
204
|
+
body: { email: input.email, role: workspaceRole(input.role) }
|
|
205
|
+
}),
|
|
206
|
+
/** memberId comes from team.members; it is not the platform userId. */
|
|
207
|
+
remove: (workspaceId, memberId) => __privateGet(this, _request).call(this, `${workspacePath(workspaceId)}/members/${segment(memberId)}`, {
|
|
208
|
+
method: "DELETE"
|
|
209
|
+
}),
|
|
210
|
+
changeRole: (workspaceId, memberId, role) => __privateGet(this, _request).call(this, `${workspacePath(workspaceId)}/members/${segment(memberId)}`, {
|
|
211
|
+
method: "PATCH",
|
|
212
|
+
body: { role: workspaceRole(role) }
|
|
213
|
+
})
|
|
214
|
+
},
|
|
215
|
+
invites: {
|
|
216
|
+
create: (workspaceId, input) => this.workspaces.members.invite(workspaceId, input),
|
|
217
|
+
revoke: (workspaceId, inviteId) => __privateGet(this, _request).call(this, `${workspacePath(workspaceId)}/invites/${segment(inviteId)}`, {
|
|
218
|
+
method: "DELETE"
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
this.tenants = {
|
|
223
|
+
members: {
|
|
224
|
+
list: (scope, options = {}) => __privateGet(this, _request).call(this, `${tenantPath(scope)}/members`, { query: options }),
|
|
225
|
+
add: (scope, userId, roles) => __privateGet(this, _request).call(this, `${tenantPath(scope)}/members`, { method: "POST", body: { userId, roles } }),
|
|
226
|
+
remove: (scope, userId) => __privateGet(this, _request).call(this, `${tenantPath(scope)}/members/${segment(userId)}`, { method: "DELETE" })
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
this.auth = {
|
|
230
|
+
/** Explicitly confirms the current platform session; passwords are not retained. */
|
|
231
|
+
stepUp: (currentPassword) => __privateGet(this, _request).call(this, "/platform/auth/step-up", { method: "POST", body: { currentPassword } })
|
|
232
|
+
};
|
|
233
|
+
__privateSet(this, _request, scopedRequest(request, token));
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
_request = new WeakMap();
|
|
237
|
+
var _request2;
|
|
238
|
+
var UserScopedClient = class {
|
|
239
|
+
/** @hidden */
|
|
240
|
+
constructor(request, token) {
|
|
241
|
+
__privateAdd(this, _request2);
|
|
242
|
+
this.tenants = {
|
|
243
|
+
members: {
|
|
244
|
+
list: (tenantId, options = {}) => __privateGet(this, _request2).call(this, `/auth/tenants/${segment(tenantId)}/members`, { query: options }),
|
|
245
|
+
/** Adds an existing environment user by email; does not send an invitation email. */
|
|
246
|
+
invite: (tenantId, input) => __privateGet(this, _request2).call(this, `/auth/tenants/${segment(tenantId)}/members`, {
|
|
247
|
+
method: "POST",
|
|
248
|
+
body: { email: input.email, roles: input.roles }
|
|
249
|
+
}),
|
|
250
|
+
remove: (tenantId, userId) => __privateGet(this, _request2).call(this, `/auth/tenants/${segment(tenantId)}/members/${segment(userId)}`, {
|
|
251
|
+
method: "DELETE"
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
__privateSet(this, _request2, scopedRequest(request, token));
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
_request2 = new WeakMap();
|
|
259
|
+
var _request3;
|
|
260
|
+
var WorkspaceInvitesClient = class {
|
|
261
|
+
/** @hidden */
|
|
262
|
+
constructor(request) {
|
|
263
|
+
__privateAdd(this, _request3);
|
|
264
|
+
__privateSet(this, _request3, request);
|
|
265
|
+
}
|
|
266
|
+
preview(token) {
|
|
267
|
+
return __privateGet(this, _request3).call(this, `/platform/workspace-invites/${segment(token)}`);
|
|
268
|
+
}
|
|
269
|
+
accept(token, input = {}) {
|
|
270
|
+
return __privateGet(this, _request3).call(this, `/platform/workspace-invites/${segment(token)}/accept`, {
|
|
271
|
+
method: "POST",
|
|
272
|
+
body: { name: input.name, password: input.password }
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
_request3 = new WeakMap();
|
|
277
|
+
|
|
278
|
+
// ../../internal/core/auth/expiringTokenProvider.ts
|
|
279
|
+
var _token, _inFlight;
|
|
280
|
+
var ExpiringTokenProvider = class {
|
|
281
|
+
constructor(acquire, expirySkewMs = DEFAULT_EXPIRY_SKEW_MS) {
|
|
282
|
+
this.acquire = acquire;
|
|
283
|
+
this.expirySkewMs = expirySkewMs;
|
|
284
|
+
__privateAdd(this, _token, null);
|
|
285
|
+
__privateAdd(this, _inFlight, null);
|
|
286
|
+
}
|
|
287
|
+
async getAccessToken() {
|
|
288
|
+
if (__privateGet(this, _token) && Date.now() < __privateGet(this, _token).expiresAt - this.expirySkewMs) {
|
|
289
|
+
return __privateGet(this, _token).accessToken;
|
|
290
|
+
}
|
|
291
|
+
if (__privateGet(this, _inFlight)) return __privateGet(this, _inFlight);
|
|
292
|
+
__privateSet(this, _inFlight, this.acquire().then((token) => {
|
|
293
|
+
__privateSet(this, _token, token);
|
|
294
|
+
return token.accessToken;
|
|
295
|
+
}).finally(() => {
|
|
296
|
+
__privateSet(this, _inFlight, null);
|
|
297
|
+
}));
|
|
298
|
+
return __privateGet(this, _inFlight);
|
|
299
|
+
}
|
|
300
|
+
clear() {
|
|
301
|
+
__privateSet(this, _token, null);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
_token = new WeakMap();
|
|
305
|
+
_inFlight = new WeakMap();
|
|
306
|
+
|
|
307
|
+
// src/client/authyonServerClient.ts
|
|
308
|
+
var _clientSecret;
|
|
27
309
|
var AuthyonServerClient = class {
|
|
28
310
|
constructor(options = {}) {
|
|
29
|
-
this
|
|
30
|
-
this.environmentTokenInFlight = null;
|
|
311
|
+
__privateAdd(this, _clientSecret);
|
|
31
312
|
// ── Discovery ──────────────────────────────────────────────────────────────
|
|
32
313
|
this.discovery = {
|
|
33
314
|
/** GET /.well-known/jwks.json — the environment's public signing keys. */
|
|
@@ -61,8 +342,19 @@ var AuthyonServerClient = class {
|
|
|
61
342
|
client_id: credentials.clientId,
|
|
62
343
|
client_secret: credentials.clientSecret
|
|
63
344
|
}
|
|
345
|
+
}),
|
|
346
|
+
/** POST /tenant/auth/validate — validates an already-minted tenant-client bearer token. */
|
|
347
|
+
validate: (accessToken, context) => this.request("/tenant/auth/validate", {
|
|
348
|
+
method: "POST",
|
|
349
|
+
headers: {
|
|
350
|
+
...clientIpHeaders(context),
|
|
351
|
+
Authorization: `Bearer ${accessToken}`
|
|
352
|
+
}
|
|
64
353
|
})
|
|
65
354
|
};
|
|
355
|
+
this.workspaceInvites = new WorkspaceInvitesClient(
|
|
356
|
+
(path, options) => this.http.request(path, options)
|
|
357
|
+
);
|
|
66
358
|
// ── Environment management (users, tenants, roles, permissions, audit) ───
|
|
67
359
|
this.environment = {
|
|
68
360
|
users: {
|
|
@@ -154,8 +446,53 @@ var AuthyonServerClient = class {
|
|
|
154
446
|
)
|
|
155
447
|
},
|
|
156
448
|
tenants: {
|
|
157
|
-
|
|
158
|
-
|
|
449
|
+
credentials: {
|
|
450
|
+
/** GET /env/tenants/{tenantId}/credentials. Returns metadata only, including revoked credentials. */
|
|
451
|
+
list: (tenantId) => allCredentials(
|
|
452
|
+
(path, options) => this.request(path, { ...options, envBearer: true }),
|
|
453
|
+
`/env/tenants/${segment(tenantId)}/credentials`
|
|
454
|
+
),
|
|
455
|
+
/** Paginated searchable listing. Prefer this over loading every credential. */
|
|
456
|
+
listPage: (tenantId, options = {}) => credentialPage(
|
|
457
|
+
(path, requestOptions) => this.request(path, { ...requestOptions, envBearer: true }),
|
|
458
|
+
`/env/tenants/${segment(tenantId)}/credentials`,
|
|
459
|
+
options
|
|
460
|
+
),
|
|
461
|
+
get: async (tenantId, credentialId) => credentialDetail(
|
|
462
|
+
await this.request(
|
|
463
|
+
`/env/tenants/${segment(tenantId)}/credentials/${segment(credentialId)}`,
|
|
464
|
+
{ envBearer: true }
|
|
465
|
+
)
|
|
466
|
+
),
|
|
467
|
+
/** Explicit scopes/permissions; only issuance returns the client secret. */
|
|
468
|
+
create: (tenantId, input) => this.request(`/env/tenants/${segment(tenantId)}/credentials`, {
|
|
469
|
+
method: "POST",
|
|
470
|
+
envBearer: true,
|
|
471
|
+
body: {
|
|
472
|
+
description: input.description,
|
|
473
|
+
...lifetimeBody(input),
|
|
474
|
+
...permissionsBody(input)
|
|
475
|
+
}
|
|
476
|
+
}),
|
|
477
|
+
/** Returns a new secret once while keeping the same client id. */
|
|
478
|
+
rotate: (tenantId, credentialId) => this.request(
|
|
479
|
+
`/env/tenants/${segment(tenantId)}/credentials/${segment(credentialId)}/rotate`,
|
|
480
|
+
{ method: "POST", envBearer: true }
|
|
481
|
+
),
|
|
482
|
+
updatePermissions: (tenantId, credentialId, input) => this.request(
|
|
483
|
+
`/env/tenants/${segment(tenantId)}/credentials/${segment(credentialId)}/permissions`,
|
|
484
|
+
{ method: "PUT", envBearer: true, body: permissionsBody(input) }
|
|
485
|
+
),
|
|
486
|
+
updateScopes: (tenantId, credentialId, scopes) => this.environment.tenants.credentials.updatePermissions(tenantId, credentialId, {
|
|
487
|
+
scopes
|
|
488
|
+
}),
|
|
489
|
+
revoke: (tenantId, credentialId) => this.request(`/env/tenants/${segment(tenantId)}/credentials/${segment(credentialId)}`, {
|
|
490
|
+
method: "DELETE",
|
|
491
|
+
envBearer: true
|
|
492
|
+
})
|
|
493
|
+
},
|
|
494
|
+
/** GET /env/tenants — paginated, searchable list of tenants in the environment. */
|
|
495
|
+
list: (params = {}) => this.request("/env/tenants", { envBearer: true, query: params }),
|
|
159
496
|
/** POST /env/tenants — create a tenant in the environment. */
|
|
160
497
|
create: (input) => this.request("/env/tenants", { method: "POST", envBearer: true, body: input }),
|
|
161
498
|
/** GET /env/tenants/{tenantId} — fetch a tenant by id. */
|
|
@@ -166,7 +503,18 @@ var AuthyonServerClient = class {
|
|
|
166
503
|
envBearer: true,
|
|
167
504
|
body: input
|
|
168
505
|
}),
|
|
169
|
-
/**
|
|
506
|
+
/** PUT /env/tenants/{tenantId}/metadata - replace public and private metadata. */
|
|
507
|
+
updateMetadata: (tenantId, input) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/metadata`, {
|
|
508
|
+
method: "PUT",
|
|
509
|
+
envBearer: true,
|
|
510
|
+
body: input
|
|
511
|
+
}),
|
|
512
|
+
/** POST /env/tenants/{tenantId}/disable — disable without removing tenant data. */
|
|
513
|
+
disable: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/disable`, {
|
|
514
|
+
method: "POST",
|
|
515
|
+
envBearer: true
|
|
516
|
+
}),
|
|
517
|
+
/** DELETE /env/tenants/{tenantId} — logically delete (disable) a tenant. */
|
|
170
518
|
delete: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}`, {
|
|
171
519
|
method: "DELETE",
|
|
172
520
|
envBearer: true
|
|
@@ -300,54 +648,60 @@ var AuthyonServerClient = class {
|
|
|
300
648
|
};
|
|
301
649
|
this.envKey = options.envKey;
|
|
302
650
|
this.clientId = options.clientId;
|
|
303
|
-
this
|
|
304
|
-
this.
|
|
305
|
-
this.
|
|
651
|
+
__privateSet(this, _clientSecret, options.clientSecret);
|
|
652
|
+
this.clientIp = options.clientIp;
|
|
653
|
+
this.transport = createSharedTransport({
|
|
654
|
+
baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
|
|
655
|
+
allowInsecureHttp: options.allowInsecureHttp,
|
|
656
|
+
timeoutMs: options.timeoutMs,
|
|
657
|
+
httpAdapter: options.httpAdapter,
|
|
658
|
+
httpLogger: options.httpLogger,
|
|
659
|
+
fetch: options.fetch
|
|
660
|
+
});
|
|
661
|
+
this.http = new JsonHttpClient(this.transport);
|
|
662
|
+
this.environmentTokenProvider = new ExpiringTokenProvider(async () => {
|
|
663
|
+
if (!this.clientId || !__privateGet(this, _clientSecret)) {
|
|
664
|
+
throw new Error(
|
|
665
|
+
"Authyon: `clientId`/`clientSecret` are required for environment management calls (mint a pair in the console, under the environment's OAuth clients)."
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
const token = await this.environmentAuth.token({
|
|
669
|
+
clientId: this.clientId,
|
|
670
|
+
clientSecret: __privateGet(this, _clientSecret)
|
|
671
|
+
});
|
|
672
|
+
return {
|
|
673
|
+
accessToken: token.access_token,
|
|
674
|
+
expiresAt: Date.now() + token.expires_in * 1e3
|
|
675
|
+
};
|
|
676
|
+
});
|
|
306
677
|
}
|
|
307
678
|
async request(path, options = {}) {
|
|
308
|
-
const headers = {
|
|
309
|
-
|
|
679
|
+
const headers = {
|
|
680
|
+
...clientIpHeaders({ clientIp: this.clientIp }),
|
|
681
|
+
...options.headers
|
|
682
|
+
};
|
|
310
683
|
if (options.envBearer) {
|
|
311
684
|
headers.Authorization = `Bearer ${await this.getEnvironmentAccessToken()}`;
|
|
312
685
|
}
|
|
313
686
|
if (this.envKey) headers["X-Authyon-Environment"] = this.envKey;
|
|
314
|
-
|
|
315
|
-
method: options.method ?? "GET",
|
|
316
|
-
headers,
|
|
317
|
-
body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
|
|
318
|
-
});
|
|
319
|
-
if (!response.ok) {
|
|
320
|
-
let body = {};
|
|
321
|
-
try {
|
|
322
|
-
body = await response.json();
|
|
323
|
-
} catch {
|
|
324
|
-
}
|
|
325
|
-
throw new AuthyonError(response.status, body);
|
|
326
|
-
}
|
|
327
|
-
if (response.status === 204) return void 0;
|
|
328
|
-
return await response.json();
|
|
687
|
+
return this.http.request(path, { ...options, headers });
|
|
329
688
|
}
|
|
330
689
|
/** Mints (and caches) an environment access token from `clientId`/`clientSecret`. */
|
|
331
690
|
async getEnvironmentAccessToken() {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
);
|
|
691
|
+
return this.environmentTokenProvider.getAccessToken();
|
|
692
|
+
}
|
|
693
|
+
/** Discovers Authyon's trusted issuer/JWKS and creates a cached local access-token verifier. */
|
|
694
|
+
async createJwksTokenVerifier(options) {
|
|
695
|
+
const { publishableKey, issuer, ...verifierOptions } = options;
|
|
696
|
+
const configuration = await this.discovery.openidConfiguration(publishableKey);
|
|
697
|
+
if (issuer && configuration.issuer !== issuer) {
|
|
698
|
+
throw new Error("Authyon: discovered JWT issuer does not match the configured issuer");
|
|
340
699
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
};
|
|
346
|
-
return token.access_token;
|
|
347
|
-
}).finally(() => {
|
|
348
|
-
this.environmentTokenInFlight = null;
|
|
700
|
+
return new JwksTokenVerifier({
|
|
701
|
+
...verifierOptions,
|
|
702
|
+
issuer: issuer ?? configuration.issuer,
|
|
703
|
+
jwksUri: configuration.jwks_uri
|
|
349
704
|
});
|
|
350
|
-
return this.environmentTokenInFlight;
|
|
351
705
|
}
|
|
352
706
|
// ── Token verification ────────────────────────────────────────────────────
|
|
353
707
|
//
|
|
@@ -358,12 +712,22 @@ var AuthyonServerClient = class {
|
|
|
358
712
|
// endpoint with only the `X-Authyon-Environment` header (no bearer)
|
|
359
713
|
// returns 401 with `WWW-Authenticate: Bearer`.
|
|
360
714
|
/** POST /auth/introspect — lightweight token introspection (RFC 7662). */
|
|
361
|
-
introspect(token) {
|
|
362
|
-
return this.request("/auth/introspect", {
|
|
715
|
+
introspect(token, context) {
|
|
716
|
+
return this.request("/auth/introspect", {
|
|
717
|
+
method: "POST",
|
|
718
|
+
envBearer: true,
|
|
719
|
+
body: { token },
|
|
720
|
+
headers: clientIpHeaders(context)
|
|
721
|
+
});
|
|
363
722
|
}
|
|
364
723
|
/** POST /auth/validate — recommended: cross-checks DB state, catches revocation immediately. */
|
|
365
|
-
async validate(token) {
|
|
366
|
-
const raw = await this.request("/auth/validate", {
|
|
724
|
+
async validate(token, context) {
|
|
725
|
+
const raw = await this.request("/auth/validate", {
|
|
726
|
+
method: "POST",
|
|
727
|
+
envBearer: true,
|
|
728
|
+
body: { token },
|
|
729
|
+
headers: clientIpHeaders(context)
|
|
730
|
+
});
|
|
367
731
|
return { valid: raw.valid, reason: raw.reason ?? null, user: raw.profile };
|
|
368
732
|
}
|
|
369
733
|
/**
|
|
@@ -375,6 +739,14 @@ var AuthyonServerClient = class {
|
|
|
375
739
|
tenant(credentials) {
|
|
376
740
|
return new TenantScopedClient(this, credentials);
|
|
377
741
|
}
|
|
742
|
+
/** Uses only the supplied platform-user access token. */
|
|
743
|
+
platform(token) {
|
|
744
|
+
return new PlatformScopedClient((path, options) => this.http.request(path, options), token);
|
|
745
|
+
}
|
|
746
|
+
/** Uses an end-user token; the configured environment key selects the environment. */
|
|
747
|
+
user(token) {
|
|
748
|
+
return new UserScopedClient((path, options) => this.request(path, options), token);
|
|
749
|
+
}
|
|
378
750
|
/** @internal used by {@link TenantScopedClient} to share the base URL/fetch/error handling. */
|
|
379
751
|
_requestAsTenant(accessToken, path, options = {}) {
|
|
380
752
|
return this.request(path, {
|
|
@@ -384,10 +756,9 @@ var AuthyonServerClient = class {
|
|
|
384
756
|
});
|
|
385
757
|
}
|
|
386
758
|
};
|
|
759
|
+
_clientSecret = new WeakMap();
|
|
387
760
|
var TenantScopedClient = class {
|
|
388
761
|
constructor(server, credentials) {
|
|
389
|
-
this.token = null;
|
|
390
|
-
this.tokenInFlight = null;
|
|
391
762
|
this.members = {
|
|
392
763
|
/**
|
|
393
764
|
* GET /tenant/members — members of the token's tenant.
|
|
@@ -444,34 +815,94 @@ var TenantScopedClient = class {
|
|
|
444
815
|
delete: (roleId) => this.request(`/tenant/roles/${encodeURIComponent(roleId)}`, { method: "DELETE" })
|
|
445
816
|
};
|
|
446
817
|
this.server = server;
|
|
447
|
-
this.
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (this.token && Date.now() < this.token.expiresAt - EXPIRY_SKEW_MS) {
|
|
451
|
-
return this.token.accessToken;
|
|
452
|
-
}
|
|
453
|
-
if (this.tokenInFlight) return this.tokenInFlight;
|
|
454
|
-
this.tokenInFlight = this.server.tenantAuth.token(this.credentials).then((token) => {
|
|
455
|
-
this.token = {
|
|
818
|
+
this.tokenProvider = new ExpiringTokenProvider(async () => {
|
|
819
|
+
const token = await this.server.tenantAuth.token(credentials);
|
|
820
|
+
return {
|
|
456
821
|
accessToken: token.access_token,
|
|
457
822
|
expiresAt: Date.now() + token.expires_in * 1e3
|
|
458
823
|
};
|
|
459
|
-
return token.access_token;
|
|
460
|
-
}).finally(() => {
|
|
461
|
-
this.tokenInFlight = null;
|
|
462
824
|
});
|
|
463
|
-
|
|
825
|
+
}
|
|
826
|
+
async getAccessToken() {
|
|
827
|
+
return this.tokenProvider.getAccessToken();
|
|
464
828
|
}
|
|
465
829
|
async request(path, options = {}) {
|
|
466
830
|
return this.server._requestAsTenant(await this.getAccessToken(), path, options);
|
|
467
831
|
}
|
|
832
|
+
/** POST /tenant/auth/validate — validates this tenant client's current bearer token. */
|
|
833
|
+
async validate(context) {
|
|
834
|
+
return this.server.tenantAuth.validate(await this.getAccessToken(), context);
|
|
835
|
+
}
|
|
468
836
|
};
|
|
469
837
|
function createClient(options = {}) {
|
|
470
838
|
return new AuthyonServerClient(options);
|
|
471
839
|
}
|
|
840
|
+
|
|
841
|
+
// src/client/authyonServerClientBuilder.ts
|
|
842
|
+
var _options;
|
|
843
|
+
var AuthyonServerClientBuilder = class {
|
|
844
|
+
constructor() {
|
|
845
|
+
__privateAdd(this, _options, {});
|
|
846
|
+
}
|
|
847
|
+
withEnvironmentKey(envKey) {
|
|
848
|
+
__privateGet(this, _options).envKey = envKey;
|
|
849
|
+
return this;
|
|
850
|
+
}
|
|
851
|
+
withEnvironmentCredentials(clientId, clientSecret) {
|
|
852
|
+
__privateGet(this, _options).clientId = clientId;
|
|
853
|
+
__privateGet(this, _options).clientSecret = clientSecret;
|
|
854
|
+
return this;
|
|
855
|
+
}
|
|
856
|
+
withClientIp(clientIp) {
|
|
857
|
+
__privateGet(this, _options).clientIp = clientIp;
|
|
858
|
+
return this;
|
|
859
|
+
}
|
|
860
|
+
withBaseUrl(baseUrl, allowInsecureHttp = false) {
|
|
861
|
+
__privateGet(this, _options).baseUrl = baseUrl;
|
|
862
|
+
__privateGet(this, _options).allowInsecureHttp = allowInsecureHttp;
|
|
863
|
+
return this;
|
|
864
|
+
}
|
|
865
|
+
withTimeout(timeoutMs) {
|
|
866
|
+
__privateGet(this, _options).timeoutMs = timeoutMs;
|
|
867
|
+
return this;
|
|
868
|
+
}
|
|
869
|
+
withHttpAdapter(httpAdapter) {
|
|
870
|
+
__privateGet(this, _options).httpAdapter = httpAdapter;
|
|
871
|
+
return this;
|
|
872
|
+
}
|
|
873
|
+
withHttpLogger(httpLogger) {
|
|
874
|
+
__privateGet(this, _options).httpLogger = httpLogger;
|
|
875
|
+
return this;
|
|
876
|
+
}
|
|
877
|
+
build() {
|
|
878
|
+
return new AuthyonServerClient({ ...__privateGet(this, _options) });
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
_options = new WeakMap();
|
|
472
882
|
export {
|
|
883
|
+
AuthorizationError,
|
|
884
|
+
AuthyonAbility,
|
|
885
|
+
AuthyonAbilityBuilder,
|
|
473
886
|
AuthyonError,
|
|
474
887
|
AuthyonServerClient,
|
|
888
|
+
AuthyonServerClientBuilder,
|
|
889
|
+
ErrorCodes,
|
|
890
|
+
FetchHttpAdapter,
|
|
891
|
+
JwksTokenVerifier,
|
|
892
|
+
LoggingHttpAdapter,
|
|
893
|
+
PlatformScopedClient,
|
|
475
894
|
TenantScopedClient,
|
|
476
|
-
|
|
895
|
+
TokenVerificationError,
|
|
896
|
+
UserScopedClient,
|
|
897
|
+
WorkspaceInvitesClient,
|
|
898
|
+
authorizeRequest,
|
|
899
|
+
authorizeToken,
|
|
900
|
+
createAuthorizationErrorResponse,
|
|
901
|
+
createAuthyonAbility,
|
|
902
|
+
createAuthyonRules,
|
|
903
|
+
createClient,
|
|
904
|
+
createExpressAuthorizationMiddleware,
|
|
905
|
+
hasPermission,
|
|
906
|
+
hasPermissionGroup,
|
|
907
|
+
normalizeClientIp
|
|
477
908
|
};
|