Enforce granular capabilities across cashup
Backend (server-side enforcement, not just UI): - auth.js: read caps from JWT; hasCap() + requireCap() helpers; legacy-token fallback (full access minus settings) so existing sessions keep working until re-login - finalise: submit final, delete draft, bulk-finalise, attachments - reports: multiday report, cash summary, debtors - floats: float management + safe count - settings: settings mutations (was is_admin) - count: draft save, newbook fetch Frontend: - can(user, cap) helper; User.caps from /verify - Nav items, routes and actions (Submit Final, delete, bulk-finalise) gated on capabilities; non-finalisers see a draft-only hint Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
43c332be9b
commit
be670f724d
13 changed files with 117 additions and 52 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { pool } from '../db.js'
|
||||
import { requireAuth } from '../auth.js'
|
||||
import { requireAuth, hasCap } from '../auth.js'
|
||||
|
||||
export async function cashupRoutes(app) {
|
||||
app.addHook('preHandler', requireAuth)
|
||||
|
|
@ -38,6 +38,12 @@ export async function cashupRoutes(app) {
|
|||
if (!session_date) return reply.status(400).send({ error: 'session_date required' })
|
||||
if (!['draft', 'final'].includes(status)) return reply.status(400).send({ error: 'invalid status' })
|
||||
|
||||
// Draft save needs 'count'; finalising needs 'finalise'.
|
||||
if (!hasCap(req, 'count')) return reply.status(403).send({ error: 'Missing capability: count' })
|
||||
if (status === 'final' && !hasCap(req, 'finalise')) {
|
||||
return reply.status(403).send({ error: 'Missing capability: finalise' })
|
||||
}
|
||||
|
||||
let totalFloat = 0, totalCash = 0
|
||||
for (const d of denominations) {
|
||||
if (d.count_type === 'float') totalFloat += parseFloat(d.total_amount || 0)
|
||||
|
|
@ -100,8 +106,9 @@ export async function cashupRoutes(app) {
|
|||
return { message: status === 'final' ? 'Cash up submitted.' : 'Saved as draft.', cash_up_id: cashUpId }
|
||||
})
|
||||
|
||||
// DELETE /api/cashup/:id
|
||||
// DELETE /api/cashup/:id (requires cashup:finalise)
|
||||
app.delete('/api/cashup/:id', async (req, reply) => {
|
||||
if (!hasCap(req, 'finalise')) return reply.status(403).send({ error: 'Missing capability: finalise' })
|
||||
const { rows } = await pool.query('SELECT status FROM cash_ups WHERE id = $1', [req.params.id])
|
||||
if (!rows.length) return reply.status(404).send({ error: 'Not found' })
|
||||
if (rows[0].status === 'final') return reply.status(409).send({ error: 'Cannot delete a finalised cash up' })
|
||||
|
|
@ -109,8 +116,9 @@ export async function cashupRoutes(app) {
|
|||
return { message: 'Deleted' }
|
||||
})
|
||||
|
||||
// POST /api/cashup/bulk-finalize
|
||||
// POST /api/cashup/bulk-finalize (requires cashup:finalise)
|
||||
app.post('/api/cashup/bulk-finalize', async (req, reply) => {
|
||||
if (!hasCap(req, 'finalise')) return reply.status(403).send({ error: 'Missing capability: finalise' })
|
||||
const { ids } = req.body
|
||||
if (!Array.isArray(ids) || !ids.length) return reply.status(400).send({ error: 'ids required' })
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue