Skip to content

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

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

feat(authenticate): map the gate and consent errors on both auth RPCs#1915
rohanchkrabrty wants to merge 2 commits 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.

@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
frontier Ready Ready Preview Sep 7, 2026 7:57pm UTC

@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Summary

Summary by CodeRabbit

  • New Features
    • Added consent validation when starting signup flows.
    • Signup requests now require complete, valid consent document selections.
    • Authentication responses provide specific error codes for common rejection reasons.
    • Session IP information is passed through during authentication.
  • Bug Fixes
    • Login flows reject submitted consent document IDs when consent is enabled.
    • Consent document IDs are ignored when consent is disabled.
    • Authentication and callback errors are reported consistently instead of appearing as generic server errors.
  • Documentation
    • Clarified consent behavior when consent is disabled.

Walkthrough

Authentication flows now validate consent before delivery, propagate intent, accepted document IDs, and IP metadata, and map known errors to Connect responses. Tests and RFC text cover enabled and disabled consent behavior.

Changes

Consent-aware flow validation

Layer / File(s) Summary
Consent validation gate
core/authenticate/service.go, core/authenticate/mocks/consent_service.go, core/authenticate/service_test.go, docs/rfcs/0002-explicit-consent-at-signup.md
StartFlow validates consent before delivery. Signup uses complete resolution, unspecified intent validates submitted IDs, and login skips consent resolution. Generated mocks, tests, and RFC text cover the behavior.

Authentication request propagation

Layer / File(s) Summary
Authentication request propagation
internal/api/v1beta1connect/authenticate.go, internal/api/v1beta1connect/consent.go, internal/api/v1beta1connect/errors.go, internal/api/v1beta1connect/authenticate_test.go
Authenticate converts flow intent, reads accepted document IDs, extracts the client IP, rejects consent IDs on login when enabled, and passes the values to StartFlow. Tests cover enabled and disabled consent behavior.

Authentication and callback rejection mapping

Layer / File(s) Summary
Authentication and callback rejection mapping
internal/api/v1beta1connect/authenticate.go, internal/api/v1beta1connect/authenticate_test.go
Known authentication and callback errors map to Connect codes and sanitized messages. Unmapped callback errors remain internal.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Merge Risk: 🟡 Moderate · up to 127d0

This change adds consent-aware authentication validation and error mapping, but login requests carrying consent IDs can still proceed through the core flow and unknown intent values can avoid signup-level completeness checks. These request-shape and consent-validation gaps should be resolved before merge.

Suggested reviewers: amangit07, whoabhisheksah

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

coveralls commented Aug 31, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 34157510811

Coverage increased (+0.2%) to 50.212%

Details

  • Coverage increased (+0.2%) from the base build.
  • Patch coverage: 85 of 85 lines across 3 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: 41084
Covered Lines: 20629
Line Coverage: 50.21%
Coverage Strength: 15.99 hits per line

💛 - Coveralls

@rohanchkrabrty
rohanchkrabrty marked this pull request as draft August 31, 2026 10:36
@rohanchkrabrty
rohanchkrabrty force-pushed the feature/featauthenticate-map-the-gate-and-consent-errors-on-both branch from bb8970c to 8e30f1a Compare August 31, 2026 19:10
@rohanchkrabrty
rohanchkrabrty force-pushed the feature/featauthenticate-map-the-gate-and-consent-errors-on-both branch from 8e30f1a to e22800f Compare September 2, 2026 16:29
@rohanchkrabrty
rohanchkrabrty force-pushed the feature/featauthenticate-map-the-gate-and-consent-errors-on-both branch from e22800f to 40db3a2 Compare September 2, 2026 19:00
@rohanchkrabrty
rohanchkrabrty marked this pull request as ready for review September 2, 2026 20:06
@rohanchkrabrty
rohanchkrabrty force-pushed the feature/featauthenticate-map-the-gate-and-consent-errors-on-both branch from 40db3a2 to f31c0a5 Compare September 6, 2026 21:42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 63039b56-ab17-4458-89bd-e53320731adf

📥 Commits

Reviewing files that changed from the base of the PR and between aebd4f8 and f31c0a5.

📒 Files selected for processing (8)
  • core/authenticate/mocks/consent_service.go
  • core/authenticate/service.go
  • core/authenticate/service_test.go
  • docs/rfcs/0002-explicit-consent-at-signup.md
  • internal/api/v1beta1connect/authenticate.go
  • internal/api/v1beta1connect/authenticate_test.go
  • internal/api/v1beta1connect/consent.go
  • internal/api/v1beta1connect/errors.go

Included review availability: Your plan provides up to 2 included reviews per hour; 0 remain after this review.

