mirror of
https://github.com/Crazyco-xyz/48hr.email.git
synced 2026-01-09 11:19:36 +01:00
Add dashboard and update routes to use the new User object. Merge forwarding and locking to be user-only methods and remove old routes that no longer exist
172 lines
5.3 KiB
JavaScript
172 lines
5.3 KiB
JavaScript
const path = require('path')
|
|
const http = require('http')
|
|
const debug = require('debug')('48hr-email:server')
|
|
const express = require('express')
|
|
const session = require('express-session')
|
|
const cookieParser = require('cookie-parser')
|
|
const logger = require('morgan')
|
|
const Twig = require('twig')
|
|
const compression = require('compression')
|
|
const helmet = require('helmet')
|
|
const socketio = require('socket.io')
|
|
|
|
const config = require('../../application/config')
|
|
const inboxRouter = require('./routes/inbox')
|
|
const loginRouter = require('./routes/login')
|
|
const errorRouter = require('./routes/error')
|
|
const lockRouter = require('./routes/lock')
|
|
const authRouter = require('./routes/auth')
|
|
const accountRouter = require('./routes/account')
|
|
const { sanitizeHtmlTwigFilter } = require('./views/twig-filters')
|
|
|
|
const Helper = require('../../application/helper')
|
|
const helper = new(Helper)
|
|
const purgeTime = helper.purgeTimeElemetBuilder()
|
|
|
|
// Utility function for consistent error handling in routes
|
|
const handleRouteError = (error, req, res, next, context = 'route') => {
|
|
debug(`Error in ${context}:`, error.message)
|
|
console.error(`Error in ${context}`, error)
|
|
next(error)
|
|
}
|
|
|
|
// Init express middleware
|
|
const app = express()
|
|
app.use(helmet())
|
|
app.use(compression())
|
|
app.set('config', config)
|
|
const server = http.createServer(app)
|
|
const io = socketio(server)
|
|
|
|
app.set('socketio', io)
|
|
app.use(logger('dev'))
|
|
app.use(express.json())
|
|
app.use(express.urlencoded({ extended: false }))
|
|
|
|
// Cookie parser for signed cookies (email verification)
|
|
app.use(cookieParser(config.user.sessionSecret))
|
|
|
|
// Session support (always enabled for forward verification and inbox locking)
|
|
app.use(session({
|
|
secret: config.user.sessionSecret,
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
cookie: { maxAge: 24 * 60 * 60 * 1000 } // 24 hours
|
|
}))
|
|
|
|
// Clear lock session data when user goes Home (but preserve authentication)
|
|
app.get('/', (req, res, next) => {
|
|
if (req.session && req.session.lockedInbox) {
|
|
// Only clear lock-related data, preserve user authentication
|
|
delete req.session.lockedInbox
|
|
req.session.save(() => next())
|
|
} else {
|
|
next()
|
|
}
|
|
})
|
|
|
|
// Remove trailing slash middleware (except for root)
|
|
app.use((req, res, next) => {
|
|
if (req.path.length > 1 && req.path.endsWith('/')) {
|
|
const query = req.url.slice(req.path.length) // preserve query string
|
|
return res.redirect(301, req.path.slice(0, -1) + query)
|
|
}
|
|
next()
|
|
})
|
|
|
|
// View engine setup
|
|
app.set('views', path.join(__dirname, 'views'))
|
|
app.set('view engine', 'twig')
|
|
app.set('twig options', {
|
|
autoescape: true
|
|
})
|
|
|
|
// Application code:
|
|
app.use(
|
|
express.static(path.join(__dirname, 'public'), {
|
|
immutable: true,
|
|
maxAge: '1h'
|
|
})
|
|
)
|
|
Twig.extendFilter('sanitizeHtml', sanitizeHtmlTwigFilter)
|
|
|
|
// Middleware to expose user session to all templates
|
|
app.use((req, res, next) => {
|
|
res.locals.authEnabled = config.user.authEnabled
|
|
res.locals.currentUser = null
|
|
if (req.session && req.session.userId && req.session.username && req.session.isAuthenticated) {
|
|
res.locals.currentUser = {
|
|
id: req.session.userId,
|
|
username: req.session.username
|
|
}
|
|
}
|
|
next()
|
|
})
|
|
|
|
// Middleware to show loading page until IMAP is ready
|
|
app.use((req, res, next) => {
|
|
const isImapReady = req.app.get('isImapReady')
|
|
if (!isImapReady && !req.path.startsWith('/images') && !req.path.startsWith('/javascripts') && !req.path.startsWith('/stylesheets') && !req.path.startsWith('/dependencies')) {
|
|
return res.render('loading')
|
|
}
|
|
next()
|
|
})
|
|
|
|
app.use('/', loginRouter)
|
|
if (config.user.authEnabled) {
|
|
app.use('/', authRouter)
|
|
app.use('/', accountRouter)
|
|
}
|
|
app.use('/inbox', inboxRouter)
|
|
app.use('/error', errorRouter)
|
|
app.use('/lock', lockRouter)
|
|
|
|
// Catch 404 and forward to error handler
|
|
app.use((req, res, next) => {
|
|
next({ message: 'Page not found', status: 404 })
|
|
})
|
|
|
|
// Error handler
|
|
app.use(async(err, req, res, _next) => {
|
|
try {
|
|
debug('Error handler triggered:', err.message)
|
|
const mailProcessingService = req.app.get('mailProcessingService')
|
|
const count = await mailProcessingService.getCount()
|
|
|
|
// Set locals, only providing error in development
|
|
res.locals.message = err.message
|
|
res.locals.error = req.app.get('env') === 'development' ? err : {}
|
|
|
|
// Render the error page
|
|
res.status(err.status || 500)
|
|
res.render('error', {
|
|
purgeTime: purgeTime,
|
|
address: req.params && req.params.address,
|
|
count: count,
|
|
branding: config.http.branding
|
|
})
|
|
} catch (renderError) {
|
|
debug('Error in error handler:', renderError.message)
|
|
console.error('Critical error in error handler', renderError)
|
|
// Fallback: send plain text error if rendering fails
|
|
res.status(500).send('Internal Server Error')
|
|
}
|
|
})
|
|
|
|
/**
|
|
* Get port from environment and store in Express.
|
|
*/
|
|
|
|
app.set('port', config.http.port)
|
|
|
|
/**
|
|
* Listen on provided port, on all network interfaces.
|
|
*/
|
|
server.listen(config.http.port)
|
|
server.on('listening', () => {
|
|
const addr = server.address()
|
|
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port
|
|
debug('Listening on ' + bind)
|
|
})
|
|
|
|
module.exports = { app, io, server }
|