@better-auth/infra 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as dashClient, i as DashGetAuditLogsInput, n as DashAuditLogsResponse, r as DashClientOptions, t as DashAuditLog } from "./dash-client-hJHp7l_X.mjs";
1
+ import { a as DashGetAuditLogsInput, i as DashGetAllAuditLogsInput, n as DashAuditLogsResponse, o as dashClient, r as DashClientOptions, t as DashAuditLog } from "./dash-client-CVCHbtoB.mjs";
2
2
  import * as _$_better_fetch_fetch0 from "@better-fetch/fetch";
3
3
 
4
4
  //#region src/sentinel/client.d.ts
@@ -44,4 +44,4 @@ declare const sentinelClient: (options?: SentinelClientOptions) => {
44
44
  })[];
45
45
  };
46
46
  //#endregion
47
- export { type DashAuditLog, type DashAuditLogsResponse, type DashClientOptions, type DashGetAuditLogsInput, type SentinelClientOptions, dashClient, sentinelClient };
47
+ export { type DashAuditLog, type DashAuditLogsResponse, type DashClientOptions, type DashGetAllAuditLogsInput, type DashGetAuditLogsInput, type SentinelClientOptions, dashClient, sentinelClient };
package/dist/client.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { r as KV_TIMEOUT_MS } from "./constants-DdWGfvz1.mjs";
2
- import { a as identify, c as dashClient, i as generateRequestId, n as encodePoWSolution, r as solvePoWChallenge, s as hash, t as decodePoWChallenge } from "./pow-BUuN_EKw.mjs";
2
+ import { a as identify, c as dashClient, i as generateRequestId, n as encodePoWSolution, r as solvePoWChallenge, s as hash, t as decodePoWChallenge } from "./pow-CT9ehp8e.mjs";
3
3
  import { env } from "@better-auth/core/env";
4
4
  //#region src/sentinel/fingerprint.ts
