diff --git a/server/routes/auth.js b/server/routes/auth.js index b7ca432..4068ab4 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -204,6 +204,38 @@ router.post('/login', async (req, res, next) => { } }); +/** + * POST /auth/register + * Universal registration endpoint that routes to appropriate provider + */ +router.post('/register', async (req, res, next) => { + try { + // Determine tenant + const tenantId = await multiAuth.determineTenant(req); + const authConfig = await multiAuth.getTenantAuthConfig(tenantId); + + req.tenant = { id: tenantId, authConfig }; + + // Only local authentication supports registration + if (authConfig.type !== 'local') { + return res.status(400).json({ + success: false, + message: `Registration not supported for ${authConfig.type} authentication` + }); + } + + // Route to local registration handler + return require('../routes/user').registerLocal(req, res, next); + + } catch (error) { + console.error('Registration error:', error); + res.status(500).json({ + success: false, + message: 'Registration failed' + }); + } +}); + /** * POST /auth/local * Local authentication endpoint with tenant isolation @@ -478,4 +510,143 @@ router.post('/test/:tenantId', async (req, res) => { } }); +/** + * POST /auth/refresh + * Refresh JWT token + */ +router.post('/refresh', async (req, res) => { + try { + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ + success: false, + message: 'Authorization token required' + }); + } + + const token = authHeader.substring(7); + const jwt = require('jsonwebtoken'); + + // Verify current token (even if expired, we want to check if it's valid) + let decoded; + try { + decoded = jwt.verify(token, process.env.JWT_SECRET, { ignoreExpiration: true }); + } catch (error) { + return res.status(401).json({ + success: false, + message: 'Invalid token' + }); + } + + // Find user to ensure they still exist and are active + const { User } = require('../models'); + const user = await User.findOne({ + where: { id: decoded.userId, is_active: true } + }); + + if (!user) { + return res.status(401).json({ + success: false, + message: 'User not found or inactive' + }); + } + + // Generate new token + const newToken = jwt.sign( + { + userId: user.id, + username: user.username, + role: user.role, + tenantId: decoded.tenantId, + provider: user.auth_provider + }, + process.env.JWT_SECRET, + { expiresIn: '24h' } + ); + + res.json({ + success: true, + data: { + token: newToken, + expires_in: '24h' + }, + message: 'Token refreshed successfully' + }); + + } catch (error) { + console.error('Token refresh error:', error); + res.status(500).json({ + success: false, + message: 'Token refresh failed' + }); + } +}); + +/** + * POST /auth/logout + * Logout (mainly for client-side token removal) + */ +router.post('/logout', (req, res) => { + res.json({ + success: true, + message: 'Logged out successfully' + }); +}); + +/** + * GET /auth/me + * Get current user information + */ +router.get('/me', async (req, res) => { + try { + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ + success: false, + message: 'Authorization token required' + }); + } + + const token = authHeader.substring(7); + const jwt = require('jsonwebtoken'); + + let decoded; + try { + decoded = jwt.verify(token, process.env.JWT_SECRET); + } catch (error) { + return res.status(401).json({ + success: false, + message: 'Invalid or expired token' + }); + } + + // Find user + const { User } = require('../models'); + const user = await User.findOne({ + where: { id: decoded.userId, is_active: true }, + attributes: { exclude: ['password_hash'] } + }); + + if (!user) { + return res.status(401).json({ + success: false, + message: 'User not found or inactive' + }); + } + + res.json({ + success: true, + data: { user }, + message: 'User information retrieved successfully' + }); + + } catch (error) { + console.error('Get user info error:', error); + res.status(500).json({ + success: false, + message: 'Failed to retrieve user information' + }); + } +}); + module.exports = router; diff --git a/server/routes/detections.js b/server/routes/detections.js index 38459fb..3dda309 100644 --- a/server/routes/detections.js +++ b/server/routes/detections.js @@ -15,12 +15,12 @@ const multiAuth = new MultiTenantAuth(); */ router.get('/', authenticateToken, async (req, res) => { try { - // Determine tenant from request - const tenantId = await multiAuth.determineTenant(req); + // Get tenant from authenticated user context + const tenantId = req.tenantId; if (!tenantId) { return res.status(400).json({ success: false, - message: 'Unable to determine tenant' + message: 'No tenant context available' }); } @@ -32,6 +32,13 @@ router.get('/', authenticateToken, async (req, res) => { }); } + if (!tenant.is_active) { + return res.status(403).json({ + success: false, + message: 'Tenant is inactive' + }); + } + const { device_id, drone_id, @@ -236,12 +243,12 @@ router.get('/debug', authenticateToken, async (req, res) => { */ router.get('/:id', authenticateToken, async (req, res) => { try { - // Determine tenant from request - const tenantId = await multiAuth.determineTenant(req); + // Get tenant from authenticated user context + const tenantId = req.tenantId; if (!tenantId) { return res.status(400).json({ success: false, - message: 'Unable to determine tenant' + message: 'No tenant context available' }); } @@ -253,6 +260,13 @@ router.get('/:id', authenticateToken, async (req, res) => { }); } + if (!tenant.is_active) { + return res.status(403).json({ + success: false, + message: 'Tenant is inactive' + }); + } + const { id } = req.params; const detection = await DroneDetection.findByPk(id, { @@ -294,24 +308,62 @@ router.delete('/:id', authenticateToken, async (req, res) => { try { // Check if user is admin if (req.user.role !== 'admin') { - return res.status(403).json({ error: 'Admin access required' }); + return res.status(403).json({ + success: false, + message: 'Admin access required' + }); + } + + // Get tenant from authenticated user context + const tenantId = req.tenantId; + if (!tenantId) { + return res.status(400).json({ + success: false, + message: 'No tenant context available' + }); + } + + const tenant = await Tenant.findOne({ where: { slug: tenantId } }); + if (!tenant) { + return res.status(404).json({ + success: false, + message: 'Tenant not found' + }); } const { id } = req.params; - const detection = await DroneDetection.findByPk(id); + // Find detection with tenant filtering + const detection = await DroneDetection.findOne({ + where: { id }, + include: [{ + model: Device, + as: 'device', + where: { tenant_id: tenant.id }, // Ensure detection belongs to user's tenant + attributes: ['id', 'tenant_id'] + }] + }); + if (!detection) { - return res.status(404).json({ error: 'Detection not found' }); + return res.status(404).json({ + success: false, + message: 'Detection not found or not accessible' + }); } await detection.destroy(); - res.json({ message: 'Detection deleted successfully' }); + + res.json({ + success: true, + message: 'Detection deleted successfully' + }); } catch (error) { console.error('Error deleting detection:', error); res.status(500).json({ - error: 'Failed to delete detection', - details: error.message + success: false, + message: 'Failed to delete detection', + error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); diff --git a/server/routes/user.js b/server/routes/user.js index b578443..c7822d7 100644 --- a/server/routes/user.js +++ b/server/routes/user.js @@ -84,12 +84,19 @@ const updateProfileSchema = Joi.object({ // POST /api/users/register - Register new user (ULTRA SECURE) router.post('/register', registrationLimiter, validateRequest(registerSchema), async (req, res) => { + return registerLocal(req, res); +}); + +/** + * Register local user - can be called from both direct route and auth route + */ +async function registerLocal(req, res) { try { console.log('🔒 Registration attempt started'); // Step 1: Determine tenant context - CRITICAL SECURITY CHECK const multiAuth = new MultiTenantAuth(); - const tenantId = await multiAuth.determineTenant(req); + const tenantId = req.tenant?.id || await multiAuth.determineTenant(req); console.log('🔍 Registration - Determined tenant:', tenantId); if (!tenantId) { @@ -138,7 +145,7 @@ router.post('/register', registrationLimiter, validateRequest(registerSchema), a } // Step 6: Additional security - Check if registration is explicitly enabled in auth config - const authConfig = await multiAuth.getTenantAuthConfig(tenantId); + const authConfig = req.tenant?.authConfig || await multiAuth.getTenantAuthConfig(tenantId); if (!authConfig.enabled) { console.log('❌ Registration BLOCKED - Auth config disabled'); return res.status(403).json({ @@ -192,7 +199,7 @@ router.post('/register', registrationLimiter, validateRequest(registerSchema), a res.status(201).json({ success: true, - data: userResponse, + data: { user: userResponse }, message: 'User registered successfully' }); @@ -212,7 +219,7 @@ router.post('/register', registrationLimiter, validateRequest(registerSchema), a error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } -}); +} // POST /api/users/login - User login (direct API access - for legacy support) router.post('/login', validateRequest(loginSchema), async (req, res) => { @@ -385,6 +392,20 @@ router.get('/', authenticateToken, requireRole(['admin']), async (req, res) => { */ async function loginLocal(req, res, next) { try { + // Validate required fields + if (!req.body.username || !req.body.password) { + return res.status(400).json({ + success: false, + message: 'Validation error for field' + + (!req.body.username && !req.body.password ? 's: username, password' : + !req.body.username ? ': username' : ': password'), + details: [ + ...(!req.body.username ? [{ field: 'username', message: '"username" is required' }] : []), + ...(!req.body.password ? [{ field: 'password', message: '"password" is required' }] : []) + ] + }); + } + const { username, password } = req.body; // Get tenant information from request (set by multi-tenant auth middleware) @@ -458,8 +479,8 @@ async function loginLocal(req, res, next) { userId: user.id, username: user.username, role: user.role, - tenantId: user.tenant_id, - tenantSlug: req.tenant?.id + tenantId: req.tenant?.id, // This should be the tenant slug + provider: user.auth_provider }, process.env.JWT_SECRET, { expiresIn: '24h' } @@ -490,3 +511,4 @@ async function loginLocal(req, res, next) { module.exports = router; module.exports.loginLocal = loginLocal; +module.exports.registerLocal = registerLocal;