Skip to content

Commit b0573f7

Browse files
committed
feat: modal for email change, 30min expiry, 30s resend cooldown, cancel to /login
1 parent 49cf8af commit b0573f7

3 files changed

Lines changed: 45 additions & 10 deletions

File tree

public/js/app.js

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,30 @@ async function sendPowerCommand(identifier, signal, event) {
107107
}
108108

109109
function $(sel) { return document.querySelector(sel); }
110+
111+
function showModal(title, message, buttonText) {
112+
let overlay = $('#standalone-modal-overlay');
113+
if (!overlay) {
114+
overlay = document.createElement('div');
115+
overlay.id = 'standalone-modal-overlay';
116+
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.5);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:9999;display:flex;align-items:center;justify-content:center';
117+
overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
118+
document.body.appendChild(overlay);
119+
}
120+
overlay.innerHTML = html`
121+
<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:28px;max-width:400px;width:90%;box-shadow:0 20px 60px rgba(0,0,0,0.4);text-align:center" onclick="event.stopPropagation()">
122+
<div style="margin-bottom:16px;">
123+
<i data-lucide="mail-check" style="width:40px;height:40px;color:var(--accent-1);"></i>
124+
</div>
125+
<h2 style="font-size:1.15rem;font-weight:700;margin-bottom:8px">${title}</h2>
126+
<p style="font-size:0.88rem;color:var(--text-secondary);line-height:1.6;margin-bottom:20px">${message}</p>
127+
<button class="btn btn-primary btn-full standalone-modal-ok" style="justify-content:center">${buttonText || 'OK'}</button>
128+
</div>
129+
`;
130+
overlay.style.display = 'flex';
131+
initIcons();
132+
overlay.querySelector('.standalone-modal-ok').onclick = () => overlay.remove();
133+
}
110134
function md5(s) {
111135
function F(x,y,z) { return (x & y) | (~x & z); }
112136
function G(x,y,z) { return (x & z) | (y & ~z); }
@@ -1029,16 +1053,27 @@ async function renderChangeEmailVerify(token) {
10291053
try {
10301054
await api(`/auth/change-email/verify?token=${encodeURIComponent(token)}`);
10311055
showToast('New code sent', 'success');
1056+
let countdown = 30;
1057+
btn.textContent = `Resend code (${countdown}s)`;
1058+
const timer = setInterval(() => {
1059+
countdown--;
1060+
if (countdown <= 0) {
1061+
clearInterval(timer);
1062+
btn.disabled = false;
1063+
btn.textContent = 'Resend code';
1064+
} else {
1065+
btn.textContent = `Resend code (${countdown}s)`;
1066+
}
1067+
}, 1000);
10321068
} catch (err) {
10331069
showToast(err.message, 'error');
1034-
} finally {
10351070
btn.disabled = false;
10361071
}
10371072
});
10381073

10391074
$('#change-email-cancel').addEventListener('click', (e) => {
10401075
e.preventDefault();
1041-
navigateTo('account/info');
1076+
navigateTo('login');
10421077
});
10431078

10441079
initIcons();
@@ -1059,7 +1094,7 @@ async function renderChangeEmailVerify(token) {
10591094
<h1 class="auth-title">Link expired</h1>
10601095
<p class="auth-subtitle">${escapeHtml(err.message)}</p>
10611096
<div style="margin-top:24px;">
1062-
<a href="/account/info" class="btn btn-primary">Back to Account</a>
1097+
<a href="/login" class="btn btn-primary">Sign In</a>
10631098
</div>
10641099
</div>
10651100
</div>
@@ -3185,7 +3220,7 @@ async function handleChangeEmail(e) {
31853220
});
31863221
$('#acc-new-email').value = '';
31873222
$('#acc-email-pw').value = '';
3188-
showToast('Confirmation link sent to your current email', 'success');
3223+
showModal('Check your email', 'A confirmation link has been sent to your current email address. Click the link to proceed with the email change. The link expires in 30 minutes.', 'Got it');
31893224
} catch (err) {
31903225
showToast(err.message, 'error');
31913226
} finally {

routes/auth.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ router.post('/change-email', authenticateToken, sensitiveLimiter, async (req, re
527527
}
528528

529529
const token = randomBytes(32).toString('hex');
530-
const expires = new Date(Date.now() + 60 * 60 * 1000);
530+
const expires = new Date(Date.now() + 30 * 60 * 1000);
531531

532532
await query(
533533
'UPDATE users SET pending_email = ?, email_change_token = ?, email_change_expires = ? WHERE id = ?',
@@ -562,7 +562,7 @@ router.get('/change-email/verify', async (req, res) => {
562562

563563
const user = users[0];
564564
const code = String(Math.floor(100000 + Math.random() * 900000));
565-
const codeExpires = new Date(Date.now() + 10 * 60 * 1000);
565+
const codeExpires = new Date(Date.now() + 30 * 60 * 1000);
566566

567567
await query(
568568
'UPDATE users SET email_change_code = ?, email_change_expires = ? WHERE id = ?',

services/email.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export function getEmailChangeLinkHtml(username, verifyUrl, newEmail) {
192192
</tr>
193193
<tr>
194194
<td style="padding-bottom:24px;">
195-
<p style="margin:0;font-size:15px;line-height:1.6;color:#a0a0b8;">Hi ${username}, you requested to change your email to <strong style="color:#ffffff;">${newEmail}</strong>. Click the button below to confirm. This link expires in 1 hour.</p>
195+
<p style="margin:0;font-size:15px;line-height:1.6;color:#a0a0b8;">Hi ${username}, you requested to change your email to <strong style="color:#ffffff;">${newEmail}</strong>. Click the button below to confirm. This link expires in 30 minutes.</p>
196196
</td>
197197
</tr>
198198
<tr>
@@ -231,7 +231,7 @@ export function getEmailChangeLinkHtml(username, verifyUrl, newEmail) {
231231
export function getEmailChangeLinkText(username, verifyUrl, newEmail) {
232232
return `Confirm your email change
233233
234-
Hi ${username}, you requested to change your email to ${newEmail}. Click the link below to confirm. This link expires in 1 hour.
234+
Hi ${username}, you requested to change your email to ${newEmail}. Click the link below to confirm. This link expires in 30 minutes.
235235
236236
${verifyUrl}
237237
@@ -277,7 +277,7 @@ export function getEmailChangeCodeHtml(username, code) {
277277
</tr>
278278
<tr>
279279
<td style="padding-bottom:24px;">
280-
<p style="margin:0;font-size:15px;line-height:1.6;color:#a0a0b8;">Hi ${username}, here is your verification code to confirm your new email address. This code expires in 10 minutes.</p>
280+
<p style="margin:0;font-size:15px;line-height:1.6;color:#a0a0b8;">Hi ${username}, here is your verification code to confirm your new email address. This code expires in 30 minutes.</p>
281281
</td>
282282
</tr>
283283
<tr>
@@ -309,7 +309,7 @@ export function getEmailChangeCodeHtml(username, code) {
309309
export function getEmailChangeCodeText(username, code) {
310310
return `Your verification code
311311
312-
Hi ${username}, here is your verification code to confirm your new email address. This code expires in 10 minutes.
312+
Hi ${username}, here is your verification code to confirm your new email address. This code expires in 30 minutes.
313313
314314
${code}
315315

0 commit comments

Comments
 (0)