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
Render reCAPTCHA challenge on the registration form
This commit is contained in:
@@ -145,7 +145,8 @@ impl Options {
|
||||
&config.matrix,
|
||||
&config.experimental,
|
||||
&config.passwords,
|
||||
);
|
||||
&config.captcha,
|
||||
)?;
|
||||
|
||||
// Load and compile the templates
|
||||
let templates =
|
||||
|
@@ -15,8 +15,8 @@
|
||||
use clap::Parser;
|
||||
use figment::Figment;
|
||||
use mas_config::{
|
||||
BrandingConfig, ConfigurationSection, ExperimentalConfig, MatrixConfig, PasswordsConfig,
|
||||
TemplatesConfig,
|
||||
BrandingConfig, CaptchaConfig, ConfigurationSection, ExperimentalConfig, MatrixConfig,
|
||||
PasswordsConfig, TemplatesConfig,
|
||||
};
|
||||
use mas_storage::{Clock, SystemClock};
|
||||
use rand::SeedableRng;
|
||||
@@ -48,6 +48,7 @@ impl Options {
|
||||
let matrix_config = MatrixConfig::extract(figment)?;
|
||||
let experimental_config = ExperimentalConfig::extract(figment)?;
|
||||
let password_config = PasswordsConfig::extract(figment)?;
|
||||
let captcha_config = CaptchaConfig::extract(figment)?;
|
||||
|
||||
let clock = SystemClock::default();
|
||||
// XXX: we should disallow SeedableRng::from_entropy
|
||||
@@ -59,7 +60,8 @@ impl Options {
|
||||
&matrix_config,
|
||||
&experimental_config,
|
||||
&password_config,
|
||||
);
|
||||
&captcha_config,
|
||||
)?;
|
||||
let templates =
|
||||
templates_from_config(&template_config, &site_config, &url_builder).await?;
|
||||
templates.check_render(clock.now(), &mut rng)?;
|
||||
|
@@ -52,7 +52,8 @@ impl Options {
|
||||
&config.matrix,
|
||||
&config.experimental,
|
||||
&config.passwords,
|
||||
);
|
||||
&config.captcha,
|
||||
)?;
|
||||
|
||||
// Load and compile the templates
|
||||
let templates =
|
||||
|
@@ -16,7 +16,7 @@ use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use mas_config::{
|
||||
BrandingConfig, DatabaseConfig, EmailConfig, EmailSmtpMode, EmailTransportKind,
|
||||
BrandingConfig, CaptchaConfig, DatabaseConfig, EmailConfig, EmailSmtpMode, EmailTransportKind,
|
||||
ExperimentalConfig, MatrixConfig, PasswordsConfig, PolicyConfig, TemplatesConfig,
|
||||
};
|
||||
use mas_data_model::SiteConfig;
|
||||
@@ -120,13 +120,39 @@ pub async fn policy_factory_from_config(
|
||||
.context("failed to load the policy")
|
||||
}
|
||||
|
||||
pub fn captcha_config_from_config(
|
||||
captcha_config: &CaptchaConfig,
|
||||
) -> Result<Option<mas_data_model::CaptchaConfig>, anyhow::Error> {
|
||||
let Some(service) = captcha_config.service else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let service = match service {
|
||||
mas_config::CaptchaServiceKind::RecaptchaV2 => mas_data_model::CaptchaService::RecaptchaV2,
|
||||
};
|
||||
|
||||
Ok(Some(mas_data_model::CaptchaConfig {
|
||||
service,
|
||||
site_key: captcha_config
|
||||
.site_key
|
||||
.clone()
|
||||
.context("missing site key")?,
|
||||
secret_key: captcha_config
|
||||
.secret_key
|
||||
.clone()
|
||||
.context("missing secret key")?,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn site_config_from_config(
|
||||
branding_config: &BrandingConfig,
|
||||
matrix_config: &MatrixConfig,
|
||||
experimental_config: &ExperimentalConfig,
|
||||
password_config: &PasswordsConfig,
|
||||
) -> SiteConfig {
|
||||
SiteConfig {
|
||||
captcha_config: &CaptchaConfig,
|
||||
) -> Result<SiteConfig, anyhow::Error> {
|
||||
let captcha = captcha_config_from_config(captcha_config)?;
|
||||
Ok(SiteConfig {
|
||||
access_token_ttl: experimental_config.access_token_ttl,
|
||||
compat_token_ttl: experimental_config.compat_token_ttl,
|
||||
server_name: matrix_config.homeserver.clone(),
|
||||
@@ -140,7 +166,8 @@ pub fn site_config_from_config(
|
||||
displayname_change_allowed: experimental_config.displayname_change_allowed,
|
||||
password_change_allowed: password_config.enabled()
|
||||
&& experimental_config.password_change_allowed,
|
||||
}
|
||||
captcha,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn templates_from_config(
|
||||
|
80
crates/config/src/sections/captcha.rs
Normal file
80
crates/config/src/sections/captcha.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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::{de::Error, Deserialize, Serialize};
|
||||
|
||||
use crate::ConfigurationSection;
|
||||
|
||||
/// Which service should be used for CAPTCHA protection
|
||||
#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, Serialize)]
|
||||
pub enum CaptchaServiceKind {
|
||||
/// Use Google's reCAPTCHA v2 API
|
||||
#[serde(rename = "recaptcha_v2")]
|
||||
RecaptchaV2,
|
||||
}
|
||||
|
||||
/// Configuration section to setup CAPTCHA protection on a few operations
|
||||
#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, Default)]
|
||||
pub struct CaptchaConfig {
|
||||
/// Which service should be used for CAPTCHA protection
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub service: Option<CaptchaServiceKind>,
|
||||
|
||||
/// The site key to use
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub site_key: Option<String>,
|
||||
|
||||
/// The secret key to use
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub secret_key: Option<String>,
|
||||
}
|
||||
|
||||
impl CaptchaConfig {
|
||||
/// Returns true if the configuration is the default one
|
||||
pub(crate) fn is_default(&self) -> bool {
|
||||
self.service.is_none() && self.site_key.is_none() && self.secret_key.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigurationSection for CaptchaConfig {
|
||||
const PATH: Option<&'static str> = Some("captcha");
|
||||
|
||||
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
|
||||
let metadata = figment.find_metadata(Self::PATH.unwrap());
|
||||
|
||||
let error_on_field = |mut error: figment::error::Error, field: &'static str| {
|
||||
error.metadata = metadata.cloned();
|
||||
error.profile = Some(figment::Profile::Default);
|
||||
error.path = vec![Self::PATH.unwrap().to_owned(), field.to_owned()];
|
||||
error
|
||||
};
|
||||
|
||||
let missing_field = |field: &'static str| {
|
||||
error_on_field(figment::error::Error::missing_field(field), field)
|
||||
};
|
||||
|
||||
if let Some(CaptchaServiceKind::RecaptchaV2) = self.service {
|
||||
if self.site_key.is_none() {
|
||||
return Err(missing_field("site_key"));
|
||||
}
|
||||
|
||||
if self.secret_key.is_none() {
|
||||
return Err(missing_field("secret_key"));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@@ -17,6 +17,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod branding;
|
||||
mod captcha;
|
||||
mod clients;
|
||||
mod database;
|
||||
mod email;
|
||||
@@ -32,6 +33,7 @@ mod upstream_oauth2;
|
||||
|
||||
pub use self::{
|
||||
branding::BrandingConfig,
|
||||
captcha::{CaptchaConfig, CaptchaServiceKind},
|
||||
clients::{ClientAuthMethodConfig, ClientConfig, ClientsConfig},
|
||||
database::DatabaseConfig,
|
||||
email::{EmailConfig, EmailSmtpMode, EmailTransportKind},
|
||||
@@ -107,6 +109,10 @@ pub struct RootConfig {
|
||||
#[serde(default, skip_serializing_if = "BrandingConfig::is_default")]
|
||||
pub branding: BrandingConfig,
|
||||
|
||||
/// Configuration section to setup CAPTCHA protection on a few operations
|
||||
#[serde(default, skip_serializing_if = "CaptchaConfig::is_default")]
|
||||
pub captcha: CaptchaConfig,
|
||||
|
||||
/// Experimental configuration options
|
||||
#[serde(default, skip_serializing_if = "ExperimentalConfig::is_default")]
|
||||
pub experimental: ExperimentalConfig,
|
||||
@@ -126,6 +132,7 @@ impl ConfigurationSection for RootConfig {
|
||||
self.policy.validate(figment)?;
|
||||
self.upstream_oauth2.validate(figment)?;
|
||||
self.branding.validate(figment)?;
|
||||
self.captcha.validate(figment)?;
|
||||
self.experimental.validate(figment)?;
|
||||
|
||||
Ok(())
|
||||
@@ -155,6 +162,7 @@ impl RootConfig {
|
||||
policy: PolicyConfig::default(),
|
||||
upstream_oauth2: UpstreamOAuth2Config::default(),
|
||||
branding: BrandingConfig::default(),
|
||||
captcha: CaptchaConfig::default(),
|
||||
experimental: ExperimentalConfig::default(),
|
||||
})
|
||||
}
|
||||
@@ -175,6 +183,7 @@ impl RootConfig {
|
||||
policy: PolicyConfig::default(),
|
||||
upstream_oauth2: UpstreamOAuth2Config::default(),
|
||||
branding: BrandingConfig::default(),
|
||||
captcha: CaptchaConfig::default(),
|
||||
experimental: ExperimentalConfig::default(),
|
||||
}
|
||||
}
|
||||
@@ -209,6 +218,9 @@ pub struct AppConfig {
|
||||
#[serde(default)]
|
||||
pub branding: BrandingConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub captcha: CaptchaConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub experimental: ExperimentalConfig,
|
||||
}
|
||||
@@ -224,6 +236,7 @@ impl ConfigurationSection for AppConfig {
|
||||
self.matrix.validate(figment)?;
|
||||
self.policy.validate(figment)?;
|
||||
self.branding.validate(figment)?;
|
||||
self.captcha.validate(figment)?;
|
||||
self.experimental.validate(figment)?;
|
||||
|
||||
Ok(())
|
||||
|
@@ -40,7 +40,7 @@ pub use self::{
|
||||
AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, Client, DeviceCodeGrant,
|
||||
DeviceCodeGrantState, InvalidRedirectUriError, JwksOrJwksUri, Pkce, Session, SessionState,
|
||||
},
|
||||
site_config::SiteConfig,
|
||||
site_config::{CaptchaConfig, CaptchaService, SiteConfig},
|
||||
tokens::{
|
||||
AccessToken, AccessTokenState, RefreshToken, RefreshTokenState, TokenFormatError, TokenType,
|
||||
},
|
||||
|
@@ -15,6 +15,25 @@
|
||||
use chrono::Duration;
|
||||
use url::Url;
|
||||
|
||||
/// Which Captcha service is being used
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CaptchaService {
|
||||
RecaptchaV2,
|
||||
}
|
||||
|
||||
/// Captcha configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CaptchaConfig {
|
||||
/// Which Captcha service is being used
|
||||
pub service: CaptchaService,
|
||||
|
||||
/// The site key used by the instance
|
||||
pub site_key: String,
|
||||
|
||||
/// The secret key used by the instance
|
||||
pub secret_key: String,
|
||||
}
|
||||
|
||||
/// Random site configuration we want accessible in various places.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -51,4 +70,7 @@ pub struct SiteConfig {
|
||||
|
||||
/// Whether users can change their password.
|
||||
pub password_change_allowed: bool,
|
||||
|
||||
/// Captcha configuration
|
||||
pub captcha: Option<CaptchaConfig>,
|
||||
}
|
||||
|
@@ -132,6 +132,7 @@ pub fn test_site_config() -> SiteConfig {
|
||||
email_change_allowed: true,
|
||||
displayname_change_allowed: true,
|
||||
password_change_allowed: true,
|
||||
captcha: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -26,7 +26,7 @@ use mas_axum_utils::{
|
||||
csrf::{CsrfExt, CsrfToken, ProtectedForm},
|
||||
FancyError, SessionInfoExt,
|
||||
};
|
||||
use mas_data_model::UserAgent;
|
||||
use mas_data_model::{CaptchaConfig, UserAgent};
|
||||
use mas_i18n::DataLocale;
|
||||
use mas_matrix::BoxHomeserverConnection;
|
||||
use mas_policy::Policy;
|
||||
@@ -96,6 +96,7 @@ pub(crate) async fn get(
|
||||
csrf_token,
|
||||
&mut repo,
|
||||
&templates,
|
||||
site_config.captcha.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -216,6 +217,7 @@ pub(crate) async fn post(
|
||||
csrf_token,
|
||||
&mut repo,
|
||||
&templates,
|
||||
site_config.captcha.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -278,6 +280,7 @@ async fn render(
|
||||
csrf_token: CsrfToken,
|
||||
repo: &mut impl RepositoryAccess,
|
||||
templates: &Templates,
|
||||
captcha_config: Option<CaptchaConfig>,
|
||||
) -> Result<String, FancyError> {
|
||||
let next = action.load_context(repo).await?;
|
||||
let ctx = if let Some(next) = next {
|
||||
@@ -285,7 +288,10 @@ async fn render(
|
||||
} else {
|
||||
ctx
|
||||
};
|
||||
let ctx = ctx.with_csrf(csrf_token.form_value()).with_language(locale);
|
||||
let ctx = ctx
|
||||
.with_captcha(captcha_config)
|
||||
.with_csrf(csrf_token.form_value())
|
||||
.with_language(locale);
|
||||
|
||||
let content = templates.render_register(&ctx)?;
|
||||
Ok(content)
|
||||
|
@@ -15,6 +15,7 @@
|
||||
//! Contexts used in templates
|
||||
|
||||
mod branding;
|
||||
mod captcha;
|
||||
mod ext;
|
||||
mod features;
|
||||
|
||||
@@ -41,7 +42,9 @@ use serde::{ser::SerializeStruct, Deserialize, Serialize};
|
||||
use ulid::Ulid;
|
||||
use url::Url;
|
||||
|
||||
pub use self::{branding::SiteBranding, ext::SiteConfigExt, features::SiteFeatures};
|
||||
pub use self::{
|
||||
branding::SiteBranding, captcha::WithCaptcha, ext::SiteConfigExt, features::SiteFeatures,
|
||||
};
|
||||
use crate::{FieldError, FormField, FormState};
|
||||
|
||||
/// Helper trait to construct context wrappers
|
||||
@@ -95,6 +98,14 @@ pub trait TemplateContext: Serialize {
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach a CAPTCHA configuration to the template context
|
||||
fn with_captcha(self, captcha: Option<mas_data_model::CaptchaConfig>) -> WithCaptcha<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
WithCaptcha::new(captcha, self)
|
||||
}
|
||||
|
||||
/// Generate sample values for this context type
|
||||
///
|
||||
/// This is then used to check for template validity in unit tests and in
|
||||
|
77
crates/templates/src/context/captcha.rs
Normal file
77
crates/templates/src/context/captcha.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
// 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 std::sync::Arc;
|
||||
|
||||
use minijinja::{
|
||||
value::{Enumerator, Object},
|
||||
Value,
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::TemplateContext;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CaptchaConfig(mas_data_model::CaptchaConfig);
|
||||
|
||||
impl Object for CaptchaConfig {
|
||||
fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
|
||||
match key.as_str() {
|
||||
Some("service") => Some(match &self.0.service {
|
||||
mas_data_model::CaptchaService::RecaptchaV2 => "recaptcha_v2".into(),
|
||||
}),
|
||||
Some("site_key") => Some(self.0.site_key.clone().into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn enumerate(self: &Arc<Self>) -> Enumerator {
|
||||
Enumerator::Str(&["service", "site_key"])
|
||||
}
|
||||
}
|
||||
|
||||
/// Context with an optional CAPTCHA configuration in it
|
||||
#[derive(Serialize)]
|
||||
pub struct WithCaptcha<T> {
|
||||
captcha: Option<Value>,
|
||||
|
||||
#[serde(flatten)]
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T> WithCaptcha<T> {
|
||||
#[must_use]
|
||||
pub fn new(captcha: Option<mas_data_model::CaptchaConfig>, inner: T) -> Self {
|
||||
Self {
|
||||
captcha: captcha.map(|captcha| Value::from_object(CaptchaConfig(captcha))),
|
||||
inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TemplateContext> TemplateContext for WithCaptcha<T> {
|
||||
fn sample(
|
||||
now: chrono::DateTime<chrono::prelude::Utc>,
|
||||
rng: &mut impl rand::prelude::Rng,
|
||||
) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let inner = T::sample(now, rng);
|
||||
inner
|
||||
.into_iter()
|
||||
.map(|inner| Self::new(None, inner))
|
||||
.collect()
|
||||
}
|
||||
}
|
@@ -22,6 +22,7 @@ use std::{collections::HashSet, sync::Arc};
|
||||
use anyhow::Context as _;
|
||||
use arc_swap::ArcSwap;
|
||||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
use context::WithCaptcha;
|
||||
use mas_i18n::Translator;
|
||||
use mas_router::UrlBuilder;
|
||||
use mas_spa::ViteManifest;
|
||||
@@ -326,7 +327,7 @@ register_templates! {
|
||||
pub fn render_login(WithLanguage<WithCsrf<LoginContext>>) { "pages/login.html" }
|
||||
|
||||
/// Render the registration page
|
||||
pub fn render_register(WithLanguage<WithCsrf<RegisterContext>>) { "pages/register.html" }
|
||||
pub fn render_register(WithLanguage<WithCsrf<WithCaptcha<RegisterContext>>>) { "pages/register.html" }
|
||||
|
||||
/// Render the client consent page
|
||||
pub fn render_consent(WithLanguage<WithCsrf<WithSession<ConsentContext>>>) { "pages/consent.html" }
|
||||
|
@@ -184,6 +184,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"captcha": {
|
||||
"description": "Configuration section to setup CAPTCHA protection on a few operations",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CaptchaConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"experimental": {
|
||||
"description": "Experimental configuration options",
|
||||
"allOf": [
|
||||
@@ -1949,6 +1957,40 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"CaptchaConfig": {
|
||||
"description": "Configuration section to setup CAPTCHA protection on a few operations",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"service": {
|
||||
"description": "Which service should be used for CAPTCHA protection",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CaptchaServiceKind"
|
||||
}
|
||||
]
|
||||
},
|
||||
"site_key": {
|
||||
"description": "The site key to use",
|
||||
"type": "string"
|
||||
},
|
||||
"secret_key": {
|
||||
"description": "The secret key to use",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CaptchaServiceKind": {
|
||||
"description": "Which service should be used for CAPTCHA protection",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Use Google's reCAPTCHA v2 API",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"recaptcha_v2"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ExperimentalConfig": {
|
||||
"description": "Configuration sections for experimental options\n\nDo not change these options unless you know what you are doing.",
|
||||
"type": "object",
|
||||
|
@@ -23,6 +23,7 @@ limitations under the License.
|
||||
{% import "components/errors.html" as errors %}
|
||||
{% import "components/icon.html" as icon %}
|
||||
{% import "components/scope.html" as scope %}
|
||||
{% import "components/captcha.html" as captcha %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ lang }}">
|
||||
@@ -31,6 +32,7 @@ limitations under the License.
|
||||
<title>{% block title %}{{ _("app.name") }}{% endblock title %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
{{ include_asset('src/templates.css', preload=true) | indent(4) | safe }}
|
||||
{{ captcha.head() }}
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout-container">
|
||||
|
35
templates/components/captcha.html
Normal file
35
templates/components/captcha.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{#
|
||||
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.
|
||||
#}
|
||||
|
||||
{% macro form() -%}
|
||||
{%- if captcha|default(False) -%}
|
||||
{%- if captcha.service == "recaptcha_v2" -%}
|
||||
<div class="g-recaptcha" data-sitekey="{{ captcha.site_key }}"></div>
|
||||
{%- else -%}
|
||||
{{ throw(message="Invalid captcha service setup") }}
|
||||
{%- endif %}
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro head() -%}
|
||||
{%- if captcha|default(False) -%}
|
||||
{%- if captcha.service == "recaptcha_v2" -%}
|
||||
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
||||
{%- else -%}
|
||||
{{ throw(message="Invalid captcha service setup") }}
|
||||
{%- endif %}
|
||||
{%- endif -%}
|
||||
{%- endmacro %}
|
@@ -69,6 +69,8 @@ limitations under the License.
|
||||
{% endcall %}
|
||||
{% endif %}
|
||||
|
||||
{{ captcha.form() }}
|
||||
|
||||
{{ button.button(text=_("action.continue")) }}
|
||||
</form>
|
||||
|
||||
|
@@ -2,11 +2,11 @@
|
||||
"action": {
|
||||
"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:77:13-31"
|
||||
"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:79:13-31"
|
||||
},
|
||||
"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/register.html:72: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:62:30-50, pages/reauth.html:40:28-48, pages/register.html:74:28-48, pages/sso.html:45:28-48"
|
||||
},
|
||||
"create_account": "Create Account",
|
||||
"@create_account": {
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"name": "matrix-authentication-service",
|
||||
"@name": {
|
||||
"context": "app.html:25:14-27, base.html:31:31-44",
|
||||
"context": "app.html:25:14-27, base.html:32:31-44",
|
||||
"description": "Name of the application"
|
||||
},
|
||||
"technical_description": "OpenID Connect discovery document: <a class=\"cpd-link\" data-kind=\"primary\" href=\"%(discovery_url)s\">%(discovery_url)s</a>",
|
||||
@@ -349,7 +349,7 @@
|
||||
"register": {
|
||||
"call_to_login": "Already have an account?",
|
||||
"@call_to_login": {
|
||||
"context": "pages/register.html:87:11-42",
|
||||
"context": "pages/register.html:89:11-42",
|
||||
"description": "Displayed on the registration page to suggest to log in instead"
|
||||
},
|
||||
"create_account": {
|
||||
@@ -364,7 +364,7 @@
|
||||
},
|
||||
"sign_in_instead": "Sign in instead",
|
||||
"@sign_in_instead": {
|
||||
"context": "pages/register.html:91:31-64"
|
||||
"context": "pages/register.html:93:31-64"
|
||||
},
|
||||
"terms_of_service": "I agree to the <a href=\"%s\" data-kind=\"primary\" class=\"cpd-link\">Terms and Conditions</a>",
|
||||
"@terms_of_service": {
|
||||
|
Reference in New Issue
Block a user