From 8ed7ccade8bdde582529acdad9d6656841cf756e Mon Sep 17 00:00:00 2001 From: ClaraCrazy Date: Fri, 2 Jan 2026 20:56:14 +0100 Subject: [PATCH] [Chore]: Misc changes around user merge - Update lock removal timer and behaviour - Redirect to previous path on sign-in and out - Fix dashbaord UI and other UX elemets - Lose sanity threlf times --- .env.example | 9 +--- app.js | 14 ++++-- application/config.js | 2 +- domain/inbox-lock.js | 17 ++++--- infrastructure/web/middleware/lock.js | 8 ++-- .../web/public/stylesheets/custom.css | 8 +++- infrastructure/web/routes/auth.js | 44 +++++++++++-------- infrastructure/web/routes/inbox.js | 12 ++--- infrastructure/web/routes/lock.js | 14 +----- infrastructure/web/routes/login.js | 11 +---- infrastructure/web/views/account.twig | 2 +- infrastructure/web/views/error.twig | 18 +++++++- infrastructure/web/views/inbox.twig | 3 +- infrastructure/web/views/login-auth.twig | 2 +- infrastructure/web/views/login.twig | 19 +++++++- infrastructure/web/views/mail.twig | 3 +- infrastructure/web/views/verify-success.twig | 19 +++++++- 17 files changed, 127 insertions(+), 78 deletions(-) diff --git a/.env.example b/.env.example index 68aec22..d78e661 100644 --- a/.env.example +++ b/.env.example @@ -43,17 +43,10 @@ HTTP_DISPLAY_SORT=2 # Domain display HTTP_HIDE_OTHER=false # true = only show first domain, false = show all # --- USER AUTHENTICATION & INBOX LOCKING --- -# Authentication System USER_AUTH_ENABLED=false # Enable user registration/login system (default: false) - -# Session Secret (shared for both locking and user sessions) USER_SESSION_SECRET="change-this-secret-in-production" # Secret for session encryption (used for auth & locking) - -# Database Paths USER_DATABASE_PATH="./db/data.db" # Path to application database (users, forwarding, locks) - -# Feature Limits USER_MAX_FORWARD_EMAILS=5 # Maximum verified forwarding emails per user USER_MAX_LOCKED_INBOXES=5 # Maximum locked inboxes per user -LOCK_RELEASE_HOURS=720 # Auto-release locked inboxes after X hours of inactivity (default: 720 = 30 days) +LOCK_RELEASE_HOURS=168 # Auto-release locked inboxes after X hours without login (default: 168 = 7 days) diff --git a/app.js b/app.js index ba64404..ac7f500 100644 --- a/app.js +++ b/app.js @@ -51,13 +51,19 @@ if (config.user.authEnabled) { app.set('inboxLock', inboxLock) debug('Inbox lock service initialized (user-based)') - // Check for inactive locked inboxes + // Check for inactive locked inboxes (users who haven't logged in for 7 days) setInterval(() => { const inactive = inboxLock.getInactive(config.user.lockReleaseHours) if (inactive.length > 0) { - debug(`Found ${inactive.length} inactive locked inbox(es)`) - // Note: Auto-release of user locks would require storing userId - // For now, inactive locks remain until user logs in + debug(`Auto-releasing ${inactive.length} locked inbox(es) due to user inactivity (${config.user.lockReleaseHours} hours without login)`) + inactive.forEach(lock => { + try { + inboxLock.release(lock.userId, lock.address) + debug(`Released lock on ${lock.address} for inactive user ${lock.userId}`) + } catch (error) { + debug(`Failed to release lock on ${lock.address}: ${error.message}`) + } + }) } }, config.imap.refreshIntervalSeconds * 1000) diff --git a/application/config.js b/application/config.js index 80ddcdf..d18b3e2 100644 --- a/application/config.js +++ b/application/config.js @@ -86,7 +86,7 @@ const config = { // Feature Limits maxForwardEmails: Number(process.env.USER_MAX_FORWARD_EMAILS) || 5, maxLockedInboxes: Number(process.env.USER_MAX_LOCKED_INBOXES) || 5, - lockReleaseHours: Number(process.env.LOCK_RELEASE_HOURS) || 720 // 30 days default + lockReleaseHours: Number(process.env.LOCK_RELEASE_HOURS) || 168 // 7 days default } }; diff --git a/domain/inbox-lock.js b/domain/inbox-lock.js index 3565549..0af96be 100644 --- a/domain/inbox-lock.js +++ b/domain/inbox-lock.js @@ -121,17 +121,22 @@ class InboxLock { } /** - * Get inactive locked inboxes (not accessed in X hours) - * @param {number} hoursThreshold - Hours of inactivity - * @returns {Array} - Array of inactive inbox addresses + * Get inactive locked inboxes (user hasn't logged in for X hours) + * @param {number} hoursThreshold - Hours of user inactivity (no login) + * @returns {Array} - Array of {userId, address} for inactive locks */ getInactive(hoursThreshold) { const cutoff = Date.now() - (hoursThreshold * 60 * 60 * 1000) const stmt = this.db.prepare(` - SELECT inbox_address FROM user_locked_inboxes - WHERE last_accessed < ? + SELECT ul.user_id, ul.inbox_address, u.last_login + FROM user_locked_inboxes ul + JOIN users u ON ul.user_id = u.id + WHERE u.last_login IS NULL OR u.last_login < ? `) - return stmt.all(cutoff).map(row => row.inbox_address) + return stmt.all(cutoff).map(row => ({ + userId: row.user_id, + address: row.inbox_address + })) } /** diff --git a/infrastructure/web/middleware/lock.js b/infrastructure/web/middleware/lock.js index 7e8b4b8..974070e 100644 --- a/infrastructure/web/middleware/lock.js +++ b/infrastructure/web/middleware/lock.js @@ -1,8 +1,8 @@ function checkLockAccess(req, res, next) { const inboxLock = req.app.get('inboxLock') const address = req.params.address - const userId = req.session ? .userId - const isAuthenticated = req.session ? .isAuthenticated + const userId = req.session && req.session.userId + const isAuthenticated = req.session && req.session.isAuthenticated if (!address || !inboxLock) { return next() @@ -14,7 +14,7 @@ function checkLockAccess(req, res, next) { // Also allow session-based access for immediate unlock after locking const hasAccess = isAuthenticated && userId ? (inboxLock.isLockedByUser(address, userId) || req.session.lockedInbox === address.toLowerCase()) : - (req.session ? .lockedInbox === address.toLowerCase()) + (req.session && req.session.lockedInbox === address.toLowerCase()) // Block access to locked inbox without proper authentication if (isLocked && !hasAccess) { @@ -28,7 +28,7 @@ function checkLockAccess(req, res, next) { count: count, message: 'This inbox is locked by another user. Only the owner can access it.', branding: req.app.get('config').http.branding, - currentUser: req.session ? .username, + currentUser: req.session && req.session.username, authEnabled: req.app.get('config').user.authEnabled }) } diff --git a/infrastructure/web/public/stylesheets/custom.css b/infrastructure/web/public/stylesheets/custom.css index a3051d9..05117e7 100644 --- a/infrastructure/web/public/stylesheets/custom.css +++ b/infrastructure/web/public/stylesheets/custom.css @@ -741,7 +741,7 @@ text-muted { .account-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + grid-template-columns: repeat(2, 1fr); gap: 2rem; margin-top: 2rem; } @@ -852,13 +852,19 @@ text-muted { color: var(--color-text-gray); } +form { + margin: 0; +} + .inline-form { display: inline; + align-self: center; } .button-small { padding: 0rem 1rem; font-size: 0.85rem; + margin: 0; } .button-danger { diff --git a/infrastructure/web/routes/auth.js b/infrastructure/web/routes/auth.js index f2f4287..188bf24 100644 --- a/infrastructure/web/routes/auth.js +++ b/infrastructure/web/routes/auth.js @@ -162,9 +162,10 @@ router.post('/login', if (result.success) { debug(`User logged in successfully: ${username}`) - // Regenerate session to prevent fixation attacks + // Store redirect URL before regenerating session const redirectUrl = req.session.redirectAfterLogin || '/' + // Regenerate session to prevent fixation attacks req.session.regenerate((err) => { if (err) { debug(`Session regeneration error: ${err.message}`) @@ -185,7 +186,7 @@ router.post('/login', return res.redirect('/auth') } - debug(`Session created for user: ${username}`) + debug(`Session created for user: ${username}, redirecting to: ${redirectUrl}`) res.redirect(redirectUrl) }) }) @@ -205,23 +206,30 @@ router.post('/login', // GET /logout - Logout user router.get('/logout', (req, res) => { - if (req.session) { - const username = req.session.username - req.session.destroy((err) => { - if (err) { - debug(`Logout error: ${err.message}`) - console.error('Error during logout', err) - } else { - debug(`User logged out: ${username}`) - } - res.redirect('/') - }) - } else { - res.redirect('/') - } -}) + // Store redirect URL before destroying session + const redirectUrl = req.query.redirect || req.get('Referer') || '/' -// GET /auth/check - JSON endpoint for checking auth status (AJAX) + debug(`Logout requested with redirect: ${redirectUrl}`) + + if (req.session) { + const username = req.session.username + req.session.destroy((err) => { + if (err) { + debug(`Logout error: ${err.message}`) + console.error('Error during logout', err) + return res.redirect('/') + } + + debug(`User logged out: ${username}, redirecting to: ${redirectUrl}`) + // Clear cookie explicitly + res.clearCookie('connect.sid') + res.redirect(redirectUrl) + }) + } else { + debug(`No session found, redirecting to: ${redirectUrl}`) + res.redirect(redirectUrl) + } + }) // GET /auth/check - JSON endpoint for checking auth status (AJAX) router.get('/auth/check', (req, res) => { if (req.session && req.session.userId && req.session.isAuthenticated) { res.json({ diff --git a/infrastructure/web/routes/inbox.js b/infrastructure/web/routes/inbox.js index b2c533c..16714ef 100644 --- a/infrastructure/web/routes/inbox.js +++ b/infrastructure/web/routes/inbox.js @@ -113,13 +113,13 @@ router.get('^/:address([^@/]+@[^@/]+)', sanitizeAddress, validateDomain, optiona // Check lock status const isLocked = inboxLock && inboxLock.isLocked(req.params.address) - const userId = req.session ? .userId - const isAuthenticated = req.session ? .isAuthenticated + const userId = req.session && req.session.userId + const isAuthenticated = req.session && req.session.isAuthenticated // Check if user has access (either owns the lock or has session access) const hasAccess = isAuthenticated && userId && inboxLock ? (inboxLock.isLockedByUser(req.params.address, userId) || req.session.lockedInbox === req.params.address) : - (req.session ? .lockedInbox === req.params.address) + (req.session && req.session.lockedInbox === req.params.address) // Get user's verified emails if logged in let userForwardEmails = [] @@ -211,13 +211,13 @@ router.get( const inboxLock = req.app.get('inboxLock') const isLocked = inboxLock && inboxLock.isLocked(req.params.address) - const userId = req.session ? .userId - const isAuthenticated = req.session ? .isAuthenticated + const userId = req.session && req.session.userId + const isAuthenticated = req.session && req.session.isAuthenticated // Check if user has access (either owns the lock or has session access) const hasAccess = isAuthenticated && userId && inboxLock ? (inboxLock.isLockedByUser(req.params.address, userId) || req.session.lockedInbox === req.params.address) : - (req.session ? .lockedInbox === req.params.address) + (req.session && req.session.lockedInbox === req.params.address) // Get user's verified emails if logged in let userForwardEmails = [] diff --git a/infrastructure/web/routes/lock.js b/infrastructure/web/routes/lock.js index a31cdf4..608796d 100644 --- a/infrastructure/web/routes/lock.js +++ b/infrastructure/web/routes/lock.js @@ -107,19 +107,7 @@ router.post('/unlock', requireAuth, async(req, res) => { } }) -router.get('/logout', (req, res) => { - const mailProcessingService = req.app.get('mailProcessingService') - - // Clear cache before logout - if (mailProcessingService.cachedFetchFullMail && mailProcessingService.cachedFetchFullMail.clear) { - debug('Clearing lock cache for logout') - mailProcessingService.cachedFetchFullMail.clear() - } - - debug('Clearing lockedInbox from session (lock logout)') - delete req.session.lockedInbox - res.redirect('/') -}) +// Legacy logout route removed - handled by auth.js router.post('/remove', requireAuth, async(req, res) => { const { address } = req.body diff --git a/infrastructure/web/routes/login.js b/infrastructure/web/routes/login.js index c924148..ffec647 100644 --- a/infrastructure/web/routes/login.js +++ b/infrastructure/web/routes/login.js @@ -45,14 +45,7 @@ router.get('/inbox/random', (req, res, _next) => { res.redirect(`/inbox/${inbox}`) }) -router.get('/logout', (req, res, _next) => { - - /** - * If we ever need a logout sequence, now we can have one! - */ - - res.redirect('/') -}) +// Legacy logout route removed - handled by auth.js router.post( '/', [ @@ -91,4 +84,4 @@ router.post( } ) -module.exports = router \ No newline at end of file +module.exports = router diff --git a/infrastructure/web/views/account.twig b/infrastructure/web/views/account.twig index 468481b..3080cb7 100644 --- a/infrastructure/web/views/account.twig +++ b/infrastructure/web/views/account.twig @@ -87,7 +87,7 @@ + {% endif %} + {% else %} + {% if authEnabled %} + Account + {% endif %} + {% endif %} + + Example Inbox + + + {% endif %} + {% else %} + {% if authEnabled %} + Account + {% endif %} + {% endif %} + + Home