Skip to content

feat(authenticate): map the gate and consent errors on both auth RPCs - #1915

Draft
rohanchkrabrty wants to merge 1 commit into
feature/featconsent-record-consent-in-the-same-transaction-as-thefrom
feature/featauthenticate-map-the-gate-and-consent-errors-on-both
Draft

feat(authenticate): map the gate and consent errors on both auth RPCs#1915
rohanchkrabrty wants to merge 1 commit into
feature/featconsent-record-consent-in-the-same-transaction-as-thefrom
feature/featauthenticate-map-the-gate-and-consent-errors-on-both

Conversation

@rohanchkrabrty

@rohanchkrabrty rohanchkrabrty commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • The previous three PRs built machinery that can refuse a request; until this one, every refusal reached the client as a 500. ErrLoginUserNotFoundNotFound, ErrSignupUserExistsAlreadyExists, ErrConsentRequiredFailedPrecondition, from both Authenticate and AuthCallback. All three had to join the fixed list AuthCallback handles explicitly, or they fall through to Internal by construction.
  • FailedPrecondition specifically, because the other errors on that list are all InvalidArgument — 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.
  • Settles the surface RFC 0002 left open, as "a code, on both RPCs". The RFC weighed a redirect against rendering in place; neither applies. Frontier serves no route for the callback URL — it points at a page the application hosts, and that page calls AuthCallback over 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.
  • StartFlow gains 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.
  • Ids sent with a login intent are rejected as a request-shape error, unconditionally. With app.consent disabled ids are ignored rather than rejected, which is content and handled one layer down. Rendering any of this is a separate frontend change.

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
@rohanchkrabrty rohanchkrabrty self-assigned this Aug 31, 2026
@vercel

vercel Bot commented Aug 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview Aug 31, 2026 10:14am

@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: eb6a9cf8-38ae-4d6a-a1ca-fd0662075ae2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 33381454770

Coverage increased (+0.2%) to 50.135%

Details

  • Coverage increased (+0.2%) from the base build.
  • Patch coverage: 89 of 89 lines across 2 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 41079
Covered Lines: 20595
Line Coverage: 50.14%
Coverage Strength: 15.97 hits per line

💛 - Coveralls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants