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

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,114 @@ 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
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
162
212
  expect(callbackURL).toContain("/dashboard");
163
213
  });
164
214
 
165
215
  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",
216
+ const headers = new Headers();
217
+ const res = await authClient.signIn.sso({
218
+ email: "my-email@test.com",
219
+ domain: "localhost.com",
220
+ callbackURL: "/dashboard",
221
+ fetchOptions: {
222
+ throw: true,
223
+ onSuccess: cookieSetter(headers),
171
224
  },
172
225
  });
173
226
  expect(res.url).toContain("http://localhost:8080/authorize");
174
227
  expect(res.url).toContain(
175
228
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
176
229
  );
177
- const headers = new Headers();
178
- const callbackURL = await simulateOAuthFlow(res.url, headers);
230
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
179
231
  expect(callbackURL).toContain("/dashboard");
180
232
  });
181
233
 
182
234
  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",
235
+ const headers = new Headers();
236
+ const res = await authClient.signIn.sso({
237
+ providerId: "test",
238
+ callbackURL: "/dashboard",
239
+ fetchOptions: {
240
+ throw: true,
241
+ onSuccess: cookieSetter(headers),
187
242
  },
188
243
  });
189
244
  expect(res.url).toContain("http://localhost:8080/authorize");
190
245
  expect(res.url).toContain(
191
246
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
192
247
  );
193
- const headers = new Headers();
194
- const callbackURL = await simulateOAuthFlow(res.url, headers);
248
+
249
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
195
250
  expect(callbackURL).toContain("/dashboard");
196
251
  });
197
252
  });
198
253
 
