@better-auth/sso 1.4.0-beta.2 → 1.4.0-beta.20

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
+ import { betterFetch } from "@better-fetch/fetch";
2
+ import { createAuthClient } from "better-auth/client";
3
+ import { organization } from "better-auth/plugins";
4
+ import { getTestInstance } from "better-auth/test";
5
+ import { OAuth2Server } from "oauth2-mock-server";
1
6
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
2
7
  import { sso } from ".";
3
- import { OAuth2Server } from "oauth2-mock-server";
4
- import { betterFetch } from "@better-fetch/fetch";
5
- import { organization } from "better-auth/plugins/organization";
6
- import { getTestInstanceMemory } from "better-auth/test";
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 () => {
@@ -146,123 +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
+ },
154
172
  },
173
+ headers,
155
174
  });
156
- expect(res.url).toContain("http://localhost:8080/authorize");
157
- expect(res.url).toContain(
158
- "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
159
- );
160
- const headers = new Headers();
161
- const callbackURL = await simulateOAuthFlow(res.url, headers);
162
- expect(callbackURL).toContain("/dashboard");
163
- });
164
175
 
165
- it("should sign in with SSO provider with domain", async () => {
166
- const res = await auth.api.signInSSO({
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",
167
191
  body: {
168
- email: "my-email@test.com",
169
- domain: "localhost.com",
170
- callbackURL: "/dashboard",
192
+ message: "SSO provider with this providerId already exists",
171
193
  },
172
194
  });
173
- expect(res.url).toContain("http://localhost:8080/authorize");
174
- expect(res.url).toContain(
175
- "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
176
- );
177
- const headers = new Headers();
178
- const callbackURL = await simulateOAuthFlow(res.url, headers);
179
- expect(callbackURL).toContain("/dashboard");
180
195
  });
181
196
 
182
- 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",
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),
187
205
  },
188
206
  });
189
207
  expect(res.url).toContain("http://localhost:8080/authorize");
190
208
  expect(res.url).toContain(
191
209
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
192
210
  );
193
- const headers = new Headers();
194
- 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);
195
213
  expect(callbackURL).toContain("/dashboard");
196
214
  });
