@better-auth/sso 1.6.16 → 1.6.18

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/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as SSOPlugin } from "./index-DbZYHOJt.mjs";
1
+ import { t as SSOPlugin } from "./index-D9brFUE1.mjs";
2
2
 
3
3
  //#region src/client.d.ts
4
4
  interface SSOClientOptions {
package/dist/client.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-KncHedVM.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-DMVLEsxG.mjs";
2
2
  //#region src/client.ts
3
3
  const ssoClient = (options) => {
4
4
  return {
@@ -1359,7 +1359,7 @@ declare const callbackSSOShared: (options?: SSOOptions) => better_call0.StrictEn
1359
1359
  allowedMediaTypes: readonly ["application/x-www-form-urlencoded", "application/json"];
1360
1360
  }, void>;
1361
1361
  declare const callbackSSOSAML: (options?: SSOOptions) => better_call0.StrictEndpoint<"/sso/saml2/callback/:providerId", {
1362
- method: ("POST" | "GET")[];
1362
+ method: ("GET" | "POST")[];
1363
1363
  body: z.ZodOptional<z.ZodObject<{
1364
1364
  SAMLResponse: z.ZodString;
1365
1365
  RelayState: z.ZodOptional<z.ZodString>;
@@ -1410,7 +1410,7 @@ declare const acsEndpoint: (options?: SSOOptions) => better_call0.StrictEndpoint
1410
1410
  };
1411
1411
  }, never>;
1412
1412
  declare const sloEndpoint: (options?: SSOOptions) => better_call0.StrictEndpoint<"/sso/saml2/sp/slo/:providerId", {
1413
- method: ("POST" | "GET")[];
1413
+ method: ("GET" | "POST")[];
1414
1414
  body: z.ZodOptional<z.ZodObject<{
1415
1415
  SAMLRequest: z.ZodOptional<z.ZodString>;
1416
1416
  SAMLResponse: z.ZodOptional<z.ZodString>;
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as DataEncryptionAlgorithm, C as TimestampValidationOptions, D as SSOOptions, E as SAMLConfig, M as DigestAlgorithm, N as KeyEncryptionAlgorithm, O as SSOProvider, P as SignatureAlgorithm, S as SAMLConditions, T as OIDCConfig, _ as REQUIRED_DISCOVERY_FIELDS, a as fetchDiscoveryDocument, b as DEFAULT_MAX_SAML_METADATA_SIZE, c as normalizeUrl, d as validateDiscoveryUrl, f as DiscoverOIDCConfigParams, g as OIDCDiscoveryDocument, h as HydratedOIDCConfig, i as discoverOIDCConfig, j as DeprecatedAlgorithmBehavior, k as AlgorithmValidationOptions, l as selectTokenEndpointAuthMethod, m as DiscoveryErrorCode, n as sso, o as needsRuntimeDiscovery, p as DiscoveryError, r as computeDiscoveryUrl, s as normalizeDiscoveryUrls, t as SSOPlugin, u as validateDiscoveryDocument, v as RequiredDiscoveryField, w as validateSAMLTimestamp, x as DEFAULT_MAX_SAML_RESPONSE_SIZE, y as DEFAULT_CLOCK_SKEW_MS } from "./index-DbZYHOJt.mjs";
1
+ import { A as DataEncryptionAlgorithm, C as TimestampValidationOptions, D as SSOOptions, E as SAMLConfig, M as DigestAlgorithm, N as KeyEncryptionAlgorithm, O as SSOProvider, P as SignatureAlgorithm, S as SAMLConditions, T as OIDCConfig, _ as REQUIRED_DISCOVERY_FIELDS, a as fetchDiscoveryDocument, b as DEFAULT_MAX_SAML_METADATA_SIZE, c as normalizeUrl, d as validateDiscoveryUrl, f as DiscoverOIDCConfigParams, g as OIDCDiscoveryDocument, h as HydratedOIDCConfig, i as discoverOIDCConfig, j as DeprecatedAlgorithmBehavior, k as AlgorithmValidationOptions, l as selectTokenEndpointAuthMethod, m as DiscoveryErrorCode, n as sso, o as needsRuntimeDiscovery, p as DiscoveryError, r as computeDiscoveryUrl, s as normalizeDiscoveryUrls, t as SSOPlugin, u as validateDiscoveryDocument, v as RequiredDiscoveryField, w as validateSAMLTimestamp, x as DEFAULT_MAX_SAML_RESPONSE_SIZE, y as DEFAULT_CLOCK_SKEW_MS } from "./index-D9brFUE1.mjs";
2
2
  export { AlgorithmValidationOptions, DEFAULT_CLOCK_SKEW_MS, DEFAULT_MAX_SAML_METADATA_SIZE, DEFAULT_MAX_SAML_RESPONSE_SIZE, DataEncryptionAlgorithm, DeprecatedAlgorithmBehavior, DigestAlgorithm, DiscoverOIDCConfigParams, DiscoveryError, DiscoveryErrorCode, HydratedOIDCConfig, KeyEncryptionAlgorithm, OIDCConfig, OIDCDiscoveryDocument, REQUIRED_DISCOVERY_FIELDS, RequiredDiscoveryField, SAMLConditions, SAMLConfig, SSOOptions, SSOPlugin, SSOProvider, SignatureAlgorithm, TimestampValidationOptions, computeDiscoveryUrl, discoverOIDCConfig, fetchDiscoveryDocument, needsRuntimeDiscovery, normalizeDiscoveryUrls, normalizeUrl, selectTokenEndpointAuthMethod, sso, validateDiscoveryDocument, validateDiscoveryUrl, validateSAMLTimestamp };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-KncHedVM.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-DMVLEsxG.mjs";
2
2
  import { APIError, createAuthEndpoint, createAuthMiddleware, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
3
3
  import { XMLParser, XMLValidator } from "fast-xml-parser";
4
4
  import { X509Certificate } from "node:crypto";
@@ -24,8 +24,6 @@ import samlifyDefault from "samlify";
24
24
  */
25
25
  /** Prefix for AuthnRequest IDs used in InResponseTo validation */
26
26
  const AUTHN_REQUEST_KEY_PREFIX = "saml-authn-request:";
27
- /** Prefix for used Assertion IDs used in replay protection */
28
- const USED_ASSERTION_KEY_PREFIX = "saml-used-assertion:";
29
27
  /** Prefix for SAML session data (NameID + SessionIndex) for SLO */
30
28
  const SAML_SESSION_KEY_PREFIX = "saml-session:";
31
29
  /** Prefix for reverse lookup of SAML session by Better Auth session ID */
@@ -86,6 +84,16 @@ const domainMatches = (searchDomain, domainList) => {
86
84
  return domainList.split(",").map((d) => d.trim().toLowerCase()).filter(Boolean).some((d) => search === d || search.endsWith(`.${d}`));
87
85
  };
88
86
  /**
87
+ * Strictly parse a provider-supplied email-verification claim.
88
+ *
89
+ * OIDC userInfo, OIDC id-token, and SAML attribute values are frequently
90
+ * strings, so a loose `Boolean(value)` or truthy fallback treats the string
91
+ * `"false"` as verified. Only a boolean `true` or the exact string `"true"`
92
+ * count as verified; every other value, including `"false"`, `"0"`, `""`,
93
+ * numbers, arrays, and objects, is unverified.
94
+ */
95
+ const parseProviderEmailVerified = (value) => value === true || value === "true";
96
+ /**
89
97
  * Validates email domain against allowed domain(s).
90
98
  * Supports comma-separated domains for multi-domain SSO.
91
99
  */
@@ -211,171 +219,6 @@ async function assignOrganizationByDomain(ctx, options) {
211
219
  });
212
220
  }
213
221
  //#endregion
214
- //#region src/routes/domain-verification.ts
215
- const DNS_LABEL_MAX_LENGTH = 63;
216
- const DEFAULT_TOKEN_PREFIX = "better-auth-token";
217
- const domainVerificationBodySchema = z.object({ providerId: z.string() });
218
- function getVerificationIdentifier(options, providerId) {
219
- return `_${options.domainVerification?.tokenPrefix || DEFAULT_TOKEN_PREFIX}-${providerId}`;
220
- }
221
- const requestDomainVerification = (options) => {
222
- return createAuthEndpoint("/sso/request-domain-verification", {
223
- method: "POST",
224
- body: domainVerificationBodySchema,
225
- metadata: { openapi: {
226
- summary: "Request a domain verification",
227
- description: "Request a domain verification for the given SSO provider",
228
- responses: {
229
- "404": { description: "Provider not found" },
230
- "409": { description: "Domain has already been verified" },
231
- "201": { description: "Domain submitted for verification" }
232
- }
233
- } },
234
- use: [sessionMiddleware]
235
- }, async (ctx) => {
236
- const body = ctx.body;
237
- const provider = await ctx.context.adapter.findOne({
238
- model: "ssoProvider",
239
- where: [{
240
- field: "providerId",
241
- value: body.providerId
242
- }]
243
- });
244
- if (!provider) throw new APIError("NOT_FOUND", {
245
- message: "Provider not found",
246
- code: "PROVIDER_NOT_FOUND"
247
- });
248
- const userId = ctx.context.session.user.id;
249
- let isOrgMember = true;
250
- if (provider.organizationId) isOrgMember = await ctx.context.adapter.count({
251
- model: "member",
252
- where: [{
253
- field: "userId",
254
- value: userId
255
- }, {
256
- field: "organizationId",
257
- value: provider.organizationId
258
- }]
259
- }) > 0;
260
- if (provider.userId !== userId || !isOrgMember) throw new APIError("FORBIDDEN", {
261
- message: "User must be owner of or belong to the SSO provider organization",
262
- code: "INSUFICCIENT_ACCESS"
263
- });
264
- if ("domainVerified" in provider && provider.domainVerified) throw new APIError("CONFLICT", {
265
- message: "Domain has already been verified",
266
- code: "DOMAIN_VERIFIED"
267
- });
268
- const identifier = getVerificationIdentifier(options, provider.providerId);
269
- const activeVerification = await ctx.context.internalAdapter.findVerificationValue(identifier);
270
- if (activeVerification && new Date(activeVerification.expiresAt) > /* @__PURE__ */ new Date()) {
271
- ctx.setStatus(201);
272
- return ctx.json({ domainVerificationToken: activeVerification.value });
273
- }
274
- const domainVerificationToken = generateRandomString(24);
275
- await ctx.context.internalAdapter.createVerificationValue({
276
- identifier,
277
- value: domainVerificationToken,
278
- expiresAt: new Date(Date.now() + 3600 * 24 * 7 * 1e3)
279
- });
280
- ctx.setStatus(201);
281
- return ctx.json({ domainVerificationToken });
282
- });
283
- };
284
- const verifyDomain = (options) => {
285
- return createAuthEndpoint("/sso/verify-domain", {
286
- method: "POST",
287
- body: domainVerificationBodySchema,
288
- metadata: { openapi: {
289
- summary: "Verify the provider domain ownership",
290
- description: "Verify the provider domain ownership via DNS records",
291
- responses: {
292
- "404": { description: "Provider not found" },
293
- "409": { description: "Domain has already been verified or no pending verification exists" },
294
- "502": { description: "Unable to verify domain ownership due to upstream validator error" },
295
- "204": { description: "Domain ownership was verified" }
296
- }
297
- } },
298
- use: [sessionMiddleware]
299
- }, async (ctx) => {
300
- const body = ctx.body;
301
- const provider = await ctx.context.adapter.findOne({
302
- model: "ssoProvider",
303
- where: [{
304
- field: "providerId",
305
- value: body.providerId
306
- }]
307
- });
308
- if (!provider) throw new APIError("NOT_FOUND", {
309
- message: "Provider not found",
310
- code: "PROVIDER_NOT_FOUND"
311
- });
312
- const userId = ctx.context.session.user.id;
313
- let isOrgMember = true;
314
- if (provider.organizationId) isOrgMember = await ctx.context.adapter.count({
315
- model: "member",
316
- where: [{
317
- field: "userId",
318
- value: userId
319
- }, {
320
- field: "organizationId",
321
- value: provider.organizationId
322
- }]
323
- }) > 0;
324
- if (provider.userId !== userId || !isOrgMember) throw new APIError("FORBIDDEN", {
325
- message: "User must be owner of or belong to the SSO provider organization",
326
- code: "INSUFICCIENT_ACCESS"
327
- });
328
- if ("domainVerified" in provider && provider.domainVerified) throw new APIError("CONFLICT", {
329
- message: "Domain has already been verified",
330
- code: "DOMAIN_VERIFIED"
331
- });
332
- const identifier = getVerificationIdentifier(options, provider.providerId);
333
- if (identifier.length > DNS_LABEL_MAX_LENGTH) throw new APIError("BAD_REQUEST", {
334
- message: `Verification identifier exceeds the DNS label limit of ${DNS_LABEL_MAX_LENGTH} characters`,
335
- code: "IDENTIFIER_TOO_LONG"
336
- });
337
- const activeVerification = await ctx.context.internalAdapter.findVerificationValue(identifier);
338
- if (!activeVerification || new Date(activeVerification.expiresAt) <= /* @__PURE__ */ new Date()) throw new APIError("NOT_FOUND", {
339
- message: "No pending domain verification exists",
340
- code: "NO_PENDING_VERIFICATION"
341
- });
342
- let records = [];
343
- let dns;
344
- try {
345
- dns = await import("node:dns/promises");
346
- } catch (error) {
347
- ctx.context.logger.error("The core node:dns module is required for the domain verification feature", error);
348
- throw new APIError("INTERNAL_SERVER_ERROR", {
349
- message: "Unable to verify domain ownership due to server error",
350
- code: "DOMAIN_VERIFICATION_FAILED"
351
- });
352
- }
353
- const hostname = getHostnameFromDomain(provider.domain);
354
- if (!hostname) throw new APIError("BAD_REQUEST", {
355
- message: "Invalid domain",
356
- code: "INVALID_DOMAIN"
357
- });
358
- try {
359
- records = (await dns.resolveTxt(`${identifier}.${hostname}`)).flat();
360
- } catch (error) {
361
- ctx.context.logger.warn("DNS resolution failure while validating domain ownership", error);
362
- }
363
- if (!records.find((record) => record.includes(`${activeVerification.identifier}=${activeVerification.value}`))) throw new APIError("BAD_GATEWAY", {
364
- message: "Unable to verify domain ownership. Try again later",
365
- code: "DOMAIN_VERIFICATION_FAILED"
366
- });
367
- await ctx.context.adapter.update({
368
- model: "ssoProvider",
369
- where: [{
370
- field: "providerId",
371
- value: provider.providerId
372
- }],
373
- update: { domainVerified: true }
374
- });
375
- ctx.setStatus(204);
376
- });
377
- };
378
- //#endregion
379
222
  //#region src/oidc/types.ts
380
223
  /**
381
224
  * Custom error class for OIDC discovery failures.
@@ -1505,6 +1348,119 @@ const deleteSSOProvider = () => {
1505
1348
  });
1506
1349
  };
1507
1350
  //#endregion
1351
+ //#region src/routes/domain-verification.ts
1352
+ const DNS_LABEL_MAX_LENGTH = 63;
1353
+ const DEFAULT_TOKEN_PREFIX = "better-auth-token";
1354
+ const domainVerificationBodySchema = z.object({ providerId: z.string() });
1355
+ function getVerificationIdentifier(options, providerId) {
1356
+ return `_${options.domainVerification?.tokenPrefix || DEFAULT_TOKEN_PREFIX}-${providerId}`;
1357
+ }
1358
+ const requestDomainVerification = (options) => {
1359
+ return createAuthEndpoint("/sso/request-domain-verification", {
1360
+ method: "POST",
1361
+ body: domainVerificationBodySchema,
1362
+ metadata: { openapi: {
1363
+ summary: "Request a domain verification",
1364
+ description: "Request a domain verification for the given SSO provider",
1365
+ responses: {
1366
+ "404": { description: "Provider not found" },
1367
+ "409": { description: "Domain has already been verified" },
1368
+ "201": { description: "Domain submitted for verification" }
1369
+ }
1370
+ } },
1371
+ use: [sessionMiddleware]
1372
+ }, async (ctx) => {
1373
+ const body = ctx.body;
1374
+ const provider = await checkProviderAccess(ctx, body.providerId);
1375
+ if (provider.domainVerified) throw new APIError("CONFLICT", {
1376
+ message: "Domain has already been verified",
1377
+ code: "DOMAIN_VERIFIED"
1378
+ });
1379
+ const identifier = getVerificationIdentifier(options, provider.providerId);
1380
+ const activeVerification = await ctx.context.internalAdapter.findVerificationValue(identifier);
1381
+ if (activeVerification && new Date(activeVerification.expiresAt) > /* @__PURE__ */ new Date()) {
1382
+ ctx.setStatus(201);
1383
+ return ctx.json({ domainVerificationToken: activeVerification.value });
1384
+ }
1385
+ const domainVerificationToken = generateRandomString(24);
1386
+ await ctx.context.internalAdapter.createVerificationValue({
1387
+ identifier,
1388
+ value: domainVerificationToken,
1389
+ expiresAt: new Date(Date.now() + 3600 * 24 * 7 * 1e3)
1390
+ });
1391
+ ctx.setStatus(201);
1392
+ return ctx.json({ domainVerificationToken });
1393
+ });
1394
+ };
1395
+ const verifyDomain = (options) => {
1396
+ return createAuthEndpoint("/sso/verify-domain", {
1397
+ method: "POST",
1398
+ body: domainVerificationBodySchema,
1399
+ metadata: { openapi: {
1400
+ summary: "Verify the provider domain ownership",
1401
+ description: "Verify the provider domain ownership via DNS records",
1402
+ responses: {
1403
+ "404": { description: "Provider not found" },
1404
+ "409": { description: "Domain has already been verified or no pending verification exists" },
1405
+ "502": { description: "Unable to verify domain ownership due to upstream validator error" },
1406
+ "204": { description: "Domain ownership was verified" }
1407
+ }
1408
+ } },
1409
+ use: [sessionMiddleware]
1410
+ }, async (ctx) => {
1411
+ const body = ctx.body;
1412
+ const provider = await checkProviderAccess(ctx, body.providerId);
1413
+ if (provider.domainVerified) throw new APIError("CONFLICT", {
1414
+ message: "Domain has already been verified",
1415
+ code: "DOMAIN_VERIFIED"
1416
+ });
1417
+ const identifier = getVerificationIdentifier(options, provider.providerId);
1418
+ if (identifier.length > DNS_LABEL_MAX_LENGTH) throw new APIError("BAD_REQUEST", {
1419
+ message: `Verification identifier exceeds the DNS label limit of ${DNS_LABEL_MAX_LENGTH} characters`,
1420
+ code: "IDENTIFIER_TOO_LONG"
1421
+ });
1422
+ const activeVerification = await ctx.context.internalAdapter.findVerificationValue(identifier);
1423
+ if (!activeVerification || new Date(activeVerification.expiresAt) <= /* @__PURE__ */ new Date()) throw new APIError("NOT_FOUND", {
1424
+ message: "No pending domain verification exists",
1425
+ code: "NO_PENDING_VERIFICATION"
1426
+ });
1427
+ let records = [];
1428
+ let dns;
1429
+ try {
1430
+ dns = await import("node:dns/promises");
1431
+ } catch (error) {
1432
+ ctx.context.logger.error("The core node:dns module is required for the domain verification feature", error);
1433
+ throw new APIError("INTERNAL_SERVER_ERROR", {
1434
+ message: "Unable to verify domain ownership due to server error",
1435
+ code: "DOMAIN_VERIFICATION_FAILED"
1436
+ });
1437
+ }
1438
+ const hostname = getHostnameFromDomain(provider.domain);
1439
+ if (!hostname) throw new APIError("BAD_REQUEST", {
1440
+ message: "Invalid domain",
1441
+ code: "INVALID_DOMAIN"
1442
+ });
1443
+ try {
1444
+ records = (await dns.resolveTxt(`${identifier}.${hostname}`)).flat();
1445
+ } catch (error) {
1446
+ ctx.context.logger.warn("DNS resolution failure while validating domain ownership", error);
1447
+ }
1448
+ if (!records.find((record) => record.includes(`${activeVerification.identifier}=${activeVerification.value}`))) throw new APIError("BAD_GATEWAY", {
1449
+ message: "Unable to verify domain ownership. Try again later",
1450
+ code: "DOMAIN_VERIFICATION_FAILED"
1451
+ });
1452
+ await ctx.context.adapter.update({
1453
+ model: "ssoProvider",
1454
+ where: [{
1455
+ field: "providerId",
1456
+ value: provider.providerId
1457
+ }],
1458
+ update: { domainVerified: true }
1459
+ });
1460
+ ctx.setStatus(204);
1461
+ });
1462
+ };
1463
+ //#endregion
1508
1464
  //#region src/saml/error-codes.ts
