@better-auth/sso 1.4.0-beta.3 → 1.4.0-beta.5

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.4.0-beta.3 build /home/runner/work/better-auth/better-auth/packages/sso
2
+ > @better-auth/sso@1.4.0-beta.5 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: 67.2 kB, chunk size: 67.2 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: 65.5 kB, chunk size: 65.5 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): 260 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: {
@@ -91,37 +101,71 @@ const sso = (options) => {
91
101
  {
92
102
  method: "POST",
93
103
  body: z__namespace.object({
94
- providerId: z__namespace.string({}).describe(
95
- "The ID of the provider. This is used to identify the provider during login and callback"
96
- ),
97
- issuer: z__namespace.string({}).describe("The issuer of the provider"),
98
- domain: z__namespace.string({}).describe(
99
- "The domain of the provider. This is used for email matching"
100
- ),
104
+ providerId: z__namespace.string({}).meta({
105
+ description: "The ID of the provider. This is used to identify the provider during login and callback"
106
+ }),
107
+ issuer: z__namespace.string({}).meta({
108
+ description: "The issuer of the provider"
109
+ }),
110
+ domain: z__namespace.string({}).meta({
111
+ description: "The domain of the provider. This is used for email matching"
112
+ }),
101
113
  oidcConfig: z__namespace.object({
102
- clientId: z__namespace.string({}).describe("The client ID"),
103
- clientSecret: z__namespace.string({}).describe("The client secret"),
104
- authorizationEndpoint: z__namespace.string({}).describe("The authorization endpoint").optional(),
105
- tokenEndpoint: z__namespace.string({}).describe("The token endpoint").optional(),
106
- userInfoEndpoint: z__namespace.string({}).describe("The user info endpoint").optional(),
114
+ clientId: z__namespace.string({}).meta({
115
+ description: "The client ID"
116
+ }),
117
+ clientSecret: z__namespace.string({}).meta({
118
+ description: "The client secret"
119
+ }),
120
+ authorizationEndpoint: z__namespace.string({}).meta({
121
+ description: "The authorization endpoint"
122
+ }).optional(),
123
+ tokenEndpoint: z__namespace.string({}).meta({
124
+ description: "The token endpoint"
125
+ }).optional(),
126
+ userInfoEndpoint: z__namespace.string({}).meta({
127
+ description: "The user info endpoint"
128
+ }).optional(),
107
129
  tokenEndpointAuthentication: z__namespace.enum(["client_secret_post", "client_secret_basic"]).optional(),
108
- jwksEndpoint: z__namespace.string({}).describe("The JWKS endpoint").optional(),
130
+ jwksEndpoint: z__namespace.string({}).meta({
131
+ description: "The JWKS endpoint"
132
+ }).optional(),
109
133
  discoveryEndpoint: z__namespace.string().optional(),
110
- scopes: z__namespace.array(z__namespace.string(), {}).describe("The scopes to request. ").optional(),
111
- pkce: z__namespace.boolean({}).describe("Whether to use PKCE for the authorization flow").default(true).optional(),
134
+ scopes: z__namespace.array(z__namespace.string(), {}).meta({
135
+ description: "The scopes to request. Defaults to ['openid', 'email', 'profile', 'offline_access']"
136
+ }).optional(),
137
+ pkce: z__namespace.boolean({}).meta({
138
+ description: "Whether to use PKCE for the authorization flow"
139
+ }).default(true).optional(),
112
140
  mapping: z__namespace.object({
113
- id: z__namespace.string({}).describe("Field mapping for user ID ("),
114
- email: z__namespace.string({}).describe("Field mapping for email ("),
115
- emailVerified: z__namespace.string({}).describe("Field mapping for email verification (").optional(),
116
- name: z__namespace.string({}).describe("Field mapping for name ("),
117
- image: z__namespace.string({}).describe("Field mapping for image (").optional(),
141
+ id: z__namespace.string({}).meta({
142
+ description: "Field mapping for user ID (defaults to 'sub')"
143
+ }),
144
+ email: z__namespace.string({}).meta({
145
+ description: "Field mapping for email (defaults to 'email')"
146
+ }),
147
+ emailVerified: z__namespace.string({}).meta({
148
+ description: "Field mapping for email verification (defaults to 'email_verified')"
149
+ }).optional(),
150
+ name: z__namespace.string({}).meta({
151
+ description: "Field mapping for name (defaults to 'name')"
152
+ }),
153
+ image: z__namespace.string({}).meta({
154
+ description: "Field mapping for image (defaults to 'picture')"
155
+ }).optional(),
118
156
  extraFields: z__namespace.record(z__namespace.string(), z__namespace.any()).optional()
119
157
  }).optional()
120
158
  }).optional(),
121
159
  samlConfig: z__namespace.object({
122
- entryPoint: z__namespace.string({}).describe("The entry point of the provider"),
123
- cert: z__namespace.string({}).describe("The certificate of the provider"),
124
- callbackUrl: z__namespace.string({}).describe("The callback URL of the provider"),
160
+ entryPoint: z__namespace.string({}).meta({
161
+ description: "The entry point of the provider"
162
+ }),
163
+ cert: z__namespace.string({}).meta({
164
+ description: "The certificate of the provider"
165
+ }),
166
+ callbackUrl: z__namespace.string({}).meta({
167
+ description: "The callback URL of the provider"
168
+ }),
125
169
  audience: z__namespace.string().optional(),
126
170
  idpMetadata: z__namespace.object({
127
171
  metadata: z__namespace.string().optional(),
@@ -134,10 +178,16 @@ const sso = (options) => {
134
178
  encPrivateKeyPass: z__namespace.string().optional(),
135
179
  singleSignOnService: z__namespace.array(
136
180
  z__namespace.object({
137
- Binding: z__namespace.string().describe("The binding type for the SSO service"),
138
- Location: z__namespace.string().describe("The URL for the SSO service")
181
+ Binding: z__namespace.string().meta({
182
+ description: "The binding type for the SSO service"
183
+ }),
184
+ Location: z__namespace.string().meta({
185
+ description: "The URL for the SSO service"
186
+ })
139
187
  })
140
- ).optional().describe("Single Sign-On service configuration")
188
+ ).optional().meta({
189
+ description: "Single Sign-On service configuration"
190
+ })
141
191
  }).optional(),
142
192
  spMetadata: z__namespace.object({
143
193
  metadata: z__namespace.string().optional(),
@@ -157,21 +207,33 @@ const sso = (options) => {
157
207
  decryptionPvk: z__namespace.string().optional(),
158
208
  additionalParams: z__namespace.record(z__namespace.string(), z__namespace.any()).optional(),
159
209
  mapping: z__namespace.object({
160
- id: z__namespace.string({}).describe("Field mapping for user ID ("),
161
- email: z__namespace.string({}).describe("Field mapping for email ("),
162
- emailVerified: z__namespace.string({}).describe("Field mapping for email verification").optional(),
163
- name: z__namespace.string({}).describe("Field mapping for name ("),
164
- firstName: z__namespace.string({}).describe("Field mapping for first name (").optional(),
165
- lastName: z__namespace.string({}).describe("Field mapping for last name (").optional(),
210
+ id: z__namespace.string({}).meta({
211
+ description: "Field mapping for user ID (defaults to 'nameID')"
212
+ }),
213
+ email: z__namespace.string({}).meta({
214
+ description: "Field mapping for email (defaults to 'email')"
215
+ }),
216
+ emailVerified: z__namespace.string({}).meta({
217
+ description: "Field mapping for email verification"
218
+ }).optional(),
219
+ name: z__namespace.string({}).meta({
220
+ description: "Field mapping for name (defaults to 'displayName')"
221
+ }),
222
+ firstName: z__namespace.string({}).meta({
223
+ description: "Field mapping for first name (defaults to 'givenName')"
224
+ }).optional(),
225
+ lastName: z__namespace.string({}).meta({
226
+ description: "Field mapping for last name (defaults to 'surname')"
227
+ }).optional(),
166
228
  extraFields: z__namespace.record(z__namespace.string(), z__namespace.any()).optional()
167
229
  }).optional()
168
230
  }).optional(),
169
- organizationId: z__namespace.string({}).describe(
170
- "If organization plugin is enabled, the organization id to link the provider to"
171
- ).optional(),
172
- overrideUserInfo: z__namespace.boolean({}).describe(
173
- "Override user info with the provider info. Defaults to false"
174
- ).default(false).optional()
231
+ organizationId: z__namespace.string({}).meta({
232
+ description: "If organization plugin is enabled, the organization id to link the provider to"
233
+ }).optional(),
234
+ overrideUserInfo: z__namespace.boolean({}).meta({
235
+ description: "Override user info with the provider info. Defaults to false"
236
+ }).default(false).optional()
175
237
  }),
176
238
  use: [api.sessionMiddleware],
177
239
  metadata: {
@@ -445,21 +507,33 @@ const sso = (options) => {
445
507
  {
446
508
  method: "POST",
447
509
  body: z__namespace.object({
448
- email: z__namespace.string({}).describe(
449
- "The email address to sign in with. This is used to identify the issuer to sign in with"
450
- ).optional(),
451
- organizationSlug: z__namespace.string({}).describe("The slug of the organization to sign in with").optional(),
452
- providerId: z__namespace.string({}).describe(
453
- "The ID of the provider to sign in with. This can be provided instead of email or issuer"
454
- ).optional(),
455
- domain: z__namespace.string({}).describe("The domain of the provider.").optional(),
456
- callbackURL: z__namespace.string({}).describe("The URL to redirect to after login"),
457
- errorCallbackURL: z__namespace.string({}).describe("The URL to redirect to after login").optional(),
458
- newUserCallbackURL: z__namespace.string({}).describe("The URL to redirect to after login if the user is new").optional(),
459
- scopes: z__namespace.array(z__namespace.string(), {}).describe("Scopes to request from the provider.").optional(),
460
- requestSignUp: z__namespace.boolean({}).describe(
461
- "Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider"
462
- ).optional(),
510
+ email: z__namespace.string({}).meta({
511
+ description: "The email address to sign in with. This is used to identify the issuer to sign in with. It's optional if the issuer is provided"
512
+ }).optional(),
513
+ organizationSlug: z__namespace.string({}).meta({
514
+ description: "The slug of the organization to sign in with"
515
+ }).optional(),
516
+ providerId: z__namespace.string({}).meta({
517
+ description: "The ID of the provider to sign in with. This can be provided instead of email or issuer"
518
+ }).optional(),
519
+ domain: z__namespace.string({}).meta({
520
+ description: "The domain of the provider."
521
+ }).optional(),
522
+ callbackURL: z__namespace.string({}).meta({
523
+ description: "The URL to redirect to after login"
524
+ }),
525
+ errorCallbackURL: z__namespace.string({}).meta({
526
+ description: "The URL to redirect to after login"
527
+ }).optional(),
528
+ newUserCallbackURL: z__namespace.string({}).meta({
529
+ description: "The URL to redirect to after login if the user is new"
530
+ }).optional(),
531
+ scopes: z__namespace.array(z__namespace.string(), {}).meta({
532
+ description: "Scopes to request from the provider."
533
+ }).optional(),
534
+ requestSignUp: z__namespace.boolean({}).meta({
535
+ description: "Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider"
536
+ }).optional(),
463
537
  providerType: z__namespace.enum(["oidc", "saml"]).optional()
464
538
  }),
465
539
  metadata: {
@@ -647,10 +721,10 @@ const sso = (options) => {
647
721
  allowCreate: true
648
722
  });
649
723
  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
724
+ metadata: parsedSamlConfig.idpMetadata?.metadata,
725
+ entityID: parsedSamlConfig.idpMetadata?.entityID,
726
+ encryptCert: parsedSamlConfig.idpMetadata?.cert,
727
+ singleSignOnService: parsedSamlConfig.idpMetadata?.singleSignOnService
654
728
  });
655
729
  const loginRequest = sp.createLoginRequest(
656
730
  idp,
@@ -1039,7 +1113,8 @@ const sso = (options) => {
1039
1113
  isAssertionEncrypted: spData?.isAssertionEncrypted || false,
1040
1114
  encPrivateKey: spData?.encPrivateKey,
1041
1115
  encPrivateKeyPass: spData?.encPrivateKeyPass,
1042
- wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false
1116
+ wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
1117
+ nameIDFormat: parsedSamlConfig.identifierFormat ? [parsedSamlConfig.identifierFormat] : void 0
1043
1118
  });
1044
1119
  let parsedResponse;
1045
1120
  try {
@@ -1273,13 +1348,14 @@ const sso = (options) => {
1273
1348
  assertionConsumerService: [
1274
1349
  {
1275
1350
  Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
1276
- Location: parsedSamlConfig.callbackUrl || `${ctx.context.baseURL}/sso/saml2/sp/acs`
1351
+ Location: parsedSamlConfig.callbackUrl || `${ctx.context.baseURL}/sso/saml2/sp/acs/${providerId}`
1277
1352
  }
1278
1353
  ],
1279
1354
  wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
1280
1355
  metadata: parsedSamlConfig.spMetadata?.metadata,
1281
1356
  privateKey: parsedSamlConfig.spMetadata?.privateKey || parsedSamlConfig.privateKey,
1282
- privateKeyPass: parsedSamlConfig.spMetadata?.privateKeyPass
1357
+ privateKeyPass: parsedSamlConfig.spMetadata?.privateKeyPass,
1358
+ nameIDFormat: parsedSamlConfig.identifierFormat ? [parsedSamlConfig.identifierFormat] : void 0
1283
1359
  });
1284
1360
  const idpData = parsedSamlConfig.idpMetadata;
1285
1361
  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: {
@@ -74,37 +84,71 @@ const sso = (options) => {
74
84
  {
75
85
  method: "POST",
76
86
  body: z.object({
77
- providerId: z.string({}).describe(
78
- "The ID of the provider. This is used to identify the provider during login and callback"
79
- ),
80
- issuer: z.string({}).describe("The issuer of the provider"),
81
- domain: z.string({}).describe(
82
- "The domain of the provider. This is used for email matching"
83
- ),
87
+ providerId: z.string({}).meta({
88
+ description: "The ID of the provider. This is used to identify the provider during login and callback"
89
+ }),
90
+ issuer: z.string({}).meta({
91
+ description: "The issuer of the provider"
92
+ }),
93
+ domain: z.string({}).meta({
94
+ description: "The domain of the provider. This is used for email matching"
95
+ }),
84
96
  oidcConfig: z.object({
85
- clientId: z.string({}).describe("The client ID"),
86
- clientSecret: z.string({}).describe("The client secret"),
87
- authorizationEndpoint: z.string({}).describe("The authorization endpoint").optional(),
88
- tokenEndpoint: z.string({}).describe("The token endpoint").optional(),
89
- userInfoEndpoint: z.string({}).describe("The user info endpoint").optional(),
97
+ clientId: z.string({}).meta({
98
+ description: "The client ID"
99
+ }),
100
+ clientSecret: z.string({}).meta({
101
+ description: "The client secret"
102
+ }),
103
+ authorizationEndpoint: z.string({}).meta({
104
+ description: "The authorization endpoint"
105
+ }).optional(),
106
+ tokenEndpoint: z.string({}).meta({
107
+ description: "The token endpoint"
108
+ }).optional(),
109
+ userInfoEndpoint: z.string({}).meta({
110
+ description: "The user info endpoint"
111
+ }).optional(),
90
112
  tokenEndpointAuthentication: z.enum(["client_secret_post", "client_secret_basic"]).optional(),
91
- jwksEndpoint: z.string({}).describe("The JWKS endpoint").optional(),
113
+ jwksEndpoint: z.string({}).meta({
114
+ description: "The JWKS endpoint"
115
+ }).optional(),
92
116
  discoveryEndpoint: z.string().optional(),
93
- scopes: z.array(z.string(), {}).describe("The scopes to request. ").optional(),
94
- pkce: z.boolean({}).describe("Whether to use PKCE for the authorization flow").default(true).optional(),
117
+ scopes: z.array(z.string(), {}).meta({
118
+ description: "The scopes to request. Defaults to ['openid', 'email', 'profile', 'offline_access']"
119
+ }).optional(),
120
+ pkce: z.boolean({}).meta({
121
+ description: "Whether to use PKCE for the authorization flow"
122
+ }).default(true).optional(),
95
123
  mapping: z.object({
96
- id: z.string({}).describe("Field mapping for user ID ("),
97
- email: z.string({}).describe("Field mapping for email ("),
98
- emailVerified: z.string({}).describe("Field mapping for email verification (").optional(),
99
- name: z.string({}).describe("Field mapping for name ("),
100
- image: z.string({}).describe("Field mapping for image (").optional(),
124
+ id: z.string({}).meta({
125
+ description: "Field mapping for user ID (defaults to 'sub')"
126
+ }),
127
+ email: z.string({}).meta({
128
+ description: "Field mapping for email (defaults to 'email')"
129
+ }),
130
+ emailVerified: z.string({}).meta({
131
+ description: "Field mapping for email verification (defaults to 'email_verified')"
132
+ }).optional(),
133
+ name: z.string({}).meta({
134
+ description: "Field mapping for name (defaults to 'name')"
135
+ }),
136
+ image: z.string({}).meta({
137
+ description: "Field mapping for image (defaults to 'picture')"
138
+ }).optional(),
101
139
  extraFields: z.record(z.string(), z.any()).optional()
102
140
  }).optional()
103
141
  }).optional(),
104
142
  samlConfig: z.object({
105
- entryPoint: z.string({}).describe("The entry point of the provider"),
106
- cert: z.string({}).describe("The certificate of the provider"),
107
- callbackUrl: z.string({}).describe("The callback URL of the provider"),
143
+ entryPoint: z.string({}).meta({
144
+ description: "The entry point of the provider"
145
+ }),
146
+ cert: z.string({}).meta({
147
+ description: "The certificate of the provider"
148
+ }),
149
+ callbackUrl: z.string({}).meta({
150
+ description: "The callback URL of the provider"
151
+ }),
108
152
  audience: z.string().optional(),
109
153
  idpMetadata: z.object({
110
154
  metadata: z.string().optional(),
@@ -117,10 +161,16 @@ const sso = (options) => {
117
161
  encPrivateKeyPass: z.string().optional(),
118
162
  singleSignOnService: z.array(
119
163
  z.object({
120
- Binding: z.string().describe("The binding type for the SSO service"),
121
- Location: z.string().describe("The URL for the SSO service")
164
+ Binding: z.string().meta({
165
+ description: "The binding type for the SSO service"
166
+ }),
167
+ Location: z.string().meta({
168
+ description: "The URL for the SSO service"
169
+ })
122
170
  })
123
- ).optional().describe("Single Sign-On service configuration")
171
+ ).optional().meta({
172
+ description: "Single Sign-On service configuration"
173
+ })
124
174
  }).optional(),
125
175
  spMetadata: z.object({
126
176
  metadata: z.string().optional(),
@@ -140,21 +190,33 @@ const sso = (options) => {
140
190
  decryptionPvk: z.string().optional(),
141
191
  additionalParams: z.record(z.string(), z.any()).optional(),
142
192
  mapping: z.object({
143
- id: z.string({}).describe("Field mapping for user ID ("),
144
- email: z.string({}).describe("Field mapping for email ("),
145
- emailVerified: z.string({}).describe("Field mapping for email verification").optional(),
146
- name: z.string({}).describe("Field mapping for name ("),
147
- firstName: z.string({}).describe("Field mapping for first name (").optional(),
148
- lastName: z.string({}).describe("Field mapping for last name (").optional(),
193
+ id: z.string({}).meta({
194
+ description: "Field mapping for user ID (defaults to 'nameID')"
195
+ }),
196
+ email: z.string({}).meta({
197
+ description: "Field mapping for email (defaults to 'email')"
198
+ }),
199
+ emailVerified: z.string({}).meta({
200
+ description: "Field mapping for email verification"
201
+ }).optional(),
202
+ name: z.string({}).meta({
203
+ description: "Field mapping for name (defaults to 'displayName')"
204
+ }),
205
+ firstName: z.string({}).meta({
206
+ description: "Field mapping for first name (defaults to 'givenName')"
207
+ }).optional(),
208
+ lastName: z.string({}).meta({
209
+ description: "Field mapping for last name (defaults to 'surname')"
210
+ }).optional(),
149
211
  extraFields: z.record(z.string(), z.any()).optional()
150
212
  }).optional()
151
213
  }).optional(),
152
- organizationId: z.string({}).describe(
153
- "If organization plugin is enabled, the organization id to link the provider to"
154
- ).optional(),
155
- overrideUserInfo: z.boolean({}).describe(
156
- "Override user info with the provider info. Defaults to false"
157
- ).default(false).optional()
214
+ organizationId: z.string({}).meta({
215
+ description: "If organization plugin is enabled, the organization id to link the provider to"
216
+ }).optional(),
217
+ overrideUserInfo: z.boolean({}).meta({
218
+ description: "Override user info with the provider info. Defaults to false"
219
+ }).default(false).optional()
158
220
  }),
159
221
  use: [sessionMiddleware],
160
222
  metadata: {
@@ -428,21 +490,33 @@ const sso = (options) => {
428
490
  {
429
491
  method: "POST",
430
492
  body: z.object({
431
- email: z.string({}).describe(
432
- "The email address to sign in with. This is used to identify the issuer to sign in with"
433
- ).optional(),
434
- organizationSlug: z.string({}).describe("The slug of the organization to sign in with").optional(),
435
- providerId: z.string({}).describe(
436
- "The ID of the provider to sign in with. This can be provided instead of email or issuer"
437
- ).optional(),
438
- domain: z.string({}).describe("The domain of the provider.").optional(),
439
- callbackURL: z.string({}).describe("The URL to redirect to after login"),
440
- errorCallbackURL: z.string({}).describe("The URL to redirect to after login").optional(),
441
- newUserCallbackURL: z.string({}).describe("The URL to redirect to after login if the user is new").optional(),
442
- scopes: z.array(z.string(), {}).describe("Scopes to request from the provider.").optional(),
443
- requestSignUp: z.boolean({}).describe(
444
- "Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider"
445
- ).optional(),
493
+ email: z.string({}).meta({
494
+ description: "The email address to sign in with. This is used to identify the issuer to sign in with. It's optional if the issuer is provided"
495
+ }).optional(),
496
+ organizationSlug: z.string({}).meta({
497
+ description: "The slug of the organization to sign in with"
498
+ }).optional(),
499
+ providerId: z.string({}).meta({
500
+ description: "The ID of the provider to sign in with. This can be provided instead of email or issuer"
501
+ }).optional(),
502
+ domain: z.string({}).meta({
503
+ description: "The domain of the provider."
504
+ }).optional(),
505
+ callbackURL: z.string({}).meta({
506
+ description: "The URL to redirect to after login"
507
+ }),
508
+ errorCallbackURL: z.string({}).meta({
509
+ description: "The URL to redirect to after login"
510
+ }).optional(),
511
+ newUserCallbackURL: z.string({}).meta({
512
+ description: "The URL to redirect to after login if the user is new"
513
+ }).optional(),
514
+ scopes: z.array(z.string(), {}).meta({
515
+ description: "Scopes to request from the provider."
516
+ }).optional(),
517
+ requestSignUp: z.boolean({}).meta({
518
+ description: "Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider"
519
+ }).optional(),
446
520
  providerType: z.enum(["oidc", "saml"]).optional()
447
521
  }),
448
522
  metadata: {
@@ -630,10 +704,10 @@ const sso = (options) => {
630
704
  allowCreate: true
631
705
  });
632
706
  const idp = saml.IdentityProvider({
633
- metadata: parsedSamlConfig.idpMetadata.metadata,
634
- entityID: parsedSamlConfig.idpMetadata.entityID,
635
- encryptCert: parsedSamlConfig.idpMetadata.cert,
636
- singleSignOnService: parsedSamlConfig.idpMetadata.singleSignOnService
707
+ metadata: parsedSamlConfig.idpMetadata?.metadata,
708
+ entityID: parsedSamlConfig.idpMetadata?.entityID,
709
+ encryptCert: parsedSamlConfig.idpMetadata?.cert,
710
+ singleSignOnService: parsedSamlConfig.idpMetadata?.singleSignOnService
637
711
  });
638
712
  const loginRequest = sp.createLoginRequest(
639
713
  idp,
@@ -1022,7 +1096,8 @@ const sso = (options) => {
1022
1096
  isAssertionEncrypted: spData?.isAssertionEncrypted || false,
1023
1097
  encPrivateKey: spData?.encPrivateKey,
1024
1098
  encPrivateKeyPass: spData?.encPrivateKeyPass,
1025
- wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false
1099
+ wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
1100
+ nameIDFormat: parsedSamlConfig.identifierFormat ? [parsedSamlConfig.identifierFormat] : void 0
1026
1101
  });
1027
1102
  let parsedResponse;
1028
1103
  try {
@@ -1256,13 +1331,14 @@ const sso = (options) => {
1256
1331
  assertionConsumerService: [
1257
1332
  {
1258
1333
  Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
1259
- Location: parsedSamlConfig.callbackUrl || `${ctx.context.baseURL}/sso/saml2/sp/acs`
1334
+ Location: parsedSamlConfig.callbackUrl || `${ctx.context.baseURL}/sso/saml2/sp/acs/${providerId}`
1260
1335
  }
1261
1336
  ],
1262
1337
  wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
1263
1338
  metadata: parsedSamlConfig.spMetadata?.metadata,
1264
1339
  privateKey: parsedSamlConfig.spMetadata?.privateKey || parsedSamlConfig.privateKey,
1265
- privateKeyPass: parsedSamlConfig.spMetadata?.privateKeyPass
1340
+ privateKeyPass: parsedSamlConfig.spMetadata?.privateKeyPass,
1341
+ nameIDFormat: parsedSamlConfig.identifierFormat ? [parsedSamlConfig.identifierFormat] : void 0
1266
1342
  });
1267
1343
  const idpData = parsedSamlConfig.idpMetadata;
1268
1344
  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.4.0-beta.3",
4
+ "version": "1.4.0-beta.5",
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.4.0-beta.3"
61
+ "better-auth": "^1.4.0-beta.5"
62
62
  },
63
63
  "peerDependencies": {
64
- "better-auth": "1.4.0-beta.3"
64
+ "better-auth": "1.4.0-beta.5"
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",
@@ -284,62 +304,95 @@ export const sso = (options?: SSOOptions) => {
284
304
  {
285
305
  method: "POST",
286
306
  body: z.object({
287
- providerId: z
288
- .string({})
289
- .describe(
307
+ providerId: z.string({}).meta({
308
+ description:
290
309
  "The ID of the provider. This is used to identify the provider during login and callback",
291
- ),
292
- issuer: z.string({}).describe("The issuer of the provider"),
293
- domain: z
294
- .string({})
295
- .describe(
310
+ }),
311
+ issuer: z.string({}).meta({
312
+ description: "The issuer of the provider",
313
+ }),
314
+ domain: z.string({}).meta({
315
+ description:
296
316
  "The domain of the provider. This is used for email matching",
297
- ),
317
+ }),
298
318
  oidcConfig: z
299
319
  .object({
300
- clientId: z.string({}).describe("The client ID"),
301
- clientSecret: z.string({}).describe("The client secret"),
320
+ clientId: z.string({}).meta({
321
+ description: "The client ID",
322
+ }),
323
+ clientSecret: z.string({}).meta({
324
+ description: "The client secret",
325
+ }),
302
326
  authorizationEndpoint: z
303
327
  .string({})
304
- .describe("The authorization endpoint")
328
+ .meta({
329
+ description: "The authorization endpoint",
330
+ })
305
331
  .optional(),
306
332
  tokenEndpoint: z
307
333
  .string({})
308
- .describe("The token endpoint")
334
+ .meta({
335
+ description: "The token endpoint",
336
+ })
309
337
  .optional(),
310
338
  userInfoEndpoint: z
311
339
  .string({})
312
- .describe("The user info endpoint")
340
+ .meta({
341
+ description: "The user info endpoint",
342
+ })
313
343
  .optional(),
314
344
  tokenEndpointAuthentication: z
315
345
  .enum(["client_secret_post", "client_secret_basic"])
316
346
  .optional(),
317
347
  jwksEndpoint: z
318
348
  .string({})
319
- .describe("The JWKS endpoint")
349
+ .meta({
350
+ description: "The JWKS endpoint",
351
+ })
320
352
  .optional(),
321
353
  discoveryEndpoint: z.string().optional(),
322
354
  scopes: z
323
355
  .array(z.string(), {})
324
- .describe("The scopes to request. ")
356
+ .meta({
357
+ description:
358
+ "The scopes to request. Defaults to ['openid', 'email', 'profile', 'offline_access']",
359
+ })
325
360
  .optional(),
326
361
  pkce: z
327
362
  .boolean({})
328
- .describe("Whether to use PKCE for the authorization flow")
363
+ .meta({
364
+ description:
365
+ "Whether to use PKCE for the authorization flow",
366
+ })
329
367
  .default(true)
330
368
  .optional(),
331
369
  mapping: z
332
370
  .object({
333
- id: z.string({}).describe("Field mapping for user ID ("),
334
- email: z.string({}).describe("Field mapping for email ("),
371
+ id: z.string({}).meta({
372
+ description:
373
+ "Field mapping for user ID (defaults to 'sub')",
374
+ }),
375
+ email: z.string({}).meta({
376
+ description:
377
+ "Field mapping for email (defaults to 'email')",
378
+ }),
335
379
  emailVerified: z
336
380
  .string({})
337
- .describe("Field mapping for email verification (")
381
+ .meta({
382
+ description:
383
+ "Field mapping for email verification (defaults to 'email_verified')",
384
+ })
338
385
  .optional(),
339
- name: z.string({}).describe("Field mapping for name ("),
386
+ name: z.string({}).meta({
387
+ description:
388
+ "Field mapping for name (defaults to 'name')",
389
+ }),
340
390
  image: z
341
391
  .string({})
342
- .describe("Field mapping for image (")
392
+ .meta({
393
+ description:
394
+ "Field mapping for image (defaults to 'picture')",
395
+ })
343
396
  .optional(),
344
397
  extraFields: z.record(z.string(), z.any()).optional(),
345
398
  })
@@ -348,13 +401,15 @@ export const sso = (options?: SSOOptions) => {
348
401
  .optional(),
349
402
  samlConfig: z
350
403
  .object({
351
- entryPoint: z
352
- .string({})
353
- .describe("The entry point of the provider"),
354
- cert: z.string({}).describe("The certificate of the provider"),
355
- callbackUrl: z
356
- .string({})
357
- .describe("The callback URL of the provider"),
404
+ entryPoint: z.string({}).meta({
405
+ description: "The entry point of the provider",
406
+ }),
407
+ cert: z.string({}).meta({
408
+ description: "The certificate of the provider",
409
+ }),
410
+ callbackUrl: z.string({}).meta({
411
+ description: "The callback URL of the provider",
412
+ }),
358
413
  audience: z.string().optional(),
359
414
  idpMetadata: z
360
415
  .object({
@@ -369,16 +424,18 @@ export const sso = (options?: SSOOptions) => {
369
424
  singleSignOnService: z
370
425
  .array(
371
426
  z.object({
372
- Binding: z
373
- .string()
374
- .describe("The binding type for the SSO service"),
375
- Location: z
376
- .string()
377
- .describe("The URL for the SSO service"),
427
+ Binding: z.string().meta({
428
+ description: "The binding type for the SSO service",
429
+ }),
430
+ Location: z.string().meta({
431
+ description: "The URL for the SSO service",
432
+ }),
378
433
  }),
379
434
  )
380
435
  .optional()
381
- .describe("Single Sign-On service configuration"),
436
+ .meta({
437
+ description: "Single Sign-On service configuration",
438
+ }),
382
439
  })
383
440
  .optional(),
384
441
  spMetadata: z.object({
@@ -400,20 +457,37 @@ export const sso = (options?: SSOOptions) => {
400
457
  additionalParams: z.record(z.string(), z.any()).optional(),
401
458
  mapping: z
402
459
  .object({
403
- id: z.string({}).describe("Field mapping for user ID ("),
404
- email: z.string({}).describe("Field mapping for email ("),
460
+ id: z.string({}).meta({
461
+ description:
462
+ "Field mapping for user ID (defaults to 'nameID')",
463
+ }),
464
+ email: z.string({}).meta({
465
+ description:
466
+ "Field mapping for email (defaults to 'email')",
467
+ }),
405
468
  emailVerified: z
406
469
  .string({})
407
- .describe("Field mapping for email verification")
470
+ .meta({
471
+ description: "Field mapping for email verification",
472
+ })
408
473
  .optional(),
409
- name: z.string({}).describe("Field mapping for name ("),
474
+ name: z.string({}).meta({
475
+ description:
476
+ "Field mapping for name (defaults to 'displayName')",
477
+ }),
410
478
  firstName: z
411
479
  .string({})
412
- .describe("Field mapping for first name (")
480
+ .meta({
481
+ description:
482
+ "Field mapping for first name (defaults to 'givenName')",
483
+ })
413
484
  .optional(),
414
485
  lastName: z
415
486
  .string({})
416
- .describe("Field mapping for last name (")
487
+ .meta({
488
+ description:
489
+ "Field mapping for last name (defaults to 'surname')",
490
+ })
417
491
  .optional(),
418
492
  extraFields: z.record(z.string(), z.any()).optional(),
419
493
  })
@@ -422,15 +496,17 @@ export const sso = (options?: SSOOptions) => {
422
496
  .optional(),
423
497
  organizationId: z
424
498
  .string({})
425
- .describe(
426
- "If organization plugin is enabled, the organization id to link the provider to",
427
- )
499
+ .meta({
500
+ description:
501
+ "If organization plugin is enabled, the organization id to link the provider to",
502
+ })
428
503
  .optional(),
429
504
  overrideUserInfo: z
430
505
  .boolean({})
431
- .describe(
432
- "Override user info with the provider info. Defaults to false",
433
- )
506
+ .meta({
507
+ description:
508
+ "Override user info with the provider info. Defaults to false",
509
+ })
434
510
  .default(false)
435
511
  .optional(),
436
512
  }),
@@ -745,44 +821,58 @@ export const sso = (options?: SSOOptions) => {
745
821
  body: z.object({
746
822
  email: z
747
823
  .string({})
748
- .describe(
749
- "The email address to sign in with. This is used to identify the issuer to sign in with",
750
- )
824
+ .meta({
825
+ description:
826
+ "The email address to sign in with. This is used to identify the issuer to sign in with. It's optional if the issuer is provided",
827
+ })
751
828
  .optional(),
752
829
  organizationSlug: z
753
830
  .string({})
754
- .describe("The slug of the organization to sign in with")
831
+ .meta({
832
+ description: "The slug of the organization to sign in with",
833
+ })
755
834
  .optional(),
756
835
  providerId: z
757
836
  .string({})
758
- .describe(
759
- "The ID of the provider to sign in with. This can be provided instead of email or issuer",
760
- )
837
+ .meta({
838
+ description:
839
+ "The ID of the provider to sign in with. This can be provided instead of email or issuer",
840
+ })
761
841
  .optional(),
762
842
  domain: z
763
843
  .string({})
764
- .describe("The domain of the provider.")
844
+ .meta({
845
+ description: "The domain of the provider.",
846
+ })
765
847
  .optional(),
766
- callbackURL: z
767
- .string({})
768
- .describe("The URL to redirect to after login"),
848
+ callbackURL: z.string({}).meta({
849
+ description: "The URL to redirect to after login",
850
+ }),
769
851
  errorCallbackURL: z
770
852
  .string({})
771
- .describe("The URL to redirect to after login")
853
+ .meta({
854
+ description: "The URL to redirect to after login",
855
+ })
772
856
  .optional(),
773
857
  newUserCallbackURL: z
774
858
  .string({})
775
- .describe("The URL to redirect to after login if the user is new")
859
+ .meta({
860
+ description:
861
+ "The URL to redirect to after login if the user is new",
862
+ })
776
863
  .optional(),
777
864
  scopes: z
778
865
  .array(z.string(), {})
779
- .describe("Scopes to request from the provider.")
866
+ .meta({
867
+ description: "Scopes to request from the provider.",
868
+ })
780
869
  .optional(),
781
870
  requestSignUp: z
782
871
  .boolean({})
783
- .describe(
784
- "Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider",
785
- )
872
+ .meta({
873
+ description:
874
+ "Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider",
875
+ })
786
876
  .optional(),
787
877
  providerType: z.enum(["oidc", "saml"]).optional(),
788
878
  }),
@@ -1004,7 +1094,7 @@ export const sso = (options?: SSOOptions) => {
1004
1094
  });
1005
1095
  }
1006
1096
  if (provider.samlConfig) {
1007
- const parsedSamlConfig =
1097
+ const parsedSamlConfig: SAMLConfig =
1008
1098
  typeof provider.samlConfig === "object"
1009
1099
  ? provider.samlConfig
1010
1100
  : JSON.parse(provider.samlConfig as unknown as string);
@@ -1014,11 +1104,11 @@ export const sso = (options?: SSOOptions) => {
1014
1104
  });
1015
1105
 
1016
1106
  const idp = saml.IdentityProvider({
1017
- metadata: parsedSamlConfig.idpMetadata.metadata,
1018
- entityID: parsedSamlConfig.idpMetadata.entityID,
1019
- encryptCert: parsedSamlConfig.idpMetadata.cert,
1107
+ metadata: parsedSamlConfig.idpMetadata?.metadata,
1108
+ entityID: parsedSamlConfig.idpMetadata?.entityID,
1109
+ encryptCert: parsedSamlConfig.idpMetadata?.cert,
1020
1110
  singleSignOnService:
1021
- parsedSamlConfig.idpMetadata.singleSignOnService,
1111
+ parsedSamlConfig.idpMetadata?.singleSignOnService,
1022
1112
  });
1023
1113
  const loginRequest = sp.createLoginRequest(
1024
1114
  idp,
@@ -1507,6 +1597,9 @@ export const sso = (options?: SSOOptions) => {
1507
1597
  encPrivateKey: spData?.encPrivateKey,
1508
1598
  encPrivateKeyPass: spData?.encPrivateKeyPass,
1509
1599
  wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
1600
+ nameIDFormat: parsedSamlConfig.identifierFormat
1601
+ ? [parsedSamlConfig.identifierFormat]
1602
+ : undefined,
1510
1603
  });
1511
1604
 
1512
1605
  let parsedResponse: FlowResult;
@@ -1794,7 +1887,7 @@ export const sso = (options?: SSOOptions) => {
1794
1887
  Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
1795
1888
  Location:
1796
1889
  parsedSamlConfig.callbackUrl ||
1797
- `${ctx.context.baseURL}/sso/saml2/sp/acs`,
1890
+ `${ctx.context.baseURL}/sso/saml2/sp/acs/${providerId}`,
1798
1891
  },
1799
1892
  ],
1800
1893
  wantMessageSigned: parsedSamlConfig.wantAssertionsSigned || false,
@@ -1803,6 +1896,9 @@ export const sso = (options?: SSOOptions) => {
1803
1896
  parsedSamlConfig.spMetadata?.privateKey ||
1804
1897
  parsedSamlConfig.privateKey,
1805
1898
  privateKeyPass: parsedSamlConfig.spMetadata?.privateKeyPass,
1899
+ nameIDFormat: parsedSamlConfig.identifierFormat
1900
+ ? [parsedSamlConfig.identifierFormat]
1901
+ : undefined,
1806
1902
  });
1807
1903
 
1808
1904
  // 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: