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:
@@ -3,6 +3,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../db/database');
|
||||
const { requireEditor, canAccessAccount } = require('../middleware/auth');
|
||||
|
||||
// Helper: fetch deposit with items
|
||||
function getDepositWithItems(id) {
|
||||
@@ -18,6 +19,7 @@ function getDepositWithItems(id) {
|
||||
router.get('/', (req, res) => {
|
||||
const { account_id } = req.query;
|
||||
if (!account_id) return res.status(400).json({ error: 'account_id is required.' });
|
||||
if (!canAccessAccount(req.session, parseInt(account_id, 10))) return res.status(403).json({ error: 'Access denied.' });
|
||||
|
||||
const deposits = db.prepare(`
|
||||
SELECT d.*, COUNT(di.id) AS item_count,
|
||||
@@ -40,7 +42,7 @@ router.get('/:id', (req, res) => {
|
||||
});
|
||||
|
||||
// POST /api/deposits
|
||||
router.post('/', (req, res) => {
|
||||
router.post('/', requireEditor, (req, res) => {
|
||||
const { account_id, deposit_date, currency, coin, cash_back, items } = req.body;
|
||||
if (!account_id) return res.status(400).json({ error: 'account_id is required.' });
|
||||
if (!deposit_date) return res.status(400).json({ error: 'deposit_date is required.' });
|
||||
@@ -84,7 +86,7 @@ router.post('/', (req, res) => {
|
||||
});
|
||||
|
||||
// PUT /api/deposits/:id
|
||||
router.put('/:id', (req, res) => {
|
||||
router.put('/:id', requireEditor, (req, res) => {
|
||||
const existing = db.prepare('SELECT id FROM deposits WHERE id = ?').get(req.params.id);
|
||||
if (!existing) return res.status(404).json({ error: 'Deposit not found.' });
|
||||
|
||||
@@ -127,7 +129,7 @@ router.put('/:id', (req, res) => {
|
||||
});
|
||||
|
||||
// DELETE /api/deposits/:id
|
||||
router.delete('/:id', (req, res) => {
|
||||
router.delete('/:id', requireEditor, (req, res) => {
|
||||
const existing = db.prepare('SELECT id FROM deposits WHERE id = ?').get(req.params.id);
|
||||
if (!existing) return res.status(404).json({ error: 'Deposit not found.' });
|
||||
// deposit_items deleted via ON DELETE CASCADE
|
||||
@@ -136,7 +138,7 @@ router.delete('/:id', (req, res) => {
|
||||
});
|
||||
|
||||
// PATCH /api/deposits/:id/mark-printed
|
||||
router.patch('/:id/mark-printed', (req, res) => {
|
||||
router.patch('/:id/mark-printed', requireEditor, (req, res) => {
|
||||
db.prepare('UPDATE deposits SET printed = 1 WHERE id = ?').run(req.params.id);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user