@authyon/server 0.1.5
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 +84 -0
- package/dist/index.cjs +507 -0
- package/dist/index.d.cts +519 -0
- package/dist/index.d.ts +519 -0
- package/dist/index.js +477 -0
- package/package.json +36 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var AuthyonError = class extends Error {
|
|
3
|
+
constructor(status, body) {
|
|
4
|
+
super(body.detail ?? body.title ?? `Authyon request failed with status ${status}`);
|
|
5
|
+
this.name = "AuthyonError";
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.code = body.code ?? "unknown";
|
|
8
|
+
this.title = body.title ?? "Error";
|
|
9
|
+
this.detail = body.detail;
|
|
10
|
+
}
|
|
11
|
+
is(code) {
|
|
12
|
+
return this.code === code;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/client.ts
|
|
17
|
+
var DEFAULT_BASE_URL = "https://api.authyon.com";
|
|
18
|
+
var EXPIRY_SKEW_MS = 3e4;
|
|
19
|
+
function toQuery(params = {}) {
|
|
20
|
+
const query = new URLSearchParams();
|
|
21
|
+
for (const [key, value] of Object.entries(params)) {
|
|
22
|
+
if (value !== void 0) query.set(key, String(value));
|
|
23
|
+
}
|
|
24
|
+
const qs = query.toString();
|
|
25
|
+
return qs ? `?${qs}` : "";
|
|
26
|
+
}
|
|
27
|
+
var AuthyonServerClient = class {
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.environmentToken = null;
|
|
30
|
+
this.environmentTokenInFlight = null;
|
|
31
|
+
// ── Discovery ──────────────────────────────────────────────────────────────
|
|
32
|
+
this.discovery = {
|
|
33
|
+
/** GET /.well-known/jwks.json — the environment's public signing keys. */
|
|
34
|
+
jwks: (publishableKey) => this.request(
|
|
35
|
+
publishableKey ? `/.well-known/${encodeURIComponent(publishableKey)}/jwks.json` : "/.well-known/jwks.json"
|
|
36
|
+
),
|
|
37
|
+
/** GET /.well-known/openid-configuration — OIDC discovery document. */
|
|
38
|
+
openidConfiguration: (publishableKey) => this.request(
|
|
39
|
+
publishableKey ? `/.well-known/${encodeURIComponent(publishableKey)}/openid-configuration` : "/.well-known/openid-configuration"
|
|
40
|
+
)
|
|
41
|
+
};
|
|
42
|
+
// ── Environment-level machine auth ───────────────────────────────────────
|
|
43
|
+
this.environmentAuth = {
|
|
44
|
+
/** POST /env/oauth/token — exchanges environment client credentials for an access token. */
|
|
45
|
+
token: (credentials) => this.request("/env/oauth/token", {
|
|
46
|
+
method: "POST",
|
|
47
|
+
body: {
|
|
48
|
+
grant_type: "client_credentials",
|
|
49
|
+
client_id: credentials.clientId,
|
|
50
|
+
client_secret: credentials.clientSecret
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
};
|
|
54
|
+
// ── Tenant-level machine auth ─────────────────────────────────────────────
|
|
55
|
+
this.tenantAuth = {
|
|
56
|
+
/** POST /tenant/oauth/token — exchanges a tenant's client credentials for a scoped access token. */
|
|
57
|
+
token: (credentials) => this.request("/tenant/oauth/token", {
|
|
58
|
+
method: "POST",
|
|
59
|
+
body: {
|
|
60
|
+
grant_type: "client_credentials",
|
|
61
|
+
client_id: credentials.clientId,
|
|
62
|
+
client_secret: credentials.clientSecret
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
};
|
|
66
|
+
// ── Environment management (users, tenants, roles, permissions, audit) ───
|
|
67
|
+
this.environment = {
|
|
68
|
+
users: {
|
|
69
|
+
/** GET /env/users — paginated list of users in the environment. */
|
|
70
|
+
list: (params = {}) => this.request("/env/users", { envBearer: true, query: params }),
|
|
71
|
+
/** POST /env/users — create a user in the environment; returns only the new id. */
|
|
72
|
+
create: (input) => this.request("/env/users", { method: "POST", envBearer: true, body: input }),
|
|
73
|
+
/** GET /env/users/{userId} — fetch an environment user by id. */
|
|
74
|
+
get: (userId) => this.request(`/env/users/${encodeURIComponent(userId)}`, { envBearer: true }),
|
|
75
|
+
/** POST /env/users/{userId}/roles — grant a role to an environment user. */
|
|
76
|
+
assignRole: (userId, role) => this.request(`/env/users/${encodeURIComponent(userId)}/roles`, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
envBearer: true,
|
|
79
|
+
body: { role }
|
|
80
|
+
}),
|
|
81
|
+
/** DELETE /env/users/{userId}/roles/{role} — revoke a role from an environment user. */
|
|
82
|
+
removeRole: (userId, role) => this.request(`/env/users/${encodeURIComponent(userId)}/roles/${encodeURIComponent(role)}`, {
|
|
83
|
+
method: "DELETE",
|
|
84
|
+
envBearer: true
|
|
85
|
+
}),
|
|
86
|
+
/** POST /env/users/{userId}/permissions — grant a direct permission to an environment user. */
|
|
87
|
+
grantPermission: (userId, permission) => this.request(`/env/users/${encodeURIComponent(userId)}/permissions`, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
envBearer: true,
|
|
90
|
+
body: { permission }
|
|
91
|
+
}),
|
|
92
|
+
/** DELETE /env/users/{userId}/permissions/{permission} — revoke a direct permission. */
|
|
93
|
+
revokePermission: (userId, permission) => this.request(
|
|
94
|
+
`/env/users/${encodeURIComponent(userId)}/permissions/${encodeURIComponent(permission)}`,
|
|
95
|
+
{ method: "DELETE", envBearer: true }
|
|
96
|
+
),
|
|
97
|
+
/** POST /env/users/{userId}/unlock — clears an environment user's lockout. */
|
|
98
|
+
unlock: (userId, reason) => this.request(`/env/users/${encodeURIComponent(userId)}/unlock`, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
envBearer: true,
|
|
101
|
+
body: { reason }
|
|
102
|
+
}),
|
|
103
|
+
/** POST /env/users/{userId}/password — sets a new password for the user. */
|
|
104
|
+
setPassword: (userId, newPassword, reason) => this.request(`/env/users/${encodeURIComponent(userId)}/password`, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
envBearer: true,
|
|
107
|
+
body: { newPassword, reason }
|
|
108
|
+
}),
|
|
109
|
+
/** POST /env/users/{userId}/sessions/revoke-all — revokes every session the user has. */
|
|
110
|
+
revokeSessions: (userId, reason) => this.request(`/env/users/${encodeURIComponent(userId)}/sessions/revoke-all`, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
envBearer: true,
|
|
113
|
+
body: { reason }
|
|
114
|
+
}),
|
|
115
|
+
/** DELETE /env/users/{userId}/sessions/{sessionId} — revokes a single session. */
|
|
116
|
+
revokeSession: (userId, sessionId, reason) => this.request(
|
|
117
|
+
`/env/users/${encodeURIComponent(userId)}/sessions/${encodeURIComponent(sessionId)}`,
|
|
118
|
+
{ method: "DELETE", envBearer: true, query: { reason } }
|
|
119
|
+
),
|
|
120
|
+
/** POST /env/users/{userId}/disable — bans the user indefinitely; revokes live sessions. */
|
|
121
|
+
disable: (userId, reason) => this.request(`/env/users/${encodeURIComponent(userId)}/disable`, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
envBearer: true,
|
|
124
|
+
body: { reason }
|
|
125
|
+
}),
|
|
126
|
+
/** POST /env/users/{userId}/enable — lifts a ban. */
|
|
127
|
+
enable: (userId, reason) => this.request(`/env/users/${encodeURIComponent(userId)}/enable`, {
|
|
128
|
+
method: "POST",
|
|
129
|
+
envBearer: true,
|
|
130
|
+
body: { reason }
|
|
131
|
+
}),
|
|
132
|
+
/** POST /env/users/{userId}/suspend — blocks the user temporarily; clears itself after `durationMinutes`. */
|
|
133
|
+
suspend: (userId, durationMinutes, reason) => this.request(`/env/users/${encodeURIComponent(userId)}/suspend`, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
envBearer: true,
|
|
136
|
+
body: { durationMinutes, reason }
|
|
137
|
+
}),
|
|
138
|
+
/** POST /env/users/{userId}/unsuspend — lifts a suspension early. */
|
|
139
|
+
unsuspend: (userId, reason) => this.request(`/env/users/${encodeURIComponent(userId)}/unsuspend`, {
|
|
140
|
+
method: "POST",
|
|
141
|
+
envBearer: true,
|
|
142
|
+
body: { reason }
|
|
143
|
+
}),
|
|
144
|
+
/** POST /env/users/{userId}/tenants — adds an environment user to a tenant. */
|
|
145
|
+
assignTenant: (userId, tenantId, roles) => this.request(`/env/users/${encodeURIComponent(userId)}/tenants`, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
envBearer: true,
|
|
148
|
+
body: { tenantId, roles }
|
|
149
|
+
}),
|
|
150
|
+
/** DELETE /env/users/{userId}/tenants/{tenantId} — removes an environment user from a tenant. */
|
|
151
|
+
removeTenant: (userId, tenantId) => this.request(
|
|
152
|
+
`/env/users/${encodeURIComponent(userId)}/tenants/${encodeURIComponent(tenantId)}`,
|
|
153
|
+
{ method: "DELETE", envBearer: true }
|
|
154
|
+
)
|
|
155
|
+
},
|
|
156
|
+
tenants: {
|
|
157
|
+
/** GET /env/tenants — list tenants in the environment. */
|
|
158
|
+
list: () => this.request("/env/tenants", { envBearer: true }),
|
|
159
|
+
/** POST /env/tenants — create a tenant in the environment. */
|
|
160
|
+
create: (input) => this.request("/env/tenants", { method: "POST", envBearer: true, body: input }),
|
|
161
|
+
/** GET /env/tenants/{tenantId} — fetch a tenant by id. */
|
|
162
|
+
get: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}`, { envBearer: true }),
|
|
163
|
+
/** PUT /env/tenants/{tenantId} — update a tenant. */
|
|
164
|
+
update: (tenantId, input) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}`, {
|
|
165
|
+
method: "PUT",
|
|
166
|
+
envBearer: true,
|
|
167
|
+
body: input
|
|
168
|
+
}),
|
|
169
|
+
/** DELETE /env/tenants/{tenantId} — delete a tenant. */
|
|
170
|
+
delete: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}`, {
|
|
171
|
+
method: "DELETE",
|
|
172
|
+
envBearer: true
|
|
173
|
+
}),
|
|
174
|
+
members: {
|
|
175
|
+
/**
|
|
176
|
+
* GET /env/tenants/{tenantId}/members — a tenant's members as full
|
|
177
|
+
* user records. Confirmed live: a bare array (unlike
|
|
178
|
+
* `environment.users.list()`, this one does NOT paginate despite
|
|
179
|
+
* accepting `skip`/`take`), with the same shape
|
|
180
|
+
* `environment.users.list()` items have.
|
|
181
|
+
*/
|
|
182
|
+
list: (tenantId, params = {}) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/members`, {
|
|
183
|
+
envBearer: true,
|
|
184
|
+
query: params
|
|
185
|
+
}),
|
|
186
|
+
/** POST /env/tenants/{tenantId}/members — add a member to a tenant. */
|
|
187
|
+
add: (tenantId, userId, roles) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/members`, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
envBearer: true,
|
|
190
|
+
body: { userId, roles }
|
|
191
|
+
}),
|
|
192
|
+
/** DELETE /env/tenants/{tenantId}/members/{userId} — remove a member from a tenant. */
|
|
193
|
+
remove: (tenantId, userId) => this.request(
|
|
194
|
+
`/env/tenants/${encodeURIComponent(tenantId)}/members/${encodeURIComponent(userId)}`,
|
|
195
|
+
{ method: "DELETE", envBearer: true }
|
|
196
|
+
),
|
|
197
|
+
/** POST /env/tenants/{tenantId}/members/{userId}/roles — grant a tenant role to a member. */
|
|
198
|
+
assignRole: (tenantId, userId, role) => this.request(
|
|
199
|
+
`/env/tenants/${encodeURIComponent(tenantId)}/members/${encodeURIComponent(userId)}/roles`,
|
|
200
|
+
{ method: "POST", envBearer: true, body: { role } }
|
|
201
|
+
),
|
|
202
|
+
/** DELETE .../members/{userId}/roles/{role} — revoke a tenant role from a member. */
|
|
203
|
+
removeRole: (tenantId, userId, role) => this.request(
|
|
204
|
+
`/env/tenants/${encodeURIComponent(tenantId)}/members/${encodeURIComponent(userId)}/roles/${encodeURIComponent(role)}`,
|
|
205
|
+
{ method: "DELETE", envBearer: true }
|
|
206
|
+
),
|
|
207
|
+
/** POST .../members/{userId}/permissions — grant a permission to a tenant member. */
|
|
208
|
+
grantPermission: (tenantId, userId, permission) => this.request(
|
|
209
|
+
`/env/tenants/${encodeURIComponent(tenantId)}/members/${encodeURIComponent(userId)}/permissions`,
|
|
210
|
+
{ method: "POST", envBearer: true, body: { permission } }
|
|
211
|
+
),
|
|
212
|
+
/** DELETE .../members/{userId}/permissions/{permission} — revoke a permission from a tenant member. */
|
|
213
|
+
revokePermission: (tenantId, userId, permission) => this.request(
|
|
214
|
+
`/env/tenants/${encodeURIComponent(tenantId)}/members/${encodeURIComponent(userId)}/permissions/${encodeURIComponent(permission)}`,
|
|
215
|
+
{ method: "DELETE", envBearer: true }
|
|
216
|
+
)
|
|
217
|
+
},
|
|
218
|
+
roles: {
|
|
219
|
+
/** GET /env/tenants/{tenantId}/roles — list a tenant's roles. */
|
|
220
|
+
list: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/roles`, { envBearer: true }),
|
|
221
|
+
/** POST /env/tenants/{tenantId}/roles — create a tenant role. */
|
|
222
|
+
create: (tenantId, input) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/roles`, {
|
|
223
|
+
method: "POST",
|
|
224
|
+
envBearer: true,
|
|
225
|
+
body: input
|
|
226
|
+
}),
|
|
227
|
+
/** GET /env/tenants/{tenantId}/roles/{roleId} — fetch a tenant role by id. */
|
|
228
|
+
get: (tenantId, roleId) => this.request(
|
|
229
|
+
`/env/tenants/${encodeURIComponent(tenantId)}/roles/${encodeURIComponent(roleId)}`,
|
|
230
|
+
{ envBearer: true }
|
|
231
|
+
),
|
|
232
|
+
/** PUT /env/tenants/{tenantId}/roles/{roleId} — update a tenant role. */
|
|
233
|
+
update: (tenantId, roleId, input) => this.request(
|
|
234
|
+
`/env/tenants/${encodeURIComponent(tenantId)}/roles/${encodeURIComponent(roleId)}`,
|
|
235
|
+
{ method: "PUT", envBearer: true, body: input }
|
|
236
|
+
),
|
|
237
|
+
/** DELETE /env/tenants/{tenantId}/roles/{roleId} — delete a tenant role. */
|
|
238
|
+
delete: (tenantId, roleId) => this.request(
|
|
239
|
+
`/env/tenants/${encodeURIComponent(tenantId)}/roles/${encodeURIComponent(roleId)}`,
|
|
240
|
+
{ method: "DELETE", envBearer: true }
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
roles: {
|
|
245
|
+
/** GET /env/roles — environment-level roles, grouped into custom and system-defined. */
|
|
246
|
+
list: () => this.request("/env/roles", { envBearer: true }),
|
|
247
|
+
/** POST /env/roles — create an environment-level role. */
|
|
248
|
+
create: (input) => this.request("/env/roles", { method: "POST", envBearer: true, body: input }),
|
|
249
|
+
/** PUT /env/roles/{roleId} — update an environment-level role. */
|
|
250
|
+
update: (roleId, input) => this.request(`/env/roles/${encodeURIComponent(roleId)}`, {
|
|
251
|
+
method: "PUT",
|
|
252
|
+
envBearer: true,
|
|
253
|
+
body: input
|
|
254
|
+
}),
|
|
255
|
+
/** DELETE /env/roles/{roleId} — delete an environment-level role. */
|
|
256
|
+
delete: (roleId) => this.request(`/env/roles/${encodeURIComponent(roleId)}`, {
|
|
257
|
+
method: "DELETE",
|
|
258
|
+
envBearer: true
|
|
259
|
+
})
|
|
260
|
+
},
|
|
261
|
+
permissions: {
|
|
262
|
+
/** GET /env/permissions — environment-level permissions, grouped by the role they're attached to. */
|
|
263
|
+
list: () => this.request("/env/permissions", { envBearer: true }),
|
|
264
|
+
/** POST /env/permissions — create an environment-level permission. */
|
|
265
|
+
create: (input) => this.request("/env/permissions", { method: "POST", envBearer: true, body: input }),
|
|
266
|
+
/** PUT /env/permissions/{permissionId} — update an environment-level permission. */
|
|
267
|
+
update: (permissionId, description) => this.request(`/env/permissions/${encodeURIComponent(permissionId)}`, {
|
|
268
|
+
method: "PUT",
|
|
269
|
+
envBearer: true,
|
|
270
|
+
body: { description }
|
|
271
|
+
}),
|
|
272
|
+
/** DELETE /env/permissions/{permissionId} — delete an environment-level permission. */
|
|
273
|
+
delete: (permissionId) => this.request(`/env/permissions/${encodeURIComponent(permissionId)}`, {
|
|
274
|
+
method: "DELETE",
|
|
275
|
+
envBearer: true
|
|
276
|
+
}),
|
|
277
|
+
/** GET /permissions/reserved — reserved permission names you can't redefine. */
|
|
278
|
+
reserved: () => this.request("/permissions/reserved", { envBearer: true })
|
|
279
|
+
},
|
|
280
|
+
audit: {
|
|
281
|
+
/** GET /env/audit — paginated list of the environment's audit events. */
|
|
282
|
+
list: (params = {}) => this.request("/env/audit", { envBearer: true, query: params }),
|
|
283
|
+
/** GET /env/audit/login-activity — paginated login activity in the environment. */
|
|
284
|
+
loginActivity: (params = {}) => this.request("/env/audit/login-activity", { envBearer: true, query: params })
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
// ── Generic permission grants ─────────────────────────────────────────────
|
|
288
|
+
this.permissions = {
|
|
289
|
+
/** POST /users/{userId}/permissions — grant a permission directly to a user. */
|
|
290
|
+
grant: (userId, permission) => this.request(`/users/${encodeURIComponent(userId)}/permissions`, {
|
|
291
|
+
method: "POST",
|
|
292
|
+
envBearer: true,
|
|
293
|
+
body: { permission }
|
|
294
|
+
}),
|
|
295
|
+
/** DELETE /users/{userId}/permissions/{permission} — revoke a permission from a user. */
|
|
296
|
+
revoke: (userId, permission) => this.request(
|
|
297
|
+
`/users/${encodeURIComponent(userId)}/permissions/${encodeURIComponent(permission)}`,
|
|
298
|
+
{ method: "DELETE", envBearer: true }
|
|
299
|
+
)
|
|
300
|
+
};
|
|
301
|
+
this.envKey = options.envKey;
|
|
302
|
+
this.clientId = options.clientId;
|
|
303
|
+
this.clientSecret = options.clientSecret;
|
|
304
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
305
|
+
this.fetchImpl = options.fetch ?? fetch.bind(globalThis);
|
|
306
|
+
}
|
|
307
|
+
async request(path, options = {}) {
|
|
308
|
+
const headers = { ...options.headers };
|
|
309
|
+
if (options.body !== void 0) headers["Content-Type"] = "application/json";
|
|
310
|
+
if (options.envBearer) {
|
|
311
|
+
headers.Authorization = `Bearer ${await this.getEnvironmentAccessToken()}`;
|
|
312
|
+
}
|
|
313
|
+
if (this.envKey) headers["X-Authyon-Environment"] = this.envKey;
|
|
314
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path}${toQuery(options.query)}`, {
|
|
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();
|
|
329
|
+
}
|
|
330
|
+
/** Mints (and caches) an environment access token from `clientId`/`clientSecret`. */
|
|
331
|
+
async getEnvironmentAccessToken() {
|
|
332
|
+
if (this.environmentToken && Date.now() < this.environmentToken.expiresAt - EXPIRY_SKEW_MS) {
|
|
333
|
+
return this.environmentToken.accessToken;
|
|
334
|
+
}
|
|
335
|
+
if (this.environmentTokenInFlight) return this.environmentTokenInFlight;
|
|
336
|
+
if (!this.clientId || !this.clientSecret) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
"Authyon: `clientId`/`clientSecret` are required for environment management calls (mint a pair in the console, under the environment's OAuth clients)."
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
this.environmentTokenInFlight = this.environmentAuth.token({ clientId: this.clientId, clientSecret: this.clientSecret }).then((token) => {
|
|
342
|
+
this.environmentToken = {
|
|
343
|
+
accessToken: token.access_token,
|
|
344
|
+
expiresAt: Date.now() + token.expires_in * 1e3
|
|
345
|
+
};
|
|
346
|
+
return token.access_token;
|
|
347
|
+
}).finally(() => {
|
|
348
|
+
this.environmentTokenInFlight = null;
|
|
349
|
+
});
|
|
350
|
+
return this.environmentTokenInFlight;
|
|
351
|
+
}
|
|
352
|
+
// ── Token verification ────────────────────────────────────────────────────
|
|
353
|
+
//
|
|
354
|
+
// Per RFC 7662 §2.1, the CALLER must authenticate here too — the token
|
|
355
|
+
// being inspected sits in the body, but the request itself needs an
|
|
356
|
+
// environment (or tenant) client-credentials bearer token, same as
|
|
357
|
+
// `environment.*`. Confirmed against the live API: calling either
|
|
358
|
+
// endpoint with only the `X-Authyon-Environment` header (no bearer)
|
|
359
|
+
// returns 401 with `WWW-Authenticate: Bearer`.
|
|
360
|
+
/** POST /auth/introspect — lightweight token introspection (RFC 7662). */
|
|
361
|
+
introspect(token) {
|
|
362
|
+
return this.request("/auth/introspect", { method: "POST", envBearer: true, body: { token } });
|
|
363
|
+
}
|
|
364
|
+
/** POST /auth/validate — recommended: cross-checks DB state, catches revocation immediately. */
|
|
365
|
+
async validate(token) {
|
|
366
|
+
const raw = await this.request("/auth/validate", { method: "POST", envBearer: true, body: { token } });
|
|
367
|
+
return { valid: raw.valid, reason: raw.reason ?? null, user: raw.profile };
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Scopes a client to a single tenant's OAuth credentials — mints and
|
|
371
|
+
* caches its access token independently of the environment plane. Use
|
|
372
|
+
* this for the `TenantManagement` endpoints (`/tenant/...`), which act on
|
|
373
|
+
* "the token's tenant" rather than one you pass by id.
|
|
374
|
+
*/
|
|
375
|
+
tenant(credentials) {
|
|
376
|
+
return new TenantScopedClient(this, credentials);
|
|
377
|
+
}
|
|
378
|
+
/** @internal used by {@link TenantScopedClient} to share the base URL/fetch/error handling. */
|
|
379
|
+
_requestAsTenant(accessToken, path, options = {}) {
|
|
380
|
+
return this.request(path, {
|
|
381
|
+
...options,
|
|
382
|
+
headers: { ...options.headers, Authorization: `Bearer ${accessToken}` },
|
|
383
|
+
envBearer: false
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
var TenantScopedClient = class {
|
|
388
|
+
constructor(server, credentials) {
|
|
389
|
+
this.token = null;
|
|
390
|
+
this.tokenInFlight = null;
|
|
391
|
+
this.members = {
|
|
392
|
+
/**
|
|
393
|
+
* GET /tenant/members — members of the token's tenant.
|
|
394
|
+
*
|
|
395
|
+
* ⚠️ Inferred, not confirmed live (no tenant client credentials
|
|
396
|
+
* available to exchange for a scoped token) — assumed to share
|
|
397
|
+
* `/env/tenants/{id}/members`'s confirmed shape (bare array of full
|
|
398
|
+
* user records), since both are the "admin" member listing as opposed
|
|
399
|
+
* to `/auth/tenants/{id}/members`'s paginated, lightweight one.
|
|
400
|
+
*/
|
|
401
|
+
list: (params = {}) => this.request("/tenant/members", { query: params }),
|
|
402
|
+
/** POST /tenant/members — add a member to the token's tenant. */
|
|
403
|
+
add: (userId, roles) => this.request("/tenant/members", { method: "POST", body: { userId, roles } }),
|
|
404
|
+
/** DELETE /tenant/members/{userId} — remove a member from the token's tenant. */
|
|
405
|
+
remove: (userId) => this.request(`/tenant/members/${encodeURIComponent(userId)}`, { method: "DELETE" }),
|
|
406
|
+
/** POST /tenant/members/{userId}/roles — grant a role to a member of the tenant. */
|
|
407
|
+
assignRole: (userId, role) => this.request(`/tenant/members/${encodeURIComponent(userId)}/roles`, {
|
|
408
|
+
method: "POST",
|
|
409
|
+
body: { role }
|
|
410
|
+
}),
|
|
411
|
+
/** DELETE /tenant/members/{userId}/roles/{role} — revoke a role from a member of the tenant. */
|
|
412
|
+
removeRole: (userId, role) => this.request(
|
|
413
|
+
`/tenant/members/${encodeURIComponent(userId)}/roles/${encodeURIComponent(role)}`,
|
|
414
|
+
{ method: "DELETE" }
|
|
415
|
+
),
|
|
416
|
+
/** POST /tenant/members/{userId}/permissions — grant a permission to a member of the tenant. */
|
|
417
|
+
grantPermission: (userId, permission) => this.request(`/tenant/members/${encodeURIComponent(userId)}/permissions`, {
|
|
418
|
+
method: "POST",
|
|
419
|
+
body: { permission }
|
|
420
|
+
}),
|
|
421
|
+
/** DELETE .../permissions/{permission} — revoke a permission from a member of the tenant. */
|
|
422
|
+
revokePermission: (userId, permission) => this.request(
|
|
423
|
+
`/tenant/members/${encodeURIComponent(userId)}/permissions/${encodeURIComponent(permission)}`,
|
|
424
|
+
{ method: "DELETE" }
|
|
425
|
+
)
|
|
426
|
+
};
|
|
427
|
+
this.roles = {
|
|
428
|
+
/**
|
|
429
|
+
* GET /tenant/roles — roles in the token's tenant, grouped into custom
|
|
430
|
+
* and system-defined.
|
|
431
|
+
*
|
|
432
|
+
* ⚠️ Inferred, not confirmed live (the test environment had no tenants
|
|
433
|
+
* to exchange a scoped token for) — assumed to share `/env/roles`'
|
|
434
|
+
* grouped shape, since both operate on the same underlying role model.
|
|
435
|
+
*/
|
|
436
|
+
list: () => this.request("/tenant/roles"),
|
|
437
|
+
/** POST /tenant/roles — create a role in the tenant. */
|
|
438
|
+
create: (input) => this.request("/tenant/roles", { method: "POST", body: input }),
|
|
439
|
+
/** GET /tenant/roles/{roleId} — fetch a tenant role by id. */
|
|
440
|
+
get: (roleId) => this.request(`/tenant/roles/${encodeURIComponent(roleId)}`),
|
|
441
|
+
/** PUT /tenant/roles/{roleId} — update a role in the tenant. */
|
|
442
|
+
update: (roleId, input) => this.request(`/tenant/roles/${encodeURIComponent(roleId)}`, { method: "PUT", body: input }),
|
|
443
|
+
/** DELETE /tenant/roles/{roleId} — delete a role in the tenant. */
|
|
444
|
+
delete: (roleId) => this.request(`/tenant/roles/${encodeURIComponent(roleId)}`, { method: "DELETE" })
|
|
445
|
+
};
|
|
446
|
+
this.server = server;
|
|
447
|
+
this.credentials = credentials;
|
|
448
|
+
}
|
|
449
|
+
async getAccessToken() {
|
|
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 = {
|
|
456
|
+
accessToken: token.access_token,
|
|
457
|
+
expiresAt: Date.now() + token.expires_in * 1e3
|
|
458
|
+
};
|
|
459
|
+
return token.access_token;
|
|
460
|
+
}).finally(() => {
|
|
461
|
+
this.tokenInFlight = null;
|
|
462
|
+
});
|
|
463
|
+
return this.tokenInFlight;
|
|
464
|
+
}
|
|
465
|
+
async request(path, options = {}) {
|
|
466
|
+
return this.server._requestAsTenant(await this.getAccessToken(), path, options);
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
function createClient(options = {}) {
|
|
470
|
+
return new AuthyonServerClient(options);
|
|
471
|
+
}
|
|
472
|
+
export {
|
|
473
|
+
AuthyonError,
|
|
474
|
+
AuthyonServerClient,
|
|
475
|
+
TenantScopedClient,
|
|
476
|
+
createClient
|
|
477
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@authyon/server",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Authyon SDK for backends — server-to-server organization/member management and access-token verification.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"authyon",
|
|
27
|
+
"auth",
|
|
28
|
+
"server-to-server",
|
|
29
|
+
"management-api",
|
|
30
|
+
"token-verification"
|
|
31
|
+
],
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"tsup": "^8.0.0",
|
|
34
|
+
"typescript": "^5.5.0"
|
|
35
|
+
}
|
|
36
|
+
}
|