fix(auth): return 503 when Clerk verify omits API key identity - #236
Open
detail-app[bot] wants to merge 1 commit into
Open
fix(auth): return 503 when Clerk verify omits API key identity#236detail-app[bot] wants to merge 1 commit into
detail-app[bot] wants to merge 1 commit into
Conversation
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.
Detail bug report: View on Detail
Bug
ClerkApiKeyVerifier.verifytranslates every malformed Clerk Backend API reply into a clean application outcome —Optional.empty()for rejected/revoked/expired keys,ApiKeyOperationUnavailableException(→ HTTP 503) for empty responses, incomplete lifecycle state, transport failures, and non-credential HTTP errors. The one case it failed to translate was a200whose identity fields (id,subject) were null or blank: it passed those straight intonew VerifiedApiKey(...), whose record invariant throwsIllegalArgumentException. That throw happens outsideverify's try/catch (which only wraps the REST call), so the exception escapedClerkApiKeyAuthenticationFilter(which catches onlyApiKeyOperationUnavailableException), reachedCustomErrorController→ExceptionResponseBuilder.describeException, and the client received HTTP 500 with body{"status":"error","message":"Internal Server Error","details":"IllegalArgumentException"}— a crash-coded status leaking the internal exception class name. The structurally parallel missing-revoked/expiredcase was already a 503; only the identity fields were omitted from that treatment.Fix
Add an identity-field guard in
ClerkApiKeyVerifier.verify, mirroring the existing lifecycle-field guard: when a200has null/blankidorsubject, throwApiKeyOperationUnavailableException("Clerk returned an incomplete API key identity")before constructingVerifiedApiKey. The filter's existing catch then returns the consistent 503 with the human message and no exception class name.The
VerifiedApiKeyrecord'sIllegalArgumentExceptioninvariant is intentionally left unchanged (correct value-object design); the defect wasverify's exception flow at the provider boundary, not the record's guard.ClerkApiKeyAuthenticationFilteris also unchanged — its 503 mapping forApiKeyOperationUnavailableExceptionwas already correct.Testing
ClerkApiKeyVerifierTest.rejectsIncompleteIdentity(parametrized, 5 cases: missingsubject, missingid, both missing, blanksubject, blankid) drives the real verifier against aMockRestServiceServermalformed200and assertsApiKeyOperationUnavailableException— mirroring the siblingrejectsIncompleteLifecycleState. It fails on the unfixed code and passes on the fixed code.AuthenticatedUserEndpointSecurityIntegrationTest.unavailableApiKeyVerificationReturns503AndLogsOneFailureruns the real Spring Security filter chain over MockMvc (mocked verifier throwsApiKeyOperationUnavailableException) and asserts HTTP 503, the fixed human message, exactly one ERROR log at the filter logger, and that theak_…secret is absent from the log — mirroring the existing revocation-503 test for the previously-untested verify path../gradlew compileJava compileTestJava, the targeted test classes (18 + 10, 0 failures), the full non-integration JVM suite (1169 tests, 0 failures),spotlessCheck, andpmdMain/pmdTest.CLERK_SECRET_KEYintentionally unset soverifythrows theApiKeyOperationUnavailableExceptionfamily): anonymousGET /api/me→ 401;GET /api/meandDELETE /api/me/api-keywith aBearer ak_…token → 503 with{"status":"error","message":"API key verification is temporarily unavailable. Please retry.","details":null}(noIllegalArgumentExceptionleak); the server log showed one ERROR line per request atClerkApiKeyAuthenticationFilterwith theApiKeyOperationUnavailableExceptioncause.node bin/javachat.js auth statuswith aJAVACHAT_API_KEYagainst the local server surfacedKey verification failed: HTTP 503(not 500), matching the committed CLI test that asserts this 503 surfacing. CLI unit tests (npm test, 43/43) andnpm run pack:checkpass.200branch specifically and the control happy-path (well-formed200→ 200 withuserId) against live external services. They require a realCLERK_SECRET_KEYand a key whose Clerkverifyreply is well-formed, or a controllable Clerk stand-in — butCLERK_VERIFY_ENDPOINTis a hardcoded constant with no override, and this environment has neither. Both branches are pinned by the committedMockRestServiceServer/@MockitoBeantests above, which are green.Closes #223
Automatic Fixes PRs can be configured here.