@better-auth/sso 1.4.0-beta.21 → 1.4.0-beta.23

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/src/saml.test.ts CHANGED
@@ -13,7 +13,7 @@ import type {
13
13
  Response as ExpressResponse,
14
14
  } from "express";
15
15
  import express from "express";
16
- import { createServer } from "http";
16
+ import type { createServer } from "http";
17
17
  import * as saml from "samlify";
18
18
  import {
19
19
  afterAll,
@@ -1183,3 +1183,145 @@ describe("SAML SSO", async () => {
1183
1183
  });
1184
1184
  });
1185
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
+ });
1327
+ });
package/src/types.ts CHANGED
@@ -81,7 +81,7 @@ export interface SAMLConfig {
81
81
  mapping?: SAMLMapping | undefined;
82
82
  }
83
83
 
84
- export type SSOProvider = {
84
+ type BaseSSOProvider = {
85
85
  issuer: string;
86
86
  oidcConfig?: OIDCConfig | undefined;
87
87
  samlConfig?: SAMLConfig | undefined;
@@ -91,6 +91,13 @@ export type SSOProvider = {
91
91
  domain: string;
92
92
  };
93
93
 
94
+ export type SSOProvider<O extends SSOOptions> =
95
+ O["domainVerification"] extends { enabled: true }
96
+ ? {
97
+ domainVerified: boolean;
98
+ } & BaseSSOProvider
99
+ : BaseSSOProvider;
100
+
94
101
  export interface SSOOptions {
95
102
  /**
96
103
  * custom function to provision a user when they sign in with an SSO provider.
@@ -112,7 +119,7 @@ export interface SSOOptions {
112
119
  /**
113
120
  * The SSO provider
114
121
  */
115
- provider: SSOProvider;
122
+ provider: SSOProvider<SSOOptions>;
116
123
  }) => Promise<void>)
117
124
  | undefined;
118
125
  /**
@@ -138,7 +145,7 @@ export interface SSOOptions {
138
145
  /**
139
146
  * The SSO provider
140
147
  */
141
- provider: SSOProvider;
148
+ provider: SSOProvider<SSOOptions>;
142
149
  }) => Promise<"member" | "admin">;
143
150
  }
144
151
  | undefined;
@@ -177,6 +184,29 @@ export interface SSOOptions {
177
184
  * sign-in need to be called with with requestSignUp as true to create new users.
178
185
  */
179
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
+ };
180
210
  /**
181
211
  * Configure the maximum number of SSO providers a user can register.
182
212
  * You can also pass a function that returns a number.
@@ -205,4 +235,22 @@ export interface SSOOptions {
205
235
  * @default false
206
236
  */
207
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
+ };
208
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
+ };
@@ -0,0 +1,3 @@
1
+ import { defineProject } from "vitest/config";
2
+
3
+ export default defineProject({});