@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.cjs CHANGED
@@ -1341,11 +1341,12 @@ var import_drizzle_orm4 = require("drizzle-orm");
1341
1341
 
1342
1342
  // src/factory.ts
1343
1343
  function createAuth(config) {
1344
- const { db, schema, getEnv } = config;
1344
+ const { db, schema, getEnv, emailBranding } = config;
1345
1345
  const getContext = (c) => ({
1346
1346
  db,
1347
1347
  schema,
1348
- env: getEnv(c)
1348
+ env: getEnv(c),
1349
+ emailBranding
1349
1350
  });
1350
1351
  return {
1351
1352
  db,
@@ -1582,6 +1583,19 @@ var problems = {
1582
1583
  };
1583
1584
 
1584
1585
  // src/middleware/auth.ts
1586
+ var SESSION_MAX_LIFETIME_MS = AUTH_DEFAULTS.SESSION_MAX_LIFETIME_DAYS * 24 * 60 * 60 * 1e3;
1587
+ async function revokeIfPastMaxLifetime(db, sessions, sessionId, session) {
1588
+ if (Date.now() - session.createdAt <= SESSION_MAX_LIFETIME_MS) return false;
1589
+ logger_default.info("Session exceeded absolute lifetime - revoked", {
1590
+ type: "security",
1591
+ event: "session_max_lifetime_exceeded",
1592
+ severity: "low",
1593
+ sessionId: sessionId.slice(0, 8),
1594
+ userId: session.userId
1595
+ });
1596
+ await deleteSession(db, { sessions }, sessionId);
1597
+ return true;
1598
+ }
1585
1599
  async function getSessionFromCookie(db, sessions, cookieHeader, request, env) {
1586
1600
  const cookieName = getSessionCookieName(env);
1587
1601
  const cookiePattern = new RegExp(`${cookieName}=([^;]+)`);
@@ -1591,10 +1605,13 @@ async function getSessionFromCookie(db, sessions, cookieHeader, request, env) {
1591
1605
  const foundSessions = await db.select({
1592
1606
  userId: sessions.userId,
1593
1607
  expiresAt: sessions.expiresAt,
1608
+ createdAt: sessions.createdAt,
1594
1609
  fingerprint: sessions.fingerprint
1595
1610
  }).from(sessions).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(sessions.id, sessionId), (0, import_drizzle_orm4.gt)(sessions.expiresAt, Date.now()))).limit(1);
1596
1611
  if (foundSessions.length === 0) return null;
1597
1612
  const session = foundSessions[0];
1613
+ if (await revokeIfPastMaxLifetime(db, sessions, sessionId, session)) return null;
1614
+ await refreshSession(db, { sessions }, sessionId);
1598
1615
  const userAgent = request.headers.get("user-agent") || "";
1599
1616
  const cfWorker = request.headers.get("cf-worker") || "";
1600
1617
  const clientIp = request.headers.get("cf-connecting-ip") || request.headers.get("x-real-ip") || "";
@@ -1638,10 +1655,12 @@ async function getSessionFromBearerToken(db, sessions, authHeader) {
1638
1655
  const sessionId = authHeader.slice(7);
1639
1656
  const foundSessions = await db.select({
1640
1657
  userId: sessions.userId,
1641
- expiresAt: sessions.expiresAt
1658
+ expiresAt: sessions.expiresAt,
1659
+ createdAt: sessions.createdAt
1642
1660
  }).from(sessions).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(sessions.id, sessionId), (0, import_drizzle_orm4.gt)(sessions.expiresAt, Date.now()))).limit(1);
1643
1661
  if (foundSessions.length === 0) return null;
1644
1662
  const session = foundSessions[0];
1663
+ if (await revokeIfPastMaxLifetime(db, sessions, sessionId, session)) return null;
1645
1664
  await refreshSession(db, { sessions }, sessionId);
1646
1665
  return {
1647
1666
  userId: session.userId,
@@ -1909,11 +1928,15 @@ function generateEmailTemplate(options) {
1909
1928
  appName = "App",
1910
1929
  buttonBgColor = "#00fe9a",
1911
1930
  buttonTextColor = "#000004",
1912
- buttonFontWeight = 500
1931
+ buttonFontWeight = 500,
1932
+ buttonRadius = "6px",
1933
+ logoUrl,
1934
+ footerColor = "#1e1b4b"
1913
1935
  } = options;
1914
1936
  const safeButtonUrl = buttonUrl ? sanitizeEmailUrl(buttonUrl, appUrl) : "";
1915
1937
  const safeAppUrl = appUrl ? sanitizeEmailUrl(appUrl, appUrl) : "";
1916
1938
  const safeMarketingUrl = marketingUrl ? sanitizeEmailUrl(marketingUrl, appUrl) : "";
1939
+ const safeLogoUrl = logoUrl ? sanitizeEmailUrl(logoUrl, appUrl) : `${safeAppUrl.replace(/\/$/, "")}/email-header-image.png`;
1917
1940
  return `
1918
1941
  <!DOCTYPE html>
1919
1942
  <html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" lang="en">
@@ -2053,7 +2076,7 @@ function generateEmailTemplate(options) {
2053
2076
  <td class="pad" style="padding-left:40px;padding-right:40px;width:100%;">
2054
2077
  <div class="alignment" align="center">
2055
2078
  <div class="fullWidth" style="max-width: 50%;">
2056
- <img src="${safeAppUrl}/email-header-image.png" style="display: block; height: auto; border: 0; width: 100%;" alt="${appName}" title="${appName}" height="auto">
2079
+ <img src="${safeLogoUrl}" style="display: block; height: auto; border: 0; width: 100%;" alt="${appName}" title="${appName}" height="auto">
2057
2080
  </div>
2058
2081
  </div>
2059
2082
  </td>
@@ -2110,7 +2133,7 @@ function generateEmailTemplate(options) {
2110
2133
  <v:textbox inset="0px,0px,0px,0px">
2111
2134
  <center dir="false" style="color:${buttonTextColor};font-family:'Montserrat',Arial,sans-serif;font-size:16px;font-weight:${buttonFontWeight};">
2112
2135
  <![endif]-->
2113
- <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;">
2136
+ <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;">
2114
2137
  ${buttonText}
2115
2138
  </a>
2116
2139
  <!--[if mso]>
@@ -2201,7 +2224,7 @@ function generateEmailTemplate(options) {
2201
2224
  <tbody>
2202
2225
  <tr>
2203
2226
  <td>
2204
- <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;">
2227
+ <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;">
2205
2228
  <tbody>
2206
2229
  <tr>
2207
2230
  <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;">
@@ -2260,7 +2283,7 @@ function extractAppUrl(url) {
2260
2283
  return "https://your-domain.com";
2261
2284
  }
2262
2285
  }
2263
- function generateVerificationEmail(data, appName = "Your App", configuredAppUrl = "") {
2286
+ function generateVerificationEmail(data, appName = "Your App", configuredAppUrl = "", branding) {
2264
2287
  const { email, verificationUrl, firstName } = data;
2265
2288
  const appUrl = configuredAppUrl;
2266
2289
  const safeUrl = sanitizeEmailUrl(verificationUrl, appUrl);
@@ -2270,12 +2293,10 @@ function generateVerificationEmail(data, appName = "Your App", configuredAppUrl
2270
2293
  body: `Please verify that your email address is <strong>${escapeHtml(email)}</strong>, and that you entered it when signing up for ${escapeHtml(appName)}.`,
2271
2294
  buttonText: "Verify Email",
2272
2295
  buttonUrl: safeUrl,
2273
- buttonBgColor: "#00fe9a",
2274
- buttonTextColor: "#000004",
2275
- buttonFontWeight: 500,
2276
2296
  welcomeMessage: safeFirstName ? `Hi ${safeFirstName}, Welcome to ${escapeHtml(appName)}!` : void 0,
2277
2297
  footerNote: "",
2278
- appUrl
2298
+ appUrl,
2299
+ ...branding
2279
2300
  });
2280
2301
  return {
2281
2302
  to: email,
@@ -2295,7 +2316,7 @@ If you didn't create an account with ${appName}, you can safely ignore this emai
2295
2316
  }
2296
2317
  };
2297
2318
  }
2298
- function generatePasswordResetEmail(data, appName = "Your App", configuredAppUrl = "") {
2319
+ function generatePasswordResetEmail(data, appName = "Your App", configuredAppUrl = "", branding) {
2299
2320
  const { email, resetUrl } = data;
2300
2321
  const appUrl = configuredAppUrl;
2301
2322
  const safeUrl = sanitizeEmailUrl(resetUrl, appUrl);
@@ -2304,15 +2325,13 @@ function generatePasswordResetEmail(data, appName = "Your App", configuredAppUrl
2304
2325
  body: "We received a request to reset your password. Click the button below to create a new password.",
2305
2326
  buttonText: "Reset Password",
2306
2327
  buttonUrl: safeUrl,
2307
- buttonBgColor: "#00fe9a",
2308
- buttonTextColor: "#000004",
2309
- buttonFontWeight: 500,
2310
2328
  securityWarning: `
2311
2329
  <strong>Link expires in 1 hour.</strong><br>
2312
2330
  For security, this link can only be used once.
2313
2331
  `,
2314
2332
  footerNote: "If you didn't request this change, no action is needed. Your password will remain unchanged.",
2315
- appUrl
2333
+ appUrl,
2334
+ ...branding
2316
2335
  });
2317
2336
  return {
2318
2337
  to: email,
@@ -2332,7 +2351,7 @@ If you didn't request a password reset, you can safely ignore this email.`,
2332
2351
  }
2333
2352
  };
