feat[agent]: add admin setup wizard (first-time admin creation) with secure password handling
This commit is contained in:
parent
02d2374ae5
commit
f1c75faba1
2 changed files with 68 additions and 39 deletions
33
webapp/src/routes/setup.js
Normal file
33
webapp/src/routes/setup.js
Normal 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 (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' });
|
||||
|
||||
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;
|
||||
|
|
@ -1,46 +1,42 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="light">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Setup — <%= appName %></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 rel="stylesheet" href="/css/style.css">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FreightDesk | Admin Setup</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<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>
|
||||
<body class="auth-page">
|
||||
<div class="login-page">
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<div class="login-emblem">🌐</div>
|
||||
<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>
|
||||
<body>
|
||||
<div class="setup-card card p-4">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="mb-3">Welcome to FreightDesk</h3>
|
||||
<p class="text-muted mb-4">No administrator account found. Please create your first admin account to get started.</p>
|
||||
|
||||
<% if (typeof error !== 'undefined' && error) { %>
|
||||
<div class="alert alert-danger py-2 mb-3" role="alert">
|
||||
<%= error %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (typeof error !== 'undefined' && error) { %>
|
||||
<div class="alert alert-error"><%= error %></div>
|
||||
<% } %>
|
||||
|
||||
<form method="POST" action="/setup" class="login-form">
|
||||
<input type="hidden" name="_csrf" value="<%= _csrf %>">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Admin Username</label>
|
||||
<input type="text" name="username" class="form-input" required autofocus placeholder="Choose a username" minlength="3">
|
||||
<form action="/setup" method="POST" class="text-start">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Admin Username</label>
|
||||
<input type="text" name="username" class="form-control" placeholder="e.g. admin_dispatcher" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Admin Password</label>
|
||||
<input type="password" name="password" class="form-control" placeholder="Enter a strong password" required>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary py-2">Create Admin Account</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Admin Password</label>
|
||||
<input type="password" name="password" class="form-input" required placeholder="Choose a strong password" minlength="6">
|
||||
<p class="text-muted" style="font-size:12px;margin-top:4px;">Minimum 6 characters</p>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">Create Admin Account</button>
|
||||
</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>
|
||||
<script src="/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Loading…
Reference in a new issue