You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-07 17:03:01 +03:00
Move the account-related options out of experimental
This commit is contained in:
@@ -145,6 +145,7 @@ impl Options {
|
|||||||
&config.matrix,
|
&config.matrix,
|
||||||
&config.experimental,
|
&config.experimental,
|
||||||
&config.passwords,
|
&config.passwords,
|
||||||
|
&config.account,
|
||||||
&config.captcha,
|
&config.captcha,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@@ -17,8 +17,8 @@ use std::process::ExitCode;
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use figment::Figment;
|
use figment::Figment;
|
||||||
use mas_config::{
|
use mas_config::{
|
||||||
BrandingConfig, CaptchaConfig, ConfigurationSection, ExperimentalConfig, MatrixConfig,
|
AccountConfig, BrandingConfig, CaptchaConfig, ConfigurationSection, ExperimentalConfig,
|
||||||
PasswordsConfig, TemplatesConfig,
|
MatrixConfig, PasswordsConfig, TemplatesConfig,
|
||||||
};
|
};
|
||||||
use mas_storage::{Clock, SystemClock};
|
use mas_storage::{Clock, SystemClock};
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
@@ -50,6 +50,7 @@ impl Options {
|
|||||||
let matrix_config = MatrixConfig::extract(figment)?;
|
let matrix_config = MatrixConfig::extract(figment)?;
|
||||||
let experimental_config = ExperimentalConfig::extract(figment)?;
|
let experimental_config = ExperimentalConfig::extract(figment)?;
|
||||||
let password_config = PasswordsConfig::extract(figment)?;
|
let password_config = PasswordsConfig::extract(figment)?;
|
||||||
|
let account_config = AccountConfig::extract(figment)?;
|
||||||
let captcha_config = CaptchaConfig::extract(figment)?;
|
let captcha_config = CaptchaConfig::extract(figment)?;
|
||||||
|
|
||||||
let clock = SystemClock::default();
|
let clock = SystemClock::default();
|
||||||
@@ -62,6 +63,7 @@ impl Options {
|
|||||||
&matrix_config,
|
&matrix_config,
|
||||||
&experimental_config,
|
&experimental_config,
|
||||||
&password_config,
|
&password_config,
|
||||||
|
&account_config,
|
||||||
&captcha_config,
|
&captcha_config,
|
||||||
)?;
|
)?;
|
||||||
let templates =
|
let templates =
|
||||||
|
@@ -54,6 +54,7 @@ impl Options {
|
|||||||
&config.matrix,
|
&config.matrix,
|
||||||
&config.experimental,
|
&config.experimental,
|
||||||
&config.passwords,
|
&config.passwords,
|
||||||
|
&config.account,
|
||||||
&config.captcha,
|
&config.captcha,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@@ -16,8 +16,9 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use mas_config::{
|
use mas_config::{
|
||||||
BrandingConfig, CaptchaConfig, DatabaseConfig, EmailConfig, EmailSmtpMode, EmailTransportKind,
|
AccountConfig, BrandingConfig, CaptchaConfig, DatabaseConfig, EmailConfig, EmailSmtpMode,
|
||||||
ExperimentalConfig, MatrixConfig, PasswordsConfig, PolicyConfig, TemplatesConfig,
|
EmailTransportKind, ExperimentalConfig, MatrixConfig, PasswordsConfig, PolicyConfig,
|
||||||
|
TemplatesConfig,
|
||||||
};
|
};
|
||||||
use mas_data_model::SiteConfig;
|
use mas_data_model::SiteConfig;
|
||||||
use mas_email::{MailTransport, Mailer};
|
use mas_email::{MailTransport, Mailer};
|
||||||
@@ -152,6 +153,7 @@ pub fn site_config_from_config(
|
|||||||
matrix_config: &MatrixConfig,
|
matrix_config: &MatrixConfig,
|
||||||
experimental_config: &ExperimentalConfig,
|
experimental_config: &ExperimentalConfig,
|
||||||
password_config: &PasswordsConfig,
|
password_config: &PasswordsConfig,
|
||||||
|
account_config: &AccountConfig,
|
||||||
captcha_config: &CaptchaConfig,
|
captcha_config: &CaptchaConfig,
|
||||||
) -> Result<SiteConfig, anyhow::Error> {
|
) -> Result<SiteConfig, anyhow::Error> {
|
||||||
let captcha = captcha_config_from_config(captcha_config)?;
|
let captcha = captcha_config_from_config(captcha_config)?;
|
||||||
@@ -164,13 +166,13 @@ pub fn site_config_from_config(
|
|||||||
imprint: branding_config.imprint.clone(),
|
imprint: branding_config.imprint.clone(),
|
||||||
password_login_enabled: password_config.enabled(),
|
password_login_enabled: password_config.enabled(),
|
||||||
password_registration_enabled: password_config.enabled()
|
password_registration_enabled: password_config.enabled()
|
||||||
&& experimental_config.password_registration_enabled,
|
&& account_config.password_registration_enabled,
|
||||||
email_change_allowed: experimental_config.email_change_allowed,
|
email_change_allowed: account_config.email_change_allowed,
|
||||||
displayname_change_allowed: experimental_config.displayname_change_allowed,
|
displayname_change_allowed: account_config.displayname_change_allowed,
|
||||||
password_change_allowed: password_config.enabled()
|
password_change_allowed: password_config.enabled()
|
||||||
&& experimental_config.password_change_allowed,
|
&& account_config.password_change_allowed,
|
||||||
account_recovery_allowed: password_config.enabled()
|
account_recovery_allowed: password_config.enabled()
|
||||||
&& experimental_config.account_recovery_enabled,
|
&& account_config.password_recovery_enabled,
|
||||||
captcha,
|
captcha,
|
||||||
minimum_password_complexity: password_config.minimum_complexity(),
|
minimum_password_complexity: password_config.minimum_complexity(),
|
||||||
})
|
})
|
||||||
|
99
crates/config/src/sections/account.rs
Normal file
99
crates/config/src/sections/account.rs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_false() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
|
const fn is_default_false(value: &bool) -> bool {
|
||||||
|
*value == default_false()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration section to configure features related to account management
|
||||||
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
|
#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)]
|
||||||
|
pub struct AccountConfig {
|
||||||
|
/// Whether users are allowed to change their email addresses. Defaults to
|
||||||
|
/// `true`.
|
||||||
|
#[serde(default = "default_true", skip_serializing_if = "is_default_true")]
|
||||||
|
pub email_change_allowed: bool,
|
||||||
|
|
||||||
|
/// Whether users are allowed to change their display names. Defaults to
|
||||||
|
/// `true`.
|
||||||
|
///
|
||||||
|
/// This should be in sync with the policy in the homeserver configuration.
|
||||||
|
#[serde(default = "default_true", skip_serializing_if = "is_default_true")]
|
||||||
|
pub displayname_change_allowed: bool,
|
||||||
|
|
||||||
|
/// Whether to enable self-service password registration. Defaults to
|
||||||
|
/// `false` if password authentication is enabled.
|
||||||
|
///
|
||||||
|
/// This has no effect if password login is disabled.
|
||||||
|
#[serde(default = "default_false", skip_serializing_if = "is_default_false")]
|
||||||
|
pub password_registration_enabled: bool,
|
||||||
|
|
||||||
|
/// Whether users are allowed to change their passwords. Defaults to `true`.
|
||||||
|
///
|
||||||
|
/// This has no effect if password login is disabled.
|
||||||
|
#[serde(default = "default_true", skip_serializing_if = "is_default_true")]
|
||||||
|
pub password_change_allowed: bool,
|
||||||
|
|
||||||
|
/// Whether email-based password recovery is enabled. Defaults to `false`.
|
||||||
|
///
|
||||||
|
/// This has no effect if password login is disabled.
|
||||||
|
#[serde(default = "default_false", skip_serializing_if = "is_default_false")]
|
||||||
|
pub password_recovery_enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AccountConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
email_change_allowed: default_true(),
|
||||||
|
displayname_change_allowed: default_true(),
|
||||||
|
password_registration_enabled: default_false(),
|
||||||
|
password_change_allowed: default_true(),
|
||||||
|
password_recovery_enabled: default_false(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountConfig {
|
||||||
|
/// Returns true if the configuration is the default one
|
||||||
|
pub(crate) fn is_default(&self) -> bool {
|
||||||
|
is_default_false(&self.password_registration_enabled)
|
||||||
|
&& is_default_true(&self.email_change_allowed)
|
||||||
|
&& is_default_true(&self.displayname_change_allowed)
|
||||||
|
&& is_default_true(&self.password_change_allowed)
|
||||||
|
&& is_default_false(&self.password_recovery_enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigurationSection for AccountConfig {
|
||||||
|
const PATH: Option<&'static str> = Some("account");
|
||||||
|
}
|
@@ -27,24 +27,6 @@ fn is_default_token_ttl(value: &Duration) -> bool {
|
|||||||
*value == default_token_ttl()
|
*value == default_token_ttl()
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn default_true() -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
|
||||||
const fn is_default_true(value: &bool) -> bool {
|
|
||||||
*value == default_true()
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn default_false() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
|
||||||
const fn is_default_false(value: &bool) -> bool {
|
|
||||||
*value == default_false()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration sections for experimental options
|
/// Configuration sections for experimental options
|
||||||
///
|
///
|
||||||
/// Do not change these options unless you know what you are doing.
|
/// Do not change these options unless you know what you are doing.
|
||||||
@@ -70,29 +52,6 @@ pub struct ExperimentalConfig {
|
|||||||
)]
|
)]
|
||||||
#[serde_as(as = "serde_with::DurationSeconds<i64>")]
|
#[serde_as(as = "serde_with::DurationSeconds<i64>")]
|
||||||
pub compat_token_ttl: Duration,
|
pub compat_token_ttl: Duration,
|
||||||
|
|
||||||
/// Whether to enable self-service password registration. Defaults to `true`
|
|
||||||
/// if password authentication is enabled.
|
|
||||||
#[serde(default = "default_true", skip_serializing_if = "is_default_true")]
|
|
||||||
pub password_registration_enabled: bool,
|
|
||||||
|
|
||||||
/// Whether users are allowed to change their email addresses. Defaults to
|
|
||||||
/// `true`.
|
|
||||||
#[serde(default = "default_true", skip_serializing_if = "is_default_true")]
|
|
||||||
pub email_change_allowed: bool,
|
|
||||||
|
|
||||||
/// Whether users are allowed to change their display names. Defaults to
|
|
||||||
/// `true`.
|
|
||||||
#[serde(default = "default_true", skip_serializing_if = "is_default_true")]
|
|
||||||
pub displayname_change_allowed: bool,
|
|
||||||
|
|
||||||
/// Whether users are allowed to change their passwords. Defaults to `true`.
|
|
||||||
#[serde(default = "default_true", skip_serializing_if = "is_default_true")]
|
|
||||||
pub password_change_allowed: bool,
|
|
||||||
|
|
||||||
/// Whether email-based account recovery is enabled. Defaults to `false`.
|
|
||||||
#[serde(default = "default_false", skip_serializing_if = "is_default_false")]
|
|
||||||
pub account_recovery_enabled: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExperimentalConfig {
|
impl Default for ExperimentalConfig {
|
||||||
@@ -100,24 +59,13 @@ impl Default for ExperimentalConfig {
|
|||||||
Self {
|
Self {
|
||||||
access_token_ttl: default_token_ttl(),
|
access_token_ttl: default_token_ttl(),
|
||||||
compat_token_ttl: default_token_ttl(),
|
compat_token_ttl: default_token_ttl(),
|
||||||
password_registration_enabled: default_true(),
|
|
||||||
email_change_allowed: default_true(),
|
|
||||||
displayname_change_allowed: default_true(),
|
|
||||||
password_change_allowed: default_true(),
|
|
||||||
account_recovery_enabled: default_false(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExperimentalConfig {
|
impl ExperimentalConfig {
|
||||||
pub(crate) fn is_default(&self) -> bool {
|
pub(crate) fn is_default(&self) -> bool {
|
||||||
is_default_token_ttl(&self.access_token_ttl)
|
is_default_token_ttl(&self.access_token_ttl) && is_default_token_ttl(&self.compat_token_ttl)
|
||||||
&& is_default_token_ttl(&self.compat_token_ttl)
|
|
||||||
&& is_default_true(&self.password_registration_enabled)
|
|
||||||
&& is_default_true(&self.email_change_allowed)
|
|
||||||
&& is_default_true(&self.displayname_change_allowed)
|
|
||||||
&& is_default_true(&self.password_change_allowed)
|
|
||||||
&& is_default_false(&self.account_recovery_enabled)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,6 +16,7 @@ use rand::Rng;
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
mod account;
|
||||||
mod branding;
|
mod branding;
|
||||||
mod captcha;
|
mod captcha;
|
||||||
mod clients;
|
mod clients;
|
||||||
@@ -32,6 +33,7 @@ mod templates;
|
|||||||
mod upstream_oauth2;
|
mod upstream_oauth2;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
|
account::AccountConfig,
|
||||||
branding::BrandingConfig,
|
branding::BrandingConfig,
|
||||||
captcha::{CaptchaConfig, CaptchaServiceKind},
|
captcha::{CaptchaConfig, CaptchaServiceKind},
|
||||||
clients::{ClientAuthMethodConfig, ClientConfig, ClientsConfig},
|
clients::{ClientAuthMethodConfig, ClientConfig, ClientsConfig},
|
||||||
@@ -113,6 +115,11 @@ pub struct RootConfig {
|
|||||||
#[serde(default, skip_serializing_if = "CaptchaConfig::is_default")]
|
#[serde(default, skip_serializing_if = "CaptchaConfig::is_default")]
|
||||||
pub captcha: CaptchaConfig,
|
pub captcha: CaptchaConfig,
|
||||||
|
|
||||||
|
/// Configuration section to configure features related to account
|
||||||
|
/// management
|
||||||
|
#[serde(default, skip_serializing_if = "AccountConfig::is_default")]
|
||||||
|
pub account: AccountConfig,
|
||||||
|
|
||||||
/// Experimental configuration options
|
/// Experimental configuration options
|
||||||
#[serde(default, skip_serializing_if = "ExperimentalConfig::is_default")]
|
#[serde(default, skip_serializing_if = "ExperimentalConfig::is_default")]
|
||||||
pub experimental: ExperimentalConfig,
|
pub experimental: ExperimentalConfig,
|
||||||
@@ -133,6 +140,7 @@ impl ConfigurationSection for RootConfig {
|
|||||||
self.upstream_oauth2.validate(figment)?;
|
self.upstream_oauth2.validate(figment)?;
|
||||||
self.branding.validate(figment)?;
|
self.branding.validate(figment)?;
|
||||||
self.captcha.validate(figment)?;
|
self.captcha.validate(figment)?;
|
||||||
|
self.account.validate(figment)?;
|
||||||
self.experimental.validate(figment)?;
|
self.experimental.validate(figment)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -163,6 +171,7 @@ impl RootConfig {
|
|||||||
upstream_oauth2: UpstreamOAuth2Config::default(),
|
upstream_oauth2: UpstreamOAuth2Config::default(),
|
||||||
branding: BrandingConfig::default(),
|
branding: BrandingConfig::default(),
|
||||||
captcha: CaptchaConfig::default(),
|
captcha: CaptchaConfig::default(),
|
||||||
|
account: AccountConfig::default(),
|
||||||
experimental: ExperimentalConfig::default(),
|
experimental: ExperimentalConfig::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -184,6 +193,7 @@ impl RootConfig {
|
|||||||
upstream_oauth2: UpstreamOAuth2Config::default(),
|
upstream_oauth2: UpstreamOAuth2Config::default(),
|
||||||
branding: BrandingConfig::default(),
|
branding: BrandingConfig::default(),
|
||||||
captcha: CaptchaConfig::default(),
|
captcha: CaptchaConfig::default(),
|
||||||
|
account: AccountConfig::default(),
|
||||||
experimental: ExperimentalConfig::default(),
|
experimental: ExperimentalConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,6 +231,9 @@ pub struct AppConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub captcha: CaptchaConfig,
|
pub captcha: CaptchaConfig,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub account: AccountConfig,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub experimental: ExperimentalConfig,
|
pub experimental: ExperimentalConfig,
|
||||||
}
|
}
|
||||||
@@ -237,6 +250,7 @@ impl ConfigurationSection for AppConfig {
|
|||||||
self.policy.validate(figment)?;
|
self.policy.validate(figment)?;
|
||||||
self.branding.validate(figment)?;
|
self.branding.validate(figment)?;
|
||||||
self.captcha.validate(figment)?;
|
self.captcha.validate(figment)?;
|
||||||
|
self.account.validate(figment)?;
|
||||||
self.experimental.validate(figment)?;
|
self.experimental.validate(figment)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@@ -193,6 +193,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"account": {
|
||||||
|
"description": "Configuration section to configure features related to account management",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/AccountConfig"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"experimental": {
|
"experimental": {
|
||||||
"description": "Experimental configuration options",
|
"description": "Experimental configuration options",
|
||||||
"allOf": [
|
"allOf": [
|
||||||
@@ -2107,6 +2115,32 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"AccountConfig": {
|
||||||
|
"description": "Configuration section to configure features related to account management",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email_change_allowed": {
|
||||||
|
"description": "Whether users are allowed to change their email addresses. Defaults to `true`.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"displayname_change_allowed": {
|
||||||
|
"description": "Whether users are allowed to change their display names. Defaults to `true`.\n\nThis should be in sync with the policy in the homeserver configuration.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"password_registration_enabled": {
|
||||||
|
"description": "Whether to enable self-service password registration. Defaults to `false` if password authentication is enabled.\n\nThis has no effect if password login is disabled.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"password_change_allowed": {
|
||||||
|
"description": "Whether users are allowed to change their passwords. Defaults to `true`.\n\nThis has no effect if password login is disabled.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"password_recovery_enabled": {
|
||||||
|
"description": "Whether email-based password recovery is enabled. Defaults to `false`.\n\nThis has no effect if password login is disabled.",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ExperimentalConfig": {
|
"ExperimentalConfig": {
|
||||||
"description": "Configuration sections for experimental options\n\nDo not change these options unless you know what you are doing.",
|
"description": "Configuration sections for experimental options\n\nDo not change these options unless you know what you are doing.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -2124,26 +2158,6 @@
|
|||||||
"format": "uint64",
|
"format": "uint64",
|
||||||
"maximum": 86400.0,
|
"maximum": 86400.0,
|
||||||
"minimum": 60.0
|
"minimum": 60.0
|
||||||
},
|
|
||||||
"password_registration_enabled": {
|
|
||||||
"description": "Whether to enable self-service password registration. Defaults to `true` if password authentication is enabled.",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"email_change_allowed": {
|
|
||||||
"description": "Whether users are allowed to change their email addresses. Defaults to `true`.",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"displayname_change_allowed": {
|
|
||||||
"description": "Whether users are allowed to change their display names. Defaults to `true`.",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"password_change_allowed": {
|
|
||||||
"description": "Whether users are allowed to change their passwords. Defaults to `true`.",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"account_recovery_enabled": {
|
|
||||||
"description": "Whether email-based account recovery is enabled. Defaults to `false`.",
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user