@better-auth/sso 1.4.7-beta.4 → 1.4.8-beta.1
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/.turbo/turbo-build.log +7 -7
- package/dist/client.d.mts +1 -1
- package/dist/{index-GoyGoP_a.d.mts → index-DNWhGQW-.d.mts} +94 -77
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +537 -286
- package/package.json +3 -3
- package/src/constants.ts +42 -0
- package/src/domain-verification.test.ts +1 -0
- package/src/index.ts +38 -11
- package/src/linking/index.ts +2 -0
- package/src/linking/org-assignment.ts +158 -0
- package/src/linking/types.ts +10 -0
- package/src/oidc/discovery.test.ts +359 -25
- package/src/oidc/discovery.ts +168 -29
- package/src/oidc/errors.ts +6 -0
- package/src/oidc/types.ts +9 -0
- package/src/oidc.test.ts +3 -0
- package/src/routes/sso.ts +339 -332
- package/src/saml/algorithms.test.ts +205 -0
- package/src/saml/algorithms.ts +259 -0
- package/src/saml/index.ts +9 -0
- package/src/saml.test.ts +351 -127
- package/src/types.ts +18 -16
- package/src/authn-request-store.ts +0 -76
- package/src/authn-request.test.ts +0 -99
|
@@ -75,10 +75,13 @@ describe("OIDC Discovery", () => {
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
describe("validateDiscoveryUrl", () => {
|
|
78
|
+
const isTrustedOrigin = vi.fn().mockReturnValue(true);
|
|
79
|
+
|
|
78
80
|
it("should accept valid HTTPS URL", () => {
|
|
79
81
|
expect(() =>
|
|
80
82
|
validateDiscoveryUrl(
|
|
81
83
|
"https://idp.example.com/.well-known/openid-configuration",
|
|
84
|
+
isTrustedOrigin,
|
|
82
85
|
),
|
|
83
86
|
).not.toThrow();
|
|
84
87
|
});
|
|
@@ -87,28 +90,31 @@ describe("OIDC Discovery", () => {
|
|
|
87
90
|
expect(() =>
|
|
88
91
|
validateDiscoveryUrl(
|
|
89
92
|
"http://localhost:8080/.well-known/openid-configuration",
|
|
93
|
+
isTrustedOrigin,
|
|
90
94
|
),
|
|
91
95
|
).not.toThrow();
|
|
92
96
|
});
|
|
93
97
|
|
|
94
98
|
it("should reject invalid URL", () => {
|
|
95
|
-
expect(() => validateDiscoveryUrl("not-a-url")).toThrow(
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
expect(() => validateDiscoveryUrl("not-a-url", isTrustedOrigin)).toThrow(
|
|
100
|
+
DiscoveryError,
|
|
101
|
+
);
|
|
102
|
+
expect(() => validateDiscoveryUrl("not-a-url", isTrustedOrigin)).toThrow(
|
|
103
|
+
'The url "discoveryEndpoint" must be valid',
|
|
98
104
|
);
|
|
99
105
|
});
|
|
100
106
|
|
|
101
107
|
it("should reject non-HTTP protocols", () => {
|
|
102
|
-
expect(() =>
|
|
103
|
-
|
|
104
|
-
);
|
|
105
|
-
expect(() =>
|
|
106
|
-
"
|
|
107
|
-
);
|
|
108
|
+
expect(() =>
|
|
109
|
+
validateDiscoveryUrl("ftp://example.com/config", isTrustedOrigin),
|
|
110
|
+
).toThrow(DiscoveryError);
|
|
111
|
+
expect(() =>
|
|
112
|
+
validateDiscoveryUrl("ftp://example.com/config", isTrustedOrigin),
|
|
113
|
+
).toThrow("must use the http or https supported protocols");
|
|
108
114
|
});
|
|
109
115
|
|
|
110
116
|
it("should throw DiscoveryError with discovery_invalid_url code for invalid URL", () => {
|
|
111
|
-
expect(() => validateDiscoveryUrl("not-a-url")).toThrow(
|
|
117
|
+
expect(() => validateDiscoveryUrl("not-a-url", isTrustedOrigin)).toThrow(
|
|
112
118
|
expect.objectContaining({
|
|
113
119
|
code: "discovery_invalid_url",
|
|
114
120
|
details: expect.objectContaining({
|
|
@@ -119,7 +125,9 @@ describe("OIDC Discovery", () => {
|
|
|
119
125
|
});
|
|
120
126
|
|
|
121
127
|
it("should throw DiscoveryError with discovery_invalid_url code for non-HTTP protocol", () => {
|
|
122
|
-
expect(() =>
|
|
128
|
+
expect(() =>
|
|
129
|
+
validateDiscoveryUrl("ftp://example.com/config", isTrustedOrigin),
|
|
130
|
+
).toThrow(
|
|
123
131
|
expect.objectContaining({
|
|
124
132
|
code: "discovery_invalid_url",
|
|
125
133
|
details: expect.objectContaining({
|
|
@@ -128,6 +136,22 @@ describe("OIDC Discovery", () => {
|
|
|
128
136
|
}),
|
|
129
137
|
);
|
|
130
138
|
});
|
|
139
|
+
|
|
140
|
+
it("should throw DiscoveryError with discovery_untrusted_origin code for untrusted origins", () => {
|
|
141
|
+
isTrustedOrigin.mockReturnValue(false);
|
|
142
|
+
|
|
143
|
+
expect(() =>
|
|
144
|
+
validateDiscoveryUrl(
|
|
145
|
+
"https://untrusted.com/.well-known/openid-configuration",
|
|
146
|
+
isTrustedOrigin,
|
|
147
|
+
),
|
|
148
|
+
).toThrow(
|
|
149
|
+
expect.objectContaining({
|
|
150
|
+
code: "discovery_untrusted_origin",
|
|
151
|
+
message: `The main discovery endpoint "https://untrusted.com/.well-known/openid-configuration" is not trusted by your trusted origins configuration.`,
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
});
|
|
131
155
|
});
|
|
132
156
|
|
|
133
157
|
describe("validateDiscoveryDocument", () => {
|
|
@@ -314,18 +338,265 @@ describe("OIDC Discovery", () => {
|
|
|
314
338
|
});
|
|
315
339
|
});
|
|
316
340
|
|
|
317
|
-
describe("normalizeDiscoveryUrls
|
|
318
|
-
|
|
341
|
+
describe("normalizeDiscoveryUrls", () => {
|
|
342
|
+
const isTrustedOrigin = vi.fn().mockReturnValue(true);
|
|
343
|
+
|
|
344
|
+
it("should return the document unchanged if all urls are already absolute", () => {
|
|
319
345
|
const doc = createMockDiscoveryDocument();
|
|
320
|
-
const result = normalizeDiscoveryUrls(
|
|
346
|
+
const result = normalizeDiscoveryUrls(
|
|
347
|
+
doc,
|
|
348
|
+
"https://idp.example.com",
|
|
349
|
+
isTrustedOrigin,
|
|
350
|
+
);
|
|
321
351
|
expect(result).toEqual(doc);
|
|
322
352
|
});
|
|
353
|
+
|
|
354
|
+
it("should resolve all required discovery urls relative to the issuer", () => {
|
|
355
|
+
const expected = createMockDiscoveryDocument({
|
|
356
|
+
issuer: "https://idp.example.com",
|
|
357
|
+
authorization_endpoint: "https://idp.example.com/oauth2/authorize",
|
|
358
|
+
token_endpoint: "https://idp.example.com/oauth2/token",
|
|
359
|
+
jwks_uri: "https://idp.example.com/.well-known/jwks.json",
|
|
360
|
+
});
|
|
361
|
+
const doc = createMockDiscoveryDocument({
|
|
362
|
+
issuer: "https://idp.example.com",
|
|
363
|
+
authorization_endpoint: "/oauth2/authorize",
|
|
364
|
+
token_endpoint: "/oauth2/token",
|
|
365
|
+
jwks_uri: "/.well-known/jwks.json",
|
|
366
|
+
});
|
|
367
|
+
const result = normalizeDiscoveryUrls(
|
|
368
|
+
doc,
|
|
369
|
+
"https://idp.example.com",
|
|
370
|
+
isTrustedOrigin,
|
|
371
|
+
);
|
|
372
|
+
expect(result).toEqual(expected);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("should resolve all discovery urls relative to the issuer", () => {
|
|
376
|
+
const expected = createMockDiscoveryDocument({
|
|
377
|
+
issuer: "https://idp.example.com",
|
|
378
|
+
authorization_endpoint: "https://idp.example.com/oauth2/authorize",
|
|
379
|
+
token_endpoint: "https://idp.example.com/oauth2/token",
|
|
380
|
+
jwks_uri: "https://idp.example.com/.well-known/jwks.json",
|
|
381
|
+
userinfo_endpoint: "https://idp.example.com/userinfo",
|
|
382
|
+
revocation_endpoint: "https://idp.example.com/revoke",
|
|
383
|
+
});
|
|
384
|
+
const doc = createMockDiscoveryDocument({
|
|
385
|
+
issuer: "https://idp.example.com",
|
|
386
|
+
authorization_endpoint: "/oauth2/authorize",
|
|
387
|
+
token_endpoint: "/oauth2/token",
|
|
388
|
+
jwks_uri: "/.well-known/jwks.json",
|
|
389
|
+
userinfo_endpoint: "/userinfo",
|
|
390
|
+
revocation_endpoint: "/revoke",
|
|
391
|
+
});
|
|
392
|
+
const result = normalizeDiscoveryUrls(
|
|
393
|
+
doc,
|
|
394
|
+
"https://idp.example.com",
|
|
395
|
+
isTrustedOrigin,
|
|
396
|
+
);
|
|
397
|
+
expect(result).toEqual(expected);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it("should reject on invalid discovery urls", () => {
|
|
401
|
+
const doc = createMockDiscoveryDocument({
|
|
402
|
+
authorization_endpoint: "/oauth2/authorize",
|
|
403
|
+
});
|
|
404
|
+
expect(() =>
|
|
405
|
+
normalizeDiscoveryUrls(doc, "not-url", isTrustedOrigin),
|
|
406
|
+
).toThrowError('The url "authorization_endpoint" must be valid');
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("should reject with discovery_untrusted_origin code on untrusted discovery urls", () => {
|
|
410
|
+
const doc = createMockDiscoveryDocument({
|
|
411
|
+
authorization_endpoint: "/oauth2/authorize",
|
|
412
|
+
token_endpoint: "/oauth2/token",
|
|
413
|
+
jwks_uri: "/.well-known/jwks.json",
|
|
414
|
+
userinfo_endpoint: "/userinfo",
|
|
415
|
+
revocation_endpoint: "/revoke",
|
|
416
|
+
end_session_endpoint: "/endsession",
|
|
417
|
+
introspection_endpoint: "/introspection",
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
expect(() =>
|
|
421
|
+
normalizeDiscoveryUrls(
|
|
422
|
+
doc,
|
|
423
|
+
"https://idp.example.com",
|
|
424
|
+
(url) => !url.endsWith("/oauth2/token"),
|
|
425
|
+
),
|
|
426
|
+
).toThrowError(
|
|
427
|
+
expect.objectContaining({
|
|
428
|
+
code: "discovery_untrusted_origin",
|
|
429
|
+
message:
|
|
430
|
+
'The token_endpoint "https://idp.example.com/oauth2/token" is not trusted by your trusted origins configuration.',
|
|
431
|
+
details: {
|
|
432
|
+
endpoint: "token_endpoint",
|
|
433
|
+
url: "https://idp.example.com/oauth2/token",
|
|
434
|
+
},
|
|
435
|
+
}),
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
expect(() =>
|
|
439
|
+
normalizeDiscoveryUrls(
|
|
440
|
+
doc,
|
|
441
|
+
"https://idp.example.com",
|
|
442
|
+
(url) => !url.endsWith("/oauth2/authorize"),
|
|
443
|
+
),
|
|
444
|
+
).toThrowError(
|
|
445
|
+
expect.objectContaining({
|
|
446
|
+
code: "discovery_untrusted_origin",
|
|
447
|
+
message:
|
|
448
|
+
'The authorization_endpoint "https://idp.example.com/oauth2/authorize" is not trusted by your trusted origins configuration.',
|
|
449
|
+
details: {
|
|
450
|
+
endpoint: "authorization_endpoint",
|
|
451
|
+
url: "https://idp.example.com/oauth2/authorize",
|
|
452
|
+
},
|
|
453
|
+
}),
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
expect(() =>
|
|
457
|
+
normalizeDiscoveryUrls(
|
|
458
|
+
doc,
|
|
459
|
+
"https://idp.example.com",
|
|
460
|
+
(url) => !url.endsWith("/.well-known/jwks.json"),
|
|
461
|
+
),
|
|
462
|
+
).toThrowError(
|
|
463
|
+
expect.objectContaining({
|
|
464
|
+
code: "discovery_untrusted_origin",
|
|
465
|
+
message:
|
|
466
|
+
'The jwks_uri "https://idp.example.com/.well-known/jwks.json" is not trusted by your trusted origins configuration.',
|
|
467
|
+
details: {
|
|
468
|
+
endpoint: "jwks_uri",
|
|
469
|
+
url: "https://idp.example.com/.well-known/jwks.json",
|
|
470
|
+
},
|
|
471
|
+
}),
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
expect(() =>
|
|
475
|
+
normalizeDiscoveryUrls(
|
|
476
|
+
doc,
|
|
477
|
+
"https://idp.example.com",
|
|
478
|
+
(url) => !url.endsWith("/userinfo"),
|
|
479
|
+
),
|
|
480
|
+
).toThrowError(
|
|
481
|
+
expect.objectContaining({
|
|
482
|
+
code: "discovery_untrusted_origin",
|
|
483
|
+
message:
|
|
484
|
+
'The userinfo_endpoint "https://idp.example.com/userinfo" is not trusted by your trusted origins configuration.',
|
|
485
|
+
details: {
|
|
486
|
+
endpoint: "userinfo_endpoint",
|
|
487
|
+
url: "https://idp.example.com/userinfo",
|
|
488
|
+
},
|
|
489
|
+
}),
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
expect(() =>
|
|
493
|
+
normalizeDiscoveryUrls(
|
|
494
|
+
doc,
|
|
495
|
+
"https://idp.example.com",
|
|
496
|
+
(url) => !url.endsWith("/revoke"),
|
|
497
|
+
),
|
|
498
|
+
).toThrowError(
|
|
499
|
+
expect.objectContaining({
|
|
500
|
+
code: "discovery_untrusted_origin",
|
|
501
|
+
message:
|
|
502
|
+
'The revocation_endpoint "https://idp.example.com/revoke" is not trusted by your trusted origins configuration.',
|
|
503
|
+
details: {
|
|
504
|
+
endpoint: "revocation_endpoint",
|
|
505
|
+
url: "https://idp.example.com/revoke",
|
|
506
|
+
},
|
|
507
|
+
}),
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
expect(() =>
|
|
511
|
+
normalizeDiscoveryUrls(
|
|
512
|
+
doc,
|
|
513
|
+
"https://idp.example.com",
|
|
514
|
+
(url) => !url.endsWith("/endsession"),
|
|
515
|
+
),
|
|
516
|
+
).toThrowError(
|
|
517
|
+
expect.objectContaining({
|
|
518
|
+
code: "discovery_untrusted_origin",
|
|
519
|
+
message:
|
|
520
|
+
'The end_session_endpoint "https://idp.example.com/endsession" is not trusted by your trusted origins configuration.',
|
|
521
|
+
details: {
|
|
522
|
+
endpoint: "end_session_endpoint",
|
|
523
|
+
url: "https://idp.example.com/endsession",
|
|
524
|
+
},
|
|
525
|
+
}),
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
expect(() =>
|
|
529
|
+
normalizeDiscoveryUrls(
|
|
530
|
+
doc,
|
|
531
|
+
"https://idp.example.com",
|
|
532
|
+
(url) => !url.endsWith("/introspection"),
|
|
533
|
+
),
|
|
534
|
+
).toThrowError(
|
|
535
|
+
expect.objectContaining({
|
|
536
|
+
code: "discovery_untrusted_origin",
|
|
537
|
+
message:
|
|
538
|
+
'The introspection_endpoint "https://idp.example.com/introspection" is not trusted by your trusted origins configuration.',
|
|
539
|
+
details: {
|
|
540
|
+
endpoint: "introspection_endpoint",
|
|
541
|
+
url: "https://idp.example.com/introspection",
|
|
542
|
+
},
|
|
543
|
+
}),
|
|
544
|
+
);
|
|
545
|
+
});
|
|
323
546
|
});
|
|
324
547
|
|
|
325
|
-
describe("normalizeUrl
|
|
326
|
-
it("should return endpoint unchanged
|
|
548
|
+
describe("normalizeUrl", () => {
|
|
549
|
+
it("should return endpoint unchanged if already absolute", () => {
|
|
327
550
|
const endpoint = "https://idp.example.com/oauth2/token";
|
|
328
|
-
expect(normalizeUrl(endpoint, "https://idp.example.com")).toBe(
|
|
551
|
+
expect(normalizeUrl("url", endpoint, "https://idp.example.com")).toBe(
|
|
552
|
+
endpoint,
|
|
553
|
+
);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it("should return endpoint as an absolute url", () => {
|
|
557
|
+
const endpoint = "/oauth2/token";
|
|
558
|
+
expect(normalizeUrl("url", endpoint, "https://idp.example.com")).toBe(
|
|
559
|
+
"https://idp.example.com/oauth2/token",
|
|
560
|
+
);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it.each([
|
|
564
|
+
[
|
|
565
|
+
"/oauth2/token",
|
|
566
|
+
"https://idp.example.com/base",
|
|
567
|
+
"endpoint with leading slash",
|
|
568
|
+
],
|
|
569
|
+
[
|
|
570
|
+
"oauth2/token",
|
|
571
|
+
"https://idp.example.com/base",
|
|
572
|
+
"endpoint without leading slash",
|
|
573
|
+
],
|
|
574
|
+
[
|
|
575
|
+
"/oauth2/token",
|
|
576
|
+
"https://idp.example.com/base/",
|
|
577
|
+
"issuer with trailing slash",
|
|
578
|
+
],
|
|
579
|
+
["//oauth2/token", "https://idp.example.com/base//", "multiple slashes"],
|
|
580
|
+
])("should resolve relative endpoint preserving issuer base path (%s, %s) - %s", (endpoint, issuer) => {
|
|
581
|
+
expect(normalizeUrl("url", endpoint, issuer)).toBe(
|
|
582
|
+
"https://idp.example.com/base/oauth2/token",
|
|
583
|
+
);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it("should reject invalid endpoint urls", () => {
|
|
587
|
+
const endpoint = "oauth2/token";
|
|
588
|
+
const issuer = "not-a-url";
|
|
589
|
+
expect(() => normalizeUrl("url", endpoint, issuer)).toThrowError(
|
|
590
|
+
'The url "url" must be valid',
|
|
591
|
+
);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
it("should reject urls with unsupported protocols", () => {
|
|
595
|
+
const endpoint = "not-a-url";
|
|
596
|
+
const issuer = "ftp://idp.example.com";
|
|
597
|
+
expect(() => normalizeUrl("url", endpoint, issuer)).toThrowError(
|
|
598
|
+
'The url "url" must use the http or https supported protocols',
|
|
599
|
+
);
|
|
329
600
|
});
|
|
330
601
|
});
|
|
331
602
|
|
|
@@ -521,6 +792,7 @@ describe("OIDC Discovery", () => {
|
|
|
521
792
|
describe("discoverOIDCConfig (integration)", () => {
|
|
522
793
|
const mockBetterFetch = betterFetch as ReturnType<typeof vi.fn>;
|
|
523
794
|
const issuer = "https://idp.example.com";
|
|
795
|
+
const isTrustedOrigin = vi.fn().mockReturnValue(true);
|
|
524
796
|
|
|
525
797
|
beforeEach(() => {
|
|
526
798
|
vi.clearAllMocks();
|
|
@@ -539,7 +811,7 @@ describe("OIDC Discovery", () => {
|
|
|
539
811
|
error: null,
|
|
540
812
|
});
|
|
541
813
|
|
|
542
|
-
const result = await discoverOIDCConfig({ issuer });
|
|
814
|
+
const result = await discoverOIDCConfig({ issuer, isTrustedOrigin });
|
|
543
815
|
|
|
544
816
|
expect(result.issuer).toBe(issuer);
|
|
545
817
|
expect(result.authorizationEndpoint).toBe(`${issuer}/oauth2/authorize`);
|
|
@@ -570,6 +842,7 @@ describe("OIDC Discovery", () => {
|
|
|
570
842
|
tokenEndpoint: "https://custom.example.com/token",
|
|
571
843
|
tokenEndpointAuthentication: "client_secret_post",
|
|
572
844
|
},
|
|
845
|
+
isTrustedOrigin,
|
|
573
846
|
});
|
|
574
847
|
|
|
575
848
|
expect(result.tokenEndpoint).toBe("https://custom.example.com/token");
|
|
@@ -589,6 +862,7 @@ describe("OIDC Discovery", () => {
|
|
|
589
862
|
const result = await discoverOIDCConfig({
|
|
590
863
|
issuer,
|
|
591
864
|
discoveryEndpoint: customEndpoint,
|
|
865
|
+
isTrustedOrigin,
|
|
592
866
|
});
|
|
593
867
|
|
|
594
868
|
expect(result.discoveryEndpoint).toBe(customEndpoint);
|
|
@@ -610,6 +884,7 @@ describe("OIDC Discovery", () => {
|
|
|
610
884
|
existingConfig: {
|
|
611
885
|
discoveryEndpoint: existingEndpoint,
|
|
612
886
|
},
|
|
887
|
+
isTrustedOrigin,
|
|
613
888
|
});
|
|
614
889
|
|
|
615
890
|
expect(result.discoveryEndpoint).toBe(existingEndpoint);
|
|
@@ -627,7 +902,9 @@ describe("OIDC Discovery", () => {
|
|
|
627
902
|
error: null,
|
|
628
903
|
});
|
|
629
904
|
|
|
630
|
-
await expect(
|
|
905
|
+
await expect(
|
|
906
|
+
discoverOIDCConfig({ issuer, isTrustedOrigin }),
|
|
907
|
+
).rejects.toThrow(
|
|
631
908
|
expect.objectContaining({
|
|
632
909
|
code: "issuer_mismatch",
|
|
633
910
|
}),
|
|
@@ -643,7 +920,9 @@ describe("OIDC Discovery", () => {
|
|
|
643
920
|
error: null,
|
|
644
921
|
});
|
|
645
922
|
|
|
646
|
-
await expect(
|
|
923
|
+
await expect(
|
|
924
|
+
discoverOIDCConfig({ issuer, isTrustedOrigin }),
|
|
925
|
+
).rejects.toThrow(
|
|
647
926
|
expect.objectContaining({
|
|
648
927
|
code: "discovery_incomplete",
|
|
649
928
|
}),
|
|
@@ -656,7 +935,9 @@ describe("OIDC Discovery", () => {
|
|
|
656
935
|
error: { status: 404, message: "Not Found" },
|
|
657
936
|
});
|
|
658
937
|
|
|
659
|
-
await expect(
|
|
938
|
+
await expect(
|
|
939
|
+
discoverOIDCConfig({ issuer, isTrustedOrigin }),
|
|
940
|
+
).rejects.toThrow(
|
|
660
941
|
expect.objectContaining({
|
|
661
942
|
code: "discovery_not_found",
|
|
662
943
|
}),
|
|
@@ -673,7 +954,7 @@ describe("OIDC Discovery", () => {
|
|
|
673
954
|
error: null,
|
|
674
955
|
});
|
|
675
956
|
|
|
676
|
-
const result = await discoverOIDCConfig({ issuer });
|
|
957
|
+
const result = await discoverOIDCConfig({ issuer, isTrustedOrigin });
|
|
677
958
|
|
|
678
959
|
expect(result.scopesSupported).toEqual(scopes);
|
|
679
960
|
});
|
|
@@ -689,7 +970,7 @@ describe("OIDC Discovery", () => {
|
|
|
689
970
|
error: null,
|
|
690
971
|
});
|
|
691
972
|
|
|
692
|
-
const result = await discoverOIDCConfig({ issuer });
|
|
973
|
+
const result = await discoverOIDCConfig({ issuer, isTrustedOrigin });
|
|
693
974
|
|
|
694
975
|
expect(result.issuer).toBe(issuer);
|
|
695
976
|
expect(result.authorizationEndpoint).toBe(`${issuer}/authorize`);
|
|
@@ -720,6 +1001,7 @@ describe("OIDC Discovery", () => {
|
|
|
720
1001
|
tokenEndpointAuthentication: "client_secret_post",
|
|
721
1002
|
scopesSupported: ["openid", "profile"],
|
|
722
1003
|
},
|
|
1004
|
+
isTrustedOrigin,
|
|
723
1005
|
});
|
|
724
1006
|
|
|
725
1007
|
expect(result.issuer).toBe(issuer);
|
|
@@ -750,7 +1032,7 @@ describe("OIDC Discovery", () => {
|
|
|
750
1032
|
error: null,
|
|
751
1033
|
});
|
|
752
1034
|
|
|
753
|
-
const result = await discoverOIDCConfig({ issuer });
|
|
1035
|
+
const result = await discoverOIDCConfig({ issuer, isTrustedOrigin });
|
|
754
1036
|
expect(result.tokenEndpointAuthentication).toBe("client_secret_basic");
|
|
755
1037
|
});
|
|
756
1038
|
|
|
@@ -774,6 +1056,7 @@ describe("OIDC Discovery", () => {
|
|
|
774
1056
|
// Only jwksEndpoint is set (simulating a legacy/partial config)
|
|
775
1057
|
jwksEndpoint: "https://custom.example.com/jwks",
|
|
776
1058
|
},
|
|
1059
|
+
isTrustedOrigin,
|
|
777
1060
|
});
|
|
778
1061
|
|
|
779
1062
|
// Existing value should be preserved
|
|
@@ -804,7 +1087,7 @@ describe("OIDC Discovery", () => {
|
|
|
804
1087
|
error: null,
|
|
805
1088
|
});
|
|
806
1089
|
|
|
807
|
-
const result = await discoverOIDCConfig({ issuer });
|
|
1090
|
+
const result = await discoverOIDCConfig({ issuer, isTrustedOrigin });
|
|
808
1091
|
|
|
809
1092
|
// Should successfully extract required fields
|
|
810
1093
|
expect(result.issuer).toBe(issuer);
|
|
@@ -819,5 +1102,56 @@ describe("OIDC Discovery", () => {
|
|
|
819
1102
|
// Should default auth method when not specified
|
|
820
1103
|
expect(result.tokenEndpointAuthentication).toBe("client_secret_basic");
|
|
821
1104
|
});
|
|
1105
|
+
|
|
1106
|
+
it("should throw an error with discovery_untrusted_origin code when the main discovery url is untrusted", async () => {
|
|
1107
|
+
isTrustedOrigin.mockReturnValue(false);
|
|
1108
|
+
|
|
1109
|
+
await expect(
|
|
1110
|
+
discoverOIDCConfig({ issuer, isTrustedOrigin }),
|
|
1111
|
+
).rejects.toThrow(
|
|
1112
|
+
expect.objectContaining({
|
|
1113
|
+
name: "DiscoveryError",
|
|
1114
|
+
message:
|
|
1115
|
+
'The main discovery endpoint "https://idp.example.com/.well-known/openid-configuration" is not trusted by your trusted origins configuration.',
|
|
1116
|
+
code: "discovery_untrusted_origin",
|
|
1117
|
+
details: {
|
|
1118
|
+
url: "https://idp.example.com/.well-known/openid-configuration",
|
|
1119
|
+
},
|
|
1120
|
+
}),
|
|
1121
|
+
);
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
it("should throw an error with discovery_untrusted_origin code when discovered urls are untrusted", async () => {
|
|
1125
|
+
isTrustedOrigin.mockImplementation((url: string) => {
|
|
1126
|
+
return url.endsWith(".well-known/openid-configuration");
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
const discoveryDoc = createMockDiscoveryDocument({
|
|
1130
|
+
issuer,
|
|
1131
|
+
authorization_endpoint: `${issuer}/oauth2/authorize`,
|
|
1132
|
+
token_endpoint: `${issuer}/oauth2/token`,
|
|
1133
|
+
jwks_uri: `${issuer}/.well-known/jwks.json`,
|
|
1134
|
+
userinfo_endpoint: `${issuer}/userinfo`,
|
|
1135
|
+
});
|
|
1136
|
+
mockBetterFetch.mockResolvedValueOnce({
|
|
1137
|
+
data: discoveryDoc,
|
|
1138
|
+
error: null,
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
await expect(
|
|
1142
|
+
discoverOIDCConfig({ issuer, isTrustedOrigin }),
|
|
1143
|
+
).rejects.toThrow(
|
|
1144
|
+
expect.objectContaining({
|
|
1145
|
+
name: "DiscoveryError",
|
|
1146
|
+
message:
|
|
1147
|
+
'The token_endpoint "https://idp.example.com/oauth2/token" is not trusted by your trusted origins configuration.',
|
|
1148
|
+
code: "discovery_untrusted_origin",
|
|
1149
|
+
details: {
|
|
1150
|
+
endpoint: "token_endpoint",
|
|
1151
|
+
url: "https://idp.example.com/oauth2/token",
|
|
1152
|
+
},
|
|
1153
|
+
}),
|
|
1154
|
+
);
|
|
1155
|
+
});
|
|
822
1156
|
});
|
|
823
1157
|
});
|