Implement user authentication and role-based access control
Three-tier user model: admin (all accounts, all actions), editor (assigned accounts, read/write), viewer (assigned accounts, read-only). Backend: - express-session with custom SQLite session store (no extra packages) - bcryptjs for password hashing - src/middleware/auth.js: requireAuth, requireAdmin, requireEditor, canAccessAccount helpers - src/routes/auth.js: login, logout, /me, setup-needed, change-password - src/routes/users.js: full CRUD + account assignments (admin only) - All API routes protected; /api/accounts filtered by user access; write routes gated by requireEditor; admin-only routes locked down Frontend: - Login overlay (full-page) with first-run admin-setup flow - Role-based UI: admin-only elements hidden for non-admins; edit/delete and PDF buttons hidden for viewers; account switcher shows only accessible accounts for non-admins - Users modal (admin only): user list with role badges, create/edit/delete users, set account access via checkboxes - Change-password section available to all logged-in users - apiFetch redirects to login on 401
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
const db = require('../db/database');
|
||||
|
||||
function requireAuth(req, res, next) {
|
||||
if (!req.session || !req.session.userId) {
|
||||
return res.status(401).json({ error: 'Not authenticated.' });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
function requireAdmin(req, res, next) {
|
||||
if (!req.session || req.session.role !== 'admin') {
|
||||
return res.status(403).json({ error: 'Admin access required.' });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
// Blocks viewers; allows admin and editor
|
||||
function requireEditor(req, res, next) {
|
||||
if (!req.session || req.session.role === 'viewer') {
|
||||
return res.status(403).json({ error: 'Write access required.' });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
// Returns true if the current session user can access the given account
|
||||
function canAccessAccount(session, accountId) {
|
||||
if (!session || !session.userId) return false;
|
||||
if (session.role === 'admin') return true;
|
||||
const row = db.prepare(
|
||||
'SELECT 1 FROM user_accounts WHERE user_id = ? AND account_id = ?'
|
||||
).get(session.userId, accountId);
|
||||
return !!row;
|
||||
}
|
||||
|
||||
// Middleware factory — resolves accountId via a callback on req, then checks access
|
||||
function requireAccountAccess(getAccountId) {
|
||||
return (req, res, next) => {
|
||||
if (!req.session || !req.session.userId) {
|
||||
return res.status(401).json({ error: 'Not authenticated.' });
|
||||
}
|
||||
if (req.session.role === 'admin') return next();
|
||||
const accountId = parseInt(getAccountId(req), 10);
|
||||
if (!accountId) return next(); // route handler will deal with missing param
|
||||
if (!canAccessAccount(req.session, accountId)) {
|
||||
return res.status(403).json({ error: 'Access denied.' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { requireAuth, requireAdmin, requireEditor, requireAccountAccess, canAccessAccount };
|
||||
Reference in New Issue
Block a user