2334
2353
  }
2335
- function generateEmailChangeConfirmation(data, appName = "Your App", configuredAppUrl = "") {
2354
+ function generateEmailChangeConfirmation(data, appName = "Your App", configuredAppUrl = "", branding) {
2336
2355
  const { newEmail, confirmUrl } = data;
2337
2356
  const appUrl = configuredAppUrl;
2338
2357
  const safeUrl = sanitizeEmailUrl(confirmUrl, appUrl);
@@ -2341,15 +2360,13 @@ function generateEmailChangeConfirmation(data, appName = "Your App", configuredA
2341
2360
  body: `We received a request to change your ${escapeHtml(appName)} account email to this address. Click the button below to confirm this change.`,
2342
2361
  buttonText: "Confirm Email Change",
2343
2362
  buttonUrl: safeUrl,
2344
- buttonBgColor: "#00fe9a",
2345
- buttonTextColor: "#000004",
2346
- buttonFontWeight: 500,
2347
2363
  securityWarning: `
2348
2364
  <strong>This link expires in 24 hours.</strong><br>
2349
2365
  For security, this link can only be used once.
2350
2366
  `,
2351
2367
  footerNote: "If you didn't request this change, you can safely ignore this email.",
2352
- appUrl
2368
+ appUrl,
2369
+ ...branding
2353
2370
  });
2354
2371
  return {
2355
2372
  to: newEmail,
@@ -2371,7 +2388,7 @@ If you didn't request this change, you can safely ignore this email.`,
2371
2388
  }
2372
2389
  };
