const jwt = require('jsonwebtoken'); const { db } = require('../database/db'); const { formatBoolean } = require('../utils/dbCompat'); const { isTokenRevoked } = require('../utils/tokenRevocation'); const logger = require('../utils/logger'); /** * Enhanced admin authentication middleware with revocation checking */ async function adminAuth(req, res, next) { try { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'No token provided' }); } let decoded; try { decoded = jwt.verify(token, process.env.JWT_SECRET, { issuer: 'picpeak-auth', complete: true }); decoded = decoded.payload; } catch (err) { if (err.name === 'TokenExpiredError') { return res.status(401).json({ error: 'Token expired', code: 'TOKEN_EXPIRED' }); } return res.status(401).json({ error: 'Invalid token' }); } // Check if token is revoked if (await isTokenRevoked(decoded)) { logger.warn('Revoked token used', { userId: decoded.id, tokenType: decoded.type }); return res.status(401).json({ error: 'Token has been revoked', code: 'TOKEN_REVOKED' }); } // Verify token type if (decoded.type !== 'admin') { logger.warn('Non-admin token used for admin endpoint', { userId: decoded.id, tokenType: decoded.type }); return res.status(403).json({ error: 'Insufficient permissions' }); } // IP validation (optional - can be strict or just log) const currentIp = req.ip || req.connection.remoteAddress; if (decoded.ip && decoded.ip !== currentIp) { logger.warn('Token used from different IP', { userId: decoded.id, tokenIp: decoded.ip, currentIp: currentIp }); } // Check if admin still exists and is active const admin = await db('admin_users') .where({ id: decoded.id, is_active: formatBoolean(true) }) .first(); if (!admin) { return res.status(401).json({ error: 'Invalid token' }); } // Check if password was changed after token was issued if (admin.password_changed_at) { const passwordChangedTime = new Date(admin.password_changed_at).getTime() / 1000; if (decoded.iat < passwordChangedTime) { logger.warn('Token used after password change', { userId: decoded.id }); return res.status(401).json({ error: 'Token invalid due to password change', code: 'PASSWORD_CHANGED' }); } } // Add user info to request req.admin = { id: admin.id, username: admin.username, email: admin.email }; req.token = token; // Store token for potential revocation next(); } catch (error) { logger.error('Auth middleware error:', error); res.status(401).json({ error: 'Authentication failed' }); } } /** * Enhanced gallery authentication middleware with revocation checking */ async function galleryAuth(req, res, next) { try { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'No token provided' }); } let decoded; try { decoded = jwt.verify(token, process.env.JWT_SECRET, { issuer: 'picpeak-auth', complete: true }); decoded = decoded.payload; } catch (err) { if (err.name === 'TokenExpiredError') { return res.status(401).json({ error: 'Session expired', code: 'TOKEN_EXPIRED' }); } return res.status(401).json({ error: 'Invalid session' }); } // Check if token is revoked if (await isTokenRevoked(decoded)) { return res.status(401).json({ error: 'Session has been invalidated', code: 'TOKEN_REVOKED' }); } // Verify token type if (decoded.type !== 'gallery') { return res.status(403).json({ error: 'Invalid access token' }); } // Check if event still exists and is active const event = await db('events') .where({ id: decoded.eventId, is_active: true, is_archived: false }) .first(); if (!event) { return res.status(404).json({ error: 'Gallery not found or expired' }); } // Check if gallery has expired if (new Date(event.expires_at) < new Date()) { return res.status(410).json({ error: 'Gallery has expired', code: 'GALLERY_EXPIRED' }); } // Add event info to request req.event = event; req.galleryToken = decoded; req.token = token; next(); } catch (error) { logger.error('Gallery auth middleware error:', error); res.status(401).json({ error: 'Authentication failed' }); } } // Export other middleware functions from original file... module.exports = { adminAuth, galleryAuth, // ... other exports };