fix(flags): close CRM/accounting feature-gating gaps from the audit
A sweep of every CRM/accounting toggle found surfaces still reachable
with their flag OFF. Adds a shared requireFeatureFlag middleware (the two
existing per-file copies predate it) and closes the gaps:
- Hours logging: only createEntry checked the flag — edit/delete/bill and
the list/summary routes were permission-only. Gate all six
/hour-entries routes on the hoursLogging master so a disabled feature
can't be read, mutated, or invoiced via a direct API hit.
- Installment plans: PUT /deals/:uuid/installment-plan mutates invoices
but wasn't bills-gated; add requireFeatureFlag('bills').
- Customer invoice PDF: /invoices/:id/pdf lacked the feature_bills check
the list + quotes routes have. Also fixes the quotes-PDF gate, which
read req.customer.feature_quotes (never populated → silent no-op).
- Customer contracts: /contracts + /contracts/:id/pdf were gated by
neither the master nor a per-customer column.
Per-customer contracts override (the missing counterpart):
- Migration 131 adds customer_accounts.feature_contracts, default TRUE so
existing customers keep their Contracts tab (preserve-visuals).
- Effective resolver now contractsMaster AND feature_contracts; admin
detail page gains the toggle; service/validator/serializer wired.
Cleanups:
- Drop stale `taxReport` from the sidebar's Clients-reveal list (Tax moved
to Accounting); add the missing `projects` so it mirrors the context
derivation.
- SettingsPage tab-snap effect now depends on flags.accounting.
- Fix stale taxReport "forced off when bills off" comment (it's accounting).
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* requireFeatureFlag(key, code?) — 403 when the named `feature_flags` row is off.
|
||||
*
|
||||
* Belt-and-braces gate for admin routes whose feature can be toggled in
|
||||
* Settings → Features. The frontend hides disabled surfaces, but a direct API
|
||||
* hit must still be refused so a disabled feature is never actable. Mirrors the
|
||||
* truthy logic feature_flags uses everywhere (true | 1 | '1').
|
||||
*
|
||||
* Several route files (adminLedger, adminExpenses) predate this and define an
|
||||
* identical local `requireFlag`; new gates should import this instead.
|
||||
*/
|
||||
const { db } = require('../database/db');
|
||||
|
||||
function requireFeatureFlag(key, code) {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const row = await db('feature_flags').where({ key }).first();
|
||||
const enabled = row && (row.value === true || row.value === 1 || row.value === '1');
|
||||
if (!enabled) {
|
||||
return res.status(403).json({
|
||||
error: `${key} feature is disabled`,
|
||||
code: code || `${key.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase()}_DISABLED`,
|
||||
});
|
||||
}
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { requireFeatureFlag };
|
||||
Reference in New Issue
Block a user