@meistrari/auth-core 1.20.1 → 1.22.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/dist/index.d.mts +1280 -2942
- package/dist/index.d.ts +1280 -2942
- package/dist/index.mjs +575 -30
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2,13 +2,75 @@ import { decodeJwt, createRemoteJWKSet, jwtVerify } from 'jose';
|
|
|
2
2
|
import { apiKeyClient } from '@better-auth/api-key/client';
|
|
3
3
|
import { ssoClient } from '@better-auth/sso/client';
|
|
4
4
|
import { createAuthClient } from 'better-auth/client';
|
|
5
|
-
import { organizationClient, inferOrgAdditionalFields, twoFactorClient, jwtClient, adminClient, inferAdditionalFields } from 'better-auth/client/plugins';
|
|
5
|
+
import { organizationClient, inferOrgAdditionalFields, twoFactorClient, jwtClient, adminClient, inferAdditionalFields, emailOTPClient } from 'better-auth/client/plugins';
|
|
6
6
|
import { createAccessControl } from 'better-auth/plugins/access';
|
|
7
7
|
import { defaultStatements } from 'better-auth/plugins/organization/access';
|
|
8
|
-
import z
|
|
8
|
+
import z, { z as z$1 } from 'zod';
|
|
9
9
|
export { APIError } from 'better-auth';
|
|
10
10
|
|
|
11
|
-
const version = "1.
|
|
11
|
+
const version = "1.22.0";
|
|
12
|
+
|
|
13
|
+
function parsedEnumValues(schema) {
|
|
14
|
+
return Object.fromEntries(
|
|
15
|
+
Object.entries(schema.enum).map(([key, value]) => [
|
|
16
|
+
key,
|
|
17
|
+
schema.parse(value)
|
|
18
|
+
])
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const AMRBrand = Symbol("amr");
|
|
23
|
+
const AALBrand = Symbol("aal");
|
|
24
|
+
const ACRBrand = Symbol("acr");
|
|
25
|
+
const AuthMethodBrand = Symbol("authMethod");
|
|
26
|
+
const AMR = z.enum({
|
|
27
|
+
Pwd: "pwd",
|
|
28
|
+
Otp: "otp",
|
|
29
|
+
Oauth: "oauth",
|
|
30
|
+
Sso: "sso",
|
|
31
|
+
Saml: "saml"
|
|
32
|
+
}).brand(AMRBrand);
|
|
33
|
+
const AAL = z.enum({
|
|
34
|
+
Aal1: "aal1",
|
|
35
|
+
Aal2: "aal2",
|
|
36
|
+
Aal3: "aal3"
|
|
37
|
+
}).brand(AALBrand);
|
|
38
|
+
const ACR = z.enum({
|
|
39
|
+
Unspecified: "urn:tela:auth:unspecified"
|
|
40
|
+
}).brand(ACRBrand);
|
|
41
|
+
const AuthMethod = z.enum({
|
|
42
|
+
Password: "password",
|
|
43
|
+
EmailOTP: "email_otp",
|
|
44
|
+
OAuth: "oauth",
|
|
45
|
+
SAML: "saml",
|
|
46
|
+
Unknown: "unknown"
|
|
47
|
+
}).brand(AuthMethodBrand);
|
|
48
|
+
const AMRs = parsedEnumValues(AMR);
|
|
49
|
+
const AALs = parsedEnumValues(AAL);
|
|
50
|
+
const ACRs = parsedEnumValues(ACR);
|
|
51
|
+
const AuthMethods = parsedEnumValues(AuthMethod);
|
|
52
|
+
const assuranceFields = {
|
|
53
|
+
authMethod: {
|
|
54
|
+
type: "string",
|
|
55
|
+
required: true
|
|
56
|
+
},
|
|
57
|
+
amr: {
|
|
58
|
+
type: "json",
|
|
59
|
+
required: true
|
|
60
|
+
},
|
|
61
|
+
aal: {
|
|
62
|
+
type: "string",
|
|
63
|
+
required: true
|
|
64
|
+
},
|
|
65
|
+
acr: {
|
|
66
|
+
type: "string",
|
|
67
|
+
required: true
|
|
68
|
+
},
|
|
69
|
+
assuredAt: {
|
|
70
|
+
type: "date",
|
|
71
|
+
required: true
|
|
72
|
+
}
|
|
73
|
+
};
|
|
12
74
|
|
|
13
75
|
const statements = {
|
|
14
76
|
...defaultStatements,
|
|
@@ -68,21 +130,36 @@ const invitationAdditionalFields = {
|
|
|
68
130
|
required: false
|
|
69
131
|
}
|
|
70
132
|
};
|
|
71
|
-
const JWTPayloadUser = z.object({
|
|
72
|
-
id: z.string(),
|
|
73
|
-
name: z.string(),
|
|
74
|
-
image: z.string().nullable().optional(),
|
|
75
|
-
role: z.string().nullable()
|
|
133
|
+
const JWTPayloadUser = z$1.object({
|
|
134
|
+
id: z$1.string(),
|
|
135
|
+
name: z$1.string(),
|
|
136
|
+
image: z$1.string().nullable().optional(),
|
|
137
|
+
role: z$1.string().nullable()
|
|
138
|
+
});
|
|
139
|
+
const JWTPayloadWorkspace = z$1.object({
|
|
140
|
+
id: z$1.string(),
|
|
141
|
+
title: z$1.string()
|
|
76
142
|
});
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
143
|
+
const JWTPayloadAssurance = z$1.object({
|
|
144
|
+
authMethod: AuthMethod,
|
|
145
|
+
amr: AMR.array(),
|
|
146
|
+
aal: AAL,
|
|
147
|
+
acr: ACR,
|
|
148
|
+
assuredAt: z$1.string().datetime()
|
|
80
149
|
});
|
|
81
|
-
const
|
|
82
|
-
|
|
150
|
+
const DEFAULT_JWT_ASSURANCE = {
|
|
151
|
+
authMethod: AuthMethods.Unknown,
|
|
152
|
+
amr: [],
|
|
153
|
+
aal: AALs.Aal1,
|
|
154
|
+
acr: ACRs.Unspecified,
|
|
155
|
+
assuredAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
156
|
+
};
|
|
157
|
+
const JWTPayload = z$1.object({
|
|
158
|
+
email: z$1.string(),
|
|
83
159
|
user: JWTPayloadUser,
|
|
84
160
|
workspace: JWTPayloadWorkspace,
|
|
85
|
-
sessionKey: z.string()
|
|
161
|
+
sessionKey: z$1.string(),
|
|
162
|
+
assurance: JWTPayloadAssurance.optional().default(DEFAULT_JWT_ASSURANCE)
|
|
86
163
|
});
|
|
87
164
|
|
|
88
165
|
const DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
@@ -227,6 +304,34 @@ function applicationsPluginClient() {
|
|
|
227
304
|
};
|
|
228
305
|
}
|
|
229
306
|
|
|
307
|
+
function sessionAssurancePluginClient() {
|
|
308
|
+
return {
|
|
309
|
+
id: "session-assurance",
|
|
310
|
+
getActions: ($fetch) => ({
|
|
311
|
+
sessionAssurance: {
|
|
312
|
+
getSessionAssurance: async () => {
|
|
313
|
+
return await $fetch("/session-assurance");
|
|
314
|
+
},
|
|
315
|
+
stepUpWithPassword: async (body) => {
|
|
316
|
+
return await $fetch("/session-assurance/step-up/password", { method: "POST", body });
|
|
317
|
+
},
|
|
318
|
+
sendOtp: async () => {
|
|
319
|
+
return await $fetch("/session-assurance/step-up/otp/send", { method: "POST" });
|
|
320
|
+
},
|
|
321
|
+
verifyOtp: async (body) => {
|
|
322
|
+
return await $fetch("/session-assurance/step-up/otp/verify", { method: "POST", body });
|
|
323
|
+
},
|
|
324
|
+
beginOAuthStepUp: async (body) => {
|
|
325
|
+
return await $fetch("/session-assurance/step-up/oauth/begin", { method: "POST", body });
|
|
326
|
+
},
|
|
327
|
+
completeOAuthStepUp: async (body) => {
|
|
328
|
+
return await $fetch("/session-assurance/step-up/oauth/complete", { method: "POST", body });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
230
335
|
function customEndpointsPluginClient() {
|
|
231
336
|
return {
|
|
232
337
|
id: "custom-endpoints",
|
|
@@ -263,7 +368,9 @@ function createAPIClient(apiUrl, fetchOptions = {}) {
|
|
|
263
368
|
inferAdditionalFields({
|
|
264
369
|
user: userAdditionalFields
|
|
265
370
|
}),
|
|
266
|
-
applicationsPluginClient()
|
|
371
|
+
applicationsPluginClient(),
|
|
372
|
+
sessionAssurancePluginClient(),
|
|
373
|
+
emailOTPClient()
|
|
267
374
|
],
|
|
268
375
|
fetchOptions: {
|
|
269
376
|
...fetchOptions,
|
|
@@ -277,6 +384,127 @@ function createAPIClient(apiUrl, fetchOptions = {}) {
|
|
|
277
384
|
});
|
|
278
385
|
}
|
|
279
386
|
|
|
387
|
+
class AdminService {
|
|
388
|
+
/**
|
|
389
|
+
* Creates a new AdminService instance.
|
|
390
|
+
*
|
|
391
|
+
* @param client - The API client for making admin requests
|
|
392
|
+
*/
|
|
393
|
+
constructor(client) {
|
|
394
|
+
this.client = client;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Lists all organizations, optionally with member and application previews
|
|
398
|
+
* and totals when a preview size is requested.
|
|
399
|
+
*
|
|
400
|
+
* @param options - Pagination, ordering, search, and preview options
|
|
401
|
+
* @returns The paginated organization listing
|
|
402
|
+
*/
|
|
403
|
+
async listOrganizations(options = {}) {
|
|
404
|
+
return await this.client.$fetch("/admin/organizations", {
|
|
405
|
+
method: "GET",
|
|
406
|
+
query: {
|
|
407
|
+
...options.limit !== void 0 ? { limit: options.limit } : {},
|
|
408
|
+
...options.offset !== void 0 ? { offset: options.offset } : {},
|
|
409
|
+
...options.sortBy ? { sortBy: options.sortBy } : {},
|
|
410
|
+
...options.sortDirection ? { sortDirection: options.sortDirection } : {},
|
|
411
|
+
...options.query ? { query: options.query } : {},
|
|
412
|
+
...options.membersPreview !== void 0 ? { members_preview: options.membersPreview } : {},
|
|
413
|
+
...options.applicationsPreview !== void 0 ? { applications_preview: options.applicationsPreview } : {}
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Lists all Better Auth users from the user table.
|
|
419
|
+
*
|
|
420
|
+
* @param options - Pagination, search, filter, and ordering options
|
|
421
|
+
* @returns The paginated user listing
|
|
422
|
+
*/
|
|
423
|
+
async listUsers(options = {}) {
|
|
424
|
+
return await this.client.$fetch("/admin/list-users", {
|
|
425
|
+
method: "GET",
|
|
426
|
+
query: {
|
|
427
|
+
...options.limit !== void 0 ? { limit: options.limit } : {},
|
|
428
|
+
...options.offset !== void 0 ? { offset: options.offset } : {},
|
|
429
|
+
...options.searchValue ? { searchValue: options.searchValue } : {},
|
|
430
|
+
...options.searchField ? { searchField: options.searchField } : {},
|
|
431
|
+
...options.searchOperator ? { searchOperator: options.searchOperator } : {},
|
|
432
|
+
...options.sortBy ? { sortBy: options.sortBy } : {},
|
|
433
|
+
...options.sortDirection ? { sortDirection: options.sortDirection } : {},
|
|
434
|
+
...options.filterField ? { filterField: options.filterField } : {},
|
|
435
|
+
...options.filterValue !== void 0 ? { filterValue: options.filterValue } : {},
|
|
436
|
+
...options.filterOperator ? { filterOperator: options.filterOperator } : {}
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Fetches a single organization, optionally with adjacent IDs in
|
|
442
|
+
* the listing defined by the ordering options.
|
|
443
|
+
*
|
|
444
|
+
* @param options - Organization ID and listing adjacency options
|
|
445
|
+
* @returns The organization
|
|
446
|
+
*/
|
|
447
|
+
async getOrganization(options) {
|
|
448
|
+
const { data } = await this.client.$fetch(`/admin/organizations/${encodeURIComponent(options.organizationId)}`, {
|
|
449
|
+
method: "GET",
|
|
450
|
+
query: {
|
|
451
|
+
...options.sortBy ? { sortBy: options.sortBy } : {},
|
|
452
|
+
...options.sortDirection ? { sortDirection: options.sortDirection } : {},
|
|
453
|
+
...options.includeAdjacent ? { includeAdjacent: true } : {}
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
return data;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Lists active members for one organization as a global admin.
|
|
460
|
+
*
|
|
461
|
+
* @param options - Organization ID and pagination options
|
|
462
|
+
* @returns The paginated organization member listing
|
|
463
|
+
*/
|
|
464
|
+
async listOrganizationMembers(options) {
|
|
465
|
+
return await this.client.$fetch(`/admin/organizations/${encodeURIComponent(options.organizationId)}/members`, {
|
|
466
|
+
method: "GET",
|
|
467
|
+
query: {
|
|
468
|
+
...options.limit !== void 0 ? { limit: options.limit } : {},
|
|
469
|
+
...options.offset !== void 0 ? { offset: options.offset } : {}
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Creates an organization without adding the creator as a member.
|
|
475
|
+
*
|
|
476
|
+
* @param options - Organization name and optional slug, logo, metadata, and settings
|
|
477
|
+
* @returns The created organization
|
|
478
|
+
*/
|
|
479
|
+
async createOrganization(options) {
|
|
480
|
+
const { data } = await this.client.$fetch("/admin/organizations", {
|
|
481
|
+
method: "POST",
|
|
482
|
+
body: options
|
|
483
|
+
});
|
|
484
|
+
return data;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Updates an organization's profile and settings.
|
|
488
|
+
*
|
|
489
|
+
* @param options - Organization ID and the fields to update
|
|
490
|
+
* @returns The updated organization
|
|
491
|
+
*/
|
|
492
|
+
async updateOrganization(options) {
|
|
493
|
+
const { data } = await this.client.$fetch("/admin/organizations/update", {
|
|
494
|
+
method: "POST",
|
|
495
|
+
body: options
|
|
496
|
+
});
|
|
497
|
+
return data;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Bans a user from the auth system and invalidates all their sessions.
|
|
501
|
+
* @param userId - The ID of the user to ban
|
|
502
|
+
*/
|
|
503
|
+
async banUser(userId) {
|
|
504
|
+
await this.client.$fetch("/admin/ban-user", { method: "POST", body: { userId } });
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
280
508
|
class BaseError extends Error {
|
|
281
509
|
/** Machine-readable error code (e.g. `"INVALID_SOCIAL_PROVIDER"`). */
|
|
282
510
|
code;
|
|
@@ -308,6 +536,20 @@ class AuthorizationFlowError extends ApplicationError {
|
|
|
308
536
|
this.code = "AUTHORIZATION_FLOW_ERROR";
|
|
309
537
|
}
|
|
310
538
|
}
|
|
539
|
+
class ApplicationSessionAssuranceError extends ApplicationError {
|
|
540
|
+
payload;
|
|
541
|
+
currentAssurance;
|
|
542
|
+
requiredAssurance;
|
|
543
|
+
validStepUps;
|
|
544
|
+
constructor(payload = null, options) {
|
|
545
|
+
super("Session assurance does not meet application policy. Start a new device authorization flow.", options);
|
|
546
|
+
this.code = "APPLICATION_SESSION_ASSURANCE_NOT_MET";
|
|
547
|
+
this.payload = payload;
|
|
548
|
+
this.currentAssurance = payload?.currentAssurance ?? null;
|
|
549
|
+
this.requiredAssurance = payload?.requiredAssurance ?? null;
|
|
550
|
+
this.validStepUps = payload?.validStepUps ?? [];
|
|
551
|
+
}
|
|
552
|
+
}
|
|
311
553
|
class UserNotLoggedInError extends ApplicationError {
|
|
312
554
|
constructor(message, options) {
|
|
313
555
|
super(message, options);
|
|
@@ -345,6 +587,174 @@ class DeviceTransientServerError extends ApplicationError {
|
|
|
345
587
|
}
|
|
346
588
|
}
|
|
347
589
|
|
|
590
|
+
const OAuthStepUpProvider = z.enum(["google", "microsoft"]);
|
|
591
|
+
const StepUpOption = z.discriminatedUnion("method", [
|
|
592
|
+
z.object({ method: z.literal(AMRs.Pwd) }),
|
|
593
|
+
z.object({ method: z.literal(AMRs.Otp) }),
|
|
594
|
+
z.object({
|
|
595
|
+
method: z.literal(AMRs.Oauth),
|
|
596
|
+
providers: OAuthStepUpProvider.array().min(1)
|
|
597
|
+
})
|
|
598
|
+
]);
|
|
599
|
+
const SessionAssurancePolicy = z.object({
|
|
600
|
+
minAal: AAL,
|
|
601
|
+
acceptedAmr: AMR.array().min(1)
|
|
602
|
+
});
|
|
603
|
+
const SENSITIVE_ACTION_ASSURANCE_POLICY = {
|
|
604
|
+
minAal: AALs.Aal2,
|
|
605
|
+
acceptedAmr: [AMRs.Pwd, AMRs.Otp, AMRs.Oauth]
|
|
606
|
+
};
|
|
607
|
+
function getAalRank(aal) {
|
|
608
|
+
switch (aal) {
|
|
609
|
+
case AALs.Aal3:
|
|
610
|
+
return 3;
|
|
611
|
+
case AALs.Aal2:
|
|
612
|
+
return 2;
|
|
613
|
+
case AALs.Aal1:
|
|
614
|
+
return 1;
|
|
615
|
+
default:
|
|
616
|
+
return 0;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function maxAal(left, right) {
|
|
620
|
+
return getAalRank(left) >= getAalRank(right) ? left : right;
|
|
621
|
+
}
|
|
622
|
+
function isAssuranceSufficientForPolicy(assurance, policy) {
|
|
623
|
+
if (!assurance) {
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
if (getAalRank(assurance.aal) < getAalRank(policy.minAal)) {
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
if (!assurance.amr.length) {
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
return assurance.amr.some((amr) => policy.acceptedAmr.includes(amr));
|
|
633
|
+
}
|
|
634
|
+
function canStepUpWithAmr(assurance, amr) {
|
|
635
|
+
return !assurance.amr.includes(amr);
|
|
636
|
+
}
|
|
637
|
+
function mergeStepUpAssurance(assurance, amr, now = /* @__PURE__ */ new Date()) {
|
|
638
|
+
if (!canStepUpWithAmr(assurance, amr)) {
|
|
639
|
+
throw new Error(`AMR ${amr} is already present on the session`);
|
|
640
|
+
}
|
|
641
|
+
return {
|
|
642
|
+
...assurance,
|
|
643
|
+
amr: [...assurance.amr, amr],
|
|
644
|
+
aal: maxAal(assurance.aal, AALs.Aal2),
|
|
645
|
+
assuredAt: now.toISOString()
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const SessionAssuranceRequiredCode = z.enum([
|
|
650
|
+
"session_assurance_required",
|
|
651
|
+
"application_session_assurance_not_met"
|
|
652
|
+
]);
|
|
653
|
+
const SessionAssurancePayloadAssurance = z.preprocess((value) => {
|
|
654
|
+
if (typeof value !== "object" || value === null) {
|
|
655
|
+
return value;
|
|
656
|
+
}
|
|
657
|
+
const assurance = value;
|
|
658
|
+
if (!(assurance.assuredAt instanceof Date)) {
|
|
659
|
+
return value;
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
...assurance,
|
|
663
|
+
assuredAt: assurance.assuredAt.toISOString()
|
|
664
|
+
};
|
|
665
|
+
}, JWTPayloadAssurance);
|
|
666
|
+
const SessionAssuranceRequiredPayload = z.object({
|
|
667
|
+
code: SessionAssuranceRequiredCode,
|
|
668
|
+
currentAssurance: SessionAssurancePayloadAssurance,
|
|
669
|
+
requiredAssurance: SessionAssurancePolicy,
|
|
670
|
+
validStepUps: StepUpOption.array()
|
|
671
|
+
});
|
|
672
|
+
function getCandidatePayloads(error) {
|
|
673
|
+
const candidate = error;
|
|
674
|
+
return [
|
|
675
|
+
candidate.details,
|
|
676
|
+
candidate.error?.details,
|
|
677
|
+
candidate.data?.details,
|
|
678
|
+
candidate.body?.details,
|
|
679
|
+
candidate.payload,
|
|
680
|
+
error
|
|
681
|
+
].filter((payload) => payload !== void 0);
|
|
682
|
+
}
|
|
683
|
+
function toSdkCode(code) {
|
|
684
|
+
return code.toUpperCase();
|
|
685
|
+
}
|
|
686
|
+
function parseSessionAssuranceRequiredPayload(error) {
|
|
687
|
+
const seen = /* @__PURE__ */ new Set();
|
|
688
|
+
let current = error;
|
|
689
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
690
|
+
seen.add(current);
|
|
691
|
+
for (const payload of getCandidatePayloads(current)) {
|
|
692
|
+
const result = SessionAssuranceRequiredPayload.safeParse(payload);
|
|
693
|
+
if (result.success) {
|
|
694
|
+
return result.data;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
current = current.cause;
|
|
698
|
+
}
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
class SessionAssuranceRequiredError extends BaseError {
|
|
702
|
+
payload;
|
|
703
|
+
currentAssurance;
|
|
704
|
+
requiredAssurance;
|
|
705
|
+
validStepUps;
|
|
706
|
+
constructor(payload, options) {
|
|
707
|
+
super(toSdkCode(payload.code), "Session assurance is required for this action", options);
|
|
708
|
+
this.name = "SessionAssuranceRequiredError";
|
|
709
|
+
this.payload = payload;
|
|
710
|
+
this.currentAssurance = payload.currentAssurance;
|
|
711
|
+
this.requiredAssurance = payload.requiredAssurance;
|
|
712
|
+
this.validStepUps = payload.validStepUps;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const DEFAULT_SESSION_ASSURANCE_CONFIG = {
|
|
717
|
+
acceptedAmr: [
|
|
718
|
+
AMRs.Pwd,
|
|
719
|
+
AMRs.Oauth,
|
|
720
|
+
AMRs.Saml
|
|
721
|
+
],
|
|
722
|
+
minAal: AALs.Aal1
|
|
723
|
+
};
|
|
724
|
+
const DEFAULT_APPLICATION_CONFIG = {
|
|
725
|
+
version: 1,
|
|
726
|
+
sessionAssurance: DEFAULT_SESSION_ASSURANCE_CONFIG
|
|
727
|
+
};
|
|
728
|
+
const ApplicationSessionAssuranceConfig = z.object({
|
|
729
|
+
acceptedAmr: AMR.array().optional().default(DEFAULT_SESSION_ASSURANCE_CONFIG.acceptedAmr),
|
|
730
|
+
minAal: AAL.refine(
|
|
731
|
+
(aal) => aal !== AALs.Aal3,
|
|
732
|
+
"Application session assurance currently supports aal1 and aal2 policies"
|
|
733
|
+
).optional().default(DEFAULT_SESSION_ASSURANCE_CONFIG.minAal)
|
|
734
|
+
});
|
|
735
|
+
const ApplicationConfig = z.object({
|
|
736
|
+
version: z.literal(1),
|
|
737
|
+
sessionAssurance: ApplicationSessionAssuranceConfig.optional().default(DEFAULT_SESSION_ASSURANCE_CONFIG)
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
function allowsDefaultAmrPolicy(config) {
|
|
741
|
+
const acceptedAmr = new Set(config.sessionAssurance.acceptedAmr);
|
|
742
|
+
const defaultAcceptedAmr = DEFAULT_SESSION_ASSURANCE_CONFIG.acceptedAmr;
|
|
743
|
+
return config.sessionAssurance.minAal === DEFAULT_SESSION_ASSURANCE_CONFIG.minAal && defaultAcceptedAmr.every((amr) => acceptedAmr.has(amr));
|
|
744
|
+
}
|
|
745
|
+
function isSessionAssuranceSufficient(sessionAssurance, config = DEFAULT_APPLICATION_CONFIG) {
|
|
746
|
+
if (!sessionAssurance) {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
if (!sessionAssurance.amr.length && allowsDefaultAmrPolicy(config)) {
|
|
750
|
+
return getAalRank(sessionAssurance.aal) >= getAalRank(config.sessionAssurance.minAal);
|
|
751
|
+
}
|
|
752
|
+
if (!isAssuranceSufficientForPolicy(sessionAssurance, config.sessionAssurance)) {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
757
|
+
|
|
348
758
|
function parseErrorCode(error) {
|
|
349
759
|
if (!error || typeof error !== "object") {
|
|
350
760
|
return null;
|
|
@@ -376,6 +786,9 @@ function throwDeviceGrantError(error) {
|
|
|
376
786
|
if (code === "temporarily_unavailable") {
|
|
377
787
|
throw new DeviceTransientServerError({ cause: error });
|
|
378
788
|
}
|
|
789
|
+
if (code === "application_session_assurance_not_met") {
|
|
790
|
+
throw new ApplicationSessionAssuranceError(parseSessionAssuranceRequiredPayload(error), { cause: error });
|
|
791
|
+
}
|
|
379
792
|
throw new ApplicationError(parseErrorMessage(error), { cause: error });
|
|
380
793
|
}
|
|
381
794
|
class ApplicationService {
|
|
@@ -425,11 +838,26 @@ class ApplicationService {
|
|
|
425
838
|
* @param organizationId - The organization ID to start the authorization flow for
|
|
426
839
|
*/
|
|
427
840
|
async startAuthorizationFlow(applicationId, redirectUri, codeChallenge, organizationId) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
841
|
+
try {
|
|
842
|
+
const response = await this.client.applications.startAuthorizationFlow(applicationId, redirectUri, codeChallenge, organizationId);
|
|
843
|
+
if (!response.data) {
|
|
844
|
+
const payload = parseSessionAssuranceRequiredPayload(response.error);
|
|
845
|
+
if (payload) {
|
|
846
|
+
throw new SessionAssuranceRequiredError(payload, { cause: response.error });
|
|
847
|
+
}
|
|
848
|
+
throw new Error("No data returned from the API", { cause: response.error });
|
|
849
|
+
}
|
|
850
|
+
return response.data;
|
|
851
|
+
} catch (error) {
|
|
852
|
+
if (error instanceof SessionAssuranceRequiredError) {
|
|
853
|
+
throw error;
|
|
854
|
+
}
|
|
855
|
+
const payload = parseSessionAssuranceRequiredPayload(error);
|
|
856
|
+
if (payload) {
|
|
857
|
+
throw new SessionAssuranceRequiredError(payload, { cause: error });
|
|
858
|
+
}
|
|
859
|
+
throw error;
|
|
431
860
|
}
|
|
432
|
-
return response.data;
|
|
433
861
|
}
|
|
434
862
|
/**
|
|
435
863
|
* Starts a device authorization flow (RFC 8628).
|
|
@@ -505,6 +933,7 @@ class ApplicationService {
|
|
|
505
933
|
* @throws {DeviceAccessDeniedError} The user denied the request
|
|
506
934
|
* @throws {DeviceCodeExpiredError} The device code has expired
|
|
507
935
|
* @throws {DeviceTransientServerError} Transient server error — safe to retry
|
|
936
|
+
* @throws {ApplicationSessionAssuranceError} Assurance policy changed or is no longer met — start a new flow
|
|
508
937
|
*/
|
|
509
938
|
async exchangeDeviceCodeForTokens(deviceCode) {
|
|
510
939
|
try {
|
|
@@ -1103,22 +1532,123 @@ class ApiKeyService {
|
|
|
1103
1532
|
}
|
|
1104
1533
|
}
|
|
1105
1534
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1535
|
+
function getApplicationId(options) {
|
|
1536
|
+
if (!options?.applicationId) {
|
|
1537
|
+
throw new Error("applicationId is required");
|
|
1538
|
+
}
|
|
1539
|
+
return options.applicationId;
|
|
1540
|
+
}
|
|
1541
|
+
class EmailOTPService {
|
|
1542
|
+
/**
|
|
1543
|
+
* Creates a new EmailOTPService instance.
|
|
1544
|
+
*
|
|
1545
|
+
* @param client - The API client for making email OTP requests
|
|
1546
|
+
*/
|
|
1547
|
+
constructor(client) {
|
|
1548
|
+
this.client = client;
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Sends a sign-in verification OTP to an email address.
|
|
1552
|
+
*
|
|
1553
|
+
* @param email - Email address that should receive the OTP
|
|
1554
|
+
* @param options - Request configuration
|
|
1555
|
+
* @returns The underlying email OTP API response
|
|
1556
|
+
*/
|
|
1557
|
+
async sendVerificationOTP(email, options) {
|
|
1558
|
+
const applicationId = getApplicationId(options);
|
|
1559
|
+
return await this.client.emailOtp.sendVerificationOtp({ email, type: "sign-in" }, { query: { applicationId } });
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Completes email OTP sign-in with a one-time passcode.
|
|
1563
|
+
*
|
|
1564
|
+
* @param email - Email address that received the OTP
|
|
1565
|
+
* @param otp - One-time passcode from the sign-in email
|
|
1566
|
+
* @param options - Request configuration
|
|
1567
|
+
* @returns The underlying email OTP sign-in API response
|
|
1568
|
+
*/
|
|
1569
|
+
async signIn(email, otp, options) {
|
|
1570
|
+
const applicationId = getApplicationId(options);
|
|
1571
|
+
return await this.client.signIn.emailOtp({ email, otp }, { query: { applicationId } });
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
const ClientJWTPayloadAssurance = JWTPayloadAssurance.extend({
|
|
1576
|
+
assuredAt: z.date()
|
|
1577
|
+
});
|
|
1578
|
+
const SessionAssuranceResponse = z.object({
|
|
1579
|
+
assurance: ClientJWTPayloadAssurance,
|
|
1580
|
+
validStepUps: StepUpOption.array()
|
|
1581
|
+
});
|
|
1582
|
+
const OAuthBeginStepUpResponse = z.object({
|
|
1583
|
+
stepUpToken: z.string(),
|
|
1584
|
+
callbackURL: z.string()
|
|
1585
|
+
});
|
|
1586
|
+
const OAuthCompleteStepUpResponse = SessionAssuranceResponse.extend({
|
|
1587
|
+
returnPath: z.string()
|
|
1588
|
+
});
|
|
1589
|
+
const SendOtpResponse = z.object({
|
|
1590
|
+
success: z.boolean()
|
|
1591
|
+
});
|
|
1592
|
+
function isBetterAuthResponse(response) {
|
|
1593
|
+
return typeof response === "object" && response !== null && "data" in response && "error" in response;
|
|
1594
|
+
}
|
|
1595
|
+
function getResponseData(response) {
|
|
1596
|
+
if (!isBetterAuthResponse(response)) {
|
|
1597
|
+
return response;
|
|
1598
|
+
}
|
|
1599
|
+
if (response.data !== null && response.data !== void 0) {
|
|
1600
|
+
return response.data;
|
|
1601
|
+
}
|
|
1602
|
+
throw new Error("No data returned from the API", { cause: response.error });
|
|
1603
|
+
}
|
|
1604
|
+
function parseResponse(schema, response) {
|
|
1605
|
+
return schema.parse(getResponseData(response));
|
|
1606
|
+
}
|
|
1607
|
+
class SessionAssuranceService {
|
|
1608
|
+
constructor(client) {
|
|
1609
|
+
this.client = client;
|
|
1610
|
+
}
|
|
1611
|
+
async get() {
|
|
1612
|
+
return parseResponse(SessionAssuranceResponse, await this.client.sessionAssurance.getSessionAssurance());
|
|
1613
|
+
}
|
|
1614
|
+
async stepUpWithPassword(password) {
|
|
1615
|
+
return parseResponse(SessionAssuranceResponse, await this.client.sessionAssurance.stepUpWithPassword({ password }));
|
|
1616
|
+
}
|
|
1617
|
+
async sendOtp() {
|
|
1618
|
+
return parseResponse(SendOtpResponse, await this.client.sessionAssurance.sendOtp());
|
|
1619
|
+
}
|
|
1620
|
+
async verifyOtp(otp) {
|
|
1621
|
+
return parseResponse(SessionAssuranceResponse, await this.client.sessionAssurance.verifyOtp({ otp }));
|
|
1622
|
+
}
|
|
1623
|
+
async beginOAuthStepUp(params) {
|
|
1624
|
+
return parseResponse(OAuthBeginStepUpResponse, await this.client.sessionAssurance.beginOAuthStepUp(params));
|
|
1625
|
+
}
|
|
1626
|
+
async completeOAuthStepUp(stepUpToken) {
|
|
1627
|
+
return parseResponse(OAuthCompleteStepUpResponse, await this.client.sessionAssurance.completeOAuthStepUp({ stepUpToken }));
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
const ApiKeyMetadata = z.object({
|
|
1632
|
+
user: z.object({
|
|
1633
|
+
id: z.string(),
|
|
1634
|
+
email: z.string()
|
|
1110
1635
|
}),
|
|
1111
|
-
workspace: z
|
|
1112
|
-
id: z
|
|
1113
|
-
title: z
|
|
1636
|
+
workspace: z.object({
|
|
1637
|
+
id: z.string(),
|
|
1638
|
+
title: z.string()
|
|
1114
1639
|
}),
|
|
1115
|
-
application: z
|
|
1116
|
-
id: z
|
|
1117
|
-
name: z
|
|
1640
|
+
application: z.object({
|
|
1641
|
+
id: z.string(),
|
|
1642
|
+
name: z.string()
|
|
1118
1643
|
}).optional().nullable()
|
|
1119
1644
|
});
|
|
1120
1645
|
|
|
1121
1646
|
class AuthClient {
|
|
1647
|
+
/**
|
|
1648
|
+
* Configured Better Auth client used by the public service wrappers.
|
|
1649
|
+
*
|
|
1650
|
+
* @internal
|
|
1651
|
+
*/
|
|
1122
1652
|
client;
|
|
1123
1653
|
/**
|
|
1124
1654
|
* Session management service for authentication operations
|
|
@@ -1136,6 +1666,18 @@ class AuthClient {
|
|
|
1136
1666
|
* API key management service for API key operations
|
|
1137
1667
|
*/
|
|
1138
1668
|
apiKey;
|
|
1669
|
+
/**
|
|
1670
|
+
* Email OTP service for passwordless sign-in operations
|
|
1671
|
+
*/
|
|
1672
|
+
emailOtp;
|
|
1673
|
+
/**
|
|
1674
|
+
* Session assurance service for step-up operations
|
|
1675
|
+
*/
|
|
1676
|
+
sessionAssurance;
|
|
1677
|
+
/**
|
|
1678
|
+
* Admin service for global admin operations
|
|
1679
|
+
*/
|
|
1680
|
+
admin;
|
|
1139
1681
|
/**
|
|
1140
1682
|
* Creates a new AuthClient instance.
|
|
1141
1683
|
*
|
|
@@ -1148,6 +1690,9 @@ class AuthClient {
|
|
|
1148
1690
|
this.organization = new OrganizationService(this.client);
|
|
1149
1691
|
this.application = new ApplicationService(this.client);
|
|
1150
1692
|
this.apiKey = new ApiKeyService(this.client);
|
|
1693
|
+
this.emailOtp = new EmailOTPService(this.client);
|
|
1694
|
+
this.sessionAssurance = new SessionAssuranceService(this.client);
|
|
1695
|
+
this.admin = new AdminService(this.client);
|
|
1151
1696
|
}
|
|
1152
1697
|
}
|
|
1153
1698
|
|
|
@@ -1184,4 +1729,4 @@ function extractTokenPayload(token) {
|
|
|
1184
1729
|
return payload;
|
|
1185
1730
|
}
|
|
1186
1731
|
|
|
1187
|
-
export { ApiKeyMetadata, ApplicationError, AuthClient, AuthorizationFlowError, DeviceAccessDeniedError, DeviceAuthorizationPendingError, DeviceAuthorizationSlowDownError, DeviceCodeExpiredError, DeviceTransientServerError, EmailRequired, InvalidCallbackURL, InvalidSocialProvider, JWTPayload, JWTPayloadUser, JWTPayloadWorkspace, RefreshTokenExpiredError, Roles, UserNotLoggedInError, ac, extractTokenPayload, invitationAdditionalFields, isTokenExpired, memberAdditionalFields, organizationAdditionalFields, rolesAccessControl, userAdditionalFields, validateToken };
|
|
1732
|
+
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, SessionAssurancePolicy, SessionAssuranceRequiredError, SessionAssuranceRequiredPayload, SessionAssuranceService, StepUpOption, UserNotLoggedInError, ac, assuranceFields, canStepUpWithAmr, extractTokenPayload, getAalRank, invitationAdditionalFields, isAssuranceSufficientForPolicy, isSessionAssuranceSufficient, isTokenExpired, memberAdditionalFields, mergeStepUpAssurance, organizationAdditionalFields, parseSessionAssuranceRequiredPayload, rolesAccessControl, sessionAssurancePluginClient, userAdditionalFields, validateToken };
|