@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.js
CHANGED
|
@@ -260,6 +260,9 @@ var AUTH_DEFAULTS = {
|
|
|
260
260
|
LOCKOUT_DURATION_MINUTES: 30,
|
|
261
261
|
LOCKOUT_MAX_ATTEMPTS: 5,
|
|
262
262
|
PASSWORD_RESET_TTL_MINUTES: 15,
|
|
263
|
+
EMAIL_VERIFICATION_TTL_HOURS: 24,
|
|
264
|
+
EMAIL_CHANGE_TTL_HOURS: 24,
|
|
265
|
+
TWO_FACTOR_EMAIL_CODE_TTL_MINUTES: 5,
|
|
263
266
|
TWO_FACTOR_CHALLENGE_TTL_SECONDS: 300,
|
|
264
267
|
// 5 minutes
|
|
265
268
|
APP_NAME: "App"
|
|
@@ -749,7 +752,7 @@ async function verifyTurnstileToken(token, secretKey, remoteIp, environment) {
|
|
|
749
752
|
|
|
750
753
|
// src/core/email-change.ts
|
|
751
754
|
import { eq as eq3 } from "drizzle-orm";
|
|
752
|
-
var TOKEN_EXPIRY_MS =
|
|
755
|
+
var TOKEN_EXPIRY_MS = AUTH_DEFAULTS.EMAIL_CHANGE_TTL_HOURS * 60 * 60 * 1e3;
|
|
753
756
|
async function requestEmailChange(params) {
|
|
754
757
|
const { userId, password, newEmail, pepper, db, tables: tables2 } = params;
|
|
755
758
|
const { users, emailChangeTokens } = tables2;
|
|
@@ -994,37 +997,140 @@ function escapeHtml(text) {
|
|
|
994
997
|
}
|
|
995
998
|
|
|
996
999
|
// src/core/email-template.ts
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1000
|
+
var FONT = "'Montserrat',Arial,sans-serif";
|
|
1001
|
+
var PAGE = "#f4f5f7";
|
|
1002
|
+
var BODY_TEXT = "#404040";
|
|
1003
|
+
var MUTED = "#737373";
|
|
1004
|
+
var RULE = "#e5e7eb";
|
|
1005
|
+
var TABLE = 'width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;"';
|
|
1006
|
+
function logoDimensions(width, height) {
|
|
1007
|
+
if (width === void 0 && height === void 0) return null;
|
|
1008
|
+
if (width === void 0 || height === void 0) {
|
|
1009
|
+
throw new Error("logoWidth and logoHeight must be given together");
|
|
1010
|
+
}
|
|
1011
|
+
return { width: pixels("logoWidth", width), height: pixels("logoHeight", height) };
|
|
1012
|
+
}
|
|
1013
|
+
function pixels(name, value) {
|
|
1014
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
1015
|
+
throw new Error(`${name} must be a positive integer`);
|
|
1016
|
+
}
|
|
1017
|
+
return escapeHtml(String(value));
|
|
1018
|
+
}
|
|
1019
|
+
function brandColours(branding) {
|
|
1020
|
+
return {
|
|
1021
|
+
buttonBgColor: escapeHtml(branding.buttonBgColor ?? "#00fe9a"),
|
|
1022
|
+
buttonTextColor: escapeHtml(branding.buttonTextColor ?? "#000004"),
|
|
1023
|
+
buttonFontWeight: escapeHtml(String(branding.buttonFontWeight ?? 500)),
|
|
1024
|
+
buttonRadius: escapeHtml(branding.buttonRadius ?? "6px"),
|
|
1025
|
+
footerColor: escapeHtml(branding.footerColor ?? "#1e1b4b"),
|
|
1026
|
+
headingColor: escapeHtml(branding.headingColor ?? "#1e1b4b")
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
function logoUrlFor(logoUrl, appUrl) {
|
|
1030
|
+
if (logoUrl) return escapeHtml(sanitizeEmailUrl(logoUrl, appUrl));
|
|
1031
|
+
const base = appUrl ? sanitizeEmailUrl(appUrl, appUrl) : "";
|
|
1032
|
+
return escapeHtml(`${base.replace(/\/$/, "")}/email-header-image.png`);
|
|
1033
|
+
}
|
|
1034
|
+
function marketingUrlFor(marketingUrl) {
|
|
1035
|
+
return marketingUrl ? escapeHtml(sanitizeEmailUrl(marketingUrl, marketingUrl).replace(/\/$/, "")) : "";
|
|
1036
|
+
}
|
|
1037
|
+
var cardRow = (padding, content) => `
|
|
1038
|
+
<table ${TABLE}>
|
|
1039
|
+
<tr>
|
|
1040
|
+
<td class="pad" style="${padding}">
|
|
1041
|
+
${content}
|
|
1042
|
+
</td>
|
|
1043
|
+
</tr>
|
|
1044
|
+
</table>`;
|
|
1045
|
+
function logoBlock({ logoUrl, logoSize, appName }) {
|
|
1046
|
+
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%;">
|
|
1047
|
+
<img src="${logoUrl}" style="display: block; height: auto; border: 0; width: 100%;" alt="${appName}" title="${appName}" height="auto">
|
|
1048
|
+
</div>`;
|
|
1049
|
+
return `
|
|
1050
|
+
<!-- Header Image -->
|
|
1051
|
+
<table class="image_block" ${TABLE}>
|
|
1052
|
+
<tr>
|
|
1053
|
+
<td class="pad" style="padding-top:40px;padding-left:40px;padding-right:40px;padding-bottom:8px;">
|
|
1054
|
+
<div class="alignment" align="center">
|
|
1055
|
+
${img}
|
|
1056
|
+
</div>
|
|
1057
|
+
</td>
|
|
1058
|
+
</tr>
|
|
1059
|
+
</table>`;
|
|
1060
|
+
}
|
|
1061
|
+
function buttonBlock({ label, url }, brand) {
|
|
1062
|
+
const font = `font-family: ${FONT}; font-size: 16px; font-weight: ${brand.buttonFontWeight};`;
|
|
1063
|
+
return `
|
|
1064
|
+
<!-- Button -->${cardRow(
|
|
1065
|
+
"padding-top:8px;padding-bottom:32px;padding-left:40px;padding-right:40px;text-align:center;",
|
|
1066
|
+
`<div class="alignment" align="center">
|
|
1067
|
+
<!--[if mso]>
|
|
1068
|
+
<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}">
|
|
1069
|
+
<v:stroke dashstyle="Solid" weight="0px" color="${brand.buttonBgColor}"/>
|
|
1070
|
+
<w:anchorlock/>
|
|
1071
|
+
<v:textbox inset="0px,0px,0px,0px">
|
|
1072
|
+
<center dir="false" style="color:${brand.buttonTextColor};${font}">
|
|
1073
|
+
<![endif]-->
|
|
1074
|
+
<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>
|
|
1075
|
+
<!--[if mso]>
|
|
1076
|
+
</center>
|
|
1077
|
+
</v:textbox>
|
|
1078
|
+
</v:roundrect>
|
|
1079
|
+
<![endif]-->
|
|
1080
|
+
</div>`
|
|
1081
|
+
)}
|
|
1082
|
+
|
|
1083
|
+
<!-- Copy Link -->${cardRow(
|
|
1084
|
+
"padding-bottom:24px;padding-left:40px;padding-right:40px;",
|
|
1085
|
+
`<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;">
|
|
1086
|
+
<p style="margin: 0 0 4px 0; word-break: break-word;">Or copy and paste this link into your browser:</p>
|
|
1087
|
+
<a href="${url}" style="color:${MUTED};text-decoration:underline;word-break:break-all;">${url}</a>
|
|
1088
|
+
</div>`
|
|
1089
|
+
)}`;
|
|
1090
|
+
}
|
|
1091
|
+
function renderLayout(parts) {
|
|
1092
|
+
const { brand } = parts;
|
|
1093
|
+
const welcome = parts.welcome ? `
|
|
1094
|
+
<!-- Welcome Message -->${cardRow(
|
|
1095
|
+
"padding-top:24px;padding-left:40px;padding-right:40px;",
|
|
1096
|
+
`<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;">
|
|
1097
|
+
<p style="margin: 0; word-break: break-word;">${parts.welcome}</p>
|
|
1098
|
+
</div>`
|
|
1099
|
+
)}` : "";
|
|
1100
|
+
const heading = parts.heading ? `
|
|
1101
|
+
<!-- Heading -->${cardRow(
|
|
1102
|
+
"padding-top:24px;padding-left:40px;padding-right:40px;",
|
|
1103
|
+
`<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;">
|
|
1104
|
+
<p style="margin: 0; word-break: break-word;">${parts.heading}</p>
|
|
1105
|
+
</div>`
|
|
1106
|
+
)}` : "";
|
|
1107
|
+
const notice = parts.notice ? `
|
|
1108
|
+
<!-- Notice -->${cardRow(
|
|
1109
|
+
"padding-bottom:24px;padding-left:40px;padding-right:40px;",
|
|
1110
|
+
`<table ${TABLE.replace('style="', `style="background-color: ${PAGE}; border-left: 3px solid ${brand.headingColor}; border-radius: 4px; `)}>
|
|
1111
|
+
<tr>
|
|
1112
|
+
<td style="padding: 14px 16px; color: ${BODY_TEXT}; font-family: ${FONT}; font-size: 13px; line-height: 1.5; text-align: left;">
|
|
1113
|
+
${parts.notice}
|
|
1114
|
+
</td>
|
|
1115
|
+
</tr>
|
|
1116
|
+
</table>`
|
|
1117
|
+
)}` : "";
|
|
1118
|
+
const footerNote = parts.footerNote ? `
|
|
1119
|
+
<!-- Footer Note -->${cardRow(
|
|
1120
|
+
"padding-bottom:32px;padding-left:40px;padding-right:40px;",
|
|
1121
|
+
`<div style="color:${MUTED};font-family:${FONT};font-size:13px;line-height:1.5;text-align:left;mso-line-height-alt:20px;">
|
|
1122
|
+
<p style="margin: 0; word-break: break-word;">${parts.footerNote}</p>
|
|
1123
|
+
</div>`
|
|
1124
|
+
)}` : "";
|
|
1125
|
+
const links = parts.marketingUrl ? `
|
|
1126
|
+
<p style="margin: 0 0 12px 0; word-break: break-word;">
|
|
1127
|
+
<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>
|
|
1128
|
+
</p>` : "";
|
|
1023
1129
|
return `
|
|
1024
1130
|
<!DOCTYPE html>
|
|
1025
1131
|
<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" lang="en">
|
|
1026
1132
|
<head>
|
|
1027
|
-
<title>${
|
|
1133
|
+
<title>${parts.title}</title>
|
|
1028
1134
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
1029
1135
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1030
1136
|
<!--[if mso]>
|
|
@@ -1039,7 +1145,7 @@ function generateEmailTemplate(options) {
|
|
|
1039
1145
|
</xml>
|
|
1040
1146
|
<![endif]-->
|
|
1041
1147
|
<!--[if !mso]><!-->
|
|
1042
|
-
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@
|
|
1148
|
+
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet" type="text/css">
|
|
1043
1149
|
<!--<![endif]-->
|
|
1044
1150
|
<style>
|
|
1045
1151
|
* {
|
|
@@ -1060,13 +1166,6 @@ function generateEmailTemplate(options) {
|
|
|
1060
1166
|
p {
|
|
1061
1167
|
line-height: inherit;
|
|
1062
1168
|
}
|
|
1063
|
-
.desktop_hide,
|
|
1064
|
-
.desktop_hide table {
|
|
1065
|
-
mso-hide: all;
|
|
1066
|
-
display: none;
|
|
1067
|
-
max-height: 0px;
|
|
1068
|
-
overflow: hidden;
|
|
1069
|
-
}
|
|
1070
1169
|
.image_block img+div {
|
|
1071
1170
|
display: none;
|
|
1072
1171
|
}
|
|
@@ -1075,41 +1174,16 @@ function generateEmailTemplate(options) {
|
|
|
1075
1174
|
line-height: 0;
|
|
1076
1175
|
}
|
|
1077
1176
|
@media (max-width:660px) {
|
|
1078
|
-
.
|
|
1079
|
-
|
|
1080
|
-
}
|
|
1081
|
-
.icons-inner {
|
|
1082
|
-
text-align: center;
|
|
1083
|
-
}
|
|
1084
|
-
.icons-inner td {
|
|
1085
|
-
margin: 0 auto;
|
|
1086
|
-
}
|
|
1087
|
-
.mobile_hide {
|
|
1088
|
-
display: none;
|
|
1177
|
+
.page {
|
|
1178
|
+
padding: 16px 8px !important;
|
|
1089
1179
|
}
|
|
1090
1180
|
.row-content {
|
|
1091
1181
|
width: 100% !important;
|
|
1092
1182
|
max-width: 100% !important;
|
|
1093
1183
|
}
|
|
1094
|
-
.stack .column {
|
|
1095
|
-
width: 100%;
|
|
1096
|
-
display: block;
|
|
1097
|
-
}
|
|
1098
|
-
.mobile_hide {
|
|
1099
|
-
min-height: 0;
|
|
1100
|
-
max-height: 0;
|
|
1101
|
-
max-width: 0;
|
|
1102
|
-
overflow: hidden;
|
|
1103
|
-
font-size: 0px;
|
|
1104
|
-
}
|
|
1105
|
-
.desktop_hide,
|
|
1106
|
-
.desktop_hide table {
|
|
1107
|
-
display: table !important;
|
|
1108
|
-
max-height: none !important;
|
|
1109
|
-
}
|
|
1110
1184
|
.pad {
|
|
1111
|
-
padding-left:
|
|
1112
|
-
padding-right:
|
|
1185
|
+
padding-left: 24px !important;
|
|
1186
|
+
padding-right: 24px !important;
|
|
1113
1187
|
}
|
|
1114
1188
|
}
|
|
1115
1189
|
</style>
|
|
@@ -1121,233 +1195,46 @@ function generateEmailTemplate(options) {
|
|
|
1121
1195
|
</style>
|
|
1122
1196
|
<![endif]-->
|
|
1123
1197
|
</head>
|
|
1124
|
-
<body class="body" style="background-color:
|
|
1125
|
-
<table class="nl-container"
|
|
1198
|
+
<body class="body" style="background-color: ${PAGE}; margin: 0; padding: 0; -webkit-text-size-adjust: none; text-size-adjust: none;">
|
|
1199
|
+
<table class="nl-container" ${TABLE.replace('style="', `style="background-color: ${PAGE}; `)}>
|
|
1126
1200
|
<tbody>
|
|
1127
1201
|
<tr>
|
|
1128
|
-
<td>
|
|
1129
|
-
<!--
|
|
1130
|
-
<table class="row
|
|
1202
|
+
<td class="page" style="padding: 32px 16px;">
|
|
1203
|
+
<!-- Card -->
|
|
1204
|
+
<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;">
|
|
1131
1205
|
<tbody>
|
|
1132
1206
|
<tr>
|
|
1133
|
-
<td>
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
<span style="word-break: break-word;"> </span>
|
|
1148
|
-
</td>
|
|
1149
|
-
</tr>
|
|
1150
|
-
</table>
|
|
1151
|
-
</div>
|
|
1152
|
-
</td>
|
|
1153
|
-
</tr>
|
|
1154
|
-
</table>
|
|
1207
|
+
<td width="100%" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; text-align: left; vertical-align: top;">
|
|
1208
|
+
${logoBlock(parts)}
|
|
1209
|
+
${welcome}
|
|
1210
|
+
${heading}
|
|
1211
|
+
|
|
1212
|
+
<!-- Body Text -->${cardRow(
|
|
1213
|
+
"padding-top:16px;padding-bottom:24px;padding-left:40px;padding-right:40px;",
|
|
1214
|
+
`<div style="color:${BODY_TEXT};font-family:${FONT};font-size:15px;line-height:1.6;text-align:left;mso-line-height-alt:24px;">
|
|
1215
|
+
${parts.body}
|
|
1216
|
+
</div>`
|
|
1217
|
+
)}
|
|
1218
|
+
${parts.button ? buttonBlock(parts.button, brand) : ""}
|
|
1219
|
+
${notice}
|
|
1220
|
+
${footerNote}
|
|
1155
1221
|
|
|
1156
|
-
<!-- Header Image -->
|
|
1157
|
-
<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;">
|
|
1158
|
-
<tr>
|
|
1159
|
-
<td class="pad" style="padding-left:40px;padding-right:40px;width:100%;">
|
|
1160
|
-
<div class="alignment" align="center">
|
|
1161
|
-
<div class="fullWidth" style="max-width: 50%;">
|
|
1162
|
-
<img src="${safeLogoUrl}" style="display: block; height: auto; border: 0; width: 100%;" alt="${safeAppName}" title="${safeAppName}" height="auto">
|
|
1163
|
-
</div>
|
|
1164
|
-
</div>
|
|
1165
|
-
</td>
|
|
1166
|
-
</tr>
|
|
1167
|
-
</table>
|
|
1168
|
-
|
|
1169
|
-
${welcomeMessage ? `
|
|
1170
|
-
<!-- Welcome Message -->
|
|
1171
|
-
<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;">
|
|
1172
|
-
<tr>
|
|
1173
|
-
<td class="pad" style="padding-top:30px;padding-left:40px;padding-right:40px;">
|
|
1174
|
-
<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;">
|
|
1175
|
-
<p style="margin: 0; word-break: break-word;">${welcomeMessage}</p>
|
|
1176
|
-
</div>
|
|
1177
|
-
</td>
|
|
1178
|
-
</tr>
|
|
1179
|
-
</table>
|
|
1180
|
-
` : ""}
|
|
1181
|
-
|
|
1182
|
-
${heading ? `
|
|
1183
|
-
<!-- Heading -->
|
|
1184
|
-
<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;">
|
|
1185
|
-
<tr>
|
|
1186
|
-
<td class="pad" style="padding-bottom:10px;padding-left:40px;padding-right:40px;padding-top:10px;">
|
|
1187
|
-
<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;">
|
|
1188
|
-
<p style="margin: 0; word-break: break-word;">${heading}</p>
|
|
1189
|
-
</div>
|
|
1190
|
-
</td>
|
|
1191
|
-
</tr>
|
|
1192
|
-
</table>
|
|
1193
|
-
` : ""}
|
|
1194
|
-
|
|
1195
|
-
<!-- Body Text -->
|
|
1196
|
-
<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;">
|
|
1197
|
-
<tr>
|
|
1198
|
-
<td class="pad" style="padding-bottom:30px;padding-left:40px;padding-right:40px;padding-top:30px;">
|
|
1199
|
-
<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;">
|
|
1200
|
-
<p style="margin: 0; word-break: break-word;">${body}</p>
|
|
1201
|
-
</div>
|
|
1202
|
-
</td>
|
|
1203
|
-
</tr>
|
|
1204
|
-
</table>
|
|
1205
|
-
|
|
1206
|
-
${safeButtonUrl ? `
|
|
1207
|
-
<!-- Button -->
|
|
1208
|
-
<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;">
|
|
1209
|
-
<tr>
|
|
1210
|
-
<td class="pad" style="padding-left:10px;padding-right:10px;padding-bottom:10px;text-align:center;">
|
|
1211
|
-
<div class="alignment" align="center">
|
|
1212
|
-
<!--[if mso]>
|
|
1213
|
-
<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}">
|
|
1214
|
-
<v:stroke dashstyle="Solid" weight="0px" color="${buttonBgColor}"/>
|
|
1215
|
-
<w:anchorlock/>
|
|
1216
|
-
<v:textbox inset="0px,0px,0px,0px">
|
|
1217
|
-
<center dir="false" style="color:${buttonTextColor};font-family:'Montserrat',Arial,sans-serif;font-size:16px;font-weight:${buttonFontWeight};">
|
|
1218
|
-
<![endif]-->
|
|
1219
|
-
<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;">
|
|
1220
|
-
${buttonText}
|
|
1221
|
-
</a>
|
|
1222
|
-
<!--[if mso]>
|
|
1223
|
-
</center>
|
|
1224
|
-
</v:textbox>
|
|
1225
|
-
</v:roundrect>
|
|
1226
|
-
<![endif]-->
|
|
1227
|
-
</div>
|
|
1228
|
-
</td>
|
|
1229
|
-
</tr>
|
|
1230
|
-
</table>
|
|
1231
|
-
|
|
1232
|
-
<!-- Copy Link -->
|
|
1233
|
-
<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;">
|
|
1234
|
-
<tr>
|
|
1235
|
-
<td class="pad" style="padding-bottom:30px;padding-left:40px;padding-right:40px;">
|
|
1236
|
-
<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;">
|
|
1237
|
-
<p style="margin: 0 0 5px 0; word-break: break-word;">or copy and paste this link into your browser:</p>
|
|
1238
|
-
<a href="${safeButtonUrl}" style="color:#4a90d9;text-decoration:underline;word-break:break-all;">${safeButtonUrl}</a>
|
|
1239
|
-
</div>
|
|
1240
|
-
</td>
|
|
1241
|
-
</tr>
|
|
1242
|
-
</table>
|
|
1243
|
-
` : ""}
|
|
1244
|
-
|
|
1245
|
-
${securityWarning ? `
|
|
1246
|
-
<!-- Security Warning -->
|
|
1247
|
-
<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;">
|
|
1248
|
-
<tr>
|
|
1249
|
-
<td class="pad" style="padding-left:40px;padding-right:40px;padding-bottom:20px;">
|
|
1250
|
-
<div style="background-color: #a1f1ff; border-radius: 8px; padding: 16px; display: flex; align-items: center; justify-content: center;">
|
|
1251
|
-
<p style="margin: 0; color: #01001b; font-family: 'Montserrat', Arial, sans-serif; font-size: 13px; line-height: 1.5; text-align: center;">
|
|
1252
|
-
${securityWarning}
|
|
1253
|
-
</p>
|
|
1254
|
-
</div>
|
|
1255
|
-
</td>
|
|
1256
|
-
</tr>
|
|
1257
|
-
</table>
|
|
1258
|
-
` : ""}
|
|
1259
|
-
|
|
1260
|
-
<!-- Footer Note -->
|
|
1261
|
-
<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;">
|
|
1262
|
-
<tr>
|
|
1263
|
-
<td class="pad" style="padding-bottom:30px;padding-left:40px;padding-right:40px;">
|
|
1264
|
-
<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;">
|
|
1265
|
-
<p style="margin: 0; word-break: break-word;">${footerNote}</p>
|
|
1266
|
-
</div>
|
|
1267
|
-
</td>
|
|
1268
|
-
</tr>
|
|
1269
|
-
</table>
|
|
1270
|
-
|
|
1271
|
-
</td>
|
|
1272
|
-
</tr>
|
|
1273
|
-
</tbody>
|
|
1274
|
-
</table>
|
|
1275
1222
|
</td>
|
|
1276
1223
|
</tr>
|
|
1277
1224
|
</tbody>
|
|
1278
1225
|
</table>
|
|
1279
1226
|
|
|
1280
|
-
<!--
|
|
1281
|
-
<table class="row
|
|
1227
|
+
<!-- Footer -->
|
|
1228
|
+
<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;">
|
|
1282
1229
|
<tbody>
|
|
1283
1230
|
<tr>
|
|
1284
|
-
<td
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
<tr>
|
|
1288
|
-
<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;">
|
|
1289
|
-
<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;">
|
|
1290
|
-
<tr>
|
|
1291
|
-
<td class="pad">
|
|
1292
|
-
<div></div>
|
|
1293
|
-
</td>
|
|
1294
|
-
</tr>
|
|
1295
|
-
</table>
|
|
1296
|
-
</td>
|
|
1297
|
-
</tr>
|
|
1298
|
-
</tbody>
|
|
1299
|
-
</table>
|
|
1300
|
-
</td>
|
|
1301
|
-
</tr>
|
|
1302
|
-
</tbody>
|
|
1303
|
-
</table>
|
|
1304
|
-
|
|
1305
|
-
<!-- Footer Row -->
|
|
1306
|
-
<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;">
|
|
1307
|
-
<tbody>
|
|
1308
|
-
<tr>
|
|
1309
|
-
<td>
|
|
1310
|
-
<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;">
|
|
1311
|
-
<tbody>
|
|
1312
|
-
<tr>
|
|
1313
|
-
<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;">
|
|
1314
|
-
|
|
1315
|
-
<!-- Policy Links -->
|
|
1316
|
-
<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;">
|
|
1317
|
-
<tr>
|
|
1318
|
-
<td class="pad" style="padding-bottom:20px;padding-left:40px;padding-right:40px;padding-top:20px;">
|
|
1319
|
-
<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;">
|
|
1320
|
-
<p style="margin: 0; word-break: break-word;">
|
|
1321
|
-
<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>
|
|
1322
|
-
</p>
|
|
1323
|
-
</div>
|
|
1324
|
-
</td>
|
|
1325
|
-
</tr>
|
|
1326
|
-
</table>
|
|
1327
|
-
|
|
1328
|
-
<!-- Copyright & Disclaimer -->
|
|
1329
|
-
<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;">
|
|
1330
|
-
<tr>
|
|
1331
|
-
<td class="pad" style="padding-bottom:15px;padding-left:40px;padding-right:40px;">
|
|
1332
|
-
<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;">
|
|
1333
|
-
<p style="margin: 0 0 12px 0; word-break: break-word;">\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} ${safeAppName}. All rights reserved.</p>
|
|
1334
|
-
<p style="margin: 0; font-size: 11px; line-height: 1.5; word-break: break-word;">
|
|
1335
|
-
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.
|
|
1336
|
-
</p>
|
|
1337
|
-
</div>
|
|
1338
|
-
</td>
|
|
1339
|
-
</tr>
|
|
1340
|
-
</table>
|
|
1341
|
-
|
|
1342
|
-
</td>
|
|
1343
|
-
</tr>
|
|
1344
|
-
</tbody>
|
|
1345
|
-
</table>
|
|
1231
|
+
<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}
|
|
1232
|
+
<p style="margin: 0 0 8px 0; word-break: break-word;">\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} ${parts.appName}. All rights reserved.</p>
|
|
1233
|
+
<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>
|
|
1346
1234
|
</td>
|
|
1347
1235
|
</tr>
|
|
1348
1236
|
</tbody>
|
|
1349
1237
|
</table>
|
|
1350
|
-
|
|
1351
1238
|
</td>
|
|
1352
1239
|
</tr>
|
|
1353
1240
|
</tbody>
|
|
@@ -1356,6 +1243,55 @@ function generateEmailTemplate(options) {
|
|
|
1356
1243
|
</html>
|
|
1357
1244
|
`;
|
|
1358
1245
|
}
|
|
1246
|
+
function generateEmailTemplate(options) {
|
|
1247
|
+
const { appUrl, buttonUrl } = options;
|
|
1248
|
+
const safeButtonUrl = buttonUrl ? escapeHtml(sanitizeEmailUrl(buttonUrl, appUrl)) : "";
|
|
1249
|
+
return renderLayout({
|
|
1250
|
+
title: options.heading,
|
|
1251
|
+
logoUrl: logoUrlFor(options.logoUrl, appUrl),
|
|
1252
|
+
logoSize: logoDimensions(options.logoWidth, options.logoHeight),
|
|
1253
|
+
appName: escapeHtml(options.appName ?? "App"),
|
|
1254
|
+
welcome: options.welcomeMessage ?? "",
|
|
1255
|
+
heading: options.heading,
|
|
1256
|
+
body: options.body,
|
|
1257
|
+
button: safeButtonUrl ? { label: options.buttonText, url: safeButtonUrl } : null,
|
|
1258
|
+
notice: options.securityWarning?.trim() ?? "",
|
|
1259
|
+
footerNote: options.footerNote,
|
|
1260
|
+
marketingUrl: marketingUrlFor(options.marketingUrl),
|
|
1261
|
+
brand: brandColours(options)
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
function renderEmail(content) {
|
|
1265
|
+
const { appUrl, branding = {} } = content;
|
|
1266
|
+
const link = (url) => escapeHtml(sanitizeEmailUrl(url, appUrl));
|
|
1267
|
+
const text = (value) => escapeHtml(value).replace(/\r?\n/g, "<br>");
|
|
1268
|
+
const brand = brandColours(branding);
|
|
1269
|
+
const paragraphs = content.paragraphs.map(
|
|
1270
|
+
(p) => `<p style="margin: 0 0 16px 0; word-break: break-word;">${text(p)}</p>`
|
|
1271
|
+
);
|
|
1272
|
+
const sections = (content.sections ?? []).map((section) => {
|
|
1273
|
+
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>`;
|
|
1274
|
+
const rows = section.rows.map(
|
|
1275
|
+
(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>`
|
|
1276
|
+
);
|
|
1277
|
+
return `<p style="margin: 8px 0 4px 0; word-break: break-word;">${title}</p>
|
|
1278
|
+
<ul style="margin: 0 0 16px 0; padding-left: 20px;">${rows.join("")}</ul>`;
|
|
1279
|
+
});
|
|
1280
|
+
return renderLayout({
|
|
1281
|
+
title: escapeHtml(content.heading),
|
|
1282
|
+
logoUrl: logoUrlFor(branding.logoUrl, appUrl),
|
|
1283
|
+
logoSize: logoDimensions(branding.logoWidth, branding.logoHeight),
|
|
1284
|
+
appName: escapeHtml(content.appName ?? "App"),
|
|
1285
|
+
welcome: "",
|
|
1286
|
+
heading: escapeHtml(content.heading),
|
|
1287
|
+
body: [...paragraphs, ...sections].join("\n "),
|
|
1288
|
+
button: content.button ? { label: escapeHtml(content.button.label), url: link(content.button.url) } : null,
|
|
1289
|
+
notice: content.notice ? text(content.notice) : "",
|
|
1290
|
+
footerNote: content.footerNote ? text(content.footerNote) : "",
|
|
1291
|
+
marketingUrl: marketingUrlFor(branding.marketingUrl),
|
|
1292
|
+
brand
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1359
1295
|
|
|
1360
1296
|
// src/core/passwordless.ts
|
|
1361
1297
|
var PASSWORDLESS_TTL_MS = 10 * 60 * 1e3;
|
|
@@ -1450,13 +1386,13 @@ async function cleanupExpiredPasswordlessChallenges(db, tables2) {
|
|
|
1450
1386
|
function passwordlessEmail(input) {
|
|
1451
1387
|
const { appName, appUrl, linkUrl, code } = input;
|
|
1452
1388
|
const minutes = Math.max(1, Math.round((input.ttlMs ?? PASSWORDLESS_TTL_MS) / 6e4));
|
|
1453
|
-
const
|
|
1389
|
+
const lifetime2 = `${minutes} ${minutes === 1 ? "minute" : "minutes"}`;
|
|
1454
1390
|
const safeLink = sanitizeEmailUrl(linkUrl, appUrl);
|
|
1455
1391
|
const html = generateEmailTemplate({
|
|
1456
1392
|
heading: `Sign in to ${escapeHtml(appName)}`,
|
|
1457
1393
|
body: `<p>Use this code, or the button below, to sign in:</p>
|
|
1458
1394
|
<p style="font-size: 32px; font-weight: bold; letter-spacing: 4px; text-align: center; margin: 24px 0; font-family: monospace;">${escapeHtml(code)}</p>
|
|
1459
|
-
<p>The code and the link expire in ${
|
|
1395
|
+
<p>The code and the link expire in ${lifetime2}.</p>`,
|
|
1460
1396
|
buttonText: "Sign in",
|
|
1461
1397
|
buttonUrl: safeLink,
|
|
1462
1398
|
footerNote: "If you didn't ask to sign in, you can ignore this email.",
|
|
@@ -1471,7 +1407,7 @@ function passwordlessEmail(input) {
|
|
|
1471
1407
|
Or sign in with this link:
|
|
1472
1408
|
${safeLink}
|
|
1473
1409
|
|
|
1474
|
-
The code and the link expire in ${
|
|
1410
|
+
The code and the link expire in ${lifetime2}.
|
|
1475
1411
|
|
|
1476
1412
|
If you didn't ask to sign in, you can ignore this email.`
|
|
1477
1413
|
};
|
|
@@ -2021,6 +1957,15 @@ import { eq as eq9 } from "drizzle-orm";
|
|
|
2021
1957
|
import { eq as eq7 } from "drizzle-orm";
|
|
2022
1958
|
|
|
2023
1959
|
// src/lib/email/templates.ts
|
|
1960
|
+
function lifetime(minutes) {
|
|
1961
|
+
if (minutes % 60 !== 0) return `${minutes} ${minutes === 1 ? "minute" : "minutes"}`;
|
|
1962
|
+
const hours = minutes / 60;
|
|
1963
|
+
return `${hours} ${hours === 1 ? "hour" : "hours"}`;
|
|
1964
|
+
}
|
|
1965
|
+
var VERIFICATION_LIFETIME = lifetime(AUTH_DEFAULTS.EMAIL_VERIFICATION_TTL_HOURS * 60);
|
|
1966
|
+
var PASSWORD_RESET_LIFETIME = lifetime(AUTH_DEFAULTS.PASSWORD_RESET_TTL_MINUTES);
|
|
1967
|
+
var EMAIL_CHANGE_LIFETIME = lifetime(AUTH_DEFAULTS.EMAIL_CHANGE_TTL_HOURS * 60);
|
|
1968
|
+
var TWO_FACTOR_CODE_LIFETIME = lifetime(AUTH_DEFAULTS.TWO_FACTOR_EMAIL_CODE_TTL_MINUTES);
|
|
2024
1969
|
function extractAppUrl(url) {
|
|
2025
1970
|
try {
|
|
2026
1971
|
const parsed = new URL(url);
|
|
@@ -2036,7 +1981,7 @@ function generateVerificationEmail(data, appName = "Your App", configuredAppUrl
|
|
|
2036
1981
|
const safeFirstName = firstName ? escapeHtml(firstName) : null;
|
|
2037
1982
|
const html = generateEmailTemplate({
|
|
2038
1983
|
heading: "",
|
|
2039
|
-
body: `Please verify that your email address is <strong>${escapeHtml(email)}</strong>, and that you entered it when signing up for ${escapeHtml(appName)}.`,
|
|
1984
|
+
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}.`,
|
|
2040
1985
|
buttonText: "Verify Email",
|
|
2041
1986
|
buttonUrl: safeUrl,
|
|
2042
1987
|
welcomeMessage: safeFirstName ? `Hi ${safeFirstName}, Welcome to ${escapeHtml(appName)}!` : void 0,
|
|
@@ -2055,7 +2000,7 @@ Thank you for signing up for ${appName}! Please verify your email address by cli
|
|
|
2055
2000
|
|
|
2056
2001
|
${safeUrl}
|
|
2057
2002
|
|
|
2058
|
-
This link expires in
|
|
2003
|
+
This link expires in ${VERIFICATION_LIFETIME}.
|
|
2059
2004
|
|
|
2060
2005
|
If you didn't create an account with ${appName}, you can safely ignore this email.`,
|
|
2061
2006
|
tags: {
|
|
@@ -2073,7 +2018,7 @@ function generatePasswordResetEmail(data, appName = "Your App", configuredAppUrl
|
|
|
2073
2018
|
buttonText: "Reset Password",
|
|
2074
2019
|
buttonUrl: safeUrl,
|
|
2075
2020
|
securityWarning: `
|
|
2076
|
-
<strong>Link expires in
|
|
2021
|
+
<strong>Link expires in ${PASSWORD_RESET_LIFETIME}.</strong><br>
|
|
2077
2022
|
For security, this link can only be used once.
|
|
2078
2023
|
`,
|
|
2079
2024
|
footerNote: "If you didn't request this change, no action is needed. Your password will remain unchanged.",
|
|
@@ -2091,7 +2036,7 @@ We received a request to reset your password. Click the link below to create a n
|
|
|
2091
2036
|
|
|
2092
2037
|
${safeUrl}
|
|
2093
2038
|
|
|
2094
|
-
This link
|
|
2039
|
+
This link expires in ${PASSWORD_RESET_LIFETIME}.
|
|
2095
2040
|
|
|
2096
2041
|
If you didn't request a password reset, you can safely ignore this email.`,
|
|
2097
2042
|
tags: {
|
|
@@ -2109,7 +2054,7 @@ function generateEmailChangeConfirmation(data, appName = "Your App", configuredA
|
|
|
2109
2054
|
buttonText: "Confirm Email Change",
|
|
2110
2055
|
buttonUrl: safeUrl,
|
|
2111
2056
|
securityWarning: `
|
|
2112
|
-
<strong>This link expires in
|
|
2057
|
+
<strong>This link expires in ${EMAIL_CHANGE_LIFETIME}.</strong><br>
|
|
2113
2058
|
For security, this link can only be used once.
|
|
2114
2059
|
`,
|
|
2115
2060
|
footerNote: "If you didn't request this change, you can safely ignore this email.",
|
|
@@ -2129,7 +2074,7 @@ Click the link below to confirm this change:
|
|
|
2129
2074
|
|
|
2130
2075
|
${safeUrl}
|
|
2131
2076
|
|
|
2132
|
-
This link expires in
|
|
2077
|
+
This link expires in ${EMAIL_CHANGE_LIFETIME}.
|
|
2133
2078
|
|
|
2134
2079
|
If you didn't request this change, you can safely ignore this email.`,
|
|
2135
2080
|
tags: {
|
|
@@ -2183,7 +2128,7 @@ function generate2faCodeEmail(data, appName = "Your App", appUrl = "", branding)
|
|
|
2183
2128
|
<p>Hi ${safeFirstName},</p>
|
|
2184
2129
|
<p>Your verification code is:</p>
|
|
2185
2130
|
<p style="font-size: 32px; font-weight: bold; letter-spacing: 4px; text-align: center; margin: 24px 0; font-family: monospace;">${escapeHtml(code)}</p>
|
|
2186
|
-
<p>This code expires in
|
|
2131
|
+
<p>This code expires in ${TWO_FACTOR_CODE_LIFETIME}.</p>
|
|
2187
2132
|
<p>If you didn't request this code, you can safely ignore this email.</p>
|
|
2188
2133
|
`,
|
|
2189
2134
|
buttonText: "",
|
|
@@ -2203,7 +2148,7 @@ Your verification code is:
|
|
|
2203
2148
|
|
|
2204
2149
|
${code}
|
|
2205
2150
|
|
|
2206
|
-
This code expires in
|
|
2151
|
+
This code expires in ${TWO_FACTOR_CODE_LIFETIME}.
|
|
2207
2152
|
|
|
2208
2153
|
If you didn't request this code, you can safely ignore this email.
|
|
2209
2154
|
|
|
@@ -3282,7 +3227,7 @@ var signupHandler = async (c) => {
|
|
|
3282
3227
|
if (!invitedVerified) {
|
|
3283
3228
|
const emailToken = generateSecureToken(32);
|
|
3284
3229
|
const tokenHash = await hashToken(emailToken);
|
|
3285
|
-
const expiresAt = Date.now() +
|
|
3230
|
+
const expiresAt = Date.now() + AUTH_DEFAULTS.EMAIL_VERIFICATION_TTL_HOURS * 60 * 60 * 1e3;
|
|
3286
3231
|
await database.insert(schema.emailVerificationTokens).values({
|
|
3287
3232
|
tokenHash,
|
|
3288
3233
|
userId: newUser.id,
|
|
@@ -3361,7 +3306,7 @@ function generateEmailOtp() {
|
|
|
3361
3306
|
const num = (bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]) >>> 0;
|
|
3362
3307
|
return String(num % 1e6).padStart(6, "0");
|
|
3363
3308
|
}
|
|
3364
|
-
var EMAIL_CODE_TTL_SECONDS =
|
|
3309
|
+
var EMAIL_CODE_TTL_SECONDS = AUTH_DEFAULTS.TWO_FACTOR_EMAIL_CODE_TTL_MINUTES * 60;
|
|
3365
3310
|
async function sendEmailChallengeCode(env, database, usersTable, user, emailBranding) {
|
|
3366
3311
|
const code = generateEmailOtp();
|
|
3367
3312
|
await env.OAUTH_STATES.put(`email_2fa_challenge:${user.id}`, await hashToken(code), {
|
|
@@ -4419,7 +4364,7 @@ var resendVerificationHandler = async (c) => {
|
|
|
4419
4364
|
await db.delete(schema.emailVerificationTokens).where(eq19(schema.emailVerificationTokens.userId, userId));
|
|
4420
4365
|
const emailToken = generateSecureToken(32);
|
|
4421
4366
|
const tokenHash = await hashToken(emailToken);
|
|
4422
|
-
const expiresAt = Date.now() +
|
|
4367
|
+
const expiresAt = Date.now() + AUTH_DEFAULTS.EMAIL_VERIFICATION_TTL_HOURS * 60 * 60 * 1e3;
|
|
4423
4368
|
await db.insert(schema.emailVerificationTokens).values({
|
|
4424
4369
|
tokenHash,
|
|
4425
4370
|
userId: user.id,
|
|
@@ -6027,6 +5972,7 @@ export {
|
|
|
6027
5972
|
rateLimit,
|
|
6028
5973
|
recentPasswordlessFailures,
|
|
6029
5974
|
refreshSession,
|
|
5975
|
+
renderEmail,
|
|
6030
5976
|
requestEmailChange,
|
|
6031
5977
|
requireAuth,
|
|
6032
5978
|
requireVerifiedEmail,
|