@better-auth/infra 0.2.6 → 0.2.8

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/index.mjs CHANGED
@@ -1,10 +1,11 @@
1
- import { n as INFRA_KV_URL, r as KV_TIMEOUT_MS, t as INFRA_API_URL } from "./constants-DdWGfvz1.mjs";
1
+ import { n as INFRA_API_URL, r as INFRA_KV_URL } from "./constants-CvriWQVc.mjs";
2
+ import { n as createKV, t as createAPI } from "./fetch-DiAhoiKA.mjs";
2
3
  import { EMAIL_TEMPLATES, createEmailSender, sendBulkEmails, sendEmail } from "./email.mjs";
3
4
  import { APIError, generateId, getAuthTables, logger, parseState } from "better-auth";
4
5
  import { env } from "@better-auth/core/env";
5
6
  import { APIError as APIError$1, createAuthEndpoint, createAuthMiddleware, requestPasswordReset, sendVerificationEmailFn, sessionMiddleware } from "better-auth/api";
6
7
  import { getCurrentAuthContext } from "@better-auth/core/context";
7
- import { betterFetch, createFetch } from "@better-fetch/fetch";
8
+ import { createFetch } from "@better-fetch/fetch";
8
9
  import { isValidPhoneNumber, parsePhoneNumberFromString } from "libphonenumber-js";
9
10
  import { createLocalJWKSet, jwtVerify } from "jose";
10
11
  import z$1, { z } from "zod";
@@ -14,7 +15,9 @@ function resolveConnectionOptions(options) {
14
15
  return {
15
16
  apiUrl: options?.apiUrl || INFRA_API_URL,
16
17
  kvUrl: options?.kvUrl || INFRA_KV_URL,
17
- apiKey: options?.apiKey || env.BETTER_AUTH_API_KEY || ""
18
+ apiKey: options?.apiKey || env.BETTER_AUTH_API_KEY || "",
19
+ apiTimeout: options?.apiTimeout ?? 3e3,
20
+ kvTimeout: options?.kvTimeout ?? 1e3
18
21
  };
19
22
  }