199
254
  describe("SSO disable implicit sign in", async () => {
200
- const { auth, signInWithTestUser, customFetchImpl } =
201
- await getTestInstanceMemory({
255
+ const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
256
+ await getTestInstance({
202
257
  plugins: [sso({ disableImplicitSignUp: true }), organization()],
203
258
  });
204
259
 
260
+ const authClient = createAuthClient({
261
+ plugins: [ssoClient()],
262
+ baseURL: "http://localhost:3000",
263
+ fetchOptions: {
264
+ customFetchImpl,
265
+ },
266
+ });
267
+
205
268
  beforeAll(async () => {
206
269
  await server.issuer.keys.generate("RS256");
207
270
  server.issuer.on;
@@ -246,7 +309,7 @@ describe("SSO disable implicit sign in", async () => {
246
309
  });
247
310
 
248
311
  if (!location) throw new Error("No redirect location found");
249
-
312
+ const newHeaders = new Headers(headers);
250
313
  let callbackURL = "";
251
314
  await betterFetch(location, {
252
315
  method: "GET",
@@ -254,10 +317,11 @@ describe("SSO disable implicit sign in", async () => {
254
317
  headers,
255
318
  onError(context) {
256
319
  callbackURL = context.response.headers.get("location") || "";
320
+ cookieSetter(newHeaders)(context);
257
321
  },
258
322
  });
259
323
 
260
- return callbackURL;
324
+ return { callbackURL, headers: newHeaders };
261
325
  }
262
326
 
263
327
  it("should register a new SSO provider", async () => {
@@ -272,13 +336,14 @@ describe("SSO disable implicit sign in", async () => {
272
336
  authorizationEndpoint: `${server.issuer.url}/authorize`,
273
337
  tokenEndpoint: `${server.issuer.url}/token`,
274
338
  jwksEndpoint: `${server.issuer.url}/jwks`,
275
- },
276
- mapping: {
277
- id: "sub",
278
- email: "email",
279
- emailVerified: "email_verified",
280
- name: "name",
281
- image: "picture",
339
+ discoveryEndpoint: `${server.issuer.url}/.well-known/openid-configuration`,
340
+ mapping: {
341
+ id: "sub",
342
+ email: "email",
343
+ emailVerified: "email_verified",
344
+ name: "name",
345
+ image: "picture",
346
+ },
282
347
  },
283
348
  providerId: "test",
284
349
  },
@@ -307,150 +372,61 @@ describe("SSO disable implicit sign in", async () => {
307
372
  userId: expect.any(String),
308
373
  });
309
374
  });
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
375
 
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
376
  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",
377
+ const headers = new Headers();
378
+ const res = await authClient.signIn.sso({
379
+ email: "my-email@localhost.com",
380
+ callbackURL: "/dashboard",
381
+ fetchOptions: {
382
+ throw: true,
383
+ onSuccess: cookieSetter(headers),
417
384
  },
418
385
  });
419
386
  expect(res.url).toContain("http://localhost:8080/authorize");
420
387
  expect(res.url).toContain(
421
388
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
422
389
  );
423
- const headers = new Headers();
424
- const callbackURL = await simulateOAuthFlow(res.url, headers);
390
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
425
391
  expect(callbackURL).toContain(
426
392
  "/api/auth/error/error?error=signup disabled",
427
393
  );
428
394
  });
429
395
 
430
396
  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,
397
+ const headers = new Headers();
398
+ const res = await authClient.signIn.sso({
399
+ email: "my-email@localhost.com",
400
+ callbackURL: "/dashboard",
401
+ requestSignUp: true,
402
+ fetchOptions: {
403
+ throw: true,
404
+ onSuccess: cookieSetter(headers),
436
405
  },
437
406
  });
438
407
  expect(res.url).toContain("http://localhost:8080/authorize");
439
408
  expect(res.url).toContain(
440
409
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
441
410
  );
442
- const headers = new Headers();
443
- const callbackURL = await simulateOAuthFlow(res.url, headers);
411
+ const { callbackURL } = await simulateOAuthFlow(res.url, headers);
444
412
  expect(callbackURL).toContain("/dashboard");
445
413
  });
446
414
  });
447
415
 
448
416
  describe("provisioning", async (ctx) => {
449
- const { auth, signInWithTestUser, customFetchImpl } =
450
- await getTestInstanceMemory({
417
+ const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
418
+ await getTestInstance({
451
419
  plugins: [sso(), organization()],
452
420
  });
453
421
 
422
+ const authClient = createAuthClient({
423
+ plugins: [ssoClient()],
424
+ baseURL: "http://localhost:3000",
425
+ fetchOptions: {
426
+ customFetchImpl,
427
+ },
428
+ });
429
+
454
430
  beforeAll(async () => {
455
431
  await server.issuer.keys.generate("RS256");
456
432
  server.issuer.on;
@@ -478,12 +454,14 @@ describe("provisioning", async (ctx) => {
478
454
  if (!location) throw new Error("No redirect location found");
479
455
 
480
456
  let callbackURL = "";
457
+ const newHeaders = new Headers();
481
458
  await betterFetch(location, {
482
459
  method: "GET",
483
460
  customFetchImpl: fetchImpl || customFetchImpl,
484
461
  headers,
485
462
  onError(context) {
486
463
  callbackURL = context.response.headers.get("location") || "";
464
+ cookieSetter(newHeaders)(context);
487
465
  },
488
466
  });
489
467
 
@@ -526,13 +504,14 @@ describe("provisioning", async (ctx) => {
526
504
  authorizationEndpoint: `${server.issuer.url}/authorize`,
527
505
  tokenEndpoint: `${server.issuer.url}/token`,
528
506
  jwksEndpoint: `${server.issuer.url}/jwks`,
529
- },
530
- mapping: {
531
- id: "sub",
532
- email: "email",
533
- emailVerified: "email_verified",
534
- name: "name",
535
- image: "picture",
507
+ discoveryEndpoint: `${server.issuer.url}/.well-known/openid-configuration`,
508
+ mapping: {
509
+ id: "sub",
510
+ email: "email",
511
+ emailVerified: "email_verified",
512
+ name: "name",
513
+ image: "picture",
514
+ },
536
515
  },
537
516
  providerId: "test2",
538
517
  organizationId: organization?.id,
@@ -542,18 +521,20 @@ describe("provisioning", async (ctx) => {
542
521
  expect(provider).toMatchObject({
543
522
  organizationId: organization?.id,
544
523
  });
545
-
546
- const res = await auth.api.signInSSO({
547
- body: {
548
- email: "my-email@localhost.com",
549
- callbackURL: "/dashboard",
524
+ const newHeaders = new Headers();
525
+ const res = await authClient.signIn.sso({
526
+ email: "my-email@localhost.com",
527
+ callbackURL: "/dashboard",
528
+ fetchOptions: {
529
+ onSuccess: cookieSetter(newHeaders),
530
+ throw: true,
550
531
  },
551
532
  });
552
533
  expect(res.url).toContain("http://localhost:8080/authorize");
553
534
  expect(res.url).toContain(
554
535
  "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
555
536
  );
556
- const newHeaders = new Headers();
537
+
557
538
  const callbackURL = await simulateOAuthFlow(res.url, newHeaders);
558
539
  expect(callbackURL).toContain("/dashboard");
559
540
  const org = await auth.api.getFullOrganization({