@hammadj/better-auth-sso 1.5.0-beta.9

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.
Files changed (42) hide show
  1. package/.turbo/turbo-build.log +116 -0
  2. package/LICENSE.md +20 -0
  3. package/dist/client.d.mts +10 -0
  4. package/dist/client.mjs +15 -0
  5. package/dist/client.mjs.map +1 -0
  6. package/dist/index.d.mts +738 -0
  7. package/dist/index.mjs +2953 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +87 -0
  10. package/src/client.ts +29 -0
  11. package/src/constants.ts +58 -0
  12. package/src/domain-verification.test.ts +551 -0
  13. package/src/index.ts +265 -0
  14. package/src/linking/index.ts +2 -0
  15. package/src/linking/org-assignment.test.ts +325 -0
  16. package/src/linking/org-assignment.ts +176 -0
  17. package/src/linking/types.ts +10 -0
  18. package/src/oidc/discovery.test.ts +1157 -0
  19. package/src/oidc/discovery.ts +494 -0
  20. package/src/oidc/errors.ts +92 -0
  21. package/src/oidc/index.ts +31 -0
  22. package/src/oidc/types.ts +219 -0
  23. package/src/oidc.test.ts +688 -0
  24. package/src/providers.test.ts +1326 -0
  25. package/src/routes/domain-verification.ts +275 -0
  26. package/src/routes/providers.ts +565 -0
  27. package/src/routes/schemas.ts +96 -0
  28. package/src/routes/sso.ts +2750 -0
  29. package/src/saml/algorithms.test.ts +449 -0
  30. package/src/saml/algorithms.ts +338 -0
  31. package/src/saml/assertions.test.ts +239 -0
  32. package/src/saml/assertions.ts +62 -0
  33. package/src/saml/index.ts +13 -0
  34. package/src/saml/parser.ts +56 -0
  35. package/src/saml-state.ts +78 -0
  36. package/src/saml.test.ts +4319 -0
  37. package/src/types.ts +365 -0
  38. package/src/utils.test.ts +103 -0
  39. package/src/utils.ts +81 -0
  40. package/tsconfig.json +14 -0
  41. package/tsdown.config.ts +9 -0
  42. package/vitest.config.ts +3 -0
