Repository navigation
chore(auth): enumerate permission actions and resources, and check the TS unions against them - #1288
Conversation
…e TS unions against them Go cannot enumerate a const block, so the frontend's Action and Resource unions were a hand-maintained mirror with nothing detecting drift. Add auth.AllActions() / auth.AllResources(). Their completeness is not left to memory: a go/ast test parses every non-test file in internal/auth and fails when an Action*/Resource* constant is declared without being enumerated (or enumerated without being declared), when such a constant is not a string literal, or when two share a value. A second test checks that the default group permissions and admin carve-outs use only enumerated values. cmd/gen-permissions gains a read-only test that parses the Action and Resource unions in frontend/src/permissions.ts and fails CI on drift in either direction. No frontend file changes. Refs LeanerCloud#166 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 3 billable files and costs up to $0.75. Or wait 59 minutes for your next included review. View limit details
Comment |
Brings in main's CI fixes (LeanerCloud#1236, LeanerCloud#1252) and CI changes since, so this PR gets a fresh CI run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rB19. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rB20. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Local verification at |
|
Merged by IAmJSD's agent at IAmJSD's request (IAmJSD is acting owner while the maintainers are away), under the bar IAmJSD set: 2 approving independent review(s) on |
Root cause
internal/authdeclares its permission vocabulary as individual untypedconsts (ActionView,ResourcePlans, ...). Go can't enumerate a const block, so nothing in Go could list the full set. The frontend'sAction/Resourceunions infrontend/src/permissions.tswere therefore a hand-kept mirror, and nothing detected them drifting from Go. That drift is what caused cli#1629, where the group-edit form silently dropped permissions and widenedview:historytoview:*.Fix
internal/auth/permission_catalog.go(new file):auth.AllActions()andauth.AllResources()return the full sets in declaration order. They includeadminand the*wildcard, and each call returns a fresh copy. I put them in a new file becausetypes.gois already over 500 lines, and because this keeps the const blocks untouched.Completeness doesn't depend on anyone remembering to update the list, which is the failure mode the issue warns about.
TestPermissionCatalogMatchesConstDeclsusesgo/astto parse every non-test.gofile ininternal/auth. It collects each package-level const namedAction<Upper>…/Resource<Upper>…, including Go's implicit-repeat specs, and fails when:Each failure names the constant and its position.
TestPermissionCatalogCoversSeededPermissionschecks that everyDefault*Permissions()entry and everyadminCarvedOutspair uses only enumerated actions and resources.Go-to-TS seam:
cmd/gen-permissions/union_drift_test.goreadsfrontend/src/permissions.ts, parses theexport type Action = …/export type Resource = …string-literal unions (comment-aware), and fails CI if either side has a value the other lacks. The frontend's existingRecord<Action, true>check keepsALL_ACTIONSequal to the union, and this test keeps the union equal to Go. Drift now failsgo test ./...in CI. The test only reads the file. This PR changes nothing underfrontend/.I kept the enumeration as a listed array plus an AST exhaustiveness test. A
go:generatestep or restructuring the consts around a slice would have rewritten the heavily commented const blocks intypes.go, and an open branch is already editing those blocks. This approach leavestypes.gountouched.Refs #166
(This is
Refs, not a closing keyword, because the audit finding attached to the issue, A11-011 onpermissionMatrix.ts, and the TS generation step both need frontend changes. See below.)Verification
All evidence below is from unit tests on this branch. No DB, cloud or integration code is involved.
Fail-before / mutation probes. Each was applied temporarily and then reverted:
permission_catalog.goremoved): both test packages fail to build (undefined: AllResources/undefined: auth.AllResources). Go has no enumeration to test against.ActionFreeze = "freeze"added to the action block):TestPermissionCatalogMatchesConstDeclsFAILS withActionFreeze = "freeze" is declared but missing from the catalog in permission_catalog.go.allActions): the auth tests pass, andTestFrontendPermissionUnionsMatchGoFAILS withAction union in ../../frontend/src/permissions.ts lacks Go value(s) ["freeze"].ResourceRIExchangeremoved fromallResources): the const-decl test FAILS (ResourceRIExchange = "ri-exchange" is declared but missing…), the seeded-permissions test FAILS (adminCarvedOutsusesri-exchange), and the frontend drift test FAILS (Resource union … has ["ri-exchange"]).ResourceAllAliaswith no value afterResourceAll): FAILS withResourceAll and ResourceAllAlias both have value "*".ResourceComputed = ResourcePlans + "-x"): FAILS withResourceComputed must be a string literal….TestTSUnionMemberscovers the TS parser: line comments, block comments, a/*inside a line comment,//inside a string literal, double quotes, and a missing declaration.I also checked with
git merge-treethat the branch merges cleanly with our unopenedfix/issue-313branch, which removes theRole*constants. This change doesn't reference them.Combined-tree verification. This branch was tested together with the other queued branches on a local, never-pushed tree:
queue/platform@936996ad= origin/maina14dc8f2plus these heads, merged in this order:fix/issue-72andfix/issue-312conflict on one line ininternal/api/handler_dashboard.go(both change thecalculateCommitmentMetricscall). The tree used #72's side with #312's validatedprovidervariable; whichever PR merges second takes that same resolution.Linux, Go 1.26.9,
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true; Docker Postgres via testcontainers:936996ad:go build ./...,go vet ./...,go vet -tags integration ./...and the fullgo test -race -short -count=1 ./....936996ad: golangci-lint v2.10.1 found 0 issues on every changed package, with and without--build-tags integration.tests/e2eis a separate module, so it was vetted under its owne2etag.gocyclo -over 10is silent on the changed non-test files.-tags integration -count=1, 60-minute local timeout because the VM was shared):936996ad, these pass:internal/server,internal/credentials,internal/scheduler,internal/database/postgres/migrations,internal/purchaseandinternal/api.internal/configpasses exceptTestPostgresStoreDB_ResolveStaleRIExchange_ValidatesCloudCheck, which fails the same way on unmodified main (fixed by fix(config): compare resolve checked_at at the database's microsecond precision #1252).26795376, same branches at earlier heads):internal/analytics,internal/auth,internal/commitmentopts,internal/databaseandcmd/lambda/clear-rate-limitall pass. The later fixes don't touch those packages.httpToLambdaRequestsignature change;scripts/setup-git-secrets.shpatterns in a fresh repo with an isolated HOME, found nothing in the changed files, and a planted fake key was flagged.scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.Deliberately out of scope
cmd/gen-permissions: that would change the committedfrontend/src/permissions.generated.ts, and the UI owner ownsfrontend/. The drift test gets the same "CI fails on drift" result without editing the frontend. See the follow-up below.AllActions()/AllResources()would make one trivial if it's ever wanted.internal/auth: the AST scan covers this package only. Handlers that pass bare string literals to permission checks elsewhere aren't covered.Action/Resource+ upper-case prefix. A permission const named some other way wouldn't be seen, and the test docs say this.Frontend follow-up for the UI owner
cmd/gen-permissionsto emit, for example,export const ACTIONS = [...] as const; export type Action = (typeof ACTIONS)[number];(and the same for resources) fromauth.AllActions()/auth.AllResources()intopermissions.generated.ts. Then import those inpermissions.ts, regenerate withgo run ./cmd/gen-permissions, and commit the result. The existingpermissions-codegenpre-commit hook then catches staleness, andcmd/gen-permissions/union_drift_test.gocan be deleted or pointed at the generated file. Until that happens, a new verb needs updating in three places: the const ininternal/auth/types.go, the list ininternal/auth/permission_catalog.go, and the union plus*_EXHAUSTIVENESS_CHECKrecord infrontend/src/permissions.ts. The tests name whichever one was missed.frontend/src/users/permissionMatrix.ts:11still has its own seven-verbACTIONSlist, so the matrix shows no row forapprove-any,retry-any,cancel-any,update-any,execute-any,sell-any,revoke-anyor any-ownverb. Fix: derive the matrix rows fromALL_ACTIONS, asgroupModals.tsalready does.permissions.tsheader says the unions are hand-written mirrors. It could also mention thatcmd/gen-permissions/union_drift_test.gonow enforces them.ACTION_EXHAUSTIVENESS_CHECKholds within the frontend, but reads as repo-wide. The issue asks for it to be scoped.Maintainer: please mirror the issue's triage labels onto this PR:
impact/internal,type/chore,triaged,priority/p3,severity/low,urgency/eventually,effort/m.🤖 Generated with Claude Code