@authyon/auth 0.1.3 → 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/dist/index.cjs +81 -29
- package/dist/index.d.cts +101 -20
- package/dist/index.d.ts +101 -20
- package/dist/index.js +81 -29
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -98,6 +98,22 @@ function defaultStorage() {
|
|
|
98
98
|
// src/client.ts
|
|
99
99
|
var DEFAULT_BASE_URL = "https://api.authyon.com";
|
|
100
100
|
var EXPIRY_SKEW_MS = 3e4;
|
|
101
|
+
var FALLBACK_EXPIRES_IN = 1800;
|
|
102
|
+
function readTokens(raw) {
|
|
103
|
+
const tokens = raw.tokens ?? raw;
|
|
104
|
+
if (!tokens.accessToken || !tokens.refreshToken) {
|
|
105
|
+
throw new AuthyonError(502, {
|
|
106
|
+
code: "session.malformed",
|
|
107
|
+
title: "Malformed session response",
|
|
108
|
+
detail: "The session response carried no access/refresh token pair."
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
accessToken: tokens.accessToken,
|
|
113
|
+
refreshToken: tokens.refreshToken,
|
|
114
|
+
expiresIn: tokens.expiresIn ?? FALLBACK_EXPIRES_IN
|
|
115
|
+
};
|
|
116
|
+
}
|
|
101
117
|
var AuthyonClient = class {
|
|
102
118
|
constructor(options) {
|
|
103
119
|
this.listeners = /* @__PURE__ */ new Set();
|
|
@@ -113,7 +129,7 @@ var AuthyonClient = class {
|
|
|
113
129
|
loginFinish: (assertion) => this.request("/auth/webauthn/login/finish", {
|
|
114
130
|
method: "POST",
|
|
115
131
|
body: assertion
|
|
116
|
-
}).then((data) => this.setSession(data, "signed_in"))
|
|
132
|
+
}).then((data) => this.setSession({ tokens: readTokens(data) }, "signed_in")).then((session) => this.hydrateUser(session))
|
|
117
133
|
};
|
|
118
134
|
// ── Social sign-in (SSO) ─────────────────────────────────────────────────
|
|
119
135
|
this.sso = {
|
|
@@ -134,9 +150,7 @@ var AuthyonClient = class {
|
|
|
134
150
|
* POST /auth/sso/exchange — swaps the one-time code from the provider
|
|
135
151
|
* callback for tokens and stores the session.
|
|
136
152
|
*/
|
|
137
|
-
exchange: (code) => this.request("/auth/sso/exchange", { method: "POST", body: { code } }).then(
|
|
138
|
-
(data) => this.setSession(data, "signed_in")
|
|
139
|
-
)
|
|
153
|
+
exchange: (code) => this.request("/auth/sso/exchange", { method: "POST", body: { code } }).then((data) => this.setSession({ tokens: readTokens(data) }, "signed_in")).then((session) => this.hydrateUser(session))
|
|
140
154
|
};
|
|
141
155
|
// ── User ─────────────────────────────────────────────────────────────────
|
|
142
156
|
this.user = {
|
|
@@ -144,7 +158,7 @@ var AuthyonClient = class {
|
|
|
144
158
|
me: () => this.request("/auth/me", { bearer: true }).then(normalizeUser),
|
|
145
159
|
/** GET /auth/sessions — active refresh-token sessions with device/IP data. */
|
|
146
160
|
sessions: () => this.request("/auth/sessions", { bearer: true }),
|
|
147
|
-
/** GET /auth/me/activities — recent account activity for the current user. */
|
|
161
|
+
/** GET /auth/me/activities — paginated recent account activity for the current user. */
|
|
148
162
|
activities: (params = {}) => this.request(`/auth/me/activities?${toQuery(params)}`, { bearer: true }),
|
|
149
163
|
/**
|
|
150
164
|
* Revokes a single session by id (e.g. one entry from `sessions()`),
|
|
@@ -194,11 +208,16 @@ var AuthyonClient = class {
|
|
|
194
208
|
method: "POST",
|
|
195
209
|
bearer: true,
|
|
196
210
|
body: { tenantSlug: organizationSlug }
|
|
197
|
-
}).then((data) => this.setSession(data, "refreshed")),
|
|
211
|
+
}).then((data) => this.setSession({ tokens: readTokens(data) }, "refreshed")).then((session) => this.hydrateUser(session)),
|
|
198
212
|
/** The organization the current session is scoped to, from the cached session — no network call. */
|
|
199
213
|
current: () => this.getSession()?.user?.activeOrganization ?? null,
|
|
200
214
|
members: {
|
|
201
|
-
/**
|
|
215
|
+
/**
|
|
216
|
+
* GET /auth/tenants/{organizationId}/members — paginated list of an
|
|
217
|
+
* organization's members. Consistent with the confirmed-live
|
|
218
|
+
* `Page<T>` envelope every other `skip`/`take` endpoint returns
|
|
219
|
+
* (`user.activities()`, `@authyon/server`'s `environment.users.list()`).
|
|
220
|
+
*/
|
|
202
221
|
list: (organizationId, params = {}) => this.request(
|
|
203
222
|
`/auth/tenants/${encodeURIComponent(organizationId)}/members?${toQuery(params)}`,
|
|
204
223
|
{ bearer: true }
|
|
@@ -332,9 +351,9 @@ var AuthyonClient = class {
|
|
|
332
351
|
}
|
|
333
352
|
setSession(raw, event) {
|
|
334
353
|
const session = {
|
|
335
|
-
...raw,
|
|
354
|
+
...raw.tokens,
|
|
336
355
|
user: raw.user ? normalizeUser(raw.user) : void 0,
|
|
337
|
-
expiresAt: Date.now() + raw.expiresIn * 1e3
|
|
356
|
+
expiresAt: Date.now() + raw.tokens.expiresIn * 1e3
|
|
338
357
|
};
|
|
339
358
|
this.storage.set(session);
|
|
340
359
|
this.emit(event === "signed_out" ? { type: "signed_out" } : { type: event, session });
|
|
@@ -344,6 +363,23 @@ var AuthyonClient = class {
|
|
|
344
363
|
this.storage.clear();
|
|
345
364
|
this.emit({ type: "signed_out" });
|
|
346
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* `/auth/login` and the other endpoints that mint a session don't return
|
|
368
|
+
* a `user` object on the wire — only `tokens` (plus `twoFactor`, when a
|
|
369
|
+
* challenge is required). Fetch the profile right after so callers get a
|
|
370
|
+
* fully-populated `session.user` without an extra manual round trip.
|
|
371
|
+
* Best-effort: keeps the session usable even if this fetch fails.
|
|
372
|
+
*/
|
|
373
|
+
async hydrateUser(session) {
|
|
374
|
+
try {
|
|
375
|
+
const user = await this.user.me();
|
|
376
|
+
const hydrated = { ...session, user };
|
|
377
|
+
this.storage.set(hydrated);
|
|
378
|
+
return hydrated;
|
|
379
|
+
} catch {
|
|
380
|
+
return session;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
347
383
|
// ── HTTP core ────────────────────────────────────────────────────────────
|
|
348
384
|
async request(path, options = {}, isRetry = false) {
|
|
349
385
|
const headers = {
|
|
@@ -395,20 +431,22 @@ var AuthyonClient = class {
|
|
|
395
431
|
async login(params) {
|
|
396
432
|
const { organizationSlug, ...rest } = params;
|
|
397
433
|
const body = organizationSlug ? { ...rest, tenantSlug: organizationSlug } : rest;
|
|
398
|
-
const data = await this.request("/auth/login", {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
});
|
|
402
|
-
if (data.twoFactorRequired) {
|
|
403
|
-
return data;
|
|
434
|
+
const data = await this.request("/auth/login", { method: "POST", body });
|
|
435
|
+
if (data.twoFactor) {
|
|
436
|
+
return { twoFactorRequired: true, ...data.twoFactor };
|
|
404
437
|
}
|
|
405
|
-
const session = this.
|
|
438
|
+
const session = await this.hydrateUser(
|
|
439
|
+
this.setSession({ tokens: readTokens(data) }, "signed_in")
|
|
440
|
+
);
|
|
406
441
|
return { twoFactorRequired: false, session };
|
|
407
442
|
}
|
|
408
443
|
/** POST /auth/2fa/verify — redeems a 2FA challenge from `login()` and stores the session. */
|
|
409
444
|
async verifyTwoFactor(params) {
|
|
410
|
-
const data = await this.request("/auth/2fa/verify", {
|
|
411
|
-
|
|
445
|
+
const data = await this.request("/auth/2fa/verify", {
|
|
446
|
+
method: "POST",
|
|
447
|
+
body: params
|
|
448
|
+
});
|
|
449
|
+
return this.hydrateUser(this.setSession({ tokens: readTokens(data) }, "signed_in"));
|
|
412
450
|
}
|
|
413
451
|
/** POST /auth/refresh — rotates the single-use refresh token (single-flight). */
|
|
414
452
|
async refresh() {
|
|
@@ -420,7 +458,10 @@ var AuthyonClient = class {
|
|
|
420
458
|
method: "POST",
|
|
421
459
|
body: { refreshToken: current.refreshToken }
|
|
422
460
|
}).then(
|
|
423
|
-
(data) => this.setSession(
|
|
461
|
+
(data) => this.setSession(
|
|
462
|
+
{ tokens: readTokens(data), user: current.user },
|
|
463
|
+
"refreshed"
|
|
464
|
+
)
|
|
424
465
|
).catch((error) => {
|
|
425
466
|
if (error instanceof AuthyonError && (error.status === 401 || error.status === 403)) {
|
|
426
467
|
this.clearSession();
|
|
@@ -453,30 +494,41 @@ var AuthyonClient = class {
|
|
|
453
494
|
this.clearSession();
|
|
454
495
|
}
|
|
455
496
|
// ── Token verification ───────────────────────────────────────────────────
|
|
456
|
-
/**
|
|
497
|
+
/**
|
|
498
|
+
* POST /auth/introspect — lightweight token introspection (RFC 7662).
|
|
499
|
+
*
|
|
500
|
+
* ⚠️ Confirmed live: this endpoint requires the CALLER to also
|
|
501
|
+
* authenticate, with an environment or tenant client-credentials bearer
|
|
502
|
+
* token — the end user's own access token doesn't satisfy that (401).
|
|
503
|
+
* A browser app has no client secret to present, so this will fail from
|
|
504
|
+
* `@authyon/auth` in practice; call it from your backend via
|
|
505
|
+
* `@authyon/server` instead.
|
|
506
|
+
*/
|
|
457
507
|
async introspect(token) {
|
|
458
508
|
const accessToken = token ?? await this.getAccessToken();
|
|
459
509
|
return this.request("/auth/introspect", { method: "POST", body: { token: accessToken } });
|
|
460
510
|
}
|
|
461
|
-
/**
|
|
511
|
+
/**
|
|
512
|
+
* POST /auth/validate — recommended: cross-checks DB state, catches
|
|
513
|
+
* revocation immediately. Same caller-authentication requirement (and
|
|
514
|
+
* the same practical limitation from the browser) as `introspect()`.
|
|
515
|
+
*/
|
|
462
516
|
async validate(token) {
|
|
463
517
|
const accessToken = token ?? await this.getAccessToken();
|
|
464
|
-
const raw = await this.request("/auth/validate", {
|
|
465
|
-
method: "POST",
|
|
466
|
-
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : void 0
|
|
467
|
-
});
|
|
518
|
+
const raw = await this.request("/auth/validate", { method: "POST", body: { token: accessToken } });
|
|
468
519
|
return {
|
|
469
|
-
|
|
470
|
-
|
|
520
|
+
valid: raw.valid,
|
|
521
|
+
reason: raw.reason ?? null,
|
|
522
|
+
user: raw.profile ? normalizeUser(raw.profile) : null
|
|
471
523
|
};
|
|
472
524
|
}
|
|
473
525
|
};
|
|
474
526
|
function normalizeUser(raw) {
|
|
475
|
-
const { tenants, activeTenant, ...rest } = raw;
|
|
527
|
+
const { tenant, tenants, activeTenant, ...rest } = raw;
|
|
476
528
|
return {
|
|
477
529
|
...rest,
|
|
478
530
|
organizations: raw.organizations ?? tenants,
|
|
479
|
-
activeOrganization: raw.activeOrganization ?? activeTenant ?? null
|
|
531
|
+
activeOrganization: tenant ?? raw.activeOrganization ?? activeTenant ?? null
|
|
480
532
|
};
|
|
481
533
|
}
|
|
482
534
|
function createClient(options) {
|
package/dist/index.d.cts
CHANGED
|
@@ -15,10 +15,14 @@ interface CreateOrganizationParams {
|
|
|
15
15
|
slug?: string;
|
|
16
16
|
description?: string;
|
|
17
17
|
}
|
|
18
|
+
/** GET /auth/tenants/{organizationId}/members — confirmed against the live API. */
|
|
18
19
|
interface OrganizationMember {
|
|
19
20
|
userId: string;
|
|
20
21
|
email?: string;
|
|
22
|
+
username?: string;
|
|
21
23
|
roles?: string[];
|
|
24
|
+
createdAt?: string;
|
|
25
|
+
lastLoginAt?: string | null;
|
|
22
26
|
}
|
|
23
27
|
/** POST /auth/tenants/{tenantId}/members — invites a member by e-mail. */
|
|
24
28
|
interface InviteMemberParams {
|
|
@@ -35,9 +39,17 @@ interface User {
|
|
|
35
39
|
id: string;
|
|
36
40
|
email: string;
|
|
37
41
|
username?: string;
|
|
42
|
+
emailConfirmed?: boolean;
|
|
43
|
+
firstName?: string | null;
|
|
44
|
+
lastName?: string | null;
|
|
45
|
+
roles?: string[];
|
|
46
|
+
permissions?: string[];
|
|
47
|
+
createdAt?: string;
|
|
48
|
+
lastLoginAt?: string;
|
|
38
49
|
organizations?: Organization[];
|
|
39
50
|
activeOrganization?: Organization | null;
|
|
40
|
-
|
|
51
|
+
/** Actions the user must complete before continuing (e.g. confirm e-mail). */
|
|
52
|
+
pendencies?: string[];
|
|
41
53
|
}
|
|
42
54
|
/** Token pair issued by login / refresh / tenant switch. */
|
|
43
55
|
interface Session {
|
|
@@ -89,9 +101,18 @@ interface TwoFactorVerifyParams {
|
|
|
89
101
|
/** Required when `method` is `"webauthn"`. */
|
|
90
102
|
webAuthnAssertion?: WebAuthnAssertion;
|
|
91
103
|
}
|
|
104
|
+
/** GET /auth/2fa/status — per-method enrolment flags, confirmed against the live API. */
|
|
92
105
|
interface TwoFactorStatus {
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
authenticatorEnabled: boolean;
|
|
107
|
+
authenticatorConfirmedAt?: string | null;
|
|
108
|
+
emailEnabled: boolean;
|
|
109
|
+
emailEnabledAt?: string | null;
|
|
110
|
+
/** Partially redacted (e.g. `"n**********@h***.com"`). */
|
|
111
|
+
emailHint?: string | null;
|
|
112
|
+
webAuthnEnabled: boolean;
|
|
113
|
+
webAuthnCredentialCount: number;
|
|
114
|
+
webAuthnCredentials: WebAuthnCredential[];
|
|
115
|
+
remainingRecoveryCodes: number;
|
|
95
116
|
}
|
|
96
117
|
interface AuthenticatorSetup {
|
|
97
118
|
secret: string;
|
|
@@ -123,12 +144,27 @@ interface SsoProvider {
|
|
|
123
144
|
/** URL to redirect the browser to in order to start this provider's flow. */
|
|
124
145
|
startUrl: string;
|
|
125
146
|
}
|
|
147
|
+
/** GET /auth/me/activities — one audit-trail entry, confirmed against the live API. */
|
|
126
148
|
interface Activity {
|
|
127
149
|
id: string;
|
|
128
|
-
|
|
129
|
-
|
|
150
|
+
eventType: string;
|
|
151
|
+
occurredAt: string;
|
|
152
|
+
environmentId?: string;
|
|
130
153
|
ip?: string;
|
|
131
|
-
|
|
154
|
+
userAgent?: string;
|
|
155
|
+
/** JSON-encoded string — `JSON.parse` it for the event-specific payload. */
|
|
156
|
+
payloadJson?: string;
|
|
157
|
+
}
|
|
158
|
+
/** Paginated list envelope returned by `user.activities()`. */
|
|
159
|
+
interface Page<T> {
|
|
160
|
+
data: T[];
|
|
161
|
+
/** Item count actually returned for this page. */
|
|
162
|
+
perPage?: number;
|
|
163
|
+
pageSize: number;
|
|
164
|
+
total: number;
|
|
165
|
+
pages: number;
|
|
166
|
+
hasNext: boolean;
|
|
167
|
+
hasPrev: boolean;
|
|
132
168
|
}
|
|
133
169
|
/** A role available within an organization (tenant). */
|
|
134
170
|
interface Role {
|
|
@@ -137,25 +173,44 @@ interface Role {
|
|
|
137
173
|
description?: string;
|
|
138
174
|
permissions?: string[];
|
|
139
175
|
}
|
|
176
|
+
/** GET /auth/sessions — confirmed against the live API. */
|
|
140
177
|
interface SessionInfo {
|
|
141
178
|
id: string;
|
|
142
|
-
createdAt
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
179
|
+
createdAt: string;
|
|
180
|
+
expiresAt: string;
|
|
181
|
+
revokedAt?: string | null;
|
|
182
|
+
createdFromIp?: string;
|
|
183
|
+
isActive: boolean;
|
|
184
|
+
userAgent?: string;
|
|
185
|
+
lastUsedAt?: string | null;
|
|
186
|
+
lastUsedFromIp?: string | null;
|
|
147
187
|
}
|
|
188
|
+
/** POST /auth/introspect (RFC 7662) — confirmed against the live API. */
|
|
148
189
|
interface IntrospectResult {
|
|
149
190
|
active: boolean;
|
|
150
191
|
sub?: string;
|
|
192
|
+
username?: string | null;
|
|
193
|
+
email?: string | null;
|
|
194
|
+
roles?: string[] | null;
|
|
195
|
+
permissions?: string[];
|
|
151
196
|
client_id?: string;
|
|
152
197
|
scope?: string;
|
|
153
198
|
exp?: number;
|
|
199
|
+
iat?: number;
|
|
200
|
+
jti?: string;
|
|
154
201
|
token_type?: string;
|
|
155
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* POST /auth/validate — confirmed against the live API. The wire shape is
|
|
205
|
+
* `{ valid, reason, profile }`, not `{ user, organization }` as the
|
|
206
|
+
* OpenAPI schema (which didn't document response bodies) suggested.
|
|
207
|
+
* `profile` is `null` for machine tokens (there's no user behind them) and
|
|
208
|
+
* for tokens that fail validation.
|
|
209
|
+
*/
|
|
156
210
|
interface ValidateResult {
|
|
157
|
-
|
|
158
|
-
|
|
211
|
+
valid: boolean;
|
|
212
|
+
reason?: string | null;
|
|
213
|
+
user: User | null;
|
|
159
214
|
}
|
|
160
215
|
type AuthEvent = {
|
|
161
216
|
type: "signed_in";
|
|
@@ -211,6 +266,14 @@ declare class AuthyonClient {
|
|
|
211
266
|
private emit;
|
|
212
267
|
private setSession;
|
|
213
268
|
private clearSession;
|
|
269
|
+
/**
|
|
270
|
+
* `/auth/login` and the other endpoints that mint a session don't return
|
|
271
|
+
* a `user` object on the wire — only `tokens` (plus `twoFactor`, when a
|
|
272
|
+
* challenge is required). Fetch the profile right after so callers get a
|
|
273
|
+
* fully-populated `session.user` without an extra manual round trip.
|
|
274
|
+
* Best-effort: keeps the session usable even if this fetch fails.
|
|
275
|
+
*/
|
|
276
|
+
private hydrateUser;
|
|
214
277
|
private request;
|
|
215
278
|
private toError;
|
|
216
279
|
/** POST /auth/register — creates a new user (rate-limited to 20/hour per IP). */
|
|
@@ -266,8 +329,8 @@ declare class AuthyonClient {
|
|
|
266
329
|
me: () => Promise<User>;
|
|
267
330
|
/** GET /auth/sessions — active refresh-token sessions with device/IP data. */
|
|
268
331
|
sessions: () => Promise<SessionInfo[]>;
|
|
269
|
-
/** GET /auth/me/activities — recent account activity for the current user. */
|
|
270
|
-
activities: (params?: PageParams) => Promise<Activity
|
|
332
|
+
/** GET /auth/me/activities — paginated recent account activity for the current user. */
|
|
333
|
+
activities: (params?: PageParams) => Promise<Page<Activity>>;
|
|
271
334
|
/**
|
|
272
335
|
* Revokes a single session by id (e.g. one entry from `sessions()`),
|
|
273
336
|
* signing that device out without affecting the current one.
|
|
@@ -305,8 +368,13 @@ declare class AuthyonClient {
|
|
|
305
368
|
/** The organization the current session is scoped to, from the cached session — no network call. */
|
|
306
369
|
current: () => Organization | null;
|
|
307
370
|
members: {
|
|
308
|
-
/**
|
|
309
|
-
|
|
371
|
+
/**
|
|
372
|
+
* GET /auth/tenants/{organizationId}/members — paginated list of an
|
|
373
|
+
* organization's members. Consistent with the confirmed-live
|
|
374
|
+
* `Page<T>` envelope every other `skip`/`take` endpoint returns
|
|
375
|
+
* (`user.activities()`, `@authyon/server`'s `environment.users.list()`).
|
|
376
|
+
*/
|
|
377
|
+
list: (organizationId: string, params?: PageParams) => Promise<Page<OrganizationMember>>;
|
|
310
378
|
/** POST /auth/tenants/{organizationId}/members — invite a member by e-mail. */
|
|
311
379
|
invite: (organizationId: string, params: InviteMemberParams) => Promise<void>;
|
|
312
380
|
/** DELETE /auth/tenants/{organizationId}/members/{userId} — remove a member. */
|
|
@@ -361,9 +429,22 @@ declare class AuthyonClient {
|
|
|
361
429
|
assertionStart: (challengeToken: string) => Promise<WebAuthnCeremonyStart>;
|
|
362
430
|
};
|
|
363
431
|
};
|
|
364
|
-
/**
|
|
432
|
+
/**
|
|
433
|
+
* POST /auth/introspect — lightweight token introspection (RFC 7662).
|
|
434
|
+
*
|
|
435
|
+
* ⚠️ Confirmed live: this endpoint requires the CALLER to also
|
|
436
|
+
* authenticate, with an environment or tenant client-credentials bearer
|
|
437
|
+
* token — the end user's own access token doesn't satisfy that (401).
|
|
438
|
+
* A browser app has no client secret to present, so this will fail from
|
|
439
|
+
* `@authyon/auth` in practice; call it from your backend via
|
|
440
|
+
* `@authyon/server` instead.
|
|
441
|
+
*/
|
|
365
442
|
introspect(token?: string): Promise<IntrospectResult>;
|
|
366
|
-
/**
|
|
443
|
+
/**
|
|
444
|
+
* POST /auth/validate — recommended: cross-checks DB state, catches
|
|
445
|
+
* revocation immediately. Same caller-authentication requirement (and
|
|
446
|
+
* the same practical limitation from the browser) as `introspect()`.
|
|
447
|
+
*/
|
|
367
448
|
validate(token?: string): Promise<ValidateResult>;
|
|
368
449
|
}
|
|
369
450
|
/** Convenience factory: `const authyon = createClient({ envKey: "pk_live_..." })`. */
|
|
@@ -400,4 +481,4 @@ declare function memoryStorage(): TokenStorage;
|
|
|
400
481
|
declare function localStorageAdapter(key?: string): TokenStorage;
|
|
401
482
|
declare function defaultStorage(): TokenStorage;
|
|
402
483
|
|
|
403
|
-
export { type Activity, type AuthEvent, type AuthStateListener, type AuthenticatorSetup, AuthyonClient, type AuthyonClientOptions, AuthyonError, type CreateOrganizationParams, ErrorCodes, type IntrospectResult, type InviteMemberParams, type LoginParams, type LoginResult, type Organization, type OrganizationMember, type PageParams, type RegisterParams, type Role, type Session, type SessionInfo, type SsoProvider, type TokenStorage, type TwoFactorChallenge, type TwoFactorMethod, type TwoFactorStatus, type TwoFactorVerifyParams, type User, type ValidateResult, type WebAuthnAssertion, type WebAuthnCeremonyStart, type WebAuthnCredential, createClient, defaultStorage, localStorageAdapter, memoryStorage };
|
|
484
|
+
export { type Activity, type AuthEvent, type AuthStateListener, type AuthenticatorSetup, AuthyonClient, type AuthyonClientOptions, AuthyonError, type CreateOrganizationParams, ErrorCodes, type IntrospectResult, type InviteMemberParams, type LoginParams, type LoginResult, type Organization, type OrganizationMember, type Page, type PageParams, type RegisterParams, type Role, type Session, type SessionInfo, type SsoProvider, type TokenStorage, type TwoFactorChallenge, type TwoFactorMethod, type TwoFactorStatus, type TwoFactorVerifyParams, type User, type ValidateResult, type WebAuthnAssertion, type WebAuthnCeremonyStart, type WebAuthnCredential, createClient, defaultStorage, localStorageAdapter, memoryStorage };
|
package/dist/index.d.ts
CHANGED
|
@@ -15,10 +15,14 @@ interface CreateOrganizationParams {
|
|
|
15
15
|
slug?: string;
|
|
16
16
|
description?: string;
|
|
17
17
|
}
|
|
18
|
+
/** GET /auth/tenants/{organizationId}/members — confirmed against the live API. */
|
|
18
19
|
interface OrganizationMember {
|
|
19
20
|
userId: string;
|
|
20
21
|
email?: string;
|
|
22
|
+
username?: string;
|
|
21
23
|
roles?: string[];
|
|
24
|
+
createdAt?: string;
|
|
25
|
+
lastLoginAt?: string | null;
|
|
22
26
|
}
|
|
23
27
|
/** POST /auth/tenants/{tenantId}/members — invites a member by e-mail. */
|
|
24
28
|
interface InviteMemberParams {
|
|
@@ -35,9 +39,17 @@ interface User {
|
|
|
35
39
|
id: string;
|
|
36
40
|
email: string;
|
|
37
41
|
username?: string;
|
|
42
|
+
emailConfirmed?: boolean;
|
|
43
|
+
firstName?: string | null;
|
|
44
|
+
lastName?: string | null;
|
|
45
|
+
roles?: string[];
|
|
46
|
+
permissions?: string[];
|
|
47
|
+
createdAt?: string;
|
|
48
|
+
lastLoginAt?: string;
|
|
38
49
|
organizations?: Organization[];
|
|
39
50
|
activeOrganization?: Organization | null;
|
|
40
|
-
|
|
51
|
+
/** Actions the user must complete before continuing (e.g. confirm e-mail). */
|
|
52
|
+
pendencies?: string[];
|
|
41
53
|
}
|
|
42
54
|
/** Token pair issued by login / refresh / tenant switch. */
|
|
43
55
|
interface Session {
|
|
@@ -89,9 +101,18 @@ interface TwoFactorVerifyParams {
|
|
|
89
101
|
/** Required when `method` is `"webauthn"`. */
|
|
90
102
|
webAuthnAssertion?: WebAuthnAssertion;
|
|
91
103
|
}
|
|
104
|
+
/** GET /auth/2fa/status — per-method enrolment flags, confirmed against the live API. */
|
|
92
105
|
interface TwoFactorStatus {
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
authenticatorEnabled: boolean;
|
|
107
|
+
authenticatorConfirmedAt?: string | null;
|
|
108
|
+
emailEnabled: boolean;
|
|
109
|
+
emailEnabledAt?: string | null;
|
|
110
|
+
/** Partially redacted (e.g. `"n**********@h***.com"`). */
|
|
111
|
+
emailHint?: string | null;
|
|
112
|
+
webAuthnEnabled: boolean;
|
|
113
|
+
webAuthnCredentialCount: number;
|
|
114
|
+
webAuthnCredentials: WebAuthnCredential[];
|
|
115
|
+
remainingRecoveryCodes: number;
|
|
95
116
|
}
|
|
96
117
|
interface AuthenticatorSetup {
|
|
97
118
|
secret: string;
|
|
@@ -123,12 +144,27 @@ interface SsoProvider {
|
|
|
123
144
|
/** URL to redirect the browser to in order to start this provider's flow. */
|
|
124
145
|
startUrl: string;
|
|
125
146
|
}
|
|
147
|
+
/** GET /auth/me/activities — one audit-trail entry, confirmed against the live API. */
|
|
126
148
|
interface Activity {
|
|
127
149
|
id: string;
|
|
128
|
-
|
|
129
|
-
|
|
150
|
+
eventType: string;
|
|
151
|
+
occurredAt: string;
|
|
152
|
+
environmentId?: string;
|
|
130
153
|
ip?: string;
|
|
131
|
-
|
|
154
|
+
userAgent?: string;
|
|
155
|
+
/** JSON-encoded string — `JSON.parse` it for the event-specific payload. */
|
|
156
|
+
payloadJson?: string;
|
|
157
|
+
}
|
|
158
|
+
/** Paginated list envelope returned by `user.activities()`. */
|
|
159
|
+
interface Page<T> {
|
|
160
|
+
data: T[];
|
|
161
|
+
/** Item count actually returned for this page. */
|
|
162
|
+
perPage?: number;
|
|
163
|
+
pageSize: number;
|
|
164
|
+
total: number;
|
|
165
|
+
pages: number;
|
|
166
|
+
hasNext: boolean;
|
|
167
|
+
hasPrev: boolean;
|
|
132
168
|
}
|
|
133
169
|
/** A role available within an organization (tenant). */
|
|
134
170
|
interface Role {
|
|
@@ -137,25 +173,44 @@ interface Role {
|
|
|
137
173
|
description?: string;
|
|
138
174
|
permissions?: string[];
|
|
139
175
|
}
|
|
176
|
+
/** GET /auth/sessions — confirmed against the live API. */
|
|
140
177
|
interface SessionInfo {
|
|
141
178
|
id: string;
|
|
142
|
-
createdAt
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
179
|
+
createdAt: string;
|
|
180
|
+
expiresAt: string;
|
|
181
|
+
revokedAt?: string | null;
|
|
182
|
+
createdFromIp?: string;
|
|
183
|
+
isActive: boolean;
|
|
184
|
+
userAgent?: string;
|
|
185
|
+
lastUsedAt?: string | null;
|
|
186
|
+
lastUsedFromIp?: string | null;
|
|
147
187
|
}
|
|
188
|
+
/** POST /auth/introspect (RFC 7662) — confirmed against the live API. */
|
|
148
189
|
interface IntrospectResult {
|
|
149
190
|
active: boolean;
|
|
150
191
|
sub?: string;
|
|
192
|
+
username?: string | null;
|
|
193
|
+
email?: string | null;
|
|
194
|
+
roles?: string[] | null;
|
|
195
|
+
permissions?: string[];
|
|
151
196
|
client_id?: string;
|
|
152
197
|
scope?: string;
|
|
153
198
|
exp?: number;
|
|
199
|
+
iat?: number;
|
|
200
|
+
jti?: string;
|
|
154
201
|
token_type?: string;
|
|
155
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* POST /auth/validate — confirmed against the live API. The wire shape is
|
|
205
|
+
* `{ valid, reason, profile }`, not `{ user, organization }` as the
|
|
206
|
+
* OpenAPI schema (which didn't document response bodies) suggested.
|
|
207
|
+
* `profile` is `null` for machine tokens (there's no user behind them) and
|
|
208
|
+
* for tokens that fail validation.
|
|
209
|
+
*/
|
|
156
210
|
interface ValidateResult {
|
|
157
|
-
|
|
158
|
-
|
|
211
|
+
valid: boolean;
|
|
212
|
+
reason?: string | null;
|
|
213
|
+
user: User | null;
|
|
159
214
|
}
|
|
160
215
|
type AuthEvent = {
|
|
161
216
|
type: "signed_in";
|
|
@@ -211,6 +266,14 @@ declare class AuthyonClient {
|
|
|
211
266
|
private emit;
|
|
212
267
|
private setSession;
|
|
213
268
|
private clearSession;
|
|
269
|
+
/**
|
|
270
|
+
* `/auth/login` and the other endpoints that mint a session don't return
|
|
271
|
+
* a `user` object on the wire — only `tokens` (plus `twoFactor`, when a
|
|
272
|
+
* challenge is required). Fetch the profile right after so callers get a
|
|
273
|
+
* fully-populated `session.user` without an extra manual round trip.
|
|
274
|
+
* Best-effort: keeps the session usable even if this fetch fails.
|
|
275
|
+
*/
|
|
276
|
+
private hydrateUser;
|
|
214
277
|
private request;
|
|
215
278
|
private toError;
|
|
216
279
|
/** POST /auth/register — creates a new user (rate-limited to 20/hour per IP). */
|
|
@@ -266,8 +329,8 @@ declare class AuthyonClient {
|
|
|
266
329
|
me: () => Promise<User>;
|
|
267
330
|
/** GET /auth/sessions — active refresh-token sessions with device/IP data. */
|
|
268
331
|
sessions: () => Promise<SessionInfo[]>;
|
|
269
|
-
/** GET /auth/me/activities — recent account activity for the current user. */
|
|
270
|
-
activities: (params?: PageParams) => Promise<Activity
|
|
332
|
+
/** GET /auth/me/activities — paginated recent account activity for the current user. */
|
|
333
|
+
activities: (params?: PageParams) => Promise<Page<Activity>>;
|
|
271
334
|
/**
|
|
272
335
|
* Revokes a single session by id (e.g. one entry from `sessions()`),
|
|
273
336
|
* signing that device out without affecting the current one.
|
|
@@ -305,8 +368,13 @@ declare class AuthyonClient {
|
|
|
305
368
|
/** The organization the current session is scoped to, from the cached session — no network call. */
|
|
306
369
|
current: () => Organization | null;
|
|
307
370
|
members: {
|
|
308
|
-
/**
|
|
309
|
-
|
|
371
|
+
/**
|
|
372
|
+
* GET /auth/tenants/{organizationId}/members — paginated list of an
|
|
373
|
+
* organization's members. Consistent with the confirmed-live
|
|
374
|
+
* `Page<T>` envelope every other `skip`/`take` endpoint returns
|
|
375
|
+
* (`user.activities()`, `@authyon/server`'s `environment.users.list()`).
|
|
376
|
+
*/
|
|
377
|
+
list: (organizationId: string, params?: PageParams) => Promise<Page<OrganizationMember>>;
|
|
310
378
|
/** POST /auth/tenants/{organizationId}/members — invite a member by e-mail. */
|
|
311
379
|
invite: (organizationId: string, params: InviteMemberParams) => Promise<void>;
|
|
312
380
|
/** DELETE /auth/tenants/{organizationId}/members/{userId} — remove a member. */
|
|
@@ -361,9 +429,22 @@ declare class AuthyonClient {
|
|
|
361
429
|
assertionStart: (challengeToken: string) => Promise<WebAuthnCeremonyStart>;
|
|
362
430
|
};
|
|
363
431
|
};
|
|
364
|
-
/**
|
|
432
|
+
/**
|
|
433
|
+
* POST /auth/introspect — lightweight token introspection (RFC 7662).
|
|
434
|
+
*
|
|
435
|
+
* ⚠️ Confirmed live: this endpoint requires the CALLER to also
|
|
436
|
+
* authenticate, with an environment or tenant client-credentials bearer
|
|
437
|
+
* token — the end user's own access token doesn't satisfy that (401).
|
|
438
|
+
* A browser app has no client secret to present, so this will fail from
|
|
439
|
+
* `@authyon/auth` in practice; call it from your backend via
|
|
440
|
+
* `@authyon/server` instead.
|
|
441
|
+
*/
|
|
365
442
|
introspect(token?: string): Promise<IntrospectResult>;
|
|
366
|
-
/**
|
|
443
|
+
/**
|
|
444
|
+
* POST /auth/validate — recommended: cross-checks DB state, catches
|
|
445
|
+
* revocation immediately. Same caller-authentication requirement (and
|
|
446
|
+
* the same practical limitation from the browser) as `introspect()`.
|
|
447
|
+
*/
|
|
367
448
|
validate(token?: string): Promise<ValidateResult>;
|
|
368
449
|
}
|
|
369
450
|
/** Convenience factory: `const authyon = createClient({ envKey: "pk_live_..." })`. */
|
|
@@ -400,4 +481,4 @@ declare function memoryStorage(): TokenStorage;
|
|
|
400
481
|
declare function localStorageAdapter(key?: string): TokenStorage;
|
|
401
482
|
declare function defaultStorage(): TokenStorage;
|
|
402
483
|
|
|
403
|
-
export { type Activity, type AuthEvent, type AuthStateListener, type AuthenticatorSetup, AuthyonClient, type AuthyonClientOptions, AuthyonError, type CreateOrganizationParams, ErrorCodes, type IntrospectResult, type InviteMemberParams, type LoginParams, type LoginResult, type Organization, type OrganizationMember, type PageParams, type RegisterParams, type Role, type Session, type SessionInfo, type SsoProvider, type TokenStorage, type TwoFactorChallenge, type TwoFactorMethod, type TwoFactorStatus, type TwoFactorVerifyParams, type User, type ValidateResult, type WebAuthnAssertion, type WebAuthnCeremonyStart, type WebAuthnCredential, createClient, defaultStorage, localStorageAdapter, memoryStorage };
|
|
484
|
+
export { type Activity, type AuthEvent, type AuthStateListener, type AuthenticatorSetup, AuthyonClient, type AuthyonClientOptions, AuthyonError, type CreateOrganizationParams, ErrorCodes, type IntrospectResult, type InviteMemberParams, type LoginParams, type LoginResult, type Organization, type OrganizationMember, type Page, type PageParams, type RegisterParams, type Role, type Session, type SessionInfo, type SsoProvider, type TokenStorage, type TwoFactorChallenge, type TwoFactorMethod, type TwoFactorStatus, type TwoFactorVerifyParams, type User, type ValidateResult, type WebAuthnAssertion, type WebAuthnCeremonyStart, type WebAuthnCredential, createClient, defaultStorage, localStorageAdapter, memoryStorage };
|
package/dist/index.js
CHANGED
|
@@ -66,6 +66,22 @@ function defaultStorage() {
|
|
|
66
66
|
// src/client.ts
|
|
67
67
|
var DEFAULT_BASE_URL = "https://api.authyon.com";
|
|
68
68
|
var EXPIRY_SKEW_MS = 3e4;
|
|
69
|
+
var FALLBACK_EXPIRES_IN = 1800;
|
|
70
|
+
function readTokens(raw) {
|
|
71
|
+
const tokens = raw.tokens ?? raw;
|
|
72
|
+
if (!tokens.accessToken || !tokens.refreshToken) {
|
|
73
|
+
throw new AuthyonError(502, {
|
|
74
|
+
code: "session.malformed",
|
|
75
|
+
title: "Malformed session response",
|
|
76
|
+
detail: "The session response carried no access/refresh token pair."
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
accessToken: tokens.accessToken,
|
|
81
|
+
refreshToken: tokens.refreshToken,
|
|
82
|
+
expiresIn: tokens.expiresIn ?? FALLBACK_EXPIRES_IN
|
|
83
|
+
};
|
|
84
|
+
}
|
|
69
85
|
var AuthyonClient = class {
|
|
70
86
|
constructor(options) {
|
|
71
87
|
this.listeners = /* @__PURE__ */ new Set();
|
|
@@ -81,7 +97,7 @@ var AuthyonClient = class {
|
|
|
81
97
|
loginFinish: (assertion) => this.request("/auth/webauthn/login/finish", {
|
|
82
98
|
method: "POST",
|
|
83
99
|
body: assertion
|
|
84
|
-
}).then((data) => this.setSession(data, "signed_in"))
|
|
100
|
+
}).then((data) => this.setSession({ tokens: readTokens(data) }, "signed_in")).then((session) => this.hydrateUser(session))
|
|
85
101
|
};
|
|
86
102
|
// ── Social sign-in (SSO) ─────────────────────────────────────────────────
|
|
87
103
|
this.sso = {
|
|
@@ -102,9 +118,7 @@ var AuthyonClient = class {
|
|
|
102
118
|
* POST /auth/sso/exchange — swaps the one-time code from the provider
|
|
103
119
|
* callback for tokens and stores the session.
|
|
104
120
|
*/
|
|
105
|
-
exchange: (code) => this.request("/auth/sso/exchange", { method: "POST", body: { code } }).then(
|
|
106
|
-
(data) => this.setSession(data, "signed_in")
|
|
107
|
-
)
|
|
121
|
+
exchange: (code) => this.request("/auth/sso/exchange", { method: "POST", body: { code } }).then((data) => this.setSession({ tokens: readTokens(data) }, "signed_in")).then((session) => this.hydrateUser(session))
|
|
108
122
|
};
|
|
109
123
|
// ── User ─────────────────────────────────────────────────────────────────
|
|
110
124
|
this.user = {
|
|
@@ -112,7 +126,7 @@ var AuthyonClient = class {
|
|
|
112
126
|
me: () => this.request("/auth/me", { bearer: true }).then(normalizeUser),
|
|
113
127
|
/** GET /auth/sessions — active refresh-token sessions with device/IP data. */
|
|
114
128
|
sessions: () => this.request("/auth/sessions", { bearer: true }),
|
|
115
|
-
/** GET /auth/me/activities — recent account activity for the current user. */
|
|
129
|
+
/** GET /auth/me/activities — paginated recent account activity for the current user. */
|
|
116
130
|
activities: (params = {}) => this.request(`/auth/me/activities?${toQuery(params)}`, { bearer: true }),
|
|
117
131
|
/**
|
|
118
132
|
* Revokes a single session by id (e.g. one entry from `sessions()`),
|
|
@@ -162,11 +176,16 @@ var AuthyonClient = class {
|
|
|
162
176
|
method: "POST",
|
|
163
177
|
bearer: true,
|
|
164
178
|
body: { tenantSlug: organizationSlug }
|
|
165
|
-
}).then((data) => this.setSession(data, "refreshed")),
|
|
179
|
+
}).then((data) => this.setSession({ tokens: readTokens(data) }, "refreshed")).then((session) => this.hydrateUser(session)),
|
|
166
180
|
/** The organization the current session is scoped to, from the cached session — no network call. */
|
|
167
181
|
current: () => this.getSession()?.user?.activeOrganization ?? null,
|
|
168
182
|
members: {
|
|
169
|
-
/**
|
|
183
|
+
/**
|
|
184
|
+
* GET /auth/tenants/{organizationId}/members — paginated list of an
|
|
185
|
+
* organization's members. Consistent with the confirmed-live
|
|
186
|
+
* `Page<T>` envelope every other `skip`/`take` endpoint returns
|
|
187
|
+
* (`user.activities()`, `@authyon/server`'s `environment.users.list()`).
|
|
188
|
+
*/
|
|
170
189
|
list: (organizationId, params = {}) => this.request(
|
|
171
190
|
`/auth/tenants/${encodeURIComponent(organizationId)}/members?${toQuery(params)}`,
|
|
172
191
|
{ bearer: true }
|
|
@@ -300,9 +319,9 @@ var AuthyonClient = class {
|
|
|
300
319
|
}
|
|
301
320
|
setSession(raw, event) {
|
|
302
321
|
const session = {
|
|
303
|
-
...raw,
|
|
322
|
+
...raw.tokens,
|
|
304
323
|
user: raw.user ? normalizeUser(raw.user) : void 0,
|
|
305
|
-
expiresAt: Date.now() + raw.expiresIn * 1e3
|
|
324
|
+
expiresAt: Date.now() + raw.tokens.expiresIn * 1e3
|
|
306
325
|
};
|
|
307
326
|
this.storage.set(session);
|
|
308
327
|
this.emit(event === "signed_out" ? { type: "signed_out" } : { type: event, session });
|
|
@@ -312,6 +331,23 @@ var AuthyonClient = class {
|
|
|
312
331
|
this.storage.clear();
|
|
313
332
|
this.emit({ type: "signed_out" });
|
|
314
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* `/auth/login` and the other endpoints that mint a session don't return
|
|
336
|
+
* a `user` object on the wire — only `tokens` (plus `twoFactor`, when a
|
|
337
|
+
* challenge is required). Fetch the profile right after so callers get a
|
|
338
|
+
* fully-populated `session.user` without an extra manual round trip.
|
|
339
|
+
* Best-effort: keeps the session usable even if this fetch fails.
|
|
340
|
+
*/
|
|
341
|
+
async hydrateUser(session) {
|
|
342
|
+
try {
|
|
343
|
+
const user = await this.user.me();
|
|
344
|
+
const hydrated = { ...session, user };
|
|
345
|
+
this.storage.set(hydrated);
|
|
346
|
+
return hydrated;
|
|
347
|
+
} catch {
|
|
348
|
+
return session;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
315
351
|
// ── HTTP core ────────────────────────────────────────────────────────────
|
|
316
352
|
async request(path, options = {}, isRetry = false) {
|
|
317
353
|
const headers = {
|
|
@@ -363,20 +399,22 @@ var AuthyonClient = class {
|
|
|
363
399
|
async login(params) {
|
|
364
400
|
const { organizationSlug, ...rest } = params;
|
|
365
401
|
const body = organizationSlug ? { ...rest, tenantSlug: organizationSlug } : rest;
|
|
366
|
-
const data = await this.request("/auth/login", {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
});
|
|
370
|
-
if (data.twoFactorRequired) {
|
|
371
|
-
return data;
|
|
402
|
+
const data = await this.request("/auth/login", { method: "POST", body });
|
|
403
|
+
if (data.twoFactor) {
|
|
404
|
+
return { twoFactorRequired: true, ...data.twoFactor };
|
|
372
405
|
}
|
|
373
|
-
const session = this.
|
|
406
|
+
const session = await this.hydrateUser(
|
|
407
|
+
this.setSession({ tokens: readTokens(data) }, "signed_in")
|
|
408
|
+
);
|
|
374
409
|
return { twoFactorRequired: false, session };
|
|
375
410
|
}
|
|
376
411
|
/** POST /auth/2fa/verify — redeems a 2FA challenge from `login()` and stores the session. */
|
|
377
412
|
async verifyTwoFactor(params) {
|
|
378
|
-
const data = await this.request("/auth/2fa/verify", {
|
|
379
|
-
|
|
413
|
+
const data = await this.request("/auth/2fa/verify", {
|
|
414
|
+
method: "POST",
|
|
415
|
+
body: params
|
|
416
|
+
});
|
|
417
|
+
return this.hydrateUser(this.setSession({ tokens: readTokens(data) }, "signed_in"));
|
|
380
418
|
}
|
|
381
419
|
/** POST /auth/refresh — rotates the single-use refresh token (single-flight). */
|
|
382
420
|
async refresh() {
|
|
@@ -388,7 +426,10 @@ var AuthyonClient = class {
|
|
|
388
426
|
method: "POST",
|
|
389
427
|
body: { refreshToken: current.refreshToken }
|
|
390
428
|
}).then(
|
|
391
|
-
(data) => this.setSession(
|
|
429
|
+
(data) => this.setSession(
|
|
430
|
+
{ tokens: readTokens(data), user: current.user },
|
|
431
|
+
"refreshed"
|
|
432
|
+
)
|
|
392
433
|
).catch((error) => {
|
|
393
434
|
if (error instanceof AuthyonError && (error.status === 401 || error.status === 403)) {
|
|
394
435
|
this.clearSession();
|
|
@@ -421,30 +462,41 @@ var AuthyonClient = class {
|
|
|
421
462
|
this.clearSession();
|
|
422
463
|
}
|
|
423
464
|
// ── Token verification ───────────────────────────────────────────────────
|
|
424
|
-
/**
|
|
465
|
+
/**
|
|
466
|
+
* POST /auth/introspect — lightweight token introspection (RFC 7662).
|
|
467
|
+
*
|
|
468
|
+
* ⚠️ Confirmed live: this endpoint requires the CALLER to also
|
|
469
|
+
* authenticate, with an environment or tenant client-credentials bearer
|
|
470
|
+
* token — the end user's own access token doesn't satisfy that (401).
|
|
471
|
+
* A browser app has no client secret to present, so this will fail from
|
|
472
|
+
* `@authyon/auth` in practice; call it from your backend via
|
|
473
|
+
* `@authyon/server` instead.
|
|
474
|
+
*/
|
|
425
475
|
async introspect(token) {
|
|
426
476
|
const accessToken = token ?? await this.getAccessToken();
|
|
427
477
|
return this.request("/auth/introspect", { method: "POST", body: { token: accessToken } });
|
|
428
478
|
}
|
|
429
|
-
/**
|
|
479
|
+
/**
|
|
480
|
+
* POST /auth/validate — recommended: cross-checks DB state, catches
|
|
481
|
+
* revocation immediately. Same caller-authentication requirement (and
|
|
482
|
+
* the same practical limitation from the browser) as `introspect()`.
|
|
483
|
+
*/
|
|
430
484
|
async validate(token) {
|
|
431
485
|
const accessToken = token ?? await this.getAccessToken();
|
|
432
|
-
const raw = await this.request("/auth/validate", {
|
|
433
|
-
method: "POST",
|
|
434
|
-
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : void 0
|
|
435
|
-
});
|
|
486
|
+
const raw = await this.request("/auth/validate", { method: "POST", body: { token: accessToken } });
|
|
436
487
|
return {
|
|
437
|
-
|
|
438
|
-
|
|
488
|
+
valid: raw.valid,
|
|
489
|
+
reason: raw.reason ?? null,
|
|
490
|
+
user: raw.profile ? normalizeUser(raw.profile) : null
|
|
439
491
|
};
|
|
440
492
|
}
|
|
441
493
|
};
|
|
442
494
|
function normalizeUser(raw) {
|
|
443
|
-
const { tenants, activeTenant, ...rest } = raw;
|
|
495
|
+
const { tenant, tenants, activeTenant, ...rest } = raw;
|
|
444
496
|
return {
|
|
445
497
|
...rest,
|
|
446
498
|
organizations: raw.organizations ?? tenants,
|
|
447
|
-
activeOrganization: raw.activeOrganization ?? activeTenant ?? null
|
|
499
|
+
activeOrganization: tenant ?? raw.activeOrganization ?? activeTenant ?? null
|
|
448
500
|
};
|
|
449
501
|
}
|
|
450
502
|
function createClient(options) {
|