@@ -0,0 +1,449 @@
1
+ /* cspell:ignore xenc */
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import * as alg from "./algorithms";
4
+
5
+ const encryptedAssertionXml = `
6
+ <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
7
+ <saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
8
+ <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
9
+ <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
10
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
11
+ <xenc:EncryptedKey>
12
+ <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
13
+ </xenc:EncryptedKey>
14
+ </ds:KeyInfo>
15
+ </xenc:EncryptedData>
16
+ </saml:EncryptedAssertion>
17
+ </samlp:Response>
18
+ `;
19
+
20
+ const deprecatedEncryptionXml = `
21
+ <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
22
+ <saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
23
+ <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
24
+ <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
25
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
26
+ <xenc:EncryptedKey>
27
+ <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
28
+ </xenc:EncryptedKey>
29
+ </ds:KeyInfo>
30
+ </xenc:EncryptedData>
31
+ </saml:EncryptedAssertion>
32
+ </samlp:Response>
33
+ `;
34
+
35
+ const plainAssertionXml = `
36
+ <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
37
+ <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
38
+ <saml:Subject>test</saml:Subject>
39
+ </saml:Assertion>
40
+ </samlp:Response>
41
+ `;
42
+
43
+ describe("validateSAMLAlgorithms", () => {
44
+ afterEach(() => {
45
+ vi.restoreAllMocks();
46
+ });
47
+
48
+ describe("signature validation", () => {
49
+ it("should accept secure signature algorithms", () => {
50
+ expect(() =>
51
+ alg.validateSAMLAlgorithms({
52
+ sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
53
+ samlContent: plainAssertionXml,
54
+ }),
55
+ ).not.toThrow();
56
+ });
57
+
58
+ it("should warn by default for deprecated signature algorithms", () => {
59
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
60
+
61
+ expect(() =>
62
+ alg.validateSAMLAlgorithms({
63
+ sigAlg: alg.SignatureAlgorithm.RSA_SHA1,
64
+ samlContent: plainAssertionXml,
65
+ }),
66
+ ).not.toThrow();
67
+
68
+ expect(warnSpy).toHaveBeenCalledWith(
69
+ expect.stringContaining("SAML Security Warning"),
70
+ );
71
+ });
72
+
73
+ it("should reject deprecated signature with onDeprecated: reject", () => {
74
+ expect(() =>
75
+ alg.validateSAMLAlgorithms(
76
+ {
77
+ sigAlg: alg.SignatureAlgorithm.RSA_SHA1,
78
+ samlContent: plainAssertionXml,
79
+ },
80
+ { onDeprecated: "reject" },
81
+ ),
82
+ ).toThrow(/deprecated/i);
83
+ });
84
+
85
+ it("should silently allow deprecated with onDeprecated: allow", () => {
86
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
87
+
88
+ expect(() =>
89
+ alg.validateSAMLAlgorithms(
90
+ {
91
+ sigAlg: alg.SignatureAlgorithm.RSA_SHA1,
92
+ samlContent: plainAssertionXml,
93
+ },
94
+ { onDeprecated: "allow" },
95
+ ),
96
+ ).not.toThrow();
97
+
98
+ expect(warnSpy).not.toHaveBeenCalled();
99
+ });
100
+
101
+ it("should enforce custom signature allow-list", () => {
102
+ expect(() =>
103
+ alg.validateSAMLAlgorithms(
104
+ {
105
+ sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
106
+ samlContent: plainAssertionXml,
107
+ },
108
+ { allowedSignatureAlgorithms: [alg.SignatureAlgorithm.RSA_SHA512] },
109
+ ),
110
+ ).toThrow(/not in allow-list/i);
111
+ });
112
+
113
+ it("should pass null/undefined sigAlg without error", () => {
114
+ expect(() =>
115
+ alg.validateSAMLAlgorithms({
116
+ sigAlg: null,
117
+ samlContent: plainAssertionXml,
118
+ }),
119
+ ).not.toThrow();
120
+ });
121
+
122
+ it("should reject unknown signature algorithms", () => {
123
+ expect(() =>
124
+ alg.validateSAMLAlgorithms({
125
+ sigAlg: "http://example.com/unknown-algo",
126
+ samlContent: plainAssertionXml,
127
+ }),
128
+ ).toThrow(/not recognized/i);
129
+ });
130
+ });
131
+
132
+ describe("encryption validation", () => {
133
+ it("should accept secure encryption algorithms", () => {
134
+ expect(() =>
135
+ alg.validateSAMLAlgorithms({
136
+ sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
137
+ samlContent: encryptedAssertionXml,
138
+ }),
139
+ ).not.toThrow();
140
+ });
141
+
142
+ it("should warn by default for deprecated encryption algorithms", () => {
143
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
144
+
145
+ expect(() =>
146
+ alg.validateSAMLAlgorithms({
147
+ sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
148
+ samlContent: deprecatedEncryptionXml,
149
+ }),
150
+ ).not.toThrow();
151
+
152
+ expect(warnSpy).toHaveBeenCalled();
153
+ });
154
+
155
+ it("should reject deprecated encryption with onDeprecated: reject", () => {
156
+ expect(() =>
157
+ alg.validateSAMLAlgorithms(
158
+ {
159
+ sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
160
+ samlContent: deprecatedEncryptionXml,
161
+ },
162
+ { onDeprecated: "reject" },
163
+ ),
164
+ ).toThrow(/deprecated/i);
165
+ });
166
+
167
+ it("should skip encryption validation for plain assertions", () => {
168
+ expect(() =>
169
+ alg.validateSAMLAlgorithms({
170
+ sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
171
+ samlContent: plainAssertionXml,
172
+ }),
173
+ ).not.toThrow();
174
+ });
175
+
176
+ it("should handle malformed XML gracefully", () => {
177
+ expect(() =>
178
+ alg.validateSAMLAlgorithms({
179
+ sigAlg: alg.SignatureAlgorithm.RSA_SHA256,
180
+ samlContent: "not valid xml",
181
+ }),
182
+ ).not.toThrow();
183
+ });
184
+ });
185
+ });
186
+
187
+ describe("algorithm constants", () => {
188
+ it("should export signature algorithm constants", () => {
189
+ expect(alg.SignatureAlgorithm.RSA_SHA256).toBe(
190
+ "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
191
+ );
192
+ expect(alg.SignatureAlgorithm.RSA_SHA1).toBe(
193
+ "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
194
+ );
195
+ });
196
+
197
+ it("should export encryption algorithm constants", () => {
198
+ expect(alg.KeyEncryptionAlgorithm.RSA_OAEP).toBe(
199
+ "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
200
+ );
201
+ expect(alg.DataEncryptionAlgorithm.AES_256_GCM).toBe(
202
+ "http://www.w3.org/2009/xmlenc11#aes256-gcm",
203
+ );
204
+ });
205
+ });
206
+
207
+ describe("validateConfigAlgorithms", () => {
208
+ afterEach(() => {
209
+ vi.restoreAllMocks();
210
+ });
211
+
212
+ describe("signature algorithm validation", () => {
213
+ it("should accept secure signature algorithms", () => {
214
+ expect(() =>
215
+ alg.validateConfigAlgorithms({
216
+ signatureAlgorithm: alg.SignatureAlgorithm.RSA_SHA256,
217
+ }),
218
+ ).not.toThrow();
219
+ });
220
+
221
+ it("should warn by default for deprecated signature algorithms", () => {
222
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
223
+
224
+ expect(() =>
225
+ alg.validateConfigAlgorithms({
226
+ signatureAlgorithm: alg.SignatureAlgorithm.RSA_SHA1,
227
+ }),
228
+ ).not.toThrow();
229
+
230
+ expect(warnSpy).toHaveBeenCalledWith(
231
+ expect.stringContaining("SAML Security Warning"),
232
+ );
233
+ });
234
+
235
+ it("should reject deprecated signature with onDeprecated: reject", () => {
236
+ expect(() =>
237
+ alg.validateConfigAlgorithms(
238
+ { signatureAlgorithm: alg.SignatureAlgorithm.RSA_SHA1 },
239
+ { onDeprecated: "reject" },
240
+ ),
241
+ ).toThrow(/deprecated/i);
242
+ });
243
+
244
+ it("should silently allow deprecated with onDeprecated: allow", () => {
245
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
246
+
247
+ expect(() =>
248
+ alg.validateConfigAlgorithms(
249
+ { signatureAlgorithm: alg.SignatureAlgorithm.RSA_SHA1 },
250
+ { onDeprecated: "allow" },
251
+ ),
252
+ ).not.toThrow();
253
+
254
+ expect(warnSpy).not.toHaveBeenCalled();
255
+ });
256
+
257
+ it("should enforce custom signature allow-list", () => {
258
+ expect(() =>
259
+ alg.validateConfigAlgorithms(
260
+ { signatureAlgorithm: alg.SignatureAlgorithm.RSA_SHA256 },
261
+ { allowedSignatureAlgorithms: [alg.SignatureAlgorithm.RSA_SHA512] },
262
+ ),
263
+ ).toThrow(/not in allow-list/i);
264
+ });
265
+
266
+ it("should reject unknown signature algorithms", () => {
267
+ expect(() =>
268
+ alg.validateConfigAlgorithms({
269
+ signatureAlgorithm: "http://example.com/unknown-algo",
270
+ }),
271
+ ).toThrow(/not recognized/i);
272
+ });
273
+
274
+ it("should pass undefined signatureAlgorithm without error", () => {
275
+ expect(() => alg.validateConfigAlgorithms({})).not.toThrow();
276
+ });
277
+
278
+ it("should accept short-form signature algorithm names", () => {
279
+ expect(() =>
280
+ alg.validateConfigAlgorithms({
281
+ signatureAlgorithm: "rsa-sha256",
282
+ }),
283
+ ).not.toThrow();
284
+ });
285
+
286
+ it("should accept digest-style short-form for signature (backward compat)", () => {
287
+ expect(() =>
288
+ alg.validateConfigAlgorithms({
289
+ signatureAlgorithm: "sha256",
290
+ }),
291
+ ).not.toThrow();
292
+ });
293
+
294
+ it("should reject typos in short-form signature algorithm names", () => {
295
+ expect(() =>
296
+ alg.validateConfigAlgorithms({
297
+ signatureAlgorithm: "rsa-sha257",
298
+ }),
299
+ ).toThrow(/not recognized/i);
300
+ });
301
+
302
+ it("should warn for deprecated short-form signature algorithms", () => {
303
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
304
+
305
+ expect(() =>
306
+ alg.validateConfigAlgorithms({
307
+ signatureAlgorithm: "rsa-sha1",
308
+ }),
309
+ ).not.toThrow();
310
+
311
+ expect(warnSpy).toHaveBeenCalledWith(
312
+ expect.stringContaining("SAML Security Warning"),
313
+ );
314
+ });
315
+
316
+ it("should support short-form names in signature allow-list", () => {
317
+ expect(() =>
318
+ alg.validateConfigAlgorithms(
319
+ { signatureAlgorithm: "rsa-sha256" },
320
+ { allowedSignatureAlgorithms: ["rsa-sha256", "rsa-sha512"] },
321
+ ),
322
+ ).not.toThrow();
323
+ });
324
+ });
325
+
326
+ describe("digest algorithm validation", () => {
327
+ it("should accept secure digest algorithms", () => {
328
+ expect(() =>
329
+ alg.validateConfigAlgorithms({
330
+ digestAlgorithm: alg.DigestAlgorithm.SHA256,
331
+ }),
332
+ ).not.toThrow();
333
+ });
334
+
335
+ it("should warn by default for deprecated digest algorithms", () => {
336
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
337
+
338
+ expect(() =>
339
+ alg.validateConfigAlgorithms({
340
+ digestAlgorithm: alg.DigestAlgorithm.SHA1,
341
+ }),
342
+ ).not.toThrow();
343
+
344
+ expect(warnSpy).toHaveBeenCalledWith(
345
+ expect.stringContaining("SAML Security Warning"),
346
+ );
347
+ });
348
+
349
+ it("should reject deprecated digest with onDeprecated: reject", () => {
350
+ expect(() =>
351
+ alg.validateConfigAlgorithms(
352
+ { digestAlgorithm: alg.DigestAlgorithm.SHA1 },
353
+ { onDeprecated: "reject" },
354
+ ),
355
+ ).toThrow(/deprecated/i);
356
+ });
357
+
358
+ it("should enforce custom digest allow-list", () => {
359
+ expect(() =>
360
+ alg.validateConfigAlgorithms(
361
+ { digestAlgorithm: alg.DigestAlgorithm.SHA256 },
362
+ { allowedDigestAlgorithms: [alg.DigestAlgorithm.SHA512] },
363
+ ),
364
+ ).toThrow(/not in allow-list/i);
365
+ });
366
+
367
+ it("should reject unknown digest algorithms", () => {
368
+ expect(() =>
369
+ alg.validateConfigAlgorithms({
370
+ digestAlgorithm: "http://example.com/unknown-digest",
371
+ }),
372
+ ).toThrow(/not recognized/i);
373
+ });
374
+
375
+ it("should accept short-form digest algorithm names", () => {
376
+ expect(() =>
377
+ alg.validateConfigAlgorithms({
378
+ digestAlgorithm: "sha256",
379
+ }),
380
+ ).not.toThrow();
381
+ });
382
+
383
+ it("should reject typos in short-form digest algorithm names", () => {
384
+ expect(() =>
385
+ alg.validateConfigAlgorithms({
386
+ digestAlgorithm: "sha257",
387
+ }),
388
+ ).toThrow(/not recognized/i);
389
+ });
390
+
391
+ it("should warn for deprecated short-form digest algorithms", () => {
392
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
393
+
394
+ expect(() =>
395
+ alg.validateConfigAlgorithms({
396
+ digestAlgorithm: "sha1",
397
+ }),
398
+ ).not.toThrow();
399
+
400
+ expect(warnSpy).toHaveBeenCalledWith(
401
+ expect.stringContaining("SAML Security Warning"),
402
+ );
403
+ });
404
+
405
+ it("should support short-form names in digest allow-list", () => {
406
+ expect(() =>
407
+ alg.validateConfigAlgorithms(
408
+ { digestAlgorithm: "sha256" },
409
+ { allowedDigestAlgorithms: ["sha256", "sha512"] },
410
+ ),
411
+ ).not.toThrow();
412
+ });
413
+ });
414
+
415
+ describe("combined validation", () => {
416
+ it("should validate both signature and digest algorithms", () => {
417
+ expect(() =>
418
+ alg.validateConfigAlgorithms({
419
+ signatureAlgorithm: alg.SignatureAlgorithm.RSA_SHA256,
420
+ digestAlgorithm: alg.DigestAlgorithm.SHA256,
421
+ }),
422
+ ).not.toThrow();
423
+ });
424
+
425
+ it("should reject if signature is deprecated even if digest is secure", () => {
426
+ expect(() =>
427
+ alg.validateConfigAlgorithms(
428
+ {
429
+ signatureAlgorithm: alg.SignatureAlgorithm.RSA_SHA1,
430
+ digestAlgorithm: alg.DigestAlgorithm.SHA256,
431
+ },
432
+ { onDeprecated: "reject" },
433
+ ),
434
+ ).toThrow(/deprecated/i);
435
+ });
436
+
437
+ it("should reject if digest is deprecated even if signature is secure", () => {
438
+ expect(() =>
439
+ alg.validateConfigAlgorithms(
440
+ {
441
+ signatureAlgorithm: alg.SignatureAlgorithm.RSA_SHA256,
442
+ digestAlgorithm: alg.DigestAlgorithm.SHA1,
443
+ },
444
+ { onDeprecated: "reject" },
445
+ ),
446
+ ).toThrow(/deprecated/i);
447
+ });
448
+ });
449
+ });