2373
2390
  }
2374
- function generateEmailChangeNotification(data, appName = "Your App", configuredAppUrl = "") {
2391
+ function generateEmailChangeNotification(data, appName = "Your App", configuredAppUrl = "", branding) {
2375
2392
  const { oldEmail, newEmail, cancelUrl } = data;
2376
2393
  const appUrl = configuredAppUrl;
2377
2394
  const safeUrl = sanitizeEmailUrl(cancelUrl, appUrl);
@@ -2381,15 +2398,13 @@ function generateEmailChangeNotification(data, appName = "Your App", configuredA
2381
2398
  body: `Someone requested to change your ${escapeHtml(appName)} account email to <strong>${safeNewEmail}</strong>.`,
2382
2399
  buttonText: "This Wasn't Me - Cancel Change",
2383
2400
  buttonUrl: safeUrl,
2384
- buttonBgColor: "#00fe9a",
2385
- buttonTextColor: "#000004",
2386
- buttonFontWeight: 500,
2387
2401
  securityWarning: `
2388
2402
  If you made this request, no action is needed.<br>
2389
2403
  Complete the change by clicking the link sent to your new email address.
2390
2404
  `,
2391
2405
  footerNote: "If you didn't request this change, click the button above to cancel it.",
2392
- appUrl
2406
+ appUrl,
2407
+ ...branding
2393
2408
  });
2394
2409
  return {
2395
2410
  to: oldEmail,
@@ -2409,7 +2424,7 @@ ${safeUrl}`,
2409
2424
  }
2410
2425
  };
2411
2426
  }
2412
- function generate2faCodeEmail(data, appName = "Your App", appUrl = "") {
2427
+ function generate2faCodeEmail(data, appName = "Your App", appUrl = "", branding) {
2413
2428
  const { email, firstName, code } = data;
2414
2429
  const safeFirstName = firstName ? escapeHtml(firstName) : "there";
2415
2430
  const html = generateEmailTemplate({
@@ -2424,7 +2439,8 @@ function generate2faCodeEmail(data, appName = "Your App", appUrl = "") {
2424
2439
  buttonText: "",
2425
2440
  buttonUrl: "",
2426
2441
  footerNote: "",
2427
- appUrl
2442
+ appUrl,
2443
+ ...branding
2428
2444
  });
2429
2445
  return {
2430
2446
  to: email,
@@ -2444,7 +2460,7 @@ If you didn't request this code, you can safely ignore this email.
2444
2460
  tags: { type: "2fa_code" }
2445
2461
  };
2446
2462
  }
2447
- function generate2faEnabledEmail(data, appName = "Your App", appUrl = "") {
2463
+ function generate2faEnabledEmail(data, appName = "Your App", appUrl = "", branding) {
2448
2464
  const { email, firstName, method } = data;
2449
2465
  const safeFirstName = firstName ? escapeHtml(firstName) : "there";
2450
2466
  const methodName = method === "totp" ? "an authenticator app" : "email codes";
@@ -2458,7 +2474,8 @@ function generate2faEnabledEmail(data, appName = "Your App", appUrl = "") {
2458
2474
  buttonText: "",
2459
2475
  buttonUrl: "",
2460
2476
  footerNote: "",
2461
- appUrl
2477
+ appUrl,
2478
+ ...branding
2462
2479
  });
2463
2480
  return {
2464
2481
  to: email,
@@ -2474,7 +2491,7 @@ If you didn't make this change, please contact support immediately and reset you
2474
2491
  tags: { type: "2fa_enabled" }
2475
2492
  };
2476
2493
  }
2477
- function generate2faDisabledEmail(data, appName = "Your App", appUrl = "") {
2494
+ function generate2faDisabledEmail(data, appName = "Your App", appUrl = "", branding) {
2478
2495
  const { email, firstName } = data;
2479
2496
  const safeFirstName = firstName ? escapeHtml(firstName) : "there";
2480
2497
  const html = generateEmailTemplate({
@@ -2486,10 +2503,9 @@ function generate2faDisabledEmail(data, appName = "Your App", appUrl = "") {
2486
2503
  `,
2487
2504
  buttonText: "Reset Password",
2488
2505
  buttonUrl: appUrl ? `${appUrl}/forgot-password` : "",
2489
- buttonBgColor: "#ef4444",
2490
- buttonTextColor: "#ffffff",
2491
2506
  footerNote: "",
2492
- appUrl
2507
+ appUrl,
2508
+ ...branding
2493
2509
  });
