Repository navigation
feat(staff): gestao de equipe via API, permissoes RBAC e rotas staff - #27
Merged
Merged
Conversation
The five hard questions answered before any code: the last-administrator guard counts holders of the permission rather than roles; granting staff.manage is granting everything, written down instead of half-contained; staff is a non-default role or any direct grant, with exact-email lookup as the only way in; nobody gives themselves anything, but renouncing is allowed so the guard is reachable at all; and 404 stays where an id is in the path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fifteenth permission, and the only one that hands out the other fourteen: whoever holds it can move an account to admin. It goes to admin by derivation (Object.values), and the operator list is left untouched on purpose — operators run the store, they do not hire for it. The migration carries it into databases that already exist. Reference data still comes from the code through prisma/seed.ts and is not copied here; what a running database cannot learn from a changed constant is that admin's role_permissions needs a fifteenth row. Without it the feature ships unreachable: nobody holds staff.manage, and the only route that could grant it requires staff.manage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
List the team, change a role, grant and revoke a per-user permission — all gated on staff.manage. The case that forced it: an operator reads the catalogue and cannot create a product, so the person hired to catalogue pieces is none of the three roles. The design is mostly its refusals (docs/specs/staff-management.md): - Nobody gives themselves anything. Granting to yourself and changing your own role are refused outright; a role change adds as easily as it removes, so it is not a decision anybody makes about themselves. - Renouncing your own grant IS allowed, because giving up access is never an escalation — and because it is the one path that can reach the guard below, which would otherwise be untestable. - The store never loses its last administrator. The count is over effective holders of staff.manage, role permissions united with per-user grants, not over who has the admin role: an operator carrying the grant administers exactly as much as an admin. Locked FOR UPDATE before the write, ORDER BY id, so two simultaneous renunciations queue instead of both reading "somebody else is still here" and both committing. The listing is staff only — a non-default role or any direct grant — with exact-email lookup as the way to find whoever is about to be promoted, since by definition they are not staff yet. Substring search would be customer enumeration and does not exist. Responses are written column by column: User carries passwordHash and googleId. Lives in auth because users, roles and user_permissions are auth's tables; a module of its own would be a second boundary crossing, this one writing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Thirty-six cases against a real Postgres, covering what a mock cannot: - the whole hiring flow — promote a shopper, grant products.create, and the account creates a piece with the token it already held, while orders.refund stays 403 because it was never given; - revoking, and the very next request with that same token is 403. No new token, no waiting fifteen minutes for the old one to expire; - the last-administrator guard, twice, both times with NO account holding the admin role: one holder renouncing is refused, two holders means the first may go. A guard that counted admins would answer wrongly in both, which is the point of counting holders; - the listing recorte, which is a where clause and needs rows to exist; - that no response carries passwordHash or googleId. Granting answers 200 rather than Nest's default 201: the response is the account, which already existed, and a repeat grant creates nothing at all. Same reading the order transitions already take. openapi.json regenerated — 42 paths, 50 operations, and the staff 403s name staff.manage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The README's promote-by-SQL is now only the bootstrap — there is nobody to ask for the first admin, since the route that grants requires the permission nobody has yet. Everything after that is /staff, and the Limitações section says what remains: no e-mail invite, no account lifecycle, and staff.manage not being contained. admin-api.md item 4 closes. Two of the three rules it demanded shipped as written; the third — "refuse to grant what the caller does not have" — did not, and the entry says why: it contains nothing while role changes exist, because whoever can move an account to admin grants the whole catalogue without touching the grant route. Half a containment reads as a limit that is not there, so the consequence is written down instead. security.md gains the two entries this feature owes a reader: the account enumeration exception (GET /staff?email= answers whether an address has an account — to a caller who already holds full administrative power, and only on exact equality), and what actually limits an insider once delegation is delegable. Also: the module map records why the routes live in auth rather than in a module of their own, the storefront contract gets the shape of a team screen and its four refusals, and the new-store runbook gets the curl calls that replace the second UPDATE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
O que este PR introduz