@better-auth/sso 1.5.0-beta.8 → 1.5.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/src/oidc.test.ts DELETED
@@ -1,576 +0,0 @@
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";
6
- import { afterAll, beforeAll, describe, expect, it } from "vitest";
7
- import { sso } from ".";
8
- import { ssoClient } from "./client";
9
-
10
- const server = new OAuth2Server();
11
-
12
- describe("SSO", async () => {
13
- const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
14
- await getTestInstance({
15
- trustedOrigins: ["http://localhost:8080"],
16
- plugins: [sso(), organization()],
17
- });
18
-
19
- const authClient = createAuthClient({
20
- plugins: [ssoClient()],
21
- baseURL: "http://localhost:3000",
22
- fetchOptions: {
23
- customFetchImpl,
24
- },
25
- });
26
-
27
- beforeAll(async () => {
28
- await server.issuer.keys.generate("RS256");
29
- server.issuer.on;
30
- await server.start(8080, "localhost");
31
- console.log("Issuer URL:", server.issuer.url); // -> http://localhost:8080
32
- });
33
-
34
- afterAll(async () => {
35
- await server.stop().catch(() => {});
36
- });
37
-
38
- server.service.on("beforeUserinfo", (userInfoResponse, req) => {
39
- userInfoResponse.body = {
40
- email: "oauth2@test.com",
41
- name: "OAuth2 Test",
42
- sub: "oauth2",
43
- picture: "https://test.com/picture.png",
44
- email_verified: true,
45
- };
46
- userInfoResponse.statusCode = 200;
47
- });
48
-
49
- server.service.on("beforeTokenSigning", (token, req) => {
50
- token.payload.email = "sso-user@localhost:8000.com";
51
- token.payload.email_verified = true;
52
- token.payload.name = "Test User";
53
- token.payload.picture = "https://test.com/picture.png";
54
- });
55
-
56
- async function simulateOAuthFlow(
57
- authUrl: string,
58
- headers: Headers,
59
- fetchImpl?: (...args: any) => any,
60
- ) {
61
- let location: string | null = null;
62
- await betterFetch(authUrl, {
63
- method: "GET",
64
- redirect: "manual",
65
- onError(context) {
66
- location = context.response.headers.get("location");
67
- },
68
- });
69
-
70
- if (!location) throw new Error("No redirect location found");
71
- const newHeaders = new Headers();
72
- let callbackURL = "";
73
- await betterFetch(location, {
74
- method: "GET",
75
- customFetchImpl: fetchImpl || customFetchImpl,
76
- headers,
77
- onError(context) {
78
- callbackURL = context.response.headers.get("location") || "";
79
- cookieSetter(newHeaders)(context);
80
- },
81
- });
82
-
83
- return { callbackURL, headers: newHeaders };
84
- }
85
-
86
- it("should register a new SSO provider", async () => {
87
- const { headers } = await signInWithTestUser();
88
- const provider = await auth.api.registerSSOProvider({
89
- body: {
90
- issuer: server.issuer.url!,
91
- domain: "localhost.com",
92
- oidcConfig: {
93
- clientId: "test",
94
- clientSecret: "test",
95
- authorizationEndpoint: `${server.issuer.url}/authorize`,
96
- tokenEndpoint: `${server.issuer.url}/token`,
97
- jwksEndpoint: `${server.issuer.url}/jwks`,
98
- discoveryEndpoint: `${server.issuer.url}/.well-known/openid-configuration`,
99
- mapping: {
100
- id: "sub",
101
- email: "email",
102
- emailVerified: "email_verified",
103
- name: "name",
104
- image: "picture",
105
- },
106
- },
107
- providerId: "test",
108
- },
109
- headers,
110
- });
111
- expect(provider).toMatchObject({
112
- id: expect.any(String),
113
- issuer: "http://localhost:8080",
114
- oidcConfig: {
115
- issuer: "http://localhost:8080",
116
- clientId: "test",
117
- clientSecret: "test",
118
- authorizationEndpoint: "http://localhost:8080/authorize",
119
- tokenEndpoint: "http://localhost:8080/token",
120
- jwksEndpoint: "http://localhost:8080/jwks",
121
- discoveryEndpoint:
122
- "http://localhost:8080/.well-known/openid-configuration",
123
- mapping: {
124
- id: "sub",
125
- email: "email",
126
- emailVerified: "email_verified",
127
- name: "name",
128
- image: "picture",
129
- },
130
- },
131
- userId: expect.any(String),
132
- });
133
- });
134
-
135
- it("should fail to register a new SSO provider with invalid issuer", async () => {
136
- const { headers } = await signInWithTestUser();
137
-
138
- try {
139
- await auth.api.registerSSOProvider({
140
- body: {
141
- issuer: "invalid",
142
- domain: "localhost",
143
- providerId: "test",
144
- oidcConfig: {
145
- clientId: "test",
146
- clientSecret: "test",
147
- },
148
- },
149
- headers,
150
- });
151
- } catch (e) {
152
- expect(e).toMatchObject({
153
- status: "BAD_REQUEST",
154
- body: {
155
- message: "Invalid issuer. Must be a valid URL",
156
- },
157
- });
158
- }
159
- });
160
-
161
- it("should not allow creating a provider with duplicate providerId", async () => {
162
- const { headers } = await signInWithTestUser();
163
-
164
- await auth.api.registerSSOProvider({
165
- body: {
166
- issuer: server.issuer.url!,
167
- domain: "duplicate.com",
168
- providerId: "duplicate-oidc-provider",
169
- oidcConfig: {
170
- clientId: "test",
171
- clientSecret: "test",
172
- },
173
- },
174
- headers,
175
- });
176
-
177
- await expect(
178
- auth.api.registerSSOProvider({
179
- body: {
180
- issuer: server.issuer.url!,
181
- domain: "another-duplicate.com",
182
- providerId: "duplicate-oidc-provider",
183
- oidcConfig: {
184
- clientId: "test2",
185
- clientSecret: "test2",
186
- },
187
- },
188
- headers,
189
- }),
190
- ).rejects.toMatchObject({
191
- status: "UNPROCESSABLE_ENTITY",
192
- body: {
193
- message: "SSO provider with this providerId already exists",
194
- },
195
- });
196
- });
197
-
198
- it("should sign in with SSO provider with email matching", async () => {
199
- const headers = new Headers();
200
- const res = await authClient.signIn.sso({
201
- email: "my-email@localhost.com",
202
- callbackURL: "/dashboard",
203
- fetchOptions: {
204
- throw: true,
205
- onSuccess: cookieSetter(headers),
206
- },
207
- });
208
- expect(res.url).toContain("http://localhost:8080/authorize");
209
- expect(res.url).toContain(
210
- "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
211
- );
212
- expect(res.url).toContain("login_hint=my-email%40localhost.com");
213
- const { callbackURL } = await simulateOAuthFlow(res.url, headers);
214
- expect(callbackURL).toContain("/dashboard");
215
- });
216
-
217
- it("should sign in with SSO provider with domain", async () => {
218
- const headers = new Headers();
219
- const res = await authClient.signIn.sso({
220
- email: "my-email@test.com",
221
- domain: "localhost.com",
222
- callbackURL: "/dashboard",
223
- fetchOptions: {
224
- throw: true,
225
- onSuccess: cookieSetter(headers),
226
- },
227
- });
228
- expect(res.url).toContain("http://localhost:8080/authorize");
229
- expect(res.url).toContain(
230
- "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
231
- );
232
- const { callbackURL } = await simulateOAuthFlow(res.url, headers);
233
- expect(callbackURL).toContain("/dashboard");
234
- });
235
-
236
- it("should sign in with SSO provider with providerId", async () => {
237
- const headers = new Headers();
238
- const res = await authClient.signIn.sso({
239
- providerId: "test",
240
- loginHint: "user@example.com",
241
- callbackURL: "/dashboard",
242
- fetchOptions: {
243
- throw: true,
244
- onSuccess: cookieSetter(headers),
245
- },
246
- });
247
- expect(res.url).toContain("http://localhost:8080/authorize");
248
- expect(res.url).toContain(
249
- "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
250
- );
251
- expect(res.url).toContain("login_hint=user%40example.com");
252
-
253
- const { callbackURL } = await simulateOAuthFlow(res.url, headers);
254
- expect(callbackURL).toContain("/dashboard");
255
- });
256
- });
257
-
258
- describe("SSO disable implicit sign in", async () => {
259
- const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
260
- await getTestInstance({
261
- trustedOrigins: ["http://localhost:8080"],
262
- plugins: [sso({ disableImplicitSignUp: true }), organization()],
263
- });
264
-
265
- const authClient = createAuthClient({
266
- plugins: [ssoClient()],
267
- baseURL: "http://localhost:3000",
268
- fetchOptions: {
269
- customFetchImpl,
270
- },
271
- });
272
-
273
- beforeAll(async () => {
274
- await server.issuer.keys.generate("RS256");
275
- server.issuer.on;
276
- await server.start(8080, "localhost");
277
- console.log("Issuer URL:", server.issuer.url); // -> http://localhost:8080
278
- });
279
-
280
- afterAll(async () => {
281
- await server.stop();
282
- });
283
-
284
- server.service.on("beforeUserinfo", (userInfoResponse, req) => {
285
- userInfoResponse.body = {
286
- email: "oauth2@test.com",
287
- name: "OAuth2 Test",
288
- sub: "oauth2",
289
- picture: "https://test.com/picture.png",
290
- email_verified: true,
291
- };
292
- userInfoResponse.statusCode = 200;
293
- });
294
-
295
- server.service.on("beforeTokenSigning", (token, req) => {
296
- token.payload.email = "sso-user@localhost:8000.com";
297
- token.payload.email_verified = true;
298
- token.payload.name = "Test User";
299
- token.payload.picture = "https://test.com/picture.png";
300
- });
301
-
302
- async function simulateOAuthFlow(
303
- authUrl: string,
304
- headers: Headers,
305
- fetchImpl?: (...args: any) => any,
306
- ) {
307
- let location: string | null = null;
308
- await betterFetch(authUrl, {
309
- method: "GET",
310
- redirect: "manual",
311
- onError(context) {
312
- location = context.response.headers.get("location");
313
- },
314
- });
315
-
316
- if (!location) throw new Error("No redirect location found");
317
- const newHeaders = new Headers(headers);
318
- let callbackURL = "";
319
- await betterFetch(location, {
320
- method: "GET",
321
- customFetchImpl: fetchImpl || customFetchImpl,
322
- headers,
323
- onError(context) {
324
- callbackURL = context.response.headers.get("location") || "";
325
- cookieSetter(newHeaders)(context);
326
- },
327
- });
328
-
329
- return { callbackURL, headers: newHeaders };
330
- }
331
-
332
- it("should register a new SSO provider", async () => {
333
- const { headers } = await signInWithTestUser();
334
- const provider = await auth.api.registerSSOProvider({
335
- body: {
336
- issuer: server.issuer.url!,
337
- domain: "localhost.com",
338
- oidcConfig: {
339
- clientId: "test",
340
- clientSecret: "test",
341
- authorizationEndpoint: `${server.issuer.url}/authorize`,
342
- tokenEndpoint: `${server.issuer.url}/token`,
343
- jwksEndpoint: `${server.issuer.url}/jwks`,
344
- discoveryEndpoint: `${server.issuer.url}/.well-known/openid-configuration`,
345
- mapping: {
346
- id: "sub",
347
- email: "email",
348
- emailVerified: "email_verified",
349
- name: "name",
350
- image: "picture",
351
- },
352
- },
353
- providerId: "test",
354
- },
355
- headers,
356
- });
357
- expect(provider).toMatchObject({
358
- id: expect.any(String),
359
- issuer: "http://localhost:8080",
360
- oidcConfig: {
361
- issuer: "http://localhost:8080",
362
- clientId: "test",
363
- clientSecret: "test",
364
- authorizationEndpoint: "http://localhost:8080/authorize",
365
- tokenEndpoint: "http://localhost:8080/token",
366
- jwksEndpoint: "http://localhost:8080/jwks",
367
- discoveryEndpoint:
368
- "http://localhost:8080/.well-known/openid-configuration",
369
- mapping: {
370
- id: "sub",
371
- email: "email",
372
- emailVerified: "email_verified",
373
- name: "name",
374
- image: "picture",
375
- },
376
- },
377
- userId: expect.any(String),
378
- });
379
- });
380
-
381
- it("should not create user with SSO provider when sign ups are disabled", async () => {
382
- const headers = new Headers();
383
- const res = await authClient.signIn.sso({
384
- email: "my-email@localhost.com",
385
- callbackURL: "/dashboard",
386
- fetchOptions: {
387
- throw: true,
388
- onSuccess: cookieSetter(headers),
389
- },
390
- });
391
- expect(res.url).toContain("http://localhost:8080/authorize");
392
- expect(res.url).toContain(
393
- "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
394
- );
395
- const { callbackURL } = await simulateOAuthFlow(res.url, headers);
396
- expect(callbackURL).toContain(
397
- "/api/auth/error/error?error=signup disabled",
398
- );
399
- });
400
-
401
- it("should create user with SSO provider when sign ups are disabled but sign up is requested", async () => {
402
- const headers = new Headers();
403
- const res = await authClient.signIn.sso({
404
- email: "my-email@localhost.com",
405
- callbackURL: "/dashboard",
406
- requestSignUp: true,
407
- fetchOptions: {
408
- throw: true,
409
- onSuccess: cookieSetter(headers),
410
- },
411
- });
412
- expect(res.url).toContain("http://localhost:8080/authorize");
413
- expect(res.url).toContain(
414
- "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
415
- );
416
- const { callbackURL } = await simulateOAuthFlow(res.url, headers);
417
- expect(callbackURL).toContain("/dashboard");
418
- });
419
- });
420
-
421
- describe("provisioning", async (ctx) => {
422
- const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
423
- await getTestInstance({
424
- trustedOrigins: ["http://localhost:8080"],
425
- plugins: [sso(), organization()],
426
- });
427
-
428
- const authClient = createAuthClient({
429
- plugins: [ssoClient()],
430
- baseURL: "http://localhost:3000",
431
- fetchOptions: {
432
- customFetchImpl,
433
- },
434
- });
435
-
436
- beforeAll(async () => {
437
- await server.issuer.keys.generate("RS256");
438
- server.issuer.on;
439
- await server.start(8080, "localhost");
440
- console.log("Issuer URL:", server.issuer.url); // -> http://localhost:8080
441
- });
442
-
443
- afterAll(async () => {
444
- await server.stop();
445
- });
446
- async function simulateOAuthFlow(
447
- authUrl: string,
448
- headers: Headers,
449
- fetchImpl?: (...args: any) => any,
450
- ) {
451
- let location: string | null = null;
452
- await betterFetch(authUrl, {
453
- method: "GET",
454
- redirect: "manual",
455
- onError(context) {
456
- location = context.response.headers.get("location");
457
- },
458
- });
459
-
460
- if (!location) throw new Error("No redirect location found");
461
-
462
- let callbackURL = "";
463
- const newHeaders = new Headers();
464
- await betterFetch(location, {
465
- method: "GET",
466
- customFetchImpl: fetchImpl || customFetchImpl,
467
- headers,
468
- onError(context) {
469
- callbackURL = context.response.headers.get("location") || "";
470
- cookieSetter(newHeaders)(context);
471
- },
472
- });
473
-
474
- return callbackURL;
475
- }
476
-
477
- server.service.on("beforeUserinfo", (userInfoResponse, req) => {
478
- userInfoResponse.body = {
479
- email: "test@localhost.com",
480
- name: "OAuth2 Test",
481
- sub: "oauth2",
482
- picture: "https://test.com/picture.png",
483
- email_verified: true,
484
- };
485
- userInfoResponse.statusCode = 200;
486
- });
487
-
488
- server.service.on("beforeTokenSigning", (token, req) => {
489
- token.payload.email = "sso-user@localhost:8000.com";
490
- token.payload.email_verified = true;
491
- token.payload.name = "Test User";
492
- token.payload.picture = "https://test.com/picture.png";
493
- });
494
- it("should provision user", async () => {
495
- const { headers } = await signInWithTestUser();
496
- const organization = await auth.api.createOrganization({
497
- body: {
498
- name: "Localhost",
499
- slug: "localhost",
500
- },
501
- headers,
502
- });
503
- const provider = await auth.api.registerSSOProvider({
504
- body: {
505
- issuer: server.issuer.url!,
506
- domain: "localhost.com",
507
- oidcConfig: {
508
- clientId: "test",
509
- clientSecret: "test",
510
- authorizationEndpoint: `${server.issuer.url}/authorize`,
511
- tokenEndpoint: `${server.issuer.url}/token`,
512
- jwksEndpoint: `${server.issuer.url}/jwks`,
513
- discoveryEndpoint: `${server.issuer.url}/.well-known/openid-configuration`,
514
- mapping: {
515
- id: "sub",
516
- email: "email",
517
- emailVerified: "email_verified",
518
- name: "name",
519
- image: "picture",
520
- },
521
- },
522
- providerId: "test2",
523
- organizationId: organization?.id,
524
- },
525
- headers,
526
- });
527
- expect(provider).toMatchObject({
528
- organizationId: organization?.id,
529
- });
530
- const newHeaders = new Headers();
531
- const res = await authClient.signIn.sso({
532
- email: "my-email@localhost.com",
533
- callbackURL: "/dashboard",
534
- fetchOptions: {
535
- onSuccess: cookieSetter(newHeaders),
536
- throw: true,
537
- },
538
- });
539
- expect(res.url).toContain("http://localhost:8080/authorize");
540
- expect(res.url).toContain(
541
- "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
542
- );
543
-
544
- const callbackURL = await simulateOAuthFlow(res.url, newHeaders);
545
- expect(callbackURL).toContain("/dashboard");
546
- const org = await auth.api.getFullOrganization({
547
- query: {
548
- organizationId: organization?.id || "",
549
- },
550
- headers,
551
- });
552
- const member = org?.members.find(
553
- (m: any) => m.user.email === "sso-user@localhost:8000.com",
554
- );
555
- expect(member).toMatchObject({
556
- role: "member",
557
- user: {
558
- id: expect.any(String),
559
- name: "Test User",
560
- email: "sso-user@localhost:8000.com",
561
- image: "https://test.com/picture.png",
562
- },
563
- });
564
- });
565
-
566
- it("should sign in with SSO provide with org slug", async () => {
567
- const res = await auth.api.signInSSO({
568
- body: {
569
- organizationSlug: "localhost",
570
- callbackURL: "/dashboard",
571
- },
572
- });
573
-
574
- expect(res.url).toContain("http://localhost:8080/authorize");
575
- });
576
- });