security: rename LIVE constant to SQL_NOT_DELETED with injection safety documentation
This commit is contained in:
parent
ff7ae8b3ab
commit
b81b41d302
|
|
@ -6,7 +6,11 @@ const { computeBalanceDelta } = require('../services/billsService');
|
|||
const { validatePaymentInput } = require('../services/paymentValidation');
|
||||
const { getCycleRange, resolveDueDate } = require('../services/statusService');
|
||||
|
||||
const LIVE = 'deleted_at IS NULL'; // filter for non-deleted payments
|
||||
// SQL_NOT_DELETED is a compile-time constant SQL fragment, never user-supplied.
|
||||
// It cannot be a bind parameter (SQL fragments are not parameterisable — only
|
||||
// values are). Interpolating a hardcoded constant like this is safe by
|
||||
// construction; do NOT replace this pattern with dynamic/user-controlled input.
|
||||
const SQL_NOT_DELETED = 'deleted_at IS NULL';
|
||||
const TRANSACTION_MATCH_SOURCE = 'transaction_match';
|
||||
|
||||
function isTransactionLinkedPayment(payment) {
|
||||
|
|
@ -83,7 +87,7 @@ router.get('/', (req, res) => {
|
|||
}
|
||||
}
|
||||
|
||||
let query = `SELECT p.* FROM payments p JOIN bills b ON b.id = p.bill_id WHERE p.${LIVE} AND b.user_id = ? AND b.deleted_at IS NULL`;
|
||||
let query = `SELECT p.* FROM payments p JOIN bills b ON b.id = p.bill_id WHERE p.${SQL_NOT_DELETED} AND b.user_id = ? AND b.deleted_at IS NULL`;
|
||||
const params = [req.user.id];
|
||||
|
||||
if (bill_id) { query += ' AND p.bill_id = ?'; params.push(parseInt(bill_id, 10)); }
|
||||
|
|
@ -104,7 +108,7 @@ router.get('/', (req, res) => {
|
|||
// GET /api/payments/:id
|
||||
router.get('/:id', (req, res) => {
|
||||
const db = getDb();
|
||||
const payment = db.prepare(`SELECT p.* FROM payments p JOIN bills b ON b.id = p.bill_id WHERE p.id = ? AND p.${LIVE} AND b.user_id = ? AND b.deleted_at IS NULL`).get(req.params.id, req.user.id);
|
||||
const payment = db.prepare(`SELECT p.* FROM payments p JOIN bills b ON b.id = p.bill_id WHERE p.id = ? AND p.${SQL_NOT_DELETED} AND b.user_id = ? AND b.deleted_at IS NULL`).get(req.params.id, req.user.id);
|
||||
if (!payment) return res.status(404).json(standardizeError('Payment not found', 'NOT_FOUND', 'id'));
|
||||
res.json(payment);
|
||||
});
|
||||
|
|
@ -319,7 +323,7 @@ router.post('/bulk', (req, res) => {
|
|||
AND p.bill_id = ?
|
||||
AND p.paid_date = ?
|
||||
AND p.amount = ?
|
||||
AND p.${LIVE}`
|
||||
AND p.${SQL_NOT_DELETED}`
|
||||
);
|
||||
|
||||
const created = [];
|
||||
|
|
@ -360,7 +364,7 @@ router.post('/bulk', (req, res) => {
|
|||
// PUT /api/payments/:id
|
||||
router.put('/:id', (req, res) => {
|
||||
const db = getDb();
|
||||
const existing = db.prepare(`SELECT p.* FROM payments p JOIN bills b ON b.id = p.bill_id WHERE p.id = ? AND p.${LIVE} AND b.user_id = ? AND b.deleted_at IS NULL`).get(req.params.id, req.user.id);
|
||||
const existing = db.prepare(`SELECT p.* FROM payments p JOIN bills b ON b.id = p.bill_id WHERE p.id = ? AND p.${SQL_NOT_DELETED} AND b.user_id = ? AND b.deleted_at IS NULL`).get(req.params.id, req.user.id);
|
||||
if (!existing) return res.status(404).json(standardizeError('Payment not found', 'NOT_FOUND', 'id'));
|
||||
if (isTransactionLinkedPayment(existing)) return rejectTransactionLinkedPayment(res);
|
||||
|
||||
|
|
@ -413,13 +417,13 @@ router.put('/:id', (req, res) => {
|
|||
req.user.id,
|
||||
);
|
||||
|
||||
res.json(db.prepare(`SELECT p.* FROM payments p JOIN bills b ON b.id = p.bill_id WHERE p.id = ? AND p.${LIVE} AND b.user_id = ? AND b.deleted_at IS NULL`).get(req.params.id, req.user.id));
|
||||
res.json(db.prepare(`SELECT p.* FROM payments p JOIN bills b ON b.id = p.bill_id WHERE p.id = ? AND p.${SQL_NOT_DELETED} AND b.user_id = ? AND b.deleted_at IS NULL`).get(req.params.id, req.user.id));
|
||||
});
|
||||
|
||||
// DELETE /api/payments/:id — soft delete (sets deleted_at)
|
||||
router.delete('/:id', (req, res) => {
|
||||
const db = getDb();
|
||||
const payment = db.prepare(`SELECT p.* FROM payments p JOIN bills b ON b.id = p.bill_id WHERE p.id = ? AND p.${LIVE} AND b.user_id = ? AND b.deleted_at IS NULL`).get(req.params.id, req.user.id);
|
||||
const payment = db.prepare(`SELECT p.* FROM payments p JOIN bills b ON b.id = p.bill_id WHERE p.id = ? AND p.${SQL_NOT_DELETED} AND b.user_id = ? AND b.deleted_at IS NULL`).get(req.params.id, req.user.id);
|
||||
if (!payment) return res.status(404).json(standardizeError('Payment not found', 'NOT_FOUND', 'id'));
|
||||
if (isTransactionLinkedPayment(payment)) return rejectTransactionLinkedPayment(res);
|
||||
|
||||
|
|
@ -453,7 +457,7 @@ router.post('/:id/restore', (req, res) => {
|
|||
}
|
||||
|
||||
db.prepare('UPDATE payments SET deleted_at = NULL WHERE id = ? AND bill_id IN (SELECT id FROM bills WHERE user_id = ? AND deleted_at IS NULL)').run(req.params.id, req.user.id);
|
||||
res.json(db.prepare(`SELECT p.* FROM payments p JOIN bills b ON b.id = p.bill_id WHERE p.id = ? AND p.${LIVE} AND b.user_id = ? AND b.deleted_at IS NULL`).get(req.params.id, req.user.id));
|
||||
res.json(db.prepare(`SELECT p.* FROM payments p JOIN bills b ON b.id = p.bill_id WHERE p.id = ? AND p.${SQL_NOT_DELETED} AND b.user_id = ? AND b.deleted_at IS NULL`).get(req.params.id, req.user.id));
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
Loading…
Reference in New Issue