Repository navigation
Conversation
… rejected POST /api/api-keys decoded the body into api.CreateAPIKeyRequest, whose constraint max_amount is a plain float64 with omitempty, and the service then re-encoded it through JSON into auth.APICreateAPIKeyRequest. The explicit 0 was erased on that re-encode, so APIPermissionConstraint's decode-time maxAmountSet flag was never set, validateExplicitZeroCap (LeanerCloud#133/LeanerCloud#983) never fired, and the key was minted with no spend cap. That fails open on a money path: typing 0 means "spend nothing". The handler now decodes straight into auth.APICreateAPIKeyRequest, as the group handlers do, and CreateAPIKeyAPI takes that concrete type instead of any. The JSON re-encode (the #1440 workaround noted in LeanerCloud#950) is gone, so no other caller type can reintroduce the erasure; the compiler now enforces what the re-encode used to paper over. The unused api.CreateAPIKeyRequest is removed. An explicit 0 (0, 0.0, -0, 0e5) now returns 400 and no key is stored. Absent and null still mean no cap, and positive caps are stored as sent. Tests: HTTP-level tests drive the real handler into a real auth.Service (MockStore). The four rejection subtests fail on main with 200 and a stored key. The obsolete cross-package re-encode test is dropped (the typed signature makes it a compile-time guarantee) and the adapter constraint test uses the auth wire types. Closes LeanerCloud#980 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Comment |
|
VERDICT: REQUEST_CHANGES 102d3a2 Reviewed: Findings
Correctness of the fix itself (checked, no findings)
Local tests (merged tree 253beb5f, macOS)
Attacked
|
|
Independent review 2 (claude-fleet, cold): REQUEST_CHANGES 102d3a2 The fix is correct on its own branch. Once merged with current main, the test build breaks, so it needs a rebase plus a 3-line test fix. Findings:
Root cause: the claim checks out.
Attacked:
Verified locally, on
Reviewed by an independent agent with no author context (second reviewer). Review only: nothing was pushed, and the GitHub review state is unchanged. |
|
@IAmJSD could you rebase this onto current |
…h.APICreateAPIKeyRequest The merge of origin/main brought in apikey_admin_constraints_test.go (LeanerCloud#1096), which still built the removed api.CreateAPIKeyRequest. Retype it to auth.APICreateAPIKeyRequest / auth.APIPermission / auth.APIPermissionConstraint so internal/server builds on the merged tree. A Go literal leaves maxAmountSet false, so the 500 cap is kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks to both reviewers. Addressed on head [blocking]
nit (process): commit size. No action needed. The new commit changes 7 lines. Verification (merged tree, macOS,
The PR body is unchanged; no review asked for a body edit. |
…eateAPIKeyHandler coverage case main's LeanerCloud#1089 (5c2dcbd) added a TestRouter_Handlers_Coverage subtest that builds the removed api.CreateAPIKeyRequest. This branch types CreateAPIKeyAPI as auth.APICreateAPIKeyRequest, so the merged tree failed go vet with "undefined: CreateAPIKeyRequest". Port the expectation to the auth wire type; the JSON body, the decoded password and the asserted response are unchanged. Refs LeanerCloud#980 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Follow-up: the branch was green on its own, but What broke
Fix
Interaction with
Verification (merged tree, Linux,
The PR body is unchanged; it is still accurate. The PR stays a draft. 🤖 Generated with Claude Code |
Summary
Follow-up to #133 / #983. Owner decision on #133: the API rejects an explicit
max_amount: 0with a 400, and an absent cap still means "no cap". #983 did this for group writes. API-key creation over HTTP still minted the key with no cap, which fails open on a money path.Root cause
POST /api/api-keysdecoded the body intoapi.CreateAPIKeyRequest/api.Permission. There,max_amountis a plainfloat64withomitempty.auth.CreateAPIKeyAPI(req any)then re-encoded that struct through JSON intoauth.APICreateAPIKeyRequest(the #1440 workaround noted in #950).The
omitemptyre-encode dropped the explicit 0. As a result:APIPermissionConstraint.UnmarshalJSONnever sawmax_amount, somaxAmountSetstayed false;validateExplicitZeroCapinapiPermissionsToPermissionsnever fired;MaxPurchaseAmount: 0, which every reader treats as unlimited.Fix
createAPIKeydecodes straight intoauth.APICreateAPIKeyRequest, the same way the group handlers decode intoauth.APICreateGroupRequest. The explicit-zero signal now survives to the shared check.CreateAPIKeyAPItakesauth.APICreateAPIKeyRequestinstead ofany. This applies onAuthServiceInterface,auth.Serviceand the server adapter. The JSON re-encode is removed, so no caller type can bring the erasure back. The compiler now enforces what the re-encode used to work around.api.CreateAPIKeyRequesttype is removed.openapi.yaml(PermissionConstraint.max_amount) now documents the 400 on API-key creation. The CHANGELOG has a new entry, and the "not covered yet (fix(api): API key create erases an explicit max_amount of 0 before validation #980)" wording is gone from the fix(frontend): a max_amount spend cap of 0 is silently erased when a group is edited #133 entry and from thevalidateExplicitZeroCapcomment.Behavior:
max_amount0,0.0,-0,0e5... max_amount must be greater than 0; omit it for no limit; no key storednull5000There is no DB, migration or concurrency change. This is a pure request-decode and type change, so no race test applies.
Closes #980
Verification (local, macOS; unit and mock evidence, no real cloud or DB)
New
internal/api/apikey_explicit_zero_cap_test.godrives the realHandler.HandleRequestinto a realauth.Servicebacked byauth.MockStore. It reuses the #951 round-trip harness, so it tests the production handler-to-service conversion.TestHandleRequest_CreateAPIKey_RejectsExplicitZeroMaxAmount:0,0.0,-0and0e5must return 400 with the message, and no key may be stored.TestHandleRequest_CreateAPIKey_AcceptsAbsentNullAndPositiveMaxAmount: absent andnullare stored with a cap of 0 (no cap), and5000is stored as 5000. Regions are kept in every case.Fail-before / pass-after:
main(2d822f3), with only the new test file added: all 4 rejection subtests FAIL withexpected: 400, actual: 200, and the key is stored withconstraints: {"regions":["us-east-1"]}and no cap.api.Permission, then JSON re-encode into the auth type). All 4 rejection subtests fail again.Test updates:
TestService_CreateAPIKeyAPI_CrossPackageType(#1440) is removed. It asserted that the re-encode accepts foreign struct types, which is the behavior removed here. The typed signature now makes the handler-to-service handoff a compile-time guarantee.TestAuthServiceAdapter_CreateAPIKeyAPI_KeepsPermissionConstraintskeeps its assertions and now uses the auth wire types.TestHandler_createAPIKey_PasswordDecodedBeforeForwardingtakes the typed request.Commands, all passing (tests run with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null, because this machine's default AWS profile is invalid):Coverage gaps: nothing runs against a deployed stack or real Postgres. The path changed here is request decoding and validation before any store write, and the store is mocked at that boundary.
Out of scope
any-typed*APImethods (CreateGroupAPI,UpdateGroupAPI,CreateUserAPI, ...) use a type assertion, not a re-encode. They are left as they are.Maintainer: please mirror the issue's triage labels on this PR:
triaged,priority/p3,severity/low,urgency/this-quarter,impact/few,effort/s,type/bug.🤖 Generated with Claude Code