@syncello/auth 3.6.0 → 4.0.0

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.cts CHANGED
@@ -66,7 +66,7 @@ declare function validatePasswordWithBreachCheck(password: string, checkBreaches
66
66
  warning?: string;
67
67
  }>;
68
68
 
69
- type DrizzleDB$2 = any;
69
+ type DrizzleDB$3 = any;
70
70
 
71
71
  type SessionsTable = PgTableWithColumns<any>;
72
72
  type UsersTable$4 = PgTableWithColumns<any>;
@@ -101,7 +101,7 @@ interface SessionData {
101
101
  * @param sessionTtlMs - Session TTL in milliseconds (optional)
102
102
  * @returns Session ID
103
103
  */
104
- declare function createSession(db: DrizzleDB$2, tables: Pick<SessionTables, 'sessions'>, userId: string, fingerprint?: string, ipAddress?: string, sessionTtlMs?: number): Promise<string>;
104
+ declare function createSession(db: DrizzleDB$3, tables: Pick<SessionTables, 'sessions'>, userId: string, fingerprint?: string, ipAddress?: string, sessionTtlMs?: number): Promise<string>;
105
105
  /**
106
106
  * Validate a session and return user data
107
107
  * @param db - Drizzle database instance
@@ -111,7 +111,9 @@ declare function createSession(db: DrizzleDB$2, tables: Pick<SessionTables, 'ses
111
111
  * @param sessionMaxLifetimeMs - Absolute session lifetime cap in milliseconds (optional)
112
112
  * @returns Session data or null if invalid
113
113
  */
114
- declare function validateSession(db: DrizzleDB$2, tables: SessionTables, sessionId: string, currentFingerprint?: string, sessionMaxLifetimeMs?: number): Promise<SessionData | null>;
114
+ declare function validateSession(db: DrizzleDB$3, tables: SessionTables, sessionId: string, currentFingerprint?: string, sessionMaxLifetimeMs?: number): Promise<SessionData | null>;
115
+ /** Whether the sliding window should move: never yet, or not in the last refresh interval. */
116
+ declare function sessionRefreshDue(lastActiveAt: number | null, now?: number): boolean;
115
117
  /**
116
118
  * Refresh session expiration (sliding window)
117
119
  * @param db - Drizzle database instance
@@ -119,28 +121,28 @@ declare function validateSession(db: DrizzleDB$2, tables: SessionTables, session
119
121
  * @param sessionId - Session ID
120
122
  * @param sessionTtlMs - Session TTL in milliseconds (optional)
121
123
  */
122
- declare function refreshSession(db: DrizzleDB$2, tables: Pick<SessionTables, 'sessions'>, sessionId: string, sessionTtlMs?: number): Promise<void>;
124
+ declare function refreshSession(db: DrizzleDB$3, tables: Pick<SessionTables, 'sessions'>, sessionId: string, sessionTtlMs?: number): Promise<void>;
123
125
  /**
124
126
  * Delete a session (logout)
125
127
  * @param db - Drizzle database instance
126
128
  * @param tables - Session tables (sessions)
127
129
  * @param sessionId - Session ID
128
130
  */
129
- declare function deleteSession(db: DrizzleDB$2, tables: Pick<SessionTables, 'sessions'>, sessionId: string): Promise<void>;
131
+ declare function deleteSession(db: DrizzleDB$3, tables: Pick<SessionTables, 'sessions'>, sessionId: string): Promise<void>;
130
132
  /**
131
133
  * Delete all sessions for a user (logout all devices)
132
134
  * @param db - Drizzle database instance
133
135
  * @param tables - Session tables (sessions)
134
136
  * @param userId - User ID
135
137
  */
136
- declare function deleteAllUserSessions(db: DrizzleDB$2, tables: Pick<SessionTables, 'sessions'>, userId: string): Promise<void>;
138
+ declare function deleteAllUserSessions(db: DrizzleDB$3, tables: Pick<SessionTables, 'sessions'>, userId: string): Promise<void>;
137
139
  /**
138
140
  * Invalidate all sessions after password change
139
141
  * @param db - Drizzle database instance
140
142
  * @param tables - Session tables (sessions, users)
141
143
  * @param userId - User ID
142
144
  */
143
- declare function invalidateAllUserSessions(db: DrizzleDB$2, tables: SessionTables, userId: string): Promise<void>;
145
+ declare function invalidateAllUserSessions(db: DrizzleDB$3, tables: SessionTables, userId: string): Promise<void>;
144
146
 
145
147
  /**
146
148
  * Generate a cryptographically secure random token
@@ -163,6 +165,7 @@ declare function hashToken(token: string): Promise<string>;
163
165
  declare const AUTH_DEFAULTS: {
164
166
  readonly SESSION_TTL_DAYS: 7;
165
167
  readonly SESSION_MAX_LIFETIME_DAYS: 30;
168
+ readonly SESSION_REFRESH_INTERVAL_MINUTES: 15;
166
169
  readonly LOCKOUT_DURATION_MINUTES: 30;
167
170
  readonly LOCKOUT_MAX_ATTEMPTS: 5;
168
171
  readonly PASSWORD_RESET_TTL_MINUTES: 15;
@@ -255,6 +258,8 @@ interface EmailBranding {
255
258
  buttonRadius?: string;
256
259
  /** Footer band background color. Default: '#1e1b4b' */
257
260
  footerColor?: string;
261
+ /** Heading and welcome-line color. Default: '#1e1b4b' */
262
+ headingColor?: string;
258
263
  }
