Auth + input-validation cluster (CODE-REVIEW-2026-06 #5, #6, #16, #17
+ libra-#36, libra-#51, libra-#52):
- can_access_user_data compares full user ids only. The 8-char prefix
comparison was a 32-bit space: any prefix collision (or a crafted
short target id) let one user read another's data.
- can_access_account matches the User-{short} SEGMENT exactly; the
substring test also matched accounts merely containing it
(Expenses:Misc-User-deadbeef).
- Manual-payment approve/reject are status-guarded
(UPDATE ... WHERE status='pending' + rowcount): concurrent admins
can't double-book. The approve endpoint claims the request BEFORE
writing the ledger entry and reverts the claim if the write fails,
so at most one journal entry can exist per request.
- Account-name validation centralized into
account_utils.validate_account_name (libra-#51) — called from
crud.create_account (the choke point for every creation path,
virtual parents allowed a bare root), the admin add-account
endpoint, and fava_client.add_account at the writer boundary
(libra-#52).
- crud.create_account translates backend unique-violations into
AccountExistsError instead of leaking sqlalchemy internals
(libra-#36); POST /accounts returns 409 on duplicates and 400 on
malformed names. get_or_create_user_account catches the domain
error instead of string-matching the SQLite message (which never
matched on Postgres).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>