"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.requireAuth = requireAuth; exports.requireRole = requireRole; const express_1 = __importDefault(require("express")); const simpleAuth_1 = require("../config/simpleAuth"); const databaseService_1 = __importDefault(require("../services/databaseService")); const router = express_1.default.Router(); // Enhanced logging for production debugging function logAuthEvent(event, details = {}) { const timestamp = new Date().toISOString(); console.log(`🔐 [AUTH ${timestamp}] ${event}:`, JSON.stringify(details, null, 2)); } // Validate environment variables on startup function validateAuthEnvironment() { const required = ['GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_SECRET', 'GOOGLE_REDIRECT_URI', 'FRONTEND_URL']; const missing = required.filter(key => !process.env[key]); if (missing.length > 0) { logAuthEvent('ENVIRONMENT_ERROR', { missing_variables: missing }); return false; } // Validate URLs const frontendUrl = process.env.FRONTEND_URL; const redirectUri = process.env.GOOGLE_REDIRECT_URI; if (!frontendUrl?.startsWith('http')) { logAuthEvent('ENVIRONMENT_ERROR', { error: 'FRONTEND_URL must start with http/https' }); return false; } if (!redirectUri?.startsWith('http')) { logAuthEvent('ENVIRONMENT_ERROR', { error: 'GOOGLE_REDIRECT_URI must start with http/https' }); return false; } logAuthEvent('ENVIRONMENT_VALIDATED', { frontend_url: frontendUrl, redirect_uri: redirectUri, client_id_configured: !!process.env.GOOGLE_CLIENT_ID, client_secret_configured: !!process.env.GOOGLE_CLIENT_SECRET }); return true; } // Validate environment on module load const isEnvironmentValid = validateAuthEnvironment(); // Middleware to check authentication function requireAuth(req, res, next) { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { logAuthEvent('AUTH_FAILED', { reason: 'no_token', ip: req.ip, path: req.path, headers_present: !!req.headers.authorization }); return res.status(401).json({ error: 'No token provided' }); } const token = authHeader.substring(7); if (!token || token.length < 10) { logAuthEvent('AUTH_FAILED', { reason: 'invalid_token_format', ip: req.ip, path: req.path, token_length: token?.length || 0 }); return res.status(401).json({ error: 'Invalid token format' }); } const user = (0, simpleAuth_1.verifyToken)(token); if (!user) { logAuthEvent('AUTH_FAILED', { reason: 'token_verification_failed', ip: req.ip, path: req.path, token_prefix: token.substring(0, 10) + '...' }); return res.status(401).json({ error: 'Invalid or expired token' }); } logAuthEvent('AUTH_SUCCESS', { user_id: user.id, user_email: user.email, user_role: user.role, ip: req.ip, path: req.path }); req.user = user; next(); } catch (error) { logAuthEvent('AUTH_ERROR', { error: error instanceof Error ? error.message : 'Unknown error', ip: req.ip, path: req.path }); return res.status(500).json({ error: 'Authentication system error' }); } } // Middleware to check role function requireRole(roles) { return (req, res, next) => { const user = req.user; if (!user || !roles.includes(user.role)) { return res.status(403).json({ error: 'Insufficient permissions' }); } next(); }; } // Get current user router.get('/me', requireAuth, (req, res) => { res.json(req.user); }); // Setup status endpoint (required by frontend) router.get('/setup', async (req, res) => { try { const clientId = process.env.GOOGLE_CLIENT_ID; const clientSecret = process.env.GOOGLE_CLIENT_SECRET; const redirectUri = process.env.GOOGLE_REDIRECT_URI; const frontendUrl = process.env.FRONTEND_URL; logAuthEvent('SETUP_CHECK', { client_id_present: !!clientId, client_secret_present: !!clientSecret, redirect_uri_present: !!redirectUri, frontend_url_present: !!frontendUrl, environment_valid: isEnvironmentValid }); // Check database connectivity let userCount = 0; let databaseConnected = false; try { userCount = await databaseService_1.default.getUserCount(); databaseConnected = true; logAuthEvent('DATABASE_CHECK', { status: 'connected', user_count: userCount }); } catch (dbError) { logAuthEvent('DATABASE_ERROR', { error: dbError instanceof Error ? dbError.message : 'Unknown database error' }); return res.status(500).json({ error: 'Database connection failed', details: 'Cannot connect to PostgreSQL database' }); } const setupCompleted = !!(clientId && clientSecret && redirectUri && frontendUrl && clientId !== 'your-google-client-id-from-console' && clientId !== 'your-google-client-id' && isEnvironmentValid); const response = { setupCompleted, firstAdminCreated: userCount > 0, oauthConfigured: !!(clientId && clientSecret), databaseConnected, environmentValid: isEnvironmentValid, configuration: { google_oauth: !!(clientId && clientSecret), redirect_uri_configured: !!redirectUri, frontend_url_configured: !!frontendUrl, production_ready: setupCompleted && databaseConnected } }; logAuthEvent('SETUP_STATUS', response); res.json(response); } catch (error) { logAuthEvent('SETUP_ERROR', { error: error instanceof Error ? error.message : 'Unknown setup error' }); res.status(500).json({ error: 'Setup check failed', details: error instanceof Error ? error.message : 'Unknown error' }); } }); // Start Google OAuth flow router.get('/google', (req, res) => { try { const authUrl = (0, simpleAuth_1.getGoogleAuthUrl)(); res.redirect(authUrl); } catch (error) { console.error('Error starting Google OAuth:', error); const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:5173'; res.redirect(`${frontendUrl}?error=oauth_not_configured`); } }); // Handle Google OAuth callback (this is where Google redirects back to) router.get('/google/callback', async (req, res) => { const { code, error, state } = req.query; const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:5173'; logAuthEvent('OAUTH_CALLBACK', { has_code: !!code, has_error: !!error, error_type: error, state, frontend_url: frontendUrl, ip: req.ip, user_agent: req.get('User-Agent') }); // Validate environment before proceeding if (!isEnvironmentValid) { logAuthEvent('OAUTH_CALLBACK_ERROR', { reason: 'invalid_environment' }); return res.redirect(`${frontendUrl}?error=configuration_error&message=OAuth not properly configured`); } if (error) { logAuthEvent('OAUTH_ERROR', { error, ip: req.ip }); return res.redirect(`${frontendUrl}?error=${error}&message=OAuth authorization failed`); } if (!code) { logAuthEvent('OAUTH_ERROR', { reason: 'no_authorization_code', ip: req.ip }); return res.redirect(`${frontendUrl}?error=no_code&message=No authorization code received`); } try { logAuthEvent('OAUTH_TOKEN_EXCHANGE_START', { code_length: code.length }); // Exchange code for tokens const tokens = await (0, simpleAuth_1.exchangeCodeForTokens)(code); if (!tokens || !tokens.access_token) { logAuthEvent('OAUTH_TOKEN_EXCHANGE_FAILED', { tokens_received: !!tokens }); return res.redirect(`${frontendUrl}?error=token_exchange_failed&message=Failed to exchange authorization code`); } logAuthEvent('OAUTH_TOKEN_EXCHANGE_SUCCESS', { has_access_token: !!tokens.access_token }); // Get user info const googleUser = await (0, simpleAuth_1.getGoogleUserInfo)(tokens.access_token); if (!googleUser || !googleUser.email) { logAuthEvent('OAUTH_USER_INFO_FAILED', { user_data: !!googleUser }); return res.redirect(`${frontendUrl}?error=user_info_failed&message=Failed to get user information from Google`); } logAuthEvent('OAUTH_USER_INFO_SUCCESS', { email: googleUser.email, name: googleUser.name, verified_email: googleUser.verified_email }); // Check if user exists or create new user let user = await databaseService_1.default.getUserByEmail(googleUser.email); if (!user) { // Determine role - first user becomes admin, others need approval const approvedUserCount = await databaseService_1.default.getApprovedUserCount(); const role = approvedUserCount === 0 ? 'administrator' : 'coordinator'; logAuthEvent('USER_CREATION', { email: googleUser.email, role, is_first_user: approvedUserCount === 0 }); user = await databaseService_1.default.createUser({ id: googleUser.id, google_id: googleUser.id, email: googleUser.email, name: googleUser.name, profile_picture_url: googleUser.picture, role }); // Auto-approve first admin, others need approval if (approvedUserCount === 0) { await databaseService_1.default.updateUserApprovalStatus(googleUser.email, 'approved'); user.approval_status = 'approved'; logAuthEvent('FIRST_ADMIN_CREATED', { email: googleUser.email }); } else { logAuthEvent('USER_PENDING_APPROVAL', { email: googleUser.email }); } } else { // Update last sign in await databaseService_1.default.updateUserLastSignIn(googleUser.email); logAuthEvent('USER_LOGIN', { email: user.email, name: user.name, role: user.role, approval_status: user.approval_status }); } // Check if user is approved if (user.approval_status !== 'approved') { logAuthEvent('USER_NOT_APPROVED', { email: user.email, status: user.approval_status }); return res.redirect(`${frontendUrl}?error=pending_approval&message=Your account is pending administrator approval`); } // Generate JWT token const token = (0, simpleAuth_1.generateToken)(user); logAuthEvent('JWT_TOKEN_GENERATED', { user_id: user.id, email: user.email, role: user.role, token_length: token.length }); // Redirect to frontend with token const callbackUrl = `${frontendUrl}/auth/callback?token=${token}`; logAuthEvent('OAUTH_SUCCESS_REDIRECT', { callback_url: callbackUrl }); res.redirect(callbackUrl); } catch (error) { logAuthEvent('OAUTH_CALLBACK_ERROR', { error: error instanceof Error ? error.message : 'Unknown error', stack: error instanceof Error ? error.stack : undefined, ip: req.ip }); res.redirect(`${frontendUrl}?error=oauth_failed&message=Authentication failed due to server error`); } }); // Exchange OAuth code for JWT token (alternative endpoint for frontend) router.post('/google/exchange', async (req, res) => { const { code } = req.body; if (!code) { return res.status(400).json({ error: 'Authorization code is required' }); } try { // Exchange code for tokens const tokens = await (0, simpleAuth_1.exchangeCodeForTokens)(code); // Get user info const googleUser = await (0, simpleAuth_1.getGoogleUserInfo)(tokens.access_token); // Check if user exists or create new user let user = await databaseService_1.default.getUserByEmail(googleUser.email); if (!user) { // Determine role - first user becomes admin const userCount = await databaseService_1.default.getUserCount(); const role = userCount === 0 ? 'administrator' : 'coordinator'; user = await databaseService_1.default.createUser({ id: googleUser.id, google_id: googleUser.id, email: googleUser.email, name: googleUser.name, profile_picture_url: googleUser.picture, role }); } else { // Update last sign in await databaseService_1.default.updateUserLastSignIn(googleUser.email); console.log(`✅ User logged in: ${user.name} (${user.email})`); } // Generate JWT token const token = (0, simpleAuth_1.generateToken)(user); // Return token to frontend res.json({ token, user: { id: user.id, email: user.email, name: user.name, picture: user.profile_picture_url, role: user.role } }); } catch (error) { console.error('Error in OAuth exchange:', error); res.status(500).json({ error: 'Failed to exchange authorization code' }); } }); // Get OAuth URL for frontend to redirect to router.get('/google/url', (req, res) => { try { const authUrl = (0, simpleAuth_1.getGoogleAuthUrl)(); res.json({ url: authUrl }); } catch (error) { console.error('Error getting Google OAuth URL:', error); res.status(500).json({ error: 'OAuth not configured' }); } }); // Logout router.post('/logout', (req, res) => { // With JWT, logout is handled client-side by removing the token res.json({ message: 'Logged out successfully' }); }); // Get auth status router.get('/status', (req, res) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.json({ authenticated: false }); } const token = authHeader.substring(7); const user = (0, simpleAuth_1.verifyToken)(token); if (!user) { return res.json({ authenticated: false }); } res.json({ authenticated: true, user: { id: user.id, email: user.email, name: user.name, picture: user.profile_picture_url, role: user.role } }); }); // USER MANAGEMENT ENDPOINTS // List all users (admin only) router.get('/users', requireAuth, requireRole(['administrator']), async (req, res) => { try { const users = await databaseService_1.default.getAllUsers(); const userList = users.map(user => ({ id: user.id, email: user.email, name: user.name, picture: user.profile_picture_url, role: user.role, created_at: user.created_at, last_login: user.last_login, provider: 'google' })); res.json(userList); } catch (error) { console.error('Error fetching users:', error); res.status(500).json({ error: 'Failed to fetch users' }); } }); // Update user role (admin only) router.patch('/users/:email/role', requireAuth, requireRole(['administrator']), async (req, res) => { const { email } = req.params; const { role } = req.body; if (!['administrator', 'coordinator', 'driver'].includes(role)) { return res.status(400).json({ error: 'Invalid role' }); } try { const user = await databaseService_1.default.updateUserRole(email, role); if (!user) { return res.status(404).json({ error: 'User not found' }); } res.json({ success: true, user: { id: user.id, email: user.email, name: user.name, role: user.role } }); } catch (error) { console.error('Error updating user role:', error); res.status(500).json({ error: 'Failed to update user role' }); } }); // Delete user (admin only) router.delete('/users/:email', requireAuth, requireRole(['administrator']), async (req, res) => { const { email } = req.params; const currentUser = req.user; // Prevent admin from deleting themselves if (email === currentUser.email) { return res.status(400).json({ error: 'Cannot delete your own account' }); } try { const deletedUser = await databaseService_1.default.deleteUser(email); if (!deletedUser) { return res.status(404).json({ error: 'User not found' }); } res.json({ success: true, message: 'User deleted successfully' }); } catch (error) { console.error('Error deleting user:', error); res.status(500).json({ error: 'Failed to delete user' }); } }); // Get user by email (admin only) router.get('/users/:email', requireAuth, requireRole(['administrator']), async (req, res) => { const { email } = req.params; try { const user = await databaseService_1.default.getUserByEmail(email); if (!user) { return res.status(404).json({ error: 'User not found' }); } res.json({ id: user.id, email: user.email, name: user.name, picture: user.profile_picture_url, role: user.role, created_at: user.created_at, last_login: user.last_login, provider: 'google', approval_status: user.approval_status }); } catch (error) { console.error('Error fetching user:', error); res.status(500).json({ error: 'Failed to fetch user' }); } }); // USER APPROVAL ENDPOINTS // Get pending users (admin only) router.get('/users/pending/list', requireAuth, requireRole(['administrator']), async (req, res) => { try { const pendingUsers = await databaseService_1.default.getPendingUsers(); const userList = pendingUsers.map(user => ({ id: user.id, email: user.email, name: user.name, picture: user.profile_picture_url, role: user.role, created_at: user.created_at, provider: 'google', approval_status: user.approval_status })); res.json(userList); } catch (error) { console.error('Error fetching pending users:', error); res.status(500).json({ error: 'Failed to fetch pending users' }); } }); // Approve or deny user (admin only) router.patch('/users/:email/approval', requireAuth, requireRole(['administrator']), async (req, res) => { const { email } = req.params; const { status } = req.body; if (!['approved', 'denied'].includes(status)) { return res.status(400).json({ error: 'Invalid approval status. Must be "approved" or "denied"' }); } try { const user = await databaseService_1.default.updateUserApprovalStatus(email, status); if (!user) { return res.status(404).json({ error: 'User not found' }); } res.json({ success: true, message: `User ${status} successfully`, user: { id: user.id, email: user.email, name: user.name, role: user.role, approval_status: user.approval_status } }); } catch (error) { console.error('Error updating user approval:', error); res.status(500).json({ error: 'Failed to update user approval' }); } }); exports.default = router; //# sourceMappingURL=simpleAuth.js.map