diff --git a/webapp/src/routes/setup.js b/webapp/src/routes/setup.js index 5c111d7..d5bc4cb 100644 --- a/webapp/src/routes/setup.js +++ b/webapp/src/routes/setup.js @@ -2,32 +2,54 @@ const express = require('express'); const router = express.Router(); const bcrypt = require('bcryptjs'); const supabase = require('../services/supabase'); +const { asyncHandler } = require('../middleware/security'); + +// GET /setup — show wizard if no admin exists +router.get('/', asyncHandler(async (req, res) => { + const { count } = await supabase + .from('portal_users') + .select('*', { count: 'exact', head: true }) + .eq('role', 'admin'); + + if (count > 0) return res.redirect('/login'); -// GET /setup – show wizard if no admin exists -router.get('/', async (req, res) => { - const { count } = await supabase.from('portal_users').select('*', { count: 'exact', head: true }).eq('role', 'admin'); - if (count > 0) return res.redirect('/login'); // admin already exists res.render('pages/setup', { error: null }); -}); +})); -// POST /setup – create first admin securely -router.post('/', async (req, res) => { +// POST /setup — create first admin securely (race-condition safe) +router.post('/', asyncHandler(async (req, res) => { const { username, password } = req.body; - if (!username || !password) return res.render('pages/setup', { error: 'All fields are required' }); + if (!username || !password) { + return res.render('pages/setup', { error: 'Username and password are required' }); + } + if (password.length < 6) { + return res.render('pages/setup', { error: 'Password must be at least 6 characters' }); + } - // ensure admin does not already exist (race‑condition safety) - const { data: existing } = await supabase.from('portal_users').select('id').eq('role', 'admin').single(); - if (existing) return res.render('pages/setup', { error: 'Admin already configured' }); + // Race-condition safety: double-check no admin exists + const { data: existing } = await supabase + .from('portal_users') + .select('id') + .eq('role', 'admin') + .single(); + + if (existing) { + return res.render('pages/setup', { error: 'Admin already configured' }); + } const hash = await bcrypt.hash(password, 12); - await supabase.from('portal_users').insert({ + const { error } = await supabase.from('portal_users').insert({ username, password_hash: hash, role: 'admin', is_active: true, }); - // redirect to login after creation + + if (error) { + return res.render('pages/setup', { error: 'Failed to create admin: ' + error.message }); + } + res.redirect('/login'); -}); +})); module.exports = router; diff --git a/webapp/src/server.js b/webapp/src/server.js index beff82f..394837f 100644 --- a/webapp/src/server.js +++ b/webapp/src/server.js @@ -154,48 +154,6 @@ app.get('/logout', (req, res) => { res.redirect('/login'); }); -app.get('/setup', asyncHandler(async (req, res) => { - // Check if any user exists - const { count } = await supabase - .from('portal_users') - .select('*', { count: 'exact', head: true }); - - if (count > 0) { - return res.redirect('/login'); - } - - res.render('pages/setup', { error: null }); -})); - -app.post('/setup', asyncHandler(async (req, res) => { - const { count } = await supabase - .from('portal_users') - .select('*', { count: 'exact', head: true }); - - if (count > 0) { - return res.redirect('/login'); - } - - const { username, password } = req.body; - if (!username || !password || password.length < 6) { - return res.render('pages/setup', { error: 'Username required and password must be at least 6 characters' }); - } - - const hash = await bcrypt.hash(password, 10); - const { error } = await supabase.from('portal_users').insert({ - username, - password_hash: hash, - role: 'admin', - is_active: true, - }); - - if (error) { - return res.render('pages/setup', { error: 'Failed to create admin. ' + error.message }); - } - - res.redirect('/login'); -})); - // ============================================================ // API ROUTES (for React dashboard + WhatsApp parser) // ============================================================ @@ -240,6 +198,7 @@ app.get('/api/stats', requireAuth, asyncHandler(async (req, res) => { // ============================================================ app.use('/', require('./routes/dashboard')); +app.use('/setup', require('./routes/setup')); app.use('/loads', require('./routes/loads')); app.use('/shippers', require('./routes/shippers')); app.use('/vehicles', require('./routes/vehicles'));