docs/decisions/0002-backend-authoritative-security-and-tax.md
Rama main · réplica de lectura del repositorio original.
# 0002: Security, payment, and tax calculations are backend-authoritative
Status: Accepted
Recorded: 2026-09-25
The decision this record describes predates the record. It is written down here because the
code does not explain itself: nothing in the source tells a reader that the alternative was
considered and rejected, or what a re-introduction would cost.
## Context
FloCafe is an Electron application: a React renderer, a Node backend, and a local SQLite database.
A renderer-side implementation of an authorization check or a tax calculation is technically
straightforward and looks reasonable in isolation. It also fails open the moment someone opens the
developer tools, points a modified client at the API, or runs the frontend against a different
backend.
The risk is not that someone would deliberately bypass a renderer check. It is that a
renderer-side check reads as correct in review, so it survives long enough to be depended on.
## Decision
Security-critical, payment, and tax calculations are decided in the backend. The renderer never
decides.
- Authorization is enforced at the route, in the backend, by resolving each request's effective
permissions from the database. The authorization surface is roughly 240 `requirePermission(`/
`requireAnyPermission(` call sites across `main/routes/*.ts`, plus checks inside transaction
bodies on the endpoints registered inline on `app`, which no middleware can see. See
[authentication and authorization](../architecture/authentication-and-authorization.md).
- Tax calculation has exactly one path: `TaxEngine.calculate()` in
`main/services/tax-engine.ts`. `previewCategoryRate()` in `main/services/tax.ts` is a
display-only preview for UI category pickers, and the code says so at the definition.
- Amounts that affect money use `decimal.js`, never a JavaScript `number`.
## Consequences
Positive:
- A modified or third-party client gets the same answers as the app.
- The authorization and tax rules are testable without a browser.
Negative:
- Any UI that needs to know "can this user do X" must ask the backend or duplicate the rule for
display purposes. The in-app capability matrix is a presentation aid, not an enforcement
mechanism, and is documented as such in
[roles and permissions](../reference/roles-and-permissions.md).
- The route-level authorization surface is large and expressed inline, which makes auditing it a
matter of review discipline rather than a single check.
## Alternatives rejected
**Sharing the authorization and tax logic through a common package imported by both the renderer
and the backend.** Rejected: a shared module is still advisory when it runs in the renderer, and it
creates the appearance of a single enforcement point where none exists.
**Declaring the renderer untrusted and validating at the edge only.** Rejected for authorization:
several gates legitimately re-read state inside a transaction, such as the KDS station gate, and an
edge check cannot see that.