Comment on lines +82 to +83
default:
return authenticate.FlowIntentUnspecified

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- authenticate.go conversion and handler ---'
sed -n '70,155p' internal/api/v1beta1connect/authenticate.go
printf '%s\n' '--- consent gate and intent semantics ---'
rg -n -A80 -B10 'func .*gateFlowConsent|FlowIntentSignup|FlowIntentUnspecified|resolve' core/authenticate internal/api/v1beta1connect
printf '%s\n' '--- rejection mapping and conversion tests ---'
rg -n -A35 -B10 'lookupAuthFlowRejection|TestToFlowIntent|FlowIntent\(99\)' internal/api/v1beta1connect

Repository: raystack/frontier

Length of output: 50374


🤖 get_repo_knowledge executed:

get_repo_knowledge raystack/frontier /tmp/coderabbit-repo-knowledge/raystack-frontier-36c605d8/learnings

Length of output: 12095


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- changed file ---'
sed -n '70,155p' internal/api/v1beta1connect/authenticate.go
printf '%s\n' '--- consent and intent definitions ---'
rg -n -A90 -B10 'func .*gateFlowConsent|FlowIntentSignup|FlowIntentUnspecified|resolve' core/authenticate internal/api/v1beta1connect
printf '%s\n' '--- rejection mapping and conversion tests ---'
rg -n -A35 -B10 'lookupAuthFlowRejection|TestToFlowIntent|FlowIntent\(99\)' internal/api/v1beta1connect

Repository: raystack/frontier

Length of output: 50374


Other (CWE-841)

Reachability: External · Exploitability: Trivial

Reject unknown FlowIntent values.

toFlowIntent maps unknown enum values, such as FlowIntent(99), to FlowIntentUnspecified. This applies known-ID consent validation instead of the signup completeness check, allowing a new account to start without all required documents. Return connect.CodeInvalidArgument before StartFlow, preserve only FLOW_INTENT_UNSPECIFIED, and update TestToFlowIntent.

@rohanchkrabrty
rohanchkrabrty force-pushed the feature/featauthenticate-map-the-gate-and-consent-errors-on-both branch from f31c0a5 to 30b9c17 Compare September 7, 2026 05:29
@rohanchkrabrty
rohanchkrabrty force-pushed the feature/featauthenticate-map-the-gate-and-consent-errors-on-both branch from 30b9c17 to 1f684a9 Compare September 7, 2026 06:10
@rohanchkrabrty
rohanchkrabrty force-pushed the feature/featauthenticate-map-the-gate-and-consent-errors-on-both branch from 1f684a9 to 70630b3 Compare September 7, 2026 07:12
// consentEnabled reports whether this deployment asks for consent at all. Boot
// validation rejects an enabled block with no documents, so an empty set means
// the feature is off.
func (h *ConnectHandler) consentEnabled() bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consentEnabled derives "consent is on" from the length of Documents(), which leans on boot validation to stay true. Can we instead expose Enabled() on the consent service and have the handler ask, so the rule lives with the config it reads?

rohanchkrabrty and others added 2 commits September 8, 2026 01:24
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. The handler's own rejection
follows that rule: it fires only when documents are configured, since a
deployment that records no consent under any intent has nothing an id could
refer to, and a client built for a consent deployment must still be able to
log in against one that is not.

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
…ady say

The same pass over the error mapping: keep the reasons that are not in the
code, and drop what the tests restated from the handler's own doc comments
(the FailedPrecondition rationale, the bare-sentinel rule, the unspecified
intent default).

No behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rohanchkrabrty
rohanchkrabrty force-pushed the feature/featauthenticate-map-the-gate-and-consent-errors-on-both branch from 70630b3 to 127d0b4 Compare September 7, 2026 19:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: b45680bf-1653-430a-8b9f-984598b06422

📥 Commits

Reviewing files that changed from the base of the PR and between 70630b3 and 127d0b4.

📒 Files selected for processing (2)
  • core/authenticate/service.go
  • core/authenticate/service_test.go

Included review availability: Your plan provides up to 2 included reviews per hour; 0 remain after this review.

// identical: a signup has to be complete here, an unset intent only has to
// name known ids, and a login checks nothing because it writes no record.
func (s Service) gateFlowConsent(intent FlowIntent, ids []string) error {
if s.consentService == nil || intent == FlowIntentLogin {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject supplied consent IDs for login flows.

Line 459 returns before it inspects ids. A login request with consent IDs can therefore send an OTP or redirect to the provider. The PR requires this request shape to be rejected when consent is enabled.

Keep the disabled-consent behavior. Add a login validation path for non-empty IDs and update the login test to assert that no flow starts.

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.

3 participants