@meistrari/auth-core 1.35.0 → 1.37.0
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 +6 -0
- package/dist/index.d.mts +230 -53
- package/dist/index.d.ts +230 -53
- package/dist/index.mjs +233 -27
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -8,7 +8,20 @@ import { defaultStatements } from 'better-auth/plugins/organization/access';
|
|
|
8
8
|
import z, { z as z$1 } from 'zod';
|
|
9
9
|
export { APIError } from 'better-auth';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
function isTokenExpired(token, expiresInMs) {
|
|
12
|
+
const payload = JSON.parse(atob(token.split(".")[1] ?? ""));
|
|
13
|
+
if (!payload.exp)
|
|
14
|
+
return false;
|
|
15
|
+
const nowInSeconds = Date.now() / 1e3;
|
|
16
|
+
const expirationTime = payload.exp;
|
|
17
|
+
if (expiresInMs !== void 0) {
|
|
18
|
+
const futureTimeInSeconds = nowInSeconds + expiresInMs / 1e3;
|
|
19
|
+
return futureTimeInSeconds >= expirationTime;
|
|
20
|
+
}
|
|
21
|
+
return nowInSeconds > expirationTime;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const version = "1.37.0";
|
|
12
25
|
|
|
13
26
|
function parsedEnumValues(schema) {
|
|
14
27
|
return Object.fromEntries(
|
|
@@ -187,15 +200,17 @@ const JWTPayload = z$1.object({
|
|
|
187
200
|
});
|
|
188
201
|
|
|
189
202
|
const DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
203
|
+
const CLIENT_CREDENTIALS_GRANT = "client_credentials";
|
|
190
204
|
function normalizeOrganizationsResponse(response) {
|
|
191
205
|
if (Array.isArray(response)) {
|
|
192
206
|
return {
|
|
193
207
|
data: response
|
|
194
208
|
};
|
|
195
209
|
}
|
|
210
|
+
const candidate = response;
|
|
196
211
|
return {
|
|
197
|
-
...
|
|
198
|
-
data: Array.isArray(
|
|
212
|
+
...candidate,
|
|
213
|
+
data: Array.isArray(candidate.data) ? candidate.data : []
|
|
199
214
|
};
|
|
200
215
|
}
|
|
201
216
|
function applicationsPluginClient() {
|
|
@@ -273,6 +288,17 @@ function applicationsPluginClient() {
|
|
|
273
288
|
}
|
|
274
289
|
});
|
|
275
290
|
},
|
|
291
|
+
exchangeClientCredentialsForToken: async (credentials) => {
|
|
292
|
+
return await $fetch("/applications/token", {
|
|
293
|
+
method: "POST",
|
|
294
|
+
body: {
|
|
295
|
+
grantType: CLIENT_CREDENTIALS_GRANT,
|
|
296
|
+
clientId: credentials.clientId,
|
|
297
|
+
clientSecret: credentials.clientSecret,
|
|
298
|
+
organizationId: credentials.organizationId
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
},
|
|
276
302
|
completeAuthorizationFlow: async (code, codeVerifier) => {
|
|
277
303
|
return await $fetch("/applications/token", {
|
|
278
304
|
method: "POST",
|
|
@@ -408,12 +434,23 @@ function createAPIClient(apiUrl, fetchOptions = {}) {
|
|
|
408
434
|
});
|
|
409
435
|
}
|
|
410
436
|
|
|
411
|
-
|
|
437
|
+
class ApplicationRouteError extends Error {
|
|
438
|
+
code;
|
|
439
|
+
details;
|
|
440
|
+
status;
|
|
441
|
+
constructor(response, body) {
|
|
442
|
+
super(body?.error ?? body?.message ?? response.statusText);
|
|
443
|
+
this.name = "ApplicationRouteError";
|
|
444
|
+
this.status = response.status;
|
|
445
|
+
this.code = body?.code;
|
|
446
|
+
this.details = body?.details;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
async function readErrorBody(response) {
|
|
412
450
|
try {
|
|
413
|
-
|
|
414
|
-
return body.error ?? body.message ?? response.statusText;
|
|
451
|
+
return await response.json();
|
|
415
452
|
} catch {
|
|
416
|
-
return
|
|
453
|
+
return null;
|
|
417
454
|
}
|
|
418
455
|
}
|
|
419
456
|
class AdminService {
|
|
@@ -459,7 +496,7 @@ class AdminService {
|
|
|
459
496
|
...request.body !== void 0 ? { body: JSON.stringify(request.body) } : {}
|
|
460
497
|
});
|
|
461
498
|
if (!response.ok)
|
|
462
|
-
throw new
|
|
499
|
+
throw new ApplicationRouteError(response, await readErrorBody(response));
|
|
463
500
|
return await response.json();
|
|
464
501
|
}
|
|
465
502
|
/**
|
|
@@ -546,6 +583,53 @@ class AdminService {
|
|
|
546
583
|
body: options
|
|
547
584
|
});
|
|
548
585
|
}
|
|
586
|
+
/**
|
|
587
|
+
* Lists client secrets for an application.
|
|
588
|
+
*
|
|
589
|
+
* @param applicationId - ID of the application to inspect
|
|
590
|
+
* @returns Secret metadata without plaintext values
|
|
591
|
+
*/
|
|
592
|
+
async listApplicationSecrets(applicationId) {
|
|
593
|
+
return await this.requestApplicationRoute(`applications/${encodeURIComponent(applicationId)}/secrets`);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Creates a client secret for an application.
|
|
597
|
+
*
|
|
598
|
+
* @param applicationId - ID of the application to update
|
|
599
|
+
* @param options - Optional secret display name
|
|
600
|
+
* @returns The created secret, including the plaintext value shown only once
|
|
601
|
+
*/
|
|
602
|
+
async createApplicationSecret(applicationId, options = {}) {
|
|
603
|
+
return await this.requestApplicationRoute(`applications/${encodeURIComponent(applicationId)}/secrets`, {
|
|
604
|
+
method: "POST",
|
|
605
|
+
body: options
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Deletes one client secret for an application.
|
|
610
|
+
*
|
|
611
|
+
* @param applicationId - ID of the application to update
|
|
612
|
+
* @param secretId - ID of the secret to delete
|
|
613
|
+
* @returns The deleted secret metadata
|
|
614
|
+
*/
|
|
615
|
+
async deleteApplicationSecret(applicationId, secretId) {
|
|
616
|
+
return await this.requestApplicationRoute(
|
|
617
|
+
`applications/${encodeURIComponent(applicationId)}/secrets/${encodeURIComponent(secretId)}`,
|
|
618
|
+
{ method: "DELETE" }
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Retires an application's service-account identity.
|
|
623
|
+
*
|
|
624
|
+
* @param applicationId - ID of the application to update
|
|
625
|
+
* @returns The updated application
|
|
626
|
+
*/
|
|
627
|
+
async retireApplicationServiceAccount(applicationId) {
|
|
628
|
+
return await this.requestApplicationRoute(
|
|
629
|
+
`applications/${encodeURIComponent(applicationId)}/service-account`,
|
|
630
|
+
{ method: "DELETE" }
|
|
631
|
+
);
|
|
632
|
+
}
|
|
549
633
|
/**
|
|
550
634
|
* Lists all organizations, optionally with member and application previews
|
|
551
635
|
* and totals when a preview size is requested.
|
|
@@ -1359,6 +1443,22 @@ class ApplicationService {
|
|
|
1359
1443
|
throwDeviceGrantError(error);
|
|
1360
1444
|
}
|
|
1361
1445
|
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Exchanges application client credentials for a service-account access token.
|
|
1448
|
+
*
|
|
1449
|
+
* The returned token is scoped to the requested organization and cannot be
|
|
1450
|
+
* refreshed. Exchange the credentials again when a new token is needed.
|
|
1451
|
+
*
|
|
1452
|
+
* @param credentials - Application credentials and target organization
|
|
1453
|
+
* @returns A short-lived access token with service-account and organization details
|
|
1454
|
+
*/
|
|
1455
|
+
async exchangeClientCredentialsForToken(credentials) {
|
|
1456
|
+
const response = await this.client.applications.exchangeClientCredentialsForToken(credentials);
|
|
1457
|
+
if (!response.data) {
|
|
1458
|
+
throw new Error("No data returned from the API", { cause: response.error });
|
|
1459
|
+
}
|
|
1460
|
+
return response.data;
|
|
1461
|
+
}
|
|
1362
1462
|
/**
|
|
1363
1463
|
* Completes an authorization flow for a specific application.
|
|
1364
1464
|
*
|
|
@@ -1472,14 +1572,10 @@ class OrganizationService {
|
|
|
1472
1572
|
});
|
|
1473
1573
|
return organization;
|
|
1474
1574
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
*/
|
|
1480
|
-
async listOrganizations() {
|
|
1481
|
-
const organizations = await this.client.organization.list();
|
|
1482
|
-
return organizations;
|
|
1575
|
+
async listOrganizations(options = {}) {
|
|
1576
|
+
return await this.client.organization.list({
|
|
1577
|
+
query: options.include?.length ? { include: options.include.join(",") } : void 0
|
|
1578
|
+
});
|
|
1483
1579
|
}
|
|
1484
1580
|
/**
|
|
1485
1581
|
* Sets the active organization for the current user session.
|
|
@@ -2162,18 +2258,128 @@ class AuthClient {
|
|
|
2162
2258
|
}
|
|
2163
2259
|
}
|
|
2164
2260
|
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2261
|
+
const TOKEN_REFRESH_WINDOW_MS = 6e4;
|
|
2262
|
+
const DEFAULT_INITIAL_BACKOFF_MS = 100;
|
|
2263
|
+
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
2264
|
+
const DEFAULT_MAX_ORGANIZATIONS = 300;
|
|
2265
|
+
function validateMaxAttempts(maxAttempts) {
|
|
2266
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
|
|
2267
|
+
throw new RangeError("maxAttempts must be a positive integer");
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
function validateInitialBackoff(initialBackoffMs) {
|
|
2271
|
+
if (!Number.isFinite(initialBackoffMs) || initialBackoffMs < 0) {
|
|
2272
|
+
throw new RangeError("initialBackoffMs must be a non-negative finite number");
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
function getErrorStatus(error) {
|
|
2276
|
+
const candidates = [error];
|
|
2277
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2278
|
+
while (candidates.length > 0) {
|
|
2279
|
+
const candidate = candidates.shift();
|
|
2280
|
+
if (!candidate || typeof candidate !== "object" || visited.has(candidate))
|
|
2281
|
+
continue;
|
|
2282
|
+
visited.add(candidate);
|
|
2283
|
+
const errorCandidate = candidate;
|
|
2284
|
+
if (typeof errorCandidate.status === "number")
|
|
2285
|
+
return errorCandidate.status;
|
|
2286
|
+
candidates.push(errorCandidate.cause, errorCandidate.error);
|
|
2287
|
+
}
|
|
2288
|
+
return void 0;
|
|
2289
|
+
}
|
|
2290
|
+
function isTransientError(error) {
|
|
2291
|
+
const status = getErrorStatus(error);
|
|
2292
|
+
return status === void 0 || status === 429 || status >= 500 && status < 600;
|
|
2293
|
+
}
|
|
2294
|
+
class ApplicationTokenStorage {
|
|
2295
|
+
auth;
|
|
2296
|
+
clientId;
|
|
2297
|
+
clientSecret;
|
|
2298
|
+
initialBackoffMs;
|
|
2299
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
2300
|
+
maxAttempts;
|
|
2301
|
+
maxOrganizations;
|
|
2302
|
+
tokens = /* @__PURE__ */ new Map();
|
|
2303
|
+
/** Creates an application token cache for one client-credentials identity. */
|
|
2304
|
+
constructor({
|
|
2305
|
+
authApiUrl,
|
|
2306
|
+
clientId,
|
|
2307
|
+
clientSecret,
|
|
2308
|
+
initialBackoffMs = DEFAULT_INITIAL_BACKOFF_MS,
|
|
2309
|
+
maxAttempts = DEFAULT_MAX_ATTEMPTS,
|
|
2310
|
+
maxOrganizations = DEFAULT_MAX_ORGANIZATIONS
|
|
2311
|
+
}) {
|
|
2312
|
+
if (!Number.isInteger(maxOrganizations) || maxOrganizations < 1) {
|
|
2313
|
+
throw new RangeError("maxOrganizations must be a positive integer");
|
|
2314
|
+
}
|
|
2315
|
+
validateMaxAttempts(maxAttempts);
|
|
2316
|
+
validateInitialBackoff(initialBackoffMs);
|
|
2317
|
+
this.auth = new AuthClient(authApiUrl);
|
|
2318
|
+
this.clientId = clientId;
|
|
2319
|
+
this.clientSecret = clientSecret;
|
|
2320
|
+
this.initialBackoffMs = initialBackoffMs;
|
|
2321
|
+
this.maxAttempts = maxAttempts;
|
|
2322
|
+
this.maxOrganizations = maxOrganizations;
|
|
2323
|
+
}
|
|
2324
|
+
/** Returns a fresh access token scoped to the requested organization. */
|
|
2325
|
+
async getToken(organizationId, {
|
|
2326
|
+
initialBackoffMs = this.initialBackoffMs,
|
|
2327
|
+
maxAttempts = this.maxAttempts
|
|
2328
|
+
} = {}) {
|
|
2329
|
+
validateMaxAttempts(maxAttempts);
|
|
2330
|
+
validateInitialBackoff(initialBackoffMs);
|
|
2331
|
+
const cachedToken = this.tokens.get(organizationId);
|
|
2332
|
+
if (cachedToken && !isTokenExpired(cachedToken, TOKEN_REFRESH_WINDOW_MS)) {
|
|
2333
|
+
this.tokens.delete(organizationId);
|
|
2334
|
+
this.tokens.set(organizationId, cachedToken);
|
|
2335
|
+
return cachedToken;
|
|
2336
|
+
}
|
|
2337
|
+
const pendingExchange = this.inFlight.get(organizationId);
|
|
2338
|
+
if (pendingExchange) {
|
|
2339
|
+
return await pendingExchange;
|
|
2340
|
+
}
|
|
2341
|
+
const exchange = this.exchangeTokenWithRetry(organizationId, maxAttempts, initialBackoffMs).then((token) => {
|
|
2342
|
+
this.tokens.delete(organizationId);
|
|
2343
|
+
this.tokens.set(organizationId, token);
|
|
2344
|
+
if (this.tokens.size > this.maxOrganizations) {
|
|
2345
|
+
const leastRecentlyUsedOrganization = this.tokens.keys().next().value;
|
|
2346
|
+
if (leastRecentlyUsedOrganization !== void 0) {
|
|
2347
|
+
this.tokens.delete(leastRecentlyUsedOrganization);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
return token;
|
|
2351
|
+
}).finally(() => this.inFlight.delete(organizationId));
|
|
2352
|
+
this.inFlight.set(organizationId, exchange);
|
|
2353
|
+
return await exchange;
|
|
2354
|
+
}
|
|
2355
|
+
async exchangeTokenWithRetry(organizationId, maxAttempts, initialBackoffMs) {
|
|
2356
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2357
|
+
try {
|
|
2358
|
+
return await this.exchangeToken(organizationId);
|
|
2359
|
+
} catch (error) {
|
|
2360
|
+
if (attempt === maxAttempts || !isTransientError(error))
|
|
2361
|
+
throw error;
|
|
2362
|
+
const exponentialDelay = initialBackoffMs * 2 ** (attempt - 1);
|
|
2363
|
+
await this.wait(Math.random() * exponentialDelay);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
throw new Error("Client credentials exchange exhausted without a result");
|
|
2367
|
+
}
|
|
2368
|
+
/** Performs the client-credentials exchange for one organization. */
|
|
2369
|
+
async exchangeToken(organizationId) {
|
|
2370
|
+
const { accessToken } = await this.auth.application.exchangeClientCredentialsForToken({
|
|
2371
|
+
clientId: this.clientId,
|
|
2372
|
+
clientSecret: this.clientSecret,
|
|
2373
|
+
organizationId
|
|
2374
|
+
});
|
|
2375
|
+
return accessToken;
|
|
2376
|
+
}
|
|
2377
|
+
/** Waits before the next exchange attempt. */
|
|
2378
|
+
async wait(delayMs) {
|
|
2379
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2174
2380
|
}
|
|
2175
|
-
return nowInSeconds > expirationTime;
|
|
2176
2381
|
}
|
|
2382
|
+
|
|
2177
2383
|
async function validateToken(token, apiUrl) {
|
|
2178
2384
|
if (isTokenExpired(token)) {
|
|
2179
2385
|
return false;
|
|
@@ -2195,4 +2401,4 @@ function extractTokenPayload(token) {
|
|
|
2195
2401
|
return payload;
|
|
2196
2402
|
}
|
|
2197
2403
|
|
|
2198
|
-
export { AAL, AALBrand, AALs, ACR, ACRBrand, ACRs, AMR, AMRBrand, AMRs, ApiKeyMetadata, ApplicationConfig, ApplicationError, ApplicationSessionAssuranceConfig, ApplicationSessionAssuranceError, AuthClient, AuthMethod, AuthMethodBrand, AuthMethods, AuthorizationFlowError, DEFAULT_APPLICATION_CONFIG, DEFAULT_JWT_ASSURANCE, DEFAULT_SESSION_ASSURANCE_CONFIG, DeviceAccessDeniedError, DeviceAuthorizationPendingError, DeviceAuthorizationSlowDownError, DeviceCodeExpiredError, DeviceTransientServerError, EmailOTPService, EmailRequired, InvalidCallbackURL, InvalidSocialProvider, JWTPayload, JWTPayloadAssurance, JWTPayloadUser, JWTPayloadWorkspace, OAuthStepUpProvider, RefreshTokenExpiredError, Roles, SENSITIVE_ACTION_ASSURANCE_POLICY, STEP_UP_MAX_AGE_SECONDS, SessionAssurancePolicy, SessionAssuranceRequiredError, SessionAssuranceRequiredPayload, SessionAssuranceService, StepUpOption, UserNotLoggedInError, ac, assuranceFields, canStepUpWithAmr, extractTokenPayload, getAalRank, invitationAdditionalFields, isAssuranceSufficientForPolicy, isSessionAssuranceSufficient, isTokenExpired, memberAdditionalFields, mergeStepUpAssurance, organizationAdditionalFields, parseSessionAssuranceRequiredPayload, rolesAccessControl, sessionAssurancePluginClient, userAdditionalFields, validateToken };
|
|
2404
|
+
export { AAL, AALBrand, AALs, ACR, ACRBrand, ACRs, AMR, AMRBrand, AMRs, ApiKeyMetadata, ApplicationConfig, ApplicationError, ApplicationSessionAssuranceConfig, ApplicationSessionAssuranceError, ApplicationTokenStorage, AuthClient, AuthMethod, AuthMethodBrand, AuthMethods, AuthorizationFlowError, DEFAULT_APPLICATION_CONFIG, DEFAULT_JWT_ASSURANCE, DEFAULT_SESSION_ASSURANCE_CONFIG, DeviceAccessDeniedError, DeviceAuthorizationPendingError, DeviceAuthorizationSlowDownError, DeviceCodeExpiredError, DeviceTransientServerError, EmailOTPService, EmailRequired, InvalidCallbackURL, InvalidSocialProvider, JWTPayload, JWTPayloadAssurance, JWTPayloadUser, JWTPayloadWorkspace, OAuthStepUpProvider, RefreshTokenExpiredError, Roles, SENSITIVE_ACTION_ASSURANCE_POLICY, STEP_UP_MAX_AGE_SECONDS, SessionAssurancePolicy, SessionAssuranceRequiredError, SessionAssuranceRequiredPayload, SessionAssuranceService, StepUpOption, UserNotLoggedInError, ac, assuranceFields, canStepUpWithAmr, extractTokenPayload, getAalRank, invitationAdditionalFields, isAssuranceSufficientForPolicy, isSessionAssuranceSufficient, isTokenExpired, memberAdditionalFields, mergeStepUpAssurance, organizationAdditionalFields, parseSessionAssuranceRequiredPayload, rolesAccessControl, sessionAssurancePluginClient, userAdditionalFields, validateToken };
|