5
5
  function murmurhash3(str, seed = 0) {
@@ -40,6 +40,15 @@ interface DashGetAuditLogsInput {
40
40
  user?: UserLike | null;
41
41
  session?: SessionLike | null;
42
42
  }
43
+ interface DashGetAllAuditLogsInput {
44
+ limit?: number;
45
+ offset?: number;
46
+ organizationId?: string;
47
+ userId?: string;
48
+ eventType?: string;
49
+ identifier?: string;
50
+ session?: SessionLike | null;
51
+ }
43
52
  interface DashClientOptions {
44
53
  resolveUserId?: (input: {
45
54
  userId?: string;
@@ -62,11 +71,23 @@ declare const dashClient: (options?: DashClientOptions) => {
62
71
  data: DashAuditLogsResponse;
63
72
  error: null;
64
73
  }>;
74
+ getAllAuditLogs: (input?: DashGetAllAuditLogsInput) => Promise<{
75
+ data: null;
76
+ error: {
77
+ message?: string | undefined;
78
+ status: number;
79
+ statusText: string;
80
+ };
81
+ } | {
82
+ data: DashAuditLogsResponse;
83
+ error: null;
84
+ }>;
65
85
  };
66
86
  };
67
87
  pathMethods: {
68
88
  "/events/audit-logs": "GET";
89
+ "/events/all-audit-logs": "GET";
69
90
  };
70
91
  };
71
92
  //#endregion
72
- export { dashClient as a, DashGetAuditLogsInput as i, DashAuditLogsResponse as n, DashClientOptions as r, DashAuditLog as t };
93
+ export { DashGetAuditLogsInput as a, DashGetAllAuditLogsInput as i, DashAuditLogsResponse as n, dashClient as o, DashClientOptions as r, DashAuditLog as t };
package/dist/index.d.mts CHANGED
@@ -726,7 +726,7 @@ interface DashIdRow {
726
726
  id: string;
727
727
  }
728
728
  //#endregion
729
- //#region ../../node_modules/.bun/@better-auth+scim@1.6.4+f772dc2362714ab9/node_modules/@better-auth/scim/dist/index.d.mts
729
+ //#region ../../node_modules/.bun/@better-auth+scim@1.6.9+191040a6ff16b29a/node_modules/@better-auth/scim/dist/index.d.mts
730
730
  //#region src/types.d.ts
731
731
  interface SCIMProvider {
732
732
  id: string;
@@ -5637,6 +5637,40 @@ declare const dash: <O extends DashOptions>(options?: O) => {
5637
5637
  eventType: zod.ZodOptional<zod.ZodString>;
5638
5638
  }, zod_v4_core0.$strip>>;
5639
5639
  }, UserEventsResponse>;
5640
+ getAllAuditLogs: _$better_call0.StrictEndpoint<"/events/all-audit-logs", {
5641
+ method: "GET";
5642
+ use: ((inputContext: _$better_call0.MiddlewareInputContext<_$better_call0.MiddlewareOptions>) => Promise<{
5643
+ session: {
5644
+ session: Record<string, any> & {
5645
+ id: string;
5646
+ createdAt: Date;
5647
+ updatedAt: Date;
5648
+ userId: string;
5649
+ expiresAt: Date;
5650
+ token: string;
5651
+ ipAddress?: string | null | undefined;
5652
+ userAgent?: string | null | undefined;
5653
+ };
5654
+ user: Record<string, any> & {
5655
+ id: string;
5656
+ createdAt: Date;
5657
+ updatedAt: Date;
5658
+ email: string;
5659
+ emailVerified: boolean;
5660
+ name: string;
5661
+ image?: string | null | undefined;
5662
+ };
5663
+ };
5664
+ }>)[];
5665
+ query: zod.ZodOptional<zod.ZodObject<{
5666
+ limit: zod.ZodOptional<zod.ZodUnion<[zod.ZodNumber, zod.ZodPipe<zod.ZodString, zod.ZodTransform<number, string>>]>>;
5667
+ offset: zod.ZodOptional<zod.ZodUnion<[zod.ZodNumber, zod.ZodPipe<zod.ZodString, zod.ZodTransform<number, string>>]>>;
5668
+ userId: zod.ZodOptional<zod.ZodString>;
5669
+ organizationId: zod.ZodOptional<zod.ZodString>;
5670
+ eventType: zod.ZodOptional<zod.ZodString>;
5671
+ identifier: zod.ZodOptional<zod.ZodString>;
5672
+ }, zod_v4_core0.$strip>>;
5673
+ }, UserEventsResponse>;
5640
5674
  getEventTypes: _$better_call0.StrictEndpoint<"/events/types", {
5641
5675
  method: "GET";
5642
5676
  use: ((inputContext: _$better_call0.MiddlewareInputContext<_$better_call0.MiddlewareOptions>) => Promise<{
package/dist/index.mjs CHANGED
@@ -997,6 +997,59 @@ function getCountryCodeFromRequest(request) {
997
997
  return cc ? cc.toUpperCase() : void 0;
998
998
  }
999
999
  //#endregion
1000
+ //#region src/sentinel/evaluation.ts
1001
+ /** Lazily attach the buffer to the request context. */
1002
+ function getEvaluation(ctx) {
1003
+ if (!ctx.context.sentinelEvaluation) ctx.context.sentinelEvaluation = {
1004
+ checks: /* @__PURE__ */ new Set(),
1005
+ outcome: "passed",
1006
+ emitted: false
1007
+ };
1008
+ return ctx.context.sentinelEvaluation;
1009
+ }
1010
+ function recordCheck(ctx, name) {
1011
+ getEvaluation(ctx).checks.add(name);
1012
+ }
1013
+ /** Call immediately before throwing so the buffer reflects the final outcome. */
1014
+ function setOutcome(ctx, outcome, triggeredBy, details) {
1015
+ const ev = getEvaluation(ctx);
1016
+ ev.outcome = outcome;
1017
+ ev.triggeredBy = triggeredBy;
1018
+ if (details) ev.details = details;
1019
+ }
1020
+ /**
1021
+ * Emit one `security_check` event for the request. Idempotent per request and
1022
+ * skipped when no checks ran (unprotected route, or short-circuit before any
1023
+ * sentinel hook). `evaluatedChecks` carries the per-type breakdown — all under
1024
+ * the same Stripe meter, so the array is for support inspection only.
1025
+ */
1026
+ function emitEvaluation(ctx, trackEvent, options = {}) {
1027
+ const ev = ctx.context.sentinelEvaluation;
1028
+ if (!ev || ev.emitted || ev.checks.size === 0) return;
1029
+ ev.emitted = true;
1030
+ const identification = ctx.context.identification ?? null;
1031
+ const visitorId = ctx.context.visitorId ?? null;
1032
+ const path = ctx.path ?? "";
1033
+ trackEvent({
1034
+ eventKey: visitorId || identification?.ip || "unknown",
1035
+ eventType: "security_check",
1036
+ eventDisplayName: ev.outcome === "blocked" ? "Security: blocked" : ev.outcome === "challenged" ? "Security: challenged" : "Security: passed",
1037
+ eventData: {
1038
+ outcome: ev.outcome,
1039
+ evaluatedChecks: [...ev.checks].sort(),
1040
+ triggeredBy: ev.triggeredBy,
1041
+ identifier: options.identifier,
1042
+ path,
1043
+ userAgent: options.userAgent,
1044
+ details: ev.details
1045
+ },
1046
+ ipAddress: identification?.ip || void 0,
1047
+ city: identification?.location?.city || void 0,
1048
+ country: identification?.location?.country?.name || void 0,
1049
+ countryCode: identification?.location?.country?.code || void 0
1050
+ });
1051
+ }
1052
+ //#endregion
1000
1053
  //#region src/sentinel/security.ts
1001
1054
  async function hashForFingerprint(input) {
1002
1055
  const data = new TextEncoder().encode(input);
@@ -1183,6 +1236,9 @@ function createSecurityClient(apiUrl, apiKey, options, onSecurityEvent) {
1183
1236
  return null;
1184
1237
  }
1185
1238
  },
1239
+ /**
1240
+ * Store user's last known location for impossible travel detection
1241
+ */
1186
1242
  async storeLastLocation(userId, location) {
1187
1243
  if (!location) return;
1188
1244
  try {
@@ -1197,6 +1253,9 @@ function createSecurityClient(apiUrl, apiKey, options, onSecurityEvent) {
1197
1253
  logger.error("[Dash] Store last location error:", error);
1198
1254
  }
1199
1255
  },
1256
+ /**
1257
+ * Check if a visitor has exceeded the free trial signup limit.
1258
+ */
1200
1259
  async checkFreeTrialAbuse(visitorId) {
1201
1260
  if (!options.freeTrialAbuse?.enabled) return {
1202
1261
  isAbuse: false,
@@ -1235,6 +1294,10 @@ function createSecurityClient(apiUrl, apiKey, options, onSecurityEvent) {
1235
1294
  };
1236
1295
  }
1237
1296
  },
1297
+ /**
1298
+ * Track a new signup for free trial abuse detection.
1299
+ * Stores the userId for auditing purposes.
1300
+ */
1238
1301
  async trackFreeTrialSignup(visitorId, userId) {
1239
1302
  if (!options.freeTrialAbuse?.enabled) return;
1240
1303
  try {
@@ -1285,6 +1348,10 @@ function createSecurityClient(apiUrl, apiKey, options, onSecurityEvent) {
1285
1348
  return { compromised: false };
1286
1349
  }
1287
1350
  },
1351
+ /**
1352
+ * Check if a user account is stale (inactive for a configured period)
1353
+ * This helps detect potential account takeover when dormant accounts become active
1354
+ */
1288
1355
  async checkStaleUser(userId, lastActiveAt) {
1289
1356
  if (!options.staleUsers?.enabled) return { isStale: false };
1290
1357
  try {
@@ -1317,6 +1384,9 @@ function createSecurityClient(apiUrl, apiKey, options, onSecurityEvent) {
1317
1384
  return { isStale: false };
1318
1385
  }
1319
1386
  },
1387
+ /**
1388
+ * Send stale account reactivation notification to the user
1389
+ */
1320
1390
  async notifyStaleAccountUser(userEmail, userName, daysSinceLastActive, identification, appName) {
1321
1391
  const loginTime = (/* @__PURE__ */ new Date()).toLocaleString("en-US", {
1322
1392
  dateStyle: "long",
@@ -1344,6 +1414,9 @@ function createSecurityClient(apiUrl, apiKey, options, onSecurityEvent) {
1344
1414
  if (result.success) logger.info(`[Dash] Stale account notification sent to user: ${userEmail}`);
1345
1415
  else logger.error(`[Dash] Failed to send stale account user notification: ${result.error}`);
1346
1416
  },
1417
+ /**
1418
+ * Send stale account reactivation notification to the admin
1419
+ */
1347
1420
  async notifyStaleAccountAdmin(adminEmail, userId, userEmail, userName, daysSinceLastActive, identification, appName) {
1348
1421
  const loginTime = (/* @__PURE__ */ new Date()).toLocaleString("en-US", {
1349
1422
  dateStyle: "long",
@@ -1522,7 +1595,12 @@ function createEmailValidator(options = {}) {
1522
1595
  }
1523
1596
  return null;
1524
1597
  }
1525
- return { async validate(email, checkMx = true) {
1598
+ return {
1599
+ /**
1600
+ * Validate an email address
1601
+ * Returns validation result - caller should use getPolicy() to determine action
1602
+ */
1603
+ async validate(email, checkMx = true) {
1526
1604
  const trimmed = email.trim();
1527
1605
  const policy = await fetchPolicy();
1528
1606
  if (!policy?.enabled) return {
@@ -1608,16 +1686,27 @@ function createEmailNormalizationHook() {
1608
1686
  };
1609
1687
  }
1610
1688
  /**
1611
- * Create email validation hook with configurable validation strategy
1689
+ * Records `email_validity` on every email-bearing request. On block, emits the
1690
+ * `security_check` event inline before throwing — the after-hook won't run on throw.
1612
1691
  */
1613
- function createEmailValidationHook(validator, onDisposableEmail) {
1692
+ function createEmailValidationHook(validator, trackEvent) {
1614
1693
  return {
1615
1694
  matcher: allEmail,
1616
1695
  handler: createAuthMiddleware(async (ctx) => {
1617
1696
  const { email } = getEmail(ctx);
1618
1697
  if (typeof email !== "string") return;
1619
1698
  const trimmed = email.trim();
1620
- if (!isValidEmailFormatLocal(trimmed)) throw new APIError$1("BAD_REQUEST", { message: "Invalid email" });
1699
+ recordCheck(ctx, "email_validity");
1700
+ if (!isValidEmailFormatLocal(trimmed)) {
1701
+ if (trackEvent) {
1702
+ setOutcome(ctx, "blocked", "email_validity", {
1703
+ reason: "invalid_format",
1704
+ email: trimmed
1705
+ });
1706
+ emitEvaluation(ctx, trackEvent, { identifier: trimmed });
1707
+ }
1708
+ throw new APIError$1("BAD_REQUEST", { message: "Invalid email" });
1709
+ }
1621
1710
  if (validator) {
1622
1711
  const result = await validator.validate(trimmed);
1623
1712
  const policy = result.policy;
@@ -1628,19 +1717,17 @@ function createEmailValidationHook(validator, onDisposableEmail) {
1628
1717
  }
1629
1718
  const action = policy.action;
1630
1719
  if (!result.valid) {
1631
- if ((result.disposable || result.reason === "no_mx_records" || result.reason === "blocklist" || result.reason === "known_invalid_email" || result.reason === "known_invalid_host" || result.reason === "reserved_tld" || result.reason === "provider_local_too_short") && onDisposableEmail) {
1632
- const ip = ctx.request?.headers?.get("x-forwarded-for")?.split(",")[0] || ctx.request?.headers?.get("cf-connecting-ip") || void 0;
1633
- onDisposableEmail({
1634
- email: trimmed,
1635
- reason: result.reason || "disposable",
1720
+ if (action === "allow") return;
1721
+ const message = result.reason === "no_mx_records" ? "This email domain cannot receive emails" : result.disposable || result.reason === "blocklist" ? "Disposable email addresses are not allowed" : result.reason === "known_invalid_email" || result.reason === "known_invalid_host" || result.reason === "reserved_tld" || result.reason === "provider_local_too_short" ? "This email address appears to be invalid" : "Invalid email";
1722
+ if (trackEvent) {
1723
+ setOutcome(ctx, "blocked", "email_validity", {
1724
+ reason: result.reason,
1636
1725
  confidence: result.confidence,
1637
- ip,
1638
- path: ctx.path,
1639
- action
1726
+ email: trimmed
1640
1727
  });
1728
+ emitEvaluation(ctx, trackEvent, { identifier: trimmed });
1641
1729
  }
1642
- if (action === "allow") return;
1643
- throw new APIError$1("BAD_REQUEST", { message: result.reason === "no_mx_records" ? "This email domain cannot receive emails" : result.disposable || result.reason === "blocklist" ? "Disposable email addresses are not allowed" : result.reason === "known_invalid_email" || result.reason === "known_invalid_host" || result.reason === "reserved_tld" || result.reason === "provider_local_too_short" ? "This email address appears to be invalid" : "Invalid email" });
1730
+ throw new APIError$1("BAD_REQUEST", { message });
1644
1731
  }
1645
1732
  }
1646
1733
  })
@@ -1668,7 +1755,7 @@ function createEmailValidationHook(validator, onDisposableEmail) {
1668
1755
  * })
1669
1756
  */
1670
1757
  function createEmailHooks(options = {}) {
1671
- const { emailValidationOptions, emailNormalizationOptions, useApi = false, apiKey = "", apiUrl = INFRA_API_URL, kvUrl = INFRA_KV_URL, onDisposableEmail } = options;
1758
+ const { emailValidationOptions, emailNormalizationOptions, useApi = false, apiKey = "", apiUrl = INFRA_API_URL, kvUrl = INFRA_KV_URL, trackEvent } = options;
1672
1759
  const emailConfig = {
1673
1760
  enabled: true,
1674
1761
  strictness: "medium",
@@ -1683,7 +1770,7 @@ function createEmailHooks(options = {}) {
1683
1770
  kvUrl,
1684
1771
  apiKey,
1685
1772
  emailValidationOptions: emailConfig
1686
- }) : void 0, onDisposableEmail)] : []] };
1773
+ }) : void 0, trackEvent)] : []] };
1687
1774
  }
1688
1775
  //#endregion
1689
1776
  //#region src/validation/phone.ts
@@ -1898,32 +1985,6 @@ const ERROR_MESSAGES = {
1898
1985
  compromised_password: "This password has been found in data breaches. Please choose a different password.",
1899
1986
  impossible_travel: "Login blocked due to suspicious location change."
1900
1987
  };
1901
- const DISPLAY_NAMES = {
1902
- geo_blocked: {
1903
- challenge: "Security: geo challenge",
1904
- block: "Security: geo blocked"
1905
- },
1906
- bot_detected: {
1907
- challenge: "Security: bot challenge",
1908
- block: "Security: bot blocked"
1909
- },
1910
- suspicious_ip_detected: {
1911
- challenge: "Security: anonymous IP challenge",
1912
- block: "Security: anonymous IP blocked"
1913
- },
1914
- rate_limited: {
1915
- challenge: "Security: velocity challenge",
1916
- block: "Security: velocity exceeded"
1917
- },
1918
- compromised_password: {
1919
- challenge: "Security: breached password warning",
1920
- block: "Security: breached password blocked"
1921
- },
1922
- impossible_travel: {
1923
- challenge: "Security: impossible travel challenge",
1924
- block: "Security: impossible travel blocked"
1925
- }
1926
- };
1927
1988
  /**
1928
1989
  * Throw a challenge error with appropriate headers
1929
1990
  */
@@ -1938,73 +1999,52 @@ function throwChallengeError(challenge, reason, message = "Please complete a sec
1938
1999
  };
1939
2000
  throw error;
1940
2001
  }
1941
- /**
1942
- * Build common event data for security tracking
1943
- */
1944
- function buildEventData(ctx, action, reason, confidence = 1, extraData) {
1945
- const { visitorId, identification, path, identifier, userAgent } = ctx;
1946
- const countryCode = identification?.location?.country?.code || void 0;
1947
- return {
1948
- eventKey: visitorId || identification?.ip || "unknown",
1949
- eventType: action === "challenged" ? "security_challenged" : "security_blocked",
1950
- eventDisplayName: DISPLAY_NAMES[reason]?.[action === "challenged" ? "challenge" : "block"] || `Security: ${reason}`,
1951
- eventData: {
1952
- action,
1953
- reason,
1954
- visitorId: visitorId || "",
1955
- path,
1956
- userAgent,
1957
- identifier,
1958
- confidence,
1959
- ...extraData
1960
- },
1961
- ipAddress: identification?.ip || void 0,
1962
- city: identification?.location?.city || void 0,
1963
- country: identification?.location?.country?.name || void 0,
1964
- countryCode
1965
- };
1966
- }
1967
- /**
1968
- * Handle a security check result by tracking events and throwing appropriate errors
1969
- *
1970
- * @param verdict - The security verdict from the security service
1971
- * @param ctx - Security check context with request information
1972
- * @param trackEvent - Function to track security events
1973
- * @param securityService - Security service for generating challenges
1974
- * @returns True if the request should be blocked
1975
- */
1976
- async function handleSecurityVerdict(verdict, ctx, trackEvent, securityService) {
2002
+ /** Emits the `security_check` event inline before throwing — the after-hook won't run on throw. */
2003
+ async function handleSecurityVerdict(verdict, hookCtx, checkCtx, trackEvent, securityService) {
1977
2004
  if (verdict.action === "allow") return;
1978
2005
  const reason = verdict.reason || "unknown";
1979
- const confidence = 1;
1980
- if (verdict.action === "challenge" && ctx.visitorId) {
1981
- const challenge = verdict.challenge || await securityService.generateChallenge(ctx.visitorId);
2006
+ if (verdict.action === "challenge" && checkCtx.visitorId) {
2007
+ const challenge = verdict.challenge || await securityService.generateChallenge(checkCtx.visitorId);
1982
2008
  if (!challenge?.trim()) {
1983
2009
  logger.warn("[Sentinel] Could not generate PoW challenge (service may be unavailable). Falling back to allow.");
1984
2010
  return;
1985
2011
  }
1986
- trackEvent(buildEventData(ctx, "challenged", reason, confidence, verdict.details));
1987
- throwChallengeError(challenge, reason, "Please complete a security check to continue.");
1988
- } else if (verdict.action === "block") {
1989
- trackEvent(buildEventData(ctx, "blocked", reason, confidence, verdict.details));
2012
+ setOutcome(hookCtx, "challenged", "server_security_check", {
2013
+ reason,
2014
+ ...verdict.details
2015
+ });
2016
+ emitEvaluation(hookCtx, trackEvent, {
2017
+ identifier: checkCtx.identifier,
2018
+ userAgent: checkCtx.userAgent
2019
+ });
2020
+ throwChallengeError(challenge, reason);
2021
+ }
2022
+ if (verdict.action === "block") {
2023
+ setOutcome(hookCtx, "blocked", "server_security_check", {
2024
+ reason,
2025
+ ...verdict.details
2026
+ });
2027
+ emitEvaluation(hookCtx, trackEvent, {
2028
+ identifier: checkCtx.identifier,
2029
+ userAgent: checkCtx.userAgent
2030
+ });
1990
2031
  throw new APIError("FORBIDDEN", { message: ERROR_MESSAGES[reason] || "Access denied." });
1991
2032
  }
1992
2033
  }
1993
2034
  /**
1994
- * Run all security checks using the consolidated checkSecurity API
1995
- *
1996
- * This replaces the multiple individual check calls with a single API call
1997
- * that handles all security checks server-side.
2035
+ * Single API call from the plugin; server runs all configured rules and returns
2036
+ * one verdict — counts as one billable check.
1998
2037
  */
1999
- async function runSecurityChecks(ctx, securityService, trackEvent, powVerified) {
2038
+ async function runSecurityChecks(hookCtx, checkCtx, securityService, trackEvent, powVerified) {
2000
2039
  if (powVerified) return;
2040
+ recordCheck(hookCtx, "server_security_check");
2001
2041
  await handleSecurityVerdict(await securityService.checkSecurity({
2002
- visitorId: ctx.visitorId,
2003
- requestId: ctx.identification?.requestId || null,
2004
- ip: ctx.identification?.ip || null,
2005
- path: ctx.path,
2006
- identifier: ctx.identifier
2007
- }), ctx, trackEvent, securityService);
2042
+ visitorId: checkCtx.visitorId,
2043
+ requestId: checkCtx.identification?.requestId || null,
2044
+ ip: checkCtx.identification?.ip || null,
2045
+ path: checkCtx.path,
2046
+ identifier: checkCtx.identifier
2047
+ }), hookCtx, checkCtx, trackEvent, securityService);
2008
2048
  }
2009
2049
  //#endregion
2010
2050
  //#region src/sentinel/sentinel.ts
@@ -2016,8 +2056,8 @@ const sentinel = (options) => {
2016
2056
  const securityService = createSecurityClient(opts.apiUrl, opts.apiKey, opts.security || {}, (event) => {
2017
2057
  trackEvent({
2018
2058
  eventKey: event.visitorId || event.userId || "unknown",
2019
- eventType: `security_${event.type}`,
2020
- eventDisplayName: `Security: ${event.type.replace(/_/g, " ")}`,
2059
+ eventType: "security_signal",
2060
+ eventDisplayName: `Security signal: ${event.type.replace(/_/g, " ")}`,
2021
2061
  eventData: {
2022
2062
  type: event.type,
2023
2063
  userId: event.userId || void 0,
@@ -2036,31 +2076,7 @@ const sentinel = (options) => {
2036
2076
  apiKey: opts.apiKey,
2037
2077
  apiUrl: opts.apiUrl,
2038
2078
  kvUrl: opts.kvUrl,
2039
- onDisposableEmail: (data) => {
2040
- const isNoMxRecord = data.reason === "no_mx_records";
2041
- const reason = isNoMxRecord ? "no_mx_records" : "disposable_email";
2042
- const label = isNoMxRecord ? "No MX Records" : "Disposable Email";
2043
- const actionLabel = data.action === "allow" ? "allowed" : "blocked";
2044
- const actionVerb = data.action === "allow" ? "detected" : "blocked";
2045
- const eventType = data.action === "allow" ? "security_allowed" : "security_blocked";
2046
- const displayName = isNoMxRecord ? `Security: invalid email domain ${actionVerb}` : `Security: disposable email ${actionVerb}`;
2047
- const description = isNoMxRecord ? `${actionVerb.charAt(0).toUpperCase() + actionVerb.slice(1)} signup attempt with invalid email domain (no MX records): ${data.email}` : `${actionVerb.charAt(0).toUpperCase() + actionVerb.slice(1)} signup attempt with disposable email: ${data.email} (${data.reason}, ${data.confidence} confidence)`;
2048
- logger.info(`[Sentinel] Tracking ${reason} event for email: ${data.email} (action: ${actionLabel})`);
2049
- trackEvent({
2050
- eventKey: data.email,
2051
- eventType,
2052
- eventDisplayName: displayName,
2053
- eventData: {
2054
- action: actionLabel,
2055
- reason,
2056
- identifier: data.email,
2057
- detectionLabel: label,
2058
- description,
2059
- path: data.path
2060
- },
2061
- ipAddress: data.ip
2062
- });
2063
- }
2079
+ trackEvent
2064
2080
  });
2065
2081
  return {
2066
2082
  id: "sentinel",
@@ -2075,8 +2091,16 @@ const sentinel = (options) => {
2075
2091
  if (!ctx) return;
2076
2092
  const visitorId = ctx.context.visitorId;
2077
2093
  if (visitorId && opts.security?.freeTrialAbuse?.enabled) {
2094
+ recordCheck(ctx, "free_trial_abuse");
2078
2095
  const abuseCheck = await securityService.checkFreeTrialAbuse(visitorId);
2079
- if (abuseCheck.isAbuse && abuseCheck.action === "block") throw new APIError("FORBIDDEN", { message: "Account creation is not allowed from this device." });
2096
+ if (abuseCheck.isAbuse && abuseCheck.action === "block") {
2097
+ setOutcome(ctx, "blocked", "free_trial_abuse", {
2098
+ accountCount: abuseCheck.accountCount,
2099
+ maxAccounts: abuseCheck.maxAccounts
2100
+ });
2101
+ emitEvaluation(ctx, trackEvent);
2102
+ throw new APIError("FORBIDDEN", { message: "Account creation is not allowed from this device." });
2103
+ }
2080
2104
  }
2081
2105
  if (user.email && typeof user.email === "string" && isEmailNormalizationEnabled(opts.security)) return { data: {
2082
2106
  ...user,
@@ -2095,10 +2119,29 @@ const sentinel = (options) => {
2095
2119
  const visitorId = ctx.context.visitorId;
2096
2120
  const identification = ctx.context.identification;
2097
2121
  if (session.userId && identification?.location && visitorId) {
2122
+ recordCheck(ctx, "impossible_travel");
2098
2123
  const travelCheck = await securityService.checkImpossibleTravel(session.userId, identification.location, visitorId);
2099
2124
  if (travelCheck?.isImpossible) {
2100
- if (travelCheck.action === "block") throw new APIError("FORBIDDEN", { message: "Login blocked due to suspicious location change." });
2101
- if (travelCheck.action === "challenge" && travelCheck.challenge) throwChallengeError(travelCheck.challenge, "impossible_travel", "Unusual login location detected. Please complete a security check.");
2125
+ if (travelCheck.action === "block") {
2126
+ setOutcome(ctx, "blocked", "impossible_travel", {
2127
+ from: travelCheck.from,
2128
+ to: travelCheck.to,
2129
+ distance: travelCheck.distance,
2130
+ speedRequired: travelCheck.speedRequired
2131
+ });
2132
+ emitEvaluation(ctx, trackEvent);
2133
+ throw new APIError("FORBIDDEN", { message: "Login blocked due to suspicious location change." });
2134
+ }
2135
+ if (travelCheck.action === "challenge" && travelCheck.challenge) {
2136
+ setOutcome(ctx, "challenged", "impossible_travel", {
2137
+ from: travelCheck.from,
2138
+ to: travelCheck.to,
2139
+ distance: travelCheck.distance,
2140
+ speedRequired: travelCheck.speedRequired
2141
+ });
2142
+ emitEvaluation(ctx, trackEvent);
2143
+ throwChallengeError(travelCheck.challenge, "impossible_travel", "Unusual login location detected. Please complete a security check.");
2144
+ }
2102
2145
  }
2103
2146
  }
2104
2147
  },
@@ -2128,6 +2171,7 @@ const sentinel = (options) => {
2128
2171
  if (await securityService.checkUnknownDevice(session.userId, visitorId) && user?.email) await ctx.context.runInBackgroundOrAwait(securityService.notifyUnknownDevice(session.userId, user.email, identification));
2129
2172
  }
2130
2173
  if (opts.security?.staleUsers?.enabled && user) {
2174
+ recordCheck(ctx, "stale_users");
2131
2175
  const lastActiveAtForStale = activityTrackingEnabled ? user.lastActiveAt ?? null : null;
2132
2176
  const staleCheck = await securityService.checkStaleUser(session.userId, lastActiveAtForStale);
2133
2177
  if (staleCheck.isStale) {
@@ -2138,31 +2182,21 @@ const sentinel = (options) => {
2138
2182
  if (notificationPromises.length > 0) Promise.all(notificationPromises).catch((error) => {
2139
2183
  logger.error("[Sentinel] Failed to send stale account notifications:", error);
2140
2184
  });
2141
- trackEvent({
2142
- eventKey: session.userId,
2143
- eventType: "security_stale_account",
2144
- eventDisplayName: "Security: stale account reactivated",
2145
- eventData: {
2146
- action: staleCheck.action === "block" ? "blocked" : staleCheck.action === "challenge" ? "challenged" : "logged",
2147
- reason: "stale_account_reactivation",
2185
+ if (staleCheck.action === "block") {
2186
+ setOutcome(ctx, "blocked", "stale_users", {
2148
2187
  userId: session.userId,
2149
2188
  daysSinceLastActive: staleCheck.daysSinceLastActive,
2150
2189
  staleDays: staleCheck.staleDays,
2151
2190
  lastActiveAt: staleCheck.lastActiveAt,
2152
2191
  notifyUser: staleCheck.notifyUser,
2153
- notifyAdmin: staleCheck.notifyAdmin,
2154
- detectionLabel: "Stale Account Reactivation",
2155
- description: `Dormant account (inactive for ${staleCheck.daysSinceLastActive} days) became active`
2156
- },
2157
- ipAddress: identification?.ip || void 0,
2158
- city: identification?.location?.city || void 0,
2159
- country: identification?.location?.country?.name || void 0,
2160
- countryCode: identification?.location?.country?.code || void 0
2161
- });
2162
- if (staleCheck.action === "block") throw new APIError("FORBIDDEN", {
2163
- message: "This account has been inactive for an extended period. Please contact support to reactivate.",
2164
- code: "STALE_ACCOUNT"
2165
- });
2192
+ notifyAdmin: staleCheck.notifyAdmin
2193
+ });
2194
+ emitEvaluation(ctx, trackEvent);
2195
+ throw new APIError("FORBIDDEN", {
2196
+ message: "This account has been inactive for an extended period. Please contact support to reactivate.",
2197
+ code: "STALE_ACCOUNT"
2198
+ });
2199
+ }
2166
2200
  }
2167
2201
  }
2168
2202
  if (identification?.location) await ctx.context.runInBackgroundOrAwait(securityService.storeLastLocation(session.userId, identification.location));
@@ -2225,52 +2259,23 @@ const sentinel = (options) => {
2225
2259
  const requestBody = ctx.body;
2226
2260
  const identifier = requestBody?.email || requestBody?.phone || requestBody?.username || void 0;
2227
2261
  const powVerified = ctx.context.powVerified === true;
2228
- if (visitorId && powVerified) trackEvent({
2229
- eventKey: visitorId,
2230
- eventType: "security_allowed",
2231
- eventDisplayName: "Security: challenge completed",
2232
- eventData: {
2233
- action: "allowed",
2234
- reason: "pow_verified",
2235
- visitorId,
2236
- path: ctx.path,
2237
- userAgent: ctx.headers?.get?.("user-agent") || "",
2238
- identifier,
2239
- detectionLabel: "Challenge Completed",
2240
- description: identifier ? `Successfully completed security challenge for "${identifier}"` : "Successfully completed security challenge"
2241
- },
2242
- ipAddress: identification?.ip || void 0,
2243
- city: identification?.location?.city || void 0,
2244
- country: identification?.location?.country?.name || void 0,
2245
- countryCode: identification?.location?.country?.code || void 0
2246
- });
2262
+ if (visitorId && powVerified) recordCheck(ctx, "pow_challenge");
2247
2263
  if (visitorId) {
2264
+ recordCheck(ctx, "credential_stuffing");
2248
2265
  if (await securityService.isBlocked(visitorId)) {
2249
- trackEvent({
2250
- eventKey: visitorId,
2251
- eventType: "security_blocked",
2252
- eventDisplayName: "Security: credential stuffing blocked",
2253
- eventData: {
2254
- action: "blocked",
2255
- reason: "credential_stuffing",
2256
- visitorId,
2257
- path: ctx.path,
2258
- userAgent: ctx.headers?.get?.("user-agent") || "",
2259
- identifier,
2260
- detectionType: "cooldown_active",
2261
- detectionLabel: "Credential Stuffing",
2262
- description: identifier ? `Visitor attempting "${identifier}" still in cooldown from prior detection` : "Visitor still in cooldown from prior detection",
2263
- confidence: 1
2264
- },
2265
- ipAddress: identification?.ip || void 0,
2266
- city: identification?.location?.city || void 0,
2267
- country: identification?.location?.country?.name || void 0,
2268
- countryCode: identification?.location?.country?.code || void 0
2266
+ setOutcome(ctx, "blocked", "credential_stuffing", {
2267
+ reason: "cooldown_active",
2268
+ visitorId,
2269
+ confidence: 1
2270
+ });
2271
+ emitEvaluation(ctx, trackEvent, {
2272
+ identifier,
2273
+ userAgent: ctx.headers?.get?.("user-agent") || ""
2269
2274
  });
2270
2275
  throw new APIError(403, { message: "Too many failed attempts. Please try again later." });
2271
2276
  }
2272
2277
  }
2273
- await runSecurityChecks({
2278
+ await runSecurityChecks(ctx, {
2274
2279
  path: ctx.path,
2275
2280
  identifier,
2276
2281
  visitorId,
@@ -2286,12 +2291,20 @@ const sentinel = (options) => {
2286
2291
  const body = ctx.body;
2287
2292
  const passwordToCheck = body?.newPassword || body?.password;
2288
2293
  if (passwordToCheck) {
2294
+ recordCheck(ctx, "compromised_password");
2289
2295
  const compromisedResult = await securityService.checkCompromisedPassword(passwordToCheck);
2290
2296
  if (compromisedResult.compromised && compromisedResult.action) {
2291
- if (compromisedResult.action === "block") throw new APIError("BAD_REQUEST", {
2292
- message: "This password has been found in data breaches. Please choose a different password.",
2293
- code: "COMPROMISED_PASSWORD"
2294
- });
2297
+ if (compromisedResult.action === "block") {
2298
+ setOutcome(ctx, "blocked", "compromised_password", { breachCount: compromisedResult.breachCount });
2299
+ emitEvaluation(ctx, trackEvent, {
2300
+ identifier,
2301
+ userAgent: ctx.headers?.get?.("user-agent") || ""
2302
+ });
2303
+ throw new APIError("BAD_REQUEST", {
2304
+ message: "This password has been found in data breaches. Please choose a different password.",
2305
+ code: "COMPROMISED_PASSWORD"
2306
+ });
2307
+ }
2295
2308
  }
2296
2309
  }
2297
2310
  }
@@ -2304,6 +2317,10 @@ const sentinel = (options) => {
2304
2317
  const visitorId = ctx.context.visitorId;
2305
2318
  const identification = ctx.context.identification;
2306
2319
  const body = ctx.body;
2320
+ emitEvaluation(ctx, trackEvent, {
2321
+ identifier: body?.email,
2322
+ userAgent: ctx.headers?.get?.("user-agent") || ""
2323
+ });
2307
2324
  if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_EMAIL, routes.SIGN_IN_EMAIL_OTP]) && ctx.context.returned instanceof Error && body?.email && body?.password && visitorId) {
2308
2325
  const { email, password } = body;
2309
2326
  const ip = identification?.ip || null;
@@ -2754,7 +2771,7 @@ const jwtValidateMiddleware = (options) => createAuthMiddleware(async (ctx) => {
2754
2771
  });
2755
2772
  //#endregion
2756
2773
  //#region src/version.ts
2757
- const PLUGIN_VERSION = "0.2.5";
2774
+ const PLUGIN_VERSION = "0.2.6";
2758
2775
  //#endregion
2759
2776
  //#region src/routes/auth/config.ts
2760
2777
  const PLUGIN_OPTIONS_EXCLUDE_KEYS = { stripe: new Set(["stripeClient"]) };
@@ -3068,6 +3085,12 @@ function getScimEndpoint(baseUrl) {
3068
3085
  function getSCIMPlugin(ctx) {
3069
3086
  return ctx.context.getPlugin("scim");
3070
3087
  }
3088
+ const DIRECTORY_SYNC_DUPLICATE_MESSAGE = "A directory sync connection with this provider ID already exists. Remove the existing connection or choose a different provider ID.";
3089
+ function isDuplicateDirectorySyncError(e) {
3090
+ if (e instanceof APIError$1) return e.status === "CONFLICT" || /duplicate|already exists|unique constraint/i.test(e.message ?? "");
3091
+ if (e instanceof Error) return /unique constraint|duplicate key|already exists|P2002/i.test(e.message);
3092
+ return false;
3093
+ }
3071
3094
  const listOrganizationDirectories = (options) => {
3072
3095
  return createAuthEndpoint("/dash/organization/:id/directories", {
3073
3096
  method: "GET",
@@ -3118,14 +3141,22 @@ const createOrganizationDirectory = (options) => {
3118
3141
  const scimPlugin = getSCIMPlugin(ctx);
3119
3142
  if (!scimPlugin?.endpoints.generateSCIMToken || !scimPlugin?.options?.providerOwnership?.enabled) throw ctx.error("BAD_REQUEST", { message: "SCIM plugin is not enabled or does not support this feature" });
3120
3143
  const { providerId, ownerUserId } = ctx.body;
3121
- const { scimToken } = await scimPlugin.endpoints.generateSCIMToken({
3122
- body: {
3123
- providerId,
3124
- organizationId
3125
- },
3126
- context: await buildSyntheticSession(ctx, ownerUserId),
3127
- headers: ctx.request?.headers ?? new Headers()
3128
- });
3144
+ let scimToken;
3145
+ try {
3146
+ scimToken = (await scimPlugin.endpoints.generateSCIMToken({
3147
+ body: {
3148
+ providerId,
3149
+ organizationId
3150
+ },
3151
+ context: await buildSyntheticSession(ctx, ownerUserId),
3152
+ headers: ctx.request?.headers ?? new Headers()
3153
+ })).scimToken;
3154
+ } catch (e) {
3155
+ if (isDuplicateDirectorySyncError(e)) throw ctx.error("BAD_REQUEST", { message: DIRECTORY_SYNC_DUPLICATE_MESSAGE });
3156
+ ctx.context.logger.error("[Dash] Failed to create directory sync provider (organizationId=%s, providerId=%s)", organizationId, providerId, e);
3157
+ if (e instanceof APIError$1) throw e;
3158
+ throw ctx.error("INTERNAL_SERVER_ERROR", { message: "Failed to create directory sync provider" });
3159
+ }
3129
3160
  if (!scimToken) throw ctx.error("INTERNAL_SERVER_ERROR", { message: "Failed to create directory sync provider" });
3130
3161
  return {
3131
3162
  organizationId,
@@ -3234,8 +3265,11 @@ const getUserEvents = (options) => {
3234
3265
  method: "GET",
3235
3266
  use: [sessionMiddleware],
3236
3267
  query: z$1.object({
3268
+ /** Maximum number of events to return (default: 50, max: 100) */
3237
3269
  limit: z$1.number().or(z$1.string().transform(Number)).optional(),
3270
+ /** Number of events to skip for pagination (default: 0) */
3238
3271
  offset: z$1.number().or(z$1.string().transform(Number)).optional(),
3272
+ /** Filter by event type (e.g., "user_signed_in") */
3239
3273
  eventType: z$1.string().optional()
3240
3274
  }).optional()
3241
3275
  }, async (ctx) => {
@@ -3383,6 +3417,113 @@ const getAuditLogs = (options) => {
3383
3417
  };
3384
3418
  });
3385
3419
  };
3420
+ const OWNER_ADMIN_ROLES = new Set(["owner", "admin"]);
3421
+ function isOwnerOrAdminRole(role) {
3422
+ return role !== void 0 && OWNER_ADMIN_ROLES.has(role);
3423
+ }
3424
+ /**
3425
+ * Get all audit logs the current user has access to
3426
+ * Queries can be restricted to a single organization, a single user, or all organizations the user has access to
3427
+ * @param options
3428
+ * @returns
3429
+ */
3430
+ const getAllAuditLogs = (options) => {
3431
+ const $fetch = createFetch({
3432
+ baseURL: options.apiUrl,
3433
+ headers: { "x-api-key": options.apiKey }
3434
+ });
3435
+ const getUserOrganizationRole = async (adapter, orgId, userId) => {
3436
+ return (await adapter.findOne({
3437
+ model: "member",
3438
+ where: [{
3439
+ field: "organizationId",
3440
+ value: orgId
3441
+ }, {
3442
+ field: "userId",
3443
+ value: userId
3444
+ }],
3445
+ select: ["role"]
3446
+ }))?.role;
3447
+ };
3448
+ const listElevatedOrganizationIds = async (adapter, userId) => {
3449
+ const memberships = await adapter.findMany({
3450
+ model: "member",
3451
+ where: [{
3452
+ field: "userId",
3453
+ value: userId
3454
+ }],
3455
+ select: ["organizationId", "role"]
3456
+ });
3457
+ const ids = /* @__PURE__ */ new Set();
3458
+ for (const m of memberships) if (isOwnerOrAdminRole(m.role)) ids.add(m.organizationId);
3459
+ return [...ids];
3460
+ };
3461
+ return createAuthEndpoint("/events/all-audit-logs", {
3462
+ method: "GET",
3463
+ use: [sessionMiddleware],
3464
+ query: z$1.object({
3465
+ limit: z$1.number().or(z$1.string().transform(Number)).optional(),
3466
+ offset: z$1.number().or(z$1.string().transform(Number)).optional(),
3467
+ userId: z$1.string().optional(),
3468
+ organizationId: z$1.string().optional(),
3469
+ /** Filter by event type (e.g. `organization_member_added`) */
3470
+ eventType: z$1.string().optional(),
3471
+ /** Match `eventData.identifier` (organization-scoped actor identity) */
3472
+ identifier: z$1.string().optional()
3473
+ }).refine((q) => {
3474
+ const u = q.userId?.trim();
3475
+ const o = q.organizationId?.trim();
3476
+ return !(u && o);
3477
+ }, { message: "Provide at most one of userId and organizationId." }).optional()
3478
+ }, async (ctx) => {
3479
+ const session = ctx.context.session;
3480
+ if (!session?.user?.id) throw ctx.error("UNAUTHORIZED", { message: "You must be signed in to view activity logs" });
3481
+ if (!options.apiKey) throw ctx.error("INTERNAL_SERVER_ERROR", { message: "Events API is not configured" });
3482
+ const query = ctx.query ?? {};
3483
+ const adapter = ctx.context.adapter;
3484
+ const sessionUserId = session.user.id;
3485
+ const limit = Math.min(Math.max(1, query.limit ?? 50), 100);
3486
+ const offset = Math.max(0, query.offset ?? 0);
3487
+ const organizationId = query.organizationId?.trim();
3488
+ const userId = query.userId?.trim();
3489
+ const forbidAccess = () => {
3490
+ return ctx.error("FORBIDDEN", { message: "Only organization owners and admins can view activity logs." });
3491
+ };
3492
+ const elevatedOrgIds = await listElevatedOrganizationIds(adapter, sessionUserId);
3493
+ if (organizationId) {
3494
+ if (!isOwnerOrAdminRole(await getUserOrganizationRole(adapter, organizationId, sessionUserId))) throw forbidAccess();
3495
+ } else if (userId) {
3496
+ if (elevatedOrgIds.length === 0) throw forbidAccess();
3497
+ } else if (elevatedOrgIds.length === 0) throw forbidAccess();
3498
+ const apiQuery = {
3499
+ limit: limit.toString(),
3500
+ offset: offset.toString()
3501
+ };
3502
+ if (organizationId) apiQuery.organizationIds = organizationId;
3503
+ else if (userId) {
3504
+ apiQuery.userId = userId;
3505
+ apiQuery.organizationIds = elevatedOrgIds.join(",");
3506
+ } else apiQuery.organizationIds = elevatedOrgIds.join(",");
3507
+ const eventTypeFilter = query.eventType?.trim();
3508
+ const identifierFilter = query.identifier?.trim();
3509
+ if (eventTypeFilter) apiQuery.eventType = eventTypeFilter;
3510
+ if (identifierFilter) apiQuery.identifier = identifierFilter;
3511
+ const { data, error } = await $fetch("/events/activity", {
3512
+ method: "GET",
3513
+ query: apiQuery
3514
+ });
3515
+ if (error || !data) {
3516
+ ctx.context.logger.error("[Dash] Failed to fetch activity logs:", error);
3517
+ throw ctx.error("INTERNAL_SERVER_ERROR", { message: "Failed to fetch activity logs" });
3518
+ }
3519
+ return {
3520
+ events: data.events.map(transformEvent),
3521
+ total: data.total,
3522
+ limit: data.limit,
3523
+ offset: data.offset
3524
+ };
3525
+ });
3526
+ };
3386
3527
  //#endregion
3387
3528
  //#region src/routes/execute-adapter/index.ts
3388
3529
  const whereClause = z.object({
@@ -6674,7 +6815,7 @@ const updateUser = (options) => createAuthEndpoint("/dash/update-user", {
6674
6815
  const updateData = ctx.body;
6675
6816
  const userId = ctx.context.payload?.userId;
6676
6817
  if (!userId) throw new APIError("FORBIDDEN", { message: "Invalid payload" });
6677
- const filteredData = Object.fromEntries(Object.entries(updateData).filter(([_, value]) => value !== void 0));
6818
+ const filteredData = Object.fromEntries(Object.entries(updateData).filter(([, value]) => value !== void 0));
6678
6819
  if (Object.keys(filteredData).length === 0) throw new APIError("BAD_REQUEST", { message: "No valid fields to update" });
6679
6820
  const user = await ctx.context.internalAdapter.updateUser(userId, {
6680
6821
  ...filteredData,
@@ -7874,6 +8015,7 @@ const dash = (options) => {
7874
8015
  dashGenerateBackupCodes: generateBackupCodes(opts),
7875
8016
  getUserEvents: getUserEvents(opts),
7876
8017
  getAuditLogs: getAuditLogs(opts),
8018
+ getAllAuditLogs: getAllAuditLogs(opts),
7877
8019
  getEventTypes: getEventTypes(opts),
7878
8020
  dashAcceptInvitation: acceptInvitation(opts),
7879
8021
  dashCompleteInvitation: completeInvitation(opts),
package/dist/native.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as dashClient, i as DashGetAuditLogsInput, n as DashAuditLogsResponse, r as DashClientOptions, t as DashAuditLog } from "./dash-client-hJHp7l_X.mjs";
1
+ import { a as DashGetAuditLogsInput, i as DashGetAllAuditLogsInput, n as DashAuditLogsResponse, o as dashClient, r as DashClientOptions, t as DashAuditLog } from "./dash-client-CVCHbtoB.mjs";
2
2
  import { BetterAuthClientPlugin } from "better-auth";
3
3
 
4
4
  //#region src/sentinel/native/client.d.ts
@@ -15,4 +15,4 @@ interface SentinelNativeClientOptions {
15
15
  }
16
16
  declare const sentinelNativeClient: (options?: SentinelNativeClientOptions) => BetterAuthClientPlugin;
17
17
  //#endregion
18
- export { type DashAuditLog, type DashAuditLogsResponse, type DashClientOptions, type DashGetAuditLogsInput, type SentinelNativeClientOptions, dashClient, sentinelNativeClient };
18
+ export { type DashAuditLog, type DashAuditLogsResponse, type DashClientOptions, type DashGetAllAuditLogsInput, type DashGetAuditLogsInput, type SentinelNativeClientOptions, dashClient, sentinelNativeClient };
package/dist/native.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as identify, c as dashClient, n as encodePoWSolution, o as bytesToHex, r as solvePoWChallenge, t as decodePoWChallenge } from "./pow-BUuN_EKw.mjs";
1
+ import { a as identify, c as dashClient, n as encodePoWSolution, o as bytesToHex, r as solvePoWChallenge, t as decodePoWChallenge } from "./pow-CT9ehp8e.mjs";
2
2
  import { env } from "@better-auth/core/env";
3
3
  import { Dimensions, InteractionManager, PixelRatio, Platform } from "react-native";
4
4
  //#region src/sentinel/native/components.ts
@@ -10,21 +10,39 @@ function resolveDashUserId(input, options) {
10
10
  const dashClient = (options) => {
11
11
  return {
12
12
  id: "dash",
13
- getActions: ($fetch) => ({ dash: { getAuditLogs: async (input = {}) => {
14
- const userId = resolveDashUserId(input, options);
15
- return $fetch("/events/audit-logs", {
16
- method: "GET",
17
- query: {
18
- limit: input.limit,
19
- offset: input.offset,
20
- organizationId: input.organizationId,
21
- identifier: input.identifier,
22
- eventType: input.eventType,
23
- userId
24
- }
25
- });
26
- } } }),
27
- pathMethods: { "/events/audit-logs": "GET" }
13
+ getActions: ($fetch) => ({ dash: {
14
+ getAuditLogs: async (input = {}) => {
15
+ const userId = resolveDashUserId(input, options);
16
+ return $fetch("/events/audit-logs", {
17
+ method: "GET",
18
+ query: {
19
+ limit: input.limit,
20
+ offset: input.offset,
21
+ organizationId: input.organizationId,
22
+ identifier: input.identifier,
23
+ eventType: input.eventType,
24
+ userId
25
+ }
26
+ });
27
+ },
28
+ getAllAuditLogs: async (input = {}) => {
29
+ return $fetch("/events/all-audit-logs", {
30
+ method: "GET",
31
+ query: {
32
+ limit: input.limit,
33
+ offset: input.offset,
34
+ organizationId: input.organizationId,
35
+ userId: input.userId,
36
+ eventType: input.eventType,
37
+ identifier: input.identifier
38
+ }
39
+ });
40
+ }
41
+ } }),
42
+ pathMethods: {
43
+ "/events/audit-logs": "GET",
44
+ "/events/all-audit-logs": "GET"
45
+ }
28
46
  };
29
47
  };
30
48
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/infra",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Dashboard and analytics plugin for Better Auth",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -11,9 +11,9 @@
11
11
  "build": "tsdown",
12
12
  "dev": "tsdown --watch --no-clean",
13
13
  "typecheck": "tsc --noEmit",
14
- "test": "bun test",
14
+ "test": "bun test --parallel",
15
15
  "test:watch": "bun test --watch",
16
- "coverage": "bun test --coverage"
16
+ "coverage": "bun test --coverage --parallel"
17
17
  },
18
18
  "exports": {
19
19
  ".": {
@@ -69,8 +69,8 @@
69
69
  "better-auth": "catalog:",
70
70
  "expo-crypto": "^14.0.2",
71
71
  "happy-dom": "^20.9.0",
72
- "msw": "^2.13.3",
73
- "tsdown": "^0.21.9",
72
+ "msw": "^2.14.3",
73
+ "tsdown": "^0.21.10",
74
74
  "typescript": "catalog:",
75
75
  "zod": "catalog:"
76
76
  },
@@ -78,7 +78,7 @@
78
78
  "@better-fetch/fetch": "^1.1.21",
79
79
  "better-call": "^1.3.3",
80
80
  "jose": "^6.1.0",
81
- "libphonenumber-js": "^1.12.36"
81
+ "libphonenumber-js": "^1.12.42"
82
82
  },
83
83
  "peerDependencies": {
84
84
  "better-auth": ">=1.4.0",