@better-auth/sso 1.3.2 → 1.3.4-beta.1

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.2 build /home/runner/work/better-auth/better-auth/packages/sso
2
+ > @better-auth/sso@1.3.4-beta.1 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: 45.3 kB, chunk size: 45.3 kB, exports: sso)
8
+ [log] dist/index.cjs (total size: 47.5 kB, chunk size: 47.5 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: 43.9 kB, chunk size: 43.9 kB, exports: sso)
12
+ [log] dist/index.mjs (total size: 46.1 kB, chunk size: 46.1 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): 199 kB
16
+ Σ Total dist size (byte size): 204 kB
17
17
  [log]
package/dist/index.cjs CHANGED
@@ -416,7 +416,8 @@ const sso = (options) => {
416
416
  identifierFormat: body.samlConfig.identifierFormat,
417
417
  privateKey: body.samlConfig.privateKey,
418
418
  decryptionPvk: body.samlConfig.decryptionPvk,
419
- additionalParams: body.samlConfig.additionalParams
419
+ additionalParams: body.samlConfig.additionalParams,
420
+ mapping: body.mapping
420
421
  }) : null,
421
422
  organizationId: body.organizationId,
422
423
  userId: ctx.context.session.user.id,
@@ -790,7 +791,7 @@ const sso = (options) => {
790
791
  ),
791
792
  id: idToken[mapping.id || "sub"],
792
793
  email: idToken[mapping.email || "email"],
793
- emailVerified: idToken[mapping.emailVerified || "email_verified"],
794
+ emailVerified: options?.trustEmailVerified ? idToken[mapping.emailVerified || "email_verified"] : false,
794
795
  name: idToken[mapping.name || "name"],
795
796
  image: idToken[mapping.image || "picture"]
796
797
  };
@@ -824,7 +825,7 @@ const sso = (options) => {
824
825
  name: userInfo.name || userInfo.email,
825
826
  id: userInfo.id,
826
827
  image: userInfo.image,
827
- emailVerified: userInfo.emailVerified || false
828
+ emailVerified: options?.trustEmailVerified ? userInfo.emailVerified || false : false
828
829
  },
829
830
  account: {
830
831
  idToken: tokenResponse.idToken,
@@ -979,7 +980,8 @@ const sso = (options) => {
979
980
  attributes[mapping.firstName] || attributes["givenName"],
980
981
  attributes[mapping.lastName] || attributes["surname"]
981
982
  ].filter(Boolean).join(" ") || parsedResponse.extract.attributes?.displayName,
982
- attributes: parsedResponse.extract.attributes
983
+ attributes: parsedResponse.extract.attributes,
984
+ emailVerified: options?.trustEmailVerified ? attributes?.[mapping.emailVerified] || false : false
983
985
  };
984
986
  let user;
985
987
  const existingUser = await ctx.context.adapter.findOne({
@@ -992,6 +994,36 @@ const sso = (options) => {
992
994
  ]
993
995
  });
