feat(authenticate): map the gate and consent errors on both auth RPCs - #1915
Conversation
The two request fields reach StartFlow, and the three rejections become legible to a client instead of arriving as a 500. Authenticate calls sessionutils.ExtractSessionMetadata itself for the IP. Authenticate and AuthCallback are both on the authentication skip list, so nothing puts session metadata on the context. That helper parses the user agent into an OS and a browser family and drops the raw string, so what reaches the consent record is the IP and nothing else, from when the user accepted rather than from the callback. ErrLoginUserNotFound maps to NotFound, ErrSignupUserExists to AlreadyExists and ErrConsentRequired to FailedPrecondition, from both RPCs. All three had to join the errors AuthCallback handles explicitly, which maps a fixed list to a 4xx and everything else to Internal; they keep their own codes rather than the InvalidArgument the rest of that list gets, because FailedPrecondition is what lets a client separate a consent rejection from a bad code or an expired flow. Both RPCs answer with a code rather than a redirect. Frontier serves no route for the callback URL: it points at a page the application hosts, and that page is what calls AuthCallback over connect, so it already holds the rejection and decides where the user goes next. Handing it a location header instead would oblige every such page to be written to read one, and a 2xx carrying that header reads as success to one that is not. The three codes are distinct, so a client tells the rejections apart without a second vocabulary alongside them. StartFlow gains the consent half of the flow start gate, so a rejection lands before an OTP is sent and before the browser leaves for an identity provider. A signup intent runs ResolveAll there; an unspecified intent runs Resolve, which still catches an unknown id before the redirect while completeness waits for user creation; a login intent checks nothing, because it writes no record. Ids sent with a login intent are rejected by the handler as InvalidArgument, since accepting them silently would leave a client believing it recorded a consent that does not exist. With app.consent disabled both Resolve and ResolveAll resolve nothing and reject nothing, so the ids are ignored rather than rejected and one client build works against both kinds of deployment. Rendering any of this is a separate frontend change: the sign-in and sign-up views for what Authenticate returns, and the callback page for what AuthCallback returns. Refs docs/rfcs/0002-explicit-consent-at-signup.md, Enforcement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hf1XuPCWcHZ7QY5u4WBB2G
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Coverage Report for CI Build 33381454770Coverage increased (+0.2%) to 50.135%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Summary
ErrLoginUserNotFound→NotFound,ErrSignupUserExists→AlreadyExists,ErrConsentRequired→FailedPrecondition, from bothAuthenticateandAuthCallback. All three had to join the fixed listAuthCallbackhandles explicitly, or they fall through toInternalby construction.FailedPreconditionspecifically, because the other errors on that list are allInvalidArgument— the client sent something wrong and resending will not help. A consent rejection is the opposite: the request was well formed and the client can fix it by asking the user to accept what is missing. That distinction is what a client cannot recover from a message string.AuthCallbackover connect, so it already holds the rejection and decides where the user goes. A redirect was implemented first and withdrawn: nothing follows the location header, and a 2xx carrying it reads as success to a client that is not looking for it.StartFlowgains the consent half of the flow-start gate, so a signup rejection lands before an OTP is sent and before the browser leaves for the provider. An unspecified intent checks only that the ids are known, since completeness is not yet knowable; a login intent checks nothing, because it writes no record.app.consentdisabled ids are ignored rather than rejected, which is content and handled one layer down. Rendering any of this is a separate frontend change.