From b4a0f2fe8eaef5626e1661e7e2c9faede89693b9 Mon Sep 17 00:00:00 2001 From: Rohan Chakraborty Date: Sat, 29 Aug 2026 18:53:45 +0530 Subject: [PATCH] feat(frontier): add FlowIntent and the consent document RPC Frontier cannot tell a signup from a login today: AuthenticateRequest carries no intent, so a login with an unknown address creates the account. FlowIntent separates the two, and its zero value keeps every existing client on today's create-or-get behaviour. Consent rides on the same request. accepted_document_ids carries the ids the user accepted, and ListConsentDocuments serves the list they came from, unauthenticated like ListAuthStrategies so a sign-up view can render the documents before the account exists. Both fields land here together so neither can claim the other's number. Flat fields rather than a oneof over login and signup arms: AuthenticateRequest.email is already a field only some strategies use, checked at runtime, so this is the shape the message has. AuthCallback needs neither field, since both ride on the flow. Part of RFC 0002: https://github.com/raystack/frontier/blob/main/docs/rfcs/0002-explicit-consent-at-signup.md Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VW3nysiE4H83VQk6BroMYc --- raystack/frontier/v1beta1/frontier.proto | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/raystack/frontier/v1beta1/frontier.proto b/raystack/frontier/v1beta1/frontier.proto index d8aca710..955731e0 100644 --- a/raystack/frontier/v1beta1/frontier.proto +++ b/raystack/frontier/v1beta1/frontier.proto @@ -256,6 +256,12 @@ service FrontierService { rpc ListAuthStrategies(ListAuthStrategiesRequest) returns (ListAuthStrategiesResponse) {} + // ListConsentDocuments returns the documents a user has to accept before an + // account is created for them. Unauthenticated, like ListAuthStrategies, so a + // sign-up view can render them before the account exists. An empty list means + // the deployment asks for no consent. + rpc ListConsentDocuments(ListConsentDocumentsRequest) returns (ListConsentDocumentsResponse) {} + rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse) {} rpc AuthCallback(AuthCallbackRequest) returns (AuthCallbackResponse) {} @@ -948,6 +954,15 @@ message AuthCallbackRequest { message AuthCallbackResponse {} +// FlowIntent says whether the caller is logging an existing user in or signing a +// new one up. Unspecified keeps the existing create-or-get behaviour, so clients +// that do not set it are unaffected. +enum FlowIntent { + FLOW_INTENT_UNSPECIFIED = 0; + FLOW_INTENT_LOGIN = 1; + FLOW_INTENT_SIGNUP = 2; +} + message AuthenticateRequest { string strategy_name = 1; @@ -972,6 +987,16 @@ message AuthenticateRequest { // callback_url should be one of the allowed urls configured at instance level string callback_url = 5; + // flow_intent separates a login from a signup. A login never creates an + // account and a signup never logs an existing user in. Left unspecified, + // the flow creates the user if it does not exist, as it does today. + FlowIntent flow_intent = 6; + + // accepted_document_ids are the ids of the consent documents the user + // accepted, as returned by ListConsentDocuments. They accompany a signup + // intent only, and are ignored when the deployment asks for no consent. + repeated string accepted_document_ids = 7; + // TODO(kushsharma): add support for passing state from client and returning it once auth is finished } @@ -997,6 +1022,27 @@ message ListAuthStrategiesResponse { repeated AuthStrategy strategies = 1; } +// ConsentDocument is one document a user has to accept at signup, as the +// deployment configured it. +message ConsentDocument { + // id is what the client sends back in accepted_document_ids + string id = 1; + // human readable name of the document, for example "Privacy Policy" + string title = 2; + // opaque version of the document, compared for equality only + string version = 3; + // url the document can be read at + string url = 4; +} + +message ListConsentDocumentsRequest {} + +message ListConsentDocumentsResponse { + // every document the deployment requires, ordered by id; empty when it + // requires none + repeated ConsentDocument documents = 1; +} + message AuthTokenRequest { // grant_type can be one of the following: // - client_credentials