2494
2510
  return {
2495
2511
  to: email,
@@ -2679,6 +2695,7 @@ function createEmailAdapter(env) {
2679
2695
  var EmailService = class {
2680
2696
  adapter;
2681
2697
  env;
2698
+ branding;
2682
2699
  get fromAddress() {
2683
2700
  if (this.env.EMAIL_FROM) {
2684
2701
  return this.env.EMAIL_FROM;
@@ -2692,9 +2709,10 @@ var EmailService = class {
2692
2709
  }
2693
2710
  return `${appName} <hello@${domain}>`;
2694
2711
  }
2695
- constructor(env, adapter) {
2712
+ constructor(env, adapter, branding) {
2696
2713
  this.env = env;
2697
2714
  this.adapter = adapter ?? createEmailAdapter(env);
2715
+ this.branding = branding;
2698
2716
  }
2699
2717
  /**
2700
2718
  * Check if email address has bounced or complained
@@ -2746,7 +2764,8 @@ var EmailService = class {
2746
2764
  const template = generateVerificationEmail(
2747
2765
  data,
2748
2766
  this.env.APP_NAME ?? "Your App",
2749
- this.env.APP_URL ?? ""
2767
+ this.env.APP_URL ?? "",
2768
+ this.branding
2750
2769
  );
2751
2770
  const recipient = this.getRecipientEmail(data.email, "verification");
2752
2771
  logger_default.info("Sending verification email", {
@@ -2806,7 +2825,8 @@ var EmailService = class {
2806
2825
  const template = generatePasswordResetEmail(
2807
2826
  data,
2808
2827
  this.env.APP_NAME ?? "Your App",
2809
- this.env.APP_URL ?? ""
2828
+ this.env.APP_URL ?? "",
2829
+ this.branding
2810
2830
  );
2811
2831
  const recipient = this.getRecipientEmail(data.email, "password_reset");
2812
2832
  logger_default.info("Sending password reset email", {
@@ -2866,7 +2886,8 @@ var EmailService = class {
2866
2886
  const template = generateEmailChangeConfirmation(
2867
2887
  data,
2868
2888
  this.env.APP_NAME ?? "Your App",
2869
- this.env.APP_URL ?? ""
2889
+ this.env.APP_URL ?? "",
2890
+ this.branding
2870
2891
  );
2871
2892
  const recipient = this.getRecipientEmail(data.newEmail, "email_change_confirmation");
2872
2893
  logger_default.info("Sending email change confirmation", {
@@ -2926,7 +2947,8 @@ var EmailService = class {
2926
2947
  const template = generateEmailChangeNotification(
2927
2948
  data,
2928
2949
  this.env.APP_NAME ?? "Your App",
2929
- this.env.APP_URL ?? ""
2950
+ this.env.APP_URL ?? "",
2951
+ this.branding
2930
2952
  );
2931
2953
  const recipient = this.getRecipientEmail(data.oldEmail, "email_change_notification");
2932
2954
  logger_default.info("Sending email change notification", {
@@ -2983,7 +3005,8 @@ var EmailService = class {
2983
3005
  const template = generate2faCodeEmail(
2984
3006
  data,
2985
3007
  this.env.APP_NAME ?? "Your App",
2986
- this.env.APP_URL ?? ""
3008
+ this.env.APP_URL ?? "",
3009
+ this.branding
2987
3010
  );
2988
3011
  const recipient = this.getRecipientEmail(data.email, "2fa_code");
2989
3012
  logger_default.info("Sending 2FA code email", {
@@ -3040,7 +3063,8 @@ var EmailService = class {
3040
3063
  const template = generate2faEnabledEmail(
3041
3064
  data,
3042
3065
  this.env.APP_NAME ?? "Your App",
3043
- this.env.APP_URL ?? ""
3066
+ this.env.APP_URL ?? "",
3067
+ this.branding
3044
3068
  );
3045
3069
  const recipient = this.getRecipientEmail(data.email, "2fa_enabled");
3046
3070
  logger_default.info("Sending 2FA enabled email", {
@@ -3098,7 +3122,8 @@ var EmailService = class {
3098
3122
  const template = generate2faDisabledEmail(
3099
3123
  data,
3100
3124
  this.env.APP_NAME ?? "Your App",
3101
- this.env.APP_URL ?? ""
3125
+ this.env.APP_URL ?? "",
3126
+ this.branding
3102
3127
  );
3103
3128
  const recipient = this.getRecipientEmail(data.email, "2fa_disabled");
3104
3129
  logger_default.info("Sending 2FA disabled email", {
@@ -3430,7 +3455,7 @@ var signupRoute = (0, import_zod_openapi2.createRoute)({
3430
3455
  });
3431
3456
  var signupHandler = async (c) => {
3432
3457
  const { email, password, name, turnstileToken, oauthToken } = c.req.valid("json");
3433
- const { db: database, schema } = getAuthContext(c);
3458
+ const { db: database, schema, emailBranding } = getAuthContext(c);
3434
3459
  const env = c.env;
3435
3460
  if (oauthToken) {
3436
3461
  const stored = await env.OAUTH_STATES.get(`oauth:signup:${oauthToken}`);
@@ -3530,7 +3555,7 @@ var signupHandler = async (c) => {
3530
3555
  expiresAt,
3531
3556
  createdAt: Date.now()
3532
3557
  });
3533
- const emailService = new EmailService(env);
3558
+ const emailService = new EmailService(env, void 0, emailBranding);
3534
3559
  const verificationUrl = `${env.APP_URL}/verify-email?token=${emailToken}`;
3535
3560
  await emailService.sendVerificationEmail(
3536
3561
  {
@@ -4014,7 +4039,7 @@ var forgotPasswordRoute = (0, import_zod_openapi7.createRoute)({
4014
4039
  });
4015
4040
  var forgotPasswordHandler = async (c) => {
4016
4041
  const { email, turnstileToken } = c.req.valid("json");
4017
- const { db: database, schema } = getAuthContext(c);
4042
+ const { db: database, schema, emailBranding } = getAuthContext(c);
4018
4043
  const env = c.env;
4019
4044
  const turnstileValid = await verifyTurnstileToken(
4020
4045
  turnstileToken,
@@ -4033,7 +4058,7 @@ var forgotPasswordHandler = async (c) => {
4033
4058
  if (!resetResult) {
4034
4059
  return c.json({ success: true });
4035
4060
  }
4036
- const emailService = new EmailService(env);
4061
+ const emailService = new EmailService(env, void 0, emailBranding);
4037
4062
  const resetUrl = `${env.APP_URL}/reset-password?token=${resetResult.token}`;
4038
4063
  await emailService.sendPasswordResetEmail(
4039
4064
  {
@@ -4281,7 +4306,7 @@ var changeEmailHandler = async (c) => {
4281
4306
  const userId = c.get("userId");
4282
4307
  if (!userId) return problems.unauthorized(c, "Not authenticated");
4283
4308
  const { password, newEmail } = c.req.valid("json");
4284
- const { db: database, schema } = getAuthContext(c);
4309
+ const { db: database, schema, emailBranding } = getAuthContext(c);
4285
4310
  const env = c.env;
4286
4311
  const appUrl = env.APP_URL || "http://localhost:5173";
4287
4312
  const result = await requestEmailChange({
@@ -4298,7 +4323,7 @@ var changeEmailHandler = async (c) => {
4298
4323
  }
4299
4324
  return problems.badRequest(c, result.error || "Failed to request email change");
4300
4325
  }
4301
- const emailService = new EmailService(env);
4326
+ const emailService = new EmailService(env, void 0, emailBranding);
4302
4327
  const confirmUrl = `${appUrl}/confirm-email-change?token=${result.confirmToken}`;
4303
4328
  const confirmResult = await emailService.sendEmailChangeConfirmation(
4304
4329
  { newEmail, confirmUrl },
@@ -4603,7 +4628,7 @@ var resendVerificationRoute = (0, import_zod_openapi21.createRoute)({
4603
4628
  });
4604
4629
  var resendVerificationHandler = async (c) => {
4605
4630
  const userId = c.get("userId");
4606
- const { db, schema } = getAuthContext(c);
4631
+ const { db, schema, emailBranding } = getAuthContext(c);
4607
4632
  const env = c.env;
4608
4633
  const [user] = await db.select({
4609
4634
  id: schema.users.id,
@@ -4628,7 +4653,7 @@ var resendVerificationHandler = async (c) => {
4628
4653
  expiresAt,
4629
4654
  createdAt: Date.now()
4630
4655
  });
4631
- const emailService = new EmailService(env);
4656
+ const emailService = new EmailService(env, void 0, emailBranding);
4632
4657
  const verificationUrl = `${env.APP_URL}/verify-email?token=${emailToken}`;
4633
4658
  await emailService.sendVerificationEmail(
4634
4659
  {
@@ -4804,10 +4829,10 @@ var statusRoute = (0, import_zod_openapi24.createRoute)({
4804
4829
  content: { "application/json": { schema: statusResponseSchema } },
4805
4830
  headers: {
4806
4831
  "Cache-Control": {
4807
- description: "Cache for 24 hours",
4832
+ description: "Cache for 60 seconds",
4808
4833
  schema: {
4809
4834
  type: "string",
4810
- example: "private, max-age=86400, stale-while-revalidate=3600"
4835
+ example: "private, max-age=60"
4811
4836
  }
4812
4837
  }
4813
4838
  }
@@ -4824,7 +4849,7 @@ var statusHandler = async (c) => {
4824
4849
  const { db, schema } = getAuthContext(c);
4825
4850
  const methods = await getUserEnabled2faMethods(db, userId, schema.user2faMethods);
4826
4851
  const backupCodes = await db.select({ id: schema.userBackupCodes.id }).from(schema.userBackupCodes).where((0, import_drizzle_orm20.and)((0, import_drizzle_orm20.eq)(schema.userBackupCodes.userId, userId), (0, import_drizzle_orm20.isNull)(schema.userBackupCodes.usedAt)));
4827
- c.header("Cache-Control", "private, max-age=86400, stale-while-revalidate=3600");
4852
+ c.header("Cache-Control", "private, max-age=60");
4828
4853
  c.header("Vary", "Cookie");
4829
4854
  return c.json({
4830
4855
  enabled: methods.length > 0,
@@ -4916,7 +4941,7 @@ var totpVerifyHandler = async (c) => {
4916
4941
  const userId = c.get("userId");
4917
4942
  if (!userId) return problems.unauthorized(c);
4918
4943
  const { code } = c.req.valid("json");
4919
- const { db, schema, env } = getAuthContext(c);
4944
+ const { db, schema, env, emailBranding } = getAuthContext(c);
4920
4945
  const secret = await env.OAUTH_STATES.get(`totp_setup:${userId}`);
4921
4946
  if (!secret) {
4922
4947
  return problems.badRequest(c, "Setup expired. Please start again.");
@@ -4960,7 +4985,7 @@ var totpVerifyHandler = async (c) => {
4960
4985
  backupCodes = codes.map(formatBackupCode);
4961
4986
  }
4962
4987
  if (user) {
4963
- const emailService = new EmailService(env);
4988
+ const emailService = new EmailService(env, void 0, emailBranding);
4964
4989
  const firstName = user.name?.split(" ")[0] || null;
4965
4990
  await emailService.send2faEnabledEmail(
4966
4991
  { email: user.email, firstName, method: "totp" },
@@ -5008,7 +5033,7 @@ var totpDisableHandler = async (c) => {
5008
5033
  return problems.unauthorized(c);
5009
5034
  }
5010
5035
  const { code, method } = c.req.valid("json");
5011
- const { db, schema, env } = getAuthContext(c);
5036
+ const { db, schema, env, emailBranding } = getAuthContext(c);
5012
5037
  const verification = await verifyAny2faCode(db, env, userId, code, method, {
5013
5038
  user2faMethods: schema.user2faMethods,
5014
5039
  userBackupCodes: schema.userBackupCodes
@@ -5025,7 +5050,7 @@ var totpDisableHandler = async (c) => {
5025
5050
  await invalidateAllUserSessions(db, { sessions: schema.sessions, users: schema.users }, userId);
5026
5051
  const [user] = await db.select({ email: schema.users.email, name: schema.users.name }).from(schema.users).where((0, import_drizzle_orm23.eq)(schema.users.id, userId)).limit(1);
5027
5052
  if (user) {
5028
- const emailService = new EmailService(env);
5053
+ const emailService = new EmailService(env, void 0, emailBranding);
5029
5054
  const firstName = user.name?.split(" ")[0] || null;
5030
5055
  await emailService.send2faDisabledEmail({ email: user.email, firstName }, db, schema.users);
5031
5056
  }
@@ -5071,7 +5096,7 @@ var emailSetupRoute = (0, import_zod_openapi28.createRoute)({
5071
5096
  var emailSetupHandler = async (c) => {
5072
5097
  const userId = c.get("userId");
5073
5098
  if (!userId) return problems.unauthorized(c);
5074
- const { db, schema, env } = getAuthContext(c);
5099
+ const { db, schema, env, emailBranding } = getAuthContext(c);
5075
5100
  const existing = await db.select({ id: schema.user2faMethods.id }).from(schema.user2faMethods).where((0, import_drizzle_orm24.and)((0, import_drizzle_orm24.eq)(schema.user2faMethods.userId, userId), (0, import_drizzle_orm24.eq)(schema.user2faMethods.method, "email"))).limit(1);
5076
5101
  if (existing.length > 0) {
5077
5102
  return problems.badRequest(c, "Email 2FA already enabled");
@@ -5081,7 +5106,7 @@ var emailSetupHandler = async (c) => {
5081
5106
  const code = generateEmailOtp();
5082
5107
  const codeHash = await hashToken(code);
5083
5108
  await env.OAUTH_STATES.put(`email_2fa_setup:${userId}`, codeHash, { expirationTtl: 300 });
5084
- const emailService = new EmailService(env);
5109
+ const emailService = new EmailService(env, void 0, emailBranding);
5085
5110
  const firstName = user.name?.split(" ")[0] || null;
5086
5111
  await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db, schema.users);
5087
5112
  logSecurityEvent("2fa_email_setup_initiated", "low", { userId });
@@ -5122,7 +5147,7 @@ var emailVerifyHandler = async (c) => {
5122
5147
  const userId = c.get("userId");
5123
5148
  if (!userId) return problems.unauthorized(c);
5124
5149
  const { code } = c.req.valid("json");
5125
- const { db, schema, env } = getAuthContext(c);
5150
+ const { db, schema, env, emailBranding } = getAuthContext(c);
5126
5151
  const storedHash = await env.OAUTH_STATES.get(`email_2fa_setup:${userId}`);
5127
5152
  if (!storedHash) {
5128
5153
  return problems.badRequest(c, "Code expired. Please request a new one.");
@@ -5160,7 +5185,7 @@ var emailVerifyHandler = async (c) => {
5160
5185
  backupCodes = codes.map(formatBackupCode);
5161
5186
  }
5162
5187
  if (user) {
5163
- const emailService = new EmailService(env);
5188
+ const emailService = new EmailService(env, void 0, emailBranding);
5164
5189
  const firstName = user.name?.split(" ")[0] || null;
5165
5190
  await emailService.send2faEnabledEmail(
5166
5191
  { email: user.email, firstName, method: "email" },
@@ -5203,7 +5228,7 @@ var emailSendCodeRoute = (0, import_zod_openapi30.createRoute)({
5203
5228
  var emailSendCodeHandler = async (c) => {
5204
5229
  const userId = c.get("userId");
5205
5230
  if (!userId) return problems.unauthorized(c);
5206
- const { db, schema, env } = getAuthContext(c);
5231
+ const { db, schema, env, emailBranding } = getAuthContext(c);
5207
5232
  const [emailMethod] = await db.select().from(schema.user2faMethods).where((0, import_drizzle_orm26.and)((0, import_drizzle_orm26.eq)(schema.user2faMethods.userId, userId), (0, import_drizzle_orm26.eq)(schema.user2faMethods.method, "email"))).limit(1);
5208
5233
  if (!emailMethod) {
5209
5234
  return problems.badRequest(c, "Email 2FA not enabled");
@@ -5215,7 +5240,7 @@ var emailSendCodeHandler = async (c) => {
5215
5240
  await env.OAUTH_STATES.put(`email_2fa_challenge:${userId}`, codeHash, {
5216
5241
  expirationTtl: 300
5217
5242
  });
5218
- const emailService = new EmailService(env);
5243
+ const emailService = new EmailService(env, void 0, emailBranding);
5219
5244
  const firstName = user.name?.split(" ")[0] || null;
5220
5245
  const emailResult = await emailService.send2faCodeEmail(
5221
5246
  { email: user.email, firstName, code },
@@ -5268,7 +5293,7 @@ var emailDisableHandler = async (c) => {
5268
5293
  return problems.unauthorized(c);
5269
5294
  }
5270
5295
  const { code, method } = c.req.valid("json");
5271
- const { db, schema, env } = getAuthContext(c);
5296
+ const { db, schema, env, emailBranding } = getAuthContext(c);
5272
5297
  const verification = await verifyAny2faCode(db, env, userId, code, method, {
5273
5298
  user2faMethods: schema.user2faMethods,
5274
5299
  userBackupCodes: schema.userBackupCodes
@@ -5285,7 +5310,7 @@ var emailDisableHandler = async (c) => {
5285
5310
  await invalidateAllUserSessions(db, { sessions: schema.sessions, users: schema.users }, userId);
5286
5311
  const [user] = await db.select({ email: schema.users.email, name: schema.users.name }).from(schema.users).where((0, import_drizzle_orm27.eq)(schema.users.id, userId)).limit(1);
5287
5312
  if (user) {
5288
- const emailService = new EmailService(env);
5313
+ const emailService = new EmailService(env, void 0, emailBranding);
5289
5314
  const firstName = user.name?.split(" ")[0] || null;
5290
5315
  await emailService.send2faDisabledEmail({ email: user.email, firstName }, db, schema.users);
5291
5316
  }
@@ -5315,10 +5340,10 @@ var trustedDevicesGetRoute = (0, import_zod_openapi32.createRoute)({
5315
5340
  content: { "application/json": { schema: trustedDevicesResponseSchema } },
5316
5341
  headers: {
5317
5342
  "Cache-Control": {
5318
- description: "Cache for 24 hours",
5343
+ description: "Cache for 60 seconds",
5319
5344
  schema: {
5320
5345
  type: "string",
5321
- example: "private, max-age=86400, stale-while-revalidate=3600"
5346
+ example: "private, max-age=60"
5322
5347
  }
5323
5348
  }
5324
5349
  }
@@ -5342,7 +5367,7 @@ var trustedDevicesGetHandler = async (c) => {
5342
5367
  lastUsedAt: schema.userTrustedDevices.lastUsedAt,
5343
5368
  createdAt: schema.userTrustedDevices.createdAt
5344
5369
  }).from(schema.userTrustedDevices).where((0, import_drizzle_orm28.and)((0, import_drizzle_orm28.eq)(schema.userTrustedDevices.userId, userId), (0, import_drizzle_orm28.gt)(schema.userTrustedDevices.expiresAt, now)));
5345
- c.header("Cache-Control", "private, max-age=86400, stale-while-revalidate=3600");
5370
+ c.header("Cache-Control", "private, max-age=60");
5346
5371
  c.header("Vary", "Cookie");
5347
5372
  return c.json({ devices });
5348
5373
  };
@@ -5582,7 +5607,7 @@ var challengeResendRoute = (0, import_zod_openapi35.createRoute)({
5582
5607
  }
5583
5608
  });
5584
5609
  var challengeResendHandler = async (c) => {
5585
- const { db, schema } = getAuthContext(c);
5610
+ const { db, schema, emailBranding } = getAuthContext(c);
5586
5611
  const cookieName = getChallengeCookieName(c.env);
5587
5612
  const challengeToken = (0, import_cookie4.getCookie)(c, cookieName);
5588
5613
  if (!challengeToken) {
@@ -5606,7 +5631,7 @@ var challengeResendHandler = async (c) => {
5606
5631
  await c.env.OAUTH_STATES.put(`email_2fa_challenge:${payload.userId}`, codeHash, {
5607
5632
  expirationTtl: 300
5608
5633
  });
5609
- const emailService = new EmailService(c.env);
5634
+ const emailService = new EmailService(c.env, void 0, emailBranding);
5610
5635
  const firstName = user.name?.split(" ")[0] || null;
5611
5636
  await emailService.send2faCodeEmail({ email: user.email, firstName, code }, db, schema.users);
5612
5637
  logger_default.info("Email 2FA code resent", { userId: payload.userId });