@better-auth/sso 1.4.0-beta.9 → 1.4.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/.turbo/turbo-build.log +14 -15
- package/dist/client.d.mts +18 -9
- package/dist/client.mjs +8 -6
- package/dist/index-D-JmJR9N.d.mts +853 -0
- package/dist/index.d.mts +2 -959
- package/dist/index.mjs +1481 -1649
- package/package.json +26 -19
- package/src/client.ts +20 -3
- package/src/domain-verification.test.ts +550 -0
- package/src/index.ts +83 -2210
- package/src/oidc.test.ts +8 -5
- package/src/routes/domain-verification.ts +275 -0
- package/src/routes/sso.ts +2182 -0
- package/src/saml.test.ts +226 -20
- package/src/types.ts +256 -0
- package/src/utils.ts +10 -0
- package/tsconfig.json +6 -4
- package/tsdown.config.ts +8 -0
- package/vitest.config.ts +3 -0
- package/build.config.ts +0 -12
- package/dist/client.cjs +0 -10
- package/dist/client.d.cts +0 -11
- package/dist/client.d.ts +0 -11
- package/dist/index.cjs +0 -1672
- package/dist/index.d.cts +0 -959
- package/dist/index.d.ts +0 -959
package/src/saml.test.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
afterAll,
|
|
3
|
-
beforeAll,
|
|
4
|
-
beforeEach,
|
|
5
|
-
describe,
|
|
6
|
-
expect,
|
|
7
|
-
it,
|
|
8
|
-
vi,
|
|
9
|
-
} from "vitest";
|
|
1
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
10
2
|
import { betterAuth } from "better-auth";
|
|
11
3
|
import { memoryAdapter } from "better-auth/adapters/memory";
|
|
12
4
|
import { createAuthClient } from "better-auth/client";
|
|
13
|
-
import { betterFetch } from "@better-fetch/fetch";
|
|
14
5
|
import { setCookieToHeader } from "better-auth/cookies";
|
|
15
6
|
import { bearer } from "better-auth/plugins";
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import * as saml from "samlify";
|
|
7
|
+
import { getTestInstance } from "better-auth/test";
|
|
8
|
+
import bodyParser from "body-parser";
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
20
10
|
import type {
|
|
21
11
|
Application as ExpressApp,
|
|
22
12
|
Request as ExpressRequest,
|
|
23
13
|
Response as ExpressResponse,
|
|
24
14
|
} from "express";
|
|
25
15
|
import express from "express";
|
|
26
|
-
import
|
|
27
|
-
import
|
|
28
|
-
import {
|
|
16
|
+
import type { createServer } from "http";
|
|
17
|
+
import * as saml from "samlify";
|
|
18
|
+
import {
|
|
19
|
+
afterAll,
|
|
20
|
+
beforeAll,
|
|
21
|
+
beforeEach,
|
|
22
|
+
describe,
|
|
23
|
+
expect,
|
|
24
|
+
it,
|
|
25
|
+
vi,
|
|
26
|
+
} from "vitest";
|
|
27
|
+
import { sso } from ".";
|
|
28
|
+
import { ssoClient } from "./client";
|
|
29
29
|
|
|
30
30
|
const spMetadata = `
|
|
31
31
|
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:3001/api/sso/saml2/sp/metadata">
|
|
@@ -926,7 +926,7 @@ describe("SAML SSO", async () => {
|
|
|
926
926
|
});
|
|
927
927
|
|
|
928
928
|
it("should not allow creating a provider if limit is set to 0", async () => {
|
|
929
|
-
const { auth, signInWithTestUser } = await
|
|
929
|
+
const { auth, signInWithTestUser } = await getTestInstance({
|
|
930
930
|
plugins: [sso({ providersLimit: 0 })],
|
|
931
931
|
});
|
|
932
932
|
const { headers } = await signInWithTestUser();
|
|
@@ -957,7 +957,7 @@ describe("SAML SSO", async () => {
|
|
|
957
957
|
});
|
|
958
958
|
|
|
959
959
|
it("should not allow creating a provider if limit is reached", async () => {
|
|
960
|
-
const { auth, signInWithTestUser } = await
|
|
960
|
+
const { auth, signInWithTestUser } = await getTestInstance({
|
|
961
961
|
plugins: [sso({ providersLimit: 1 })],
|
|
962
962
|
});
|
|
963
963
|
const { headers } = await signInWithTestUser();
|
|
@@ -1011,7 +1011,7 @@ describe("SAML SSO", async () => {
|
|
|
1011
1011
|
});
|
|
1012
1012
|
|
|
1013
1013
|
it("should not allow creating a provider if limit from function is reached", async () => {
|
|
1014
|
-
const { auth, signInWithTestUser } = await
|
|
1014
|
+
const { auth, signInWithTestUser } = await getTestInstance({
|
|
1015
1015
|
plugins: [
|
|
1016
1016
|
sso({
|
|
1017
1017
|
providersLimit: async (user) => {
|
|
@@ -1118,4 +1118,210 @@ describe("SAML SSO", async () => {
|
|
|
1118
1118
|
},
|
|
1119
1119
|
});
|
|
1120
1120
|
});
|
|
1121
|
+
|
|
1122
|
+
it("should reject SAML sign-in when disableImplicitSignUp is true and user doesn't exist", async () => {
|
|
1123
|
+
const { auth: authWithDisabledSignUp, signInWithTestUser } =
|
|
1124
|
+
await getTestInstance({
|
|
1125
|
+
plugins: [sso({ disableImplicitSignUp: true })],
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
const { headers } = await signInWithTestUser();
|
|
1129
|
+
|
|
1130
|
+
// Register SAML provider
|
|
1131
|
+
await authWithDisabledSignUp.api.registerSSOProvider({
|
|
1132
|
+
body: {
|
|
1133
|
+
providerId: "saml-test-provider",
|
|
1134
|
+
issuer: "http://localhost:8081",
|
|
1135
|
+
domain: "http://localhost:8081",
|
|
1136
|
+
samlConfig: {
|
|
1137
|
+
entryPoint: "http://localhost:8081/api/sso/saml2/idp/post",
|
|
1138
|
+
cert: certificate,
|
|
1139
|
+
callbackUrl: "http://localhost:3000/dashboard",
|
|
1140
|
+
wantAssertionsSigned: false,
|
|
1141
|
+
signatureAlgorithm: "sha256",
|
|
1142
|
+
digestAlgorithm: "sha256",
|
|
1143
|
+
idpMetadata: {
|
|
1144
|
+
metadata: idpMetadata,
|
|
1145
|
+
},
|
|
1146
|
+
spMetadata: {
|
|
1147
|
+
metadata: spMetadata,
|
|
1148
|
+
},
|
|
1149
|
+
identifierFormat:
|
|
1150
|
+
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
|
1151
|
+
},
|
|
1152
|
+
},
|
|
1153
|
+
headers: headers,
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
// Identity Provider-initiated: Get SAML response directly from IdP
|
|
1157
|
+
// The mock IdP will return test@email.com, which doesn't exist in the DB
|
|
1158
|
+
let samlResponse: any;
|
|
1159
|
+
await betterFetch("http://localhost:8081/api/sso/saml2/idp/post", {
|
|
1160
|
+
onSuccess: async (context) => {
|
|
1161
|
+
samlResponse = await context.data;
|
|
1162
|
+
},
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
// Attempt to complete SAML callback - should fail because test@email.com doesn't exist
|
|
1166
|
+
// and disableImplicitSignUp is true
|
|
1167
|
+
await expect(
|
|
1168
|
+
authWithDisabledSignUp.api.callbackSSOSAML({
|
|
1169
|
+
body: {
|
|
1170
|
+
SAMLResponse: samlResponse.samlResponse,
|
|
1171
|
+
RelayState: "http://localhost:3000/dashboard",
|
|
1172
|
+
},
|
|
1173
|
+
params: {
|
|
1174
|
+
providerId: "saml-test-provider",
|
|
1175
|
+
},
|
|
1176
|
+
}),
|
|
1177
|
+
).rejects.toMatchObject({
|
|
1178
|
+
status: "UNAUTHORIZED",
|
|
1179
|
+
body: {
|
|
1180
|
+
message:
|
|
1181
|
+
"User not found and implicit sign up is disabled for this provider",
|
|
1182
|
+
},
|
|
1183
|
+
});
|
|
1184
|
+
});
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
describe("SAML SSO with custom fields", () => {
|
|
1188
|
+
const ssoOptions = {
|
|
1189
|
+
modelName: "sso_provider",
|
|
1190
|
+
fields: {
|
|
1191
|
+
issuer: "the_issuer",
|
|
1192
|
+
oidcConfig: "oidc_config",
|
|
1193
|
+
samlConfig: "saml_config",
|
|
1194
|
+
userId: "user_id",
|
|
1195
|
+
providerId: "provider_id",
|
|
1196
|
+
organizationId: "organization_id",
|
|
1197
|
+
domain: "the_domain",
|
|
1198
|
+
},
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
const data = {
|
|
1202
|
+
user: [],
|
|
1203
|
+
session: [],
|
|
1204
|
+
verification: [],
|
|
1205
|
+
account: [],
|
|
1206
|
+
sso_provider: [],
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
const memory = memoryAdapter(data);
|
|
1210
|
+
const mockIdP = createMockSAMLIdP(8081); // Different port from your main app
|
|
1211
|
+
|
|
1212
|
+
const auth = betterAuth({
|
|
1213
|
+
database: memory,
|
|
1214
|
+
baseURL: "http://localhost:3000",
|
|
1215
|
+
emailAndPassword: {
|
|
1216
|
+
enabled: true,
|
|
1217
|
+
},
|
|
1218
|
+
plugins: [sso(ssoOptions)],
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
const authClient = createAuthClient({
|
|
1222
|
+
baseURL: "http://localhost:3000",
|
|
1223
|
+
plugins: [bearer(), ssoClient()],
|
|
1224
|
+
fetchOptions: {
|
|
1225
|
+
customFetchImpl: async (url, init) => {
|
|
1226
|
+
return auth.handler(new Request(url, init));
|
|
1227
|
+
},
|
|
1228
|
+
},
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
const testUser = {
|
|
1232
|
+
email: "test@email.com",
|
|
1233
|
+
password: "password",
|
|
1234
|
+
name: "Test User",
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
beforeAll(async () => {
|
|
1238
|
+
await mockIdP.start();
|
|
1239
|
+
const res = await authClient.signUp.email({
|
|
1240
|
+
email: testUser.email,
|
|
1241
|
+
password: testUser.password,
|
|
1242
|
+
name: testUser.name,
|
|
1243
|
+
});
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
afterAll(async () => {
|
|
1247
|
+
await mockIdP.stop();
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
beforeEach(() => {
|
|
1251
|
+
data.user = [];
|
|
1252
|
+
data.session = [];
|
|
1253
|
+
data.verification = [];
|
|
1254
|
+
data.account = [];
|
|
1255
|
+
data.sso_provider = [];
|
|
1256
|
+
|
|
1257
|
+
vi.clearAllMocks();
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
async function getAuthHeaders() {
|
|
1261
|
+
const headers = new Headers();
|
|
1262
|
+
await authClient.signUp.email({
|
|
1263
|
+
email: testUser.email,
|
|
1264
|
+
password: testUser.password,
|
|
1265
|
+
name: testUser.name,
|
|
1266
|
+
});
|
|
1267
|
+
await authClient.signIn.email(testUser, {
|
|
1268
|
+
throw: true,
|
|
1269
|
+
onSuccess: setCookieToHeader(headers),
|
|
1270
|
+
});
|
|
1271
|
+
return headers;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
it("should register a new SAML provider", async () => {
|
|
1275
|
+
const headers = await getAuthHeaders();
|
|
1276
|
+
|
|
1277
|
+
const provider = await auth.api.registerSSOProvider({
|
|
1278
|
+
body: {
|
|
1279
|
+
providerId: "saml-provider-1",
|
|
1280
|
+
issuer: "http://localhost:8081",
|
|
1281
|
+
domain: "http://localhost:8081",
|
|
1282
|
+
samlConfig: {
|
|
1283
|
+
entryPoint: mockIdP.metadataUrl,
|
|
1284
|
+
cert: certificate,
|
|
1285
|
+
callbackUrl: "http://localhost:8081/api/sso/saml2/callback",
|
|
1286
|
+
wantAssertionsSigned: false,
|
|
1287
|
+
signatureAlgorithm: "sha256",
|
|
1288
|
+
digestAlgorithm: "sha256",
|
|
1289
|
+
idpMetadata: {
|
|
1290
|
+
metadata: idpMetadata,
|
|
1291
|
+
privateKey: idpPrivateKey,
|
|
1292
|
+
privateKeyPass: "q9ALNhGT5EhfcRmp8Pg7e9zTQeP2x1bW",
|
|
1293
|
+
isAssertionEncrypted: true,
|
|
1294
|
+
encPrivateKey: idpEncryptionKey,
|
|
1295
|
+
encPrivateKeyPass: "g7hGcRmp8PxT5QeP2q9Ehf1bWe9zTALN",
|
|
1296
|
+
},
|
|
1297
|
+
spMetadata: {
|
|
1298
|
+
metadata: spMetadata,
|
|
1299
|
+
binding: "post",
|
|
1300
|
+
privateKey: spPrivateKey,
|
|
1301
|
+
privateKeyPass: "VHOSp5RUiBcrsjrcAuXFwU1NKCkGA8px",
|
|
1302
|
+
isAssertionEncrypted: true,
|
|
1303
|
+
encPrivateKey: spEncryptionKey,
|
|
1304
|
+
encPrivateKeyPass: "BXFNKpxrsjrCkGA8cAu5wUVHOSpci1RU",
|
|
1305
|
+
},
|
|
1306
|
+
identifierFormat:
|
|
1307
|
+
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
|
1308
|
+
},
|
|
1309
|
+
},
|
|
1310
|
+
headers,
|
|
1311
|
+
});
|
|
1312
|
+
expect(provider).toMatchObject({
|
|
1313
|
+
id: expect.any(String),
|
|
1314
|
+
issuer: "http://localhost:8081",
|
|
1315
|
+
samlConfig: {
|
|
1316
|
+
entryPoint: mockIdP.metadataUrl,
|
|
1317
|
+
cert: expect.any(String),
|
|
1318
|
+
callbackUrl: "http://localhost:8081/api/sso/saml2/callback",
|
|
1319
|
+
wantAssertionsSigned: false,
|
|
1320
|
+
signatureAlgorithm: "sha256",
|
|
1321
|
+
digestAlgorithm: "sha256",
|
|
1322
|
+
identifierFormat:
|
|
1323
|
+
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
|
1324
|
+
},
|
|
1325
|
+
});
|
|
1326
|
+
});
|
|
1121
1327
|
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import type { OAuth2Tokens, User } from "better-auth";
|
|
2
|
+
|
|
3
|
+
export interface OIDCMapping {
|
|
4
|
+
id?: string | undefined;
|
|
5
|
+
email?: string | undefined;
|
|
6
|
+
emailVerified?: string | undefined;
|
|
7
|
+
name?: string | undefined;
|
|
8
|
+
image?: string | undefined;
|
|
9
|
+
extraFields?: Record<string, string> | undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SAMLMapping {
|
|
13
|
+
id?: string | undefined;
|
|
14
|
+
email?: string | undefined;
|
|
15
|
+
emailVerified?: string | undefined;
|
|
16
|
+
name?: string | undefined;
|
|
17
|
+
firstName?: string | undefined;
|
|
18
|
+
lastName?: string | undefined;
|
|
19
|
+
extraFields?: Record<string, string> | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface OIDCConfig {
|
|
23
|
+
issuer: string;
|
|
24
|
+
pkce: boolean;
|
|
25
|
+
clientId: string;
|
|
26
|
+
clientSecret: string;
|
|
27
|
+
authorizationEndpoint?: string | undefined;
|
|
28
|
+
discoveryEndpoint: string;
|
|
29
|
+
userInfoEndpoint?: string | undefined;
|
|
30
|
+
scopes?: string[] | undefined;
|
|
31
|
+
overrideUserInfo?: boolean | undefined;
|
|
32
|
+
tokenEndpoint?: string | undefined;
|
|
33
|
+
tokenEndpointAuthentication?:
|
|
34
|
+
| ("client_secret_post" | "client_secret_basic")
|
|
35
|
+
| undefined;
|
|
36
|
+
jwksEndpoint?: string | undefined;
|
|
37
|
+
mapping?: OIDCMapping | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SAMLConfig {
|
|
41
|
+
issuer: string;
|
|
42
|
+
entryPoint: string;
|
|
43
|
+
cert: string;
|
|
44
|
+
callbackUrl: string;
|
|
45
|
+
audience?: string | undefined;
|
|
46
|
+
idpMetadata?:
|
|
47
|
+
| {
|
|
48
|
+
metadata?: string;
|
|
49
|
+
entityID?: string;
|
|
50
|
+
entityURL?: string;
|
|
51
|
+
redirectURL?: string;
|
|
52
|
+
cert?: string;
|
|
53
|
+
privateKey?: string;
|
|
54
|
+
privateKeyPass?: string;
|
|
55
|
+
isAssertionEncrypted?: boolean;
|
|
56
|
+
encPrivateKey?: string;
|
|
57
|
+
encPrivateKeyPass?: string;
|
|
58
|
+
singleSignOnService?: Array<{
|
|
59
|
+
Binding: string;
|
|
60
|
+
Location: string;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
| undefined;
|
|
64
|
+
spMetadata: {
|
|
65
|
+
metadata?: string | undefined;
|
|
66
|
+
entityID?: string | undefined;
|
|
67
|
+
binding?: string | undefined;
|
|
68
|
+
privateKey?: string | undefined;
|
|
69
|
+
privateKeyPass?: string | undefined;
|
|
70
|
+
isAssertionEncrypted?: boolean | undefined;
|
|
71
|
+
encPrivateKey?: string | undefined;
|
|
72
|
+
encPrivateKeyPass?: string | undefined;
|
|
73
|
+
};
|
|
74
|
+
wantAssertionsSigned?: boolean | undefined;
|
|
75
|
+
signatureAlgorithm?: string | undefined;
|
|
76
|
+
digestAlgorithm?: string | undefined;
|
|
77
|
+
identifierFormat?: string | undefined;
|
|
78
|
+
privateKey?: string | undefined;
|
|
79
|
+
decryptionPvk?: string | undefined;
|
|
80
|
+
additionalParams?: Record<string, any> | undefined;
|
|
81
|
+
mapping?: SAMLMapping | undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type BaseSSOProvider = {
|
|
85
|
+
issuer: string;
|
|
86
|
+
oidcConfig?: OIDCConfig | undefined;
|
|
87
|
+
samlConfig?: SAMLConfig | undefined;
|
|
88
|
+
userId: string;
|
|
89
|
+
providerId: string;
|
|
90
|
+
organizationId?: string | undefined;
|
|
91
|
+
domain: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type SSOProvider<O extends SSOOptions> =
|
|
95
|
+
O["domainVerification"] extends { enabled: true }
|
|
96
|
+
? {
|
|
97
|
+
domainVerified: boolean;
|
|
98
|
+
} & BaseSSOProvider
|
|
99
|
+
: BaseSSOProvider;
|
|
100
|
+
|
|
101
|
+
export interface SSOOptions {
|
|
102
|
+
/**
|
|
103
|
+
* custom function to provision a user when they sign in with an SSO provider.
|
|
104
|
+
*/
|
|
105
|
+
provisionUser?:
|
|
106
|
+
| ((data: {
|
|
107
|
+
/**
|
|
108
|
+
* The user object from the database
|
|
109
|
+
*/
|
|
110
|
+
user: User & Record<string, any>;
|
|
111
|
+
/**
|
|
112
|
+
* The user info object from the provider
|
|
113
|
+
*/
|
|
114
|
+
userInfo: Record<string, any>;
|
|
115
|
+
/**
|
|
116
|
+
* The OAuth2 tokens from the provider
|
|
117
|
+
*/
|
|
118
|
+
token?: OAuth2Tokens;
|
|
119
|
+
/**
|
|
120
|
+
* The SSO provider
|
|
121
|
+
*/
|
|
122
|
+
provider: SSOProvider<SSOOptions>;
|
|
123
|
+
}) => Promise<void>)
|
|
124
|
+
| undefined;
|
|
125
|
+
/**
|
|
126
|
+
* Organization provisioning options
|
|
127
|
+
*/
|
|
128
|
+
organizationProvisioning?:
|
|
129
|
+
| {
|
|
130
|
+
disabled?: boolean;
|
|
131
|
+
defaultRole?: "member" | "admin";
|
|
132
|
+
getRole?: (data: {
|
|
133
|
+
/**
|
|
134
|
+
* The user object from the database
|
|
135
|
+
*/
|
|
136
|
+
user: User & Record<string, any>;
|
|
137
|
+
/**
|
|
138
|
+
* The user info object from the provider
|
|
139
|
+
*/
|
|
140
|
+
userInfo: Record<string, any>;
|
|
141
|
+
/**
|
|
142
|
+
* The OAuth2 tokens from the provider
|
|
143
|
+
*/
|
|
144
|
+
token?: OAuth2Tokens;
|
|
145
|
+
/**
|
|
146
|
+
* The SSO provider
|
|
147
|
+
*/
|
|
148
|
+
provider: SSOProvider<SSOOptions>;
|
|
149
|
+
}) => Promise<"member" | "admin">;
|
|
150
|
+
}
|
|
151
|
+
| undefined;
|
|
152
|
+
/**
|
|
153
|
+
* Default SSO provider configurations for testing.
|
|
154
|
+
* These will take the precedence over the database providers.
|
|
155
|
+
*/
|
|
156
|
+
defaultSSO?:
|
|
157
|
+
| Array<{
|
|
158
|
+
/**
|
|
159
|
+
* The domain to match for this default provider.
|
|
160
|
+
* This is only used to match incoming requests to this default provider.
|
|
161
|
+
*/
|
|
162
|
+
domain: string;
|
|
163
|
+
/**
|
|
164
|
+
* The provider ID to use
|
|
165
|
+
*/
|
|
166
|
+
providerId: string;
|
|
167
|
+
/**
|
|
168
|
+
* SAML configuration
|
|
169
|
+
*/
|
|
170
|
+
samlConfig?: SAMLConfig;
|
|
171
|
+
/**
|
|
172
|
+
* OIDC configuration
|
|
173
|
+
*/
|
|
174
|
+
oidcConfig?: OIDCConfig;
|
|
175
|
+
}>
|
|
176
|
+
| undefined;
|
|
177
|
+
/**
|
|
178
|
+
* Override user info with the provider info.
|
|
179
|
+
* @default false
|
|
180
|
+
*/
|
|
181
|
+
defaultOverrideUserInfo?: boolean | undefined;
|
|
182
|
+
/**
|
|
183
|
+
* Disable implicit sign up for new users. When set to true for the provider,
|
|
184
|
+
* sign-in need to be called with with requestSignUp as true to create new users.
|
|
185
|
+
*/
|
|
186
|
+
disableImplicitSignUp?: boolean | undefined;
|
|
187
|
+
/**
|
|
188
|
+
* The model name for the SSO provider table. Defaults to "ssoProvider".
|
|
189
|
+
*/
|
|
190
|
+
modelName?: string;
|
|
191
|
+
/**
|
|
192
|
+
* Map fields
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* {
|
|
197
|
+
* samlConfig: "saml_config"
|
|
198
|
+
* }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
fields?: {
|
|
202
|
+
issuer?: string | undefined;
|
|
203
|
+
oidcConfig?: string | undefined;
|
|
204
|
+
samlConfig?: string | undefined;
|
|
205
|
+
userId?: string | undefined;
|
|
206
|
+
providerId?: string | undefined;
|
|
207
|
+
organizationId?: string | undefined;
|
|
208
|
+
domain?: string | undefined;
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Configure the maximum number of SSO providers a user can register.
|
|
212
|
+
* You can also pass a function that returns a number.
|
|
213
|
+
* Set to 0 to disable SSO provider registration.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```ts
|
|
217
|
+
* providersLimit: async (user) => {
|
|
218
|
+
* const plan = await getUserPlan(user);
|
|
219
|
+
* return plan.name === "pro" ? 10 : 1;
|
|
220
|
+
* }
|
|
221
|
+
* ```
|
|
222
|
+
* @default 10
|
|
223
|
+
*/
|
|
224
|
+
providersLimit?:
|
|
225
|
+
| (number | ((user: User) => Promise<number> | number))
|
|
226
|
+
| undefined;
|
|
227
|
+
/**
|
|
228
|
+
* Trust the email verified flag from the provider.
|
|
229
|
+
*
|
|
230
|
+
* ⚠️ Use this with caution — it can lead to account takeover if misused. Only enable it if users **cannot freely register new providers**. You can
|
|
231
|
+
* prevent that by using `disabledPaths` or other safeguards to block provider registration from the client.
|
|
232
|
+
*
|
|
233
|
+
* If you want to allow account linking for specific trusted providers, enable the `accountLinking` option in your auth config and specify those
|
|
234
|
+
* providers in the `trustedProviders` list.
|
|
235
|
+
* @default false
|
|
236
|
+
*/
|
|
237
|
+
trustEmailVerified?: boolean | undefined;
|
|
238
|
+
/**
|
|
239
|
+
* Enable domain verification on SSO providers
|
|
240
|
+
*
|
|
241
|
+
* When this option is enabled, new SSO providers will require the associated domain to be verified by the owner
|
|
242
|
+
* prior to allowing sign-ins.
|
|
243
|
+
*/
|
|
244
|
+
domainVerification?: {
|
|
245
|
+
/**
|
|
246
|
+
* Enables or disables the domain verification feature
|
|
247
|
+
*/
|
|
248
|
+
enabled?: boolean;
|
|
249
|
+
/**
|
|
250
|
+
* Prefix used to generate the domain verification token
|
|
251
|
+
*
|
|
252
|
+
* @default "better-auth-token-"
|
|
253
|
+
*/
|
|
254
|
+
tokenPrefix?: string;
|
|
255
|
+
};
|
|
256
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const validateEmailDomain = (email: string, domain: string) => {
|
|
2
|
+
const emailDomain = email.split("@")[1]?.toLowerCase();
|
|
3
|
+
const providerDomain = domain.toLowerCase();
|
|
4
|
+
if (!emailDomain || !providerDomain) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return (
|
|
8
|
+
emailDomain === providerDomain || emailDomain.endsWith(`.${providerDomain}`)
|
|
9
|
+
);
|
|
10
|
+
};
|
package/tsconfig.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": "../../tsconfig.json",
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
|
-
"rootDir": "./src",
|
|
5
|
-
"outDir": "./dist",
|
|
6
4
|
"lib": ["esnext", "dom", "dom.iterable"]
|
|
7
5
|
},
|
|
8
|
-
"
|
|
6
|
+
"references": [
|
|
7
|
+
{
|
|
8
|
+
"path": "../better-auth/tsconfig.json"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
9
11
|
}
|
package/tsdown.config.ts
ADDED
package/vitest.config.ts
ADDED
package/build.config.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { defineBuildConfig } from "unbuild";
|
|
2
|
-
|
|
3
|
-
export default defineBuildConfig({
|
|
4
|
-
declaration: true,
|
|
5
|
-
rollup: {
|
|
6
|
-
emitCJS: true,
|
|
7
|
-
},
|
|
8
|
-
outDir: "dist",
|
|
9
|
-
clean: false,
|
|
10
|
-
failOnWarn: false,
|
|
11
|
-
externals: ["better-auth", "better-call", "@better-fetch/fetch", "stripe"],
|
|
12
|
-
});
|
package/dist/client.cjs
DELETED
package/dist/client.d.cts
DELETED
package/dist/client.d.ts
DELETED