@better-auth/sso 1.3.0-beta.1 → 1.3.0-beta.11
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/client.d.cts +1 -1
- package/dist/client.d.mts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/index.cjs +111 -84
- package/dist/index.d.cts +57 -319
- package/dist/index.d.mts +57 -319
- package/dist/index.d.ts +57 -319
- package/dist/index.mjs +68 -42
- package/package.json +3 -3
- package/src/index.ts +115 -42
- package/src/oidc.test.ts +101 -0
- package/src/saml.test.ts +292 -107
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
> @better-auth/sso@1.3.0-beta.
|
|
2
|
+
> @better-auth/sso@1.3.0-beta.11 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: 45.3 kB, chunk size: 45.3 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: 43.9 kB, chunk size: 43.9 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): 199 kB
|
|
17
17
|
[log]
|
package/dist/client.d.cts
CHANGED
package/dist/client.d.mts
CHANGED
package/dist/client.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@ const betterAuth = require('better-auth');
|
|
|
4
4
|
const api = require('better-auth/api');
|
|
5
5
|
const oauth2 = require('better-auth/oauth2');
|
|
6
6
|
const plugins = require('better-auth/plugins');
|
|
7
|
-
const
|
|
7
|
+
const z = require('zod/v4');
|
|
8
8
|
const saml = require('samlify');
|
|
9
9
|
const fetch = require('@better-fetch/fetch');
|
|
10
10
|
const jose = require('jose');
|
|
@@ -23,6 +23,7 @@ function _interopNamespaceCompat(e) {
|
|
|
23
23
|
return n;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
const z__namespace = /*#__PURE__*/_interopNamespaceCompat(z);
|
|
26
27
|
const saml__namespace = /*#__PURE__*/_interopNamespaceCompat(saml);
|
|
27
28
|
|
|
28
29
|
const fastValidator = {
|
|
@@ -43,9 +44,9 @@ const sso = (options) => {
|
|
|
43
44
|
"/sso/saml2/sp/metadata",
|
|
44
45
|
{
|
|
45
46
|
method: "GET",
|
|
46
|
-
query:
|
|
47
|
-
providerId:
|
|
48
|
-
format:
|
|
47
|
+
query: z__namespace.object({
|
|
48
|
+
providerId: z__namespace.string(),
|
|
49
|
+
format: z__namespace.enum(["xml", "json"]).default("xml")
|
|
49
50
|
}),
|
|
50
51
|
metadata: {
|
|
51
52
|
openapi: {
|
|
@@ -89,96 +90,102 @@ const sso = (options) => {
|
|
|
89
90
|
"/sso/register",
|
|
90
91
|
{
|
|
91
92
|
method: "POST",
|
|
92
|
-
body:
|
|
93
|
-
providerId:
|
|
93
|
+
body: z__namespace.object({
|
|
94
|
+
providerId: z__namespace.string({}).meta({
|
|
94
95
|
description: "The ID of the provider. This is used to identify the provider during login and callback"
|
|
95
96
|
}),
|
|
96
|
-
issuer:
|
|
97
|
+
issuer: z__namespace.string({}).meta({
|
|
97
98
|
description: "The issuer of the provider"
|
|
98
99
|
}),
|
|
99
|
-
domain:
|
|
100
|
+
domain: z__namespace.string({}).meta({
|
|
100
101
|
description: "The domain of the provider. This is used for email matching"
|
|
101
102
|
}),
|
|
102
|
-
oidcConfig:
|
|
103
|
-
clientId:
|
|
103
|
+
oidcConfig: z__namespace.object({
|
|
104
|
+
clientId: z__namespace.string({}).meta({
|
|
104
105
|
description: "The client ID"
|
|
105
106
|
}),
|
|
106
|
-
clientSecret:
|
|
107
|
+
clientSecret: z__namespace.string({}).meta({
|
|
107
108
|
description: "The client secret"
|
|
108
109
|
}),
|
|
109
|
-
authorizationEndpoint:
|
|
110
|
+
authorizationEndpoint: z__namespace.string({}).meta({
|
|
110
111
|
description: "The authorization endpoint"
|
|
111
112
|
}).optional(),
|
|
112
|
-
tokenEndpoint:
|
|
113
|
+
tokenEndpoint: z__namespace.string({}).meta({
|
|
113
114
|
description: "The token endpoint"
|
|
114
115
|
}).optional(),
|
|
115
|
-
userInfoEndpoint:
|
|
116
|
+
userInfoEndpoint: z__namespace.string({}).meta({
|
|
116
117
|
description: "The user info endpoint"
|
|
117
118
|
}).optional(),
|
|
118
|
-
tokenEndpointAuthentication:
|
|
119
|
-
jwksEndpoint:
|
|
119
|
+
tokenEndpointAuthentication: z__namespace.enum(["client_secret_post", "client_secret_basic"]).optional(),
|
|
120
|
+
jwksEndpoint: z__namespace.string({}).meta({
|
|
120
121
|
description: "The JWKS endpoint"
|
|
121
122
|
}).optional(),
|
|
122
|
-
discoveryEndpoint:
|
|
123
|
-
scopes:
|
|
123
|
+
discoveryEndpoint: z__namespace.string().optional(),
|
|
124
|
+
scopes: z__namespace.array(z__namespace.string(), {}).meta({
|
|
124
125
|
description: "The scopes to request. Defaults to ['openid', 'email', 'profile', 'offline_access']"
|
|
125
126
|
}).optional(),
|
|
126
|
-
pkce:
|
|
127
|
+
pkce: z__namespace.boolean({}).meta({
|
|
127
128
|
description: "Whether to use PKCE for the authorization flow"
|
|
128
129
|
}).default(true).optional()
|
|
129
130
|
}).optional(),
|
|
130
|
-
samlConfig:
|
|
131
|
-
entryPoint:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
131
|
+
samlConfig: z__namespace.object({
|
|
132
|
+
entryPoint: z__namespace.string({}).meta({
|
|
133
|
+
description: "The entry point of the provider"
|
|
134
|
+
}),
|
|
135
|
+
cert: z__namespace.string({}).meta({
|
|
136
|
+
description: "The certificate of the provider"
|
|
137
|
+
}),
|
|
138
|
+
callbackUrl: z__namespace.string({}).meta({
|
|
139
|
+
description: "The callback URL of the provider"
|
|
140
|
+
}),
|
|
141
|
+
audience: z__namespace.string().optional(),
|
|
142
|
+
idpMetadata: z__namespace.object({
|
|
143
|
+
metadata: z__namespace.string(),
|
|
144
|
+
privateKey: z__namespace.string().optional(),
|
|
145
|
+
privateKeyPass: z__namespace.string().optional(),
|
|
146
|
+
isAssertionEncrypted: z__namespace.boolean().optional(),
|
|
147
|
+
encPrivateKey: z__namespace.string().optional(),
|
|
148
|
+
encPrivateKeyPass: z__namespace.string().optional()
|
|
142
149
|
}).optional(),
|
|
143
|
-
spMetadata:
|
|
144
|
-
metadata:
|
|
145
|
-
binding:
|
|
146
|
-
privateKey:
|
|
147
|
-
privateKeyPass:
|
|
148
|
-
isAssertionEncrypted:
|
|
149
|
-
encPrivateKey:
|
|
150
|
-
encPrivateKeyPass:
|
|
150
|
+
spMetadata: z__namespace.object({
|
|
151
|
+
metadata: z__namespace.string(),
|
|
152
|
+
binding: z__namespace.string().optional(),
|
|
153
|
+
privateKey: z__namespace.string().optional(),
|
|
154
|
+
privateKeyPass: z__namespace.string().optional(),
|
|
155
|
+
isAssertionEncrypted: z__namespace.boolean().optional(),
|
|
156
|
+
encPrivateKey: z__namespace.string().optional(),
|
|
157
|
+
encPrivateKeyPass: z__namespace.string().optional()
|
|
151
158
|
}),
|
|
152
|
-
wantAssertionsSigned:
|
|
153
|
-
signatureAlgorithm:
|
|
154
|
-
digestAlgorithm:
|
|
155
|
-
identifierFormat:
|
|
156
|
-
privateKey:
|
|
157
|
-
decryptionPvk:
|
|
158
|
-
additionalParams:
|
|
159
|
+
wantAssertionsSigned: z__namespace.boolean().optional(),
|
|
160
|
+
signatureAlgorithm: z__namespace.string().optional(),
|
|
161
|
+
digestAlgorithm: z__namespace.string().optional(),
|
|
162
|
+
identifierFormat: z__namespace.string().optional(),
|
|
163
|
+
privateKey: z__namespace.string().optional(),
|
|
164
|
+
decryptionPvk: z__namespace.string().optional(),
|
|
165
|
+
additionalParams: z__namespace.record(z__namespace.string(), z__namespace.any()).optional()
|
|
159
166
|
}).optional(),
|
|
160
|
-
mapping:
|
|
161
|
-
id:
|
|
167
|
+
mapping: z__namespace.object({
|
|
168
|
+
id: z__namespace.string({}).meta({
|
|
162
169
|
description: "The field in the user info response that contains the id. Defaults to 'sub'"
|
|
163
170
|
}),
|
|
164
|
-
email:
|
|
171
|
+
email: z__namespace.string({}).meta({
|
|
165
172
|
description: "The field in the user info response that contains the email. Defaults to 'email'"
|
|
166
173
|
}),
|
|
167
|
-
emailVerified:
|
|
174
|
+
emailVerified: z__namespace.string({}).meta({
|
|
168
175
|
description: "The field in the user info response that contains whether the email is verified. defaults to 'email_verified'"
|
|
169
176
|
}).optional(),
|
|
170
|
-
name:
|
|
177
|
+
name: z__namespace.string({}).meta({
|
|
171
178
|
description: "The field in the user info response that contains the name. Defaults to 'name'"
|
|
172
179
|
}),
|
|
173
|
-
image:
|
|
180
|
+
image: z__namespace.string({}).meta({
|
|
174
181
|
description: "The field in the user info response that contains the image. Defaults to 'picture'"
|
|
175
182
|
}).optional(),
|
|
176
|
-
extraFields:
|
|
183
|
+
extraFields: z__namespace.record(z__namespace.string(), z__namespace.any()).optional()
|
|
177
184
|
}).optional(),
|
|
178
|
-
organizationId:
|
|
185
|
+
organizationId: z__namespace.string({}).meta({
|
|
179
186
|
description: "If organization plugin is enabled, the organization id to link the provider to"
|
|
180
187
|
}).optional(),
|
|
181
|
-
overrideUserInfo:
|
|
188
|
+
overrideUserInfo: z__namespace.boolean({}).meta({
|
|
182
189
|
description: "Override user info with the provider info. Defaults to false"
|
|
183
190
|
}).default(false).optional()
|
|
184
191
|
}),
|
|
@@ -349,8 +356,27 @@ const sso = (options) => {
|
|
|
349
356
|
}
|
|
350
357
|
},
|
|
351
358
|
async (ctx) => {
|
|
359
|
+
const user = ctx.context.session?.user;
|
|
360
|
+
if (!user) {
|
|
361
|
+
throw new api.APIError("UNAUTHORIZED");
|
|
362
|
+
}
|
|
363
|
+
const limit = typeof options?.providersLimit === "function" ? await options.providersLimit(user) : options?.providersLimit ?? 10;
|
|
364
|
+
if (!limit) {
|
|
365
|
+
throw new api.APIError("FORBIDDEN", {
|
|
366
|
+
message: "SSO provider registration is disabled"
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
const providers = await ctx.context.adapter.findMany({
|
|
370
|
+
model: "ssoProvider",
|
|
371
|
+
where: [{ field: "userId", value: user.id }]
|
|
372
|
+
});
|
|
373
|
+
if (providers.length >= limit) {
|
|
374
|
+
throw new api.APIError("FORBIDDEN", {
|
|
375
|
+
message: "You have reached the maximum number of SSO providers"
|
|
376
|
+
});
|
|
377
|
+
}
|
|
352
378
|
const body = ctx.body;
|
|
353
|
-
const issuerValidator =
|
|
379
|
+
const issuerValidator = z__namespace.string().url();
|
|
354
380
|
if (issuerValidator.safeParse(body.issuer).error) {
|
|
355
381
|
throw new api.APIError("BAD_REQUEST", {
|
|
356
382
|
message: "Invalid issuer. Must be a valid URL"
|
|
@@ -413,35 +439,35 @@ const sso = (options) => {
|
|
|
413
439
|
"/sign-in/sso",
|
|
414
440
|
{
|
|
415
441
|
method: "POST",
|
|
416
|
-
body:
|
|
417
|
-
email:
|
|
442
|
+
body: z__namespace.object({
|
|
443
|
+
email: z__namespace.string({}).meta({
|
|
418
444
|
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"
|
|
419
445
|
}).optional(),
|
|
420
|
-
organizationSlug:
|
|
446
|
+
organizationSlug: z__namespace.string({}).meta({
|
|
421
447
|
description: "The slug of the organization to sign in with"
|
|
422
448
|
}).optional(),
|
|
423
|
-
providerId:
|
|
449
|
+
providerId: z__namespace.string({}).meta({
|
|
424
450
|
description: "The ID of the provider to sign in with. This can be provided instead of email or issuer"
|
|
425
451
|
}).optional(),
|
|
426
|
-
domain:
|
|
452
|
+
domain: z__namespace.string({}).meta({
|
|
427
453
|
description: "The domain of the provider."
|
|
428
454
|
}).optional(),
|
|
429
|
-
callbackURL:
|
|
455
|
+
callbackURL: z__namespace.string({}).meta({
|
|
430
456
|
description: "The URL to redirect to after login"
|
|
431
457
|
}),
|
|
432
|
-
errorCallbackURL:
|
|
458
|
+
errorCallbackURL: z__namespace.string({}).meta({
|
|
433
459
|
description: "The URL to redirect to after login"
|
|
434
460
|
}).optional(),
|
|
435
|
-
newUserCallbackURL:
|
|
461
|
+
newUserCallbackURL: z__namespace.string({}).meta({
|
|
436
462
|
description: "The URL to redirect to after login if the user is new"
|
|
437
463
|
}).optional(),
|
|
438
|
-
scopes:
|
|
464
|
+
scopes: z__namespace.array(z__namespace.string(), {}).meta({
|
|
439
465
|
description: "Scopes to request from the provider."
|
|
440
466
|
}).optional(),
|
|
441
|
-
requestSignUp:
|
|
467
|
+
requestSignUp: z__namespace.boolean({}).meta({
|
|
442
468
|
description: "Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider"
|
|
443
469
|
}).optional(),
|
|
444
|
-
providerType:
|
|
470
|
+
providerType: z__namespace.enum(["oidc", "saml"]).optional()
|
|
445
471
|
}),
|
|
446
472
|
metadata: {
|
|
447
473
|
openapi: {
|
|
@@ -617,7 +643,9 @@ const sso = (options) => {
|
|
|
617
643
|
});
|
|
618
644
|
}
|
|
619
645
|
return ctx.json({
|
|
620
|
-
url: loginRequest.context
|
|
646
|
+
url: `${loginRequest.context}&RelayState=${encodeURIComponent(
|
|
647
|
+
body.callbackURL
|
|
648
|
+
)}`,
|
|
621
649
|
redirect: true
|
|
622
650
|
});
|
|
623
651
|
}
|
|
@@ -630,11 +658,11 @@ const sso = (options) => {
|
|
|
630
658
|
"/sso/callback/:providerId",
|
|
631
659
|
{
|
|
632
660
|
method: "GET",
|
|
633
|
-
query:
|
|
634
|
-
code:
|
|
635
|
-
state:
|
|
636
|
-
error:
|
|
637
|
-
error_description:
|
|
661
|
+
query: z__namespace.object({
|
|
662
|
+
code: z__namespace.string().optional(),
|
|
663
|
+
state: z__namespace.string(),
|
|
664
|
+
error: z__namespace.string().optional(),
|
|
665
|
+
error_description: z__namespace.string().optional()
|
|
638
666
|
}),
|
|
639
667
|
metadata: {
|
|
640
668
|
isAction: false,
|
|
@@ -876,9 +904,9 @@ const sso = (options) => {
|
|
|
876
904
|
"/sso/saml2/callback/:providerId",
|
|
877
905
|
{
|
|
878
906
|
method: "POST",
|
|
879
|
-
body:
|
|
880
|
-
SAMLResponse:
|
|
881
|
-
RelayState:
|
|
907
|
+
body: z__namespace.object({
|
|
908
|
+
SAMLResponse: z__namespace.string(),
|
|
909
|
+
RelayState: z__namespace.string().optional()
|
|
882
910
|
}),
|
|
883
911
|
metadata: {
|
|
884
912
|
isAction: false,
|
|
@@ -945,11 +973,11 @@ const sso = (options) => {
|
|
|
945
973
|
extract.attributes[value]
|
|
946
974
|
])
|
|
947
975
|
),
|
|
948
|
-
id: attributes[mapping.id || "nameID"],
|
|
949
|
-
email: attributes[mapping.email || "nameID"],
|
|
976
|
+
id: attributes[mapping.id] || attributes["nameID"],
|
|
977
|
+
email: attributes[mapping.email] || attributes["nameID"] || attributes["email"],
|
|
950
978
|
name: [
|
|
951
|
-
attributes[mapping.firstName || "givenName"],
|
|
952
|
-
attributes[mapping.lastName || "surname"]
|
|
979
|
+
attributes[mapping.firstName] || attributes["givenName"],
|
|
980
|
+
attributes[mapping.lastName] || attributes["surname"]
|
|
953
981
|
].filter(Boolean).join(" ") || parsedResponse.extract.attributes?.displayName,
|
|
954
982
|
attributes: parsedResponse.extract.attributes
|
|
955
983
|
};
|
|
@@ -1015,10 +1043,9 @@ const sso = (options) => {
|
|
|
1015
1043
|
}
|
|
1016
1044
|
let session = await ctx.context.internalAdapter.createSession(user.id, ctx);
|
|
1017
1045
|
await cookies.setSessionCookie(ctx, { session, user });
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
});
|
|
1046
|
+
throw ctx.redirect(
|
|
1047
|
+
RelayState || `${parsedSamlConfig.callbackUrl}` || `${parsedSamlConfig.issuer}`
|
|
1048
|
+
);
|
|
1022
1049
|
}
|
|
1023
1050
|
)
|
|
1024
1051
|
},
|