20
23
  function resolveDashOptions(options) {
@@ -96,6 +99,8 @@ const routes = {
96
99
  EMAIL_OTP_SEND: "/email-otp/send-verification-otp",
97
100
  PHONE_SEND_OTP: "/phone-number/send-otp",
98
101
  PHONE_VERIFY_OTP: "/phone-number/verify-phone-number",
102
+ DEVICE_TOKEN: "/device/token",
103
+ SIWE_VERIFY: "/siwe/verify",
99
104
  ORG_CREATE: "/organization/create",
100
105
  ORG_INVITE_MEMBER: "/organization/invite-member",
101
106
  API_KEY_CREATE: "/api-key/create",
@@ -109,7 +114,8 @@ const routes = {
109
114
  ADMIN_REVOKE_USER_SESSIONS: "/admin/revoke-user-sessions",
110
115
  ADMIN_SET_PASSWORD: "/admin/set-user-password",
111
116
  ADMIN_BAN_USER: "/admin/ban-user",
112
- ADMIN_UNBAN_USER: "/admin/unban-user"
117
+ ADMIN_UNBAN_USER: "/admin/unban-user",
118
+ ADMIN_IMPERSONATE_USER: "/admin/impersonate-user"
113
119
  };
114
120
  const ORGANIZATION_EVENT_TYPES = {
115
121
  ORGANIZATION_CREATED: "organization_created",
@@ -308,16 +314,33 @@ const matchesAnyRoute = (path, routes) => {
308
314
  };
309
315
  //#endregion
310
316
  //#region src/events/login-methods.ts
311
- const loginPaths = [
312
- routes.SIGN_IN_SOCIAL_CALLBACK,
313
- routes.SIGN_IN_OAUTH_CALLBACK,
314
- routes.SIGN_IN_EMAIL,
315
- routes.SIGN_IN_SOCIAL,
316
- routes.SIGN_IN_EMAIL_OTP,
317
- routes.SIGN_UP_EMAIL
317
+ const OAUTH_CALLBACK_PATHS = [routes.SIGN_IN_SOCIAL_CALLBACK, routes.SIGN_IN_OAUTH_CALLBACK];
318
+ const PATH_TO_METHOD = [
319
+ [routes.SIGN_IN_EMAIL, "email"],
320
+ [routes.SIGN_UP_EMAIL, "email"],
321
+ [routes.SIGN_IN_USERNAME, "username"],
322
+ [routes.SIGN_IN_EMAIL_OTP, "email-otp"],
323
+ [routes.SIGN_IN_SOCIAL, "social"],
324
+ [routes.SIGN_IN_ANONYMOUS, "anonymous"],
325
+ [routes.SIGN_IN_PASSKEY, "passkey"],
326
+ [routes.SIGN_IN_MAGIC_LINK, "magic-link"],
327
+ [routes.MAGIC_LINK_VERIFY, "magic-link"],
328
+ [routes.SIGN_IN_SSO, "sso"],
329
+ [routes.PHONE_VERIFY_OTP, "phone"],
330
+ [routes.TWO_FACTOR_VERIFY_TOTP, "totp"],
331
+ [routes.TWO_FACTOR_VERIFY_OTP, "two-factor-otp"],
332
+ [routes.TWO_FACTOR_VERIFY_BACKUP, "backup-code"],
333
+ [routes.ADMIN_IMPERSONATE_USER, "impersonation"],
334
+ [routes.DEVICE_TOKEN, "device-code"],
335
+ [routes.SIWE_VERIFY, "siwe"]
318
336
  ];
319
337
  const getLoginMethod = (ctx) => {
320
- if (matchesAnyRoute(ctx.path, loginPaths)) return ctx.params?.id ? ctx.params.id : ctx.path.split("/").pop();
338
+ if (matchesAnyRoute(ctx.path, OAUTH_CALLBACK_PATHS)) {
339
+ const id = ctx.params?.id;
340
+ if (typeof id === "string" && id && !id.startsWith(":")) return id;
341
+ return null;
342
+ }
343
+ for (const [path, method] of PATH_TO_METHOD) if (matchesAnyRoute(ctx.path, [path])) return method;
321
344
  return null;
322
345
  };
323
346
  //#endregion
@@ -512,7 +535,7 @@ const initSessionEvents = (tracker) => {
512
535
  eventData: {
513
536
  userId: session.userId,
514
537
  userName: user.name,
515
- userEmail: user.email,
538
+ userEmail: user.email ?? "unknown",
516
539
  sessionId: session.id,
517
540
  triggeredBy: trigger.triggeredBy,
518
541
  triggerContext: trigger.triggerContext
@@ -598,10 +621,10 @@ const initUserEvents = (tracker) => {
598
621
  trackEvent({
599
622
  eventKey: user.id,
600
623
  eventType: EVENT_TYPES.USER_CREATED,
601
- eventDisplayName: `${user.name || user.email} signed up`,
624
+ eventDisplayName: `${user.name || user.email || "unknown"} signed up`,
602
625
  eventData: {
603
626
  userId: user.id,
604
- userEmail: user.email,
627
+ userEmail: user.email ?? "unknown",
605
628
  userName: user.name,
606
629
  triggeredBy: trigger.triggeredBy,
607
630
  triggerContext: trigger.triggerContext
@@ -619,7 +642,7 @@ const initUserEvents = (tracker) => {
619
642
  eventDisplayName: "User deleted",
620
643
  eventData: {
621
644
  userId: user.id,
622
- userEmail: user.email,
645
+ userEmail: user.email ?? "unknown",
623
646
  userName: user.name,
624
647
  triggeredBy: trigger.triggeredBy,
625
648
  triggerContext: trigger.triggerContext
@@ -637,7 +660,7 @@ const initUserEvents = (tracker) => {
637
660
  eventDisplayName: "Profile updated",
638
661
  eventData: {
639
662
  userId: user.id,
640
- userEmail: user.email,
663
+ userEmail: user.email ?? "unknown",
641
664
  userName: user.name,
642
665
  updatedFields: Object.keys(ctx?.body || {}),
643
666
  triggeredBy: trigger.triggeredBy,
@@ -656,7 +679,7 @@ const initUserEvents = (tracker) => {
656
679
  eventDisplayName: "Profile image updated",
657
680
  eventData: {
658
681
  userId: user.id,
659
- userEmail: user.email,
682
+ userEmail: user.email ?? "unknown",
660
683
  userName: user.name,
661
684
  triggeredBy: trigger.triggeredBy,
662
685
  triggerContext: trigger.triggerContext
@@ -676,7 +699,7 @@ const initUserEvents = (tracker) => {
676
699
  eventDisplayName: `User banned${reasonSuffix}${expiresSuffix}`,
677
700
  eventData: {
678
701
  userId: user.id,
679
- userEmail: user.email,
702
+ userEmail: user.email ?? "unknown",
680
703
  userName: user.name,
681
704
  banned: user.banned,
682
705
  banReason: user.banReason,
@@ -697,7 +720,7 @@ const initUserEvents = (tracker) => {
697
720
  eventDisplayName: "User unbanned",
698
721
  eventData: {
699
722
  userId: user.id,
700
- userEmail: user.email,
723
+ userEmail: user.email ?? "unknown",
701
724
  userName: user.name,
702
725
  banned: user.banned,
703
726
  triggeredBy: trigger.triggeredBy,
@@ -716,7 +739,7 @@ const initUserEvents = (tracker) => {
716
739
  eventDisplayName: "Email verified",
717
740
  eventData: {
718
741
  userId: user.id,
719
- userEmail: user.email,
742
+ userEmail: user.email ?? "unknown",
720
743
  userName: user.name,
721
744
  triggeredBy: trigger.triggeredBy,
722
745
  triggerContext: trigger.triggerContext
@@ -812,18 +835,11 @@ const getOrganizationTriggerInfo = (user) => {
812
835
  };
813
836
  //#endregion
814
837
  //#region src/events/index.ts
815
- const initTrackEvents = (options) => {
816
- const $fetch = createFetch({
817
- baseURL: options.apiUrl,
818
- headers: {
819
- "user-agent": "better-auth",
820
- "x-api-key": options.apiKey
821
- }
822
- });
838
+ const initTrackEvents = ($api) => {
823
839
  const trackEvent = (data) => {
824
840
  const track = async () => {
825
841
  try {
826
- await $fetch("/events/track", {
842
+ await $api("/events/track", {
827
843
  method: "POST",
828
844
  body: {
829
845
  eventType: data.eventType,
@@ -874,11 +890,10 @@ function maybeCleanup() {
874
890
  /**
875
891
  * Fetch identification data from durable-kv by requestId
876
892
  */
877
- async function getIdentification(requestId, apiKey, kvUrl) {
893
+ async function getIdentification(requestId, $kv) {
878
894
  maybeCleanup();
879
895
  const cached = identificationCache.get(requestId);
880
896
  if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) return cached.data;
881
- const baseUrl = kvUrl || INFRA_KV_URL;
882
897
  const maxRetries = 3;
883
898
  const retryDelays = [
884
899
  50,
@@ -886,24 +901,20 @@ async function getIdentification(requestId, apiKey, kvUrl) {
886
901
  200
887
902
  ];
888
903
  for (let attempt = 0; attempt <= maxRetries; attempt++) try {
889
- const response = await fetch(`${baseUrl}/identify/${requestId}`, {
890
- method: "GET",
891
- headers: { "x-api-key": apiKey },
892
- signal: AbortSignal.timeout(KV_TIMEOUT_MS)
893
- });
894
- if (response.ok) {
895
- const data = await response.json();
904
+ const { data, error } = await $kv(`/identify/${requestId}`, { method: "GET" });
905
+ if (data && !error) {
896
906
  identificationCache.set(requestId, {
897
907
  data,
898
908
  timestamp: Date.now()
899
909
  });
900
910
  return data;
901
911
  }
902
- if (response.status === 404 && attempt < maxRetries) {
912
+ const status = error?.status;
913
+ if (status === 404 && attempt < maxRetries) {
903
914
  await new Promise((resolve) => setTimeout(resolve, retryDelays[attempt]));
904
915
  continue;
905
916
  }
906
- if (response.status !== 404) identificationCache.set(requestId, {
917
+ if (status !== 404) identificationCache.set(requestId, {
907
918
  data: null,
908
919
  timestamp: Date.now()
909
920
  });
@@ -931,16 +942,19 @@ function extractIdentificationHeaders(request) {
931
942
  };
932
943
  }
933
944
  /**
934
- * Early middleware that loads identification data
945
+ * Early middleware that loads identification data.
946
+ *
947
+ * @param $kv — KV client from {@link createKV} for the same plugin options (one instance per plugin).
935
948
  */
936
- function createIdentificationMiddleware(options) {
949
+ function createIdentificationMiddleware($kv) {
937
950
  return createAuthMiddleware(async (ctx) => {
938
951
  const { visitorId, requestId: headerRequestId } = extractIdentificationHeaders(ctx.request);
939
952
  const requestId = headerRequestId ?? ctx.getCookie("__infra-rid") ?? null;
940
953
  ctx.context.visitorId = visitorId;
941
954
  ctx.context.requestId = requestId;
942
- if (requestId) ctx.context.identification = ctx.context.identification ?? await getIdentification(requestId, options.apiKey, options.kvUrl) ?? null;
943
- else ctx.context.identification = null;
955
+ if (requestId) {
956
+ if (ctx.context.identification === void 0) ctx.context.identification = await getIdentification(requestId, $kv) ?? null;
957
+ } else ctx.context.identification = null;
944
958
  const ipConfig = ctx.context.options?.advanced?.ipAddress;
945
959
  if (ipConfig?.disableIpTracking === true) {
946
960
  ctx.context.location = void 0;
@@ -1064,23 +1078,19 @@ async function sha1Hash(input) {
1064
1078
  /**
1065
1079
  * Whether Sentinel should normalize emails for deduplication/sign-in consistency.
1066
1080
  * If `emailNormalization` is set, it wins; otherwise legacy behavior uses `emailValidation.enabled`.
1067
- * @internal Not part of the package public API; used by the sentinel plugin and email helpers.
1068
1081
  */
1069
1082
  function isEmailNormalizationEnabled(security) {
1070
1083
  const explicit = security?.emailNormalization;
1071
1084
  if (explicit !== void 0) return explicit.enabled !== false;
1072
1085
  return security?.emailValidation?.enabled !== false;
1073
1086
  }
1074
- function createSecurityClient(apiUrl, apiKey, options, onSecurityEvent) {
1075
- const resolvedApiUrl = apiUrl || INFRA_API_URL;
1076
- const $api = createFetch({
1077
- baseURL: resolvedApiUrl,
1078
- headers: { "x-api-key": apiKey },
1079
- throw: true
1080
- });
1087
+ /**
1088
+ * @param $api Dash client from `createAPI(opts, { throw: true })`.
1089
+ */
1090
+ function createSecurityClient(conn, $api, options, onSecurityEvent) {
1081
1091
  const emailSender = createEmailSender({
1082
- apiUrl: resolvedApiUrl,
1083
- apiKey
1092
+ apiUrl: conn.apiUrl || INFRA_API_URL,
1093
+ apiKey: conn.apiKey
1084
1094
  });
1085
1095
  function logEvent(event) {
1086
1096
  const fullEvent = {
@@ -1485,7 +1495,9 @@ const all = new Set([
1485
1495
  "/forget-password",
1486
1496
  "/request-password-reset",
1487
1497
  "/send-verification-email",
1488
- "/change-email"
1498
+ "/change-email",
1499
+ "/organization/invite-member",
1500
+ "/dash/organization/invite-member"
1489
1501
  ]);
1490
1502
  /**
1491
1503
  * Path is one of `[
@@ -1502,7 +1514,9 @@ const all = new Set([
1502
1514
  * '/forget-password',
1503
1515
  * '/request-password-reset',
1504
1516
  * '/send-verification-email',
1505
- * '/change-email'
1517
+ * '/change-email',
1518
+ * '/organization/invite-member',
1519
+ * '/dash/organization/invite-member'
1506
1520
  * ]`.
1507
1521
  * @param context Request context
1508
1522
  * @param context.path Request path
@@ -1560,17 +1574,11 @@ function normalizeEmail(email, context) {
1560
1574
  if (GMAIL_LIKE_DOMAINS.has(domain)) localPart = localPart.replace(/\./g, "");
1561
1575
  return `${localPart}@${domain}`;
1562
1576
  }
1563
- function createEmailValidator(options = {}) {
1564
- const { apiKey = "", apiUrl = INFRA_API_URL, kvUrl = INFRA_KV_URL, emailValidationOptions = {} } = options;
1565
- const $api = createFetch({
1566
- baseURL: apiUrl,
1567
- headers: { "x-api-key": apiKey }
1568
- });
1569
- const $kv = createFetch({
1570
- baseURL: kvUrl,
1571
- headers: { "x-api-key": apiKey },
1572
- timeout: KV_TIMEOUT_MS
1573
- });
1577
+ function hasRemoteEmailClients(options) {
1578
+ return options.$api !== void 0 && options.$kv !== void 0;
1579
+ }
1580
+ function createEmailValidator(options) {
1581
+ const { $api, $kv, emailValidationOptions = {} } = options;
1574
1582
  /**
1575
1583
  * Fetch and resolve email validity policy from API with caching
1576
1584
  * Sends client config to API which merges with user's dashboard settings
@@ -1744,18 +1752,10 @@ function createEmailValidationHook(validator, trackEvent) {
1744
1752
  *
1745
1753
  * @example
1746
1754
  * // API-backed validation
1747
- * createEmailHooks({ useApi: true, apiKey: "your-api-key" })
1748
- *
1749
- * @example
1750
- * // High strictness + API
1751
- * createEmailHooks({
1752
- * emailValidationOptions: { strictness: "high" },
1753
- * useApi: true,
1754
- * apiKey: "your-api-key",
1755
- * })
1755
+ * createEmailHooks({ $api, $kv })
1756
1756
  */
1757
1757
  function createEmailHooks(options = {}) {
1758
- const { emailValidationOptions, emailNormalizationOptions, useApi = false, apiKey = "", apiUrl = INFRA_API_URL, kvUrl = INFRA_KV_URL, trackEvent } = options;
1758
+ const { emailValidationOptions, emailNormalizationOptions, trackEvent } = options;
1759
1759
  const emailConfig = {
1760
1760
  enabled: true,
1761
1761
  strictness: "medium",
@@ -1765,10 +1765,9 @@ function createEmailHooks(options = {}) {
1765
1765
  return { before: [...isEmailNormalizationEnabled({
1766
1766
  emailValidation: emailValidationOptions,
1767
1767
  emailNormalization: emailNormalizationOptions
1768
- }) ? [createEmailNormalizationHook()] : [], ...emailConfig.enabled ? [createEmailValidationHook(useApi ? createEmailValidator({
1769
- apiUrl,
1770
- kvUrl,
1771
- apiKey,
1768
+ }) ? [createEmailNormalizationHook()] : [], ...emailConfig.enabled ? [createEmailValidationHook(hasRemoteEmailClients(options) ? createEmailValidator({
1769
+ $api: options.$api,
1770
+ $kv: options.$kv,
1772
1771
  emailValidationOptions: emailConfig
1773
1772
  }) : void 0, trackEvent)] : []] };
1774
1773
  }
@@ -2050,10 +2049,13 @@ async function runSecurityChecks(hookCtx, checkCtx, securityService, trackEvent,
2050
2049
  //#region src/sentinel/sentinel.ts
2051
2050
  const sentinel = (options) => {
2052
2051
  const opts = resolveSentinelOptions(options);
2053
- const { tracker } = initTrackEvents(opts);
2052
+ const $api = createAPI(opts);
2053
+ const $apiThrowing = createAPI(opts, { throw: true });
2054
+ const $kv = createKV(opts);
2055
+ const { tracker } = initTrackEvents($api);
2054
2056
  const { trackEvent } = tracker;
2055
2057
  if (!opts.apiKey) logger.warn("[Sentinel] Missing BETTER_AUTH_API_KEY. Security checks may fall back to allow mode when the Infra API rejects requests.");
2056
- const securityService = createSecurityClient(opts.apiUrl, opts.apiKey, opts.security || {}, (event) => {
2058
+ const securityService = createSecurityClient(opts, $apiThrowing, opts.security || {}, (event) => {
2057
2059
  trackEvent({
2058
2060
  eventKey: event.visitorId || event.userId || "unknown",
2059
2061
  eventType: "security_signal",
@@ -2072,11 +2074,11 @@ const sentinel = (options) => {
2072
2074
  const emailHooks = createEmailHooks({
2073
2075
  emailValidationOptions: opts.security?.emailValidation,
2074
2076
  emailNormalizationOptions: opts.security?.emailNormalization,
2075
- useApi: !!opts.apiKey,
2076
- apiKey: opts.apiKey,
2077
- apiUrl: opts.apiUrl,
2078
- kvUrl: opts.kvUrl,
2079
- trackEvent
2077
+ trackEvent,
2078
+ ...opts.apiKey ? {
2079
+ $api,
2080
+ $kv
2081
+ } : {}
2080
2082
  });
2081
2083
  return {
2082
2084
  id: "sentinel",
@@ -2209,7 +2211,7 @@ const sentinel = (options) => {
2209
2211
  before: [
2210
2212
  {
2211
2213
  matcher: (ctx) => ctx.request?.method !== "GET",
2212
- handler: createIdentificationMiddleware(opts)
2214
+ handler: createIdentificationMiddleware($kv)
2213
2215
  },
2214
2216
  {
2215
2217
  matcher: (ctx) => ctx.request?.method !== "GET",
@@ -2354,7 +2356,7 @@ const initInvitationEvents = (tracker) => {
2354
2356
  inviteeTeamId: invitation.teamId,
2355
2357
  inviterId: inviter.id,
2356
2358
  inviterName: inviter.name,
2357
- inviterEmail: inviter.email,
2359
+ inviterEmail: inviter.email ?? "unknown",
2358
2360
  triggeredBy: trigger.triggeredBy,
2359
2361
  triggerContext: trigger.triggerContext
2360
2362
  }
@@ -2374,7 +2376,7 @@ const initInvitationEvents = (tracker) => {
2374
2376
  inviteeRole: invitation.role,
2375
2377
  inviteeTeamId: invitation.teamId,
2376
2378
  acceptedById: acceptedBy.id,
2377
- acceptedByEmail: acceptedBy.email,
2379
+ acceptedByEmail: acceptedBy.email ?? "unknown",
2378
2380
  acceptedByName: acceptedBy.name,
2379
2381
  memberId: member.id,
2380
2382
  memberRole: member.role,
@@ -2397,7 +2399,7 @@ const initInvitationEvents = (tracker) => {
2397
2399
  inviteeRole: invitation.role,
2398
2400
  inviteeTeamId: invitation.teamId,
2399
2401
  rejectedById: rejectedBy.id,
2400
- rejectedByEmail: rejectedBy.email,
2402
+ rejectedByEmail: rejectedBy.email ?? "unknown",
2401
2403
  rejectedByName: rejectedBy.name,
2402
2404
  triggeredBy: trigger.triggeredBy,
2403
2405
  triggerContext: trigger.triggerContext
@@ -2419,7 +2421,7 @@ const initInvitationEvents = (tracker) => {
2419
2421
  inviteeTeamId: invitation.teamId,
2420
2422
  cancelledById: cancelledBy.id,
2421
2423
  cancelledByName: cancelledBy.name,
2422
- cancelledByEmail: cancelledBy.email,
2424
+ cancelledByEmail: cancelledBy.email ?? "unknown",
2423
2425
  triggeredBy: trigger.triggeredBy,
2424
2426
  triggerContext: trigger.triggerContext
2425
2427
  }
@@ -2449,7 +2451,7 @@ const initMemberEvents = (tracker) => {
2449
2451
  memberName: user.name,
2450
2452
  role: member.role,
2451
2453
  memberId: member.id,
2452
- memberEmail: user.email,
2454
+ memberEmail: user.email ?? "unknown",
2453
2455
  triggeredBy: trigger.triggeredBy,
2454
2456
  triggerContext: trigger.triggerContext
2455
2457
  }
@@ -2468,7 +2470,7 @@ const initMemberEvents = (tracker) => {
2468
2470
  memberName: user.name,
2469
2471
  role: member.role,
2470
2472
  memberId: member.id,
2471
- memberEmail: user.email,
2473
+ memberEmail: user.email ?? "unknown",
2472
2474
  triggeredBy: trigger.triggeredBy,
2473
2475
  triggerContext: trigger.triggerContext
2474
2476
  }
@@ -2488,7 +2490,7 @@ const initMemberEvents = (tracker) => {
2488
2490
  newRole: member.role,
2489
2491
  oldRole: previousRole,
2490
2492
  memberId: member.id,
2491
- memberEmail: user.email,
2493
+ memberEmail: user.email ?? "unknown",
2492
2494
  triggeredBy: trigger.triggeredBy,
2493
2495
  triggerContext: trigger.triggerContext
2494
2496
  }
@@ -2655,32 +2657,32 @@ const JTI_CHECK_GRACE_PERIOD_SECONDS = 30;
2655
2657
  const JWKS_CACHE_TTL_MS = 900 * 1e3;
2656
2658
  const jwksCache = /* @__PURE__ */ new Map();
2657
2659
  const inflightRequests = /* @__PURE__ */ new Map();
2658
- async function fetchJWKS(ctx, apiUrl) {
2659
- const { data, error } = await betterFetch(`${apiUrl}/api/auth/jwks`);
2660
+ async function fetchJWKS(ctx, cacheKey, $api) {
2661
+ const { data, error } = await $api("/api/auth/jwks");
2660
2662
  if (error || !data) {
2661
2663
  ctx.context.logger.warn("[Dash] Failed to fetch JWKS", error);
2662
- throw new APIError$1("UNAUTHORIZED", { message: "Invalid API key" });
2664
+ throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2663
2665
  }
2664
- jwksCache.set(apiUrl, {
2666
+ jwksCache.set(cacheKey, {
2665
2667
  data,
2666
2668
  expiresAt: Date.now() + JWKS_CACHE_TTL_MS
2667
2669
  });
2668
2670
  return createLocalJWKSet(data);
2669
2671
  }
2670
- async function prefetchJWKS(ctx, apiUrl) {
2671
- const fetchPromise = fetchJWKS(ctx, apiUrl);
2672
- inflightRequests.set(apiUrl, fetchPromise);
2673
- fetchPromise.finally(() => inflightRequests.delete(apiUrl));
2672
+ async function prefetchJWKS(ctx, cacheKey, $api) {
2673
+ const fetchPromise = fetchJWKS(ctx, cacheKey, $api);
2674
+ inflightRequests.set(cacheKey, fetchPromise);
2675
+ fetchPromise.finally(() => inflightRequests.delete(cacheKey));
2674
2676
  return fetchPromise;
2675
2677
  }
2676
- async function getJWKs(ctx, apiUrl) {
2677
- const cached = jwksCache.get(apiUrl);
2678
+ async function getJWKs(ctx, cacheKey, $api) {
2679
+ const cached = jwksCache.get(cacheKey);
2678
2680
  if (cached && Date.now() < cached.expiresAt) return createLocalJWKSet(cached.data);
2679
2681
  if (cached) {
2680
- if (!inflightRequests.has(apiUrl)) prefetchJWKS(ctx, apiUrl);
2682
+ if (!inflightRequests.has(cacheKey)) prefetchJWKS(ctx, cacheKey, $api);
2681
2683
  return createLocalJWKSet(cached.data);
2682
2684
  }
2683
- return inflightRequests.get(apiUrl) ?? prefetchJWKS(ctx, apiUrl);
2685
+ return inflightRequests.get(cacheKey) ?? prefetchJWKS(ctx, cacheKey, $api);
2684
2686
  }
2685
2687
  /**
2686
2688
  * Check if JWT is recently issued and can skip JTI verification.
@@ -2692,86 +2694,92 @@ function isRecentlyIssued(payload) {
2692
2694
  const issuedAt = payload.iat * 1e3;
2693
2695
  return Date.now() - issuedAt < JTI_CHECK_GRACE_PERIOD_SECONDS * 1e3;
2694
2696
  }
2695
- const jwtMiddleware = (options, schema, getJWT) => createAuthMiddleware(async (ctx) => {
2696
- const jwsFromHeader = getJWT ? await getJWT(ctx) : ctx.headers?.get("Authorization")?.split(" ")[1];
2697
- if (!jwsFromHeader) {
2698
- ctx.context.logger.warn("[Dash] JWT is missing from header");
2699
- throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2700
- }
2701
- const { payload } = await jwtVerify(jwsFromHeader, await getJWKs(ctx, options.apiUrl), { maxTokenAge: "5m" }).catch((e) => {
2702
- ctx.context.logger.warn("[Dash] JWT verification failed:", e);
2703
- throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2704
- });
2705
- if (!isRecentlyIssued(payload)) {
2706
- const { error, data } = await betterFetch("/api/auth/check-jti", {
2707
- baseURL: options.apiUrl,
2708
- method: "POST",
2709
- headers: { "x-api-key": options.apiKey },
2710
- body: {
2711
- jti: payload.jti,
2712
- expiresAt: payload.exp
2713
- }
2714
- });
2715
- if (error || !data?.valid) {
2716
- ctx.context.logger.warn("[Dash] JTI check failed with error", error, data?.valid);
2697
+ const jwtMiddleware = (options, schema, getJWT) => {
2698
+ const { $api } = options;
2699
+ const cacheKey = options.apiUrl;
2700
+ return createAuthMiddleware(async (ctx) => {
2701
+ const jwsFromHeader = getJWT ? await getJWT(ctx) : ctx.headers?.get("Authorization")?.split(" ")[1];
2702
+ if (!jwsFromHeader) {
2703
+ ctx.context.logger.warn("[Dash] JWT is missing from header");
2717
2704
  throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2718
2705
  }
2719
- }
2720
- const apiKeyHash = payload.apiKeyHash;
2721
- if (typeof apiKeyHash !== "string" || !options.apiKey) {
2722
- ctx.context.logger.warn("[Dash] API key hash is missing or invalid", {
2723
- apiKeyHash,
2724
- apiKey: options.apiKey ? "present" : "missing"
2706
+ const { payload } = await jwtVerify(jwsFromHeader, await getJWKs(ctx, cacheKey, $api), { maxTokenAge: "5m" }).catch((e) => {
2707
+ ctx.context.logger.warn("[Dash] JWT verification failed:", e);
2708
+ throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2725
2709
  });
2726
- throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2727
- }
2728
- const expectedHash = await hash(options.apiKey);
2729
- if (apiKeyHash !== expectedHash) {
2730
- ctx.context.logger.warn("[Dash] API key hash is invalid", apiKeyHash, expectedHash);
2731
- throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2732
- }
2733
- if (schema) {
2734
- const parsed = schema.safeParse(payload);
2735
- if (!parsed.success) {
2736
- ctx.context.logger.warn("[Dash] JWT payload is invalid", parsed.error);
2710
+ const apiKeyHash = payload.apiKeyHash;
2711
+ if (typeof apiKeyHash !== "string" || !options.apiKey) {
2712
+ ctx.context.logger.warn("[Dash] API key hash is missing or invalid", {
2713
+ apiKeyHash,
2714
+ apiKey: options.apiKey ? "present" : "missing"
2715
+ });
2737
2716
  throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2738
2717
  }
2739
- return { payload: parsed.data };
2740
- }
2741
- return { payload };
2742
- });
2718
+ const expectedHash = await hash(options.apiKey);
2719
+ if (apiKeyHash !== expectedHash) {
2720
+ ctx.context.logger.warn("[Dash] API key hash is invalid", apiKeyHash, expectedHash);
2721
+ throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2722
+ }
2723
+ if (!isRecentlyIssued(payload)) {
2724
+ const { error, data } = await $api("/api/auth/check-jti", {
2725
+ method: "POST",
2726
+ body: {
2727
+ jti: payload.jti,
2728
+ expiresAt: payload.exp
2729
+ }
2730
+ });
2731
+ if (error || !data?.valid) {
2732
+ ctx.context.logger.warn("[Dash] JTI check failed with error", error, data?.valid);
2733
+ throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2734
+ }
2735
+ }
2736
+ if (schema) {
2737
+ const parsed = schema.safeParse(payload);
2738
+ if (!parsed.success) {
2739
+ ctx.context.logger.warn("[Dash] JWT payload is invalid", parsed.error);
2740
+ throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2741
+ }
2742
+ return { payload: parsed.data };
2743
+ }
2744
+ return { payload };
2745
+ });
2746
+ };
2743
2747
  /**
2744
2748
  * Lightweight JWT middleware for /dash/validate. Verifies JWT signature and
2745
2749
  * apiKeyHash only—no JTI check. Used during onboarding when the org doesn't
2746
2750
  * exist yet.
2747
2751
  */
2748
- const jwtValidateMiddleware = (options) => createAuthMiddleware(async (ctx) => {
2749
- const jwsFromHeader = ctx.headers?.get("Authorization")?.split(" ")[1];
2750
- if (!jwsFromHeader) {
2751
- ctx.context.logger.warn("[Dash] JWT is missing from header");
2752
- throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2753
- }
2754
- const { payload } = await jwtVerify(jwsFromHeader, await getJWKs(ctx, options.apiUrl), { maxTokenAge: "5m" }).catch((e) => {
2755
- ctx.context.logger.error("[Dash] JWT verification failed:", e);
2756
- throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2757
- });
2758
- const apiKeyHash = payload.apiKeyHash;
2759
- if (typeof apiKeyHash !== "string" || !options.apiKey) {
2760
- ctx.context.logger.warn("[Dash] API key hash is missing or invalid", {
2761
- apiKeyHash: typeof apiKeyHash,
2762
- apiKey: options.apiKey ? "present" : "missing"
2752
+ const jwtValidateMiddleware = (options) => {
2753
+ const { $api } = options;
2754
+ const cacheKey = options.apiUrl;
2755
+ return createAuthMiddleware(async (ctx) => {
2756
+ const jwsFromHeader = ctx.headers?.get("Authorization")?.split(" ")[1];
2757
+ if (!jwsFromHeader) {
2758
+ ctx.context.logger.warn("[Dash] JWT is missing from header");
2759
+ throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2760
+ }
2761
+ const { payload } = await jwtVerify(jwsFromHeader, await getJWKs(ctx, cacheKey, $api), { maxTokenAge: "5m" }).catch((e) => {
2762
+ ctx.context.logger.error("[Dash] JWT verification failed:", e);
2763
+ throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2763
2764
  });
2764
- throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2765
- }
2766
- if (apiKeyHash !== await hash(options.apiKey)) {
2767
- ctx.context.logger.warn("[Dash] API key hash mismatch");
2768
- throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2769
- }
2770
- return { payload };
2771
- });
2765
+ const apiKeyHash = payload.apiKeyHash;
2766
+ if (typeof apiKeyHash !== "string" || !options.apiKey) {
2767
+ ctx.context.logger.warn("[Dash] API key hash is missing or invalid", {
2768
+ apiKeyHash: typeof apiKeyHash,
2769
+ apiKey: options.apiKey ? "present" : "missing"
2770
+ });
2771
+ throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2772
+ }
2773
+ if (apiKeyHash !== await hash(options.apiKey)) {
2774
+ ctx.context.logger.warn("[Dash] API key hash mismatch");
2775
+ throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
2776
+ }
2777
+ return { payload };
2778
+ });
2779
+ };
2772
2780
  //#endregion
2773
2781
  //#region src/version.ts
2774
- const PLUGIN_VERSION = "0.2.6";
2782
+ const PLUGIN_VERSION = "0.2.8";
2775
2783
  //#endregion
2776
2784
  //#region src/routes/auth/config.ts
2777
2785
  const PLUGIN_OPTIONS_EXCLUDE_KEYS = { stripe: new Set(["stripeClient"]) };
@@ -3257,10 +3265,7 @@ function transformEvent(raw) {
3257
3265
  * ```
3258
3266
  */
3259
3267
  const getUserEvents = (options) => {
3260
- const $fetch = createFetch({
3261
- baseURL: options.apiUrl,
3262
- headers: { "x-api-key": options.apiKey }
3263
- });
3268
+ const { $api } = options;
3264
3269
  return createAuthEndpoint("/events/list", {
3265
3270
  method: "GET",
3266
3271
  use: [sessionMiddleware],
@@ -3279,7 +3284,7 @@ const getUserEvents = (options) => {
3279
3284
  const requestedLimit = ctx.query?.limit ?? 50;
3280
3285
  const limit = Math.min(Math.max(1, requestedLimit), 100);
3281
3286
  const offset = Math.max(0, ctx.query?.offset ?? 0);
3282
- const { data, error } = await $fetch("/events/user", {
3287
+ const { data, error } = await $api("/events/user", {
3283
3288
  method: "GET",
3284
3289
  query: {
3285
3290
  userId: session.user.id,
@@ -3329,10 +3334,7 @@ const getEventTypes = (options) => {
3329
3334
  * results to the resolved user/identifier.
3330
3335
  */
3331
3336
  const getAuditLogs = (options) => {
3332
- const $fetch = createFetch({
3333
- baseURL: options.apiUrl,
3334
- headers: { "x-api-key": options.apiKey }
3335
- });
3337
+ const { $api } = options;
3336
3338
  return createAuthEndpoint("/events/audit-logs", {
3337
3339
  method: "GET",
3338
3340
  use: [sessionMiddleware],
@@ -3364,7 +3366,7 @@ const getAuditLogs = (options) => {
3364
3366
  if (!(sessionData.session?.activeOrganizationId === organizationId) && (!Array.isArray(sessionOrganizations) || !sessionOrganizations.some((org) => {
3365
3367
  return org.id === organizationId;
3366
3368
  }))) throw ctx.error("FORBIDDEN", { message: "Not allowed to access this organization" });
3367
- const { data, error } = await $fetch("/events/organization", {
3369
+ const { data, error } = await $api("/events/organization", {
3368
3370
  method: "GET",
3369
3371
  query: {
3370
3372
  organizationId,
@@ -3381,7 +3383,7 @@ const getAuditLogs = (options) => {
3381
3383
  responseLimit = data.limit;
3382
3384
  responseOffset = data.offset;
3383
3385
  } else {
3384
- const { data, error } = await $fetch("/events/user", {
3386
+ const { data, error } = await $api("/events/user", {
3385
3387
  method: "GET",
3386
3388
  query: {
3387
3389
  userId: resolvedUserId,
@@ -3428,10 +3430,7 @@ function isOwnerOrAdminRole(role) {
3428
3430
  * @returns
3429
3431
  */
3430
3432
  const getAllAuditLogs = (options) => {
3431
- const $fetch = createFetch({
3432
- baseURL: options.apiUrl,
3433
- headers: { "x-api-key": options.apiKey }
3434
- });
3433
+ const { $api } = options;
3435
3434
  const getUserOrganizationRole = async (adapter, orgId, userId) => {
3436
3435
  return (await adapter.findOne({
3437
3436
  model: "member",
@@ -3508,7 +3507,7 @@ const getAllAuditLogs = (options) => {
3508
3507
  const identifierFilter = query.identifier?.trim();
3509
3508
  if (eventTypeFilter) apiQuery.eventType = eventTypeFilter;
3510
3509
  if (identifierFilter) apiQuery.identifier = identifierFilter;
3511
- const { data, error } = await $fetch("/events/activity", {
3510
+ const { data, error } = await $api("/events/activity", {
3512
3511
  method: "GET",
3513
3512
  query: apiQuery
3514
3513
  });
@@ -3628,10 +3627,7 @@ const executeAdapter = (options) => {
3628
3627
  * It creates the user in the auth database and sets up a session.
3629
3628
  */
3630
3629
  const acceptInvitation = (options) => {
3631
- const $api = createFetch({
3632
- baseURL: options.apiUrl || INFRA_API_URL,
3633
- headers: { "x-api-key": options.apiKey }
3634
- });
3630
+ const { $api } = options;
3635
3631
  return createAuthEndpoint("/dash/accept-invitation", {
3636
3632
  method: "GET",
3637
3633
  query: z$1.object({ token: z$1.string() })
@@ -3711,10 +3707,7 @@ const acceptInvitation = (options) => {
3711
3707
  * This creates the user in the auth database and sets up a session
3712
3708
  */
3713
3709
  const completeInvitation = (options) => {
3714
- const $api = createFetch({
3715
- baseURL: options.apiUrl || INFRA_API_URL,
3716
- headers: { "x-api-key": options.apiKey }
3717
- });
3710
+ const { $api } = options;
3718
3711
  return createAuthEndpoint("/dash/complete-invitation", {
3719
3712
  method: "POST",
3720
3713
  body: z$1.object({
@@ -3844,7 +3837,7 @@ const listOrganizationInvitations = (options) => {
3844
3837
  operator: "in"
3845
3838
  }]
3846
3839
  }) : [];
3847
- const userByEmail = new Map(users.map((u) => [normalizeEmail(u.email, ctx.context), u]));
3840
+ const userByEmail = new Map(users.filter((u) => u.email != null).map((u) => [normalizeEmail(u.email, ctx.context), u]));
3848
3841
  return invitations.map((invitation) => {
3849
3842
  const invitationEmail = normalizeEmail(invitation.email, ctx.context);
3850
3843
  const user = userByEmail.get(invitationEmail);
@@ -3853,7 +3846,7 @@ const listOrganizationInvitations = (options) => {
3853
3846
  user: user ? {
3854
3847
  id: user.id,
3855
3848
  name: user.name,
3856
- email: user.email,
3849
+ email: user.email ?? void 0,
3857
3850
  image: user.image || null
3858
3851
  } : null
3859
3852
  };
@@ -3939,7 +3932,7 @@ const checkUserByEmail = (options) => {
3939
3932
  user: {
3940
3933
  id: user.id,
3941
3934
  name: user.name,
3942
- email: user.email,
3935
+ email: user.email ?? void 0,
3943
3936
  image: user.image ?? null
3944
3937
  },
3945
3938
  isAlreadyMember: !!existingMember
@@ -4095,18 +4088,18 @@ const listOrganizationMembers = (options) => {
4095
4088
  for (const m of members) {
4096
4089
  const joinedUser = Array.isArray(m.user) ? m.user[0] : m.user;
4097
4090
  if (!joinedUser) continue;
4098
- const invitation = invitationByEmail.get(joinedUser.email.toLowerCase());
4091
+ const invitation = joinedUser.email ? invitationByEmail.get(joinedUser.email.toLowerCase()) : void 0;
4099
4092
  const inviter = invitation ? inviterById.get(invitation.inviterId) : void 0;
4100
4093
  const user = {
4101
4094
  id: joinedUser.id,
4102
- email: joinedUser.email,
4095
+ email: joinedUser.email ?? void 0,
4103
4096
  name: joinedUser.name,
4104
4097
  image: joinedUser.image ?? null
4105
4098
  };
4106
4099
  const invitedBy = inviter ? {
4107
4100
  id: inviter.id,
4108
4101
  name: inviter.name,
4109
- email: inviter.email,
4102
+ email: inviter.email ?? void 0,
4110
4103
  image: inviter.image ?? null
4111
4104
  } : null;
4112
4105
  result.push({
@@ -4549,8 +4542,8 @@ const listOrganizations = (options) => {
4549
4542
  const members = organization.member.map((m) => userMap.get(m.userId)).filter((u) => u !== void 0).map((u) => ({
4550
4543
  id: u.id,
4551
4544
  name: u.name,
4552
- email: u.email,
4553
- image: u.image
4545
+ email: u.email ?? void 0,
4546
+ image: u.image ?? null
4554
4547
  }));
4555
4548
  const { member: _members, ...org } = organization;
4556
4549
  return {
@@ -5292,7 +5285,7 @@ const listTeamMembers = (options) => {
5292
5285
  user: user ? {
5293
5286
  id: user.id,
5294
5287
  name: user.name,
5295
- email: user.email,
5288
+ email: user.email ?? void 0,
5296
5289
  image: user.image ?? null
5297
5290
  } : null
5298
5291
  };
@@ -7372,6 +7365,7 @@ const sendVerificationEmail = (options) => createAuthEndpoint("/dash/send-verifi
7372
7365
  const user = await ctx.context.internalAdapter.findUserById(userId);
7373
7366
  if (!user) throw ctx.error("NOT_FOUND", { message: "User not found" });
7374
7367
  if (user.emailVerified) throw ctx.error("BAD_REQUEST", { message: "Email is already verified" });
7368
+ if (!user.email) throw ctx.error("BAD_REQUEST", { message: "User has no associated email address" });
7375
7369
  if (!ctx.context.options.emailVerification?.sendVerificationEmail) throw ctx.error("BAD_REQUEST", { message: "Email verification is not enabled" });
7376
7370
  await sendVerificationEmailFn({
7377
7371
  ...ctx,
@@ -7408,6 +7402,10 @@ const sendManyVerificationEmails = (options) => {
7408
7402
  });
7409
7403
  if (chunk.length - users.length > 0) for (const id of chunk.filter((id) => !users.some((u) => u.id === id))) skippedEmailUserIds.add(id);
7410
7404
  for (const result of await Promise.allSettled(users.map(async (user) => {
7405
+ if (!user.email) return {
7406
+ success: false,
7407
+ id: user.id
7408
+ };
7411
7409
  try {
7412
7410
  await sendVerificationEmailFn({
7413
7411
  ...ctx,
@@ -7447,6 +7445,7 @@ const sendResetPasswordEmail = (options) => createAuthEndpoint("/dash/send-reset
7447
7445
  const { userId } = ctx.context.payload;
7448
7446
  const user = await ctx.context.internalAdapter.findUserById(userId);
7449
7447
  if (!user) throw ctx.error("NOT_FOUND", { message: "User not found" });
7448
+ if (!user.email) throw ctx.error("BAD_REQUEST", { message: "User has no associated email address" });
7450
7449
  ctx.body.redirectTo = ctx.body.callbackUrl;
7451
7450
  ctx.body.email = user.email;
7452
7451
  return await requestPasswordReset(ctx);
@@ -7541,6 +7540,11 @@ function createSMSSender(config) {
7541
7540
  const apiUrl = baseUrl.endsWith("/api") ? baseUrl : `${baseUrl}/api`;
7542
7541
  const apiKey = config?.apiKey || env.BETTER_AUTH_API_KEY || "";
7543
7542
  if (!apiKey) logger.warn("[Dash] No API key provided for SMS sending. Set BETTER_AUTH_API_KEY environment variable or pass apiKey in config.");
7543
+ const $api = createFetch({
7544
+ baseURL: apiUrl,
7545
+ headers: { Authorization: `Bearer ${apiKey}` },
7546
+ timeout: config?.apiTimeout ?? 3e3
7547
+ });
7544
7548
  /**
7545
7549
  * Send an SMS with OTP code
7546
7550
  */
@@ -7550,25 +7554,25 @@ function createSMSSender(config) {
7550
7554
  error: "API key not configured"
7551
7555
  };
7552
7556
  try {
7553
- const response = await fetch(`${apiUrl}/v1/sms/send`, {
7557
+ const { data, error } = await $api("/v1/sms/send", {
7554
7558
  method: "POST",
7555
- headers: {
7556
- "Content-Type": "application/json",
7557
- Authorization: `Bearer ${apiKey}`
7558
- },
7559
- body: JSON.stringify({
7559
+ body: {
7560
7560
  to: options.to,
7561
7561
  code: options.code,
7562
7562
  template: options.template
7563
- })
7563
+ }
7564
7564
  });
7565
- if (!response.ok) return {
7565
+ if (error) return {
7566
7566
  success: false,
7567
- error: (await response.json().catch(() => ({ message: "Unknown error" }))).message || `HTTP ${response.status}`
7567
+ error: error.message || `HTTP ${error.status}`
7568
+ };
7569
+ if (typeof data !== "object" || data === null || Array.isArray(data)) return {
7570
+ success: false,
7571
+ error: "Failed to parse JSON"
7568
7572
  };
7569
7573
  return {
7570
7574
  success: true,
7571
- messageId: (await response.json()).messageId
7575
+ messageId: data.messageId
7572
7576
  };
7573
7577
  } catch (error) {
7574
7578
  logger.warn("[Dash] SMS send failed:", error);
@@ -7616,8 +7620,14 @@ async function sendSMS(options, config) {
7616
7620
  const dash = (options) => {
7617
7621
  const processedBulkOperationContexts = /* @__PURE__ */ new WeakSet();
7618
7622
  const opts = resolveDashOptions(options);
7623
+ const $api = createAPI(opts);
7624
+ const settings = {
7625
+ ...opts,
7626
+ $api
7627
+ };
7628
+ const $kv = createKV(opts);
7619
7629
  const activityUpdateInterval = opts.activityTracking?.updateInterval ?? 3e5;
7620
- const { tracker } = initTrackEvents(opts);
7630
+ const { tracker } = initTrackEvents($api);
7621
7631
  const { trackUserSignedUp, trackUserProfileUpdated, trackUserProfileImageUpdated, trackUserEmailVerified, trackUserBanned, trackUserUnBanned, trackUserDeleted } = initUserEvents(tracker);
7622
7632
  const { trackEmailVerificationSent, trackEmailSignInAttempt, trackUserSignedIn, trackUserSignedOut, trackSessionCreated, trackSocialSignInAttempt, trackSocialSignInRedirectionAttempt, trackUserImpersonated, trackUserImpersonationStop, trackSessionRevoked, trackSessionRevokedAll } = initSessionEvents(tracker);
7623
7633
  const { trackAccountLinking, trackAccountUnlink, trackAccountPasswordChange } = initAccountEvents(tracker);
@@ -7899,7 +7909,7 @@ const dash = (options) => {
7899
7909
  const path = new URL(ctx.request.url).pathname;
7900
7910
  return matchesAnyRoute(path, [routes.SIGN_IN_SOCIAL_CALLBACK, routes.SIGN_IN_OAUTH_CALLBACK]);
7901
7911
  },
7902
- handler: createIdentificationMiddleware(opts)
7912
+ handler: createIdentificationMiddleware($kv)
7903
7913
  }],
7904
7914
  after: [{
7905
7915
  matcher: (ctx) => {
@@ -7914,7 +7924,7 @@ const dash = (options) => {
7914
7924
  const body = ctx.body;
7915
7925
  if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_EMAIL, routes.SIGN_IN_EMAIL_OTP]) && ctx.context.returned instanceof Error && body?.email) trackEmailSignInAttempt(ctx, trigger);
7916
7926
  if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_SOCIAL]) && ctx.context.returned instanceof Error && ctx.body.provider && ctx.body.idToken) trackSocialSignInAttempt(ctx, trigger);
7917
- if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_SOCIAL_CALLBACK]) && ctx.context.returned instanceof Error) trackSocialSignInRedirectionAttempt(ctx, trigger);
7927
+ if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_SOCIAL_CALLBACK]) && ctx.request?.method === "GET" && ctx.context.returned instanceof Error && !ctx.context.newSession) trackSocialSignInRedirectionAttempt(ctx, trigger);
7918
7928
  const headerRequestId = ctx.request?.headers.get("X-Request-Id");
7919
7929
  if (headerRequestId) ctx.setCookie(IDENTIFICATION_COOKIE_NAME, headerRequestId, {
7920
7930
  maxAge: 600,
@@ -7952,79 +7962,79 @@ const dash = (options) => {
7952
7962
  }]
7953
7963
  },
7954
7964
  endpoints: {
7955
- getDashConfig: getConfig(opts),
7956
- getDashValidate: getValidate(opts),
7957
- getDashUsers: getUsers(opts),
7958
- exportDashUsers: exportUsers(opts),
7959
- createDashUser: createUser(opts),
7960
- deleteDashUser: deleteUser(opts),
7961
- deleteManyDashUsers: deleteManyUsers(opts),
7962
- listDashOrganizations: listOrganizations(opts),
7963
- exportDashOrganizations: exportOrganizations(opts),
7964
- getDashOrganization: getOrganization(opts),
7965
- listDashOrganizationMembers: listOrganizationMembers(opts),
7966
- listDashOrganizationInvitations: listOrganizationInvitations(opts),
7967
- listDashOrganizationTeams: listOrganizationTeams(opts),
7968
- listDashOrganizationSsoProviders: listOrganizationSsoProviders(opts),
7969
- createDashSsoProvider: createSsoProvider(opts),
7970
- updateDashSsoProvider: updateSsoProvider(opts),
7971
- requestDashSsoVerificationToken: requestSsoVerificationToken(opts),
7972
- verifyDashSsoProviderDomain: verifySsoProviderDomain(opts),
7973
- deleteDashSsoProvider: deleteSsoProvider(opts),
7974
- markDashSsoProviderDomainVerified: markSsoProviderDomainVerified(opts),
7975
- listDashTeamMembers: listTeamMembers(opts),
7976
- createDashOrganization: createOrganization(opts),
7977
- deleteDashOrganization: deleteOrganization(opts),
7978
- deleteManyDashOrganizations: deleteManyOrganizations(opts),
7979
- getDashOrganizationOptions: getOrganizationOptions(opts),
7980
- getDashUser: getUserDetails(opts),
7981
- getDashUserOrganizations: getUserOrganizations(opts),
7982
- updateDashUser: updateUser(opts),
7983
- setDashPassword: setPassword(opts),
7984
- unlinkDashAccount: unlinkAccount(opts),
7985
- dashRevokeSession: revokeSession(opts),
7986
- dashRevokeAllSessions: revokeAllSessions(opts),
7987
- dashRevokeManySessions: revokeManySessions(opts),
7988
- dashImpersonateUser: impersonateUser(opts),
7989
- updateDashOrganization: updateOrganization(opts),
7990
- createDashTeam: createTeam(opts),
7991
- updateDashTeam: updateTeam(opts),
7992
- deleteDashTeam: deleteTeam(opts),
7993
- addDashTeamMember: addTeamMember(opts),
7994
- removeDashTeamMember: removeTeamMember(opts),
7995
- addDashMember: addMember(opts),
7996
- removeDashMember: removeMember(opts),
7997
- updateDashMemberRole: updateMemberRole(opts),
7998
- inviteDashMember: inviteMember(opts),
7999
- cancelDashInvitation: cancelInvitation(opts),
8000
- resendDashInvitation: resendInvitation(opts),
8001
- dashCheckUserByEmail: checkUserByEmail(opts),
8002
- dashGetUserStats: getUserStats(opts),
8003
- dashGetUserGraphData: getUserGraphData(opts),
8004
- dashGetUserRetentionData: getUserRetentionData(opts),
8005
- dashBanUser: banUser(opts),
8006
- dashBanManyUsers: banManyUsers(opts),
8007
- dashUnbanUser: unbanUser(opts),
8008
- dashSendVerificationEmail: sendVerificationEmail(opts),
8009
- dashSendManyVerificationEmails: sendManyVerificationEmails(opts),
8010
- dashSendResetPasswordEmail: sendResetPasswordEmail(opts),
8011
- dashEnableTwoFactor: enableTwoFactor(opts),
8012
- dashViewTwoFactorTotpUri: viewTwoFactorTotpUri(opts),
8013
- dashViewBackupCodes: viewBackupCodes(opts),
8014
- dashDisableTwoFactor: disableTwoFactor(opts),
8015
- dashGenerateBackupCodes: generateBackupCodes(opts),
8016
- getUserEvents: getUserEvents(opts),
8017
- getAuditLogs: getAuditLogs(opts),
8018
- getAllAuditLogs: getAllAuditLogs(opts),
8019
- getEventTypes: getEventTypes(opts),
8020
- dashAcceptInvitation: acceptInvitation(opts),
8021
- dashCompleteInvitation: completeInvitation(opts),
8022
- dashCheckUserExists: checkUserExists(opts),
8023
- listDashOrganizationDirectories: listOrganizationDirectories(opts),
8024
- createDashOrganizationDirectory: createOrganizationDirectory(opts),
8025
- deleteDashOrganizationDirectory: deleteOrganizationDirectory(opts),
8026
- regenerateDashDirectoryToken: regenerateDirectoryToken(opts),
8027
- dashExecuteAdapter: executeAdapter(opts)
7965
+ getDashConfig: getConfig(settings),
7966
+ getDashValidate: getValidate(settings),
7967
+ getDashUsers: getUsers(settings),
7968
+ exportDashUsers: exportUsers(settings),
7969
+ createDashUser: createUser(settings),
7970
+ deleteDashUser: deleteUser(settings),
7971
+ deleteManyDashUsers: deleteManyUsers(settings),
7972
+ listDashOrganizations: listOrganizations(settings),
7973
+ exportDashOrganizations: exportOrganizations(settings),
7974
+ getDashOrganization: getOrganization(settings),
7975
+ listDashOrganizationMembers: listOrganizationMembers(settings),
7976
+ listDashOrganizationInvitations: listOrganizationInvitations(settings),
7977
+ listDashOrganizationTeams: listOrganizationTeams(settings),
7978
+ listDashOrganizationSsoProviders: listOrganizationSsoProviders(settings),
7979
+ createDashSsoProvider: createSsoProvider(settings),
7980
+ updateDashSsoProvider: updateSsoProvider(settings),
7981
+ requestDashSsoVerificationToken: requestSsoVerificationToken(settings),
7982
+ verifyDashSsoProviderDomain: verifySsoProviderDomain(settings),
7983
+ deleteDashSsoProvider: deleteSsoProvider(settings),
7984
+ markDashSsoProviderDomainVerified: markSsoProviderDomainVerified(settings),
7985
+ listDashTeamMembers: listTeamMembers(settings),
7986
+ createDashOrganization: createOrganization(settings),
7987
+ deleteDashOrganization: deleteOrganization(settings),
7988
+ deleteManyDashOrganizations: deleteManyOrganizations(settings),
7989
+ getDashOrganizationOptions: getOrganizationOptions(settings),
7990
+ getDashUser: getUserDetails(settings),
7991
+ getDashUserOrganizations: getUserOrganizations(settings),
7992
+ updateDashUser: updateUser(settings),
7993
+ setDashPassword: setPassword(settings),
7994
+ unlinkDashAccount: unlinkAccount(settings),
7995
+ dashRevokeSession: revokeSession(settings),
7996
+ dashRevokeAllSessions: revokeAllSessions(settings),
7997
+ dashRevokeManySessions: revokeManySessions(settings),
7998
+ dashImpersonateUser: impersonateUser(settings),
7999
+ updateDashOrganization: updateOrganization(settings),
8000
+ createDashTeam: createTeam(settings),
8001
+ updateDashTeam: updateTeam(settings),
8002
+ deleteDashTeam: deleteTeam(settings),
8003
+ addDashTeamMember: addTeamMember(settings),
8004
+ removeDashTeamMember: removeTeamMember(settings),
8005
+ addDashMember: addMember(settings),
8006
+ removeDashMember: removeMember(settings),
8007
+ updateDashMemberRole: updateMemberRole(settings),
8008
+ inviteDashMember: inviteMember(settings),
8009
+ cancelDashInvitation: cancelInvitation(settings),
8010
+ resendDashInvitation: resendInvitation(settings),
8011
+ dashCheckUserByEmail: checkUserByEmail(settings),
8012
+ dashGetUserStats: getUserStats(settings),
8013
+ dashGetUserGraphData: getUserGraphData(settings),
8014
+ dashGetUserRetentionData: getUserRetentionData(settings),
8015
+ dashBanUser: banUser(settings),
8016
+ dashBanManyUsers: banManyUsers(settings),
8017
+ dashUnbanUser: unbanUser(settings),
8018
+ dashSendVerificationEmail: sendVerificationEmail(settings),
8019
+ dashSendManyVerificationEmails: sendManyVerificationEmails(settings),
8020
+ dashSendResetPasswordEmail: sendResetPasswordEmail(settings),
8021
+ dashEnableTwoFactor: enableTwoFactor(settings),
8022
+ dashViewTwoFactorTotpUri: viewTwoFactorTotpUri(settings),
8023
+ dashViewBackupCodes: viewBackupCodes(settings),
8024
+ dashDisableTwoFactor: disableTwoFactor(settings),
8025
+ dashGenerateBackupCodes: generateBackupCodes(settings),
8026
+ getUserEvents: getUserEvents(settings),
8027
+ getAuditLogs: getAuditLogs(settings),
8028
+ getAllAuditLogs: getAllAuditLogs(settings),
8029
+ getEventTypes: getEventTypes(settings),
8030
+ dashAcceptInvitation: acceptInvitation(settings),
8031
+ dashCompleteInvitation: completeInvitation(settings),
8032
+ dashCheckUserExists: checkUserExists(settings),
8033
+ listDashOrganizationDirectories: listOrganizationDirectories(settings),
8034
+ createDashOrganizationDirectory: createOrganizationDirectory(settings),
8035
+ deleteDashOrganizationDirectory: deleteOrganizationDirectory(settings),
8036
+ regenerateDashDirectoryToken: regenerateDirectoryToken(settings),
8037
+ dashExecuteAdapter: executeAdapter(settings)
8028
8038
  },
8029
8039
  schema: opts.activityTracking?.enabled ? { user: { fields: { lastActiveAt: {
8030
8040
  type: "date",