Compare commits
3 commits
8ae3b403ab
...
071f759b8a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
071f759b8a | ||
|
|
0da63ae676 | ||
|
|
f1c75faba1 |
3 changed files with 57 additions and 43 deletions
55
webapp/src/routes/setup.js
Normal file
55
webapp/src/routes/setup.js
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
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');
|
||||||
|
|
||||||
|
res.render('pages/setup', { error: null });
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 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: 'Username and password are required' });
|
||||||
|
}
|
||||||
|
if (password.length < 6) {
|
||||||
|
return res.render('pages/setup', { error: 'Password must be at least 6 characters' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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');
|
||||||
|
}));
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -154,48 +154,6 @@ app.get('/logout', (req, res) => {
|
||||||
res.redirect('/login');
|
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)
|
// 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('/', require('./routes/dashboard'));
|
||||||
|
app.use('/setup', require('./routes/setup'));
|
||||||
app.use('/loads', require('./routes/loads'));
|
app.use('/loads', require('./routes/loads'));
|
||||||
app.use('/shippers', require('./routes/shippers'));
|
app.use('/shippers', require('./routes/shippers'));
|
||||||
app.use('/vehicles', require('./routes/vehicles'));
|
app.use('/vehicles', require('./routes/vehicles'));
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/js/app.js"></script>
|
<script src="/js/app.js?v=<%= typeof assetVersion !== 'undefined' ? assetVersion : '1' %>"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue