@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 +96 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -81
- package/dist/index.d.ts +100 -81
- package/dist/index.js +96 -71
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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="${
|
|
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:
|
|
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:
|
|
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
|
|
4832
|
+
description: "Cache for 60 seconds",
|
|
4808
4833
|
schema: {
|
|
4809
4834
|
type: "string",
|
|
4810
|
-
example: "private, max-age=
|
|
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=
|
|
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
|
|
5343
|
+
description: "Cache for 60 seconds",
|
|
5319
5344
|
schema: {
|
|
5320
5345
|
type: "string",
|
|
5321
|
-
example: "private, max-age=
|
|
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=
|
|
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 });
|