feat[agent]: add admin setup wizard (first-time admin creation) with secure password handling

This commit is contained in:
Hermes Agent 2026-06-07 19:46:01 +00:00
parent 5e10afebf1
commit 151606d436
2 changed files with 68 additions and 39 deletions

View file

@ -0,0 +1,33 @@
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const supabase = require('../services/supabase');
// 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) => {
const { username, password } = req.body;
if (!username || !password) return res.render('pages/setup', { error: 'All fields are required' });
// ensure admin does not already exist (racecondition 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' });
const hash = await bcrypt.hash(password, 12);
await supabase.from('portal_users').insert({
username,
password_hash: hash,
role: 'admin',
is_active: true,
});
// redirect to login after creation
res.redirect('/login');
});
module.exports = router;

View file

@ -1,46 +1,42 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" data-theme="light"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Setup — <%= appName %></title> <title>FreightDesk | Admin Setup</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Devanagari:wght@400;600;700&family=Inter:wght@400;600;700&display=swap" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/css/style.css"> <style>
body { background: #f8f9fa; height: 100vh; display: flex; align-items: center; justify-content: center; }
.setup-card { width: 100%; max-width: 450px; box-shadow: 0 10px 25px rgba(0,0,0,0.1); border: none; border-radius: 15px; }
.btn-primary { background: #0d6efd; border: none; border-radius: 8px; }
</style>
</head> </head>
<body class="auth-page"> <body>
<div class="login-page"> <div class="setup-card card p-4">
<div class="login-container"> <div class="card-body text-center">
<div class="login-header"> <h3 class="mb-3">Welcome to FreightDesk</h3>
<div class="login-emblem">&#127760;</div> <p class="text-muted mb-4">No administrator account found. Please create your first admin account to get started.</p>
<h1 class="login-title-hi"><%= appNameHi %></h1>
<h2 class="login-title-en"><%= appName %> — Initial Setup</h2>
<p class="login-tagline">Create your admin account to get started</p>
</div>
<% if (typeof error !== 'undefined' && error) { %> <% if (typeof error !== 'undefined' && error) { %>
<div class="alert alert-error"><%= error %></div> <div class="alert alert-danger py-2 mb-3" role="alert">
<%= error %>
</div>
<% } %> <% } %>
<form method="POST" action="/setup" class="login-form"> <form action="/setup" method="POST" class="text-start">
<input type="hidden" name="_csrf" value="<%= _csrf %>"> <div class="mb-3">
<div class="form-group">
<label class="form-label">Admin Username</label> <label class="form-label">Admin Username</label>
<input type="text" name="username" class="form-input" required autofocus placeholder="Choose a username" minlength="3"> <input type="text" name="username" class="form-control" placeholder="e.g. admin_dispatcher" required>
</div> </div>
<div class="form-group"> <div class="mb-3">
<label class="form-label">Admin Password</label> <label class="form-label">Admin Password</label>
<input type="password" name="password" class="form-input" required placeholder="Choose a strong password" minlength="6"> <input type="password" name="password" class="form-control" placeholder="Enter a strong password" required>
<p class="text-muted" style="font-size:12px;margin-top:4px;">Minimum 6 characters</p> </div>
<div class="d-grid">
<button type="submit" class="btn btn-primary py-2">Create Admin Account</button>
</div> </div>
<button type="submit" class="btn btn-primary btn-block">Create Admin Account</button>
</form> </form>
<div class="login-footer">
<div class="footer-tricolor"><span></span><span></span><span></span></div>
<p>Secured by Government of India</p>
</div> </div>
</div> </div>
</div>
<script src="/js/app.js"></script>
</body> </body>
</html> </html>