@better-auth/sso 1.3.0-beta.1 → 1.3.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,17 @@
1
1
 
2
- > @better-auth/sso@1.3.0-beta.1 build /home/runner/work/better-auth/better-auth/packages/sso
2
+ > @better-auth/sso@1.3.0-beta.10 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: 43.2 kB, chunk size: 43.2 kB, exports: sso)
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: 42.5 kB, chunk size: 42.5 kB, exports: sso)
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): 238 kB
16
+ Σ Total dist size (byte size): 199 kB
17
17
  [log]
package/dist/client.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { sso } from './index.cjs';
2
2
  import 'better-call';
3
3
  import 'better-auth';
4
- import 'zod';
4
+ import 'zod/v4';
5
5
 
6
6
  declare const ssoClient: () => {
7
7
  id: "sso-client";
package/dist/client.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { sso } from './index.mjs';
2
2
  import 'better-call';
3
3
  import 'better-auth';
4
- import 'zod';
4
+ import 'zod/v4';
5
5
 
6
6
  declare const ssoClient: () => {
7
7
  id: "sso-client";
package/dist/client.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { sso } from './index.js';
2
2
  import 'better-call';
3
3
  import 'better-auth';
4
- import 'zod';
4
+ import 'zod/v4';
5
5
 
6
6
  declare const ssoClient: () => {
7
7
  id: "sso-client";
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 zod = require('zod');
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: zod.z.object({
47
- providerId: zod.z.string(),
48
- format: zod.z.enum(["xml", "json"]).default("xml")
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: zod.z.object({
93
- providerId: zod.z.string({
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: zod.z.string({
97
+ issuer: z__namespace.string({}).meta({
97
98
  description: "The issuer of the provider"
98
99
  }),
99
- domain: zod.z.string({
100
+ domain: z__namespace.string({}).meta({
100
101
  description: "The domain of the provider. This is used for email matching"
101
102
  }),
102
- oidcConfig: zod.z.object({
103
- clientId: zod.z.string({
103
+ oidcConfig: z__namespace.object({
104
+ clientId: z__namespace.string({}).meta({
104
105
  description: "The client ID"
105
106
  }),
106
- clientSecret: zod.z.string({
107
+ clientSecret: z__namespace.string({}).meta({
107
108
  description: "The client secret"
108
109
  }),
109
- authorizationEndpoint: zod.z.string({
110
+ authorizationEndpoint: z__namespace.string({}).meta({
110
111
  description: "The authorization endpoint"
111
112
  }).optional(),
112
- tokenEndpoint: zod.z.string({
113
+ tokenEndpoint: z__namespace.string({}).meta({
113
114
  description: "The token endpoint"
114
115
  }).optional(),
115
- userInfoEndpoint: zod.z.string({
116
+ userInfoEndpoint: z__namespace.string({}).meta({
116
117
  description: "The user info endpoint"
117
118
  }).optional(),
118
- tokenEndpointAuthentication: zod.z.enum(["client_secret_post", "client_secret_basic"]).optional(),
119
- jwksEndpoint: zod.z.string({
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: zod.z.string().optional(),
123
- scopes: zod.z.array(zod.z.string(), {
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: zod.z.boolean({
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: zod.z.object({
131
- entryPoint: zod.z.string(),
132
- cert: zod.z.string(),
133
- callbackUrl: zod.z.string(),
134
- audience: zod.z.string().optional(),
135
- idpMetadata: zod.z.object({
136
- metadata: zod.z.string(),
137
- privateKey: zod.z.string().optional(),
138
- privateKeyPass: zod.z.string().optional(),
139
- isAssertionEncrypted: zod.z.boolean().optional(),
140
- encPrivateKey: zod.z.string().optional(),
141
- encPrivateKeyPass: zod.z.string().optional()
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: zod.z.object({
144
- metadata: zod.z.string(),
145
- binding: zod.z.string().optional(),
146
- privateKey: zod.z.string().optional(),
147
- privateKeyPass: zod.z.string().optional(),
148
- isAssertionEncrypted: zod.z.boolean().optional(),
149
- encPrivateKey: zod.z.string().optional(),
150
- encPrivateKeyPass: zod.z.string().optional()
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: zod.z.boolean().optional(),
153
- signatureAlgorithm: zod.z.string().optional(),
154
- digestAlgorithm: zod.z.string().optional(),
155
- identifierFormat: zod.z.string().optional(),
156
- privateKey: zod.z.string().optional(),
157
- decryptionPvk: zod.z.string().optional(),
158
- additionalParams: zod.z.record(zod.z.string()).optional()
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: zod.z.object({
161
- id: zod.z.string({
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: zod.z.string({
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: zod.z.string({
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: zod.z.string({
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: zod.z.string({
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: zod.z.record(zod.z.string()).optional()
183
+ extraFields: z__namespace.record(z__namespace.string(), z__namespace.any()).optional()
177
184
  }).optional(),
178
- organizationId: zod.z.string({
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: zod.z.boolean({
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 = zod.z.string().url();
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: zod.z.object({
417
- email: zod.z.string({
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: zod.z.string({
446
+ organizationSlug: z__namespace.string({}).meta({
421
447
  description: "The slug of the organization to sign in with"
422
448
  }).optional(),
423
- providerId: zod.z.string({
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: zod.z.string({
452
+ domain: z__namespace.string({}).meta({
427
453
  description: "The domain of the provider."
428
454
  }).optional(),
429
- callbackURL: zod.z.string({
455
+ callbackURL: z__namespace.string({}).meta({
430
456
  description: "The URL to redirect to after login"
431
457
  }),
432
- errorCallbackURL: zod.z.string({
458
+ errorCallbackURL: z__namespace.string({}).meta({
433
459
  description: "The URL to redirect to after login"
434
460
  }).optional(),
435
- newUserCallbackURL: zod.z.string({
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: zod.z.array(zod.z.string(), {
464
+ scopes: z__namespace.array(z__namespace.string(), {}).meta({
439
465
  description: "Scopes to request from the provider."
440
466
  }).optional(),
441
- requestSignUp: zod.z.boolean({
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: zod.z.enum(["oidc", "saml"]).optional()
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: zod.z.object({
634
- code: zod.z.string().optional(),
635
- state: zod.z.string(),
636
- error: zod.z.string().optional(),
637
- error_description: zod.z.string().optional()
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: zod.z.object({
880
- SAMLResponse: zod.z.string(),
881
- RelayState: zod.z.string().optional()
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
- return ctx.json({
1019
- redirect: true,
1020
- url: RelayState || `${parsedSamlConfig.issuer}/dashboard`
1021
- });
1046
+ throw ctx.redirect(
1047
+ RelayState || `${parsedSamlConfig.callbackUrl}` || `${parsedSamlConfig.issuer}`
1048
+ );
1022
1049
  }
1023
1050
  )
1024
1051
  },