@better-auth/sso 1.3.15 → 1.3.17

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.
@@ -1,17 +1,17 @@
1
1
 
2
- > @better-auth/sso@1.3.15 build /home/runner/work/better-auth/better-auth/packages/sso
2
+ > @better-auth/sso@1.3.17 build /home/runner/work/better-auth/better-auth/packages/sso
3
3
  > unbuild
4
4
 
5
5
  [info] Automatically detected entries: src/index, src/client [esm] [cjs] [dts]
6
6
  [info] Building sso
7
7
  [success] Build succeeded for sso
8
- [log] dist/index.cjs (total size: 64.5 kB, chunk size: 64.5 kB, exports: sso)
8
+ [log] dist/index.cjs (total size: 65.4 kB, chunk size: 65.4 kB, exports: sso)
9
9
 
10
10
  [log] dist/client.cjs (total size: 141 B, chunk size: 141 B, exports: ssoClient)
11
11
 
12
- [log] dist/index.mjs (total size: 62.9 kB, chunk size: 62.9 kB, exports: sso)
12
+ [log] dist/index.mjs (total size: 63.7 kB, chunk size: 63.7 kB, exports: sso)
13
13
 
14
14
  [log] dist/client.mjs (total size: 117 B, chunk size: 117 B, exports: ssoClient)
15
15
 
16
- Σ Total dist size (byte size): 255 kB
16
+ Σ Total dist size (byte size): 256 kB
17
17
  [log]
package/dist/index.cjs CHANGED
@@ -76,8 +76,18 @@ const sso = (options) => {
76
76
  });
77
77
  }
78
78
  const parsedSamlConfig = JSON.parse(provider.samlConfig);
