You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-06 06:02:40 +03:00
Gate account recovery behing a configuration flag
This commit is contained in:
@@ -170,6 +170,8 @@ pub fn site_config_from_config(
|
|||||||
displayname_change_allowed: experimental_config.displayname_change_allowed,
|
displayname_change_allowed: experimental_config.displayname_change_allowed,
|
||||||
password_change_allowed: password_config.enabled()
|
password_change_allowed: password_config.enabled()
|
||||||
&& experimental_config.password_change_allowed,
|
&& experimental_config.password_change_allowed,
|
||||||
|
account_recovery_allowed: password_config.enabled()
|
||||||
|
&& experimental_config.account_recovery_enabled,
|
||||||
captcha,
|
captcha,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -36,6 +36,15 @@ const fn is_default_true(value: &bool) -> bool {
|
|||||||
*value == default_true()
|
*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.
|
||||||
@@ -80,6 +89,10 @@ pub struct ExperimentalConfig {
|
|||||||
/// Whether users are allowed to change their passwords. Defaults to `true`.
|
/// Whether users are allowed to change their passwords. Defaults to `true`.
|
||||||
#[serde(default = "default_true", skip_serializing_if = "is_default_true")]
|
#[serde(default = "default_true", skip_serializing_if = "is_default_true")]
|
||||||
pub password_change_allowed: bool,
|
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 {
|
||||||
@@ -91,6 +104,7 @@ impl Default for ExperimentalConfig {
|
|||||||
email_change_allowed: default_true(),
|
email_change_allowed: default_true(),
|
||||||
displayname_change_allowed: default_true(),
|
displayname_change_allowed: default_true(),
|
||||||
password_change_allowed: default_true(),
|
password_change_allowed: default_true(),
|
||||||
|
account_recovery_enabled: default_false(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,6 +117,7 @@ impl ExperimentalConfig {
|
|||||||
&& is_default_true(&self.email_change_allowed)
|
&& is_default_true(&self.email_change_allowed)
|
||||||
&& is_default_true(&self.displayname_change_allowed)
|
&& is_default_true(&self.displayname_change_allowed)
|
||||||
&& is_default_true(&self.password_change_allowed)
|
&& is_default_true(&self.password_change_allowed)
|
||||||
|
&& is_default_false(&self.account_recovery_enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -73,6 +73,9 @@ pub struct SiteConfig {
|
|||||||
/// Whether users can change their password.
|
/// Whether users can change their password.
|
||||||
pub password_change_allowed: bool,
|
pub password_change_allowed: bool,
|
||||||
|
|
||||||
|
/// Whether users can recover their account via email.
|
||||||
|
pub account_recovery_allowed: bool,
|
||||||
|
|
||||||
/// Captcha configuration
|
/// Captcha configuration
|
||||||
pub captcha: Option<CaptchaConfig>,
|
pub captcha: Option<CaptchaConfig>,
|
||||||
}
|
}
|
||||||
|
@@ -133,6 +133,7 @@ pub fn test_site_config() -> SiteConfig {
|
|||||||
email_change_allowed: true,
|
email_change_allowed: true,
|
||||||
displayname_change_allowed: true,
|
displayname_change_allowed: true,
|
||||||
password_change_allowed: true,
|
password_change_allowed: true,
|
||||||
|
account_recovery_allowed: true,
|
||||||
captcha: None,
|
captcha: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,12 +23,13 @@ use mas_axum_utils::{
|
|||||||
csrf::{CsrfExt, ProtectedForm},
|
csrf::{CsrfExt, ProtectedForm},
|
||||||
FancyError,
|
FancyError,
|
||||||
};
|
};
|
||||||
|
use mas_data_model::SiteConfig;
|
||||||
use mas_policy::Policy;
|
use mas_policy::Policy;
|
||||||
use mas_router::UrlBuilder;
|
use mas_router::UrlBuilder;
|
||||||
use mas_storage::{BoxClock, BoxRepository, BoxRng};
|
use mas_storage::{BoxClock, BoxRepository, BoxRng};
|
||||||
use mas_templates::{
|
use mas_templates::{
|
||||||
ErrorContext, FieldError, FormState, RecoveryFinishContext, RecoveryFinishFormField,
|
EmptyContext, ErrorContext, FieldError, FormState, RecoveryFinishContext,
|
||||||
TemplateContext, Templates,
|
RecoveryFinishFormField, TemplateContext, Templates,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
@@ -50,11 +51,18 @@ pub(crate) async fn get(
|
|||||||
mut rng: BoxRng,
|
mut rng: BoxRng,
|
||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
|
State(site_config): State<SiteConfig>,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
Query(query): Query<RouteQuery>,
|
Query(query): Query<RouteQuery>,
|
||||||
) -> Result<Response, FancyError> {
|
) -> Result<Response, FancyError> {
|
||||||
|
if !site_config.account_recovery_allowed {
|
||||||
|
let context = EmptyContext.with_language(locale);
|
||||||
|
let rendered = templates.render_recovery_disabled(&context)?;
|
||||||
|
return Ok((cookie_jar, Html(rendered)).into_response());
|
||||||
|
}
|
||||||
|
|
||||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
||||||
|
|
||||||
let ticket = repo
|
let ticket = repo
|
||||||
@@ -117,6 +125,7 @@ pub(crate) async fn post(
|
|||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
mut policy: Policy,
|
mut policy: Policy,
|
||||||
|
State(site_config): State<SiteConfig>,
|
||||||
State(password_manager): State<PasswordManager>,
|
State(password_manager): State<PasswordManager>,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
State(url_builder): State<UrlBuilder>,
|
State(url_builder): State<UrlBuilder>,
|
||||||
@@ -125,6 +134,12 @@ pub(crate) async fn post(
|
|||||||
Query(query): Query<RouteQuery>,
|
Query(query): Query<RouteQuery>,
|
||||||
Form(form): Form<ProtectedForm<RouteForm>>,
|
Form(form): Form<ProtectedForm<RouteForm>>,
|
||||||
) -> Result<Response, FancyError> {
|
) -> Result<Response, FancyError> {
|
||||||
|
if !site_config.account_recovery_allowed {
|
||||||
|
let context = EmptyContext.with_language(locale);
|
||||||
|
let rendered = templates.render_recovery_disabled(&context)?;
|
||||||
|
return Ok((cookie_jar, Html(rendered)).into_response());
|
||||||
|
}
|
||||||
|
|
||||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
||||||
|
|
||||||
let ticket = repo
|
let ticket = repo
|
||||||
|
@@ -22,12 +22,13 @@ use mas_axum_utils::{
|
|||||||
csrf::{CsrfExt, ProtectedForm},
|
csrf::{CsrfExt, ProtectedForm},
|
||||||
FancyError, SessionInfoExt,
|
FancyError, SessionInfoExt,
|
||||||
};
|
};
|
||||||
|
use mas_data_model::SiteConfig;
|
||||||
use mas_router::UrlBuilder;
|
use mas_router::UrlBuilder;
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
job::{JobRepositoryExt, SendAccountRecoveryEmailsJob},
|
job::{JobRepositoryExt, SendAccountRecoveryEmailsJob},
|
||||||
BoxClock, BoxRepository, BoxRng,
|
BoxClock, BoxRepository, BoxRng,
|
||||||
};
|
};
|
||||||
use mas_templates::{RecoveryProgressContext, TemplateContext, Templates};
|
use mas_templates::{EmptyContext, RecoveryProgressContext, TemplateContext, Templates};
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
use crate::PreferredLanguage;
|
use crate::PreferredLanguage;
|
||||||
@@ -36,12 +37,19 @@ pub(crate) async fn get(
|
|||||||
mut rng: BoxRng,
|
mut rng: BoxRng,
|
||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
|
State(site_config): State<SiteConfig>,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
State(url_builder): State<UrlBuilder>,
|
State(url_builder): State<UrlBuilder>,
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
Path(id): Path<Ulid>,
|
Path(id): Path<Ulid>,
|
||||||
) -> Result<Response, FancyError> {
|
) -> Result<Response, FancyError> {
|
||||||
|
if !site_config.account_recovery_allowed {
|
||||||
|
let context = EmptyContext.with_language(locale);
|
||||||
|
let rendered = templates.render_recovery_disabled(&context)?;
|
||||||
|
return Ok((cookie_jar, Html(rendered)).into_response());
|
||||||
|
}
|
||||||
|
|
||||||
let (session_info, cookie_jar) = cookie_jar.session_info();
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
||||||
|
|
||||||
@@ -75,6 +83,7 @@ pub(crate) async fn post(
|
|||||||
mut rng: BoxRng,
|
mut rng: BoxRng,
|
||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
|
State(site_config): State<SiteConfig>,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
State(url_builder): State<UrlBuilder>,
|
State(url_builder): State<UrlBuilder>,
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
@@ -82,6 +91,12 @@ pub(crate) async fn post(
|
|||||||
Path(id): Path<Ulid>,
|
Path(id): Path<Ulid>,
|
||||||
Form(form): Form<ProtectedForm<()>>,
|
Form(form): Form<ProtectedForm<()>>,
|
||||||
) -> Result<Response, FancyError> {
|
) -> Result<Response, FancyError> {
|
||||||
|
if !site_config.account_recovery_allowed {
|
||||||
|
let context = EmptyContext.with_language(locale);
|
||||||
|
let rendered = templates.render_recovery_disabled(&context)?;
|
||||||
|
return Ok((cookie_jar, Html(rendered)).into_response());
|
||||||
|
}
|
||||||
|
|
||||||
let (session_info, cookie_jar) = cookie_jar.session_info();
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
||||||
|
|
||||||
|
@@ -25,14 +25,15 @@ use mas_axum_utils::{
|
|||||||
csrf::{CsrfExt, ProtectedForm},
|
csrf::{CsrfExt, ProtectedForm},
|
||||||
FancyError, SessionInfoExt,
|
FancyError, SessionInfoExt,
|
||||||
};
|
};
|
||||||
use mas_data_model::UserAgent;
|
use mas_data_model::{SiteConfig, UserAgent};
|
||||||
use mas_router::UrlBuilder;
|
use mas_router::UrlBuilder;
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
job::{JobRepositoryExt, SendAccountRecoveryEmailsJob},
|
job::{JobRepositoryExt, SendAccountRecoveryEmailsJob},
|
||||||
BoxClock, BoxRepository, BoxRng,
|
BoxClock, BoxRepository, BoxRng,
|
||||||
};
|
};
|
||||||
use mas_templates::{
|
use mas_templates::{
|
||||||
FieldError, FormState, RecoveryStartContext, RecoveryStartFormField, TemplateContext, Templates,
|
EmptyContext, FieldError, FormState, RecoveryStartContext, RecoveryStartFormField,
|
||||||
|
TemplateContext, Templates,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -47,11 +48,18 @@ pub(crate) async fn get(
|
|||||||
mut rng: BoxRng,
|
mut rng: BoxRng,
|
||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
|
State(site_config): State<SiteConfig>,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
State(url_builder): State<UrlBuilder>,
|
State(url_builder): State<UrlBuilder>,
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
) -> Result<Response, FancyError> {
|
) -> Result<Response, FancyError> {
|
||||||
|
if !site_config.account_recovery_allowed {
|
||||||
|
let context = EmptyContext.with_language(locale);
|
||||||
|
let rendered = templates.render_recovery_disabled(&context)?;
|
||||||
|
return Ok((cookie_jar, Html(rendered)).into_response());
|
||||||
|
}
|
||||||
|
|
||||||
let (session_info, cookie_jar) = cookie_jar.session_info();
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
||||||
|
|
||||||
@@ -78,12 +86,19 @@ pub(crate) async fn post(
|
|||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
user_agent: TypedHeader<headers::UserAgent>,
|
user_agent: TypedHeader<headers::UserAgent>,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
|
State(site_config): State<SiteConfig>,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
State(url_builder): State<UrlBuilder>,
|
State(url_builder): State<UrlBuilder>,
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
Form(form): Form<ProtectedForm<StartRecoveryForm>>,
|
Form(form): Form<ProtectedForm<StartRecoveryForm>>,
|
||||||
) -> Result<impl IntoResponse, FancyError> {
|
) -> Result<impl IntoResponse, FancyError> {
|
||||||
|
if !site_config.account_recovery_allowed {
|
||||||
|
let context = EmptyContext.with_language(locale);
|
||||||
|
let rendered = templates.render_recovery_disabled(&context)?;
|
||||||
|
return Ok((cookie_jar, Html(rendered)).into_response());
|
||||||
|
}
|
||||||
|
|
||||||
let (session_info, cookie_jar) = cookie_jar.session_info();
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
||||||
|
|
||||||
|
@@ -54,6 +54,7 @@ impl SiteConfigExt for SiteConfig {
|
|||||||
SiteFeatures {
|
SiteFeatures {
|
||||||
password_registration: self.password_registration_enabled,
|
password_registration: self.password_registration_enabled,
|
||||||
password_login: self.password_login_enabled,
|
password_login: self.password_login_enabled,
|
||||||
|
account_recovery: self.account_recovery_allowed,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,9 @@ pub struct SiteFeatures {
|
|||||||
|
|
||||||
/// Whether local password-based login is enabled.
|
/// Whether local password-based login is enabled.
|
||||||
pub password_login: bool,
|
pub password_login: bool,
|
||||||
|
|
||||||
|
/// Whether email-based account recovery is enabled.
|
||||||
|
pub account_recovery: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object for SiteFeatures {
|
impl Object for SiteFeatures {
|
||||||
@@ -34,11 +37,16 @@ impl Object for SiteFeatures {
|
|||||||
match field.as_str()? {
|
match field.as_str()? {
|
||||||
"password_registration" => Some(Value::from(self.password_registration)),
|
"password_registration" => Some(Value::from(self.password_registration)),
|
||||||
"password_login" => Some(Value::from(self.password_login)),
|
"password_login" => Some(Value::from(self.password_login)),
|
||||||
|
"account_recovery" => Some(Value::from(self.account_recovery)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enumerate(self: &Arc<Self>) -> Enumerator {
|
fn enumerate(self: &Arc<Self>) -> Enumerator {
|
||||||
Enumerator::Str(&["password_registration", "password_login"])
|
Enumerator::Str(&[
|
||||||
|
"password_registration",
|
||||||
|
"password_login",
|
||||||
|
"account_recovery",
|
||||||
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -357,6 +357,9 @@ register_templates! {
|
|||||||
/// Render the account recovery finish page
|
/// Render the account recovery finish page
|
||||||
pub fn render_recovery_finish(WithLanguage<WithCsrf<RecoveryFinishContext>>) { "pages/recovery/finish.html" }
|
pub fn render_recovery_finish(WithLanguage<WithCsrf<RecoveryFinishContext>>) { "pages/recovery/finish.html" }
|
||||||
|
|
||||||
|
/// Render the account recovery disabled page
|
||||||
|
pub fn render_recovery_disabled(WithLanguage<EmptyContext>) { "pages/recovery/disabled.html" }
|
||||||
|
|
||||||
/// Render the re-authentication form
|
/// Render the re-authentication form
|
||||||
pub fn render_reauth(WithLanguage<WithCsrf<WithSession<ReauthContext>>>) { "pages/reauth.html" }
|
pub fn render_reauth(WithLanguage<WithCsrf<WithSession<ReauthContext>>>) { "pages/reauth.html" }
|
||||||
|
|
||||||
@@ -425,6 +428,7 @@ impl Templates {
|
|||||||
check::render_recovery_start(self, now, rng)?;
|
check::render_recovery_start(self, now, rng)?;
|
||||||
check::render_recovery_progress(self, now, rng)?;
|
check::render_recovery_progress(self, now, rng)?;
|
||||||
check::render_recovery_finish(self, now, rng)?;
|
check::render_recovery_finish(self, now, rng)?;
|
||||||
|
check::render_recovery_disabled(self, now, rng)?;
|
||||||
check::render_reauth(self, now, rng)?;
|
check::render_reauth(self, now, rng)?;
|
||||||
check::render_form_post::<EmptyContext>(self, now, rng)?;
|
check::render_form_post::<EmptyContext>(self, now, rng)?;
|
||||||
check::render_error(self, now, rng)?;
|
check::render_error(self, now, rng)?;
|
||||||
@@ -455,6 +459,7 @@ mod tests {
|
|||||||
let features = SiteFeatures {
|
let features = SiteFeatures {
|
||||||
password_login: true,
|
password_login: true,
|
||||||
password_registration: true,
|
password_registration: true,
|
||||||
|
account_recovery: true,
|
||||||
};
|
};
|
||||||
let vite_manifest_path =
|
let vite_manifest_path =
|
||||||
Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("../../frontend/dist/manifest.json");
|
Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("../../frontend/dist/manifest.json");
|
||||||
|
@@ -2038,6 +2038,10 @@
|
|||||||
"password_change_allowed": {
|
"password_change_allowed": {
|
||||||
"description": "Whether users are allowed to change their passwords. Defaults to `true`.",
|
"description": "Whether users are allowed to change their passwords. Defaults to `true`.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"account_recovery_enabled": {
|
||||||
|
"description": "Whether email-based account recovery is enabled. Defaults to `false`.",
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ limitations under the License.
|
|||||||
{% from "components/idp_brand.html" import logo %}
|
{% from "components/idp_brand.html" import logo %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="flex flex-col gap-6">
|
<main class="flex flex-col gap-10">
|
||||||
{% if features.password_login %}
|
{% if features.password_login %}
|
||||||
<header class="page-heading">
|
<header class="page-heading">
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
@@ -58,6 +58,10 @@ limitations under the License.
|
|||||||
{% call(f) field.field(label=_("common.password"), name="password", form_state=form) %}
|
{% call(f) field.field(label=_("common.password"), name="password", form_state=form) %}
|
||||||
<input {{ field.attributes(f) }} class="cpd-text-control" type="password" autocomplete="password" required />
|
<input {{ field.attributes(f) }} class="cpd-text-control" type="password" autocomplete="password" required />
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
|
|
||||||
|
{% if features.account_recovery %}
|
||||||
|
{{ button.link_text(text=_("mas.login.forgot_password"), href="/recover", class="self-center") }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{{ button.button(text=_("action.continue")) }}
|
{{ button.button(text=_("action.continue")) }}
|
||||||
</form>
|
</form>
|
||||||
|
32
templates/pages/recovery/disabled.html
Normal file
32
templates/pages/recovery/disabled.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{#
|
||||||
|
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.
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<header class="page-heading">
|
||||||
|
<div class="icon invalid">
|
||||||
|
{{ icon.lock_solid() }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<h1 class="title">{{ _("mas.recovery.disabled.heading") }}</h1>
|
||||||
|
<p class="text">{{ _("mas.recovery.disabled.description") }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ button.link_outline(text=_("action.back"), href="/login") }}
|
||||||
|
</header>
|
||||||
|
{% endblock content %}
|
@@ -1,16 +1,20 @@
|
|||||||
{
|
{
|
||||||
"action": {
|
"action": {
|
||||||
|
"back": "Back",
|
||||||
|
"@back": {
|
||||||
|
"context": "pages/recovery/disabled.html:30:32-48"
|
||||||
|
},
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"@cancel": {
|
"@cancel": {
|
||||||
"context": "pages/consent.html:75:11-29, pages/device_consent.html:132:13-31, pages/login.html:100:13-31, pages/policy_violation.html:52:13-31, pages/register.html:89:13-31"
|
"context": "pages/consent.html:75:11-29, pages/device_consent.html:132:13-31, pages/login.html:104:13-31, pages/policy_violation.html:52:13-31, pages/register.html:89:13-31"
|
||||||
},
|
},
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"@continue": {
|
"@continue": {
|
||||||
"context": "pages/account/emails/add.html:45:26-46, pages/account/emails/verify.html:60:26-46, pages/consent.html:63:28-48, pages/device_consent.html:129:13-33, pages/device_link.html:48:26-46, pages/login.html:62:30-50, pages/reauth.html:40:28-48, pages/recovery/start.html:45:26-46, pages/register.html:84:28-48, pages/sso.html:45:28-48"
|
"context": "pages/account/emails/add.html:45:26-46, pages/account/emails/verify.html:60:26-46, pages/consent.html:63:28-48, pages/device_consent.html:129:13-33, pages/device_link.html:48:26-46, pages/login.html:66:30-50, pages/reauth.html:40:28-48, pages/recovery/start.html:46:26-46, pages/register.html:84:28-48, pages/sso.html:45:28-48"
|
||||||
},
|
},
|
||||||
"create_account": "Create Account",
|
"create_account": "Create Account",
|
||||||
"@create_account": {
|
"@create_account": {
|
||||||
"context": "pages/login.html:72:35-61, pages/upstream_oauth2/do_register.html:157:26-52"
|
"context": "pages/login.html:76:35-61, pages/upstream_oauth2/do_register.html:157:26-52"
|
||||||
},
|
},
|
||||||
"sign_in": "Sign in",
|
"sign_in": "Sign in",
|
||||||
"@sign_in": {
|
"@sign_in": {
|
||||||
@@ -294,17 +298,22 @@
|
|||||||
"login": {
|
"login": {
|
||||||
"call_to_register": "Don't have an account yet?",
|
"call_to_register": "Don't have an account yet?",
|
||||||
"@call_to_register": {
|
"@call_to_register": {
|
||||||
"context": "pages/login.html:68:15-46"
|
"context": "pages/login.html:72:15-46"
|
||||||
},
|
},
|
||||||
"continue_with_provider": "Continue with %(provider)s",
|
"continue_with_provider": "Continue with %(provider)s",
|
||||||
"@continue_with_provider": {
|
"@continue_with_provider": {
|
||||||
"context": "pages/login.html:87:13-65",
|
"context": "pages/login.html:91:13-65",
|
||||||
"description": "Button to log in with an upstream provider"
|
"description": "Button to log in with an upstream provider"
|
||||||
},
|
},
|
||||||
"description": "Please sign in to continue:",
|
"description": "Please sign in to continue:",
|
||||||
"@description": {
|
"@description": {
|
||||||
"context": "pages/login.html:38:31-57"
|
"context": "pages/login.html:38:31-57"
|
||||||
},
|
},
|
||||||
|
"forgot_password": "Forgot password?",
|
||||||
|
"@forgot_password": {
|
||||||
|
"context": "pages/login.html:63:35-65",
|
||||||
|
"description": "On the login page, link to the account recovery process"
|
||||||
|
},
|
||||||
"headline": "Sign in",
|
"headline": "Sign in",
|
||||||
"@headline": {
|
"@headline": {
|
||||||
"context": "pages/login.html:37:33-56"
|
"context": "pages/login.html:37:33-56"
|
||||||
@@ -321,7 +330,7 @@
|
|||||||
},
|
},
|
||||||
"no_login_methods": "No login methods available.",
|
"no_login_methods": "No login methods available.",
|
||||||
"@no_login_methods": {
|
"@no_login_methods": {
|
||||||
"context": "pages/login.html:94:11-42"
|
"context": "pages/login.html:98:11-42"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"navbar": {
|
"navbar": {
|
||||||
@@ -376,6 +385,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"recovery": {
|
"recovery": {
|
||||||
|
"disabled": {
|
||||||
|
"description": "If you have lost your credentials, please contact the administrator to recover your account.",
|
||||||
|
"@description": {
|
||||||
|
"context": "pages/recovery/disabled.html:27:25-63"
|
||||||
|
},
|
||||||
|
"heading": "Account recovery is disabled",
|
||||||
|
"@heading": {
|
||||||
|
"context": "pages/recovery/disabled.html:26:27-61"
|
||||||
|
}
|
||||||
|
},
|
||||||
"finish": {
|
"finish": {
|
||||||
"confirm": "Enter new password again",
|
"confirm": "Enter new password again",
|
||||||
"@confirm": {
|
"@confirm": {
|
||||||
|
Reference in New Issue
Block a user