@syncello/auth 4.0.0 → 4.2.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/README.md +47 -0
- package/dist/index.cjs +237 -290
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -1
- package/dist/index.d.ts +63 -1
- package/dist/index.js +236 -290
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -121,6 +121,7 @@ __export(src_exports, {
|
|
|
121
121
|
rateLimit: () => rateLimit,
|
|
122
122
|
recentPasswordlessFailures: () => recentPasswordlessFailures,
|
|
123
123
|
refreshSession: () => refreshSession,
|
|
124
|
+
renderEmail: () => renderEmail,
|
|
124
125
|
requestEmailChange: () => requestEmailChange,
|
|
125
126
|
requireAuth: () => requireAuth,
|
|
126
127
|
requireVerifiedEmail: () => requireVerifiedEmail,
|
|
@@ -680,6 +681,9 @@ var AUTH_DEFAULTS = {
|
|
|
680
681
|
LOCKOUT_DURATION_MINUTES: 30,
|
|
681
682
|
LOCKOUT_MAX_ATTEMPTS: 5,
|
|
682
683
|
PASSWORD_RESET_TTL_MINUTES: 15,
|
|
684
|
+
EMAIL_VERIFICATION_TTL_HOURS: 24,
|
|
685
|
+
EMAIL_CHANGE_TTL_HOURS: 24,
|
|
686
|
+
TWO_FACTOR_EMAIL_CODE_TTL_MINUTES: 5,
|
|
683
687
|
TWO_FACTOR_CHALLENGE_TTL_SECONDS: 300,
|
|
684
688
|
// 5 minutes
|
|
685
689
|
APP_NAME: "App"
|
|
@@ -1169,7 +1173,7 @@ async function verifyTurnstileToken(token, secretKey, remoteIp, environment) {
|
|
|
1169
1173
|
|
|
1170
1174
|
// src/core/email-change.ts
|
|
1171
1175
|
var import_drizzle_orm3 = require("drizzle-orm");
|
|
1172
|
-
var TOKEN_EXPIRY_MS =
|
|
1176
|
+
var TOKEN_EXPIRY_MS = AUTH_DEFAULTS.EMAIL_CHANGE_TTL_HOURS * 60 * 60 * 1e3;
|
|
1173
1177
|
async function requestEmailChange(params) {
|
|
1174
1178
|
const { userId, password, newEmail, pepper, db, tables: tables2 } = params;
|
|
1175
1179
|
const { users, emailChangeTokens } = tables2;
|
|
@@ -1414,37 +1418,140 @@ function escapeHtml(text) {
|
|
|
1414
1418
|
}
|
|
1415
1419
|
|
|
1416
1420
|
// src/core/email-template.ts
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1421
|
+
var FONT = "'Montserrat',Arial,sans-serif";
|
|
1422
|
+
var PAGE = "#f4f5f7";
|
|
1423
|
+
var BODY_TEXT = "#404040";
|
|
1424
|
+
var MUTED = "#737373";
|
|
1425
|
+
var RULE = "#e5e7eb";
|
|
1426
|
+
var TABLE = 'width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;"';
|
|
1427
|
+
function logoDimensions(width, height) {
|
|
1428
|
+
if (width === void 0 && height === void 0) return null;
|
|
1429
|
+
if (width === void 0 || height === void 0) {
|
|
1430
|
+
throw new Error("logoWidth and logoHeight must be given together");
|
|
1431
|
+
}
|
|
1432
|
+
return { width: pixels("logoWidth", width), height: pixels("logoHeight", height) };
|
|
1433
|
+
}
|
|
1434
|
+
function pixels(name, value) {
|
|
1435
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
1436
|
+
throw new Error(`${name} must be a positive integer`);
|
|
1437
|
+
}
|
|
1438
|
+
return escapeHtml(String(value));
|
|
1439
|
+
}
|
|
1440
|
+
function brandColours(branding) {
|
|
1441
|
+
return {
|
|
1442
|
+
buttonBgColor: escapeHtml(branding.buttonBgColor ?? "#00fe9a"),
|
|
1443
|
+
buttonTextColor: escapeHtml(branding.buttonTextColor ?? "#000004"),
|
|
1444
|
+
buttonFontWeight: escapeHtml(String(branding.buttonFontWeight ?? 500)),
|
|
1445
|
+
buttonRadius: escapeHtml(branding.buttonRadius ?? "6px"),
|
|
1446
|
+
footerColor: escapeHtml(branding.footerColor ?? "#1e1b4b"),
|
|
1447
|
+
headingColor: escapeHtml(branding.headingColor ?? "#1e1b4b")
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
function logoUrlFor(logoUrl, appUrl) {
|
|
1451
|
+
if (logoUrl) return escapeHtml(sanitizeEmailUrl(logoUrl, appUrl));
|
|
1452
|
+
const base = appUrl ? sanitizeEmailUrl(appUrl, appUrl) : "";
|
|
1453
|
+
return escapeHtml(`${base.replace(/\/$/, "")}/email-header-image.png`);
|
|
1454
|
+
}
|
|
1455
|
+
function marketingUrlFor(marketingUrl) {
|
|
1456
|
+
return marketingUrl ? escapeHtml(sanitizeEmailUrl(marketingUrl, marketingUrl).replace(/\/$/, "")) : "";
|
|
1457
|
+
}
|
|
1458
|
+
var cardRow = (padding, content) => `
|
|
1459
|
+
<table ${TABLE}>
|
|
1460
|
+
<tr>
|
|
1461
|
+
<td class="pad" style="${padding}">
|
|
1462
|
+
${content}
|
|
1463
|
+
</td>
|
|
1464
|
+
</tr>
|
|
1465
|
+
</table>`;
|
|
1466
|
+
function logoBlock({ logoUrl, logoSize, appName }) {
|
|
1467
|
+
const img = logoSize ? `<img src="${logoUrl}" width="${logoSize.width}" height="${logoSize.height}" style="width: ${logoSize.width}px; max-width: 100%; height: auto; display: block; border: 0;" alt="${appName}" title="${appName}">` : `<div class="fullWidth" style="max-width: 50%;">
|
|
1468
|
+
<img src="${logoUrl}" style="display: block; height: auto; border: 0; width: 100%;" alt="${appName}" title="${appName}" height="auto">
|
|
1469
|
+
</div>`;
|
|
1470
|
+
return `
|
|
1471
|
+
<!-- Header Image -->
|
|
1472
|
+
<table class="image_block" ${TABLE}>
|
|
1473
|
+
<tr>
|
|
1474
|
+
<td class="pad" style="padding-top:40px;padding-left:40px;padding-right:40px;padding-bottom:8px;">
|
|
1475
|
+
<div class="alignment" align="center">
|
|
1476
|
+
${img}
|
|
1477
|
+
</div>
|
|
1478
|
+
</td>
|
|
1479
|
+
</tr>
|
|
1480
|
+
</table>`;
|
|
1481
|
+
}
|
|
1482
|
+
function buttonBlock({ label, url }, brand) {
|
|
1483
|
+
const font = `font-family: ${FONT}; font-size: 16px; font-weight: ${brand.buttonFontWeight};`;
|
|
1484
|
+
return `
|
|
1485
|
+
<!-- Button -->${cardRow(
|
|
1486
|
+
"padding-top:8px;padding-bottom:32px;padding-left:40px;padding-right:40px;text-align:center;",
|
|
1487
|
+
`<div class="alignment" align="center">
|
|
1488
|
+
<!--[if mso]>
|
|
1489
|
+
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${url}" style="height:48px;width:auto;v-text-anchor:middle;" arcsize="12%" fillcolor="${brand.buttonBgColor}">
|
|
1490
|
+
<v:stroke dashstyle="Solid" weight="0px" color="${brand.buttonBgColor}"/>
|
|
1491
|
+
<w:anchorlock/>
|
|
1492
|
+
<v:textbox inset="0px,0px,0px,0px">
|
|
1493
|
+
<center dir="false" style="color:${brand.buttonTextColor};${font}">
|
|
1494
|
+
<![endif]-->
|
|
1495
|
+
<a href="${url}" style="background-color: ${brand.buttonBgColor}; border: 0px solid transparent; border-radius: ${brand.buttonRadius}; color: ${brand.buttonTextColor}; display: inline-block; ${font} line-height: 24px; mso-border-alt: none; padding: 12px 28px; text-align: center; text-decoration: none; word-break: keep-all;">${label}</a>
|
|
1496
|
+
<!--[if mso]>
|
|
1497
|
+
</center>
|
|
1498
|
+
</v:textbox>
|
|
1499
|
+
</v:roundrect>
|
|
1500
|
+
<![endif]-->
|
|
1501
|
+
</div>`
|
|
1502
|
+
)}
|
|
1503
|
+
|
|
1504
|
+
<!-- Copy Link -->${cardRow(
|
|
1505
|
+
"padding-bottom:24px;padding-left:40px;padding-right:40px;",
|
|
1506
|
+
`<div style="border-top: 1px solid ${RULE}; padding-top: 16px; color:${MUTED};font-family:${FONT};font-size:12px;line-height:1.5;text-align:left;mso-line-height-alt:18px;">
|
|
1507
|
+
<p style="margin: 0 0 4px 0; word-break: break-word;">Or copy and paste this link into your browser:</p>
|
|
1508
|
+
<a href="${url}" style="color:${MUTED};text-decoration:underline;word-break:break-all;">${url}</a>
|
|
1509
|
+
</div>`
|
|
1510
|
+
)}`;
|
|
1511
|
+
}
|
|
1512
|
+
function renderLayout(parts) {
|
|
1513
|
+
const { brand } = parts;
|
|
1514
|
+
const welcome = parts.welcome ? `
|
|
1515
|
+
<!-- Welcome Message -->${cardRow(
|
|
1516
|
+
"padding-top:24px;padding-left:40px;padding-right:40px;",
|
|
1517
|
+
`<div style="color:${brand.headingColor};font-family:${FONT};font-size:20px;font-weight:600;line-height:1.4;text-align:left;mso-line-height-alt:28px;">
|
|
1518
|
+
<p style="margin: 0; word-break: break-word;">${parts.welcome}</p>
|
|
1519
|
+
</div>`
|
|
1520
|
+
)}` : "";
|
|
1521
|
+
const heading = parts.heading ? `
|
|
1522
|
+
<!-- Heading -->${cardRow(
|
|
1523
|
+
"padding-top:24px;padding-left:40px;padding-right:40px;",
|
|
1524
|
+
`<div style="color:${brand.headingColor};font-family:${FONT};font-size:24px;font-weight:700;line-height:1.3;text-align:left;mso-line-height-alt:31px;">
|
|
1525
|
+
<p style="margin: 0; word-break: break-word;">${parts.heading}</p>
|
|
1526
|
+
</div>`
|
|
1527
|
+
)}` : "";
|
|
1528
|
+
const notice = parts.notice ? `
|
|
1529
|
+
<!-- Notice -->${cardRow(
|
|
1530
|
+
"padding-bottom:24px;padding-left:40px;padding-right:40px;",
|
|
1531
|
+
`<table ${TABLE.replace('style="', `style="background-color: ${PAGE}; border-left: 3px solid ${brand.headingColor}; border-radius: 4px; `)}>
|
|
1532
|
+
<tr>
|
|
1533
|
+
<td style="padding: 14px 16px; color: ${BODY_TEXT}; font-family: ${FONT}; font-size: 13px; line-height: 1.5; text-align: left;">
|
|
1534
|
+
${parts.notice}
|
|
1535
|
+
</td>
|
|
1536
|
+
</tr>
|
|
1537
|
+
</table>`
|
|
1538
|
+
)}` : "";
|
|
1539
|
+
const footerNote = parts.footerNote ? `
|
|
1540
|
+
<!-- Footer Note -->${cardRow(
|
|
1541
|
+
"padding-bottom:32px;padding-left:40px;padding-right:40px;",
|
|
1542
|
+
`<div style="color:${MUTED};font-family:${FONT};font-size:13px;line-height:1.5;text-align:left;mso-line-height-alt:20px;">
|
|
1543
|
+
<p style="margin: 0; word-break: break-word;">${parts.footerNote}</p>
|
|
1544
|
+
</div>`
|
|
1545
|
+
)}` : "";
|
|
1546
|
+
const links = parts.marketingUrl ? `
|
|
1547
|
+
<p style="margin: 0 0 12px 0; word-break: break-word;">
|
|
1548
|
+
<a href="${parts.marketingUrl}/privacy" style="color: #ffffff; text-decoration: underline;">Privacy Policy</a> | <a href="${parts.marketingUrl}/contact" style="color: #ffffff; text-decoration: underline;">Get in Touch</a>
|
|
1549
|
+
</p>` : "";
|
|
1443
1550
|
return `
|
|
1444
1551
|
<!DOCTYPE html>
|
|
1445
1552
|
<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" lang="en">
|
|
1446
1553
|
<head>
|
|
1447
|
-
<title>${
|
|
1554
|
+
<title>${parts.title}</title>
|
|
1448
1555
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
1449
1556
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1450
1557
|
<!--[if mso]>
|
|
@@ -1459,7 +1566,7 @@ function generateEmailTemplate(options) {
|
|
|
1459
1566
|
</xml>
|
|
1460
1567
|
<![endif]-->
|
|
1461
1568
|
<!--[if !mso]><!-->
|
|
1462
|
-
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@
|
|
1569
|
+
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet" type="text/css">
|
|
1463
1570
|
<!--<![endif]-->
|
|
1464
1571
|
<style>
|
|
1465
1572
|
* {
|
|
@@ -1480,13 +1587,6 @@ function generateEmailTemplate(options) {
|
|
|
1480
1587
|
p {
|
|
1481
1588
|
line-height: inherit;
|
|
1482
1589
|
}
|
|
1483
|
-
.desktop_hide,
|
|
1484
|
-
.desktop_hide table {
|
|
1485
|
-
mso-hide: all;
|
|
1486
|
-
display: none;
|
|
1487
|
-
max-height: 0px;
|
|
1488
|
-
overflow: hidden;
|
|
1489
|
-
}
|
|
1490
1590
|
.image_block img+div {
|
|
1491
1591
|
display: none;
|
|
1492
1592
|
}
|
|
@@ -1495,41 +1595,16 @@ function generateEmailTemplate(options) {
|
|
|
1495
1595
|
line-height: 0;
|
|
1496
1596
|
}
|
|
1497
1597
|
@media (max-width:660px) {
|
|
1498
|
-
.
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1501
|
-
.icons-inner {
|
|
1502
|
-
text-align: center;
|
|
1503
|
-
}
|
|
1504
|
-
.icons-inner td {
|
|
1505
|
-
margin: 0 auto;
|
|
1506
|
-
}
|
|
1507
|
-
.mobile_hide {
|
|
1508
|
-
display: none;
|
|
1598
|
+
.page {
|
|
1599
|
+
padding: 16px 8px !important;
|
|
1509
1600
|
}
|
|
1510
1601
|
.row-content {
|
|
1511
1602
|
width: 100% !important;
|
|
1512
1603
|
max-width: 100% !important;
|
|
1513
1604
|
}
|
|
1514
|
-
.stack .column {
|
|
1515
|
-
width: 100%;
|
|
1516
|
-
display: block;
|
|
1517
|
-
}
|
|
1518
|
-
.mobile_hide {
|
|
1519
|
-
min-height: 0;
|
|
1520
|
-
max-height: 0;
|
|
1521
|
-
max-width: 0;
|
|
1522
|
-
overflow: hidden;
|
|
1523
|
-
font-size: 0px;
|
|
1524
|
-
}
|
|
1525
|
-
.desktop_hide,
|
|
1526
|
-
.desktop_hide table {
|
|
1527
|
-
display: table !important;
|
|
1528
|
-
max-height: none !important;
|
|
1529
|
-
}
|
|
1530
1605
|
.pad {
|
|
1531
|
-
padding-left:
|
|
1532
|
-
padding-right:
|
|
1606
|
+
padding-left: 24px !important;
|
|
1607
|
+
padding-right: 24px !important;
|
|
1533
1608
|
}
|
|
1534
1609
|
}
|
|
1535
1610
|
</style>
|
|
@@ -1541,233 +1616,46 @@ function generateEmailTemplate(options) {
|
|
|
1541
1616
|
</style>
|
|
1542
1617
|
<![endif]-->
|
|
1543
1618
|
</head>
|
|
1544
|
-
<body class="body" style="background-color:
|
|
1545
|
-
<table class="nl-container"
|
|
1619
|
+
<body class="body" style="background-color: ${PAGE}; margin: 0; padding: 0; -webkit-text-size-adjust: none; text-size-adjust: none;">
|
|
1620
|
+
<table class="nl-container" ${TABLE.replace('style="', `style="background-color: ${PAGE}; `)}>
|
|
1546
1621
|
<tbody>
|
|
1547
1622
|
<tr>
|
|
1548
|
-
<td>
|
|
1549
|
-
<!--
|
|
1550
|
-
<table class="row
|
|
1623
|
+
<td class="page" style="padding: 32px 16px;">
|
|
1624
|
+
<!-- Card -->
|
|
1625
|
+
<table class="row-content" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #ffffff; border: 1px solid ${RULE}; border-radius: 12px; max-width: 640px; width: 100%; margin: 0 auto;">
|
|
1551
1626
|
<tbody>
|
|
1552
1627
|
<tr>
|
|
1553
|
-
<td>
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
<span style="word-break: break-word;"> </span>
|
|
1568
|
-
</td>
|
|
1569
|
-
</tr>
|
|
1570
|
-
</table>
|
|
1571
|
-
</div>
|
|
1572
|
-
</td>
|
|
1573
|
-
</tr>
|
|
1574
|
-
</table>
|
|
1628
|
+
<td width="100%" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; text-align: left; vertical-align: top;">
|
|
1629
|
+
${logoBlock(parts)}
|
|
1630
|
+
${welcome}
|
|
1631
|
+
${heading}
|
|
1632
|
+
|
|
1633
|
+
<!-- Body Text -->${cardRow(
|
|
1634
|
+
"padding-top:16px;padding-bottom:24px;padding-left:40px;padding-right:40px;",
|
|
1635
|
+
`<div style="color:${BODY_TEXT};font-family:${FONT};font-size:15px;line-height:1.6;text-align:left;mso-line-height-alt:24px;">
|
|
1636
|
+
${parts.body}
|
|
1637
|
+
</div>`
|
|
1638
|
+
)}
|
|
1639
|
+
${parts.button ? buttonBlock(parts.button, brand) : ""}
|
|
1640
|
+
${notice}
|
|
1641
|
+
${footerNote}
|
|
1575
1642
|
|
|
1576
|
-
<!-- Header Image -->
|
|
1577
|
-
<table class="image_block block-2" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
|
|
1578
|
-
<tr>
|
|
1579
|
-
<td class="pad" style="padding-left:40px;padding-right:40px;width:100%;">
|
|
1580
|
-
<div class="alignment" align="center">
|
|
1581
|
-
<div class="fullWidth" style="max-width: 50%;">
|
|
1582
|
-
<img src="${safeLogoUrl}" style="display: block; height: auto; border: 0; width: 100%;" alt="${safeAppName}" title="${safeAppName}" height="auto">
|
|
1583
|
-
</div>
|
|
1584
|
-
</div>
|
|
1585
|
-
</td>
|
|
1586
|
-
</tr>
|
|
1587
|
-
</table>
|
|
1588
|
-
|
|
1589
|
-
${welcomeMessage ? `
|
|
1590
|
-
<!-- Welcome Message -->
|
|
1591
|
-
<table class="paragraph_block" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
|
|
1592
|
-
<tr>
|
|
1593
|
-
<td class="pad" style="padding-top:30px;padding-left:40px;padding-right:40px;">
|
|
1594
|
-
<div style="color:${headingColor};font-family:'Montserrat',Arial,sans-serif;font-size:22px;font-weight:600;line-height:1.4;text-align:center;mso-line-height-alt:31px;">
|
|
1595
|
-
<p style="margin: 0; word-break: break-word;">${welcomeMessage}</p>
|
|
1596
|
-
</div>
|
|
1597
|
-
</td>
|
|
1598
|
-
</tr>
|
|
1599
|
-
</table>
|
|
1600
|
-
` : ""}
|
|
1601
|
-
|
|
1602
|
-
${heading ? `
|
|
1603
|
-
<!-- Heading -->
|
|
1604
|
-
<table class="paragraph_block block-4" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
|
|
1605
|
-
<tr>
|
|
1606
|
-
<td class="pad" style="padding-bottom:10px;padding-left:40px;padding-right:40px;padding-top:10px;">
|
|
1607
|
-
<div style="color:${headingColor};font-family:'Montserrat',Arial,sans-serif;font-size:30px;font-weight:700;line-height:1.2;text-align:center;mso-line-height-alt:36px;">
|
|
1608
|
-
<p style="margin: 0; word-break: break-word;">${heading}</p>
|
|
1609
|
-
</div>
|
|
1610
|
-
</td>
|
|
1611
|
-
</tr>
|
|
1612
|
-
</table>
|
|
1613
|
-
` : ""}
|
|
1614
|
-
|
|
1615
|
-
<!-- Body Text -->
|
|
1616
|
-
<table class="paragraph_block block-5" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
|
|
1617
|
-
<tr>
|
|
1618
|
-
<td class="pad" style="padding-bottom:30px;padding-left:40px;padding-right:40px;padding-top:30px;">
|
|
1619
|
-
<div style="color:#404040;font-family:'Montserrat',Arial,sans-serif;font-size:15px;line-height:1.6;text-align:left;mso-line-height-alt:24px;">
|
|
1620
|
-
<p style="margin: 0; word-break: break-word;">${body}</p>
|
|
1621
|
-
</div>
|
|
1622
|
-
</td>
|
|
1623
|
-
</tr>
|
|
1624
|
-
</table>
|
|
1625
|
-
|
|
1626
|
-
${safeButtonUrl ? `
|
|
1627
|
-
<!-- Button -->
|
|
1628
|
-
<table class="button_block block-6" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
|
|
1629
|
-
<tr>
|
|
1630
|
-
<td class="pad" style="padding-left:10px;padding-right:10px;padding-bottom:10px;text-align:center;">
|
|
1631
|
-
<div class="alignment" align="center">
|
|
1632
|
-
<!--[if mso]>
|
|
1633
|
-
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${safeButtonUrl}" style="height:52px;width:auto;v-text-anchor:middle;" arcsize="12%" fillcolor="${buttonBgColor}">
|
|
1634
|
-
<v:stroke dashstyle="Solid" weight="0px" color="${buttonBgColor}"/>
|
|
1635
|
-
<w:anchorlock/>
|
|
1636
|
-
<v:textbox inset="0px,0px,0px,0px">
|
|
1637
|
-
<center dir="false" style="color:${buttonTextColor};font-family:'Montserrat',Arial,sans-serif;font-size:16px;font-weight:${buttonFontWeight};">
|
|
1638
|
-
<![endif]-->
|
|
1639
|
-
<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;">
|
|
1640
|
-
${buttonText}
|
|
1641
|
-
</a>
|
|
1642
|
-
<!--[if mso]>
|
|
1643
|
-
</center>
|
|
1644
|
-
</v:textbox>
|
|
1645
|
-
</v:roundrect>
|
|
1646
|
-
<![endif]-->
|
|
1647
|
-
</div>
|
|
1648
|
-
</td>
|
|
1649
|
-
</tr>
|
|
1650
|
-
</table>
|
|
1651
|
-
|
|
1652
|
-
<!-- Copy Link -->
|
|
1653
|
-
<table class="paragraph_block" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
|
|
1654
|
-
<tr>
|
|
1655
|
-
<td class="pad" style="padding-bottom:30px;padding-left:40px;padding-right:40px;">
|
|
1656
|
-
<div style="color:#737373;font-family:'Montserrat',Arial,sans-serif;font-size:12px;line-height:1.5;text-align:center;mso-line-height-alt:18px;">
|
|
1657
|
-
<p style="margin: 0 0 5px 0; word-break: break-word;">or copy and paste this link into your browser:</p>
|
|
1658
|
-
<a href="${safeButtonUrl}" style="color:#4a90d9;text-decoration:underline;word-break:break-all;">${safeButtonUrl}</a>
|
|
1659
|
-
</div>
|
|
1660
|
-
</td>
|
|
1661
|
-
</tr>
|
|
1662
|
-
</table>
|
|
1663
|
-
` : ""}
|
|
1664
|
-
|
|
1665
|
-
${securityWarning ? `
|
|
1666
|
-
<!-- Security Warning -->
|
|
1667
|
-
<table class="paragraph_block" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
|
|
1668
|
-
<tr>
|
|
1669
|
-
<td class="pad" style="padding-left:40px;padding-right:40px;padding-bottom:20px;">
|
|
1670
|
-
<div style="background-color: #a1f1ff; border-radius: 8px; padding: 16px; display: flex; align-items: center; justify-content: center;">
|
|
1671
|
-
<p style="margin: 0; color: #01001b; font-family: 'Montserrat', Arial, sans-serif; font-size: 13px; line-height: 1.5; text-align: center;">
|
|
1672
|
-
${securityWarning}
|
|
1673
|
-
</p>
|
|
1674
|
-
</div>
|
|
1675
|
-
</td>
|
|
1676
|
-
</tr>
|
|
1677
|
-
</table>
|
|
1678
|
-
` : ""}
|
|
1679
|
-
|
|
1680
|
-
<!-- Footer Note -->
|
|
1681
|
-
<table class="paragraph_block" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
|
|
1682
|
-
<tr>
|
|
1683
|
-
<td class="pad" style="padding-bottom:30px;padding-left:40px;padding-right:40px;">
|
|
1684
|
-
<div style="color:#737373;font-family:'Montserrat',Arial,sans-serif;font-size:13px;line-height:1.5;text-align:center;mso-line-height-alt:20px;">
|
|
1685
|
-
<p style="margin: 0; word-break: break-word;">${footerNote}</p>
|
|
1686
|
-
</div>
|
|
1687
|
-
</td>
|
|
1688
|
-
</tr>
|
|
1689
|
-
</table>
|
|
1690
|
-
|
|
1691
|
-
</td>
|
|
1692
|
-
</tr>
|
|
1693
|
-
</tbody>
|
|
1694
|
-
</table>
|
|
1695
1643
|
</td>
|
|
1696
1644
|
</tr>
|
|
1697
1645
|
</tbody>
|
|
1698
1646
|
</table>
|
|
1699
1647
|
|
|
1700
|
-
<!--
|
|
1701
|
-
<table class="row
|
|
1648
|
+
<!-- Footer -->
|
|
1649
|
+
<table class="row-content" align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: ${brand.footerColor}; border-radius: 12px; max-width: 640px; width: 100%; margin: 16px auto 0 auto;">
|
|
1702
1650
|
<tbody>
|
|
1703
1651
|
<tr>
|
|
1704
|
-
<td
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
<tr>
|
|
1708
|
-
<td class="column column-1" width="100%" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; font-weight: 400; text-align: left; padding-bottom: 5px; vertical-align: top;">
|
|
1709
|
-
<table class="empty_block block-1" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
|
|
1710
|
-
<tr>
|
|
1711
|
-
<td class="pad">
|
|
1712
|
-
<div></div>
|
|
1713
|
-
</td>
|
|
1714
|
-
</tr>
|
|
1715
|
-
</table>
|
|
1716
|
-
</td>
|
|
1717
|
-
</tr>
|
|
1718
|
-
</tbody>
|
|
1719
|
-
</table>
|
|
1720
|
-
</td>
|
|
1721
|
-
</tr>
|
|
1722
|
-
</tbody>
|
|
1723
|
-
</table>
|
|
1724
|
-
|
|
1725
|
-
<!-- Footer Row -->
|
|
1726
|
-
<table class="row row-3" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
|
|
1727
|
-
<tbody>
|
|
1728
|
-
<tr>
|
|
1729
|
-
<td>
|
|
1730
|
-
<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;">
|
|
1731
|
-
<tbody>
|
|
1732
|
-
<tr>
|
|
1733
|
-
<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;">
|
|
1734
|
-
|
|
1735
|
-
<!-- Policy Links -->
|
|
1736
|
-
<table class="paragraph_block" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
|
|
1737
|
-
<tr>
|
|
1738
|
-
<td class="pad" style="padding-bottom:20px;padding-left:40px;padding-right:40px;padding-top:20px;">
|
|
1739
|
-
<div style="color:#ffffff;font-family:'Montserrat',Arial,sans-serif;font-size:13px;line-height:1.5;text-align:center;mso-line-height-alt:20px;">
|
|
1740
|
-
<p style="margin: 0; word-break: break-word;">
|
|
1741
|
-
<a href="${safeMarketingUrl}/privacy" style="color: #ffffff; text-decoration: underline;">Privacy Policy</a> | <a href="${safeMarketingUrl}/contact" style="color: #ffffff; text-decoration: underline;">Get in Touch</a>
|
|
1742
|
-
</p>
|
|
1743
|
-
</div>
|
|
1744
|
-
</td>
|
|
1745
|
-
</tr>
|
|
1746
|
-
</table>
|
|
1747
|
-
|
|
1748
|
-
<!-- Copyright & Disclaimer -->
|
|
1749
|
-
<table class="paragraph_block block-1" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;">
|
|
1750
|
-
<tr>
|
|
1751
|
-
<td class="pad" style="padding-bottom:15px;padding-left:40px;padding-right:40px;">
|
|
1752
|
-
<div style="color:#a3a3a3;font-family:'Montserrat',Arial,sans-serif;font-size:12px;line-height:1.5;text-align:center;mso-line-height-alt:18px;">
|
|
1753
|
-
<p style="margin: 0 0 12px 0; word-break: break-word;">\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} ${safeAppName}. All rights reserved.</p>
|
|
1754
|
-
<p style="margin: 0; font-size: 11px; line-height: 1.5; word-break: break-word;">
|
|
1755
|
-
Please do not reply to this email as this address does not accept incoming messages. This service email contains essential information relating to your ${safeAppName} account.
|
|
1756
|
-
</p>
|
|
1757
|
-
</div>
|
|
1758
|
-
</td>
|
|
1759
|
-
</tr>
|
|
1760
|
-
</table>
|
|
1761
|
-
|
|
1762
|
-
</td>
|
|
1763
|
-
</tr>
|
|
1764
|
-
</tbody>
|
|
1765
|
-
</table>
|
|
1652
|
+
<td class="pad" style="padding: 24px 40px; color:#a3a3a3; font-family:${FONT}; font-size:12px; line-height:1.5; text-align:center; mso-line-height-alt:18px;">${links}
|
|
1653
|
+
<p style="margin: 0 0 8px 0; word-break: break-word;">\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} ${parts.appName}. All rights reserved.</p>
|
|
1654
|
+
<p style="margin: 0; font-size: 11px; word-break: break-word;">Please do not reply to this email as this address does not accept incoming messages. This service email contains essential information relating to your ${parts.appName} account.</p>
|
|
1766
1655
|
</td>
|
|
1767
1656
|
</tr>
|
|
1768
1657
|
</tbody>
|
|
1769
1658
|
</table>
|
|
1770
|
-
|
|
1771
1659
|
</td>
|
|
1772
1660
|
</tr>
|
|
1773
1661
|
</tbody>
|
|
@@ -1776,6 +1664,55 @@ function generateEmailTemplate(options) {
|
|
|
1776
1664
|
</html>
|
|
1777
1665
|
`;
|
|
1778
1666
|
}
|
|
1667
|
+
function generateEmailTemplate(options) {
|
|
1668
|
+
const { appUrl, buttonUrl } = options;
|
|
1669
|
+
const safeButtonUrl = buttonUrl ? escapeHtml(sanitizeEmailUrl(buttonUrl, appUrl)) : "";
|
|
1670
|
+
return renderLayout({
|
|
1671
|
+
title: options.heading,
|
|
1672
|
+
logoUrl: logoUrlFor(options.logoUrl, appUrl),
|
|
1673
|
+
logoSize: logoDimensions(options.logoWidth, options.logoHeight),
|
|
1674
|
+
appName: escapeHtml(options.appName ?? "App"),
|
|
1675
|
+
welcome: options.welcomeMessage ?? "",
|
|
1676
|
+
heading: options.heading,
|
|
1677
|
+
body: options.body,
|
|
1678
|
+
button: safeButtonUrl ? { label: options.buttonText, url: safeButtonUrl } : null,
|
|
1679
|
+
notice: options.securityWarning?.trim() ?? "",
|
|
1680
|
+
footerNote: options.footerNote,
|
|
1681
|
+
marketingUrl: marketingUrlFor(options.marketingUrl),
|
|
1682
|
+
brand: brandColours(options)
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
function renderEmail(content) {
|
|
1686
|
+
const { appUrl, branding = {} } = content;
|
|
1687
|
+
const link = (url) => escapeHtml(sanitizeEmailUrl(url, appUrl));
|
|
1688
|
+
const text = (value) => escapeHtml(value).replace(/\r?\n/g, "<br>");
|
|
1689
|
+
const brand = brandColours(branding);
|
|
1690
|
+
const paragraphs = content.paragraphs.map(
|
|
1691
|
+
(p) => `<p style="margin: 0 0 16px 0; word-break: break-word;">${text(p)}</p>`
|
|
1692
|
+
);
|
|
1693
|
+
const sections = (content.sections ?? []).map((section) => {
|
|
1694
|
+
const title = section.url ? `<a href="${link(section.url)}" style="color: ${brand.headingColor}; font-weight: 600; text-decoration: none;">${text(section.title)}</a>` : `<span style="color: ${brand.headingColor}; font-weight: 600;">${text(section.title)}</span>`;
|
|
1695
|
+
const rows = section.rows.map(
|
|
1696
|
+
(row) => `<li style="margin: 0 0 4px 0;">${row.url ? `<a href="${link(row.url)}" style="color: ${BODY_TEXT}; text-decoration: underline;">${text(row.text)}</a>` : text(row.text)}</li>`
|
|
1697
|
+
);
|
|
1698
|
+
return `<p style="margin: 8px 0 4px 0; word-break: break-word;">${title}</p>
|
|
1699
|
+
<ul style="margin: 0 0 16px 0; padding-left: 20px;">${rows.join("")}</ul>`;
|
|
1700
|
+
});
|
|
1701
|
+
return renderLayout({
|
|
1702
|
+
title: escapeHtml(content.heading),
|
|
1703
|
+
logoUrl: logoUrlFor(branding.logoUrl, appUrl),
|
|
1704
|
+
logoSize: logoDimensions(branding.logoWidth, branding.logoHeight),
|
|
1705
|
+
appName: escapeHtml(content.appName ?? "App"),
|
|
1706
|
+
welcome: "",
|
|
1707
|
+
heading: escapeHtml(content.heading),
|
|
1708
|
+
body: [...paragraphs, ...sections].join("\n "),
|
|
1709
|
+
button: content.button ? { label: escapeHtml(content.button.label), url: link(content.button.url) } : null,
|
|
1710
|
+
notice: content.notice ? text(content.notice) : "",
|
|
1711
|
+
footerNote: content.footerNote ? text(content.footerNote) : "",
|
|
1712
|
+
marketingUrl: marketingUrlFor(branding.marketingUrl),
|
|
1713
|
+
brand
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1779
1716
|
|
|
1780
1717
|
// src/core/passwordless.ts
|
|
1781
1718
|
var PASSWORDLESS_TTL_MS = 10 * 60 * 1e3;
|
|
@@ -1870,13 +1807,13 @@ async function cleanupExpiredPasswordlessChallenges(db, tables2) {
|
|
|
1870
1807
|
function passwordlessEmail(input) {
|
|
1871
1808
|
const { appName, appUrl, linkUrl, code } = input;
|
|
1872
1809
|
const minutes = Math.max(1, Math.round((input.ttlMs ?? PASSWORDLESS_TTL_MS) / 6e4));
|
|
1873
|
-
const
|
|
1810
|
+
const lifetime2 = `${minutes} ${minutes === 1 ? "minute" : "minutes"}`;
|
|
1874
1811
|
const safeLink = sanitizeEmailUrl(linkUrl, appUrl);
|
|
1875
1812
|
const html = generateEmailTemplate({
|
|
1876
1813
|
heading: `Sign in to ${escapeHtml(appName)}`,
|
|
1877
1814
|
body: `<p>Use this code, or the button below, to sign in:</p>
|
|
1878
1815
|
<p style="font-size: 32px; font-weight: bold; letter-spacing: 4px; text-align: center; margin: 24px 0; font-family: monospace;">${escapeHtml(code)}</p>
|
|
1879
|
-
<p>The code and the link expire in ${
|
|
1816
|
+
<p>The code and the link expire in ${lifetime2}.</p>`,
|
|
1880
1817
|
buttonText: "Sign in",
|
|
1881
1818
|
buttonUrl: safeLink,
|
|
1882
1819
|
footerNote: "If you didn't ask to sign in, you can ignore this email.",
|
|
@@ -1891,7 +1828,7 @@ function passwordlessEmail(input) {
|
|
|
1891
1828
|
Or sign in with this link:
|
|
1892
1829
|
${safeLink}
|
|
1893
1830
|
|
|
1894
|
-
The code and the link expire in ${
|
|
1831
|
+
The code and the link expire in ${lifetime2}.
|
|
1895
1832
|
|
|
1896
1833
|
If you didn't ask to sign in, you can ignore this email.`
|
|
1897
1834
|
};
|
|
@@ -2441,6 +2378,15 @@ var import_drizzle_orm10 = require("drizzle-orm");
|
|
|
2441
2378
|
var import_drizzle_orm7 = require("drizzle-orm");
|
|
2442
2379
|
|
|
2443
2380
|
// src/lib/email/templates.ts
|
|
2381
|
+
function lifetime(minutes) {
|
|
2382
|
+
if (minutes % 60 !== 0) return `${minutes} ${minutes === 1 ? "minute" : "minutes"}`;
|
|
2383
|
+
const hours = minutes / 60;
|
|
2384
|
+
return `${hours} ${hours === 1 ? "hour" : "hours"}`;
|
|
2385
|
+
}
|
|
2386
|
+
var VERIFICATION_LIFETIME = lifetime(AUTH_DEFAULTS.EMAIL_VERIFICATION_TTL_HOURS * 60);
|
|
2387
|
+
var PASSWORD_RESET_LIFETIME = lifetime(AUTH_DEFAULTS.PASSWORD_RESET_TTL_MINUTES);
|
|
2388
|
+
var EMAIL_CHANGE_LIFETIME = lifetime(AUTH_DEFAULTS.EMAIL_CHANGE_TTL_HOURS * 60);
|
|
2389
|
+
var TWO_FACTOR_CODE_LIFETIME = lifetime(AUTH_DEFAULTS.TWO_FACTOR_EMAIL_CODE_TTL_MINUTES);
|
|
2444
2390
|
function extractAppUrl(url) {
|
|
2445
2391
|
try {
|
|
2446
2392
|
const parsed = new URL(url);
|
|
@@ -2456,7 +2402,7 @@ function generateVerificationEmail(data, appName = "Your App", configuredAppUrl
|
|
|
2456
2402
|
const safeFirstName = firstName ? escapeHtml(firstName) : null;
|
|
2457
2403
|
const html = generateEmailTemplate({
|
|
2458
2404
|
heading: "",
|
|
2459
|
-
body: `Please verify that your email address is <strong>${escapeHtml(email)}</strong>, and that you entered it when signing up for ${escapeHtml(appName)}.`,
|
|
2405
|
+
body: `Please verify that your email address is <strong>${escapeHtml(email)}</strong>, and that you entered it when signing up for ${escapeHtml(appName)}. This link expires in ${VERIFICATION_LIFETIME}.`,
|
|
2460
2406
|
buttonText: "Verify Email",
|
|
2461
2407
|
buttonUrl: safeUrl,
|
|
2462
2408
|
welcomeMessage: safeFirstName ? `Hi ${safeFirstName}, Welcome to ${escapeHtml(appName)}!` : void 0,
|
|
@@ -2475,7 +2421,7 @@ Thank you for signing up for ${appName}! Please verify your email address by cli
|
|
|
2475
2421
|
|
|
2476
2422
|
${safeUrl}
|
|
2477
2423
|
|
|
2478
|
-
This link expires in
|
|
2424
|
+
This link expires in ${VERIFICATION_LIFETIME}.
|
|
2479
2425
|
|
|
2480
2426
|
If you didn't create an account with ${appName}, you can safely ignore this email.`,
|
|
2481
2427
|
tags: {
|
|
@@ -2493,7 +2439,7 @@ function generatePasswordResetEmail(data, appName = "Your App", configuredAppUrl
|
|
|
2493
2439
|
buttonText: "Reset Password",
|
|
2494
2440
|
buttonUrl: safeUrl,
|
|
2495
2441
|
securityWarning: `
|
|
2496
|
-
<strong>Link expires in
|
|
2442
|
+
<strong>Link expires in ${PASSWORD_RESET_LIFETIME}.</strong><br>
|
|
2497
2443
|
For security, this link can only be used once.
|
|
2498
2444
|
`,
|
|
2499
2445
|
footerNote: "If you didn't request this change, no action is needed. Your password will remain unchanged.",
|
|
@@ -2511,7 +2457,7 @@ We received a request to reset your password. Click the link below to create a n
|
|
|
2511
2457
|
|
|
2512
2458
|
${safeUrl}
|
|
2513
2459
|
|
|
2514
|
-
This link
|
|
2460
|
+
This link expires in ${PASSWORD_RESET_LIFETIME}.
|
|
2515
2461
|
|
|
2516
2462
|
If you didn't request a password reset, you can safely ignore this email.`,
|
|
2517
2463
|
tags: {
|
|
@@ -2529,7 +2475,7 @@ function generateEmailChangeConfirmation(data, appName = "Your App", configuredA
|
|
|
2529
2475
|
buttonText: "Confirm Email Change",
|
|
2530
2476
|
buttonUrl: safeUrl,
|
|
2531
2477
|
securityWarning: `
|
|
2532
|
-
<strong>This link expires in
|
|
2478
|
+
<strong>This link expires in ${EMAIL_CHANGE_LIFETIME}.</strong><br>
|
|
2533
2479
|
For security, this link can only be used once.
|
|
2534
2480
|
`,
|
|
2535
2481
|
footerNote: "If you didn't request this change, you can safely ignore this email.",
|
|
@@ -2549,7 +2495,7 @@ Click the link below to confirm this change:
|
|
|
2549
2495
|
|
|
2550
2496
|
${safeUrl}
|
|
2551
2497
|
|
|
2552
|
-
This link expires in
|
|
2498
|
+
This link expires in ${EMAIL_CHANGE_LIFETIME}.
|
|
2553
2499
|
|
|
2554
2500
|
If you didn't request this change, you can safely ignore this email.`,
|
|
2555
2501
|
tags: {
|
|
@@ -2603,7 +2549,7 @@ function generate2faCodeEmail(data, appName = "Your App", appUrl = "", branding)
|
|
|
2603
2549
|
<p>Hi ${safeFirstName},</p>
|
|
2604
2550
|
<p>Your verification code is:</p>
|
|
2605
2551
|
<p style="font-size: 32px; font-weight: bold; letter-spacing: 4px; text-align: center; margin: 24px 0; font-family: monospace;">${escapeHtml(code)}</p>
|
|
2606
|
-
<p>This code expires in
|
|
2552
|
+
<p>This code expires in ${TWO_FACTOR_CODE_LIFETIME}.</p>
|
|
2607
2553
|
<p>If you didn't request this code, you can safely ignore this email.</p>
|
|
2608
2554
|
`,
|
|
2609
2555
|
buttonText: "",
|
|
@@ -2623,7 +2569,7 @@ Your verification code is:
|
|
|
2623
2569
|
|
|
2624
2570
|
${code}
|
|
2625
2571
|
|
|
2626
|
-
This code expires in
|
|
2572
|
+
This code expires in ${TWO_FACTOR_CODE_LIFETIME}.
|
|
2627
2573
|
|
|
2628
2574
|
If you didn't request this code, you can safely ignore this email.
|
|
2629
2575
|
|
|
@@ -3702,7 +3648,7 @@ var signupHandler = async (c) => {
|
|
|
3702
3648
|
if (!invitedVerified) {
|
|
3703
3649
|
const emailToken = generateSecureToken(32);
|
|
3704
3650
|
const tokenHash = await hashToken(emailToken);
|
|
3705
|
-
const expiresAt = Date.now() +
|
|
3651
|
+
const expiresAt = Date.now() + AUTH_DEFAULTS.EMAIL_VERIFICATION_TTL_HOURS * 60 * 60 * 1e3;
|
|
3706
3652
|
await database.insert(schema.emailVerificationTokens).values({
|
|
3707
3653
|
tokenHash,
|
|
3708
3654
|
userId: newUser.id,
|
|
@@ -3781,7 +3727,7 @@ function generateEmailOtp() {
|
|
|
3781
3727
|
const num = (bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]) >>> 0;
|
|
3782
3728
|
return String(num % 1e6).padStart(6, "0");
|
|
3783
3729
|
}
|
|
3784
|
-
var EMAIL_CODE_TTL_SECONDS =
|
|
3730
|
+
var EMAIL_CODE_TTL_SECONDS = AUTH_DEFAULTS.TWO_FACTOR_EMAIL_CODE_TTL_MINUTES * 60;
|
|
3785
3731
|
async function sendEmailChallengeCode(env, database, usersTable, user, emailBranding) {
|
|
3786
3732
|
const code = generateEmailOtp();
|
|
3787
3733
|
await env.OAUTH_STATES.put(`email_2fa_challenge:${user.id}`, await hashToken(code), {
|
|
@@ -4839,7 +4785,7 @@ var resendVerificationHandler = async (c) => {
|
|
|
4839
4785
|
await db.delete(schema.emailVerificationTokens).where((0, import_drizzle_orm20.eq)(schema.emailVerificationTokens.userId, userId));
|
|
4840
4786
|
const emailToken = generateSecureToken(32);
|
|
4841
4787
|
const tokenHash = await hashToken(emailToken);
|
|
4842
|
-
const expiresAt = Date.now() +
|
|
4788
|
+
const expiresAt = Date.now() + AUTH_DEFAULTS.EMAIL_VERIFICATION_TTL_HOURS * 60 * 60 * 1e3;
|
|
4843
4789
|
await db.insert(schema.emailVerificationTokens).values({
|
|
4844
4790
|
tokenHash,
|
|
4845
4791
|
userId: user.id,
|
|
@@ -6448,6 +6394,7 @@ function verifyWebhookSignature(payload, headers, secret) {
|
|
|
6448
6394
|
rateLimit,
|
|
6449
6395
|
recentPasswordlessFailures,
|
|
6450
6396
|
refreshSession,
|
|
6397
|
+
renderEmail,
|
|
6451
6398
|
requestEmailChange,
|
|
6452
6399
|
requireAuth,
|
|
6453
6400
|
requireVerifiedEmail,
|