994
996
  if (existingUser) {
997
+ const accounts = await ctx.context.adapter.findOne({
998
+ model: "account",
999
+ where: [
1000
+ { field: "userId", value: existingUser.id },
1001
+ { field: "providerId", value: provider.providerId },
1002
+ { field: "accountId", value: userInfo.id }
1003
+ ]
1004
+ });
1005
+ if (!accounts) {
1006
+ const isTrustedProvider = ctx.context.options.account?.accountLinking?.trustedProviders?.includes(
1007
+ provider.providerId
1008
+ );
1009
+ if (!isTrustedProvider) {
1010
+ throw ctx.redirect(
1011
+ `${parsedSamlConfig.callbackUrl}?error=account_not_found`
1012
+ );
1013
+ }
1014
+ await ctx.context.adapter.create({
1015
+ model: "account",
1016
+ data: {
1017
+ userId: existingUser.id,
1018
+ providerId: provider.providerId,
1019
+ accountId: userInfo.id,
1020
+ createdAt: /* @__PURE__ */ new Date(),
1021
+ updatedAt: /* @__PURE__ */ new Date(),
1022
+ accessToken: "",
1023
+ refreshToken: ""
1024
+ }
1025
+ });
1026
+ }
995
1027
  user = existingUser;
996
1028
  } else {
997
1029
  user = await ctx.context.adapter.create({
@@ -999,7 +1031,24 @@ const sso = (options) => {
999
1031
  data: {
1000
1032
  email: userInfo.email,
1001
1033
  name: userInfo.name,
1002
- emailVerified: true
1034
+ emailVerified: options?.trustEmailVerified ? userInfo.emailVerified || false : false,
1035
+ createdAt: /* @__PURE__ */ new Date(),
1036
+ updatedAt: /* @__PURE__ */ new Date()
1037
+ }
1038
+ });
1039
+ await ctx.context.adapter.create({
1040
+ model: "account",
1041
+ data: {
1042
+ userId: user.id,
1043
+ providerId: provider.providerId,
1044
+ accountId: userInfo.id,
1045
+ accessToken: "",
1046
+ refreshToken: "",
1047
+ accessTokenExpiresAt: /* @__PURE__ */ new Date(),
1048
+ refreshTokenExpiresAt: /* @__PURE__ */ new Date(),
1049
+ scope: "",
1050
+ createdAt: /* @__PURE__ */ new Date(),
1051
+ updatedAt: /* @__PURE__ */ new Date()
1003
1052
  }
1004
1053
  });
1005
1054
  }
package/dist/index.d.cts CHANGED
@@ -30,6 +30,14 @@ interface SAMLConfig {
30
30
  signingKey: string;
31
31
  certificate: string;
32
32
  attributeConsumingServiceIndex: number;
33
+ mapping?: {
34
+ id?: string;
35
+ email?: string;
36
+ name?: string;
37
+ firstName?: string;
38
+ lastName?: string;
39
+ extraFields?: Record<string, string>;
40
+ };
33
41
  }
34
42
  interface SSOProvider {
35
43
  issuer: string;
@@ -111,6 +119,11 @@ interface SSOOptions {
111
119
  * @default 10
112
120
  */
113
121
  providersLimit?: number | ((user: User) => Promise<number> | number);
122
+ /**
123
+ * Trust the email verified flag from the provider.
124
+ * @default false
125
+ */
126
+ trustEmailVerified?: boolean;
114
127
  }
115
128
  declare const sso: (options?: SSOOptions) => {
116
129
  id: "sso";
package/dist/index.d.mts CHANGED
@@ -30,6 +30,14 @@ interface SAMLConfig {
30
30
  signingKey: string;
31
31
  certificate: string;
32
32
  attributeConsumingServiceIndex: number;
33
+ mapping?: {
34
+ id?: string;
35
+ email?: string;
36
+ name?: string;
37
+ firstName?: string;
38
+ lastName?: string;
39
+ extraFields?: Record<string, string>;
40
+ };
33
41
  }
34
42
  interface SSOProvider {
35
43
  issuer: string;
@@ -111,6 +119,11 @@ interface SSOOptions {
111
119
  * @default 10
112
120
  */
113
121
  providersLimit?: number | ((user: User) => Promise<number> | number);
122
+ /**
123
+ * Trust the email verified flag from the provider.
124
+ * @default false
125
+ */
126
+ trustEmailVerified?: boolean;
114
127
  }
115
128
  declare const sso: (options?: SSOOptions) => {
116
129
  id: "sso";
package/dist/index.d.ts CHANGED
@@ -30,6 +30,14 @@ interface SAMLConfig {
30
30
  signingKey: string;
31
31
  certificate: string;
32
32
  attributeConsumingServiceIndex: number;
33
+ mapping?: {
34
+ id?: string;
35
+ email?: string;
36
+ name?: string;
37
+ firstName?: string;
38
+ lastName?: string;
39
+ extraFields?: Record<string, string>;
40
+ };
33
41
  }
34
42
  interface SSOProvider {
35
43
  issuer: string;
@@ -111,6 +119,11 @@ interface SSOOptions {
111
119
  * @default 10
112
120
  */
113
121
  providersLimit?: number | ((user: User) => Promise<number> | number);
122
+ /**
123
+ * Trust the email verified flag from the provider.
124
+ * @default false
125
+ */
126
+ trustEmailVerified?: boolean;
114
127
  }
115
128
  declare const sso: (options?: SSOOptions) => {
116
129
  id: "sso";
package/dist/index.mjs CHANGED
@@ -399,7 +399,8 @@ const sso = (options) => {
399
399
  identifierFormat: body.samlConfig.identifierFormat,
400
400
  privateKey: body.samlConfig.privateKey,
401
401
  decryptionPvk: body.samlConfig.decryptionPvk,
402
- additionalParams: body.samlConfig.additionalParams
402
+ additionalParams: body.samlConfig.additionalParams,
403
+ mapping: body.mapping
403
404
  }) : null,
404
405
  organizationId: body.organizationId,
405
406
  userId: ctx.context.session.user.id,
@@ -773,7 +774,7 @@ const sso = (options) => {
773
774
  ),
774
775
  id: idToken[mapping.id || "sub"],
775
776
  email: idToken[mapping.email || "email"],
776
- emailVerified: idToken[mapping.emailVerified || "email_verified"],
777
+ emailVerified: options?.trustEmailVerified ? idToken[mapping.emailVerified || "email_verified"] : false,
777
778
  name: idToken[mapping.name || "name"],
778
779
  image: idToken[mapping.image || "picture"]
779
780
  };
@@ -807,7 +808,7 @@ const sso = (options) => {
807
808
  name: userInfo.name || userInfo.email,
808
809
  id: userInfo.id,
809
810
  image: userInfo.image,
810
- emailVerified: userInfo.emailVerified || false
811
+ emailVerified: options?.trustEmailVerified ? userInfo.emailVerified || false : false
811
812
  },
812
813
  account: {
813
814
  idToken: tokenResponse.idToken,
@@ -962,7 +963,8 @@ const sso = (options) => {
962
963
  attributes[mapping.firstName] || attributes["givenName"],
963
964
  attributes[mapping.lastName] || attributes["surname"]
964
965
  ].filter(Boolean).join(" ") || parsedResponse.extract.attributes?.displayName,
965
- attributes: parsedResponse.extract.attributes
966
+ attributes: parsedResponse.extract.attributes,
967
+ emailVerified: options?.trustEmailVerified ? attributes?.[mapping.emailVerified] || false : false
966
968
  };
967
969
  let user;
968
970
  const existingUser = await ctx.context.adapter.findOne({
@@ -975,6 +977,36 @@ const sso = (options) => {
975
977
  ]
976
978
  });
977
979
  if (existingUser) {
980
+ const accounts = await ctx.context.adapter.findOne({
981
+ model: "account",
982
+ where: [
983
+ { field: "userId", value: existingUser.id },
984
+ { field: "providerId", value: provider.providerId },
985
+ { field: "accountId", value: userInfo.id }
986
+ ]
987
+ });
988
+ if (!accounts) {
989
+ const isTrustedProvider = ctx.context.options.account?.accountLinking?.trustedProviders?.includes(
990
+ provider.providerId
991
+ );
992
+ if (!isTrustedProvider) {
993
+ throw ctx.redirect(
994
+ `${parsedSamlConfig.callbackUrl}?error=account_not_found`
995
+ );
996
+ }
997
+ await ctx.context.adapter.create({
998
+ model: "account",
999
+ data: {
1000
+ userId: existingUser.id,
1001
+ providerId: provider.providerId,
1002
+ accountId: userInfo.id,
1003
+ createdAt: /* @__PURE__ */ new Date(),
1004
+ updatedAt: /* @__PURE__ */ new Date(),
1005
+ accessToken: "",
1006
+ refreshToken: ""
1007
+ }
1008
+ });
1009
+ }
978
1010
  user = existingUser;
979
1011
  } else {
980
1012
  user = await ctx.context.adapter.create({
@@ -982,7 +1014,24 @@ const sso = (options) => {
982
1014
  data: {
983
1015
  email: userInfo.email,
984
1016
  name: userInfo.name,
985
- emailVerified: true
1017
+ emailVerified: options?.trustEmailVerified ? userInfo.emailVerified || false : false,
1018
+ createdAt: /* @__PURE__ */ new Date(),
1019
+ updatedAt: /* @__PURE__ */ new Date()
1020
+ }
1021
+ });
1022
+ await ctx.context.adapter.create({
1023
+ model: "account",
1024
+ data: {
1025
+ userId: user.id,
1026
+ providerId: provider.providerId,
1027
+ accountId: userInfo.id,
1028
+ accessToken: "",
1029
+ refreshToken: "",
1030
+ accessTokenExpiresAt: /* @__PURE__ */ new Date(),
1031
+ refreshTokenExpiresAt: /* @__PURE__ */ new Date(),
1032
+ scope: "",
1033
+ createdAt: /* @__PURE__ */ new Date(),
1034
+ updatedAt: /* @__PURE__ */ new Date()
986
1035
  }
987
1036
  });
988
1037
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@better-auth/sso",
3
3
  "author": "Bereket Engida",
4
- "version": "1.3.2",
4
+ "version": "1.3.4-beta.1",
5
5
  "main": "dist/index.cjs",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -47,7 +47,7 @@
47
47
  "oauth2-mock-server": "^7.2.0",
48
48
  "samlify": "^2.10.0",
49
49
  "zod": "^3.24.1",
50
- "better-auth": "^1.3.2"
50
+ "better-auth": "^1.3.4-beta.1"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/body-parser": "^1.19.6",
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  generateState,
3
+ type Account,
3
4
  type BetterAuthPlugin,
4
5
  type OAuth2Tokens,
5
6
  type Session,
@@ -65,6 +66,14 @@ export interface SAMLConfig {
65
66
  signingKey: string;
66
67
  certificate: string;
67
68
  attributeConsumingServiceIndex: number;
69
+ mapping?: {
70
+ id?: string;
71
+ email?: string;
72
+ name?: string;
73
+ firstName?: string;
74
+ lastName?: string;
75
+ extraFields?: Record<string, string>;
76
+ };
68
77
  }
69
78
 
70
79
  export interface SSOProvider {
@@ -148,6 +157,11 @@ export interface SSOOptions {
148
157
  * @default 10
149
158
  */
150
159
  providersLimit?: number | ((user: User) => Promise<number> | number);
160
+ /**
161
+ * Trust the email verified flag from the provider.
162
+ * @default false
163
+ */
164
+ trustEmailVerified?: boolean;
151
165
  }
152
166
 
153
167
  export const sso = (options?: SSOOptions) => {
@@ -623,6 +637,7 @@ export const sso = (options?: SSOOptions) => {
623
637
  privateKey: body.samlConfig.privateKey,
624
638
  decryptionPvk: body.samlConfig.decryptionPvk,
625
639
  additionalParams: body.samlConfig.additionalParams,
640
+ mapping: body.mapping,
626
641
  })
627
642
  : null,
628
643
  organizationId: body.organizationId,
@@ -1093,7 +1108,9 @@ export const sso = (options?: SSOOptions) => {
1093
1108
  ),
1094
1109
  id: idToken[mapping.id || "sub"],
1095
1110
  email: idToken[mapping.email || "email"],
1096
- emailVerified: idToken[mapping.emailVerified || "email_verified"],
1111
+ emailVerified: options?.trustEmailVerified
1112
+ ? idToken[mapping.emailVerified || "email_verified"]
1113
+ : false,
1097
1114
  name: idToken[mapping.name || "name"],
1098
1115
  image: idToken[mapping.image || "picture"],
1099
1116
  } as {
@@ -1149,7 +1166,9 @@ export const sso = (options?: SSOOptions) => {
1149
1166
  name: userInfo.name || userInfo.email,
1150
1167
  id: userInfo.id,
1151
1168
  image: userInfo.image,
1152
- emailVerified: userInfo.emailVerified || false,
1169
+ emailVerified: options?.trustEmailVerified
1170
+ ? userInfo.emailVerified || false
1171
+ : false,
1153
1172
  },
1154
1173
  account: {
1155
1174
  idToken: tokenResponse.idToken,
@@ -1325,6 +1344,9 @@ export const sso = (options?: SSOOptions) => {
1325
1344
  .filter(Boolean)
1326
1345
  .join(" ") || parsedResponse.extract.attributes?.displayName,
1327
1346
  attributes: parsedResponse.extract.attributes,
1347
+ emailVerified: options?.trustEmailVerified
1348
+ ? ((attributes?.[mapping.emailVerified] || false) as boolean)
1349
+ : false,
1328
1350
  };
1329
1351
 
1330
1352
  let user: User;
@@ -1340,6 +1362,37 @@ export const sso = (options?: SSOOptions) => {
1340
1362
  });
1341
1363
 
1342
1364
  if (existingUser) {
1365
+ const accounts = await ctx.context.adapter.findOne<Account>({
1366
+ model: "account",
1367
+ where: [
1368
+ { field: "userId", value: existingUser.id },
1369
+ { field: "providerId", value: provider.providerId },
1370
+ { field: "accountId", value: userInfo.id },
1371
+ ],
1372
+ });
1373
+ if (!accounts) {
1374
+ const isTrustedProvider =
1375
+ ctx.context.options.account?.accountLinking?.trustedProviders?.includes(
1376
+ provider.providerId,
1377
+ );
1378
+ if (!isTrustedProvider) {
1379
+ throw ctx.redirect(
1380
+ `${parsedSamlConfig.callbackUrl}?error=account_not_found`,
1381
+ );
1382
+ }
1383
+ await ctx.context.adapter.create<Account>({
1384
+ model: "account",
1385
+ data: {
1386
+ userId: existingUser.id,
1387
+ providerId: provider.providerId,
1388
+ accountId: userInfo.id,
1389
+ createdAt: new Date(),
1390
+ updatedAt: new Date(),
1391
+ accessToken: "",
1392
+ refreshToken: "",
1393
+ },
1394
+ });
1395
+ }
1343
1396
  user = existingUser;
1344
1397
  } else {
1345
1398
  user = await ctx.context.adapter.create({
@@ -1347,7 +1400,26 @@ export const sso = (options?: SSOOptions) => {
1347
1400
  data: {
1348
1401
  email: userInfo.email,
1349
1402
  name: userInfo.name,
1350
- emailVerified: true,
1403
+ emailVerified: options?.trustEmailVerified
1404
+ ? userInfo.emailVerified || false
1405
+ : false,
1406
+ createdAt: new Date(),
1407
+ updatedAt: new Date(),
1408
+ },
1409
+ });
1410
+ await ctx.context.adapter.create<Account>({
1411
+ model: "account",
1412
+ data: {
1413
+ userId: user.id,
1414
+ providerId: provider.providerId,
1415
+ accountId: userInfo.id,
1416
+ accessToken: "",
1417
+ refreshToken: "",
1418
+ accessTokenExpiresAt: new Date(),
1419
+ refreshTokenExpiresAt: new Date(),
1420
+ scope: "",
1421
+ createdAt: new Date(),
1422
+ updatedAt: new Date(),
1351
1423
  },
1352
1424
  });
1353
1425
  }