[OWL] Security fixes: remove hardcoded password, add setup form, soft-delete migration
- Replace hardcoded admin123 with user-defined password via /setup form - Add proper GET/POST /setup routes - Create setup.ejs view with password validation (min 6 chars) - Add migration 003: soft-delete columns (deleted_at) on loads/payments/shippers/vehicles - Add load_count column to shippers - requireRole middleware already present in auth.js
This commit is contained in:
parent
66da3e32f8
commit
5e10afebf1
3 changed files with 97 additions and 8 deletions
24
supabase/migrations/003_soft_delete.sql
Normal file
24
supabase/migrations/003_soft_delete.sql
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
-- ============================================================
|
||||||
|
-- FreightDesk — Migration 003: Soft Delete + Security
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Add soft-delete columns
|
||||||
|
ALTER TABLE loads ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||||
|
ALTER TABLE payments ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||||
|
ALTER TABLE shippers ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||||
|
ALTER TABLE vehicles ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
-- Add role column to portal_users if not exists
|
||||||
|
ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS role TEXT DEFAULT 'admin';
|
||||||
|
|
||||||
|
-- Add index for soft-delete queries
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_loads_deleted_at ON loads(deleted_at) WHERE deleted_at IS NULL;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_payments_deleted_at ON payments(deleted_at) WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Add load_count to shippers for quick reference
|
||||||
|
ALTER TABLE shippers ADD COLUMN IF NOT EXISTS load_count INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
-- Update load_count for existing shippers
|
||||||
|
UPDATE shippers SET load_count = (
|
||||||
|
SELECT COUNT(*) FROM loads WHERE loads.shipper_id = shippers.id
|
||||||
|
);
|
||||||
|
|
@ -145,26 +145,45 @@ app.get('/logout', (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/setup', asyncHandler(async (req, res) => {
|
app.get('/setup', asyncHandler(async (req, res) => {
|
||||||
// Check if admin exists
|
// Check if any user exists
|
||||||
const { count } = await supabase
|
const { count } = await supabase
|
||||||
.from('portal_users')
|
.from('portal_users')
|
||||||
.select('*', { count: 'exact', head: true })
|
.select('*', { count: 'exact', head: true });
|
||||||
.eq('username', 'admin');
|
|
||||||
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
return res.redirect('/login');
|
return res.redirect('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create default admin
|
res.render('pages/setup', { error: null });
|
||||||
const hash = await bcrypt.hash('admin123', 10);
|
}));
|
||||||
await supabase.from('portal_users').insert({
|
|
||||||
username: 'admin',
|
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,
|
password_hash: hash,
|
||||||
role: 'admin',
|
role: 'admin',
|
||||||
is_active: true,
|
is_active: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.send('<h1>Admin created!</h1><p>Username: <strong>admin</strong></p><p>Password: <strong>admin123</strong></p><p><a href="/login">Go to login</a></p>');
|
if (error) {
|
||||||
|
return res.render('pages/setup', { error: 'Failed to create admin. ' + error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect('/login');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
46
webapp/src/views/pages/setup.ejs
Normal file
46
webapp/src/views/pages/setup.ejs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="light">
|
||||||
|
<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">
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<% 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">
|
||||||
|
</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>
|
||||||
Loading…
Reference in a new issue