@better-auth/sso 1.4.0-beta.1 → 1.4.0-beta.11

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/oidc.test.ts CHANGED
@@ -1,18 +1,28 @@
1
1
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
2
+ import { getTestInstanceMemory as getTestInstance } from "better-auth/test";
2
3
  import { sso } from ".";
3
4
  import { OAuth2Server } from "oauth2-mock-server";
4
5
  import { betterFetch } from "@better-fetch/fetch";
5
- import { organization } from "better-auth/plugins/organization";
6
- import { getTestInstanceMemory } from "better-auth/test";
6
+ import { organization } from "better-auth/plugins";
7
+ import { createAuthClient } from "better-auth/client";
8
+ import { ssoClient } from "./client";
7
9
 
8
10
  let server = new OAuth2Server();
9
11
 
10
12
  describe("SSO", async () => {
11
- const { auth, signInWithTestUser, customFetchImpl } =
12
- await getTestInstanceMemory({
13
+ const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
14
+ await getTestInstance({
13
15
  plugins: [sso(), organization()],
14
16
  });
15
17
 
18
+ const authClient = createAuthClient({
19
+ plugins: [ssoClient()],
20
+ baseURL: "http://localhost:3000",
21
+ fetchOptions: {
22
+ customFetchImpl,
23
+ },
24
+ });
25
+
16
26
  beforeAll(async () => {
17
27
  await server.issuer.keys.generate("RS256");
18
28
  server.issuer.on;
@@ -57,7 +67,7 @@ describe("SSO", async () => {
57
67
  });
58
68
 
59
69
  if (!location) throw new Error("No redirect location found");
60
-
70
+ const newHeaders = new Headers();
61
71
  let callbackURL = "";
62
72
  await betterFetch(location, {
63
73
  method: "GET",
@@ -65,10 +75,11 @@ describe("SSO", async () => {
65
75
  headers,
66
76
  onError(context) {
67
77
  callbackURL = context.response.headers.get("location") || "";
78
+ cookieSetter(newHeaders)(context);
68
79
  },
69
80
  });
70
81
 
71
- return callbackURL;
82
+ return { callbackURL, headers: newHeaders };
72
83
  }
73
84
 
74
85
  it("should register a new SSO provider", async () => {
@@ -84,13 +95,13 @@ describe("SSO", async () => {
84
95
  tokenEndpoint: `${server.issuer.url}/token`,
85
96
  jwksEndpoint: `${server.issuer.url}/jwks`,
86
97
  discoveryEndpoint: `${server.issuer.url}/.well-known/openid-configuration`,
87
- },
88
- mapping: {
89
- id: "sub",
90
- email: "email",
91
- emailVerified: "email_verified",
92
- name: "name",
93
- image: "picture",
98
+ mapping: {
99
+ id: "sub",
100
+ email: "email",
101
+ emailVerified: "email_verified",
102
+ name: "name",
103
+ image: "picture",
104
+ },
94
105
  },
95
106
  providerId: "test",
96
107
  },
@@ -146,62 +157,117 @@ describe("SSO", async () => {
146
157
  }
147
158
  });
148
159
 
149
- it("should sign in with SSO provider with email matching", async () => {
150
- const res = await auth.api.signInSSO({
160
+ it("should not allow creating a provider with duplicate providerId", async () => {
161
+ const { headers } = await signInWithTestUser();
162
+
163
+ await auth.api.registerSSOProvider({
151
164
  body: {
152
- email: "my-email@localhost.com",
153
- callbackURL: "/dashboard",
165
+ issuer: server.issuer.url!,
166
+ domain: "duplicate.com",
167
+ providerId: "duplicate-oidc-provider",
168
+ oidcConfig: {
169
+ clientId: "test",
170
+ clientSecret: "test",
171
+ },
172
+ },
173
+ headers,
174
+ });
175
+
176
+ await expect(
177
+ auth.api.registerSSOProvider({
178
+ body: {
179
+ issuer: server.issuer.url!,
180
+ domain: "another-duplicate.com",
181
+ providerId: "duplicate-oidc-provider",
182
+ oidcConfig: {
183
+ clientId: "test2",
184
+ clientSecret: "test2",
185
+ },
186
+ },
187
+ headers,
188
+ }),
189
+ ).rejects.toMatchObject({
190
+ status: "UNPROCESSABLE_ENTITY",
191
+ body: {
192
+ message: "SSO provider with this providerId already exists",
193
+ },
194
+ });
195
+ });
196
+
197
+ it("should sign in with SSO provider with email matching", async () => {
198
+ const headers = new Headers();
199
+ const res = await authClient.signIn.sso({
200
+ email: "my-email@localhost.com",
201
+ callbackURL: "/dashboard",
202
+ fetchOptions: {
203
+ throw: true,
204
+ onSuccess: cookieSetter(headers),
154
205
  },
155
206
  });
156
207
  expect(res.url).toContain("http://localhost:8080/authorize");
157
208
  expect(res.url).toContain(
158
209
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
159
210
  );
160
- const headers = new Headers();
161
- const callbackURL = await simulateOAuthFlow(res.url, headers);
211
+ expect(res.url).toContain("login_hint=my-email%40localhost.com");
212
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
162
213
  expect(callbackURL).toContain("/dashboard");
163
214
  });
164
215
 
165
216
  it("should sign in with SSO provider with domain", async () => {
166
- const res = await auth.api.signInSSO({
167
- body: {
168
- email: "my-email@test.com",
169
- domain: "localhost.com",
170
- callbackURL: "/dashboard",
217
+ const headers = new Headers();
218
+ const res = await authClient.signIn.sso({
219
+ email: "my-email@test.com",
220
+ domain: "localhost.com",
221
+ callbackURL: "/dashboard",
222
+ fetchOptions: {
223
+ throw: true,
224
+ onSuccess: cookieSetter(headers),
171
225
  },
172
226
  });
173
227
  expect(res.url).toContain("http://localhost:8080/authorize");
174
228
  expect(res.url).toContain(
175
229
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
176
230
  );
177
- const headers = new Headers();
178
- const callbackURL = await simulateOAuthFlow(res.url, headers);
231
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
179
232
  expect(callbackURL).toContain("/dashboard");
180
233
  });
181
234
 
182
235
  it("should sign in with SSO provider with providerId", async () => {
183
- const res = await auth.api.signInSSO({
184
- body: {
185
- providerId: "test",
186
- callbackURL: "/dashboard",
236
+ const headers = new Headers();
237
+ const res = await authClient.signIn.sso({
238
+ providerId: "test",
239
+ loginHint: "user@example.com",
240
+ callbackURL: "/dashboard",
241
+ fetchOptions: {
242
+ throw: true,
243
+ onSuccess: cookieSetter(headers),
187
244
  },
188
245
  });
189
246
  expect(res.url).toContain("http://localhost:8080/authorize");
190
247
  expect(res.url).toContain(
191
248
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
192
249
  );
193
- const headers = new Headers();
194
- const callbackURL = await simulateOAuthFlow(res.url, headers);
250
+ expect(res.url).toContain("login_hint=user%40example.com");
251
+
252
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
195
253
  expect(callbackURL).toContain("/dashboard");
196
254
  });
197
255
  });
198
256
 
199
257
  describe("SSO disable implicit sign in", async () => {
200
- const { auth, signInWithTestUser, customFetchImpl } =
201
- await getTestInstanceMemory({
258
+ const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
259
+ await getTestInstance({
202
260
  plugins: [sso({ disableImplicitSignUp: true }), organization()],
203
261
  });
204
262
 
263
+ const authClient = createAuthClient({
264
+ plugins: [ssoClient()],
265
+ baseURL: "http://localhost:3000",
266
+ fetchOptions: {
267
+ customFetchImpl,
268
+ },
269
+ });
270
+
205
271
  beforeAll(async () => {
206
272
  await server.issuer.keys.generate("RS256");
207
273
  server.issuer.on;
@@ -246,7 +312,7 @@ describe("SSO disable implicit sign in", async () => {
246
312
  });
247
313
 
248
314
  if (!location) throw new Error("No redirect location found");
249
-
315
+ const newHeaders = new Headers(headers);
250
316
  let callbackURL = "";
251
317
  await betterFetch(location, {
252
318
  method: "GET",
@@ -254,10 +320,11 @@ describe("SSO disable implicit sign in", async () => {
254
320
  headers,
255
321
  onError(context) {
256
322
  callbackURL = context.response.headers.get("location") || "";
323
+ cookieSetter(newHeaders)(context);
257
324
  },
258
325
  });
259
326
 
260
- return callbackURL;
327
+ return { callbackURL, headers: newHeaders };
261
328
  }
262
329
 
263
330
  it("should register a new SSO provider", async () => {
@@ -272,13 +339,14 @@ describe("SSO disable implicit sign in", async () => {
272
339
  authorizationEndpoint: `${server.issuer.url}/authorize`,
273
340
  tokenEndpoint: `${server.issuer.url}/token`,
274
341
  jwksEndpoint: `${server.issuer.url}/jwks`,
275
- },
276
- mapping: {
277
- id: "sub",
278
- email: "email",
279
- emailVerified: "email_verified",
280
- name: "name",
281
- image: "picture",
342
+ discoveryEndpoint: `${server.issuer.url}/.well-known/openid-configuration`,
343
+ mapping: {
344
+ id: "sub",
345
+ email: "email",
346
+ emailVerified: "email_verified",
347
+ name: "name",
348
+ image: "picture",
349
+ },
282
350
  },
283
351
  providerId: "test",
284
352
  },
@@ -307,150 +375,61 @@ describe("SSO disable implicit sign in", async () => {
307
375
  userId: expect.any(String),
308
376
  });
309
377
  });
310
- it("should not allow creating a provider if limit is set to 0", async () => {
311
- const { auth, signInWithTestUser } = await getTestInstanceMemory({
312
- plugins: [sso({ providersLimit: 0 })],
313
- });
314
- const { headers } = await signInWithTestUser();
315
- await expect(
316
- auth.api.registerSSOProvider({
317
- body: {
318
- issuer: server.issuer.url!,
319
- domain: "localhost.com",
320
- oidcConfig: {
321
- clientId: "test",
322
- clientSecret: "test",
323
- },
324
- providerId: "test",
325
- },
326
- headers,
327
- }),
328
- ).rejects.toMatchObject({
329
- status: "FORBIDDEN",
330
- body: { message: "SSO provider registration is disabled" },
331
- });
332
- });
333
- it("should not allow creating a provider if limit is reached", async () => {
334
- const { auth, signInWithTestUser } = await getTestInstanceMemory({
335
- plugins: [sso({ providersLimit: 1 })],
336
- });
337
- const { headers } = await signInWithTestUser();
338
-
339
- await auth.api.registerSSOProvider({
340
- body: {
341
- issuer: server.issuer.url!,
342
- domain: "localhost.com",
343
- oidcConfig: {
344
- clientId: "test",
345
- clientSecret: "test",
346
- },
347
- providerId: "test-1",
348
- },
349
- headers,
350
- });
351
-
352
- await expect(
353
- auth.api.registerSSOProvider({
354
- body: {
355
- issuer: server.issuer.url!,
356
- domain: "localhost.com",
357
- oidcConfig: {
358
- clientId: "test",
359
- clientSecret: "test",
360
- },
361
- providerId: "test-2",
362
- },
363
- headers,
364
- }),
365
- ).rejects.toMatchObject({
366
- status: "FORBIDDEN",
367
- body: {
368
- message: "You have reached the maximum number of SSO providers",
369
- },
370
- });
371
- });
372
-
373
- it("should not allow creating a provider if limit from function is reached", async () => {
374
- const { auth, signInWithTestUser } = await getTestInstanceMemory({
375
- plugins: [sso({ providersLimit: async () => 1 })],
376
- });
377
- const { headers } = await signInWithTestUser();
378
-
379
- await auth.api.registerSSOProvider({
380
- body: {
381
- issuer: server.issuer.url!,
382
- domain: "localhost.com",
383
- oidcConfig: {
384
- clientId: "test",
385
- clientSecret: "test",
386
- },
387
- providerId: "test-1",
388
- },
389
- headers,
390
- });
391
378
 
392
- await expect(
393
- auth.api.registerSSOProvider({
394
- body: {
395
- issuer: server.issuer.url!,
396
- domain: "localhost.com",
397
- oidcConfig: {
398
- clientId: "test",
399
- clientSecret: "test",
400
- },
401
- providerId: "test-2",
402
- },
403
- headers,
404
- }),
405
- ).rejects.toMatchObject({
406
- status: "FORBIDDEN",
407
- body: {
408
- message: "You have reached the maximum number of SSO providers",
409
- },
410
- });
411
- });
412
379
  it("should not create user with SSO provider when sign ups are disabled", async () => {
413
- const res = await auth.api.signInSSO({
414
- body: {
415
- email: "my-email@localhost.com",
416
- callbackURL: "/dashboard",
380
+ const headers = new Headers();
381
+ const res = await authClient.signIn.sso({
382
+ email: "my-email@localhost.com",
383
+ callbackURL: "/dashboard",
384
+ fetchOptions: {
385
+ throw: true,
386
+ onSuccess: cookieSetter(headers),
417
387
  },
418
388
  });
419
389
  expect(res.url).toContain("http://localhost:8080/authorize");
420
390
  expect(res.url).toContain(
421
391
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
422
392
  );
423
- const headers = new Headers();
424
- const callbackURL = await simulateOAuthFlow(res.url, headers);
393
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
425
394
  expect(callbackURL).toContain(
426
395
  "/api/auth/error/error?error=signup disabled",
427
396
  );
428
397
  });
429
398
 
430
399
  it("should create user with SSO provider when sign ups are disabled but sign up is requested", async () => {
431
- const res = await auth.api.signInSSO({
432
- body: {
433
- email: "my-email@localhost.com",
434
- callbackURL: "/dashboard",
435
- requestSignUp: true,
400
+ const headers = new Headers();
401
+ const res = await authClient.signIn.sso({
402
+ email: "my-email@localhost.com",
403
+ callbackURL: "/dashboard",
404
+ requestSignUp: true,
405
+ fetchOptions: {
406
+ throw: true,
407
+ onSuccess: cookieSetter(headers),
436
408
  },
437
409
  });
438
410
  expect(res.url).toContain("http://localhost:8080/authorize");
439
411
  expect(res.url).toContain(
440
412
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
441
413
  );
442
- const headers = new Headers();
443
- const callbackURL = await simulateOAuthFlow(res.url, headers);
414
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
444
415
  expect(callbackURL).toContain("/dashboard");
445
416
  });
446
417
  });
447
418
 
448
419
  describe("provisioning", async (ctx) => {
449
- const { auth, signInWithTestUser, customFetchImpl } =
450
- await getTestInstanceMemory({
420
+ const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
421
+ await getTestInstance({
451
422
  plugins: [sso(), organization()],
452
423
  });
453
424
 
425
+ const authClient = createAuthClient({
426
+ plugins: [ssoClient()],
427
+ baseURL: "http://localhost:3000",
428
+ fetchOptions: {
429
+ customFetchImpl,
430
+ },
431
+ });
432
+
454
433
  beforeAll(async () => {
455
434
  await server.issuer.keys.generate("RS256");
456
435
  server.issuer.on;
@@ -478,12 +457,14 @@ describe("provisioning", async (ctx) => {
478
457
  if (!location) throw new Error("No redirect location found");
479
458
 
480
459
  let callbackURL = "";
460
+ const newHeaders = new Headers();
481
461
  await betterFetch(location, {
482
462
  method: "GET",
483
463
  customFetchImpl: fetchImpl || customFetchImpl,
484
464
  headers,
485
465
  onError(context) {
486
466
  callbackURL = context.response.headers.get("location") || "";
467
+ cookieSetter(newHeaders)(context);
487
468
  },
488
469
  });
489
470
 
@@ -526,13 +507,14 @@ describe("provisioning", async (ctx) => {
526
507
  authorizationEndpoint: `${server.issuer.url}/authorize`,
527
508
  tokenEndpoint: `${server.issuer.url}/token`,
528
509
  jwksEndpoint: `${server.issuer.url}/jwks`,
529
- },
530
- mapping: {
531
- id: "sub",
532
- email: "email",
533
- emailVerified: "email_verified",
534
- name: "name",
535
- image: "picture",
510
+ discoveryEndpoint: `${server.issuer.url}/.well-known/openid-configuration`,
511
+ mapping: {
512
+ id: "sub",
513
+ email: "email",
514
+ emailVerified: "email_verified",
515
+ name: "name",
516
+ image: "picture",
517
+ },
536
518
  },
537
519
  providerId: "test2",
538
520
  organizationId: organization?.id,
@@ -542,18 +524,20 @@ describe("provisioning", async (ctx) => {
542
524
  expect(provider).toMatchObject({
543
525
  organizationId: organization?.id,
544
526
  });
545
-
546
- const res = await auth.api.signInSSO({
547
- body: {
548
- email: "my-email@localhost.com",
549
- callbackURL: "/dashboard",
527
+ const newHeaders = new Headers();
528
+ const res = await authClient.signIn.sso({
529
+ email: "my-email@localhost.com",
530
+ callbackURL: "/dashboard",
531
+ fetchOptions: {
532
+ onSuccess: cookieSetter(newHeaders),
533
+ throw: true,
550
534
  },
551
535
  });
552
536
  expect(res.url).toContain("http://localhost:8080/authorize");
553
537
  expect(res.url).toContain(
554
538
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
555
539
  );
556
- const newHeaders = new Headers();
540
+
557
541
  const callbackURL = await simulateOAuthFlow(res.url, newHeaders);
558
542
  expect(callbackURL).toContain("/dashboard");
559
543
  const org = await auth.api.getFullOrganization({