259
264
 
260
265
  /**
@@ -332,8 +337,14 @@ interface InvitationHookUser {
332
337
  * library calls `verify`. If it returns a matching email, the account is created
333
338
  * with `emailVerified: true` and NO verification email is sent — the token, which
334
339
  * was delivered to that address, is itself the proof of ownership (the same trust
335
- * model as OAuth-provider signup). `onAccepted` then runs so the app can link the
336
- * user (e.g. create a team membership) atomically with signup.
340
+ * model as OAuth-provider signup). `onAccepted` then runs right after the user is
341
+ * created so the app can link them (e.g. create a team membership); a failure is
342
+ * logged, not raised, since the account already exists. A user the OAuth callback
343
+ * creates from a verified invitation gets `onAccepted` too. A `verify` that
344
+ * throws is logged and treated as "not invited".
345
+ *
346
+ * With `signup: 'invitation'`, a verified invitation is the only way to create
347
+ * an account.
337
348
  */
338
349
  interface InvitationHooks<TEnv = Record<string, unknown>> {
339
350
  /**
@@ -358,6 +369,16 @@ interface InvitationHooks<TEnv = Record<string, unknown>> {
358
369
  env: TEnv;
359
370
  }): Promise<void>;
360
371
  }
372
+ /**
373
+ * Who may create an account.
374
+ *
375
+ * - `'open'`: anyone can sign up. An `invitationToken`, when given and verified,
376
+ * only skips email verification and links the user.
377
+ * - `'invitation'`: every new account needs an invitation that `invitations.verify`
378
+ * accepts for its email, on password signup, OAuth signup and the OAuth callback
379
+ * alike. Existing users sign in as usual. Requires `invitations`.
380
+ */
381
+ type SignupMode = 'open' | 'invitation';
361
382
  /**
362
383
  * Configuration for createAuth
363
384
  */
@@ -372,6 +393,8 @@ interface AuthConfig<TEnv = Record<string, unknown>> {
372
393
  emailBranding?: EmailBranding;
373
394
  /** Optional app-owned invitation hooks for token-verified signup. */
374
395
  invitations?: InvitationHooks<TEnv>;
396
+ /** Who may create an account; defaults to `'open'`. See {@link SignupMode}. */
397
+ signup?: SignupMode;
375
398
  }
376
399
  /**
377
400
  * Auth context available in all auth operations.
@@ -383,6 +406,8 @@ interface AuthContext<TEnv = Record<string, unknown>> {
383
406
  env: TEnv;
384
407
  emailBranding?: EmailBranding;
385
408
  invitations?: InvitationHooks<TEnv>;
409
+ /** Absent means `'open'`. */
410
+ signup?: SignupMode;
386
411
  }
387
412
  /**
388
413
  * Creates an auth instance with injected database and schema.
@@ -595,7 +620,7 @@ declare function generateFingerprint(request: Request): Promise<string>;
595
620
  */
596
621
  declare function getClientIp(request: Request): string;
597
622
 
598
- type DrizzleDB$1 = any;
623
+ type DrizzleDB$2 = any;
599
624
  type UsersTable$3 = PgTableWithColumns<any>;
600
625
  interface AccountLockoutTables {
601
626
  users: UsersTable$3;
@@ -607,7 +632,7 @@ interface AccountLockoutTables {
607
632
  * @param userId - User ID to check
608
633
  * @returns Lock status and unlock time if locked
609
634
  */
610
- declare function checkAccountLocked(db: DrizzleDB$1, tables: AccountLockoutTables, userId: string): Promise<{
635
+ declare function checkAccountLocked(db: DrizzleDB$2, tables: AccountLockoutTables, userId: string): Promise<{
611
636
  isLocked: boolean;
612
637
  unlockAt?: number;
613
638
  }>;
@@ -620,7 +645,7 @@ declare function checkAccountLocked(db: DrizzleDB$1, tables: AccountLockoutTable
620
645
  * @param maxAttempts - Maximum failed attempts before lockout
621
646
  * @param lockoutDurationMs - Duration of lockout in milliseconds
622
647
  */
623
- declare function incrementFailedAttempts(db: DrizzleDB$1, tables: AccountLockoutTables, userId: string, maxAttempts?: number, lockoutDurationMs?: number): Promise<void>;
648
+ declare function incrementFailedAttempts(db: DrizzleDB$2, tables: AccountLockoutTables, userId: string, maxAttempts?: number, lockoutDurationMs?: number): Promise<void>;
624
649
  /**
625
650
  * Clear account lockout and failed attempt counter
626
651
  * Called after successful login
@@ -628,7 +653,7 @@ declare function incrementFailedAttempts(db: DrizzleDB$1, tables: AccountLockout
628
653
  * @param tables - Account lockout tables (users)
629
654
  * @param userId - User ID
630
655
  */
631
- declare function clearAccountLockout(db: DrizzleDB$1, tables: AccountLockoutTables, userId: string): Promise<void>;
656
+ declare function clearAccountLockout(db: DrizzleDB$2, tables: AccountLockoutTables, userId: string): Promise<void>;
632
657
  /**
633
658
  * Get minutes remaining until account unlock
634
659
  * @param unlockAt - Unlock timestamp
@@ -645,7 +670,7 @@ declare function getMinutesUntilUnlock(unlockAt: number): number;
645
670
  */
646
671
  declare function verifyTurnstileToken(token: string, secretKey: string, remoteIp?: string, environment?: 'development' | 'staging' | 'production'): Promise<boolean>;
647
672
 
648
- type DrizzleDB = any;
673
+ type DrizzleDB$1 = any;
649
674
  type UsersTable$2 = PgTableWithColumns<any>;
650
675
  type EmailChangeTokensTable = PgTableWithColumns<any>;
651
676
  interface EmailChangeTables {
@@ -657,7 +682,7 @@ interface RequestEmailChangeParams {
657
682
  password: string;
658
683
  newEmail: string;
659
684
  pepper: string;
660
- db: DrizzleDB;
685
+ db: DrizzleDB$1;
661
686
  tables: EmailChangeTables;
662
687
  }
663
688
  interface RequestEmailChangeResult {
@@ -673,7 +698,7 @@ interface RequestEmailChangeResult {
673
698
  declare function requestEmailChange(params: RequestEmailChangeParams): Promise<RequestEmailChangeResult>;
674
699
  interface ConfirmEmailChangeParams {
675
700
  token: string;
676
- db: DrizzleDB;
701
+ db: DrizzleDB$1;
677
702
  tables: EmailChangeTables;
678
703
  }
679
704
  interface ConfirmEmailChangeResult {
@@ -686,7 +711,7 @@ interface ConfirmEmailChangeResult {
686
711
  declare function confirmEmailChange(params: ConfirmEmailChangeParams): Promise<ConfirmEmailChangeResult>;
687
712
  interface CancelEmailChangeParams {
688
713
  token: string;
689
- db: DrizzleDB;
714
+ db: DrizzleDB$1;
690
715
  tables: Pick<EmailChangeTables, 'emailChangeTokens'>;
691
716
  }
692
717
  interface CancelEmailChangeResult {
@@ -776,6 +801,93 @@ declare function verifyCodeChallenge(verifier: string, challenge: string): Promi
776
801
  */
777
802
  declare function isDeepLinkUri(uri: string): boolean;
778
803
 
804
+ type DrizzleDB = any;
805
+
806
+ type ChallengesTable = PgTableWithColumns<any>;
807
+ interface PasswordlessTables {
808
+ passwordlessChallenges: ChallengesTable;
809
+ }
810
+ declare const PASSWORDLESS_TTL_MS: number;
811
+ declare const PASSWORDLESS_MAX_ATTEMPTS = 5;
812
+ type PasswordlessSuccess = {
813
+ ok: true;
814
+ email: string;
815
+ subjectId: string | null;
816
+ payload: unknown;
817
+ };
818
+ type PasswordlessLinkResult = PasswordlessSuccess | {
819
+ ok: false;
820
+ reason: 'invalid' | 'expired';
821
+ };
822
+ type PasswordlessCodeResult = PasswordlessSuccess | {
823
+ ok: false;
824
+ reason: 'invalid' | 'expired' | 'locked';
825
+ };
826
+ /** A uniformly distributed 6-digit code. */
827
+ declare function generateCode(): string;
828
+ /**
829
+ * Start a sign-in for an email, replacing any open one. Email the link token and code; keep the
830
+ * browser secret in an HttpOnly cookie, since the code only works alongside it.
831
+ * @param input.subjectId - The account signing in, or null for a sign-up
832
+ * @param input.payload - JSON handed back on success (e.g. sign-up details such as a name)
833
+ */
834
+ declare function createPasswordlessChallenge(db: DrizzleDB, tables: PasswordlessTables, input: {
835
+ email: string;
836
+ subjectId?: string | null;
837
+ payload?: unknown;
838
+ ttlMs?: number;
839
+ }): Promise<{
840
+ challengeId: string;
841
+ linkToken: string;
842
+ code: string;
843
+ browserSecret: string;
844
+ }>;
845
+ /** Redeem an emailed link, from any browser. Success consumes every open challenge for the email. */
846
+ declare function verifyPasswordlessLink(db: DrizzleDB, tables: PasswordlessTables, input: {
847
+ linkToken: string;
848
+ }): Promise<PasswordlessLinkResult>;
849
+ /**
850
+ * Redeem a code against the email's newest open challenge, in the browser that started it. Any
851
+ * other browser is answered `invalid`, uncounted, so a stranger who knows the email can neither
852
+ * lock the challenge nor learn its state. A wrong code counts an attempt; at `maxAttempts` the
853
+ * challenge is locked. Success consumes every open challenge for the email.
854
+ */
855
+ declare function verifyPasswordlessCode(db: DrizzleDB, tables: PasswordlessTables, input: {
856
+ email: string;
857
+ code: string;
858
+ browserSecret: string;
859
+ maxAttempts?: number;
860
+ }): Promise<PasswordlessCodeResult>;
861
+ /**
862
+ * Failed code attempts for an email across every challenge created within the window, consumed
863
+ * ones included, so asking for a new code never resets the count. Failures are dated by their
864
+ * challenge's creation, so one can leave the window up to a TTL early. Anyone can fail codes for
865
+ * an email they know only from their own browser, so pair this with a per-browser or per-IP limit.
866
+ */
867
+ declare function recentPasswordlessFailures(db: DrizzleDB, tables: PasswordlessTables, input: {
868
+ email: string;
869
+ windowMs: number;
870
+ }): Promise<number>;
871
+ /** Delete challenges that expired more than a day ago. Run from a scheduled job. */
872
+ declare function cleanupExpiredPasswordlessChallenges(db: DrizzleDB, tables: PasswordlessTables): Promise<void>;
873
+ /**
874
+ * The sign-in email: the code in the subject and body, and the link as a button.
875
+ * @param input.appUrl - Base for the logo and footer links; the link must be on its host or a subdomain
876
+ * @param input.ttlMs - The challenge's lifetime, for the stated expiry (default PASSWORDLESS_TTL_MS)
877
+ * @throws If `linkUrl` is not http(s) on `appUrl`'s host
878
+ */
879
+ declare function passwordlessEmail(input: {
880
+ appName: string;
881
+ appUrl: string;
882
+ linkUrl: string;
883
+ code: string;
884
+ ttlMs?: number;
885
+ }): {
886
+ subject: string;
887
+ html: string;
888
+ text: string;
889
+ };
890
+
779
891
  /**
780
892
  * TOTP (Time-based One-Time Password) utilities
781
893
  * RFC 6238 compliant with replay prevention
@@ -1701,10 +1813,9 @@ declare const authRoutes: OpenAPIHono<{
1701
1813
  input: {
1702
1814
  json: {
1703
1815
  email: string;
1704
- password?: string | undefined;
1816
+ password: string;
1817
+ turnstileToken: string;
1705
1818
  name?: string | undefined;
1706
- turnstileToken?: string | undefined;
1707
- oauthToken?: string | undefined;
1708
1819
  invitationToken?: string | undefined;
1709
1820
  };
1710
1821
  };
@@ -1723,10 +1834,30 @@ declare const authRoutes: OpenAPIHono<{
1723
1834
  input: {
1724
1835
  json: {
1725
1836
  email: string;
1726
- password?: string | undefined;
1837
+ password: string;
1838
+ turnstileToken: string;
1839
+ name?: string | undefined;
1840
+ invitationToken?: string | undefined;
1841
+ };
1842
+ };
1843
+ output: {
1844
+ type: string;
1845
+ title: string;
1846
+ status: number;
1847
+ detail?: string | undefined;
1848
+ instance?: string | undefined;
1849
+ traceId?: string | undefined;
1850
+ requestId?: string | undefined;
1851
+ };
1852
+ outputFormat: "json";
1853
+ status: 403;
1854
+ } | {
1855
+ input: {
1856
+ json: {
1857
+ email: string;
1858
+ password: string;
1859
+ turnstileToken: string;
1727
1860
  name?: string | undefined;
1728
- turnstileToken?: string | undefined;
1729
- oauthToken?: string | undefined;
1730
1861
  invitationToken?: string | undefined;
1731
1862
  };
1732
1863
  };
@@ -1745,10 +1876,9 @@ declare const authRoutes: OpenAPIHono<{
1745
1876
  input: {
1746
1877
  json: {
1747
1878
  email: string;
1748
- password?: string | undefined;
1879
+ password: string;
1880
+ turnstileToken: string;
1749
1881
  name?: string | undefined;
1750
- turnstileToken?: string | undefined;
1751
- oauthToken?: string | undefined;
1752
1882
  invitationToken?: string | undefined;
1753
1883
  };
1754
1884
  };
@@ -2406,7 +2536,7 @@ declare function problemJson(c: Context, problem: ProblemDetails): Response & ho
2406
2536
  instance?: string | undefined;
2407
2537
  traceId?: string | undefined;
2408
2538
  requestId?: string | undefined;
2409
- }, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
2539
+ }, 500 | 400 | 401 | 403 | 404 | 409 | 410 | 429 | 503, "json">;
2410
2540
  /**
2411
2541
  * Helper function to return common problem types
2412
2542
  */
@@ -2423,7 +2553,7 @@ declare const problems: {
2423
2553
  instance?: string | undefined;
2424
2554
  traceId?: string | undefined;
2425
2555
  requestId?: string | undefined;
2426
- }, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
2556
+ }, 500 | 400 | 401 | 403 | 404 | 409 | 410 | 429 | 503, "json">;
2427
2557
  /**
2428
2558
  * 400 Bad Request - Validation errors with field-specific details
2429
2559
  */
@@ -2439,7 +2569,7 @@ declare const problems: {
2439
2569
  instance?: string | undefined;
2440
2570
  traceId?: string | undefined;
2441
2571
  requestId?: string | undefined;
2442
- }, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
2572
+ }, 500 | 400 | 401 | 403 | 404 | 409 | 410 | 429 | 503, "json">;
2443
2573
  /**
2444
2574
  * 401 Unauthorized - Authentication required
2445
2575
  */
@@ -2452,7 +2582,7 @@ declare const problems: {
2452
2582
  instance?: string | undefined;
2453
2583
  traceId?: string | undefined;
2454
2584
  requestId?: string | undefined;
2455
- }, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
2585
+ }, 500 | 400 | 401 | 403 | 404 | 409 | 410 | 429 | 503, "json">;
2456
2586
  /**
2457
2587
  * 403 Forbidden - Insufficient permissions
2458
2588
  */
@@ -2465,7 +2595,7 @@ declare const problems: {
2465
2595
  instance?: string | undefined;
2466
2596
  traceId?: string | undefined;
2467
2597
  requestId?: string | undefined;
2468
- }, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
2598
+ }, 500 | 400 | 401 | 403 | 404 | 409 | 410 | 429 | 503, "json">;
2469
2599
  /**
2470
2600
  * 404 Not Found
2471
2601
  */
@@ -2478,7 +2608,7 @@ declare const problems: {
2478
2608
  instance?: string | undefined;
2479
2609
  traceId?: string | undefined;
2480
2610
  requestId?: string | undefined;
2481
- }, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
2611
+ }, 500 | 400 | 401 | 403 | 404 | 409 | 410 | 429 | 503, "json">;
2482
2612
  /**
2483
2613
  * 409 Conflict - Resource conflict (e.g., duplicate email)
2484
2614
  */
@@ -2491,7 +2621,7 @@ declare const problems: {
2491
2621
  instance?: string | undefined;
2492
2622
  traceId?: string | undefined;
2493
2623
  requestId?: string | undefined;
2494
- }, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
2624
+ }, 500 | 400 | 401 | 403 | 404 | 409 | 410 | 429 | 503, "json">;
2495
2625
  /**
2496
2626
  * 410 Gone - Resource is no longer available
2497
2627
  */
@@ -2504,7 +2634,7 @@ declare const problems: {
2504
2634
  instance?: string | undefined;
2505
2635
  traceId?: string | undefined;
2506
2636
  requestId?: string | undefined;
2507
- }, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
2637
+ }, 500 | 400 | 401 | 403 | 404 | 409 | 410 | 429 | 503, "json">;
2508
2638
  /**
2509
2639
  * 429 Too Many Requests - Rate limit exceeded
2510
2640
  */
@@ -2517,7 +2647,7 @@ declare const problems: {
2517
2647
  instance?: string | undefined;
2518
2648
  traceId?: string | undefined;
2519
2649
  requestId?: string | undefined;
2520
- }, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
2650
+ }, 500 | 400 | 401 | 403 | 404 | 409 | 410 | 429 | 503, "json">;
2521
2651
  /**
2522
2652
  * 500 Internal Server Error - Generic server error
2523
2653
  * SECURITY: Never include error details or stack traces
@@ -2531,7 +2661,7 @@ declare const problems: {
2531
2661
  instance?: string | undefined;
2532
2662
  traceId?: string | undefined;
2533
2663
  requestId?: string | undefined;
2534
- }, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
2664
+ }, 500 | 400 | 401 | 403 | 404 | 409 | 410 | 429 | 503, "json">;
2535
2665
  /**
2536
2666
  * 503 Service Unavailable - Service is temporarily unavailable
2537
2667
  */
@@ -2544,7 +2674,7 @@ declare const problems: {
2544
2674
  instance?: string | undefined;
2545
2675
  traceId?: string | undefined;
2546
2676
  requestId?: string | undefined;
2547
- }, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
2677
+ }, 500 | 400 | 401 | 403 | 404 | 409 | 410 | 429 | 503, "json">;
2548
2678
  };
2549
2679
 
2550
2680
  /**
@@ -2728,11 +2858,17 @@ declare function handleWebhookEvent(payload: ResendWebhookPayload, db: PostgresJ
2728
2858
  users: UsersTable;
2729
2859
  }): Promise<void>;
2730
2860
 
2861
+ /** The svix headers Resend sends with every webhook delivery. */
2862
+ interface SvixHeaders {
2863
+ 'svix-id': string;
2864
+ 'svix-timestamp': string;
2865
+ 'svix-signature': string;
2866
+ }
2731
2867
  /**
2732
- * Verify Resend webhook signature using svix
2733
- * Resend uses svix for webhook signing
2868
+ * Whether a Resend webhook is genuine. Svix signs `<id>.<timestamp>.<body>` and refuses a
2869
+ * timestamp more than five minutes off, so all three headers are needed.
2734
2870
  */
2735
- declare function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean;
2871
+ declare function verifyWebhookSignature(payload: string, headers: SvixHeaders, secret: string): boolean;
2736
2872
 
2737
2873
  type EmailEventsTable = PgTableWithColumns<any>;
2738
2874
  /**
@@ -2746,4 +2882,4 @@ declare function calculateBounceRate(db: PostgresJsDatabase<Record<string, unkno
2746
2882
  */
2747
2883
  declare function calculateComplaintRate(db: PostgresJsDatabase<Record<string, unknown>>, emailEventsTable: EmailEventsTable, hoursAgo?: number): Promise<number>;
2748
2884
 
2749
- export { AUTH_DEFAULTS, type AccountLockoutTables, type Auth, type AuthConfig, type AuthContext, type AuthDatabase, type AuthSchema, CHALLENGE_TTL_MS, type ChallengePayload, CloudflareEmailAdapter, type DeviceType, type EmailAdapter, type EmailBranding, type EmailChangeConfirmationData, type EmailChangeNotificationData, type EmailChangeTables, type EmailSendOptions, type EmailSendResult, EmailService, type EmailTemplate, type EmailType, type Env, type InferNewSession, type InferNewUser, type InferSession, type InferUser, type InvitationHookUser, type InvitationHooks, MAX_CHALLENGE_ATTEMPTS, type PasswordResetEmailData, type ProblemDetails, ProblemTypes, ResendAdapter, type ResendEventType, type ResendWebhookPayload, type SendEmailBinding, type SendEmailResult, type SessionData, type SessionTables, TRUSTED_DEVICE_COOKIE_BASE, TRUSTED_DEVICE_TTL_MS, type TotpVerifyResult, type TwoFactorCodeEmailData, type TwoFactorDisabledEmailData, type TwoFactorEnabledEmailData, type ValidationResult, type Variables, type VerificationEmailData, authRoutes, calculateBounceRate, calculateComplaintRate, cancelEmailChange, checkAccountLocked, checkBreachedPassword, clearAccountLockout, clearChallengeCookie, clearSessionCookie, clearTrustedDeviceCookie, confirmEmailChange, createAuth, createAuthMiddleware, createChallengeToken, createDeviceToken, createEmailAdapter, createProblemDetails, createSession, csrf, decryptTotpSecret, deleteAllUserSessions, deleteChallengeToken, deleteSession, encryptTotpSecret, extractAppUrl, formatBackupCode, generate2faCodeEmail, generate2faDisabledEmail, generate2faEnabledEmail, generateApiKey, generateBackupCodes, generateCodeChallenge, generateCodeVerifier, generateEmailChangeConfirmation, generateEmailChangeNotification, generateFingerprint, generatePasswordResetEmail, generateQrCodeUri, generateSecureToken, generateTotpSecret, generateTraceId, generateVerificationEmail, getAllowedOrigins, getAuthContext, getChallengeCookieName, getClientIp, getKeyPrefix, getMinutesUntilUnlock, getSessionCookieName, getTotpCounter, getTrustedDeviceCookieName, handleWebhookEvent, hashApiKey, hashBackupCode, hashPassword, hashToken, incrementFailedAttempts, invalidateAllUserSessions, isCapacitorApp, isCloudflarePreviewUrl, isDeepLinkUri, isValidApiKeyFormat, logError, logSecurityEvent, logger, normalizeBackupCode, optionalAuth, parseDeviceName, parseDeviceType, problemJson, problems, rateLimit, refreshSession, requestEmailChange, requireAuth, requireVerifiedEmail, retrieveChallengeToken, setChallengeCookie, setSessionCookie, setTrustedDeviceCookie, storeChallengeToken, updateChallengeAttempts, validateChallengePayload, validateOrigin, validatePassword, validatePasswordWithBreachCheck, validateSession, validateTrustedDevice, verifyBackupCode, verifyCodeChallenge, verifyPassword, verifyPasswordWithRotation, verifyTotpCode, verifyTurnstileToken, verifyWebhookSignature };
2885
+ export { AUTH_DEFAULTS, type AccountLockoutTables, type Auth, type AuthConfig, type AuthContext, type AuthDatabase, type AuthSchema, CHALLENGE_TTL_MS, type ChallengePayload, CloudflareEmailAdapter, type DeviceType, type EmailAdapter, type EmailBranding, type EmailChangeConfirmationData, type EmailChangeNotificationData, type EmailChangeTables, type EmailSendOptions, type EmailSendResult, EmailService, type EmailTemplate, type EmailType, type Env, type InferNewSession, type InferNewUser, type InferSession, type InferUser, type InvitationHookUser, type InvitationHooks, MAX_CHALLENGE_ATTEMPTS, PASSWORDLESS_MAX_ATTEMPTS, PASSWORDLESS_TTL_MS, type PasswordResetEmailData, type PasswordlessCodeResult, type PasswordlessLinkResult, type PasswordlessSuccess, type PasswordlessTables, type ProblemDetails, ProblemTypes, ResendAdapter, type ResendEventType, type ResendWebhookPayload, type SendEmailBinding, type SendEmailResult, type SessionData, type SessionTables, type SignupMode, type SvixHeaders, TRUSTED_DEVICE_COOKIE_BASE, TRUSTED_DEVICE_TTL_MS, type TotpVerifyResult, type TwoFactorCodeEmailData, type TwoFactorDisabledEmailData, type TwoFactorEnabledEmailData, type ValidationResult, type Variables, type VerificationEmailData, authRoutes, calculateBounceRate, calculateComplaintRate, cancelEmailChange, checkAccountLocked, checkBreachedPassword, cleanupExpiredPasswordlessChallenges, clearAccountLockout, clearChallengeCookie, clearSessionCookie, clearTrustedDeviceCookie, confirmEmailChange, createAuth, createAuthMiddleware, createChallengeToken, createDeviceToken, createEmailAdapter, createPasswordlessChallenge, createProblemDetails, createSession, csrf, decryptTotpSecret, deleteAllUserSessions, deleteChallengeToken, deleteSession, encryptTotpSecret, extractAppUrl, formatBackupCode, generate2faCodeEmail, generate2faDisabledEmail, generate2faEnabledEmail, generateApiKey, generateBackupCodes, generateCode, generateCodeChallenge, generateCodeVerifier, generateEmailChangeConfirmation, generateEmailChangeNotification, generateFingerprint, generatePasswordResetEmail, generateQrCodeUri, generateSecureToken, generateTotpSecret, generateTraceId, generateVerificationEmail, getAllowedOrigins, getAuthContext, getChallengeCookieName, getClientIp, getKeyPrefix, getMinutesUntilUnlock, getSessionCookieName, getTotpCounter, getTrustedDeviceCookieName, handleWebhookEvent, hashApiKey, hashBackupCode, hashPassword, hashToken, incrementFailedAttempts, invalidateAllUserSessions, isCapacitorApp, isCloudflarePreviewUrl, isDeepLinkUri, isValidApiKeyFormat, logError, logSecurityEvent, logger, normalizeBackupCode, optionalAuth, parseDeviceName, parseDeviceType, passwordlessEmail, problemJson, problems, rateLimit, recentPasswordlessFailures, refreshSession, requestEmailChange, requireAuth, requireVerifiedEmail, retrieveChallengeToken, sessionRefreshDue, setChallengeCookie, setSessionCookie, setTrustedDeviceCookie, storeChallengeToken, updateChallengeAttempts, validateChallengePayload, validateOrigin, validatePassword, validatePasswordWithBreachCheck, validateSession, validateTrustedDevice, verifyBackupCode, verifyCodeChallenge, verifyPassword, verifyPasswordWithRotation, verifyPasswordlessCode, verifyPasswordlessLink, verifyTotpCode, verifyTurnstileToken, verifyWebhookSignature };