Add oauth.device_code_grant_enabled configuration option

This commit is contained in:
Hugh Nimmo-Smith committed 2026-04-07 11:13:56 +01:00
1 parent 0f4ce9eccb
commit 53e6d05f40
16 files changed
+360 -20

No files matched your search

+1
View File
@@ -154,6 +154,7 @@ impl Options {
&config.passwords,
&config.account,
&config.captcha,
&config.oauth,
)?;
// Load and compile the templates
+4 -1
View File
@@ -13,7 +13,7 @@ use clap::Parser;
use figment::Figment;
use mas_config::{
AccountConfig, BrandingConfig, CaptchaConfig, ConfigurationSection, ConfigurationSectionExt,
ExperimentalConfig, MatrixConfig, PasswordsConfig, TemplatesConfig,
ExperimentalConfig, MatrixConfig, OAuthConfig, PasswordsConfig, TemplatesConfig,
};
use mas_data_model::{Clock, SystemClock};
use rand::SeedableRng;
@@ -65,6 +65,8 @@ impl Options {
.map_err(anyhow::Error::from_boxed)?;
let captcha_config = CaptchaConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let oauth_config =
OAuthConfig::extract_or_default(figment).map_err(anyhow::Error::from_boxed)?;
let now = if stabilise {
DateTime::from_timestamp_secs(1_446_823_992).unwrap()
@@ -86,6 +88,7 @@ impl Options {
&password_config,
&account_config,
&captcha_config,
&oauth_config,
)?;
let templates = templates_from_config(
&template_config,
+1
View File
@@ -49,6 +49,7 @@ impl Options {
&config.passwords,
&config.account,
&config.captcha,
&config.oauth,
)?;
// Load and compile the templates
+4 -2
View File
@@ -9,8 +9,8 @@ use std::{sync::Arc, time::Duration};
use anyhow::Context;
use mas_config::{
AccountConfig, BrandingConfig, CaptchaConfig, DatabaseConfig, EmailConfig, EmailSmtpMode,
EmailTransportKind, ExperimentalConfig, HomeserverKind, MatrixConfig, PasswordsConfig,
PolicyConfig, TemplatesConfig,
EmailTransportKind, ExperimentalConfig, HomeserverKind, MatrixConfig, OAuthConfig,
PasswordsConfig, PolicyConfig, TemplatesConfig,
};
use mas_context::LogContext;
use mas_data_model::{SessionExpirationConfig, SessionLimitConfig, SiteConfig};
@@ -201,6 +201,7 @@ pub fn site_config_from_config(
password_config: &PasswordsConfig,
account_config: &AccountConfig,
captcha_config: &CaptchaConfig,
oauth_config: &OAuthConfig,
) -> Result<SiteConfig, anyhow::Error> {
let captcha = captcha_config_from_config(captcha_config)?;
let session_expiration = experimental_config
@@ -243,6 +244,7 @@ pub fn site_config_from_config(
soft_limit: c.soft_limit,
hard_limit: c.hard_limit,
}),
device_code_grant_enabled: oauth_config.device_code_grant_enabled,
})
}
+13
View File
@@ -19,6 +19,7 @@ mod email;
mod experimental;
mod http;
mod matrix;
mod oauth;
mod passwords;
mod policy;
mod rate_limiting;
@@ -40,6 +41,7 @@ pub use self::{
Resource as HttpResource, TlsConfig as HttpTlsConfig, UnixOrTcp,
},
matrix::{HomeserverKind, MatrixConfig},
oauth::OAuthConfig,
passwords::{
Algorithm as PasswordAlgorithm, HashingScheme as PasswordHashingScheme, PasswordsConfig,
},
@@ -126,6 +128,10 @@ pub struct RootConfig {
#[serde(default, skip_serializing_if = "AccountConfig::is_default")]
pub account: AccountConfig,
/// Configuration section for OAuth 2.0 protocol options
#[serde(default, skip_serializing_if = "OAuthConfig::is_default")]
pub oauth: OAuthConfig,
/// Experimental configuration options
#[serde(default, skip_serializing_if = "ExperimentalConfig::is_default")]
pub experimental: ExperimentalConfig,
@@ -151,6 +157,7 @@ impl ConfigurationSection for RootConfig {
self.branding.validate(figment)?;
self.captcha.validate(figment)?;
self.account.validate(figment)?;
self.oauth.validate(figment)?;
self.experimental.validate(figment)?;
Ok(())
@@ -183,6 +190,7 @@ impl RootConfig {
branding: BrandingConfig::default(),
captcha: CaptchaConfig::default(),
account: AccountConfig::default(),
oauth: OAuthConfig::default(),
experimental: ExperimentalConfig::default(),
})
}
@@ -206,6 +214,7 @@ impl RootConfig {
branding: BrandingConfig::default(),
captcha: CaptchaConfig::default(),
account: AccountConfig::default(),
oauth: OAuthConfig::default(),
experimental: ExperimentalConfig::default(),
}
}
@@ -249,6 +258,9 @@ pub struct AppConfig {
#[serde(default)]
pub account: AccountConfig,
#[serde(default)]
pub oauth: OAuthConfig,
#[serde(default)]
pub experimental: ExperimentalConfig,
}
@@ -270,6 +282,7 @@ impl ConfigurationSection for AppConfig {
self.branding.validate(figment)?;
self.captcha.validate(figment)?;
self.account.validate(figment)?;
self.oauth.validate(figment)?;
self.experimental.validate(figment)?;
Ok(())
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::ConfigurationSection;
const fn default_true() -> bool {
true
}
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn is_default_true(value: &bool) -> bool {
*value == default_true()
}
/// Configuration section for OAuth 2.0 protocol options
#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)]
pub struct OAuthConfig {
/// Whether the Device Authorization Grant (RFC 8628) is enabled. Defaults
/// to `true`.
///
/// When disabled, the device authorization endpoint will reject requests,
/// the discovery metadata will not advertise the device authorization
/// endpoint, and dynamic client registrations requesting the
/// `urn:ietf:params:oauth:grant-type:device_code` grant type will be
/// rejected.
#[serde(default = "default_true", skip_serializing_if = "is_default_true")]
pub device_code_grant_enabled: bool,
}
impl Default for OAuthConfig {
fn default() -> Self {
Self {
device_code_grant_enabled: default_true(),
}
}
}
impl OAuthConfig {
/// Returns true if the configuration is the default one
pub(crate) fn is_default(&self) -> bool {
is_default_true(&self.device_code_grant_enabled)
}
}
impl ConfigurationSection for OAuthConfig {
const PATH: Option<&'static str> = Some("oauth");
}
+3
View File
@@ -111,4 +111,7 @@ pub struct SiteConfig {
/// Limits on the number of application sessions that each user can have
pub session_limit: Option<SessionLimitConfig>,
/// Whether the Device Authorization Grant (RFC 8628) is enabled.
pub device_code_grant_enabled: bool,
}
+55 -3
View File
@@ -26,7 +26,7 @@ use rand::distributions::{Alphanumeric, DistString};
use thiserror::Error;
use ulid::Ulid;
use crate::{BoundActivityTracker, impl_from_error_for_route};
use crate::{BoundActivityTracker, SiteConfig, impl_from_error_for_route};
#[derive(Debug, Error)]
pub(crate) enum RouteError {
@@ -39,6 +39,9 @@ pub(crate) enum RouteError {
#[error("client {0} is not allowed to use the device code grant")]
ClientNotAllowed(Ulid),
#[error("the Device Authorization Grant is disabled")]
DeviceCodeGrantDisabled,
#[error("invalid client credentials for client {client_id}")]
InvalidClientCredentials {
client_id: Ulid,
@@ -73,6 +76,14 @@ impl IntoResponse for RouteError {
StatusCode::UNAUTHORIZED,
Json(ClientError::from(ClientErrorCode::UnauthorizedClient)),
),
Self::DeviceCodeGrantDisabled => (
StatusCode::BAD_REQUEST,
Json(
ClientError::from(ClientErrorCode::InvalidRequest).with_description(
"The Device Authorization Grant is disabled on this server".to_owned(),
),
),
),
};
(sentry_event_id, response).into_response()
@@ -93,8 +104,13 @@ pub(crate) async fn post(
State(url_builder): State<UrlBuilder>,
State(http_client): State<reqwest::Client>,
State(encrypter): State<Encrypter>,
State(site_config): State<SiteConfig>,
client_authorization: ClientAuthorization<DeviceAuthorizationRequest>,
) -> Result<impl IntoResponse, RouteError> {
if !site_config.device_code_grant_enabled {
return Err(RouteError::DeviceCodeGrantDisabled);
}
let client = client_authorization
.credentials
.fetch(&mut repo)
@@ -182,13 +198,16 @@ pub(crate) async fn post(
#[cfg(test)]
mod tests {
use hyper::{Request, StatusCode};
use mas_data_model::SiteConfig;
use mas_router::SimpleRoute;
use oauth2_types::{
registration::ClientRegistrationResponse, requests::DeviceAuthorizationResponse,
errors::{ClientError, ClientErrorCode},
registration::ClientRegistrationResponse,
requests::DeviceAuthorizationResponse,
};
use sqlx::PgPool;
use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};
use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup, test_site_config};
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_device_code_request(pool: PgPool) {
@@ -224,4 +243,37 @@ mod tests {
assert_eq!(response.device_code.len(), 32);
assert_eq!(response.user_code.len(), 6);
}
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_device_code_request_disabled(pool: PgPool) {
setup();
let state = TestState::from_pool_with_site_config(
pool,
SiteConfig {
device_code_grant_enabled: false,
..test_site_config()
},
)
.await
.unwrap();
// The endpoint rejects the request before looking up the client
let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
serde_json::json!({
"client_id": "some-client-id",
"scope": "openid",
}),
);
let response = state.request(request).await;
response.assert_status(StatusCode::BAD_REQUEST);
let error: ClientError = response.json();
assert_eq!(error.error, ClientErrorCode::InvalidRequest);
assert!(
error
.error_description
.unwrap()
.contains("Device Authorization Grant is disabled")
);
}
}
+13 -1
View File
@@ -29,7 +29,7 @@ use tracing::warn;
use ulid::Ulid;
use crate::{
BoundActivityTracker, PreferredLanguage,
BoundActivityTracker, PreferredLanguage, SiteConfig,
session::{SessionOrFallback, count_user_sessions_for_limiting, load_session_or_fallback},
};
@@ -53,6 +53,7 @@ pub(crate) async fn get(
State(templates): State<Templates>,
State(url_builder): State<UrlBuilder>,
State(homeserver): State<Arc<dyn HomeserverConnection>>,
State(site_config): State<SiteConfig>,
mut repo: BoxRepository,
mut policy: Policy,
activity_tracker: BoundActivityTracker,
@@ -60,6 +61,11 @@ pub(crate) async fn get(
cookie_jar: CookieJar,
Path(grant_id): Path<Ulid>,
) -> Result<Response, InternalError> {
if !site_config.device_code_grant_enabled {
return Err(InternalError::from_anyhow(anyhow::anyhow!(
"The Device Authorization Grant is disabled"
)));
}
let (cookie_jar, maybe_session) = match load_session_or_fallback(
cookie_jar, &clock, &mut rng, &templates, &locale, &mut repo,
)
@@ -191,6 +197,7 @@ pub(crate) async fn post(
State(templates): State<Templates>,
State(url_builder): State<UrlBuilder>,
State(homeserver): State<Arc<dyn HomeserverConnection>>,
State(site_config): State<SiteConfig>,
mut repo: BoxRepository,
mut policy: Policy,
activity_tracker: BoundActivityTracker,
@@ -199,6 +206,11 @@ pub(crate) async fn post(
Path(grant_id): Path<Ulid>,
Form(form): Form<ProtectedForm<ConsentForm>>,
) -> Result<Response, InternalError> {
if !site_config.device_code_grant_enabled {
return Err(InternalError::from_anyhow(anyhow::anyhow!(
"The Device Authorization Grant is disabled"
)));
}
let form = cookie_jar.verify_form(&clock, form)?;
let (cookie_jar, maybe_session) = match load_session_or_fallback(
cookie_jar, &clock, &mut rng, &templates, &locale, &mut repo,
+7 -1
View File
@@ -18,7 +18,7 @@ use mas_templates::{
};
use serde::{Deserialize, Serialize};
use crate::PreferredLanguage;
use crate::{PreferredLanguage, SiteConfig};
#[derive(Serialize, Deserialize)]
pub struct Params {
@@ -33,9 +33,15 @@ pub(crate) async fn get(
PreferredLanguage(locale): PreferredLanguage,
State(templates): State<Templates>,
State(url_builder): State<UrlBuilder>,
State(site_config): State<SiteConfig>,
cookie_jar: CookieJar,
Query(query): Query<Params>,
) -> Result<impl IntoResponse, InternalError> {
if !site_config.device_code_grant_enabled {
return Err(InternalError::from_anyhow(anyhow::anyhow!(
"The Device Authorization Grant is disabled"
)));
}
let mut form_state = FormState::from_form(&query);
// If we have a code in query, find it in the database
+50 -9
View File
@@ -59,7 +59,11 @@ pub(crate) async fn get(
let issuer = Some(url_builder.oidc_issuer().into());
let authorization_endpoint = Some(url_builder.oauth_authorization_endpoint());
let token_endpoint = Some(url_builder.oauth_token_endpoint());
let device_authorization_endpoint = Some(url_builder.oauth_device_authorization_endpoint());
let device_authorization_endpoint = if site_config.device_code_grant_enabled {
Some(url_builder.oauth_device_authorization_endpoint())
} else {
None
};
let jwks_uri = Some(url_builder.jwks_uri());
let introspection_endpoint = Some(url_builder.oauth_introspection_endpoint());
let revocation_endpoint = Some(url_builder.oauth_revocation_endpoint());
@@ -80,12 +84,17 @@ pub(crate) async fn get(
ResponseMode::Fragment,
]);
let grant_types_supported = Some(vec![
GrantType::AuthorizationCode,
GrantType::RefreshToken,
GrantType::ClientCredentials,
GrantType::DeviceCode,
]);
let grant_types_supported = {
let mut types = vec![
GrantType::AuthorizationCode,
GrantType::RefreshToken,
GrantType::ClientCredentials,
];
if site_config.device_code_grant_enabled {
types.push(GrantType::DeviceCode);
}
Some(types)
};
let token_endpoint_auth_methods_supported = client_auth_methods_supported.clone();
let token_endpoint_auth_signing_alg_values_supported =
@@ -199,10 +208,11 @@ pub(crate) async fn get(
#[cfg(test)]
mod tests {
use hyper::{Request, StatusCode};
use oauth2_types::oidc::ProviderMetadata;
use mas_data_model::SiteConfig;
use oauth2_types::{oidc::ProviderMetadata, requests::GrantType};
use sqlx::PgPool;
use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};
use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup, test_site_config};
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_valid_discovery_metadata(pool: PgPool) {
@@ -218,4 +228,35 @@ mod tests {
.validate(state.url_builder.oidc_issuer().as_str())
.expect("Invalid metadata");
}
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_discovery_device_code_grant_disabled(pool: PgPool) {
setup();
let state = TestState::from_pool_with_site_config(
pool,
SiteConfig {
device_code_grant_enabled: false,
..test_site_config()
},
)
.await
.unwrap();
let request = Request::get("/.well-known/openid-configuration").empty();
let response = state.request(request).await;
response.assert_status(StatusCode::OK);
let metadata: ProviderMetadata = response.json();
// The device_authorization_endpoint should not be present
assert!(metadata.device_authorization_endpoint.is_none());
// The grant_types_supported should not include device_code
let grant_types = metadata.grant_types_supported.as_ref().unwrap();
assert!(!grant_types.contains(&GrantType::DeviceCode));
metadata
.validate(state.url_builder.oidc_issuer().as_str())
.expect("Invalid metadata");
}
}
+72 -2
View File
@@ -21,6 +21,7 @@ use oauth2_types::{
ClientMetadata, ClientMetadataVerificationError, ClientRegistrationResponse, Localized,
VerifiedClientMetadata,
},
requests::GrantType,
};
use opentelemetry::{Key, KeyValue, metrics::Counter};
use psl::Psl;
@@ -31,7 +32,7 @@ use thiserror::Error;
use tracing::info;
use url::Url;
use crate::{BoundActivityTracker, METER, impl_from_error_for_route};
use crate::{BoundActivityTracker, METER, SiteConfig, impl_from_error_for_route};
static REGISTRATION_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
METER
@@ -58,6 +59,9 @@ pub(crate) enum RouteError {
#[error("client registration denied by the policy: {0}")]
PolicyDenied(EvaluationResult),
#[error("the Device Authorization Grant is disabled")]
DeviceCodeGrantDisabled,
}
impl_from_error_for_route!(mas_storage::RepositoryError);
@@ -169,6 +173,16 @@ impl IntoResponse for RouteError {
)
.into_response()
}
Self::DeviceCodeGrantDisabled => (
StatusCode::BAD_REQUEST,
Json(
ClientError::from(ClientErrorCode::InvalidClientMetadata).with_description(
"The Device Authorization Grant is disabled on this server".to_owned(),
),
),
)
.into_response(),
};
(sentry_event_id, response).into_response()
@@ -222,6 +236,7 @@ pub(crate) async fn post(
activity_tracker: BoundActivityTracker,
user_agent: Option<TypedHeader<headers::UserAgent>>,
State(encrypter): State<Encrypter>,
State(site_config): State<SiteConfig>,
body: Result<Json<ClientMetadata>, axum::extract::rejection::JsonRejection>,
) -> Result<impl IntoResponse, RouteError> {
// Propagate any JSON extraction error
@@ -240,6 +255,13 @@ pub(crate) async fn post(
// Validate the body
let metadata = body.validate()?;
// Reject if the client requests the device_code grant type and it's disabled
if !site_config.device_code_grant_enabled
&& metadata.grant_types().contains(&GrantType::DeviceCode)
{
return Err(RouteError::DeviceCodeGrantDisabled);
}
// Some extra validation that is hard to do in OPA and not done by the
// `validate` method either
if let Some(client_uri) = &metadata.client_uri
@@ -383,6 +405,7 @@ pub(crate) async fn post(
#[cfg(test)]
mod tests {
use hyper::{Request, StatusCode};
use mas_data_model::SiteConfig;
use mas_router::SimpleRoute;
use oauth2_types::{
errors::{ClientError, ClientErrorCode},
@@ -393,7 +416,7 @@ mod tests {
use crate::{
oauth2::registration::host_is_public_suffix,
test_utils::{RequestBuilderExt, ResponseExt, TestState, setup},
test_utils::{RequestBuilderExt, ResponseExt, TestState, setup, test_site_config},
};
#[test]
@@ -616,4 +639,51 @@ mod tests {
let response: ClientRegistrationResponse = response.json();
assert_ne!(response.client_id, client_id);
}
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_registration_device_code_grant_disabled(pool: PgPool) {
setup();
let state = TestState::from_pool_with_site_config(
pool,
SiteConfig {
device_code_grant_enabled: false,
..test_site_config()
},
)
.await
.unwrap();
// Registering a client that requests device_code grant should be rejected
let request =
Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
"client_uri": "https://example.com/",
"token_endpoint_auth_method": "none",
"grant_types": ["urn:ietf:params:oauth:grant-type:device_code"],
"response_types": [],
}));
let response = state.request(request).await;
response.assert_status(StatusCode::BAD_REQUEST);
let error: ClientError = response.json();
assert_eq!(error.error, ClientErrorCode::InvalidClientMetadata);
assert!(
error
.error_description
.unwrap()
.contains("Device Authorization Grant is disabled")
);
// Registering a client without device_code grant should still work
let request =
Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
"client_uri": "https://example.com/",
"redirect_uris": ["https://example.com/"],
"token_endpoint_auth_method": "none",
"response_types": ["code"],
"grant_types": ["authorization_code"],
}));
let response = state.request(request).await;
response.assert_status(StatusCode::CREATED);
}
}
+49 -1
View File
@@ -861,6 +861,11 @@ async fn device_code_grant(
homeserver: &Arc<dyn HomeserverConnection>,
user_agent: Option<String>,
) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
// Check that the Device Authorization Grant is enabled on this server
if !site_config.device_code_grant_enabled {
return Err(RouteError::UnsupportedGrantType);
}
// Check that the client is allowed to use this grant type
if !client.grant_types.contains(&GrantType::DeviceCode) {
return Err(RouteError::UnauthorizedClient(client.id));
@@ -1007,7 +1012,7 @@ mod tests {
use sqlx::PgPool;
use super::*;
use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};
use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup, test_site_config};
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_auth_code_grant(pool: PgPool) {
@@ -1916,4 +1921,47 @@ mod tests {
let ClientError { error, .. } = response.json();
assert_eq!(error, ClientErrorCode::UnsupportedGrantType);
}
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_device_code_grant_disabled(pool: PgPool) {
setup();
let state = TestState::from_pool_with_site_config(
pool,
SiteConfig {
device_code_grant_enabled: false,
..test_site_config()
},
)
.await
.unwrap();
// Provision a client (without device_code grant, since registration rejects it)
let request =
Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
"client_uri": "https://example.com/",
"redirect_uris": ["https://example.com/callback"],
"token_endpoint_auth_method": "none",
"response_types": ["code"],
"grant_types": ["authorization_code"],
}));
let response = state.request(request).await;
response.assert_status(StatusCode::CREATED);
let response: ClientRegistrationResponse = response.json();
let client_id = response.client_id;
// Attempt to use the device_code grant type at the token endpoint
let request =
Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": "fake-device-code",
"client_id": client_id,
}));
let response = state.request(request).await;
response.assert_status(StatusCode::BAD_REQUEST);
let ClientError { error, .. } = response.json();
assert_eq!(error, ClientErrorCode::UnsupportedGrantType);
}
}
+1
View File
@@ -150,6 +150,7 @@ pub fn test_site_config() -> SiteConfig {
login_with_email_allowed: true,
plan_management_iframe_uri: None,
session_limit: None,
device_code_grant_enabled: true,
}
}
+18
View File
@@ -204,6 +204,14 @@
}
]
},
"oauth": {
"description": "Configuration section for OAuth 2.0 protocol options",
"allOf": [
{
"$ref": "#/definitions/OAuthConfig"
}
]
},
"experimental": {
"description": "Experimental configuration options",
"allOf": [
@@ -2813,6 +2821,16 @@
}
}
},
"OAuthConfig": {
"description": "Configuration section for OAuth 2.0 protocol options",
"type": "object",
"properties": {
"device_code_grant_enabled": {
"description": "Whether the Device Authorization Grant (RFC 8628) is enabled. Defaults\n to `true`.\n\n When disabled, the device authorization endpoint will reject requests,\n the discovery metadata will not advertise the device authorization\n endpoint, and dynamic client registrations requesting the\n `urn:ietf:params:oauth:grant-type:device_code` grant type will be\n rejected.",
"type": "boolean"
}
}
},
"ExperimentalConfig": {
"description": "Configuration sections for experimental options\n\n Do not change these options unless you know what you are doing.",
"type": "object",
+17
View File
@@ -856,6 +856,23 @@ branding:
#logo_uri:
```
## `oauth`
Configuration section for OAuth 2.0 protocol options.
```yaml
oauth:
# Whether the Device Authorization Grant (RFC 8628) is enabled. Defaults
# to `true`.
#
# When disabled, the device authorization endpoint will reject requests,
# the discovery metadata will not advertise the device authorization
# endpoint, and dynamic client registrations requesting the
# `urn:ietf:params:oauth:grant-type:device_code` grant type will be
# rejected.
device_code_grant_enabled: true
```
## `experimental`
Settings that may change or be removed in future versions.