@better-auth/infra 0.2.12 → 0.2.13

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.d.mts CHANGED
@@ -132,6 +132,7 @@ interface ImpossibleTravelResult {
132
132
  action?: "allow" | "challenge" | "block";
133
133
  challenged?: boolean;
134
134
  challenge?: string;
135
+ powVerified?: boolean;
135
136
  distance?: number;
136
137
  timeElapsedHours?: number;
137
138
  speedRequired?: number;
@@ -774,7 +775,7 @@ interface DashIdRow {
774
775
  id: string;
775
776
  }
776
777
  //#endregion
777
- //#region ../../node_modules/.bun/@better-auth+scim@1.6.11+25e6a1339d5195dd/node_modules/@better-auth/scim/dist/index.d.mts
778
+ //#region ../../node_modules/.bun/@better-auth+scim@1.6.14+790dbc3a881c5018/node_modules/@better-auth/scim/dist/index.d.mts
778
779
  //#region src/types.d.ts
779
780
  interface SCIMProvider {
780
781
  id: string;
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import { n as INFRA_API_URL, r as INFRA_KV_URL } from "./constants-CvriWQVc.mjs";
2
2
  import { n as createKV, t as createAPI } from "./fetch-DiAhoiKA.mjs";
3
3
  import { EMAIL_TEMPLATES, createEmailSender, sendBulkEmails, sendEmail } from "./email.mjs";
4
+ import { getCurrentAuthContext } from "@better-auth/core/context";
4
5
  import { APIError, generateId, getAuthTables, logger, parseState } from "better-auth";
5
6
  import { env } from "@better-auth/core/env";
6
7
  import { APIError as APIError$1, createAuthEndpoint, createAuthMiddleware, requestPasswordReset, sendVerificationEmailFn, sessionMiddleware } from "better-auth/api";
7
- import { getCurrentAuthContext } from "@better-auth/core/context";
8
8
  import { createFetch } from "@better-fetch/fetch";
9
9
  import { isValidPhoneNumber, parsePhoneNumberFromString } from "libphonenumber-js";
10
10
  import { createLocalJWKSet, jwtVerify } from "jose";
@@ -108,6 +108,7 @@ const routes = {
108
108
  DASH_ROUTE: "/dash",
109
109
  DASH_UPDATE_USER: "/dash/update-user",
110
110
  DASH_REVOKE_SESSIONS_ALL: "/dash/sessions/revoke-all",
111
+ DASH_IMPERSONATE_USER: "/dash/impersonate-user",
111
112
  DASH_BAN_USER: "/dash/ban-user",
112
113
  DASH_UNBAN_USER: "/dash/unban-user",
113
114
  ADMIN_ROUTE: "/admin",
@@ -425,7 +426,7 @@ const initSessionEvents = (tracker) => {
425
426
  };
426
427
  ctx.context.runInBackground(track());
427
428
  };
428
- const trackSessionRevokedAll = (session, trigger, ctx, _location) => {
429
+ const trackSessionRevokedAll = (session, trigger, ctx, location) => {
429
430
  const track = async () => {
430
431
  const user = await getUserById(session.userId, ctx);
431
432
  trackEvent({
@@ -438,7 +439,11 @@ const initSessionEvents = (tracker) => {
438
439
  userEmail: user?.email ?? "unknown",
439
440
  triggeredBy: trigger.triggeredBy,
440
441
  triggerContext: trigger.triggerContext
441
- }
442
+ },
443
+ ipAddress: location?.ipAddress,
444
+ city: location?.city,
445
+ country: location?.country,
446
+ countryCode: location?.countryCode
442
447
  });
443
448
  };
444
449
  ctx.context.runInBackground(track());
@@ -527,7 +532,7 @@ const initSessionEvents = (tracker) => {
527
532
  };
528
533
  ctx.context.runInBackground(track());
529
534
  };
530
- const trackEmailVerificationSent = (session, user, trigger, _ctx) => {
535
+ const trackEmailVerificationSent = (session, user, trigger, location) => {
531
536
  trackEvent({
532
537
  eventKey: session.userId,
533
538
  eventType: EVENT_TYPES.EMAIL_VERIFICATION_SENT,
@@ -539,10 +544,14 @@ const initSessionEvents = (tracker) => {
539
544
  sessionId: session.id,
540
545
  triggeredBy: trigger.triggeredBy,
541
546
  triggerContext: trigger.triggerContext
542
- }
547
+ },
548
+ ipAddress: location?.ipAddress,
549
+ city: location?.city,
550
+ country: location?.country,
551
+ countryCode: location?.countryCode
543
552
  });
544
553
  };
545
- const trackEmailSignInAttempt = (ctx, trigger) => {
554
+ const trackEmailSignInAttempt = (ctx, trigger, location) => {
546
555
  const track = async () => {
547
556
  const user = await getUserByEmail(ctx.body.email, ctx);
548
557
  trackEvent({
@@ -556,12 +565,16 @@ const initSessionEvents = (tracker) => {
556
565
  loginMethod: getLoginMethod(ctx),
557
566
  triggeredBy: user?.id ?? trigger.triggeredBy,
558
567
  triggerContext: trigger.triggerContext
559
- }
568
+ },
569
+ ipAddress: location?.ipAddress,
570
+ city: location?.city,
571
+ country: location?.country,
572
+ countryCode: location?.countryCode
560
573
  });
561
574
  };
562
575
  ctx.context.runInBackground(track());
563
576
  };
564
- const trackSocialSignInAttempt = (ctx, trigger) => {
577
+ const trackSocialSignInAttempt = (ctx, trigger, location) => {
565
578
  const track = async () => {
566
579
  const user = await getUserByIdToken(ctx.body.provider, ctx.body.idToken, ctx);
567
580
  trackEvent({
@@ -575,12 +588,16 @@ const initSessionEvents = (tracker) => {
575
588
  loginMethod: getLoginMethod(ctx),
576
589
  triggeredBy: user?.user.id ?? trigger.triggeredBy,
577
590
  triggerContext: trigger.triggerContext
578
- }
591
+ },
592
+ ipAddress: location?.ipAddress,
593
+ city: location?.city,
594
+ country: location?.country,
595
+ countryCode: location?.countryCode
579
596
  });
580
597
  };
581
598
  ctx.context.runInBackground(track());
582
599
  };
583
- const trackSocialSignInRedirectionAttempt = (ctx, trigger) => {
600
+ const trackSocialSignInRedirectionAttempt = (ctx, trigger, location) => {
584
601
  const track = async () => {
585
602
  const user = await getUserByAuthorizationCode(tryDecode(ctx.params?.id), ctx);
586
603
  trackEvent({
@@ -594,7 +611,11 @@ const initSessionEvents = (tracker) => {
594
611
  loginMethod: getLoginMethod(ctx),
595
612
  triggeredBy: trigger.triggeredBy,
596
613
  triggerContext: trigger.triggerContext
597
- }
614
+ },
615
+ ipAddress: location?.ipAddress,
616
+ city: location?.city,
617
+ country: location?.country,
618
+ countryCode: location?.countryCode
598
619
  });
599
620
  };
600
621
  ctx.context.runInBackground(track());
@@ -1241,7 +1262,7 @@ function createSecurityClient(conn, $api, options, onSecurityEvent) {
1241
1262
  return "";
1242
1263
  }
1243
1264
  },
1244
- async checkImpossibleTravel(userId, currentLocation, visitorId, ip = null) {
1265
+ async checkImpossibleTravel(userId, currentLocation, visitorId, ip = null, powSolution) {
1245
1266
  if (!options.impossibleTravel?.enabled || !currentLocation) return null;
1246
1267
  try {
1247
1268
  const data = await $api("/security/impossible-travel", {
@@ -1251,7 +1272,8 @@ function createSecurityClient(conn, $api, options, onSecurityEvent) {
1251
1272
  visitorId,
1252
1273
  location: currentLocation,
1253
1274
  ip,
1254
- config: options
1275
+ config: options,
1276
+ ...powSolution ? { powSolution } : {}
1255
1277
  }
1256
1278
  });
1257
1279
  if (data.isImpossible) {
@@ -2190,7 +2212,9 @@ const sentinel = (options) => {
2190
2212
  const identification = ctx.context.identification;
2191
2213
  if (session.userId && identification?.location && visitorId) {
2192
2214
  recordCheck(ctx, "impossible_travel");
2193
- const travelCheck = await securityService.checkImpossibleTravel(session.userId, identification.location, visitorId, identification.ip);
2215
+ const powSolution = ctx.headers?.get?.("X-PoW-Solution") ?? null;
2216
+ const travelCheck = await securityService.checkImpossibleTravel(session.userId, identification.location, visitorId, identification.ip, powSolution);
2217
+ if (travelCheck?.powVerified) recordCheck(ctx, "pow_challenge");
2194
2218
  if (travelCheck?.isImpossible) {
2195
2219
  if (travelCheck.action === "block") {
2196
2220
  setOutcome(ctx, "blocked", "impossible_travel", {
@@ -2399,7 +2423,7 @@ const sentinel = (options) => {
2399
2423
  //#region src/events/organization/events-invitation.ts
2400
2424
  const initInvitationEvents = (tracker) => {
2401
2425
  const { trackEvent } = tracker;
2402
- const trackOrganizationMemberInvited = (organization, invitation, inviter, trigger) => {
2426
+ const trackOrganizationMemberInvited = (organization, invitation, inviter, trigger, location) => {
2403
2427
  trackEvent({
2404
2428
  eventKey: organization.id,
2405
2429
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_INVITED,
@@ -2417,10 +2441,14 @@ const initInvitationEvents = (tracker) => {
2417
2441
  inviterEmail: inviter.email ?? "unknown",
2418
2442
  triggeredBy: trigger.triggeredBy,
2419
2443
  triggerContext: trigger.triggerContext
2420
- }
2444
+ },
2445
+ ipAddress: location?.ipAddress,
2446
+ city: location?.city,
2447
+ country: location?.country,
2448
+ countryCode: location?.countryCode
2421
2449
  });
2422
2450
  };
2423
- const trackOrganizationMemberInviteAccepted = (organization, invitation, member, acceptedBy, trigger) => {
2451
+ const trackOrganizationMemberInviteAccepted = (organization, invitation, member, acceptedBy, trigger, location) => {
2424
2452
  trackEvent({
2425
2453
  eventKey: organization.id,
2426
2454
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_INVITE_ACCEPTED,
@@ -2440,10 +2468,14 @@ const initInvitationEvents = (tracker) => {
2440
2468
  memberRole: member.role,
2441
2469
  triggeredBy: trigger.triggeredBy,
2442
2470
  triggerContext: trigger.triggerContext
2443
- }
2471
+ },
2472
+ ipAddress: location?.ipAddress,
2473
+ city: location?.city,
2474
+ country: location?.country,
2475
+ countryCode: location?.countryCode
2444
2476
  });
2445
2477
  };
2446
- const trackOrganizationMemberInviteRejected = (organization, invitation, rejectedBy, trigger) => {
2478
+ const trackOrganizationMemberInviteRejected = (organization, invitation, rejectedBy, trigger, location) => {
2447
2479
  trackEvent({
2448
2480
  eventKey: organization.id,
2449
2481
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_INVITE_REJECTED,
@@ -2461,10 +2493,14 @@ const initInvitationEvents = (tracker) => {
2461
2493
  rejectedByName: rejectedBy.name,
2462
2494
  triggeredBy: trigger.triggeredBy,
2463
2495
  triggerContext: trigger.triggerContext
2464
- }
2496
+ },
2497
+ ipAddress: location?.ipAddress,
2498
+ city: location?.city,
2499
+ country: location?.country,
2500
+ countryCode: location?.countryCode
2465
2501
  });
2466
2502
  };
2467
- const trackOrganizationMemberInviteCanceled = (organization, invitation, cancelledBy, trigger) => {
2503
+ const trackOrganizationMemberInviteCanceled = (organization, invitation, cancelledBy, trigger, location) => {
2468
2504
  trackEvent({
2469
2505
  eventKey: organization.id,
2470
2506
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_INVITE_CANCELED,
@@ -2482,7 +2518,11 @@ const initInvitationEvents = (tracker) => {
2482
2518
  cancelledByEmail: cancelledBy.email ?? "unknown",
2483
2519
  triggeredBy: trigger.triggeredBy,
2484
2520
  triggerContext: trigger.triggerContext
2485
- }
2521
+ },
2522
+ ipAddress: location?.ipAddress,
2523
+ city: location?.city,
2524
+ country: location?.country,
2525
+ countryCode: location?.countryCode
2486
2526
  });
2487
2527
  };
2488
2528
  return {
@@ -2496,7 +2536,7 @@ const initInvitationEvents = (tracker) => {
2496
2536
  //#region src/events/organization/events-member.ts
2497
2537
  const initMemberEvents = (tracker) => {
2498
2538
  const { trackEvent } = tracker;
2499
- const trackOrganizationMemberAdded = (organization, member, user, trigger) => {
2539
+ const trackOrganizationMemberAdded = (organization, member, user, trigger, location) => {
2500
2540
  trackEvent({
2501
2541
  eventKey: organization.id,
2502
2542
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_ADDED,
@@ -2512,10 +2552,14 @@ const initMemberEvents = (tracker) => {
2512
2552
  memberEmail: user.email ?? "unknown",
2513
2553
  triggeredBy: trigger.triggeredBy,
2514
2554
  triggerContext: trigger.triggerContext
2515
- }
2555
+ },
2556
+ ipAddress: location?.ipAddress,
2557
+ city: location?.city,
2558
+ country: location?.country,
2559
+ countryCode: location?.countryCode
2516
2560
  });
2517
2561
  };
2518
- const trackOrganizationMemberRemoved = (organization, member, user, trigger) => {
2562
+ const trackOrganizationMemberRemoved = (organization, member, user, trigger, location) => {
2519
2563
  trackEvent({
2520
2564
  eventKey: organization.id,
2521
2565
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_REMOVED,
@@ -2531,10 +2575,14 @@ const initMemberEvents = (tracker) => {
2531
2575
  memberEmail: user.email ?? "unknown",
2532
2576
  triggeredBy: trigger.triggeredBy,
2533
2577
  triggerContext: trigger.triggerContext
2534
- }
2578
+ },
2579
+ ipAddress: location?.ipAddress,
2580
+ city: location?.city,
2581
+ country: location?.country,
2582
+ countryCode: location?.countryCode
2535
2583
  });
2536
2584
  };
2537
- const trackOrganizationMemberRoleUpdated = (organization, member, user, previousRole, trigger) => {
2585
+ const trackOrganizationMemberRoleUpdated = (organization, member, user, previousRole, trigger, location) => {
2538
2586
  trackEvent({
2539
2587
  eventKey: organization.id,
2540
2588
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_ROLE_UPDATED,
@@ -2551,7 +2599,11 @@ const initMemberEvents = (tracker) => {
2551
2599
  memberEmail: user.email ?? "unknown",
2552
2600
  triggeredBy: trigger.triggeredBy,
2553
2601
  triggerContext: trigger.triggerContext
2554
- }
2602
+ },
2603
+ ipAddress: location?.ipAddress,
2604
+ city: location?.city,
2605
+ country: location?.country,
2606
+ countryCode: location?.countryCode
2555
2607
  });
2556
2608
  };
2557
2609
  return {
@@ -2564,7 +2616,7 @@ const initMemberEvents = (tracker) => {
2564
2616
  //#region src/events/organization/events-organization.ts
2565
2617
  const initOrganizationEvents = (tracker) => {
2566
2618
  const { trackEvent } = tracker;
2567
- const trackOrganizationCreated = (organization, trigger) => {
2619
+ const trackOrganizationCreated = (organization, trigger, location) => {
2568
2620
  trackEvent({
2569
2621
  eventKey: organization.id,
2570
2622
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_CREATED,
@@ -2575,10 +2627,14 @@ const initOrganizationEvents = (tracker) => {
2575
2627
  organizationName: organization.name,
2576
2628
  triggeredBy: trigger.triggeredBy,
2577
2629
  triggerContext: trigger.triggerContext
2578
- }
2630
+ },
2631
+ ipAddress: location?.ipAddress,
2632
+ city: location?.city,
2633
+ country: location?.country,
2634
+ countryCode: location?.countryCode
2579
2635
  });
2580
2636
  };
2581
- const trackOrganizationUpdated = (organization, trigger) => {
2637
+ const trackOrganizationUpdated = (organization, trigger, location) => {
2582
2638
  trackEvent({
2583
2639
  eventKey: organization.id,
2584
2640
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_UPDATED,
@@ -2589,7 +2645,11 @@ const initOrganizationEvents = (tracker) => {
2589
2645
  organizationName: organization.name,
2590
2646
  triggeredBy: trigger.triggeredBy,
2591
2647
  triggerContext: trigger.triggerContext
2592
- }
2648
+ },
2649
+ ipAddress: location?.ipAddress,
2650
+ city: location?.city,
2651
+ country: location?.country,
2652
+ countryCode: location?.countryCode
2593
2653
  });
2594
2654
  };
2595
2655
  return {
@@ -2601,7 +2661,7 @@ const initOrganizationEvents = (tracker) => {
2601
2661
  //#region src/events/organization/events-team.ts
2602
2662
  const initTeamEvents = (tracker) => {
2603
2663
  const { trackEvent } = tracker;
2604
- const trackOrganizationTeamCreated = (organization, team, trigger) => {
2664
+ const trackOrganizationTeamCreated = (organization, team, trigger, location) => {
2605
2665
  trackEvent({
2606
2666
  eventKey: organization.id,
2607
2667
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_TEAM_CREATED,
@@ -2614,10 +2674,14 @@ const initTeamEvents = (tracker) => {
2614
2674
  teamName: team.name,
2615
2675
  triggeredBy: trigger.triggeredBy,
2616
2676
  triggerContext: trigger.triggerContext
2617
- }
2677
+ },
2678
+ ipAddress: location?.ipAddress,
2679
+ city: location?.city,
2680
+ country: location?.country,
2681
+ countryCode: location?.countryCode
2618
2682
  });
2619
2683
  };
2620
- const trackOrganizationTeamUpdated = (organization, team, trigger) => {
2684
+ const trackOrganizationTeamUpdated = (organization, team, trigger, location) => {
2621
2685
  trackEvent({
2622
2686
  eventKey: organization.id,
2623
2687
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_TEAM_UPDATED,
@@ -2630,10 +2694,14 @@ const initTeamEvents = (tracker) => {
2630
2694
  teamName: team.name,
2631
2695
  triggeredBy: trigger.triggeredBy,
2632
2696
  triggerContext: trigger.triggerContext
2633
- }
2697
+ },
2698
+ ipAddress: location?.ipAddress,
2699
+ city: location?.city,
2700
+ country: location?.country,
2701
+ countryCode: location?.countryCode
2634
2702
  });
2635
2703
  };
2636
- const trackOrganizationTeamDeleted = (organization, team, trigger) => {
2704
+ const trackOrganizationTeamDeleted = (organization, team, trigger, location) => {
2637
2705
  trackEvent({
2638
2706
  eventKey: organization.id,
2639
2707
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_TEAM_DELETED,
@@ -2646,10 +2714,14 @@ const initTeamEvents = (tracker) => {
2646
2714
  teamName: team.name,
2647
2715
  triggeredBy: trigger.triggeredBy,
2648
2716
  triggerContext: trigger.triggerContext
2649
- }
2717
+ },
2718
+ ipAddress: location?.ipAddress,
2719
+ city: location?.city,
2720
+ country: location?.country,
2721
+ countryCode: location?.countryCode
2650
2722
  });
2651
2723
  };
2652
- const trackOrganizationTeamMemberAdded = (organization, team, user, teamMember, trigger) => {
2724
+ const trackOrganizationTeamMemberAdded = (organization, team, user, teamMember, trigger, location) => {
2653
2725
  trackEvent({
2654
2726
  eventKey: organization.id,
2655
2727
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_TEAM_MEMBER_ADDED,
@@ -2664,10 +2736,14 @@ const initTeamEvents = (tracker) => {
2664
2736
  memberName: user.name,
2665
2737
  triggeredBy: trigger.triggeredBy,
2666
2738
  triggerContext: trigger.triggerContext
2667
- }
2739
+ },
2740
+ ipAddress: location?.ipAddress,
2741
+ city: location?.city,
2742
+ country: location?.country,
2743
+ countryCode: location?.countryCode
2668
2744
  });
2669
2745
  };
2670
- const trackOrganizationTeamMemberRemoved = (organization, team, user, teamMember, trigger) => {
2746
+ const trackOrganizationTeamMemberRemoved = (organization, team, user, teamMember, trigger, location) => {
2671
2747
  trackEvent({
2672
2748
  eventKey: organization.id,
2673
2749
  eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_TEAM_MEMBER_REMOVED,
@@ -2682,7 +2758,11 @@ const initTeamEvents = (tracker) => {
2682
2758
  memberName: user.name,
2683
2759
  triggeredBy: trigger.triggeredBy,
2684
2760
  triggerContext: trigger.triggerContext
2685
- }
2761
+ },
2762
+ ipAddress: location?.ipAddress,
2763
+ city: location?.city,
2764
+ country: location?.country,
2765
+ countryCode: location?.countryCode
2686
2766
  });
2687
2767
  };
2688
2768
  return {
@@ -2837,7 +2917,7 @@ const jwtValidateMiddleware = (options) => {
2837
2917
  };
2838
2918
  //#endregion
2839
2919
  //#region src/version.ts
2840
- const PLUGIN_VERSION = "0.2.12";
2920
+ const PLUGIN_VERSION = "0.2.13";
2841
2921
  //#endregion
2842
2922
  //#region src/routes/auth/config.ts
2843
2923
  const PLUGIN_OPTIONS_EXCLUDE_KEYS = { stripe: new Set(["stripeClient"]) };
@@ -5717,7 +5797,7 @@ const revokeAllSessions = (options) => createAuthEndpoint("/dash/sessions/revoke
5717
5797
  }, async (ctx) => {
5718
5798
  const { userId } = ctx.body;
5719
5799
  if (!await ctx.context.internalAdapter.findUserById(userId)) throw ctx.error("NOT_FOUND", { message: "User not found" });
5720
- await ctx.context.internalAdapter.deleteSessions(userId);
5800
+ await ctx.context.internalAdapter.deleteUserSessions(userId);
5721
5801
  return ctx.json({ success: true });
5722
5802
  });
5723
5803
  const revokeManySessions = (options) => createAuthEndpoint("/dash/sessions/revoke-many", {
@@ -5726,7 +5806,7 @@ const revokeManySessions = (options) => createAuthEndpoint("/dash/sessions/revok
5726
5806
  }, async (ctx) => {
5727
5807
  const { userIds } = ctx.context.payload;
5728
5808
  await withConcurrency(chunkArray(userIds, { batchSize: 50 }), async (chunk) => {
5729
- for (const userId of chunk) await ctx.context.internalAdapter.deleteSessions(userId);
5809
+ for (const userId of chunk) await ctx.context.internalAdapter.deleteUserSessions(userId);
5730
5810
  }, { concurrency: 3 });
5731
5811
  return ctx.json({
5732
5812
  success: true,
@@ -5848,39 +5928,41 @@ async function resolveHostnameAddresses(hostname) {
5848
5928
  return null;
5849
5929
  }
5850
5930
  }
5851
- function validateUrlSync(rawUrl, options = {}) {
5852
- const trimmed = rawUrl.trim();
5931
+ function validateUrlSync(input, options = {}) {
5853
5932
  let parsed;
5854
- try {
5855
- parsed = new URL(trimmed);
5856
- } catch {
5857
- return {
5858
- ok: false,
5859
- reason: "invalid_url",
5860
- url: trimmed
5861
- };
5933
+ if (input instanceof URL) parsed = input;
5934
+ else {
5935
+ const trimmed = input.trim();
5936
+ try {
5937
+ parsed = new URL(trimmed);
5938
+ } catch {
5939
+ return {
5940
+ ok: false,
5941
+ reason: "invalid_url"
5942
+ };
5943
+ }
5862
5944
  }
5863
5945
  if (!isAllowedProtocol(parsed.protocol, options.allowedProtocols)) return {
5864
5946
  ok: false,
5865
5947
  reason: "disallowed_protocol",
5866
- url: trimmed,
5948
+ url: parsed,
5867
5949
  protocol: parsed.protocol
5868
5950
  };
5869
5951
  if (parsed.username !== "" || parsed.password !== "") return {
5870
5952
  ok: false,
5871
5953
  reason: "embedded_credentials",
5872
- url: trimmed
5954
+ url: parsed
5873
5955
  };
5874
5956
  if (isPrivateHost(parsed.hostname)) return {
5875
5957
  ok: false,
5876
5958
  reason: "private_host",
5877
- url: trimmed
5959
+ url: parsed
5878
5960
  };
5879
5961
  if (options.allowedOrigins?.length) {
5880
5962
  if (!isAllowedOrigin(parsed.origin, options.allowedOrigins)) return {
5881
5963
  ok: false,
5882
5964
  reason: "disallowed_origin",
5883
- url: trimmed,
5965
+ url: parsed,
5884
5966
  hostname: parsed.hostname,
5885
5967
  origin: parsed.origin
5886
5968
  };
@@ -5891,8 +5973,8 @@ function validateUrlSync(rawUrl, options = {}) {
5891
5973
  };
5892
5974
  }
5893
5975
  /** SSRF checks with DNS resolution when available. */
5894
- async function validateUrl(rawUrl, options = {}) {
5895
- const sync = validateUrlSync(rawUrl, options);
5976
+ async function validateUrl(input, options = {}) {
5977
+ const sync = validateUrlSync(input, options);
5896
5978
  if (!sync.ok) return sync;
5897
5979
  if (options.dns === false) return sync;
5898
5980
  const hostname = sync.url.hostname;
@@ -5900,19 +5982,23 @@ async function validateUrl(rawUrl, options = {}) {
5900
5982
  if (!isIpLiteralHostname(hostname) && !addresses?.length) return {
5901
5983
  ok: false,
5902
5984
  reason: "unresolved_host",
5903
- url: sync.url.href,
5985
+ url: sync.url,
5904
5986
  hostname
5905
5987
  };
5906
5988
  if (addresses) {
5907
5989
  for (const addr of addresses) if (isPrivateHost(addr)) return {
5908
5990
  ok: false,
5909
5991
  reason: "private_dns",
5910
- url: sync.url.href,
5992
+ url: sync.url,
5911
5993
  hostname,
5912
5994
  address: addr
5913
5995
  };
5914
5996
  }
5915
- return sync;
5997
+ return {
5998
+ ok: true,
5999
+ url: sync.url,
6000
+ ...addresses ? { addresses } : {}
6001
+ };
5916
6002
  }
5917
6003
  function safeResolveUrl(raw, baseURL) {
5918
6004
  const trim = raw.trim();
@@ -5928,12 +6014,6 @@ function safeResolveUrl(raw, baseURL) {
5928
6014
  }
5929
6015
  //#endregion
5930
6016
  //#region src/validation/ssrf.ts
5931
- /**
5932
- * SSRF protection for dash plugin outbound fetches.
5933
- *
5934
- * Uses @infra/utils at build time; tsdown bundles it into published dist so
5935
- * consumers do not install the private workspace package.
5936
- */
5937
6017
  const REDIRECT_STATUSES = new Set([
5938
6018
  301,
5939
6019
  302,
@@ -5948,11 +6028,6 @@ var SsrfBlockedError = class extends Error {
5948
6028
  function isSsrfBlockedError(error) {
5949
6029
  return error instanceof SsrfBlockedError;
5950
6030
  }
5951
- /** Validates URL hostname literals and resolved DNS addresses. */
5952
- async function parseAndValidateUrl(url) {
5953
- const result = await validateUrl(url);
5954
- return result.ok ? result.url : null;
5955
- }
5956
6031
  /**
5957
6032
  * Fetch with SSRF checks on the initial URL and every redirect target.
5958
6033
  */
@@ -5960,8 +6035,9 @@ async function $outbound(url, options = {}) {
5960
6035
  const { timeout, signal: callerSignal, ...fetchInit } = options;
5961
6036
  let currentUrl = url;
5962
6037
  for (let hop = 0;; hop++) {
5963
- const validated = await parseAndValidateUrl(currentUrl);
5964
- if (!validated) throw new SsrfBlockedError("Invalid or blocked URL");
6038
+ const result = await validateUrl(currentUrl);
6039
+ if (!result.ok) throw new SsrfBlockedError("Invalid or blocked URL");
6040
+ const validated = result.url;
5965
6041
  const signals = [];
5966
6042
  if (callerSignal) signals.push(callerSignal);
5967
6043
  if (timeout !== void 0 && timeout > 0) signals.push(AbortSignal.timeout(timeout));
@@ -5976,7 +6052,7 @@ async function $outbound(url, options = {}) {
5976
6052
  const location = response.headers.get("location");
5977
6053
  const next = location ? safeResolveUrl(location, validated.href) : null;
5978
6054
  if (!next) throw new SsrfBlockedError("Invalid redirect location");
5979
- currentUrl = next.href;
6055
+ currentUrl = next;
5980
6056
  }
5981
6057
  }
5982
6058
  //#endregion
@@ -6091,23 +6167,19 @@ const oidcConfigSchema = z$1.object({
6091
6167
  });
6092
6168
  async function resolveSAMLConfig(samlConfig, providerId, baseURL, ctx) {
6093
6169
  let idpMetadataXml = samlConfig.idpMetadata?.metadata;
6094
- if (!idpMetadataXml && samlConfig.idpMetadata?.metadataUrl) {
6095
- const validatedMetadataUrl = await parseAndValidateUrl(samlConfig.idpMetadata.metadataUrl);
6096
- if (!validatedMetadataUrl) throw ctx.error("BAD_REQUEST", { message: "Invalid or blocked IdP metadata URL" });
6097
- try {
6098
- const metadataResponse = await $outbound(validatedMetadataUrl.href, {
6099
- method: "GET",
6100
- headers: { Accept: "application/xml, text/xml" },
6101
- timeout: 15e3
6102
- });
6103
- if (!metadataResponse.ok) throw ctx.error("BAD_REQUEST", { message: `Failed to fetch IdP metadata from URL: ${metadataResponse.status} ${metadataResponse.statusText}` });
6104
- idpMetadataXml = await metadataResponse.text();
6105
- } catch (e) {
6106
- if (isSsrfBlockedError(e)) throw ctx.error("BAD_REQUEST", { message: "Invalid or blocked IdP metadata URL" });
6107
- if (e instanceof APIError$1) throw e;
6108
- ctx.context.logger.error("[Dash] Failed to fetch IdP metadata from URL:", e);
6109
- throw ctx.error("BAD_REQUEST", { message: "Failed to fetch IdP metadata from URL" });
6110
- }
6170
+ if (!idpMetadataXml && samlConfig.idpMetadata?.metadataUrl) try {
6171
+ const metadataResponse = await $outbound(samlConfig.idpMetadata.metadataUrl, {
6172
+ method: "GET",
6173
+ headers: { Accept: "application/xml, text/xml" },
6174
+ timeout: 15e3
6175
+ });
6176
+ if (!metadataResponse.ok) throw ctx.error("BAD_REQUEST", { message: `Failed to fetch IdP metadata from URL: ${metadataResponse.status} ${metadataResponse.statusText}` });
6177
+ idpMetadataXml = await metadataResponse.text();
6178
+ } catch (e) {
6179
+ if (isSsrfBlockedError(e)) throw ctx.error("BAD_REQUEST", { message: "Invalid or blocked IdP metadata URL" });
6180
+ if (e instanceof APIError$1) throw e;
6181
+ ctx.context.logger.error("[Dash] Failed to fetch IdP metadata from URL:", e);
6182
+ throw ctx.error("BAD_REQUEST", { message: "Failed to fetch IdP metadata from URL" });
6111
6183
  }
6112
6184
  const metadataAlgorithmWarnings = [];
6113
6185
  if (idpMetadataXml) {
@@ -7651,7 +7723,7 @@ const banUser = (options) => createAuthEndpoint("/dash/ban-user", {
7651
7723
  banExpires: banExpires ? new Date(banExpires) : null,
7652
7724
  updatedAt: /* @__PURE__ */ new Date()
7653
7725
  });
7654
- await ctx.context.internalAdapter.deleteSessions(userId);
7726
+ await ctx.context.internalAdapter.deleteUserSessions(userId);
7655
7727
  return { success: true };
7656
7728
  });
7657
7729
  const banManyUsers = (options) => {
@@ -7981,6 +8053,13 @@ async function sendSMS(options, config) {
7981
8053
  }
7982
8054
  //#endregion
7983
8055
  //#region src/index.ts
8056
+ async function getRequestLocation() {
8057
+ try {
8058
+ return (await getCurrentAuthContext()).context.location;
8059
+ } catch {
8060
+ return;
8061
+ }
8062
+ }
7984
8063
  const dash = (options) => {
7985
8064
  const processedBulkOperationContexts = /* @__PURE__ */ new WeakSet();
7986
8065
  const opts = resolveDashOptions(options);
@@ -8012,85 +8091,85 @@ const dash = (options) => {
8012
8091
  const afterCreateOrganization = organizationHooks.afterCreateOrganization;
8013
8092
  organizationHooks.afterCreateOrganization = async (...args) => {
8014
8093
  const [{ organization, user }] = args;
8015
- trackOrganizationCreated(organization, getOrganizationTriggerInfo(user));
8094
+ trackOrganizationCreated(organization, getOrganizationTriggerInfo(user), await getRequestLocation());
8016
8095
  if (afterCreateOrganization) return afterCreateOrganization(...args);
8017
8096
  };
8018
8097
  const afterUpdateOrganization = organizationHooks.afterUpdateOrganization;
8019
8098
  organizationHooks.afterUpdateOrganization = async (...args) => {
8020
8099
  const [{ organization, user }] = args;
8021
- if (organization) trackOrganizationUpdated(organization, getOrganizationTriggerInfo(user));
8100
+ if (organization) trackOrganizationUpdated(organization, getOrganizationTriggerInfo(user), await getRequestLocation());
8022
8101
  if (afterUpdateOrganization) return afterUpdateOrganization(...args);
8023
8102
  };
8024
8103
  const afterAddMember = organizationHooks.afterAddMember;
8025
8104
  organizationHooks.afterAddMember = async (...args) => {
8026
8105
  const [{ organization, member, user }] = args;
8027
- trackOrganizationMemberAdded(organization, member, user, getOrganizationTriggerInfo(user));
8106
+ trackOrganizationMemberAdded(organization, member, user, getOrganizationTriggerInfo(user), await getRequestLocation());
8028
8107
  if (afterAddMember) return afterAddMember(...args);
8029
8108
  };
8030
8109
  const afterRemoveMember = organizationHooks.afterRemoveMember;
8031
8110
  organizationHooks.afterRemoveMember = async (...args) => {
8032
8111
  const [{ organization, member, user }] = args;
8033
- trackOrganizationMemberRemoved(organization, member, user, getOrganizationTriggerInfo(user));
8112
+ trackOrganizationMemberRemoved(organization, member, user, getOrganizationTriggerInfo(user), await getRequestLocation());
8034
8113
  if (afterRemoveMember) return afterRemoveMember(...args);
8035
8114
  };
8036
8115
  const afterUpdateMemberRole = organizationHooks.afterUpdateMemberRole;
8037
8116
  organizationHooks.afterUpdateMemberRole = async (...args) => {
8038
8117
  const [{ organization, member, user, previousRole }] = args;
8039
- trackOrganizationMemberRoleUpdated(organization, member, user, previousRole, getOrganizationTriggerInfo(user));
8118
+ trackOrganizationMemberRoleUpdated(organization, member, user, previousRole, getOrganizationTriggerInfo(user), await getRequestLocation());
8040
8119
  if (afterUpdateMemberRole) return afterUpdateMemberRole(...args);
8041
8120
  };
8042
8121
  const afterCreateInvitation = organizationHooks.afterCreateInvitation;
8043
8122
  organizationHooks.afterCreateInvitation = async (...args) => {
8044
8123
  const [{ organization, invitation, inviter }] = args;
8045
- trackOrganizationMemberInvited(organization, invitation, inviter, getOrganizationTriggerInfo(inviter));
8124
+ trackOrganizationMemberInvited(organization, invitation, inviter, getOrganizationTriggerInfo(inviter), await getRequestLocation());
8046
8125
  if (afterCreateInvitation) return afterCreateInvitation(...args);
8047
8126
  };
8048
8127
  const afterAcceptInvitation = organizationHooks.afterAcceptInvitation;
8049
8128
  organizationHooks.afterAcceptInvitation = async (...args) => {
8050
8129
  const [{ organization, invitation, member, user }] = args;
8051
- trackOrganizationMemberInviteAccepted(organization, invitation, member, user, getOrganizationTriggerInfo(user));
8130
+ trackOrganizationMemberInviteAccepted(organization, invitation, member, user, getOrganizationTriggerInfo(user), await getRequestLocation());
8052
8131
  if (afterAcceptInvitation) return afterAcceptInvitation(...args);
8053
8132
  };
8054
8133
  const afterRejectInvitation = organizationHooks.afterRejectInvitation;
8055
8134
  organizationHooks.afterRejectInvitation = async (...args) => {
8056
8135
  const [{ organization, invitation, user }] = args;
8057
- trackOrganizationMemberInviteRejected(organization, invitation, user, getOrganizationTriggerInfo(user));
8136
+ trackOrganizationMemberInviteRejected(organization, invitation, user, getOrganizationTriggerInfo(user), await getRequestLocation());
8058
8137
  if (afterRejectInvitation) return afterRejectInvitation(...args);
8059
8138
  };
8060
8139
  const afterCancelInvitation = organizationHooks.afterCancelInvitation;
8061
8140
  organizationHooks.afterCancelInvitation = async (...args) => {
8062
8141
  const [{ organization, invitation, cancelledBy }] = args;
8063
- trackOrganizationMemberInviteCanceled(organization, invitation, cancelledBy, getOrganizationTriggerInfo(cancelledBy));
8142
+ trackOrganizationMemberInviteCanceled(organization, invitation, cancelledBy, getOrganizationTriggerInfo(cancelledBy), await getRequestLocation());
8064
8143
  if (afterCancelInvitation) return afterCancelInvitation(...args);
8065
8144
  };
8066
8145
  const afterCreateTeam = organizationHooks.afterCreateTeam;
8067
8146
  organizationHooks.afterCreateTeam = async (...args) => {
8068
8147
  const [{ organization, team, user }] = args;
8069
- trackOrganizationTeamCreated(organization, team, getOrganizationTriggerInfo(user));
8148
+ trackOrganizationTeamCreated(organization, team, getOrganizationTriggerInfo(user), await getRequestLocation());
8070
8149
  if (afterCreateTeam) return afterCreateTeam(...args);
8071
8150
  };
8072
8151
  const afterUpdateTeam = organizationHooks.afterUpdateTeam;
8073
8152
  organizationHooks.afterUpdateTeam = async (...args) => {
8074
8153
  const [{ organization, team, user }] = args;
8075
- if (team) trackOrganizationTeamUpdated(organization, team, getOrganizationTriggerInfo(user));
8154
+ if (team) trackOrganizationTeamUpdated(organization, team, getOrganizationTriggerInfo(user), await getRequestLocation());
8076
8155
  if (afterUpdateTeam) return afterUpdateTeam(...args);
8077
8156
  };
8078
8157
  const afterDeleteTeam = organizationHooks.afterDeleteTeam;
8079
8158
  organizationHooks.afterDeleteTeam = async (...args) => {
8080
8159
  const [{ organization, team, user }] = args;
8081
- trackOrganizationTeamDeleted(organization, team, getOrganizationTriggerInfo(user));
8160
+ trackOrganizationTeamDeleted(organization, team, getOrganizationTriggerInfo(user), await getRequestLocation());
8082
8161
  if (afterDeleteTeam) return afterDeleteTeam(...args);
8083
8162
  };
8084
8163
  const afterAddTeamMember = organizationHooks.afterAddTeamMember;
8085
8164
  organizationHooks.afterAddTeamMember = async (...args) => {
8086
8165
  const [{ organization, team, user, teamMember }] = args;
8087
- trackOrganizationTeamMemberAdded(organization, team, user, teamMember, getOrganizationTriggerInfo(user));
8166
+ trackOrganizationTeamMemberAdded(organization, team, user, teamMember, getOrganizationTriggerInfo(user), await getRequestLocation());
8088
8167
  if (afterAddTeamMember) return afterAddTeamMember(...args);
8089
8168
  };
8090
8169
  const afterRemoveTeamMember = organizationHooks.afterRemoveTeamMember;
8091
8170
  organizationHooks.afterRemoveTeamMember = async (...args) => {
8092
8171
  const [{ organization, team, user, teamMember }] = args;
8093
- trackOrganizationTeamMemberRemoved(organization, team, user, teamMember, getOrganizationTriggerInfo(user));
8172
+ trackOrganizationTeamMemberRemoved(organization, team, user, teamMember, getOrganizationTriggerInfo(user), await getRequestLocation());
8094
8173
  if (afterRemoveTeamMember) return afterRemoveTeamMember(...args);
8095
8174
  };
8096
8175
  };
@@ -8271,7 +8350,11 @@ const dash = (options) => {
8271
8350
  matcher: (ctx) => {
8272
8351
  if (ctx.request?.method !== "GET") return true;
8273
8352
  const path = new URL(ctx.request.url).pathname;
8274
- return matchesAnyRoute(path, [routes.SIGN_IN_SOCIAL_CALLBACK, routes.SIGN_IN_OAUTH_CALLBACK]);
8353
+ return matchesAnyRoute(path, [
8354
+ routes.SIGN_IN_SOCIAL_CALLBACK,
8355
+ routes.SIGN_IN_OAUTH_CALLBACK,
8356
+ routes.DASH_IMPERSONATE_USER
8357
+ ]);
8275
8358
  },
8276
8359
  handler: createIdentificationMiddleware($kv)
8277
8360
  }],
@@ -8279,16 +8362,20 @@ const dash = (options) => {
8279
8362
  matcher: (ctx) => {
8280
8363
  if (ctx.request?.method !== "GET") return true;
8281
8364
  const path = new URL(ctx.request.url).pathname;
8282
- return matchesAnyRoute(path, [routes.SIGN_IN_SOCIAL_CALLBACK, routes.SIGN_IN_OAUTH_CALLBACK]);
8365
+ return matchesAnyRoute(path, [
8366
+ routes.SIGN_IN_SOCIAL_CALLBACK,
8367
+ routes.SIGN_IN_OAUTH_CALLBACK,
8368
+ routes.DASH_IMPERSONATE_USER
8369
+ ]);
8283
8370
  },
8284
8371
  handler: createAuthMiddleware(async (_ctx) => {
8285
8372
  const ctx = _ctx;
8286
8373
  const trigger = getTriggerInfo(ctx, ctx.context.session?.user.id ?? "unknown");
8287
- if (matchesAnyRoute(ctx.path, [routes.SEND_VERIFICATION_EMAIL]) && ctx.context.session && !(ctx.context.returned instanceof Error)) trackEmailVerificationSent(ctx.context.session.session, ctx.context.session.user, trigger, ctx);
8374
+ if (matchesAnyRoute(ctx.path, [routes.SEND_VERIFICATION_EMAIL]) && ctx.context.session && !(ctx.context.returned instanceof Error)) trackEmailVerificationSent(ctx.context.session.session, ctx.context.session.user, trigger, ctx.context.location);
8288
8375
  const body = ctx.body;
8289
- if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_EMAIL, routes.SIGN_IN_EMAIL_OTP]) && ctx.context.returned instanceof Error && body?.email) trackEmailSignInAttempt(ctx, trigger);
8290
- if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_SOCIAL]) && ctx.context.returned instanceof Error && ctx.body.provider && ctx.body.idToken) trackSocialSignInAttempt(ctx, trigger);
8291
- if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_SOCIAL_CALLBACK]) && ctx.request?.method === "GET" && ctx.context.returned instanceof Error && !ctx.context.newSession) trackSocialSignInRedirectionAttempt(ctx, trigger);
8376
+ if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_EMAIL, routes.SIGN_IN_EMAIL_OTP]) && ctx.context.returned instanceof Error && body?.email) trackEmailSignInAttempt(ctx, trigger, ctx.context.location);
8377
+ if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_SOCIAL]) && ctx.context.returned instanceof Error && ctx.body.provider && ctx.body.idToken) trackSocialSignInAttempt(ctx, trigger, ctx.context.location);
8378
+ if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_SOCIAL_CALLBACK]) && ctx.request?.method === "GET" && ctx.context.returned instanceof Error && !ctx.context.newSession) trackSocialSignInRedirectionAttempt(ctx, trigger, ctx.context.location);
8292
8379
  const headerRequestId = ctx.request?.headers.get("X-Request-Id");
8293
8380
  if (headerRequestId) ctx.setCookie(IDENTIFICATION_COOKIE_NAME, headerRequestId, {
8294
8381
  maxAge: 600,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/infra",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "Dashboard and analytics plugin for Better Auth",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -61,13 +61,13 @@
61
61
  },
62
62
  "homepage": "https://better-auth.com",
63
63
  "devDependencies": {
64
- "@better-auth/scim": "1.6.11",
65
- "@better-auth/sso": "1.6.11",
64
+ "@better-auth/scim": "1.6.14",
65
+ "@better-auth/sso": "1.6.14",
66
66
  "@infra/mocks": "0.0.0",
67
67
  "@infra/utils": "0.0.0",
68
68
  "@types/bun": "latest",
69
69
  "@types/node": "^24.12.0",
70
- "better-auth": "1.6.11",
70
+ "better-auth": "1.6.14",
71
71
  "expo-crypto": "^14.0.2",
72
72
  "happy-dom": "^20.9.0",
73
73
  "msw": "^2.14.6",