@better-auth/infra 0.2.6 → 0.2.7

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
@@ -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",
@@ -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.7";
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({
@@ -7541,6 +7534,11 @@ function createSMSSender(config) {
7541
7534
  const apiUrl = baseUrl.endsWith("/api") ? baseUrl : `${baseUrl}/api`;
7542
7535
  const apiKey = config?.apiKey || env.BETTER_AUTH_API_KEY || "";
7543
7536
  if (!apiKey) logger.warn("[Dash] No API key provided for SMS sending. Set BETTER_AUTH_API_KEY environment variable or pass apiKey in config.");
7537
+ const $api = createFetch({
7538
+ baseURL: apiUrl,
7539
+ headers: { Authorization: `Bearer ${apiKey}` },
7540
+ timeout: config?.apiTimeout ?? 3e3
7541
+ });
7544
7542
  /**
7545
7543
  * Send an SMS with OTP code
7546
7544
  */
@@ -7550,25 +7548,25 @@ function createSMSSender(config) {
7550
7548
  error: "API key not configured"
7551
7549
  };
7552
7550
  try {
7553
- const response = await fetch(`${apiUrl}/v1/sms/send`, {
7551
+ const { data, error } = await $api("/v1/sms/send", {
7554
7552
  method: "POST",
7555
- headers: {
7556
- "Content-Type": "application/json",
7557
- Authorization: `Bearer ${apiKey}`
7558
- },
7559
- body: JSON.stringify({
7553
+ body: {
7560
7554
  to: options.to,
7561
7555
  code: options.code,
7562
7556
  template: options.template
7563
- })
7557
+ }
7564
7558
  });
7565
- if (!response.ok) return {
7559
+ if (error) return {
7566
7560
  success: false,
7567
- error: (await response.json().catch(() => ({ message: "Unknown error" }))).message || `HTTP ${response.status}`
7561
+ error: error.message || `HTTP ${error.status}`
7562
+ };
7563
+ if (typeof data !== "object" || data === null || Array.isArray(data)) return {
7564
+ success: false,
7565
+ error: "Failed to parse JSON"
7568
7566
  };
7569
7567
  return {
7570
7568
  success: true,
7571
- messageId: (await response.json()).messageId
7569
+ messageId: data.messageId
7572
7570
  };
7573
7571
  } catch (error) {
7574
7572
  logger.warn("[Dash] SMS send failed:", error);
@@ -7616,8 +7614,14 @@ async function sendSMS(options, config) {
7616
7614
  const dash = (options) => {
7617
7615
  const processedBulkOperationContexts = /* @__PURE__ */ new WeakSet();
7618
7616
  const opts = resolveDashOptions(options);
7617
+ const $api = createAPI(opts);
7618
+ const settings = {
7619
+ ...opts,
7620
+ $api
7621
+ };
7622
+ const $kv = createKV(opts);
7619
7623
  const activityUpdateInterval = opts.activityTracking?.updateInterval ?? 3e5;
7620
- const { tracker } = initTrackEvents(opts);
7624
+ const { tracker } = initTrackEvents($api);
7621
7625
  const { trackUserSignedUp, trackUserProfileUpdated, trackUserProfileImageUpdated, trackUserEmailVerified, trackUserBanned, trackUserUnBanned, trackUserDeleted } = initUserEvents(tracker);
7622
7626
  const { trackEmailVerificationSent, trackEmailSignInAttempt, trackUserSignedIn, trackUserSignedOut, trackSessionCreated, trackSocialSignInAttempt, trackSocialSignInRedirectionAttempt, trackUserImpersonated, trackUserImpersonationStop, trackSessionRevoked, trackSessionRevokedAll } = initSessionEvents(tracker);
7623
7627
  const { trackAccountLinking, trackAccountUnlink, trackAccountPasswordChange } = initAccountEvents(tracker);
@@ -7899,7 +7903,7 @@ const dash = (options) => {
7899
7903
  const path = new URL(ctx.request.url).pathname;
7900
7904
  return matchesAnyRoute(path, [routes.SIGN_IN_SOCIAL_CALLBACK, routes.SIGN_IN_OAUTH_CALLBACK]);
7901
7905
  },
7902
- handler: createIdentificationMiddleware(opts)
7906
+ handler: createIdentificationMiddleware($kv)
7903
7907
  }],
7904
7908
  after: [{
7905
7909
  matcher: (ctx) => {
@@ -7914,7 +7918,7 @@ const dash = (options) => {
7914
7918
  const body = ctx.body;
7915
7919
  if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_EMAIL, routes.SIGN_IN_EMAIL_OTP]) && ctx.context.returned instanceof Error && body?.email) trackEmailSignInAttempt(ctx, trigger);
7916
7920
  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);
7921
+ 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
7922
  const headerRequestId = ctx.request?.headers.get("X-Request-Id");
7919
7923
  if (headerRequestId) ctx.setCookie(IDENTIFICATION_COOKIE_NAME, headerRequestId, {
7920
7924
  maxAge: 600,
@@ -7952,79 +7956,79 @@ const dash = (options) => {
7952
7956
  }]
7953
7957
  },
7954
7958
  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)
7959
+ getDashConfig: getConfig(settings),
7960
+ getDashValidate: getValidate(settings),
7961
+ getDashUsers: getUsers(settings),
7962
+ exportDashUsers: exportUsers(settings),
7963
+ createDashUser: createUser(settings),
7964
+ deleteDashUser: deleteUser(settings),
7965
+ deleteManyDashUsers: deleteManyUsers(settings),
7966
+ listDashOrganizations: listOrganizations(settings),
7967
+ exportDashOrganizations: exportOrganizations(settings),
7968
+ getDashOrganization: getOrganization(settings),
7969
+ listDashOrganizationMembers: listOrganizationMembers(settings),
7970
+ listDashOrganizationInvitations: listOrganizationInvitations(settings),
7971
+ listDashOrganizationTeams: listOrganizationTeams(settings),
7972
+ listDashOrganizationSsoProviders: listOrganizationSsoProviders(settings),
7973
+ createDashSsoProvider: createSsoProvider(settings),
7974
+ updateDashSsoProvider: updateSsoProvider(settings),
7975
+ requestDashSsoVerificationToken: requestSsoVerificationToken(settings),
7976
+ verifyDashSsoProviderDomain: verifySsoProviderDomain(settings),
7977
+ deleteDashSsoProvider: deleteSsoProvider(settings),
7978
+ markDashSsoProviderDomainVerified: markSsoProviderDomainVerified(settings),
7979
+ listDashTeamMembers: listTeamMembers(settings),
7980
+ createDashOrganization: createOrganization(settings),
7981
+ deleteDashOrganization: deleteOrganization(settings),
7982
+ deleteManyDashOrganizations: deleteManyOrganizations(settings),
7983
+ getDashOrganizationOptions: getOrganizationOptions(settings),
7984
+ getDashUser: getUserDetails(settings),
7985
+ getDashUserOrganizations: getUserOrganizations(settings),
7986
+ updateDashUser: updateUser(settings),
7987
+ setDashPassword: setPassword(settings),
7988
+ unlinkDashAccount: unlinkAccount(settings),
7989
+ dashRevokeSession: revokeSession(settings),
7990
+ dashRevokeAllSessions: revokeAllSessions(settings),
7991
+ dashRevokeManySessions: revokeManySessions(settings),
7992
+ dashImpersonateUser: impersonateUser(settings),
7993
+ updateDashOrganization: updateOrganization(settings),
7994
+ createDashTeam: createTeam(settings),
7995
+ updateDashTeam: updateTeam(settings),
7996
+ deleteDashTeam: deleteTeam(settings),
7997
+ addDashTeamMember: addTeamMember(settings),
7998
+ removeDashTeamMember: removeTeamMember(settings),
7999
+ addDashMember: addMember(settings),
8000
+ removeDashMember: removeMember(settings),
8001
+ updateDashMemberRole: updateMemberRole(settings),
8002
+ inviteDashMember: inviteMember(settings),
8003
+ cancelDashInvitation: cancelInvitation(settings),
8004
+ resendDashInvitation: resendInvitation(settings),
8005
+ dashCheckUserByEmail: checkUserByEmail(settings),
8006
+ dashGetUserStats: getUserStats(settings),
8007
+ dashGetUserGraphData: getUserGraphData(settings),
8008
+ dashGetUserRetentionData: getUserRetentionData(settings),
8009
+ dashBanUser: banUser(settings),
8010
+ dashBanManyUsers: banManyUsers(settings),
8011
+ dashUnbanUser: unbanUser(settings),
8012
+ dashSendVerificationEmail: sendVerificationEmail(settings),
8013
+ dashSendManyVerificationEmails: sendManyVerificationEmails(settings),
8014
+ dashSendResetPasswordEmail: sendResetPasswordEmail(settings),
8015
+ dashEnableTwoFactor: enableTwoFactor(settings),
8016
+ dashViewTwoFactorTotpUri: viewTwoFactorTotpUri(settings),
8017
+ dashViewBackupCodes: viewBackupCodes(settings),
8018
+ dashDisableTwoFactor: disableTwoFactor(settings),
8019
+ dashGenerateBackupCodes: generateBackupCodes(settings),
8020
+ getUserEvents: getUserEvents(settings),
8021
+ getAuditLogs: getAuditLogs(settings),
8022
+ getAllAuditLogs: getAllAuditLogs(settings),
8023
+ getEventTypes: getEventTypes(settings),
8024
+ dashAcceptInvitation: acceptInvitation(settings),
8025
+ dashCompleteInvitation: completeInvitation(settings),
8026
+ dashCheckUserExists: checkUserExists(settings),
8027
+ listDashOrganizationDirectories: listOrganizationDirectories(settings),
8028
+ createDashOrganizationDirectory: createOrganizationDirectory(settings),
8029
+ deleteDashOrganizationDirectory: deleteOrganizationDirectory(settings),
8030
+ regenerateDashDirectoryToken: regenerateDirectoryToken(settings),
8031
+ dashExecuteAdapter: executeAdapter(settings)
8028
8032
  },
8029
8033
  schema: opts.activityTracking?.enabled ? { user: { fields: { lastActiveAt: {
8030
8034
  type: "date",