197
- });
198
-
199
- describe("SSO with defaultSSO array", async () => {
200
- const { auth, signInWithTestUser, customFetchImpl } =
201
- await getTestInstanceMemory({
202
- plugins: [
203
- sso({
204
- defaultSSO: [
205
- {
206
- domain: "localhost.com",
207
- providerId: "default-test",
208
- oidcConfig: {
209
- issuer: "http://localhost:8080",
210
- clientId: "test",
211
- clientSecret: "test",
212
- authorizationEndpoint: "http://localhost:8080/authorize",
213
- tokenEndpoint: "http://localhost:8080/token",
214
- jwksEndpoint: "http://localhost:8080/jwks",
215
- discoveryEndpoint:
216
- "http://localhost:8080/.well-known/openid-configuration",
217
- pkce: true,
218
- mapping: {
219
- id: "sub",
220
- email: "email",
221
- emailVerified: "email_verified",
222
- name: "name",
223
- image: "picture",
224
- },
225
- },
226
- },
227
- ],
228
- }),
229
- organization(),
230
- ],
231
- });
232
215
 
233
- it("should use default SSO provider from array when no provider found in database using providerId", async () => {
234
- const res = await auth.api.signInSSO({
235
- body: {
236
- providerId: "default-test",
237
- callbackURL: "/dashboard",
216
+ it("should sign in with SSO provider with domain", async () => {
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),
238
225
  },
239
226
  });
240
227
  expect(res.url).toContain("http://localhost:8080/authorize");
241
228
  expect(res.url).toContain(
242
- "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Fdefault-test",
229
+ "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
243
230
  );
231
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
232
+ expect(callbackURL).toContain("/dashboard");
244
233
  });
245
234
 
246
- it("should use default SSO provider from array when no provider found in database using domain fallback", async () => {
247
- const res = await auth.api.signInSSO({
248
- body: {
249
- email: "test@localhost.com",
250
- callbackURL: "/dashboard",
235
+ it("should sign in with SSO provider with providerId", async () => {
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),
251
244
  },
252
245
  });
253
246
  expect(res.url).toContain("http://localhost:8080/authorize");
254
247
  expect(res.url).toContain(
255
- "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Fdefault-test",
248
+ "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
256
249
  );
250
+ expect(res.url).toContain("login_hint=user%40example.com");
251
+
252
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
253
+ expect(callbackURL).toContain("/dashboard");
257
254
  });
258
255
  });
259
256
 
260
257
  describe("SSO disable implicit sign in", async () => {
261
- const { auth, signInWithTestUser, customFetchImpl } =
262
- await getTestInstanceMemory({
258
+ const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
259
+ await getTestInstance({
263
260
  plugins: [sso({ disableImplicitSignUp: true }), organization()],
264
261
  });
265
262
 
263
+ const authClient = createAuthClient({
264
+ plugins: [ssoClient()],
265
+ baseURL: "http://localhost:3000",
266
+ fetchOptions: {
267
+ customFetchImpl,
268
+ },
269
+ });
270
+
266
271
  beforeAll(async () => {
267
272
  await server.issuer.keys.generate("RS256");
268
273
  server.issuer.on;
@@ -307,7 +312,7 @@ describe("SSO disable implicit sign in", async () => {
307
312
  });
308
313
 
309
314
  if (!location) throw new Error("No redirect location found");
310
-
315
+ const newHeaders = new Headers(headers);
311
316
  let callbackURL = "";
312
317
  await betterFetch(location, {
313
318
  method: "GET",
@@ -315,10 +320,11 @@ describe("SSO disable implicit sign in", async () => {
315
320
  headers,
316
321
  onError(context) {
317
322
  callbackURL = context.response.headers.get("location") || "";
323
+ cookieSetter(newHeaders)(context);
318
324
  },
319
325
  });
320
326
 
321
- return callbackURL;
327
+ return { callbackURL, headers: newHeaders };
322
328
  }
323
329
 
324
330
  it("should register a new SSO provider", async () => {
@@ -369,150 +375,61 @@ describe("SSO disable implicit sign in", async () => {
369
375
  userId: expect.any(String),
370
376
  });
371
377
  });
372
- it("should not allow creating a provider if limit is set to 0", async () => {
373
- const { auth, signInWithTestUser } = await getTestInstanceMemory({
374
- plugins: [sso({ providersLimit: 0 })],
375
- });
376
- const { headers } = await signInWithTestUser();
377
- await expect(
378
- auth.api.registerSSOProvider({
379
- body: {
380
- issuer: server.issuer.url!,
381
- domain: "localhost.com",
382
- oidcConfig: {
383
- clientId: "test",
384
- clientSecret: "test",
385
- },
386
- providerId: "test",
387
- },
388
- headers,
389
- }),
390
- ).rejects.toMatchObject({
391
- status: "FORBIDDEN",
392
- body: { message: "SSO provider registration is disabled" },
393
- });
394
- });
395
- it("should not allow creating a provider if limit is reached", async () => {
396
- const { auth, signInWithTestUser } = await getTestInstanceMemory({
397
- plugins: [sso({ providersLimit: 1 })],
398
- });
399
- const { headers } = await signInWithTestUser();
400
-
401
- await auth.api.registerSSOProvider({
402
- body: {
403
- issuer: server.issuer.url!,
404
- domain: "localhost.com",
405
- oidcConfig: {
406
- clientId: "test",
407
- clientSecret: "test",
408
- },
409
- providerId: "test-1",
410
- },
411
- headers,
412
- });
413
-
414
- await expect(
415
- auth.api.registerSSOProvider({
416
- body: {
417
- issuer: server.issuer.url!,
418
- domain: "localhost.com",
419
- oidcConfig: {
420
- clientId: "test",
421
- clientSecret: "test",
422
- },
423
- providerId: "test-2",
424
- },
425
- headers,
426
- }),
427
- ).rejects.toMatchObject({
428
- status: "FORBIDDEN",
429
- body: {
430
- message: "You have reached the maximum number of SSO providers",
431
- },
432
- });
433
- });
434
-
435
- it("should not allow creating a provider if limit from function is reached", async () => {
436
- const { auth, signInWithTestUser } = await getTestInstanceMemory({
437
- plugins: [sso({ providersLimit: async () => 1 })],
438
- });
439
- const { headers } = await signInWithTestUser();
440
-
441
- await auth.api.registerSSOProvider({
442
- body: {
443
- issuer: server.issuer.url!,
444
- domain: "localhost.com",
445
- oidcConfig: {
446
- clientId: "test",
447
- clientSecret: "test",
448
- },
449
- providerId: "test-1",
450
- },
451
- headers,
452
- });
453
378
 
454
- await expect(
455
- auth.api.registerSSOProvider({
456
- body: {
457
- issuer: server.issuer.url!,
458
- domain: "localhost.com",
459
- oidcConfig: {
460
- clientId: "test",
461
- clientSecret: "test",
462
- },
463
- providerId: "test-2",
464
- },
465
- headers,
466
- }),
467
- ).rejects.toMatchObject({
468
- status: "FORBIDDEN",
469
- body: {
470
- message: "You have reached the maximum number of SSO providers",
471
- },
472
- });
473
- });
474
379
  it("should not create user with SSO provider when sign ups are disabled", async () => {
475
- const res = await auth.api.signInSSO({
476
- body: {
477
- email: "my-email@localhost.com",
478
- 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),
479
387
  },
480
388
  });
481
389
  expect(res.url).toContain("http://localhost:8080/authorize");
482
390
  expect(res.url).toContain(
483
391
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
484
392
  );
485
- const headers = new Headers();
486
- const callbackURL = await simulateOAuthFlow(res.url, headers);
393
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
487
394
  expect(callbackURL).toContain(
488
395
  "/api/auth/error/error?error=signup disabled",
489
396
  );
490
397
  });
491
398
 
492
399
  it("should create user with SSO provider when sign ups are disabled but sign up is requested", async () => {
493
- const res = await auth.api.signInSSO({
494
- body: {
495
- email: "my-email@localhost.com",
496
- callbackURL: "/dashboard",
497
- 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),
498
408
  },
499
409
  });
500
410
  expect(res.url).toContain("http://localhost:8080/authorize");
501
411
  expect(res.url).toContain(
502
412
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
503
413
  );
504
- const headers = new Headers();
505
- const callbackURL = await simulateOAuthFlow(res.url, headers);
414
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
506
415
  expect(callbackURL).toContain("/dashboard");
507
416
  });
508
417
  });
509
418
 
510
419
  describe("provisioning", async (ctx) => {
511
- const { auth, signInWithTestUser, customFetchImpl } =
512
- await getTestInstanceMemory({
420
+ const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
421
+ await getTestInstance({
513
422
  plugins: [sso(), organization()],
514
423
  });
515
424
 
425
+ const authClient = createAuthClient({
426
+ plugins: [ssoClient()],
427
+ baseURL: "http://localhost:3000",
428
+ fetchOptions: {
429
+ customFetchImpl,
430
+ },
431
+ });
432
+
516
433
  beforeAll(async () => {
517
434
  await server.issuer.keys.generate("RS256");
518
435
  server.issuer.on;
@@ -540,12 +457,14 @@ describe("provisioning", async (ctx) => {
540
457
  if (!location) throw new Error("No redirect location found");
541
458
 
542
459
  let callbackURL = "";
460
+ const newHeaders = new Headers();
543
461
  await betterFetch(location, {
544
462
  method: "GET",
545
463
  customFetchImpl: fetchImpl || customFetchImpl,
546
464
  headers,
547
465
  onError(context) {
548
466
  callbackURL = context.response.headers.get("location") || "";
467
+ cookieSetter(newHeaders)(context);
549
468
  },
550
469
  });
551
470
 
@@ -605,18 +524,20 @@ describe("provisioning", async (ctx) => {
605
524
  expect(provider).toMatchObject({
606
525
  organizationId: organization?.id,
607
526
  });
608
-
609
- const res = await auth.api.signInSSO({
610
- body: {
611
- email: "my-email@localhost.com",
612
- 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,
613
534
  },
614
535
  });
615
536
  expect(res.url).toContain("http://localhost:8080/authorize");
616
537
  expect(res.url).toContain(
617
538
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
618
539
  );
619
- const newHeaders = new Headers();
540
+
620
541
  const callbackURL = await simulateOAuthFlow(res.url, newHeaders);
621
542
  expect(callbackURL).toContain("/dashboard");
622
543
  const org = await auth.api.getFullOrganization({