1509
1465
  const SAML_ERROR_CODES = defineErrorCodes({
1510
1466
  SINGLE_LOGOUT_NOT_ENABLED: "Single Logout is not enabled",
@@ -1835,26 +1791,8 @@ async function processSAMLResponse(ctx, params, options) {
1835
1791
  const conditions = extract.conditions;
1836
1792
  const clockSkew = options?.saml?.clockSkew ?? 3e5;
1837
1793
  const expiresAt = conditions?.notOnOrAfter ? new Date(conditions.notOnOrAfter).getTime() + clockSkew : Date.now() + DEFAULT_ASSERTION_TTL_MS;
1838
- const existingAssertion = await ctx.context.internalAdapter.findVerificationValue(`${USED_ASSERTION_KEY_PREFIX}${assertionId}`);
1839
- let isReplay = false;
1840
- if (existingAssertion) try {
1841
- if (JSON.parse(existingAssertion.value).expiresAt >= Date.now()) isReplay = true;
1842
- } catch (error) {
1843
- ctx.context.logger.warn("Failed to parse stored assertion record", {
1844
- assertionId,
1845
- error
1846
- });
1847
- }
1848
- if (isReplay) {
1849
- ctx.context.logger.error("SAML assertion replay detected: assertion ID already used", {
1850
- assertionId,
1851
- issuer,
1852
- providerId
1853
- });
1854
- throw ctx.redirect(`${samlRedirectUrl}?error=replay_detected&error_description=SAML+assertion+has+already+been+used`);
1855
- }
1856
- await ctx.context.internalAdapter.createVerificationValue({
1857
- identifier: `${USED_ASSERTION_KEY_PREFIX}${assertionId}`,
1794
+ if (!await ctx.context.internalAdapter.reserveVerificationValue({
1795
+ identifier: `saml-used-assertion:${assertionId}`,
1858
1796
  value: JSON.stringify({
1859
1797
  assertionId,
1860
1798
  issuer,
@@ -1863,7 +1801,14 @@ async function processSAMLResponse(ctx, params, options) {
1863
1801
  expiresAt
1864
1802
  }),
1865
1803
  expiresAt: new Date(expiresAt)
1866
- });
1804
+ })) {
1805
+ ctx.context.logger.error("SAML assertion replay detected: assertion ID already used", {
1806
+ assertionId,
1807
+ issuer,
1808
+ providerId
1809
+ });
1810
+ throw ctx.redirect(`${samlRedirectUrl}?error=replay_detected&error_description=SAML+assertion+has+already+been+used`);
1811
+ }
1867
1812
  } else ctx.context.logger.warn("Could not extract assertion ID for replay protection", { providerId });
1868
1813
  const attributes = extract.attributes || {};
1869
1814
  const mapping = parsedSamlConfig.mapping ?? {};
@@ -1876,7 +1821,7 @@ async function processSAMLResponse(ctx, params, options) {
1876
1821
  id: attr(mapping.id || "nameID") || extract.nameID,
1877
1822
  email: (attr(mapping.email || "email") || extract.nameID || "").toLowerCase(),
1878
1823
  name: [attr(mapping.firstName || "givenName"), attr(mapping.lastName || "surname")].filter(Boolean).join(" ") || attr(mapping.name || "displayName") || extract.nameID,
1879
- emailVerified: options?.trustEmailVerified && mapping.emailVerified ? attr(mapping.emailVerified) || false : false
1824
+ emailVerified: options?.trustEmailVerified && mapping.emailVerified ? parseProviderEmailVerified(attr(mapping.emailVerified)) : false
1880
1825
  };
1881
1826
  if (!userInfo.id || !userInfo.email) {
1882
1827
  ctx.context.logger.error("Missing essential user info from SAML response", {
@@ -1897,7 +1842,7 @@ async function processSAMLResponse(ctx, params, options) {
1897
1842
  email: userInfo.email,
1898
1843
  name: userInfo.name || userInfo.email,
1899
1844
  id: userInfo.id,
1900
- emailVerified: Boolean(userInfo.emailVerified)
1845
+ emailVerified: userInfo.emailVerified
1901
1846
  },
1902
1847
  account: {
1903
1848
  providerId,
@@ -1933,7 +1878,7 @@ async function processSAMLResponse(ctx, params, options) {
1933
1878
  providerId,
1934
1879
  accountId: userInfo.id,
1935
1880
  email: userInfo.email,
1936
- emailVerified: Boolean(userInfo.emailVerified),
1881
+ emailVerified: userInfo.emailVerified,
1937
1882
  rawAttributes: attributes
1938
1883
  },
1939
1884
  provider,
@@ -2777,7 +2722,7 @@ async function handleOIDCCallback(ctx, options, providerId, stateData) {
2777
2722
  ...Object.fromEntries(Object.entries(mapping.extraFields || {}).map(([key, value]) => [key, rawUserInfo[value]])),
2778
2723
  id: rawUserInfo[mapping.id || "sub"],
2779
2724
  email: rawUserInfo[mapping.email || "email"],
2780
- emailVerified: options?.trustEmailVerified ? rawUserInfo[mapping.emailVerified || "email_verified"] : false,
2725
+ emailVerified: options?.trustEmailVerified ? parseProviderEmailVerified(rawUserInfo[mapping.emailVerified || "email_verified"]) : false,
2781
2726
  name: rawUserInfo[mapping.name || "name"],
2782
2727
  image: rawUserInfo[mapping.image || "picture"]
2783
2728
  };
@@ -2796,7 +2741,7 @@ async function handleOIDCCallback(ctx, options, providerId, stateData) {
2796
2741
  ...Object.fromEntries(Object.entries(mapping.extraFields || {}).map(([key, value]) => [key, verified.payload[value]])),
2797
2742
  id: idToken[mapping.id || "sub"],
2798
2743
  email: idToken[mapping.email || "email"],
2799
- emailVerified: options?.trustEmailVerified ? idToken[mapping.emailVerified || "email_verified"] : false,
2744
+ emailVerified: options?.trustEmailVerified ? parseProviderEmailVerified(idToken[mapping.emailVerified || "email_verified"]) : false,
2800
2745
  name: idToken[mapping.name || "name"],
2801
2746
  image: idToken[mapping.image || "picture"]
2802
2747
  };
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/version.ts
3
- const PACKAGE_VERSION = "1.6.16";
3
+ const PACKAGE_VERSION = "1.6.18";
4
4
  //#endregion
5
5
  export { PACKAGE_VERSION as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/sso",
3
- "version": "1.6.16",
3
+ "version": "1.6.18",
4
4
  "description": "SSO plugin for Better Auth",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -70,15 +70,15 @@
70
70
  "express": "^5.2.1",
71
71
  "oauth2-mock-server": "^8.2.2",
72
72
  "tsdown": "0.21.1",
73
- "@better-auth/core": "1.6.16",
74
- "better-auth": "1.6.16"
73
+ "better-auth": "1.6.18",
74
+ "@better-auth/core": "1.6.18"
75
75
  },
76
76
  "peerDependencies": {
77
77
  "@better-auth/utils": "0.4.1",
78
- "@better-fetch/fetch": "1.2.2",
78
+ "@better-fetch/fetch": "1.3.0",
79
79
  "better-call": "1.3.6",
80
- "@better-auth/core": "^1.6.16",
81
- "better-auth": "^1.6.16"
80
+ "better-auth": "^1.6.18",
81
+ "@better-auth/core": "^1.6.18"
82
82
  },
83
83
  "scripts": {
84
84
  "build": "tsdown",