@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.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="${
|
|
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:
|
|
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:
|
|
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
|
|
4440
|
+
description: "Cache for 60 seconds",
|
|
4416
4441
|
schema: {
|
|
4417
4442
|
type: "string",
|
|
4418
|
-
example: "private, max-age=
|
|
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=
|
|
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
|
|
4951
|
+
description: "Cache for 60 seconds",
|
|
4927
4952
|
schema: {
|
|
4928
4953
|
type: "string",
|
|
4929
|
-
example: "private, max-age=
|
|
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=
|
|
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 });
|