diff --git a/agenda.js b/agenda.js index bdc7428..c59117b 100644 --- a/agenda.js +++ b/agenda.js @@ -244,16 +244,18 @@ const initializeAgenda = async (mongoUri, pool, io) => { // Now accepts pgPool } }); - agenda.define('delete unverified users', async (job) => { + agenda.define('deleteUnverifiedUser', async (job) => { + const { userId } = job.attrs.data; try { const result = await sharedPgPool.query( - "DELETE FROM users WHERE is_verified = false AND created_at < NOW() - INTERVAL '24 hours'" + "DELETE FROM users WHERE id = $1 AND is_verified = false", + [userId] ); if (result.rowCount > 0) { - console.log(`Cleanup: Deleted ${result.rowCount} unverified users.`); + console.log(`Deleted unverified user with ID ${userId}`); } } catch (error) { - console.error("Error cleaning up users:", error); + console.error(`Error deleting unverified user ${userId}:`, error); } }); @@ -269,6 +271,19 @@ const initializeAgenda = async (mongoUri, pool, io) => { // Now accepts pgPool console.error(`Error deleting password reset token for ${email}:`, error); } }); + + // Define the user pending email deletion job + agenda.define('deleteUserPendingEmail', async (job) => { + const { userId } = job.attrs.data; + try { + const result = await sharedPgPool.query('DELETE FROM user_pending_emails WHERE user_id = $1', [userId]); + if (result.rowCount > 0) { + console.log(`Deleted expired pending email change for user ${userId}`); + } + } catch (error) { + console.error(`Error deleting pending email for user ${userId}:`, error); + } + }); await agenda.start(); @@ -279,8 +294,6 @@ const initializeAgenda = async (mongoUri, pool, io) => { // Now accepts pgPool agenda.on('fail', (err, job) => console.error(`Job "${job.attrs.name}" failed: ${err.message}`)); await agenda.start(); - await agenda.cancel({ name: 'delete unverified users' }); - await agenda.every('24 hours', 'delete unverified users'); console.log('Agenda job processing started.'); return agenda; }; diff --git a/index.js b/index.js index c9f77e0..0535c3d 100644 --- a/index.js +++ b/index.js @@ -88,6 +88,10 @@ let agenda; // Clear all expired password reset tokens on startup await pool.query("DELETE FROM password_reset_tokens WHERE expires_at < NOW()"); console.log("Cleared expired password reset tokens"); + + // Clear all expired pending email changes on startup + await pool.query("DELETE FROM user_pending_emails WHERE expires_at < NOW()"); + console.log("Cleared expired pending email changes"); })(); const JWT_SECRET = process.env.JWT_SECRET; const TOKEN_EXPIRY = '5d'; @@ -599,6 +603,10 @@ app.post('/create_user', async (req, res) => { [name, email, hashedPass, token] ); + // Schedule deletion of unverified user after 15 minutes + const expiryTime = new Date(Date.now() + 15 * 60 * 1000); + await agenda.schedule(expiryTime, 'deleteUnverifiedUser', { userId: newUser.rows[0].id }); + await sendVerificationEmail(email, token, name); // Create temporary token for verification checking @@ -668,6 +676,9 @@ app.get('/verify-email', async (req, res) => { [user.id] ); + // Cancel any scheduled deletion job for this user + await agenda.cancel({ name: 'deleteUnverifiedUser', 'data.userId': user.id }); + // 3. Send them to a success page or back to the app res.send(`
Your email has been updated. You can now close this window.
+ `); + + } catch (err) { + console.error(err); + res.status(500).send('Internal server error'); + } +}); + +app.get('/pending-email-status', authenticateToken, async (req, res) => { + try { + const {rows} = await pool.query( + 'SELECT pending_email FROM user_pending_emails WHERE user_id = $1', + [req.user] + ); + + if (rows.length === 0) { + return res.status(200).json({ hasPending: false }); + } + + res.status(200).json({ + hasPending: true, + pendingEmail: rows[0].pending_email + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Internal server error' }); + } +}); + // Helper function to generate 6-character alphanumeric code function generateResetCode() { const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Exclude confusing chars like 0, O, 1, I diff --git a/mailer.js b/mailer.js index 5684ed6..9bbb757 100644 --- a/mailer.js +++ b/mailer.js @@ -94,7 +94,7 @@ async function sendVerificationEmail(toEmail, token, name, localHour = new Date(- This verification link will expire in 24 hours. + This verification link will expire in 15 minutes.
If you didn't create a BlindMaster account, please ignore this email!!! @@ -225,4 +225,108 @@ async function sendPasswordResetEmail(toEmail, code, name, localHour = new Date( } } -module.exports = { sendVerificationEmail, sendPasswordResetEmail }; \ No newline at end of file +module.exports = { sendVerificationEmail, sendPasswordResetEmail, sendEmailChangeVerification }; + +// Helper function to send email change verification email +async function sendEmailChangeVerification(newEmail, token, name, oldEmail, localHour = new Date().getHours()) { + const primaryColor = getColorForTime(localHour); + const verificationLink = `https://wahwa.com/verify-email-change?token=${token}`; + + try { + const info = await transporter.sendMail({ + from: `"BlindMaster" <${process.env.EMAIL_FROM}>`, + to: newEmail, + subject: "Verify your new BlindMaster email address", + html: ` + + +
+ + + + + +| + + | +