79
- const sp = saml__namespace.ServiceProvider({
79
+ const sp = parsedSamlConfig.spMetadata.metadata ? saml__namespace.ServiceProvider({
80
80
  metadata: parsedSamlConfig.spMetadata.metadata
81
+ }) : saml__namespace.SPMetadata({
82
+ entityID: parsedSamlConfig.spMetadata?.entityID || parsedSamlConfig.issuer,
83
+ assertionConsumerService: [
84
+ {
85
+ Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
86
+ Location: parsedSamlConfig.callbackUrl || `${ctx.context.baseURL}/sso/saml2/sp/acs/${provider.id}`
87
+ }
88
+ ],
89
+ wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
90
+ nameIDFormat: parsedSamlConfig.identifierFormat ? [parsedSamlConfig.identifierFormat] : void 0
81
91
  });
82
92
  return new Response(sp.getMetadata(), {
83
93
  headers: {
@@ -647,10 +657,10 @@ const sso = (options) => {
647
657
  allowCreate: true
648
658
  });
649
659
  const idp = saml__namespace.IdentityProvider({
650
- metadata: parsedSamlConfig.idpMetadata.metadata,
651
- entityID: parsedSamlConfig.idpMetadata.entityID,
652
- encryptCert: parsedSamlConfig.idpMetadata.cert,
653
- singleSignOnService: parsedSamlConfig.idpMetadata.singleSignOnService
660
+ metadata: parsedSamlConfig.idpMetadata?.metadata,
661
+ entityID: parsedSamlConfig.idpMetadata?.entityID,
662
+ encryptCert: parsedSamlConfig.idpMetadata?.cert,
663
+ singleSignOnService: parsedSamlConfig.idpMetadata?.singleSignOnService
654
664
  });
655
665
  const loginRequest = sp.createLoginRequest(
656
666
  idp,
@@ -1039,7 +1049,8 @@ const sso = (options) => {
1039
1049
  isAssertionEncrypted: spData?.isAssertionEncrypted || false,
1040
1050
  encPrivateKey: spData?.encPrivateKey,
1041
1051
  encPrivateKeyPass: spData?.encPrivateKeyPass,
1042
- wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false
1052
+ wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
1053
+ nameIDFormat: parsedSamlConfig.identifierFormat ? [parsedSamlConfig.identifierFormat] : void 0
1043
1054
  });
1044
1055
  let parsedResponse;
1045
1056
  try {
@@ -1273,13 +1284,14 @@ const sso = (options) => {
1273
1284
  assertionConsumerService: [
1274
1285
  {
1275
1286
  Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
1276
- Location: parsedSamlConfig.callbackUrl || `${ctx.context.baseURL}/sso/saml2/sp/acs`
1287
+ Location: parsedSamlConfig.callbackUrl || `${ctx.context.baseURL}/sso/saml2/sp/acs/${providerId}`
1277
1288
  }
1278
1289
  ],
1279
1290
  wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
1280
1291
  metadata: parsedSamlConfig.spMetadata?.metadata,
1281
1292
  privateKey: parsedSamlConfig.spMetadata?.privateKey || parsedSamlConfig.privateKey,
1282
- privateKeyPass: parsedSamlConfig.spMetadata?.privateKeyPass
1293
+ privateKeyPass: parsedSamlConfig.spMetadata?.privateKeyPass,
1294
+ nameIDFormat: parsedSamlConfig.identifierFormat ? [parsedSamlConfig.identifierFormat] : void 0
1283
1295
  });
1284
1296
  const idpData = parsedSamlConfig.idpMetadata;
1285
1297
  const idp = !idpData?.metadata ? saml__namespace.IdentityProvider({
package/dist/index.mjs CHANGED
@@ -59,8 +59,18 @@ const sso = (options) => {
59
59
  });
60
60
  }
61
61
  const parsedSamlConfig = JSON.parse(provider.samlConfig);
62
- const sp = saml.ServiceProvider({
62
+ const sp = parsedSamlConfig.spMetadata.metadata ? saml.ServiceProvider({
63
63
  metadata: parsedSamlConfig.spMetadata.metadata
64
+ }) : saml.SPMetadata({
65
+ entityID: parsedSamlConfig.spMetadata?.entityID || parsedSamlConfig.issuer,
66
+ assertionConsumerService: [
67
+ {
68
+ Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
69
+ Location: parsedSamlConfig.callbackUrl || `${ctx.context.baseURL}/sso/saml2/sp/acs/${provider.id}`
70
+ }
71
+ ],
72
+ wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
73
+ nameIDFormat: parsedSamlConfig.identifierFormat ? [parsedSamlConfig.identifierFormat] : void 0
64
74
  });
65
75
  return new Response(sp.getMetadata(), {
66
76
  headers: {
@@ -630,10 +640,10 @@ const sso = (options) => {
630
640
  allowCreate: true
631
641
  });
632
642
  const idp = saml.IdentityProvider({
633
- metadata: parsedSamlConfig.idpMetadata.metadata,
634
- entityID: parsedSamlConfig.idpMetadata.entityID,
635
- encryptCert: parsedSamlConfig.idpMetadata.cert,
636
- singleSignOnService: parsedSamlConfig.idpMetadata.singleSignOnService
643
+ metadata: parsedSamlConfig.idpMetadata?.metadata,
644
+ entityID: parsedSamlConfig.idpMetadata?.entityID,
645
+ encryptCert: parsedSamlConfig.idpMetadata?.cert,
646
+ singleSignOnService: parsedSamlConfig.idpMetadata?.singleSignOnService
637
647
  });
638
648
  const loginRequest = sp.createLoginRequest(
639
649
  idp,
@@ -1022,7 +1032,8 @@ const sso = (options) => {
1022
1032
  isAssertionEncrypted: spData?.isAssertionEncrypted || false,
1023
1033
  encPrivateKey: spData?.encPrivateKey,
1024
1034
  encPrivateKeyPass: spData?.encPrivateKeyPass,
1025
- wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false
1035
+ wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
1036
+ nameIDFormat: parsedSamlConfig.identifierFormat ? [parsedSamlConfig.identifierFormat] : void 0
1026
1037
  });
1027
1038
  let parsedResponse;
1028
1039
  try {
@@ -1256,13 +1267,14 @@ const sso = (options) => {
1256
1267
  assertionConsumerService: [
1257
1268
  {
1258
1269
  Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
1259
- Location: parsedSamlConfig.callbackUrl || `${ctx.context.baseURL}/sso/saml2/sp/acs`
1270
+ Location: parsedSamlConfig.callbackUrl || `${ctx.context.baseURL}/sso/saml2/sp/acs/${providerId}`
1260
1271
  }
1261
1272
  ],
1262
1273
  wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
1263
1274
  metadata: parsedSamlConfig.spMetadata?.metadata,
1264
1275
  privateKey: parsedSamlConfig.spMetadata?.privateKey || parsedSamlConfig.privateKey,
1265
- privateKeyPass: parsedSamlConfig.spMetadata?.privateKeyPass
1276
+ privateKeyPass: parsedSamlConfig.spMetadata?.privateKeyPass,
1277
+ nameIDFormat: parsedSamlConfig.identifierFormat ? [parsedSamlConfig.identifierFormat] : void 0
1266
1278
  });
1267
1279
  const idpData = parsedSamlConfig.idpMetadata;
1268
1280
  const idp = !idpData?.metadata ? saml.IdentityProvider({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@better-auth/sso",
3
3
  "author": "Bereket Engida",
4
- "version": "1.3.15",
4
+ "version": "1.3.17",
5
5
  "main": "dist/index.cjs",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -58,10 +58,10 @@
58
58
  "body-parser": "^2.2.0",
59
59
  "express": "^5.1.0",
60
60
  "unbuild": "3.6.1",
61
- "better-auth": "^1.3.15"
61
+ "better-auth": "^1.3.17"
62
62
  },
63
63
  "peerDependencies": {
64
- "better-auth": "1.3.15"
64
+ "better-auth": "1.3.17"
65
65
  },
66
66
  "scripts": {
67
67
  "test": "vitest",
package/src/index.ts CHANGED
@@ -252,6 +252,7 @@ export const sso = (options?: SSOOptions) => {
252
252
  },
253
253
  async (ctx) => {
254
254
  const provider = await ctx.context.adapter.findOne<{
255
+ id: string;
255
256
  samlConfig: string;
256
257
  }>({
257
258
  model: "ssoProvider",
@@ -268,10 +269,29 @@ export const sso = (options?: SSOOptions) => {
268
269
  });
269
270
  }
270
271
 
271
- const parsedSamlConfig = JSON.parse(provider.samlConfig);
272
- const sp = saml.ServiceProvider({
273
- metadata: parsedSamlConfig.spMetadata.metadata,
274
- });
272
+ const parsedSamlConfig: SAMLConfig = JSON.parse(provider.samlConfig);
273
+ const sp = parsedSamlConfig.spMetadata.metadata
274
+ ? saml.ServiceProvider({
275
+ metadata: parsedSamlConfig.spMetadata.metadata,
276
+ })
277
+ : saml.SPMetadata({
278
+ entityID:
279
+ parsedSamlConfig.spMetadata?.entityID ||
280
+ parsedSamlConfig.issuer,
281
+ assertionConsumerService: [
282
+ {
283
+ Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
284
+ Location:
285
+ parsedSamlConfig.callbackUrl ||
286
+ `${ctx.context.baseURL}/sso/saml2/sp/acs/${provider.id}`,
287
+ },
288
+ ],
289
+ wantMessageSigned:
290
+ parsedSamlConfig.wantAssertionsSigned || false,
291
+ nameIDFormat: parsedSamlConfig.identifierFormat
292
+ ? [parsedSamlConfig.identifierFormat]
293
+ : undefined,
294
+ });
275
295
  return new Response(sp.getMetadata(), {
276
296
  headers: {
277
297
  "Content-Type": "application/xml",
@@ -1004,7 +1024,7 @@ export const sso = (options?: SSOOptions) => {
1004
1024
  });
1005
1025
  }
1006
1026
  if (provider.samlConfig) {
1007
- const parsedSamlConfig =
1027
+ const parsedSamlConfig: SAMLConfig =
1008
1028
  typeof provider.samlConfig === "object"
1009
1029
  ? provider.samlConfig
1010
1030
  : JSON.parse(provider.samlConfig as unknown as string);
@@ -1014,11 +1034,11 @@ export const sso = (options?: SSOOptions) => {
1014
1034
  });
1015
1035
 
1016
1036
  const idp = saml.IdentityProvider({
1017
- metadata: parsedSamlConfig.idpMetadata.metadata,
1018
- entityID: parsedSamlConfig.idpMetadata.entityID,
1019
- encryptCert: parsedSamlConfig.idpMetadata.cert,
1037
+ metadata: parsedSamlConfig.idpMetadata?.metadata,
1038
+ entityID: parsedSamlConfig.idpMetadata?.entityID,
1039
+ encryptCert: parsedSamlConfig.idpMetadata?.cert,
1020
1040
  singleSignOnService:
1021
- parsedSamlConfig.idpMetadata.singleSignOnService,
1041
+ parsedSamlConfig.idpMetadata?.singleSignOnService,
1022
1042
  });
1023
1043
  const loginRequest = sp.createLoginRequest(
1024
1044
  idp,
@@ -1507,6 +1527,9 @@ export const sso = (options?: SSOOptions) => {
1507
1527
  encPrivateKey: spData?.encPrivateKey,
1508
1528
  encPrivateKeyPass: spData?.encPrivateKeyPass,
1509
1529
  wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
1530
+ nameIDFormat: parsedSamlConfig.identifierFormat
1531
+ ? [parsedSamlConfig.identifierFormat]
1532
+ : undefined,
1510
1533
  });
1511
1534
 
1512
1535
  let parsedResponse: FlowResult;
@@ -1794,7 +1817,7 @@ export const sso = (options?: SSOOptions) => {
1794
1817
  Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
1795
1818
  Location:
1796
1819
  parsedSamlConfig.callbackUrl ||
1797
- `${ctx.context.baseURL}/sso/saml2/sp/acs`,
1820
+ `${ctx.context.baseURL}/sso/saml2/sp/acs/${providerId}`,
1798
1821
  },
1799
1822
  ],
1800
1823
  wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
@@ -1803,6 +1826,9 @@ export const sso = (options?: SSOOptions) => {
1803
1826
  parsedSamlConfig.spMetadata?.privateKey ||
1804
1827
  parsedSamlConfig.privateKey,
1805
1828
  privateKeyPass: parsedSamlConfig.spMetadata?.privateKeyPass,
1829
+ nameIDFormat: parsedSamlConfig.identifierFormat
1830
+ ? [parsedSamlConfig.identifierFormat]
1831
+ : undefined,
1806
1832
  });
1807
1833
 
1808
1834
  // Update where we construct the IdP
package/src/saml.test.ts CHANGED
@@ -242,7 +242,7 @@ const certificate = `
242
242
  yyoWAJDUHiAmvFA=
243
243
  -----END CERTIFICATE-----
244
244
  `;
245
- const idpEncyptionKey = `
245
+ const idpEncryptionKey = `
246
246
  -----BEGIN RSA PRIVATE KEY-----
247
247
  Proc-Type: 4,ENCRYPTED
248
248
  DEK-Info: DES-EDE3-CBC,860FDB9F3BE14699
@@ -274,7 +274,7 @@ const idpEncyptionKey = `
274
274
  ISbutnQPUN5fsaIsgKDIV3T7n6519t6brobcW5bdigmf5ebFeZJ16/lYy6V77UM5
275
275
  -----END RSA PRIVATE KEY-----
276
276
  `;
277
- const spEncyptionKey = `
277
+ const spEncryptionKey = `
278
278
  -----BEGIN RSA PRIVATE KEY-----
279
279
  Proc-Type: 4,ENCRYPTED
280
280
  DEK-Info: DES-EDE3-CBC,860FDB9F3BE14699
@@ -698,7 +698,7 @@ describe("SAML SSO", async () => {
698
698
  privateKey: idpPrivateKey,
699
699
  privateKeyPass: "q9ALNhGT5EhfcRmp8Pg7e9zTQeP2x1bW",
700
700
  isAssertionEncrypted: true,
701
- encPrivateKey: idpEncyptionKey,
701
+ encPrivateKey: idpEncryptionKey,
702
702
  encPrivateKeyPass: "g7hGcRmp8PxT5QeP2q9Ehf1bWe9zTALN",
703
703
  },
704
704
  spMetadata: {
@@ -707,7 +707,7 @@ describe("SAML SSO", async () => {
707
707
  privateKey: spPrivateKey,
708
708
  privateKeyPass: "VHOSp5RUiBcrsjrcAuXFwU1NKCkGA8px",
709
709
  isAssertionEncrypted: true,
710
- encPrivateKey: spEncyptionKey,
710
+ encPrivateKey: spEncryptionKey,
711
711
  encPrivateKeyPass: "BXFNKpxrsjrCkGA8cAu5wUVHOSpci1RU",
712
712
  },
713
713
  identifierFormat:
@@ -754,7 +754,7 @@ describe("SAML SSO", async () => {
754
754
  privateKey: idpPrivateKey,
755
755
  privateKeyPass: "q9ALNhGT5EhfcRmp8Pg7e9zTQeP2x1bW",
756
756
  isAssertionEncrypted: true,
757
- encPrivateKey: idpEncyptionKey,
757
+ encPrivateKey: idpEncryptionKey,
758
758
  encPrivateKeyPass: "g7hGcRmp8PxT5QeP2q9Ehf1bWe9zTALN",
759
759
  },
760
760
  spMetadata: {
@@ -763,7 +763,7 @@ describe("SAML SSO", async () => {
763
763
  privateKey: spPrivateKey,
764
764
  privateKeyPass: "VHOSp5RUiBcrsjrcAuXFwU1NKCkGA8px",
765
765
  isAssertionEncrypted: true,
766
- encPrivateKey: spEncyptionKey,
766
+ encPrivateKey: spEncryptionKey,
767
767
  encPrivateKeyPass: "BXFNKpxrsjrCkGA8cAu5wUVHOSpci1RU",
768
768
  },
769
769
  identifierFormat:
@@ -782,6 +782,69 @@ describe("SAML SSO", async () => {
782
782
  expect(spMetadataRes.status).toBe(200);
783
783
  expect(spMetadataResResValue).toBe(spMetadata);
784
784
  });
785
+ it("Should fetch sp metadata", async () => {
786
+ const headers = await getAuthHeaders();
787
+ await authClient.signIn.email(testUser, {
788
+ throw: true,
789
+ onSuccess: setCookieToHeader(headers),
790
+ });
791
+ const issuer = "http://localhost:8081";
792
+ const provider = await auth.api.registerSSOProvider({
793
+ body: {
794
+ providerId: "saml-provider-1",
795
+ issuer: issuer,
796
+ domain: issuer,
797
+ samlConfig: {
798
+ entryPoint: mockIdP.metadataUrl,
799
+ cert: certificate,
800
+ callbackUrl: `${issuer}/api/sso/saml2/sp/acs`,
801
+ wantAssertionsSigned: false,
802
+ signatureAlgorithm: "sha256",
803
+ digestAlgorithm: "sha256",
804
+ idpMetadata: {
805
+ metadata: idpMetadata,
806
+ privateKey: idpPrivateKey,
807
+ privateKeyPass: "q9ALNhGT5EhfcRmp8Pg7e9zTQeP2x1bW",
808
+ isAssertionEncrypted: true,
809
+ encPrivateKey: idpEncryptionKey,
810
+ encPrivateKeyPass: "g7hGcRmp8PxT5QeP2q9Ehf1bWe9zTALN",
811
+ },
812
+ spMetadata: {
813
+ binding: "post",
814
+ privateKey: spPrivateKey,
815
+ privateKeyPass: "VHOSp5RUiBcrsjrcAuXFwU1NKCkGA8px",
816
+ isAssertionEncrypted: true,
817
+ encPrivateKey: spEncryptionKey,
818
+ encPrivateKeyPass: "BXFNKpxrsjrCkGA8cAu5wUVHOSpci1RU",
819
+ },
820
+ identifierFormat:
821
+ "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
822
+ },
823
+ },
824
+ headers,
825
+ });
826
+
827
+ const spMetadataRes = await auth.api.spMetadata({
828
+ query: {
829
+ providerId: provider.providerId,
830
+ },
831
+ });
832
+ const spMetadataResResValue = await spMetadataRes.text();
833
+ expect(spMetadataRes.status).toBe(200);
834
+ expect(spMetadataResResValue).toBeDefined();
835
+ expect(spMetadataResResValue).toContain(
836
+ "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
837
+ );
838
+ expect(spMetadataResResValue).toContain(
839
+ "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
840
+ );
841
+ expect(spMetadataResResValue).toContain(
842
+ `<EntityDescriptor entityID="${issuer}"`,
843
+ );
844
+ expect(spMetadataResResValue).toContain(
845
+ `Location="${issuer}/api/sso/saml2/sp/acs"`,
846
+ );
847
+ });
785
848
  it("should initiate SAML login and handle response", async () => {
786
849
  const headers = await getAuthHeaders();
787
850
  const res = await authClient.signIn.email(testUser, {
@@ -805,7 +868,7 @@ describe("SAML SSO", async () => {
805
868
  privateKey: idpPrivateKey,
806
869
  privateKeyPass: "q9ALNhGT5EhfcRmp8Pg7e9zTQeP2x1bW",
807
870
  isAssertionEncrypted: true,
808
- encPrivateKey: idpEncyptionKey,
871
+ encPrivateKey: idpEncryptionKey,
809
872
  encPrivateKeyPass: "g7hGcRmp8PxT5QeP2q9Ehf1bWe9zTALN",
810
873
  },
811
874
  spMetadata: {
@@ -814,7 +877,7 @@ describe("SAML SSO", async () => {
814
877
  privateKey: spPrivateKey,
815
878
  privateKeyPass: "VHOSp5RUiBcrsjrcAuXFwU1NKCkGA8px",
816
879
  isAssertionEncrypted: true,
817
- encPrivateKey: spEncyptionKey,
880
+ encPrivateKey: spEncryptionKey,
818
881
  encPrivateKeyPass: "BXFNKpxrsjrCkGA8cAu5wUVHOSpci1RU",
819
882
  },
820
883
  identifierFormat: