@syncello/auth 3.3.0 → 3.4.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.js CHANGED
@@ -949,11 +949,12 @@ import { eq as eq4, and as and2, gt as gt2 } from "drizzle-orm";
949
949
 
950
950
  // src/factory.ts
951
951
  function createAuth(config) {
952
- const { db, schema, getEnv } = config;
952
+ const { db, schema, getEnv, emailBranding } = config;
953
953
  const getContext = (c) => ({
954
954
  db,
955
955
  schema,
956
- env: getEnv(c)
956
+ env: getEnv(c),
957
+ emailBranding
957
958
  });
958
959
  return {
959
960
  db,
@@ -1190,6 +1191,19 @@ var problems = {
1190
1191
  };
1191
1192
 
1192
1193
  // src/middleware/auth.ts
1194
+ var SESSION_MAX_LIFETIME_MS = AUTH_DEFAULTS.SESSION_MAX_LIFETIME_DAYS * 24 * 60 * 60 * 1e3;
1195
+ async function revokeIfPastMaxLifetime(db, sessions, sessionId, session) {
1196
+ if (Date.now() - session.createdAt <= SESSION_MAX_LIFETIME_MS) return false;
1197
+ logger_default.info("Session exceeded absolute lifetime - revoked", {
1198
+ type: "security",
1199
+ event: "session_max_lifetime_exceeded",
1200
+ severity: "low",
1201
+ sessionId: sessionId.slice(0, 8),
1202
+ userId: session.userId
1203
+ });
1204
+ await deleteSession(db, { sessions }, sessionId);
1205
+ return true;
1206
+ }
1193
1207
  async function getSessionFromCookie(db, sessions, cookieHeader, request, env) {
1194
1208
  const cookieName = getSessionCookieName(env);
1195
1209
  const cookiePattern = new RegExp(`${cookieName}=([^;]+)`);
@@ -1199,10 +1213,13 @@ async function getSessionFromCookie(db, sessions, cookieHeader, request, env) {
1199
1213
  const foundSessions = await db.select({
1200
1214
  userId: sessions.userId,
1201
1215
  expiresAt: sessions.expiresAt,
1216
+ createdAt: sessions.createdAt,
1202
1217
  fingerprint: sessions.fingerprint
1203
1218
  }).from(sessions).where(and2(eq4(sessions.id, sessionId), gt2(sessions.expiresAt, Date.now()))).limit(1);
1204
1219
  if (foundSessions.length === 0) return null;
1205
1220
  const session = foundSessions[0];
1221
+ if (await revokeIfPastMaxLifetime(db, sessions, sessionId, session)) return null;
1222
+ await refreshSession(db, { sessions }, sessionId);
1206
1223
  const userAgent = request.headers.get("user-agent") || "";
1207
1224
  const cfWorker = request.headers.get("cf-worker") || "";
1208
1225
  const clientIp = request.headers.get("cf-connecting-ip") || request.headers.get("x-real-ip") || "";
@@ -1246,10 +1263,12 @@ async function getSessionFromBearerToken(db, sessions, authHeader) {
1246
1263
  const sessionId = authHeader.slice(7);
1247
1264
  const foundSessions = await db.select({
1248
1265
  userId: sessions.userId,
1249
- expiresAt: sessions.expiresAt
1266
+ expiresAt: sessions.expiresAt,
1267
+ createdAt: sessions.createdAt
1250
1268
  }).from(sessions).where(and2(eq4(sessions.id, sessionId), gt2(sessions.expiresAt, Date.now()))).limit(1);
1251
1269
  if (foundSessions.length === 0) return null;
1252
1270
  const session = foundSessions[0];
1271
+ if (await revokeIfPastMaxLifetime(db, sessions, sessionId, session)) return null;
1253
1272
  await refreshSession(db, { sessions }, sessionId);
1254
1273
  return {
1255
1274
  userId: session.userId,
@@ -1517,11 +1536,15 @@ function generateEmailTemplate(options) {
1517
1536
  appName = "App",
1518
1537
  buttonBgColor = "#00fe9a",
1519
1538
  buttonTextColor = "#000004",
1520
- buttonFontWeight = 500
1539
+ buttonFontWeight = 500,
1540
+ buttonRadius = "6px",
1541
+ logoUrl,
1542
+ footerColor = "#1e1b4b"
1521
1543
  } = options;
1522
1544
  const safeButtonUrl = buttonUrl ? sanitizeEmailUrl(buttonUrl, appUrl) : "";
1523
1545
  const safeAppUrl = appUrl ? sanitizeEmailUrl(appUrl, appUrl) : "";
1524
1546
  const safeMarketingUrl = marketingUrl ? sanitizeEmailUrl(marketingUrl, appUrl) : "";
1547
+ const safeLogoUrl = logoUrl ? sanitizeEmailUrl(logoUrl, appUrl) : `${safeAppUrl.replace(/\/$/, "")}/email-header-image.png`;
1525
1548
  return `
1526
1549
  <!DOCTYPE html>
1527
1550
  <html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" lang="en">
@@ -1661,7 +1684,7 @@ function generateEmailTemplate(options) {
1661
1684
  <td class="pad" style="padding-left:40px;padding-right:40px;width:100%;">
1662
1685
  <div class="alignment" align="center">
1663
1686
  <div class="fullWidth" style="max-width: 50%;">
1664
- <img src="${safeAppUrl}/email-header-image.png" style="display: block; height: auto; border: 0; width: 100%;" alt="${appName}" title="${appName}" height="auto">
1687
+ <img src="${safeLogoUrl}" style="display: block; height: auto; border: 0; width: 100%;" alt="${appName}" title="${appName}" height="auto">
1665
1688
  </div>
1666
1689
  </div>
1667
1690
  </td>
@@ -1718,7 +1741,7 @@ function generateEmailTemplate(options) {
1718
1741
  <v:textbox inset="0px,0px,0px,0px">
1719
1742
  <center dir="false" style="color:${buttonTextColor};font-family:'Montserrat',Arial,sans-serif;font-size:16px;font-weight:${buttonFontWeight};">
1720
1743
  <![endif]-->
1721
- <a href="${safeButtonUrl}" style="background-color: ${buttonBgColor}; border: 0px solid transparent; border-radius: 6px; color: ${buttonTextColor}; display: inline-block; font-family: 'Montserrat', Arial, sans-serif; font-size: 16px; font-weight: ${buttonFontWeight}; mso-border-alt: none; padding: 10px 20px; text-align: center; text-decoration: none; text-transform: capitalize; word-break: keep-all;">
1744
+ <a href="${safeButtonUrl}" style="background-color: ${buttonBgColor}; border: 0px solid transparent; border-radius: ${buttonRadius}; color: ${buttonTextColor}; display: inline-block; font-family: 'Montserrat', Arial, sans-serif; font-size: 16px; font-weight: ${buttonFontWeight}; mso-border-alt: none; padding: 10px 20px; text-align: center; text-decoration: none; text-transform: capitalize; word-break: keep-all;">
1722
1745
  ${buttonText}
1723
1746
  </a>
1724
1747
  <!--[if mso]>
@@ -1809,7 +1832,7 @@ function generateEmailTemplate(options) {
1809
1832
  <tbody>
1810
1833
  <tr>
1811
1834
  <td>
1812
- <table class="row-content stack" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #1e1b4b; color: #000000; max-width: 640px; width: 100%; margin: 0 auto;">
1835
+ <table class="row-content stack" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: ${footerColor}; color: #000000; max-width: 640px; width: 100%; margin: 0 auto;">
1813
1836
  <tbody>
1814
1837
  <tr>
1815
1838
  <td class="column column-1" width="100%" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; font-weight: 400; text-align: left; vertical-align: top; padding: 20px;">
@@ -1868,7 +1891,7 @@ function extractAppUrl(url) {
1868
1891
  return "https://your-domain.com";
1869
1892
  }
1870
1893
  }
1871
- function generateVerificationEmail(data, appName = "Your App", configuredAppUrl = "") {
1894
+ function generateVerificationEmail(data, appName = "Your App", configuredAppUrl = "", branding) {
1872
1895
  const { email, verificationUrl, firstName } = data;
1873
1896
  const appUrl = configuredAppUrl;
1874
1897
  const safeUrl = sanitizeEmailUrl(verificationUrl, appUrl);
@@ -1878,12 +1901,10 @@ function generateVerificationEmail(data, appName = "Your App", configuredAppUrl
1878
1901
  body: `Please verify that your email address is <strong>${escapeHtml(email)}</strong>, and that you entered it when signing up for ${escapeHtml(appName)}.`,
1879
1902
  buttonText: "Verify Email",
1880
1903
  buttonUrl: safeUrl,
1881
- buttonBgColor: "#00fe9a",
1882
- buttonTextColor: "#000004",
1883
- buttonFontWeight: 500,
1884
1904
  welcomeMessage: safeFirstName ? `Hi ${safeFirstName}, Welcome to ${escapeHtml(appName)}!` : void 0,
1885
1905
  footerNote: "",
1886
- appUrl
1906
+ appUrl,
1907
+ ...branding
1887
1908
  });
1888
1909
  return {
1889
1910
  to: email,
@@ -1903,7 +1924,7 @@ If you didn't create an account with ${appName}, you can safely ignore this emai
1903
1924
  }
1904
1925
  };
1905
1926
  }
1906
- function generatePasswordResetEmail(data, appName = "Your App", configuredAppUrl = "") {
1927
+ function generatePasswordResetEmail(data, appName = "Your App", configuredAppUrl = "", branding) {
1907
1928
  const { email, resetUrl } = data;
1908
1929
  const appUrl = configuredAppUrl;
1909
1930
  const safeUrl = sanitizeEmailUrl(resetUrl, appUrl);
@@ -1912,15 +1933,13 @@ function generatePasswordResetEmail(data, appName = "Your App", configuredAppUrl
1912
1933
  body: "We received a request to reset your password. Click the button below to create a new password.",
1913
1934
  buttonText: "Reset Password",
1914
1935
  buttonUrl: safeUrl,
1915
- buttonBgColor: "#00fe9a",
1916
- buttonTextColor: "#000004",
1917
- buttonFontWeight: 500,
1918
1936
  securityWarning: `
1919
1937
  <strong>Link expires in 1 hour.</strong><br>
1920
1938
  For security, this link can only be used once.
1921
1939
  `,
1922
1940
  footerNote: "If you didn't request this change, no action is needed. Your password will remain unchanged.",
1923
- appUrl
1941
+ appUrl,
1942
+ ...branding
1924
1943
  });
1925
1944
  return {
1926
1945
  to: email,
@@ -1940,7 +1959,7 @@ If you didn't request a password reset, you can safely ignore this email.`,
1940
1959
  }
1941
1960
  };
1942
1961
  }
1943
- function generateEmailChangeConfirmation(data, appName = "Your App", configuredAppUrl = "") {
1962
+ function generateEmailChangeConfirmation(data, appName = "Your App", configuredAppUrl = "", branding) {
1944
1963
  const { newEmail, confirmUrl } = data;
1945
1964
  const appUrl = configuredAppUrl;
1946
1965
  const safeUrl = sanitizeEmailUrl(confirmUrl, appUrl);
@@ -1949,15 +1968,13 @@ function generateEmailChangeConfirmation(data, appName = "Your App", configuredA
1949
1968
  body: `We received a request to change your ${escapeHtml(appName)} account email to this address. Click the button below to confirm this change.`,
1950
1969
  buttonText: "Confirm Email Change",
1951
1970
  buttonUrl: safeUrl,
1952
- buttonBgColor: "#00fe9a",
1953
- buttonTextColor: "#000004",
1954
- buttonFontWeight: 500,
1955
1971
  securityWarning: `
1956
1972
  <strong>This link expires in 24 hours.</strong><br>
1957
1973
  For security, this link can only be used once.
1958
1974
  `,
1959
1975
  footerNote: "If you didn't request this change, you can safely ignore this email.",
1960
- appUrl
1976
+ appUrl,
1977
+ ...branding
1961
1978
  });
1962
1979
  return {
1963
1980
  to: newEmail,
@@ -1979,7 +1996,7 @@ If you didn't request this change, you can safely ignore this email.`,
1979
1996
  }
1980
1997
  };
1981
1998
  }
1982
- function generateEmailChangeNotification(data, appName = "Your App", configuredAppUrl = "") {
1999
+ function generateEmailChangeNotification(data, appName = "Your App", configuredAppUrl = "", branding) {
1983
2000
  const { oldEmail, newEmail, cancelUrl } = data;
1984
2001
  const appUrl = configuredAppUrl;
1985
2002
  const safeUrl = sanitizeEmailUrl(cancelUrl, appUrl);
@@ -1989,15 +2006,13 @@ function generateEmailChangeNotification(data, appName = "Your App", configuredA
1989
2006
  body: `Someone requested to change your ${escapeHtml(appName)} account email to <strong>${safeNewEmail}</strong>.`,
1990
2007
  buttonText: "This Wasn't Me - Cancel Change",
1991
2008
  buttonUrl: safeUrl,
1992
- buttonBgColor: "#00fe9a",
1993
- buttonTextColor: "#000004",
1994
- buttonFontWeight: 500,
1995
2009
  securityWarning: `
1996
2010
  If you made this request, no action is needed.<br>
1997
2011
  Complete the change by clicking the link sent to your new email address.
1998
2012
  `,
1999
2013
  footerNote: "If you didn't request this change, click the button above to cancel it.",
2000
- appUrl
2014
+ appUrl,
2015
+ ...branding
2001
2016
  });
2002
2017
  return {
2003
2018
  to: oldEmail,
@@ -2017,7 +2032,7 @@ ${safeUrl}`,
2017
2032
  }
2018
2033
  };
2019
2034
  }
2020
- function generate2faCodeEmail(data, appName = "Your App", appUrl = "") {
2035
+ function generate2faCodeEmail(data, appName = "Your App", appUrl = "", branding) {
2021
2036
  const { email, firstName, code } = data;
2022
2037
  const safeFirstName = firstName ? escapeHtml(firstName) : "there";
2023
2038
  const html = generateEmailTemplate({
@@ -2032,7 +2047,8 @@ function generate2faCodeEmail(data, appName = "Your App", appUrl = "") {
2032
2047
  buttonText: "",
2033
2048
  buttonUrl: "",
2034
2049
  footerNote: "",
2035
- appUrl
2050
+ appUrl,
2051
+ ...branding
2036
2052
  });
2037
2053
  return {
2038
2054
  to: email,
@@ -2052,7 +2068,7 @@ If you didn't request this code, you can safely ignore this email.
2052
2068
  tags: { type: "2fa_code" }
2053
2069
  };
2054
2070
  }
2055
- function generate2faEnabledEmail(data, appName = "Your App", appUrl = "") {
2071
+ function generate2faEnabledEmail(data, appName = "Your App", appUrl = "", branding) {
2056
2072
  const { email, firstName, method } = data;
2057
2073
  const safeFirstName = firstName ? escapeHtml(firstName) : "there";
2058
2074
  const methodName = method === "totp" ? "an authenticator app" : "email codes";
@@ -2066,7 +2082,8 @@ function generate2faEnabledEmail(data, appName = "Your App", appUrl = "") {
2066
2082
  buttonText: "",
2067
2083
  buttonUrl: "",
2068
2084
  footerNote: "",
2069
- appUrl
2085
+ appUrl,
2086
+ ...branding
2070
2087
  });
2071
2088
  return {
2072
2089
  to: email,
@@ -2082,7 +2099,7 @@ If you didn't make this change, please contact support immediately and reset you
2082
2099
  tags: { type: "2fa_enabled" }
2083
2100
  };
2084
2101
  }
2085
- function generate2faDisabledEmail(data, appName = "Your App", appUrl = "") {
2102
+ function generate2faDisabledEmail(data, appName = "Your App", appUrl = "", branding) {
2086
2103
  const { email, firstName } = data;
2087
2104
  const safeFirstName = firstName ? escapeHtml(firstName) : "there";
2088
2105
  const html = generateEmailTemplate({
@@ -2094,10 +2111,9 @@ function generate2faDisabledEmail(data, appName = "Your App", appUrl = "") {
2094
2111
  `,
2095
2112
  buttonText: "Reset Password",
2096
2113
  buttonUrl: appUrl ? `${appUrl}/forgot-password` : "",
2097
- buttonBgColor: "#ef4444",
2098
- buttonTextColor: "#ffffff",
2099
2114
  footerNote: "",
2100
- appUrl
2115
+ appUrl,
2116
+ ...branding
2101
2117
  });
2102
2118
  return {
2103
2119
  to: email,
@@ -2287,6 +2303,7 @@ function createEmailAdapter(env) {
2287
2303
  var EmailService = class {
2288
2304
  adapter;
2289
2305
  env;
2306
+ branding;
2290
2307
  get fromAddress() {
2291
2308
  if (this.env.EMAIL_FROM) {
2292
2309
  return this.env.EMAIL_FROM;
@@ -2300,9 +2317,10 @@ var EmailService = class {
2300
2317
  }
2301
2318
  return `${appName} <hello@${domain}>`;
2302
2319
  }
2303
- constructor(env, adapter) {
2320
+ constructor(env, adapter, branding) {
2304
2321
  this.env = env;
2305
2322
  this.adapter = adapter ?? createEmailAdapter(env);
2323
+ this.branding = branding;
2306
2324
  }
2307
2325
  /**
2308
2326
  * Check if email address has bounced or complained
@@ -2354,7 +2372,8 @@ var EmailService = class {
2354
2372
  const template = generateVerificationEmail(
2355
2373
  data,
2356
2374
  this.env.APP_NAME ?? "Your App",
2357
- this.env.APP_URL ?? ""
2375
+ this.env.APP_URL ?? "",
2376
+ this.branding
2358
2377
  );
2359
2378
  const recipient = this.getRecipientEmail(data.email, "verification");
2360
2379
  logger_default.info("Sending verification email", {
@@ -2414,7 +2433,8 @@ var EmailService = class {
2414
2433
  const template = generatePasswordResetEmail(
2415
2434
  data,
2416
2435
  this.env.APP_NAME ?? "Your App",
2417
- this.env.APP_URL ?? ""
2436
+ this.env.APP_URL ?? "",
2437
+ this.branding
2418
2438
  );
2419
2439
  const recipient = this.getRecipientEmail(data.email, "password_reset");
2420
2440
  logger_default.info("Sending password reset email", {
@@ -2474,7 +2494,8 @@ var EmailService = class {
2474
2494
  const template = generateEmailChangeConfirmation(
2475
2495
  data,
2476
2496
  this.env.APP_NAME ?? "Your App",
2477
- this.env.APP_URL ?? ""
2497
+ this.env.APP_URL ?? "",
2498
+ this.branding
2478
2499
  );
2479
2500
  const recipient = this.getRecipientEmail(data.newEmail, "email_change_confirmation");
2480
2501
  logger_default.info("Sending email change confirmation", {
@@ -2534,7 +2555,8 @@ var EmailService = class {
2534
2555
  const template = generateEmailChangeNotification(
2535
2556
  data,
2536
2557
  this.env.APP_NAME ?? "Your App",
2537
- this.env.APP_URL ?? ""
2558
+ this.env.APP_URL ?? "",
2559
+ this.branding
2538
2560
  );
2539
2561
  const recipient = this.getRecipientEmail(data.oldEmail, "email_change_notification");
2540
2562
  logger_default.info("Sending email change notification", {
@@ -2591,7 +2613,8 @@ var EmailService = class {
2591
2613
  const template = generate2faCodeEmail(
2592
2614
  data,
2593
2615
  this.env.APP_NAME ?? "Your App",
2594
- this.env.APP_URL ?? ""
2616
+ this.env.APP_URL ?? "",
2617
+ this.branding
2595
2618
  );
2596
2619
  const recipient = this.getRecipientEmail(data.email, "2fa_code");
2597
2620
  logger_default.info("Sending 2FA code email", {
@@ -2648,7 +2671,8 @@ var EmailService = class {
2648
2671
  const template = generate2faEnabledEmail(
2649
2672
  data,
2650
2673
  this.env.APP_NAME ?? "Your App",
2651
- this.env.APP_URL ?? ""
2674
+ this.env.APP_URL ?? "",
2675
+ this.branding
2652
2676
  );
2653
2677
  const recipient = this.getRecipientEmail(data.email, "2fa_enabled");
2654
2678
  logger_default.info("Sending 2FA enabled email", {
@@ -2706,7 +2730,8 @@ var EmailService = class {
2706
2730
  const template = generate2faDisabledEmail(
2707
2731
  data,
2708
2732
  this.env.APP_NAME ?? "Your App",
2709
- this.env.APP_URL ?? ""
2733
+ this.env.APP_URL ?? "",
2734
+ this.branding
2710
2735
  );
2711
2736
  const recipient = this.getRecipientEmail(data.email, "2fa_disabled");
2712
2737
  logger_default.info("Sending 2FA disabled email", {
@@ -3038,7 +3063,7 @@ var signupRoute = createRoute({
3038
3063
  });
3039
3064
  var signupHandler = async (c) => {
3040
3065
  const { email, password, name, turnstileToken, oauthToken } = c.req.valid("json");
3041
- const { db: database, schema } = getAuthContext(c);
3066
+ const { db: database, schema, emailBranding } = getAuthContext(c);
3042
3067
  const env = c.env;
3043
3068
  if (oauthToken) {
3044
3069
  const stored = await env.OAUTH_STATES.get(`oauth:signup:${oauthToken}`);
@@ -3138,7 +3163,7 @@ var signupHandler = async (c) => {
3138
3163
  expiresAt,
3139
3164
  createdAt: Date.now()
3140
3165
  });
3141
- const emailService = new EmailService(env);
3166
+ const emailService = new EmailService(env, void 0, emailBranding);
3142
3167
  const verificationUrl = `${env.APP_URL}/verify-email?token=${emailToken}`;
3143
3168
  await emailService.sendVerificationEmail(
3144
3169
  {
@@ -3622,7 +3647,7 @@ var forgotPasswordRoute = createRoute6({
3622
3647
  });
3623
3648
  var forgotPasswordHandler = async (c) => {
3624
3649
  const { email, turnstileToken } = c.req.valid("json");
3625
- const { db: database, schema } = getAuthContext(c);
3650
+ const { db: database, schema, emailBranding } = getAuthContext(c);
3626
3651
  const env = c.env;
3627
3652
  const turnstileValid = await verifyTurnstileToken(
3628
3653
  turnstileToken,
@@ -3641,7 +3666,7 @@ var forgotPasswordHandler = async (c) => {
3641
3666
  if (!resetResult) {
3642
3667
  return c.json({ success: true });
3643
3668
  }
3644
- const emailService = new EmailService(env);
3669
+ const emailService = new EmailService(env, void 0, emailBranding);
3645
3670
  const resetUrl = `${env.APP_URL}/reset-password?token=${resetResult.token}`;
3646
3671
  await emailService.sendPasswordResetEmail(
3647
3672
  {
@@ -3889,7 +3914,7 @@ var changeEmailHandler = async (c) => {
3889
3914
  const userId = c.get("userId");
3890
3915
  if (!userId) return problems.unauthorized(c, "Not authenticated");
3891
3916
  const { password, newEmail } = c.req.valid("json");
3892
- const { db: database, schema } = getAuthContext(c);
3917
+ const { db: database, schema, emailBranding } = getAuthContext(c);
3893
3918
  const env = c.env;
3894
3919
  const appUrl = env.APP_URL || "http://localhost:5173";
3895
3920
  const result = await requestEmailChange({
@@ -3906,7 +3931,7 @@ var changeEmailHandler = async (c) => {
3906
3931
  }
3907
3932
  return problems.badRequest(c, result.error || "Failed to request email change");
3908
3933
  }
3909
- const emailService = new EmailService(env);
3934
+ const emailService = new EmailService(env, void 0, emailBranding);
3910
3935
  const confirmUrl = `${appUrl}/confirm-email-change?token=${result.confirmToken}`;
3911
3936
  const confirmResult = await emailService.sendEmailChangeConfirmation(
3912
3937
  { newEmail, confirmUrl },
@@ -4211,7 +4236,7 @@ var resendVerificationRoute = createRoute15({
4211
4236
  });
4212
4237
  var resendVerificationHandler = async (c) => {
4213
4238
  const userId = c.get("userId");
4214
- const { db, schema } = getAuthContext(c);
4239
+ const { db, schema, emailBranding } = getAuthContext(c);
4215
4240
  const env = c.env;
4216
4241
  const [user] = await db.select({
4217
4242
  id: schema.users.id,
@@ -4236,7 +4261,7 @@ var resendVerificationHandler = async (c) => {
4236
4261
  expiresAt,
4237
4262
  createdAt: Date.now()
4238
4263
  });
4239
- const emailService = new EmailService(env);
4264
+ const emailService = new EmailService(env, void 0, emailBranding);
4240
4265
  const verificationUrl = `${env.APP_URL}/verify-email?token=${emailToken}`;
4241
4266
  await emailService.sendVerificationEmail(
4242
4267
  {
@@ -4412,10 +4437,10 @@ var statusRoute = createRoute16({
4412
4437
  content: { "application/json": { schema: statusResponseSchema } },
4413
4438
  headers: {
4414
4439
  "Cache-Control": {
4415
- description: "Cache for 24 hours",
4440
+ description: "Cache for 60 seconds",
4416
4441
  schema: {
4417
4442
  type: "string",
4418
- example: "private, max-age=86400, stale-while-revalidate=3600"
4443
+ example: "private, max-age=60"
4419
4444
  }
4420
4445
  }
4421
4446
  }
@@ -4432,7 +4457,7 @@ var statusHandler = async (c) => {
4432
4457
  const { db, schema } = getAuthContext(c);
4433
4458
  const methods = await getUserEnabled2faMethods(db, userId, schema.user2faMethods);
4434
4459
  const backupCodes = await db.select({ id: schema.userBackupCodes.id }).from(schema.userBackupCodes).where(and7(eq19(schema.userBackupCodes.userId, userId), isNull2(schema.userBackupCodes.usedAt)));
4435
- c.header("Cache-Control", "private, max-age=86400, stale-while-revalidate=3600");
4460
+ c.header("Cache-Control", "private, max-age=60");
4436
4461
  c.header("Vary", "Cookie");
4437
4462
  return c.json({
4438
4463
  enabled: methods.length > 0,
@@ -4524,7 +4549,7 @@ var totpVerifyHandler = async (c) => {
4524
4549
  const userId = c.get("userId");
4525
4550
  if (!userId) return problems.unauthorized(c);
4526
4551
  const { code } = c.req.valid("json");
4527
- const { db, schema, env } = getAuthContext(c);
4552
+ const { db, schema, env, emailBranding } = getAuthContext(c);
4528
4553
  const secret = await env.OAUTH_STATES.get(`totp_setup:${userId}`);
4529
4554
  if (!secret) {
4530
4555
  return problems.badRequest(c, "Setup expired. Please start again.");
@@ -4568,7 +4593,7 @@ var totpVerifyHandler = async (c) => {
4568
4593
  backupCodes = codes.map(formatBackupCode);
4569
4594
  }
4570
4595
  if (user) {
4571
- const emailService = new EmailService(env);
4596
+ const emailService = new EmailService(env, void 0, emailBranding);
4572
4597
  const firstName = user.name?.split(" ")[0] || null;
4573
4598
  await emailService.send2faEnabledEmail(
4574
4599
  { email: user.email, firstName, method: "totp" },
@@ -4616,7 +4641,7 @@ var totpDisableHandler = async (c) => {
4616
4641
  return problems.unauthorized(c);
4617
4642
  }
4618
4643
  const { code, method } = c.req.valid("json");
4619
- const { db, schema, env } = getAuthContext(c);
4644
+ const { db, schema, env, emailBranding } = getAuthContext(c);
4620
4645
  const verification = await verifyAny2faCode(db, env, userId, code, method, {
4621
4646
  user2faMethods: schema.user2faMethods,
4622
4647
  userBackupCodes: schema.userBackupCodes
@@ -4633,7 +4658,7 @@ var totpDisableHandler = async (c) => {
4633
4658
  await invalidateAllUserSessions(db, { sessions: schema.sessions, users: schema.users }, userId);
4634
4659
  const [user] = await db.select({ email: schema.users.email, name: schema.users.name }).from(schema.users).where(eq22(schema.users.id, userId)).limit(1);
4635
4660
  if (user) {
4636
- const emailService = new EmailService(env);
4661
+ const emailService = new EmailService(env, void 0, emailBranding);
4637
4662
  const firstName = user.name?.split(" ")[0] || null;
4638
4663
  await emailService.send2faDisabledEmail({ email: user.email, firstName }, db, schema.users);
4639
4664
  }
@@ -4679,7 +4704,7 @@ var emailSetupRoute = createRoute20({
4679
4704
  var emailSetupHandler = async (c) => {
4680
4705
  const userId = c.get("userId");
4681
4706
  if (!userId) return problems.unauthorized(c);
4682
- const { db, schema, env } = getAuthContext(c);
4707
+ const { db, schema, env, emailBranding } = getAuthContext(c);
4683
4708
  const existing = await db.select({ id: schema.user2faMethods.id }).from(schema.user2faMethods).where(and10(eq23(schema.user2faMethods.userId, userId), eq23(schema.user2faMethods.method, "email"))).limit(1);
4684
4709
  if (existing.length > 0) {
4685
4710
  return problems.badRequest(c, "Email 2FA already enabled");
@@ -4689,7 +4714,7 @@ var emailSetupHandler = async (c) => {
4689
4714
  const code = generateEmailOtp();
4690
4715
  const codeHash = await hashToken(code);
4691
4716
  await env.OAUTH_STATES.put(`email_2fa_setup:${userId}`, codeHash, { expirationTtl: 300 });
4692
- const emailService = new EmailService(env);
4717
+ const emailService = new EmailService(env, void 0, emailBranding);
4693
4718
  const firstName = user.name?.split(" ")[0] || null;
4694
4719
  await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db, schema.users);
4695
4720
  logSecurityEvent("2fa_email_setup_initiated", "low", { userId });
@@ -4730,7 +4755,7 @@ var emailVerifyHandler = async (c) => {
4730
4755
  const userId = c.get("userId");
4731
4756
  if (!userId) return problems.unauthorized(c);
4732
4757
  const { code } = c.req.valid("json");
4733
- const { db, schema, env } = getAuthContext(c);
4758
+ const { db, schema, env, emailBranding } = getAuthContext(c);
4734
4759
  const storedHash = await env.OAUTH_STATES.get(`email_2fa_setup:${userId}`);
4735
4760
  if (!storedHash) {
4736
4761
  return problems.badRequest(c, "Code expired. Please request a new one.");
@@ -4768,7 +4793,7 @@ var emailVerifyHandler = async (c) => {
4768
4793
  backupCodes = codes.map(formatBackupCode);
4769
4794
  }
4770
4795
  if (user) {
4771
- const emailService = new EmailService(env);
4796
+ const emailService = new EmailService(env, void 0, emailBranding);
4772
4797
  const firstName = user.name?.split(" ")[0] || null;
4773
4798
  await emailService.send2faEnabledEmail(
4774
4799
  { email: user.email, firstName, method: "email" },
@@ -4811,7 +4836,7 @@ var emailSendCodeRoute = createRoute22({
4811
4836
  var emailSendCodeHandler = async (c) => {
4812
4837
  const userId = c.get("userId");
4813
4838
  if (!userId) return problems.unauthorized(c);
4814
- const { db, schema, env } = getAuthContext(c);
4839
+ const { db, schema, env, emailBranding } = getAuthContext(c);
4815
4840
  const [emailMethod] = await db.select().from(schema.user2faMethods).where(and11(eq25(schema.user2faMethods.userId, userId), eq25(schema.user2faMethods.method, "email"))).limit(1);
4816
4841
  if (!emailMethod) {
4817
4842
  return problems.badRequest(c, "Email 2FA not enabled");
@@ -4823,7 +4848,7 @@ var emailSendCodeHandler = async (c) => {
4823
4848
  await env.OAUTH_STATES.put(`email_2fa_challenge:${userId}`, codeHash, {
4824
4849
  expirationTtl: 300
4825
4850
  });
4826
- const emailService = new EmailService(env);
4851
+ const emailService = new EmailService(env, void 0, emailBranding);
4827
4852
  const firstName = user.name?.split(" ")[0] || null;
4828
4853
  const emailResult = await emailService.send2faCodeEmail(
4829
4854
  { email: user.email, firstName, code },
@@ -4876,7 +4901,7 @@ var emailDisableHandler = async (c) => {
4876
4901
  return problems.unauthorized(c);
4877
4902
  }
4878
4903
  const { code, method } = c.req.valid("json");
4879
- const { db, schema, env } = getAuthContext(c);
4904
+ const { db, schema, env, emailBranding } = getAuthContext(c);
4880
4905
  const verification = await verifyAny2faCode(db, env, userId, code, method, {
4881
4906
  user2faMethods: schema.user2faMethods,
4882
4907
  userBackupCodes: schema.userBackupCodes
@@ -4893,7 +4918,7 @@ var emailDisableHandler = async (c) => {
4893
4918
  await invalidateAllUserSessions(db, { sessions: schema.sessions, users: schema.users }, userId);
4894
4919
  const [user] = await db.select({ email: schema.users.email, name: schema.users.name }).from(schema.users).where(eq26(schema.users.id, userId)).limit(1);
4895
4920
  if (user) {
4896
- const emailService = new EmailService(env);
4921
+ const emailService = new EmailService(env, void 0, emailBranding);
4897
4922
  const firstName = user.name?.split(" ")[0] || null;
4898
4923
  await emailService.send2faDisabledEmail({ email: user.email, firstName }, db, schema.users);
4899
4924
  }
@@ -4923,10 +4948,10 @@ var trustedDevicesGetRoute = createRoute24({
4923
4948
  content: { "application/json": { schema: trustedDevicesResponseSchema } },
4924
4949
  headers: {
4925
4950
  "Cache-Control": {
4926
- description: "Cache for 24 hours",
4951
+ description: "Cache for 60 seconds",
4927
4952
  schema: {
4928
4953
  type: "string",
4929
- example: "private, max-age=86400, stale-while-revalidate=3600"
4954
+ example: "private, max-age=60"
4930
4955
  }
4931
4956
  }
4932
4957
  }
@@ -4950,7 +4975,7 @@ var trustedDevicesGetHandler = async (c) => {
4950
4975
  lastUsedAt: schema.userTrustedDevices.lastUsedAt,
4951
4976
  createdAt: schema.userTrustedDevices.createdAt
4952
4977
  }).from(schema.userTrustedDevices).where(and13(eq27(schema.userTrustedDevices.userId, userId), gt5(schema.userTrustedDevices.expiresAt, now)));
4953
- c.header("Cache-Control", "private, max-age=86400, stale-while-revalidate=3600");
4978
+ c.header("Cache-Control", "private, max-age=60");
4954
4979
  c.header("Vary", "Cookie");
4955
4980
  return c.json({ devices });
4956
4981
  };
@@ -5190,7 +5215,7 @@ var challengeResendRoute = createRoute27({
5190
5215
  }
5191
5216
  });
5192
5217
  var challengeResendHandler = async (c) => {
5193
- const { db, schema } = getAuthContext(c);
5218
+ const { db, schema, emailBranding } = getAuthContext(c);
5194
5219
  const cookieName = getChallengeCookieName(c.env);
5195
5220
  const challengeToken = getCookie3(c, cookieName);
5196
5221
  if (!challengeToken) {
@@ -5214,7 +5239,7 @@ var challengeResendHandler = async (c) => {
5214
5239
  await c.env.OAUTH_STATES.put(`email_2fa_challenge:${payload.userId}`, codeHash, {
5215
5240
  expirationTtl: 300
5216
5241
  });
5217
- const emailService = new EmailService(c.env);
5242
+ const emailService = new EmailService(c.env, void 0, emailBranding);
5218
5243
  const firstName = user.name?.split(" ")[0] || null;
5219
5244
  await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db, schema.users);
5220
5245
  logger_default.info("Email 2FA code resent", { userId: payload.userId });