fix(auth): migrate the six billing controllers onto @RequirePermissions() - #361
Closed
modernitconsultants wants to merge 1 commit into
Closed
modernitconsultants wants to merge 1 commit into
modernitconsultants wants to merge 1 commit into
Conversation
…ns() TelivityAI#340 migrated eighteen controllers off the legacy @roles() decorator and deliberately left six behind: accounting, cashier, folio, house-account, notifications and payment. Those six gated every route -- read and write alike -- with one identical realm-role list, so no existing permission key matched it without either widening or narrowing who can record a payment or post a charge. This finishes the migration on the rule of preserving what the realm-role gate was granting in practice, rather than narrowing it silently: - Six controllers move onto @RequirePermissions(), reusing existing keys where they exist (folios.manage, houseaccounts.manage, cashier.access, communications.manage) and adding one new key, accounting.manage, for the deposit / A-R / accounting-code routes, which had none. - ROLE_DEFAULT_PERMISSIONS gains the keys four system roles were already being granted by the old list: front_desk, night_auditor, accounting and reservations. In particular `reservations` was in the allow-list of every billing controller, so its doc comment ("reservations without cashier / folio posting") described a split the code never enforced. - Migration 0023_billing_write_access_grants.sql backfills those grants for properties that already have role_permissions rows, using the same every-property-with-RBAC cross-join as 0015. A property with no RBAC rows gets nothing, by design. - payment-legacy-seam.spec.ts asserted PaymentController's legacy @roles() metadata; it now asserts the permission key, updated in the same commit that changes the controller. - billing-role-controllers-migration.spec.ts covers all six, asserting both that no @roles() metadata is left behind and that the expected key is set. One side effect worth disclosing rather than burying: communications.manage is already held by revenue_manager for an unrelated existing route in groups.controller.ts, so reusing that key on notifications.controller.ts also lets revenue_manager send guest SMS/WhatsApp/Telegram. Reusing the existing key seemed better than inventing a near-duplicate, but it is easy to split if you would rather. README.md and docs/test-stats.json updated for the 15 added test cases and one added file.
|
Hey Charles — thank you again for #361. Really solid work on finishing the billing controller migration, the honest widenings, and the clear write-up. I took your branch as the base for #362 rather than coming back with another round of small change requests on this PR — didn’t want to keep pinging you for follow-ups. Full credit is yours; 362 only adds a Sorry for the extra PR instead of iterating here — appreciated everything you put into this. |
telivity-otaip
pushed a commit
that referenced
this pull request
Sep 8, 2026
Builds on #361 (Charles / modernitconsultants): billing controllers onto @RequirePermissions, plus a payments.refund key so night_auditor can post overnight without void/refund/correct or deposit refund. Seed now reads ROLE_DEFAULT_PERMISSIONS from @telivityhaip/shared/permissions-catalog so demo RBAC cannot drift from the API catalog. Co-authored-by: ModernIT Consultants <charles@modernit.com.au>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Finishes the migration #340 started. That PR moved eighteen controllers off the legacy
@Roles()decorator and deliberately left six behind — accounting, cashier, folio, house-account, notifications, payment — because they gated every route, read and write alike, with one identical realm-role list (admin, general_manager, front_desk, reservations, night_auditor, accounting), and no existing permission key matched that list without either widening or narrowing who can record a payment or post a charge.The rule applied
Preserve what the realm-role gate was granting in practice, rather than narrow it silently. Concretely,
reservationswas already in the allow-list of every one of these six controllers, so the doc comment on that role — "reservations without cashier / folio posting" — described a split the code has never actually enforced. Only the newer permission catalog enforced it, and only by omission. Same fornight_auditorandaccounting, which were reachable through the old gate but hold none of the corresponding keys.What's in it
@RequirePermissions(), reusing existing keys (folios.manage,houseaccounts.manage,cashier.access,communications.manage) and adding one new key,accounting.manage, for the deposit / A-R / accounting-code routes, which had no key at all.ROLE_DEFAULT_PERMISSIONSgains the keysfront_desk,night_auditor,accountingandreservationswere already being granted by the old list.adminandgeneral_managerneed nothing — they pick up a new key automatically viaALL_PERMISSIONS.0023_billing_write_access_grants.sqlbackfills those grants for properties that already haverole_permissionsrows, using the same every-property-with-RBAC cross-join as0015. A property with no RBAC rows gets nothing, by design.payment-legacy-seam.spec.tsassertedPaymentController's legacy@Roles()metadata. It now asserts the permission key — updated in the same commit that changes the controller, not separately.billing-role-controllers-migration.spec.tscovers all six: asserts no@Roles()metadata is left behind and that the expected key is set. 13 cases.One side effect, disclosed rather than buried
communications.manageis already held byrevenue_managerfor an unrelated existing route ingroups.controller.ts. Reusing that key onnotifications.controller.tstherefore also letsrevenue_managersend guest SMS/WhatsApp/Telegram, which the old gate did not. Reusing the existing key seemed better than inventing a near-duplicate — happy to split it into its own key if you'd rather.Validation, and one honest gap
Run against your
ci.ymlon this branch:pnpm lint(0 errors),pnpm typecheck(clean), the dependency builds, and the affected specs —billing-role-controllers-migration(13),permissions.catalog(12),payment-legacy-seam(4) and your ownlegacy-role-controllers-migration(31) — 60 passing.I could not run the full suite: the build host has no Postgres or Redis, so the integration specs can't execute and
scripts/sync-test-count.mjscan't produce a real count. Rather than leave the counts stale and have your--checkcatch it (as happened on #354, which you kindly fixed rather than bounced), I derived them: baseline 2222/266,permissions.catalog.spec.ts10 → 12,payment-legacy-seam.spec.ts4 → 4, plus a new 13-case file, giving 2237 tests / 267 files, and applied them with the script's own exportedapplyCountsandbuildStatsDocumentso the output is byte-identical to what a real run would write. If your CI disagrees, the--checkstep will say so and I'll correct it immediately.Happy to split this into per-controller commits if that reviews better.