@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.
- package/.turbo/turbo-build.log +4 -4
- package/dist/index.cjs +137 -61
- package/dist/index.mjs +137 -61
- package/package.json +3 -3
- package/src/index.ts +171 -75
- package/src/saml.test.ts +71 -8
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
> @better-auth/sso@1.4.0-beta.
|
|
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:
|
|
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:
|
|
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):
|
|
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({}).
|
|
95
|
-
"The ID of the provider. This is used to identify the provider during login and callback"
|
|
96
|
-
),
|
|
97
|
-
issuer: z__namespace.string({}).
|
|
98
|
-
|
|
99
|
-
|
|
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({}).
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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({}).
|
|
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(), {}).
|
|
111
|
-
|
|
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({}).
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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({}).
|
|
123
|
-
|
|
124
|
-
|
|
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().
|
|
138
|
-
|
|
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().
|
|
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({}).
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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({}).
|
|
170
|
-
"If organization plugin is enabled, the organization id to link the provider to"
|
|
171
|
-
).optional(),
|
|
172
|
-
overrideUserInfo: z__namespace.boolean({}).
|
|
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({}).
|
|
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({}).
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
).
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
"
|
|
462
|
-
)
|
|
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
|
|
651
|
-
entityID: parsedSamlConfig.idpMetadata
|
|
652
|
-
encryptCert: parsedSamlConfig.idpMetadata
|
|
653
|
-
singleSignOnService: parsedSamlConfig.idpMetadata
|
|
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({}).
|
|
78
|
-
"The ID of the provider. This is used to identify the provider during login and callback"
|
|
79
|
-
),
|
|
80
|
-
issuer: z.string({}).
|
|
81
|
-
|
|
82
|
-
|
|
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({}).
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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({}).
|
|
113
|
+
jwksEndpoint: z.string({}).meta({
|
|
114
|
+
description: "The JWKS endpoint"
|
|
115
|
+
}).optional(),
|
|
92
116
|
discoveryEndpoint: z.string().optional(),
|
|
93
|
-
scopes: z.array(z.string(), {}).
|
|
94
|
-
|
|
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({}).
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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({}).
|
|
106
|
-
|
|
107
|
-
|
|
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().
|
|
121
|
-
|
|
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().
|
|
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({}).
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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({}).
|
|
153
|
-
"If organization plugin is enabled, the organization id to link the provider to"
|
|
154
|
-
).optional(),
|
|
155
|
-
overrideUserInfo: z.boolean({}).
|
|
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({}).
|
|
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({}).
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
).
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
"
|
|
445
|
-
)
|
|
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
|
|
634
|
-
entityID: parsedSamlConfig.idpMetadata
|
|
635
|
-
encryptCert: parsedSamlConfig.idpMetadata
|
|
636
|
-
singleSignOnService: parsedSamlConfig.idpMetadata
|
|
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.
|
|
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.
|
|
61
|
+
"better-auth": "^1.4.0-beta.5"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"better-auth": "1.4.0-beta.
|
|
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 =
|
|
273
|
-
|
|
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
|
-
|
|
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({}).
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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({}).
|
|
301
|
-
|
|
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
|
-
.
|
|
328
|
+
.meta({
|
|
329
|
+
description: "The authorization endpoint",
|
|
330
|
+
})
|
|
305
331
|
.optional(),
|
|
306
332
|
tokenEndpoint: z
|
|
307
333
|
.string({})
|
|
308
|
-
.
|
|
334
|
+
.meta({
|
|
335
|
+
description: "The token endpoint",
|
|
336
|
+
})
|
|
309
337
|
.optional(),
|
|
310
338
|
userInfoEndpoint: z
|
|
311
339
|
.string({})
|
|
312
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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({}).
|
|
334
|
-
|
|
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
|
-
.
|
|
381
|
+
.meta({
|
|
382
|
+
description:
|
|
383
|
+
"Field mapping for email verification (defaults to 'email_verified')",
|
|
384
|
+
})
|
|
338
385
|
.optional(),
|
|
339
|
-
name: z.string({}).
|
|
386
|
+
name: z.string({}).meta({
|
|
387
|
+
description:
|
|
388
|
+
"Field mapping for name (defaults to 'name')",
|
|
389
|
+
}),
|
|
340
390
|
image: z
|
|
341
391
|
.string({})
|
|
342
|
-
.
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
cert: z.string({}).
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
Location: z
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
.
|
|
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({}).
|
|
404
|
-
|
|
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
|
-
.
|
|
470
|
+
.meta({
|
|
471
|
+
description: "Field mapping for email verification",
|
|
472
|
+
})
|
|
408
473
|
.optional(),
|
|
409
|
-
name: z.string({}).
|
|
474
|
+
name: z.string({}).meta({
|
|
475
|
+
description:
|
|
476
|
+
"Field mapping for name (defaults to 'displayName')",
|
|
477
|
+
}),
|
|
410
478
|
firstName: z
|
|
411
479
|
.string({})
|
|
412
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
426
|
-
|
|
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
|
-
.
|
|
432
|
-
|
|
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
|
-
.
|
|
749
|
-
|
|
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
|
-
.
|
|
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
|
-
.
|
|
759
|
-
|
|
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
|
-
.
|
|
844
|
+
.meta({
|
|
845
|
+
description: "The domain of the provider.",
|
|
846
|
+
})
|
|
765
847
|
.optional(),
|
|
766
|
-
callbackURL: z
|
|
767
|
-
|
|
768
|
-
|
|
848
|
+
callbackURL: z.string({}).meta({
|
|
849
|
+
description: "The URL to redirect to after login",
|
|
850
|
+
}),
|
|
769
851
|
errorCallbackURL: z
|
|
770
852
|
.string({})
|
|
771
|
-
.
|
|
853
|
+
.meta({
|
|
854
|
+
description: "The URL to redirect to after login",
|
|
855
|
+
})
|
|
772
856
|
.optional(),
|
|
773
857
|
newUserCallbackURL: z
|
|
774
858
|
.string({})
|
|
775
|
-
.
|
|
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
|
-
.
|
|
866
|
+
.meta({
|
|
867
|
+
description: "Scopes to request from the provider.",
|
|
868
|
+
})
|
|
780
869
|
.optional(),
|
|
781
870
|
requestSignUp: z
|
|
782
871
|
.boolean({})
|
|
783
|
-
.
|
|
784
|
-
|
|
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
|
|
1018
|
-
entityID: parsedSamlConfig.idpMetadata
|
|
1019
|
-
encryptCert: parsedSamlConfig.idpMetadata
|
|
1107
|
+
metadata: parsedSamlConfig.idpMetadata?.metadata,
|
|
1108
|
+
entityID: parsedSamlConfig.idpMetadata?.entityID,
|
|
1109
|
+
encryptCert: parsedSamlConfig.idpMetadata?.cert,
|
|
1020
1110
|
singleSignOnService:
|
|
1021
|
-
parsedSamlConfig.idpMetadata
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
880
|
+
encPrivateKey: spEncryptionKey,
|
|
818
881
|
encPrivateKeyPass: "BXFNKpxrsjrCkGA8cAu5wUVHOSpci1RU",
|
|
819
882
|
},
|
|
820
883
|
identifierFormat:
|