From 23b0d855893c846d0d48b761c3dfbf1852fcc2d8 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Mon, 22 Jun 2026 15:50:22 +0200 Subject: [PATCH] Include the browser session ID as `sid` in issued `id_token`s Persist resolved target user and user session on authorization grants --- .../src/oauth2/authorization_grant.rs | 12 ++++++++++ .../handlers/src/oauth2/authorization/mod.rs | 2 ++ crates/handlers/src/oauth2/discovery.rs | 2 ++ crates/handlers/src/oauth2/mod.rs | 2 ++ crates/handlers/src/oauth2/token.rs | 19 +++++++++++++++- ...25da902344c79b50e8dd205b1fffe58665d9.json} | 18 ++++++++++++--- ...b9a2470a8f1c37401e27192bcf9927b12979.json} | 6 +++-- ...3bbea9c52d1432815d3ff47f934d292cecf0.json} | 18 ++++++++++++--- ...622100000_oauth2_grants_target_session.sql | 13 +++++++++++ ...25256_oauth2_grants_target_session_idx.sql | 9 ++++++++ .../src/oauth2/authorization_grant.rs | 22 +++++++++++++++++-- crates/storage-pg/src/oauth2/mod.rs | 2 ++ .../storage/src/oauth2/authorization_grant.rs | 9 +++++++- crates/templates/src/context.rs | 12 +++++----- 14 files changed, 128 insertions(+), 18 deletions(-) rename crates/storage-pg/.sqlx/{query-008ef8a2092fd2424ab6215bcad376b72f26b723dc029624396bb2030e53907c.json => query-4040e4d3cecfb2b25e0c3d141df525da902344c79b50e8dd205b1fffe58665d9.json} (83%) rename crates/storage-pg/.sqlx/{query-041c4ddff9b40ff5ba16c9aa1dd9c721998de6e5798e4423df9063519ee5ac4d.json => query-4f152ad116f636d513373e07ecddb9a2470a8f1c37401e27192bcf9927b12979.json} (72%) rename crates/storage-pg/.sqlx/{query-2921f8cb19dedfc5524298ba4f42580244a4e5611d7aa8ade990bd5b2230f410.json => query-65e79b677464a0007cd8afe678953bbea9c52d1432815d3ff47f934d292cecf0.json} (83%) create mode 100644 crates/storage-pg/migrations/20260622100000_oauth2_grants_target_session.sql create mode 100644 crates/storage-pg/migrations/20260625125256_oauth2_grants_target_session_idx.sql diff --git a/crates/data-model/src/oauth2/authorization_grant.rs b/crates/data-model/src/oauth2/authorization_grant.rs index 23d0489a9..91838aa47 100644 --- a/crates/data-model/src/oauth2/authorization_grant.rs +++ b/crates/data-model/src/oauth2/authorization_grant.rs @@ -1,3 +1,4 @@ +// Copyright 2025, 2026 Element Creations Ltd. // Copyright 2024, 2025 New Vector Ltd. // Copyright 2021-2024 The Matrix.org Foundation C.I.C. // @@ -164,6 +165,15 @@ pub struct AuthorizationGrant { /// Raw query parameters from the downstream authorization request, used /// to template the parameters forwarded to the upstream provider. pub raw_parameters: BTreeMap, + /// The user resolved from a valid `id_token_hint`, if one was given and + /// verified at authorize time. The raw hint itself stays in + /// [`Self::raw_parameters`]; this is the resolved outcome. + pub target_user_id: Option, + /// The browser session resolved from the `sid` claim of a valid + /// `id_token_hint`, if present and still existing at authorize time. May + /// dangle after the session is reaped by retention; + /// [`Self::target_user_id`] is the stable anchor. + pub target_user_session_id: Option, } impl std::ops::Deref for AuthorizationGrant { @@ -243,6 +253,8 @@ impl AuthorizationGrant { login_hint: Some(String::from("mxid:@example-user:example.com")), locale: Some(String::from("fr")), raw_parameters: BTreeMap::new(), + target_user_id: None, + target_user_session_id: None, } } } diff --git a/crates/handlers/src/oauth2/authorization/mod.rs b/crates/handlers/src/oauth2/authorization/mod.rs index b72e58056..e6abdcccc 100644 --- a/crates/handlers/src/oauth2/authorization/mod.rs +++ b/crates/handlers/src/oauth2/authorization/mod.rs @@ -267,6 +267,8 @@ pub(crate) async fn get( params.auth.login_hint, Some(locale.to_string()), raw_parameters, + None, + None, ) .await?; let continue_grant = PostAuthAction::continue_grant(grant.id); diff --git a/crates/handlers/src/oauth2/discovery.rs b/crates/handlers/src/oauth2/discovery.rs index 4530ea820..5bb5964b6 100644 --- a/crates/handlers/src/oauth2/discovery.rs +++ b/crates/handlers/src/oauth2/discovery.rs @@ -1,3 +1,4 @@ +// Copyright 2025, 2026 Element Creations Ltd. // Copyright 2024, 2025 New Vector Ltd. // Copyright 2021-2024 The Matrix.org Foundation C.I.C. // @@ -126,6 +127,7 @@ pub(crate) async fn get( let claims_supported = Some(vec![ "iss".to_owned(), "sub".to_owned(), + "sid".to_owned(), "aud".to_owned(), "iat".to_owned(), "exp".to_owned(), diff --git a/crates/handlers/src/oauth2/mod.rs b/crates/handlers/src/oauth2/mod.rs index cf28818e2..340c57c34 100644 --- a/crates/handlers/src/oauth2/mod.rs +++ b/crates/handlers/src/oauth2/mod.rs @@ -1,3 +1,4 @@ +// Copyright 2025, 2026 Element Creations Ltd. // Copyright 2024, 2025 New Vector Ltd. // Copyright 2021-2024 The Matrix.org Foundation C.I.C. // @@ -59,6 +60,7 @@ pub(crate) fn generate_id_token( let now = clock.now(); claims::ISS.insert(&mut claims, url_builder.oidc_issuer().to_string())?; claims::SUB.insert(&mut claims, &browser_session.user.sub)?; + claims::SID.insert(&mut claims, browser_session.id.to_string())?; claims::AUD.insert(&mut claims, client.client_id.clone())?; claims::IAT.insert(&mut claims, now)?; claims::EXP.insert(&mut claims, now + Duration::try_hours(1).unwrap())?; diff --git a/crates/handlers/src/oauth2/token.rs b/crates/handlers/src/oauth2/token.rs index 3f2a40dbd..e4a542340 100644 --- a/crates/handlers/src/oauth2/token.rs +++ b/crates/handlers/src/oauth2/token.rs @@ -1122,6 +1122,8 @@ mod tests { None, None, std::collections::BTreeMap::new(), + None, + None, ) .await .unwrap(); @@ -1147,11 +1149,24 @@ mod tests { let response = state.request(request).await; response.assert_status(StatusCode::OK); - let AccessTokenResponse { access_token, .. } = response.json(); + let AccessTokenResponse { + access_token, + id_token, + .. + } = response.json(); // Check that the token is valid assert!(state.is_access_token_valid(&access_token).await); + // We asked for the openid scope, so we should have an ID token, and it + // should carry a `sid` claim equal to the browser session ID. + let id_token = id_token.expect("an ID token should be present for the openid scope"); + let id_token: mas_jose::jwt::Jwt> = + id_token.as_str().try_into().unwrap(); + let (_, mut claims) = id_token.into_parts(); + let sid = mas_jose::claims::SID.extract_required(&mut claims).unwrap(); + assert_eq!(sid, browser_session.id.to_string()); + // Exchange it again, this it should fail let request = Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({ @@ -1211,6 +1226,8 @@ mod tests { None, None, std::collections::BTreeMap::new(), + None, + None, ) .await .unwrap(); diff --git a/crates/storage-pg/.sqlx/query-008ef8a2092fd2424ab6215bcad376b72f26b723dc029624396bb2030e53907c.json b/crates/storage-pg/.sqlx/query-4040e4d3cecfb2b25e0c3d141df525da902344c79b50e8dd205b1fffe58665d9.json similarity index 83% rename from crates/storage-pg/.sqlx/query-008ef8a2092fd2424ab6215bcad376b72f26b723dc029624396bb2030e53907c.json rename to crates/storage-pg/.sqlx/query-4040e4d3cecfb2b25e0c3d141df525da902344c79b50e8dd205b1fffe58665d9.json index 09b6c4efe..75b714d2b 100644 --- a/crates/storage-pg/.sqlx/query-008ef8a2092fd2424ab6215bcad376b72f26b723dc029624396bb2030e53907c.json +++ b/crates/storage-pg/.sqlx/query-4040e4d3cecfb2b25e0c3d141df525da902344c79b50e8dd205b1fffe58665d9.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT oauth2_authorization_grant_id\n , created_at\n , cancelled_at\n , fulfilled_at\n , exchanged_at\n , scope\n , state\n , redirect_uri\n , response_mode\n , nonce\n , oauth2_client_id\n , authorization_code\n , response_type_code\n , response_type_id_token\n , code_challenge\n , code_challenge_method\n , login_hint\n , locale\n , raw_parameters AS \"raw_parameters: Json>\"\n , user_session_id\n , oauth2_session_id\n FROM\n oauth2_authorization_grants\n\n WHERE oauth2_authorization_grant_id = $1\n ", + "query": "\n SELECT oauth2_authorization_grant_id\n , created_at\n , cancelled_at\n , fulfilled_at\n , exchanged_at\n , scope\n , state\n , redirect_uri\n , response_mode\n , nonce\n , oauth2_client_id\n , authorization_code\n , response_type_code\n , response_type_id_token\n , code_challenge\n , code_challenge_method\n , login_hint\n , locale\n , raw_parameters AS \"raw_parameters: Json>\"\n , target_user_id\n , target_user_session_id\n , user_session_id\n , oauth2_session_id\n FROM\n oauth2_authorization_grants\n\n WHERE oauth2_authorization_grant_id = $1\n ", "describe": { "columns": [ { @@ -100,11 +100,21 @@ }, { "ordinal": 19, - "name": "user_session_id", + "name": "target_user_id", "type_info": "Uuid" }, { "ordinal": 20, + "name": "target_user_session_id", + "type_info": "Uuid" + }, + { + "ordinal": 21, + "name": "user_session_id", + "type_info": "Uuid" + }, + { + "ordinal": 22, "name": "oauth2_session_id", "type_info": "Uuid" } @@ -135,8 +145,10 @@ true, true, true, + true, + true, true ] }, - "hash": "008ef8a2092fd2424ab6215bcad376b72f26b723dc029624396bb2030e53907c" + "hash": "4040e4d3cecfb2b25e0c3d141df525da902344c79b50e8dd205b1fffe58665d9" } diff --git a/crates/storage-pg/.sqlx/query-041c4ddff9b40ff5ba16c9aa1dd9c721998de6e5798e4423df9063519ee5ac4d.json b/crates/storage-pg/.sqlx/query-4f152ad116f636d513373e07ecddb9a2470a8f1c37401e27192bcf9927b12979.json similarity index 72% rename from crates/storage-pg/.sqlx/query-041c4ddff9b40ff5ba16c9aa1dd9c721998de6e5798e4423df9063519ee5ac4d.json rename to crates/storage-pg/.sqlx/query-4f152ad116f636d513373e07ecddb9a2470a8f1c37401e27192bcf9927b12979.json index 37d3db2ab..24618e692 100644 --- a/crates/storage-pg/.sqlx/query-041c4ddff9b40ff5ba16c9aa1dd9c721998de6e5798e4423df9063519ee5ac4d.json +++ b/crates/storage-pg/.sqlx/query-4f152ad116f636d513373e07ecddb9a2470a8f1c37401e27192bcf9927b12979.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO oauth2_authorization_grants (\n oauth2_authorization_grant_id,\n oauth2_client_id,\n redirect_uri,\n scope,\n state,\n nonce,\n response_mode,\n code_challenge,\n code_challenge_method,\n response_type_code,\n response_type_id_token,\n authorization_code,\n login_hint,\n locale,\n raw_parameters,\n created_at\n )\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)\n ", + "query": "\n INSERT INTO oauth2_authorization_grants (\n oauth2_authorization_grant_id,\n oauth2_client_id,\n redirect_uri,\n scope,\n state,\n nonce,\n response_mode,\n code_challenge,\n code_challenge_method,\n response_type_code,\n response_type_id_token,\n authorization_code,\n login_hint,\n locale,\n raw_parameters,\n target_user_id,\n target_user_session_id,\n created_at\n )\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)\n ", "describe": { "columns": [], "parameters": { @@ -20,10 +20,12 @@ "Text", "Text", "Jsonb", + "Uuid", + "Uuid", "Timestamptz" ] }, "nullable": [] }, - "hash": "041c4ddff9b40ff5ba16c9aa1dd9c721998de6e5798e4423df9063519ee5ac4d" + "hash": "4f152ad116f636d513373e07ecddb9a2470a8f1c37401e27192bcf9927b12979" } diff --git a/crates/storage-pg/.sqlx/query-2921f8cb19dedfc5524298ba4f42580244a4e5611d7aa8ade990bd5b2230f410.json b/crates/storage-pg/.sqlx/query-65e79b677464a0007cd8afe678953bbea9c52d1432815d3ff47f934d292cecf0.json similarity index 83% rename from crates/storage-pg/.sqlx/query-2921f8cb19dedfc5524298ba4f42580244a4e5611d7aa8ade990bd5b2230f410.json rename to crates/storage-pg/.sqlx/query-65e79b677464a0007cd8afe678953bbea9c52d1432815d3ff47f934d292cecf0.json index f79596d08..a7782be44 100644 --- a/crates/storage-pg/.sqlx/query-2921f8cb19dedfc5524298ba4f42580244a4e5611d7aa8ade990bd5b2230f410.json +++ b/crates/storage-pg/.sqlx/query-65e79b677464a0007cd8afe678953bbea9c52d1432815d3ff47f934d292cecf0.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT oauth2_authorization_grant_id\n , created_at\n , cancelled_at\n , fulfilled_at\n , exchanged_at\n , scope\n , state\n , redirect_uri\n , response_mode\n , nonce\n , oauth2_client_id\n , authorization_code\n , response_type_code\n , response_type_id_token\n , code_challenge\n , code_challenge_method\n , login_hint\n , locale\n , raw_parameters AS \"raw_parameters: Json>\"\n , user_session_id\n , oauth2_session_id\n FROM\n oauth2_authorization_grants\n\n WHERE authorization_code = $1\n ", + "query": "\n SELECT oauth2_authorization_grant_id\n , created_at\n , cancelled_at\n , fulfilled_at\n , exchanged_at\n , scope\n , state\n , redirect_uri\n , response_mode\n , nonce\n , oauth2_client_id\n , authorization_code\n , response_type_code\n , response_type_id_token\n , code_challenge\n , code_challenge_method\n , login_hint\n , locale\n , raw_parameters AS \"raw_parameters: Json>\"\n , target_user_id\n , target_user_session_id\n , user_session_id\n , oauth2_session_id\n FROM\n oauth2_authorization_grants\n\n WHERE authorization_code = $1\n ", "describe": { "columns": [ { @@ -100,11 +100,21 @@ }, { "ordinal": 19, - "name": "user_session_id", + "name": "target_user_id", "type_info": "Uuid" }, { "ordinal": 20, + "name": "target_user_session_id", + "type_info": "Uuid" + }, + { + "ordinal": 21, + "name": "user_session_id", + "type_info": "Uuid" + }, + { + "ordinal": 22, "name": "oauth2_session_id", "type_info": "Uuid" } @@ -135,8 +145,10 @@ true, true, true, + true, + true, true ] }, - "hash": "2921f8cb19dedfc5524298ba4f42580244a4e5611d7aa8ade990bd5b2230f410" + "hash": "65e79b677464a0007cd8afe678953bbea9c52d1432815d3ff47f934d292cecf0" } diff --git a/crates/storage-pg/migrations/20260622100000_oauth2_grants_target_session.sql b/crates/storage-pg/migrations/20260622100000_oauth2_grants_target_session.sql new file mode 100644 index 000000000..607a4eddd --- /dev/null +++ b/crates/storage-pg/migrations/20260622100000_oauth2_grants_target_session.sql @@ -0,0 +1,13 @@ +-- Copyright 2026 Element Creations Ltd. +-- +-- SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +-- Please see LICENSE files in the repository root for full details. + +-- Records the resolved outcome of verifying a hint on the authorization +-- request: the target user and a user session. +ALTER TABLE oauth2_authorization_grants + ADD COLUMN target_user_id UUID + REFERENCES users (user_id), + ADD COLUMN target_user_session_id UUID + REFERENCES user_sessions (user_session_id) + ON DELETE SET NULL; diff --git a/crates/storage-pg/migrations/20260625125256_oauth2_grants_target_session_idx.sql b/crates/storage-pg/migrations/20260625125256_oauth2_grants_target_session_idx.sql new file mode 100644 index 000000000..61a72aec5 --- /dev/null +++ b/crates/storage-pg/migrations/20260625125256_oauth2_grants_target_session_idx.sql @@ -0,0 +1,9 @@ +-- no-transaction +-- Copyright 2026 Element Creations Ltd. +-- +-- SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +-- Please see LICENSE files in the repository root for full details. + +CREATE INDEX CONCURRENTLY IF NOT EXISTS + oauth2_authorization_grants_target_session_fk + ON oauth2_authorization_grants (target_user_session_id); diff --git a/crates/storage-pg/src/oauth2/authorization_grant.rs b/crates/storage-pg/src/oauth2/authorization_grant.rs index 3be349300..57fd7e521 100644 --- a/crates/storage-pg/src/oauth2/authorization_grant.rs +++ b/crates/storage-pg/src/oauth2/authorization_grant.rs @@ -11,7 +11,7 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; use mas_data_model::{ AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, BrowserSession, Client, Clock, - Pkce, Session, UlidExt as _, + Pkce, Session, UlidExt as _, User, }; use mas_iana::oauth::PkceCodeChallengeMethod; use mas_storage::oauth2::OAuth2AuthorizationGrantRepository; @@ -57,6 +57,8 @@ struct GrantLookup { login_hint: Option, locale: Option, raw_parameters: Option>>, + target_user_id: Option, + target_user_session_id: Option, oauth2_client_id: Uuid, user_session_id: Option, oauth2_session_id: Option, @@ -179,6 +181,8 @@ impl TryFrom for AuthorizationGrant { login_hint: value.login_hint, locale: value.locale, raw_parameters: value.raw_parameters.map(|Json(x)| x).unwrap_or_default(), + target_user_id: value.target_user_id.map(Into::into), + target_user_session_id: value.target_user_session_id.map(Into::into), }) } } @@ -213,6 +217,8 @@ impl OAuth2AuthorizationGrantRepository for PgOAuth2AuthorizationGrantRepository login_hint: Option, locale: Option, raw_parameters: BTreeMap, + target_user: Option<&User>, + target_user_session: Option<&BrowserSession>, ) -> Result { let code_challenge = code .as_ref() @@ -223,6 +229,8 @@ impl OAuth2AuthorizationGrantRepository for PgOAuth2AuthorizationGrantRepository .and_then(|c| c.pkce.as_ref()) .map(|p| p.challenge_method.to_string()); let code_str = code.as_ref().map(|c| &c.code); + let target_user_id = target_user.map(|u| u.id); + let target_user_session_id = target_user_session.map(|s| s.id); let created_at = clock.now(); let id = Ulid::from_datetime_with_rng(created_at, rng); @@ -246,10 +254,12 @@ impl OAuth2AuthorizationGrantRepository for PgOAuth2AuthorizationGrantRepository login_hint, locale, raw_parameters, + target_user_id, + target_user_session_id, created_at ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) "#, Uuid::from(id), Uuid::from(client.id), @@ -266,6 +276,8 @@ impl OAuth2AuthorizationGrantRepository for PgOAuth2AuthorizationGrantRepository login_hint, locale, Json(&raw_parameters) as _, + target_user_id.map(Uuid::from), + target_user_session_id.map(Uuid::from), created_at, ) .traced() @@ -287,6 +299,8 @@ impl OAuth2AuthorizationGrantRepository for PgOAuth2AuthorizationGrantRepository login_hint, locale, raw_parameters, + target_user_id, + target_user_session_id, }) } @@ -322,6 +336,8 @@ impl OAuth2AuthorizationGrantRepository for PgOAuth2AuthorizationGrantRepository , login_hint , locale , raw_parameters AS "raw_parameters: Json>" + , target_user_id + , target_user_session_id , user_session_id , oauth2_session_id FROM @@ -374,6 +390,8 @@ impl OAuth2AuthorizationGrantRepository for PgOAuth2AuthorizationGrantRepository , login_hint , locale , raw_parameters AS "raw_parameters: Json>" + , target_user_id + , target_user_session_id , user_session_id , oauth2_session_id FROM diff --git a/crates/storage-pg/src/oauth2/mod.rs b/crates/storage-pg/src/oauth2/mod.rs index 0bdaf3f4a..94362e55c 100644 --- a/crates/storage-pg/src/oauth2/mod.rs +++ b/crates/storage-pg/src/oauth2/mod.rs @@ -148,6 +148,8 @@ mod tests { None, None, raw_parameters.clone(), + None, + None, ) .await .unwrap(); diff --git a/crates/storage/src/oauth2/authorization_grant.rs b/crates/storage/src/oauth2/authorization_grant.rs index e01c1946d..7127956d4 100644 --- a/crates/storage/src/oauth2/authorization_grant.rs +++ b/crates/storage/src/oauth2/authorization_grant.rs @@ -9,7 +9,7 @@ use std::collections::BTreeMap; use async_trait::async_trait; use mas_data_model::{ - AuthorizationCode, AuthorizationGrant, BrowserSession, Client, Clock, Session, + AuthorizationCode, AuthorizationGrant, BrowserSession, Client, Clock, Session, User, }; use oauth2_types::{requests::ResponseMode, scope::Scope}; use rand_core::RngCore; @@ -49,6 +49,9 @@ pub trait OAuth2AuthorizationGrantRepository: Send + Sync { /// * `raw_parameters`: The raw query parameters of the authorization /// request, used to template the parameters forwarded to the upstream /// provider + /// * `target_user`: The user resolved from a valid hint, if any + /// * `target_user_session`: The browser session resolved from the `sid` + /// claim of a valid hint, if any /// /// # Errors /// @@ -69,6 +72,8 @@ pub trait OAuth2AuthorizationGrantRepository: Send + Sync { login_hint: Option, locale: Option, raw_parameters: BTreeMap, + target_user: Option<&User>, + target_user_session: Option<&BrowserSession>, ) -> Result; /// Lookup an authorization grant by its ID @@ -180,6 +185,8 @@ repository_impl!(OAuth2AuthorizationGrantRepository: login_hint: Option, locale: Option, raw_parameters: BTreeMap, + target_user: Option<&User>, + target_user_session: Option<&BrowserSession>, ) -> Result; async fn lookup(&mut self, id: Ulid) -> Result, Self::Error>; diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index 98121a9ad..b1ad2c38b 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -788,9 +788,9 @@ impl ConsentContext { #[serde(tag = "grant_type")] enum PolicyViolationGrant { #[serde(rename = "authorization_code")] - Authorization(AuthorizationGrant), + Authorization(Box), #[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")] - DeviceCode(DeviceCodeGrant), + DeviceCode(Box), } /// Context used by the `policy_violation.html` template @@ -853,14 +853,14 @@ impl PolicyViolationContext { /// Constructs a context for the policy violation page for an authorization /// grant #[must_use] - pub const fn for_authorization_grant( + pub fn for_authorization_grant( grant: AuthorizationGrant, client: Client, violations: Vec, ) -> Self { let action = PostAuthAction::continue_grant(grant.id); Self { - grant: PolicyViolationGrant::Authorization(grant), + grant: PolicyViolationGrant::Authorization(Box::new(grant)), client, action, violations, @@ -870,14 +870,14 @@ impl PolicyViolationContext { /// Constructs a context for the policy violation page for a device code /// grant #[must_use] - pub const fn for_device_code_grant( + pub fn for_device_code_grant( grant: DeviceCodeGrant, client: Client, violations: Vec, ) -> Self { let action = PostAuthAction::continue_device_code_grant(grant.id); Self { - grant: PolicyViolationGrant::DeviceCode(grant), + grant: PolicyViolationGrant::DeviceCode(Box::new(grant)), client, action, violations,