You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-29 22:01:14 +03:00
Add instance privacy policy, TOS and imprint, and loads of design cleanups
This commit is contained in:
@ -30,9 +30,7 @@ struct AcceptLanguagePart {
|
|||||||
|
|
||||||
impl PartialOrd for AcceptLanguagePart {
|
impl PartialOrd for AcceptLanguagePart {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
// When comparing two AcceptLanguage structs, we only consider the
|
Some(self.cmp(other))
|
||||||
// quality, in reverse.
|
|
||||||
Reverse(self.quality).partial_cmp(&Reverse(other.quality))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,13 @@ impl Options {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Load and compile the templates
|
// Load and compile the templates
|
||||||
let templates = templates_from_config(&config.templates, &url_builder).await?;
|
let templates = templates_from_config(
|
||||||
|
&config.templates,
|
||||||
|
&config.branding,
|
||||||
|
&url_builder,
|
||||||
|
&config.matrix.homeserver,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let http_client_factory = HttpClientFactory::new().await?;
|
let http_client_factory = HttpClientFactory::new().await?;
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use mas_config::TemplatesConfig;
|
use mas_config::{BrandingConfig, MatrixConfig, TemplatesConfig};
|
||||||
use mas_storage::{Clock, SystemClock};
|
use mas_storage::{Clock, SystemClock};
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use tracing::info_span;
|
use tracing::info_span;
|
||||||
@ -39,13 +39,22 @@ impl Options {
|
|||||||
SC::Check => {
|
SC::Check => {
|
||||||
let _span = info_span!("cli.templates.check").entered();
|
let _span = info_span!("cli.templates.check").entered();
|
||||||
|
|
||||||
let config: TemplatesConfig = root.load_config()?;
|
let template_config: TemplatesConfig = root.load_config()?;
|
||||||
|
let branding_config: BrandingConfig = root.load_config()?;
|
||||||
|
let matrix_config: MatrixConfig = root.load_config()?;
|
||||||
|
|
||||||
let clock = SystemClock::default();
|
let clock = SystemClock::default();
|
||||||
// XXX: we should disallow SeedableRng::from_entropy
|
// XXX: we should disallow SeedableRng::from_entropy
|
||||||
let mut rng = rand_chacha::ChaChaRng::from_entropy();
|
let mut rng = rand_chacha::ChaChaRng::from_entropy();
|
||||||
let url_builder =
|
let url_builder =
|
||||||
mas_router::UrlBuilder::new("https://example.com/".parse()?, None, None);
|
mas_router::UrlBuilder::new("https://example.com/".parse()?, None, None);
|
||||||
let templates = templates_from_config(&config, &url_builder).await?;
|
let templates = templates_from_config(
|
||||||
|
&template_config,
|
||||||
|
&branding_config,
|
||||||
|
&url_builder,
|
||||||
|
&matrix_config.homeserver,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
templates.check_render(clock.now(), &mut rng)?;
|
templates.check_render(clock.now(), &mut rng)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -44,7 +44,13 @@ impl Options {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Load and compile the templates
|
// Load and compile the templates
|
||||||
let templates = templates_from_config(&config.templates, &url_builder).await?;
|
let templates = templates_from_config(
|
||||||
|
&config.templates,
|
||||||
|
&config.branding,
|
||||||
|
&url_builder,
|
||||||
|
&config.matrix.homeserver,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mailer = mailer_from_config(&config.email, &templates)?;
|
let mailer = mailer_from_config(&config.email, &templates)?;
|
||||||
mailer.test_connection().await?;
|
mailer.test_connection().await?;
|
||||||
|
@ -16,14 +16,14 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use mas_config::{
|
use mas_config::{
|
||||||
DatabaseConfig, DatabaseConnectConfig, EmailConfig, EmailSmtpMode, EmailTransportConfig,
|
BrandingConfig, DatabaseConfig, DatabaseConnectConfig, EmailConfig, EmailSmtpMode,
|
||||||
PasswordsConfig, PolicyConfig, TemplatesConfig,
|
EmailTransportConfig, PasswordsConfig, PolicyConfig, TemplatesConfig,
|
||||||
};
|
};
|
||||||
use mas_email::{MailTransport, Mailer};
|
use mas_email::{MailTransport, Mailer};
|
||||||
use mas_handlers::{passwords::PasswordManager, ActivityTracker};
|
use mas_handlers::{passwords::PasswordManager, ActivityTracker};
|
||||||
use mas_policy::PolicyFactory;
|
use mas_policy::PolicyFactory;
|
||||||
use mas_router::UrlBuilder;
|
use mas_router::UrlBuilder;
|
||||||
use mas_templates::{TemplateLoadingError, Templates};
|
use mas_templates::{SiteBranding, TemplateLoadingError, Templates};
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
postgres::{PgConnectOptions, PgPoolOptions},
|
postgres::{PgConnectOptions, PgPoolOptions},
|
||||||
ConnectOptions, PgConnection, PgPool,
|
ConnectOptions, PgConnection, PgPool,
|
||||||
@ -116,13 +116,34 @@ pub async fn policy_factory_from_config(
|
|||||||
|
|
||||||
pub async fn templates_from_config(
|
pub async fn templates_from_config(
|
||||||
config: &TemplatesConfig,
|
config: &TemplatesConfig,
|
||||||
|
branding: &BrandingConfig,
|
||||||
url_builder: &UrlBuilder,
|
url_builder: &UrlBuilder,
|
||||||
|
server_name: &str,
|
||||||
) -> Result<Templates, TemplateLoadingError> {
|
) -> Result<Templates, TemplateLoadingError> {
|
||||||
|
let mut site_branding = SiteBranding::new(server_name);
|
||||||
|
|
||||||
|
if let Some(service_name) = branding.service_name.as_deref() {
|
||||||
|
site_branding = site_branding.with_service_name(service_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(policy_uri) = &branding.policy_uri {
|
||||||
|
site_branding = site_branding.with_policy_uri(policy_uri.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tos_uri) = &branding.tos_uri {
|
||||||
|
site_branding = site_branding.with_tos_uri(tos_uri.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(imprint) = branding.imprint.as_deref() {
|
||||||
|
site_branding = site_branding.with_imprint(imprint);
|
||||||
|
}
|
||||||
|
|
||||||
Templates::load(
|
Templates::load(
|
||||||
config.path.clone(),
|
config.path.clone(),
|
||||||
url_builder.clone(),
|
url_builder.clone(),
|
||||||
config.assets_manifest.clone(),
|
config.assets_manifest.clone(),
|
||||||
config.translations_path.clone(),
|
config.translations_path.clone(),
|
||||||
|
site_branding,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
63
crates/config/src/sections/branding.rs
Normal file
63
crates/config/src/sections/branding.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2023 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 async_trait::async_trait;
|
||||||
|
use rand::Rng;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::ConfigurationSection;
|
||||||
|
|
||||||
|
/// Configuration section for tweaking the branding of the service
|
||||||
|
#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, Default)]
|
||||||
|
pub struct BrandingConfig {
|
||||||
|
/// A human-readable name. Defaults to the server's address.
|
||||||
|
pub service_name: Option<String>,
|
||||||
|
|
||||||
|
/// Link to a privacy policy, displayed in the footer of web pages and
|
||||||
|
/// emails. It is also advertised to clients through the `op_policy_uri`
|
||||||
|
/// OIDC provider metadata.
|
||||||
|
pub policy_uri: Option<Url>,
|
||||||
|
|
||||||
|
/// Link to a terms of service document, displayed in the footer of web
|
||||||
|
/// pages and emails. It is also advertised to clients through the
|
||||||
|
/// `op_tos_uri` OIDC provider metadata.
|
||||||
|
pub tos_uri: Option<Url>,
|
||||||
|
|
||||||
|
/// Legal imprint, displayed in the footer in the footer of web pages and
|
||||||
|
/// emails.
|
||||||
|
pub imprint: Option<String>,
|
||||||
|
|
||||||
|
/// Logo displayed in some web pages.
|
||||||
|
pub logo_uri: Option<Url>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ConfigurationSection for BrandingConfig {
|
||||||
|
fn path() -> &'static str {
|
||||||
|
"branding"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generate<R>(_rng: R) -> anyhow::Result<Self>
|
||||||
|
where
|
||||||
|
R: Rng + Send,
|
||||||
|
{
|
||||||
|
Ok(Self::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ use rand::Rng;
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
mod branding;
|
||||||
mod clients;
|
mod clients;
|
||||||
mod database;
|
mod database;
|
||||||
mod email;
|
mod email;
|
||||||
@ -31,6 +32,7 @@ mod templates;
|
|||||||
mod upstream_oauth2;
|
mod upstream_oauth2;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
|
branding::BrandingConfig,
|
||||||
clients::{ClientAuthMethodConfig, ClientConfig, ClientsConfig},
|
clients::{ClientAuthMethodConfig, ClientConfig, ClientsConfig},
|
||||||
database::{ConnectConfig as DatabaseConnectConfig, DatabaseConfig},
|
database::{ConnectConfig as DatabaseConnectConfig, DatabaseConfig},
|
||||||
email::{EmailConfig, EmailSmtpMode, EmailTransportConfig},
|
email::{EmailConfig, EmailSmtpMode, EmailTransportConfig},
|
||||||
@ -103,6 +105,10 @@ pub struct RootConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub upstream_oauth2: UpstreamOAuth2Config,
|
pub upstream_oauth2: UpstreamOAuth2Config,
|
||||||
|
|
||||||
|
/// Configuration section for tweaking the branding of the service
|
||||||
|
#[serde(default)]
|
||||||
|
pub branding: BrandingConfig,
|
||||||
|
|
||||||
/// Experimental configuration options
|
/// Experimental configuration options
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub experimental: ExperimentalConfig,
|
pub experimental: ExperimentalConfig,
|
||||||
@ -130,6 +136,7 @@ impl ConfigurationSection for RootConfig {
|
|||||||
matrix: MatrixConfig::generate(&mut rng).await?,
|
matrix: MatrixConfig::generate(&mut rng).await?,
|
||||||
policy: PolicyConfig::generate(&mut rng).await?,
|
policy: PolicyConfig::generate(&mut rng).await?,
|
||||||
upstream_oauth2: UpstreamOAuth2Config::generate(&mut rng).await?,
|
upstream_oauth2: UpstreamOAuth2Config::generate(&mut rng).await?,
|
||||||
|
branding: BrandingConfig::generate(&mut rng).await?,
|
||||||
experimental: ExperimentalConfig::generate(&mut rng).await?,
|
experimental: ExperimentalConfig::generate(&mut rng).await?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -147,6 +154,7 @@ impl ConfigurationSection for RootConfig {
|
|||||||
matrix: MatrixConfig::test(),
|
matrix: MatrixConfig::test(),
|
||||||
policy: PolicyConfig::test(),
|
policy: PolicyConfig::test(),
|
||||||
upstream_oauth2: UpstreamOAuth2Config::test(),
|
upstream_oauth2: UpstreamOAuth2Config::test(),
|
||||||
|
branding: BrandingConfig::test(),
|
||||||
experimental: ExperimentalConfig::test(),
|
experimental: ExperimentalConfig::test(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,6 +186,9 @@ pub struct AppConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub policy: PolicyConfig,
|
pub policy: PolicyConfig,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub branding: BrandingConfig,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub experimental: ExperimentalConfig,
|
pub experimental: ExperimentalConfig,
|
||||||
}
|
}
|
||||||
@ -201,6 +212,7 @@ impl ConfigurationSection for AppConfig {
|
|||||||
secrets: SecretsConfig::generate(&mut rng).await?,
|
secrets: SecretsConfig::generate(&mut rng).await?,
|
||||||
matrix: MatrixConfig::generate(&mut rng).await?,
|
matrix: MatrixConfig::generate(&mut rng).await?,
|
||||||
policy: PolicyConfig::generate(&mut rng).await?,
|
policy: PolicyConfig::generate(&mut rng).await?,
|
||||||
|
branding: BrandingConfig::generate(&mut rng).await?,
|
||||||
experimental: ExperimentalConfig::generate(&mut rng).await?,
|
experimental: ExperimentalConfig::generate(&mut rng).await?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -215,6 +227,7 @@ impl ConfigurationSection for AppConfig {
|
|||||||
secrets: SecretsConfig::test(),
|
secrets: SecretsConfig::test(),
|
||||||
matrix: MatrixConfig::test(),
|
matrix: MatrixConfig::test(),
|
||||||
policy: PolicyConfig::test(),
|
policy: PolicyConfig::test(),
|
||||||
|
branding: BrandingConfig::test(),
|
||||||
experimental: ExperimentalConfig::test(),
|
experimental: ExperimentalConfig::test(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ use mas_policy::{InstantiateError, Policy, PolicyFactory};
|
|||||||
use mas_router::{SimpleRoute, UrlBuilder};
|
use mas_router::{SimpleRoute, UrlBuilder};
|
||||||
use mas_storage::{clock::MockClock, BoxClock, BoxRepository, BoxRng, Repository};
|
use mas_storage::{clock::MockClock, BoxClock, BoxRepository, BoxRng, Repository};
|
||||||
use mas_storage_pg::{DatabaseError, PgRepository};
|
use mas_storage_pg::{DatabaseError, PgRepository};
|
||||||
use mas_templates::Templates;
|
use mas_templates::{SiteBranding, Templates};
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use rand_chacha::ChaChaRng;
|
use rand_chacha::ChaChaRng;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
@ -116,11 +116,14 @@ impl TestState {
|
|||||||
|
|
||||||
let url_builder = UrlBuilder::new("https://example.com/".parse()?, None, None);
|
let url_builder = UrlBuilder::new("https://example.com/".parse()?, None, None);
|
||||||
|
|
||||||
|
let site_branding = SiteBranding::new("example.com").with_service_name("Example");
|
||||||
|
|
||||||
let templates = Templates::load(
|
let templates = Templates::load(
|
||||||
workspace_root.join("templates"),
|
workspace_root.join("templates"),
|
||||||
url_builder.clone(),
|
url_builder.clone(),
|
||||||
workspace_root.join("frontend/dist/manifest.json"),
|
workspace_root.join("frontend/dist/manifest.json"),
|
||||||
workspace_root.join("translations"),
|
workspace_root.join("translations"),
|
||||||
|
site_branding,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -54,8 +54,8 @@ impl SynapseConnection {
|
|||||||
.uri(
|
.uri(
|
||||||
self.endpoint
|
self.endpoint
|
||||||
.join(url)
|
.join(url)
|
||||||
.map(Url::into)
|
.map(String::from)
|
||||||
.unwrap_or(String::new()),
|
.unwrap_or_default(),
|
||||||
)
|
)
|
||||||
.header(AUTHORIZATION, format!("Bearer {}", self.access_token))
|
.header(AUTHORIZATION, format!("Bearer {}", self.access_token))
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::borrow::{Borrow, Cow};
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@ -41,7 +41,7 @@ pub trait Route {
|
|||||||
fn absolute_url(&self, base: &Url) -> Url {
|
fn absolute_url(&self, base: &Url) -> Url {
|
||||||
let relative = self.path_and_query();
|
let relative = self.path_and_query();
|
||||||
let relative = relative.trim_start_matches('/');
|
let relative = relative.trim_start_matches('/');
|
||||||
base.join(relative.borrow()).unwrap()
|
base.join(relative).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
//! Contexts used in templates
|
//! Contexts used in templates
|
||||||
|
|
||||||
|
mod branding;
|
||||||
|
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
@ -29,6 +31,7 @@ use serde::{ser::SerializeStruct, Deserialize, Serialize};
|
|||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
pub use self::branding::SiteBranding;
|
||||||
use crate::{FormField, FormState};
|
use crate::{FormField, FormState};
|
||||||
|
|
||||||
/// Helper trait to construct context wrappers
|
/// Helper trait to construct context wrappers
|
||||||
|
89
crates/templates/src/context/branding.rs
Normal file
89
crates/templates/src/context/branding.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use minijinja::{value::StructObject, Value};
|
||||||
|
|
||||||
|
/// Site branding information.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct SiteBranding {
|
||||||
|
server_name: Arc<str>,
|
||||||
|
service_name: Option<Arc<str>>,
|
||||||
|
policy_uri: Option<Arc<str>>,
|
||||||
|
tos_uri: Option<Arc<str>>,
|
||||||
|
imprint: Option<Arc<str>>,
|
||||||
|
logo_uri: Option<Arc<str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SiteBranding {
|
||||||
|
/// Create a new site branding based on the given server name.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(server_name: impl Into<Arc<str>>) -> Self {
|
||||||
|
Self {
|
||||||
|
server_name: server_name.into(),
|
||||||
|
service_name: None,
|
||||||
|
policy_uri: None,
|
||||||
|
tos_uri: None,
|
||||||
|
imprint: None,
|
||||||
|
logo_uri: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the service name.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_service_name(mut self, service_name: impl Into<Arc<str>>) -> Self {
|
||||||
|
self.service_name = Some(service_name.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the policy URI.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_policy_uri(mut self, policy_uri: impl Into<Arc<str>>) -> Self {
|
||||||
|
self.policy_uri = Some(policy_uri.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the terms of service URI.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_tos_uri(mut self, tos_uri: impl Into<Arc<str>>) -> Self {
|
||||||
|
self.tos_uri = Some(tos_uri.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the imprint.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_imprint(mut self, imprint: impl Into<Arc<str>>) -> Self {
|
||||||
|
self.imprint = Some(imprint.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the logo URI.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_logo_uri(mut self, logo_uri: impl Into<Arc<str>>) -> Self {
|
||||||
|
self.logo_uri = Some(logo_uri.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StructObject for SiteBranding {
|
||||||
|
fn get_field(&self, name: &str) -> Option<Value> {
|
||||||
|
match name {
|
||||||
|
"server_name" => Some(self.server_name.clone().into()),
|
||||||
|
"service_name" => self.service_name.clone().map(Value::from),
|
||||||
|
"policy_uri" => self.policy_uri.clone().map(Value::from),
|
||||||
|
"tos_uri" => self.tos_uri.clone().map(Value::from),
|
||||||
|
"imprint" => self.imprint.clone().map(Value::from),
|
||||||
|
"logo_uri" => self.logo_uri.clone().map(Value::from),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn static_fields(&self) -> Option<&'static [&'static str]> {
|
||||||
|
Some(&[
|
||||||
|
"server_name",
|
||||||
|
"service_name",
|
||||||
|
"policy_uri",
|
||||||
|
"tos_uri",
|
||||||
|
"imprint",
|
||||||
|
"logo_uri",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@ use camino::{Utf8Path, Utf8PathBuf};
|
|||||||
use mas_i18n::Translator;
|
use mas_i18n::Translator;
|
||||||
use mas_router::UrlBuilder;
|
use mas_router::UrlBuilder;
|
||||||
use mas_spa::ViteManifest;
|
use mas_spa::ViteManifest;
|
||||||
|
use minijinja::Value;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@ -52,8 +53,8 @@ pub use self::{
|
|||||||
EmailVerificationPageContext, EmptyContext, ErrorContext, FormPostContext, IndexContext,
|
EmailVerificationPageContext, EmptyContext, ErrorContext, FormPostContext, IndexContext,
|
||||||
LoginContext, LoginFormField, NotFoundContext, PolicyViolationContext, PostAuthContext,
|
LoginContext, LoginFormField, NotFoundContext, PolicyViolationContext, PostAuthContext,
|
||||||
PostAuthContextInner, ReauthContext, ReauthFormField, RegisterContext, RegisterFormField,
|
PostAuthContextInner, ReauthContext, ReauthFormField, RegisterContext, RegisterFormField,
|
||||||
TemplateContext, UpstreamExistingLinkContext, UpstreamRegister, UpstreamSuggestLink,
|
SiteBranding, TemplateContext, UpstreamExistingLinkContext, UpstreamRegister,
|
||||||
WithCsrf, WithLanguage, WithOptionalSession, WithSession,
|
UpstreamSuggestLink, WithCsrf, WithLanguage, WithOptionalSession, WithSession,
|
||||||
},
|
},
|
||||||
forms::{FieldError, FormError, FormField, FormState, ToFormState},
|
forms::{FieldError, FormError, FormField, FormState, ToFormState},
|
||||||
};
|
};
|
||||||
@ -73,6 +74,7 @@ pub struct Templates {
|
|||||||
environment: Arc<ArcSwap<minijinja::Environment<'static>>>,
|
environment: Arc<ArcSwap<minijinja::Environment<'static>>>,
|
||||||
translator: Arc<ArcSwap<Translator>>,
|
translator: Arc<ArcSwap<Translator>>,
|
||||||
url_builder: UrlBuilder,
|
url_builder: UrlBuilder,
|
||||||
|
branding: SiteBranding,
|
||||||
vite_manifest_path: Utf8PathBuf,
|
vite_manifest_path: Utf8PathBuf,
|
||||||
translations_path: Utf8PathBuf,
|
translations_path: Utf8PathBuf,
|
||||||
path: Utf8PathBuf,
|
path: Utf8PathBuf,
|
||||||
@ -151,12 +153,14 @@ impl Templates {
|
|||||||
url_builder: UrlBuilder,
|
url_builder: UrlBuilder,
|
||||||
vite_manifest_path: Utf8PathBuf,
|
vite_manifest_path: Utf8PathBuf,
|
||||||
translations_path: Utf8PathBuf,
|
translations_path: Utf8PathBuf,
|
||||||
|
branding: SiteBranding,
|
||||||
) -> Result<Self, TemplateLoadingError> {
|
) -> Result<Self, TemplateLoadingError> {
|
||||||
let (translator, environment) = Self::load_(
|
let (translator, environment) = Self::load_(
|
||||||
&path,
|
&path,
|
||||||
url_builder.clone(),
|
url_builder.clone(),
|
||||||
&vite_manifest_path,
|
&vite_manifest_path,
|
||||||
&translations_path,
|
&translations_path,
|
||||||
|
branding.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@ -166,6 +170,7 @@ impl Templates {
|
|||||||
url_builder,
|
url_builder,
|
||||||
vite_manifest_path,
|
vite_manifest_path,
|
||||||
translations_path,
|
translations_path,
|
||||||
|
branding,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +179,7 @@ impl Templates {
|
|||||||
url_builder: UrlBuilder,
|
url_builder: UrlBuilder,
|
||||||
vite_manifest_path: &Utf8Path,
|
vite_manifest_path: &Utf8Path,
|
||||||
translations_path: &Utf8Path,
|
translations_path: &Utf8Path,
|
||||||
|
branding: SiteBranding,
|
||||||
) -> Result<(Arc<Translator>, Arc<minijinja::Environment<'static>>), TemplateLoadingError> {
|
) -> Result<(Arc<Translator>, Arc<minijinja::Environment<'static>>), TemplateLoadingError> {
|
||||||
let path = path.to_owned();
|
let path = path.to_owned();
|
||||||
let span = tracing::Span::current();
|
let span = tracing::Span::current();
|
||||||
@ -226,6 +232,8 @@ impl Templates {
|
|||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
|
env.add_global("branding", Value::from_struct_object(branding));
|
||||||
|
|
||||||
self::functions::register(
|
self::functions::register(
|
||||||
&mut env,
|
&mut env,
|
||||||
url_builder,
|
url_builder,
|
||||||
@ -259,6 +267,7 @@ impl Templates {
|
|||||||
self.url_builder.clone(),
|
self.url_builder.clone(),
|
||||||
&self.vite_manifest_path,
|
&self.vite_manifest_path,
|
||||||
&self.translations_path,
|
&self.translations_path,
|
||||||
|
self.branding.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -409,13 +418,20 @@ mod tests {
|
|||||||
|
|
||||||
let path = Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("../../templates/");
|
let path = Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("../../templates/");
|
||||||
let url_builder = UrlBuilder::new("https://example.com/".parse().unwrap(), None, None);
|
let url_builder = UrlBuilder::new("https://example.com/".parse().unwrap(), None, None);
|
||||||
|
let branding = SiteBranding::new("example.com").with_service_name("Example");
|
||||||
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");
|
||||||
let translations_path =
|
let translations_path =
|
||||||
Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("../../translations");
|
Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("../../translations");
|
||||||
let templates = Templates::load(path, url_builder, vite_manifest_path, translations_path)
|
let templates = Templates::load(
|
||||||
.await
|
path,
|
||||||
.unwrap();
|
url_builder,
|
||||||
|
vite_manifest_path,
|
||||||
|
translations_path,
|
||||||
|
branding,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
templates.check_render(now, &mut rng).unwrap();
|
templates.check_render(now, &mut rng).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,21 @@
|
|||||||
"secrets"
|
"secrets"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"branding": {
|
||||||
|
"description": "Configuration section for tweaking the branding of the service",
|
||||||
|
"default": {
|
||||||
|
"imprint": null,
|
||||||
|
"logo_uri": null,
|
||||||
|
"policy_uri": null,
|
||||||
|
"service_name": null,
|
||||||
|
"tos_uri": null
|
||||||
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/BrandingConfig"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"clients": {
|
"clients": {
|
||||||
"description": "List of OAuth 2.0/OIDC clients config",
|
"description": "List of OAuth 2.0/OIDC clients config",
|
||||||
"default": [],
|
"default": [],
|
||||||
@ -299,6 +314,35 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"BrandingConfig": {
|
||||||
|
"description": "Configuration section for tweaking the branding of the service",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"imprint": {
|
||||||
|
"description": "Legal imprint, displayed in the footer in the footer of web pages and emails.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"logo_uri": {
|
||||||
|
"description": "Logo displayed in some web pages.",
|
||||||
|
"type": "string",
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"policy_uri": {
|
||||||
|
"description": "Link to a privacy policy, displayed in the footer of web pages and emails. It is also advertised to clients through the `op_policy_uri` OIDC provider metadata.",
|
||||||
|
"type": "string",
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"service_name": {
|
||||||
|
"description": "A human-readable name. Defaults to the server's address.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tos_uri": {
|
||||||
|
"description": "Link to a terms of service document, displayed in the footer of web pages and emails. It is also advertised to clients through the `op_tos_uri` OIDC provider metadata.",
|
||||||
|
"type": "string",
|
||||||
|
"format": "uri"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ClaimsImports": {
|
"ClaimsImports": {
|
||||||
"description": "How claims should be imported",
|
"description": "How claims should be imported",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -23,7 +23,7 @@ limitations under the License.
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>matrix-authentication-service</title>
|
<title>matrix-authentication-service</title>
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
window.APP_CONFIG = JSON.parse('{"root": "/account/", "graphqlEndpoint": "/graphql"}');
|
window.APP_CONFIG = JSON.parse('{"root": "/account/", "graphqlEndpoint": "/graphql", "branding": {}}');
|
||||||
(function () {
|
(function () {
|
||||||
const query = window.matchMedia("(prefers-color-scheme: dark)");
|
const query = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
function handleChange(list) {
|
function handleChange(list) {
|
||||||
|
@ -5,6 +5,16 @@
|
|||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"save": "Save"
|
"save": "Save"
|
||||||
},
|
},
|
||||||
|
"branding": {
|
||||||
|
"privacy_policy": {
|
||||||
|
"alt": "Link to the service privacy policy",
|
||||||
|
"link": "Privacy Policy"
|
||||||
|
},
|
||||||
|
"terms_and_conditions": {
|
||||||
|
"alt": "Link to the service terms and conditions",
|
||||||
|
"link": "Terms & Conditions"
|
||||||
|
}
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
|
@ -13,27 +13,28 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.container {
|
.legal-footer {
|
||||||
box-sizing: border-box;
|
display: flex;
|
||||||
max-width: calc(378px + var(--cpd-space-6x) * 2);
|
flex-direction: column;
|
||||||
margin: 0 auto;
|
gap: var(--cpd-space-2x);
|
||||||
padding: var(--cpd-space-6x);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
font: var(--cpd-font-body-sm-regular);
|
||||||
margin-top: var(--cpd-space-6x);
|
letter-spacing: var(--cpd-font-letter-spacing-body-sm);
|
||||||
padding: var(--cpd-space-6x) 0;
|
|
||||||
border-top: 1px solid var(--cpd-color-border-interactive-secondary);
|
& nav {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--cpd-space-2x);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links {
|
& .separator {
|
||||||
margin: var(--cpd-space-4x) 0;
|
color: var(--cpd-color-text-secondary);
|
||||||
|
|
||||||
& > ul {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
gap: var(--cpd-space-2x);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .imprint {
|
||||||
|
color: var(--cpd-color-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
54
frontend/src/components/Footer/Footer.stories.tsx
Normal file
54
frontend/src/components/Footer/Footer.stories.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2023 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.
|
||||||
|
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
|
import Footer from "./Footer";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "UI/Footer",
|
||||||
|
component: Footer,
|
||||||
|
tags: ["autodocs"],
|
||||||
|
} satisfies Meta<typeof Footer>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Footer>;
|
||||||
|
|
||||||
|
export const Basic: Story = {
|
||||||
|
args: {
|
||||||
|
tosUri: "https://matrix.org/legal/terms-and-conditions/",
|
||||||
|
policyUri: "https://matrix.org/legal/privacy-notice/",
|
||||||
|
imprint: "The Matrix.org Foundation C.I.C.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LinksOnly: Story = {
|
||||||
|
args: {
|
||||||
|
tosUri: "https://matrix.org/legal/terms-and-conditions/",
|
||||||
|
policyUri: "https://matrix.org/legal/privacy-notice/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ImprintOnly: Story = {
|
||||||
|
args: {
|
||||||
|
imprint: "The Matrix.org Foundation C.I.C.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OneLink: Story = {
|
||||||
|
args: {
|
||||||
|
tosUri: "https://matrix.org/legal/terms-and-conditions/",
|
||||||
|
imprint: "The Matrix.org Foundation C.I.C.",
|
||||||
|
},
|
||||||
|
};
|
57
frontend/src/components/Footer/Footer.tsx
Normal file
57
frontend/src/components/Footer/Footer.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2023 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.
|
||||||
|
|
||||||
|
import { Link } from "@vector-im/compound-web";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import styles from "./Footer.module.css";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
policyUri?: string;
|
||||||
|
tosUri?: string;
|
||||||
|
imprint?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Footer: React.FC<Props> = ({ policyUri, tosUri, imprint }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<footer className={styles.legalFooter}>
|
||||||
|
{(policyUri || tosUri) && (
|
||||||
|
<nav>
|
||||||
|
{policyUri && (
|
||||||
|
<Link href={policyUri} title={t("branding.privacy_policy.alt")}>
|
||||||
|
{t("branding.privacy_policy.link")}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{policyUri && tosUri && (
|
||||||
|
<div className={styles.separator} aria-hidden="true">
|
||||||
|
•
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tosUri && (
|
||||||
|
<Link href={tosUri} title={t("branding.terms_and_conditions.alt")}>
|
||||||
|
{t("branding.terms_and_conditions.link")}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</nav>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{imprint && <p className={styles.imprint}>{imprint}</p>}
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
15
frontend/src/components/Footer/index.ts
Normal file
15
frontend/src/components/Footer/index.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2023 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.
|
||||||
|
|
||||||
|
export { default } from "./Footer";
|
26
frontend/src/components/Layout/Layout.module.css
Normal file
26
frontend/src/components/Layout/Layout.module.css
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.layout-container {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
max-width: calc(420px + var(--cpd-space-6x) * 2);
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: var(--cpd-space-6x);
|
||||||
|
gap: var(--cpd-space-6x);
|
||||||
|
}
|
@ -17,8 +17,8 @@
|
|||||||
import { render } from "@testing-library/react";
|
import { render } from "@testing-library/react";
|
||||||
import { describe, expect, it, vi, afterAll, beforeEach } from "vitest";
|
import { describe, expect, it, vi, afterAll, beforeEach } from "vitest";
|
||||||
|
|
||||||
import { currentUserIdAtom, GqlResult } from "../atoms";
|
import { currentUserIdAtom, GqlResult } from "../../atoms";
|
||||||
import { WithLocation } from "../test-utils/WithLocation";
|
import { WithLocation } from "../../test-utils/WithLocation";
|
||||||
|
|
||||||
import Layout from "./Layout";
|
import Layout from "./Layout";
|
||||||
|
|
@ -15,19 +15,21 @@
|
|||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { currentUserIdAtom } from "../atoms";
|
import { currentUserIdAtom } from "../../atoms";
|
||||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
import { isErr, unwrapErr, unwrapOk } from "../../result";
|
||||||
import { routeAtom } from "../routing";
|
import { appConfigAtom, routeAtom } from "../../routing";
|
||||||
|
import Footer from "../Footer";
|
||||||
|
import GraphQLError from "../GraphQLError";
|
||||||
|
import NavBar from "../NavBar";
|
||||||
|
import NavItem from "../NavItem";
|
||||||
|
import NotLoggedIn from "../NotLoggedIn";
|
||||||
|
import UserGreeting from "../UserGreeting";
|
||||||
|
|
||||||
import GraphQLError from "./GraphQLError";
|
|
||||||
import styles from "./Layout.module.css";
|
import styles from "./Layout.module.css";
|
||||||
import NavBar from "./NavBar";
|
|
||||||
import NavItem from "./NavItem";
|
|
||||||
import NotLoggedIn from "./NotLoggedIn";
|
|
||||||
import UserGreeting from "./UserGreeting";
|
|
||||||
|
|
||||||
const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||||
const route = useAtomValue(routeAtom);
|
const route = useAtomValue(routeAtom);
|
||||||
|
const appConfig = useAtomValue(appConfigAtom);
|
||||||
const result = useAtomValue(currentUserIdAtom);
|
const result = useAtomValue(currentUserIdAtom);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -45,7 +47,7 @@ const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.layoutContainer}>
|
||||||
{shouldHideNavBar ? null : (
|
{shouldHideNavBar ? null : (
|
||||||
<>
|
<>
|
||||||
<UserGreeting userId={userId} />
|
<UserGreeting userId={userId} />
|
||||||
@ -63,18 +65,11 @@ const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
|||||||
|
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
|
|
||||||
{/* TODO: the footer needs to be reworked to show configurable info not hardcoded links: https://github.com/matrix-org/matrix-authentication-service/issues/1675 */}
|
<Footer
|
||||||
{/* <footer className={styles.footer}>
|
imprint={appConfig.branding?.imprint}
|
||||||
<nav className={styles.footerLinks}>
|
tosUri={appConfig.branding?.tosUri}
|
||||||
<ul>
|
policyUri={appConfig.branding?.policyUri}
|
||||||
<Link href="https://matrix.org/legal/copyright-notice">Info</Link>
|
/>
|
||||||
<Link href="https://matrix.org/legal/privacy-notice">Privacy</Link>
|
|
||||||
<Link href="https://matrix.org/legal/terms-and-conditions">
|
|
||||||
Terms & Conditions
|
|
||||||
</Link>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</footer> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
15
frontend/src/components/Layout/index.ts
Normal file
15
frontend/src/components/Layout/index.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2023 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.
|
||||||
|
|
||||||
|
export { default } from "./Layout";
|
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
.nav-bar {
|
.nav-bar {
|
||||||
border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-gray-400);
|
border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-gray-400);
|
||||||
margin: var(--cpd-space-6x) 0;
|
|
||||||
padding: 0 var(--cpd-space-10x);
|
padding: 0 var(--cpd-space-10x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,10 +13,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.alert {
|
.alert > * {
|
||||||
margin-top: var(--cpd-space-4x);
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
> * {
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -15,6 +15,11 @@
|
|||||||
export type AppConfig = {
|
export type AppConfig = {
|
||||||
root: string;
|
root: string;
|
||||||
graphqlEndpoint: string;
|
graphqlEndpoint: string;
|
||||||
|
branding?: {
|
||||||
|
tosUri?: string;
|
||||||
|
policyUri?: string;
|
||||||
|
imprint?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IWindow {
|
interface IWindow {
|
||||||
@ -22,6 +27,10 @@ interface IWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const config: AppConfig = (typeof window !== "undefined" &&
|
const config: AppConfig = (typeof window !== "undefined" &&
|
||||||
(window as IWindow).APP_CONFIG) || { root: "/", graphqlEndpoint: "/graphql" };
|
(window as IWindow).APP_CONFIG) || {
|
||||||
|
root: "/",
|
||||||
|
graphqlEndpoint: "/graphql",
|
||||||
|
branding: {},
|
||||||
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -23,6 +23,9 @@
|
|||||||
@import "./styles/cpd-form.css";
|
@import "./styles/cpd-form.css";
|
||||||
@import "./styles/cpd-link.css";
|
@import "./styles/cpd-link.css";
|
||||||
|
|
||||||
|
@import "./components/Layout/Layout.module.css";
|
||||||
|
@import "./components/Footer/Footer.module.css";
|
||||||
|
|
||||||
@config "../tailwind.templates.config.cjs";
|
@config "../tailwind.templates.config.cjs";
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@ -30,92 +33,111 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
.cpd-text-body-lg-regular {
|
.cpd-text-body-lg-regular {
|
||||||
font: var(--cpd-font-body-lg-regular);
|
font: var(--cpd-font-body-lg-regular);
|
||||||
letter-spacing: var(--cpd-font-letter-spacing-body-lg);
|
letter-spacing: var(--cpd-font-letter-spacing-body-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cpd-text-heading-xl-semibold {
|
.cpd-text-heading-xl-semibold {
|
||||||
font: var(--cpd-font-heading-xl-semibold);
|
font: var(--cpd-font-heading-xl-semibold);
|
||||||
letter-spacing: var(--cpd-font-letter-spacing-heading-xl);
|
letter-spacing: var(--cpd-font-letter-spacing-heading-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cpd-text-body-md-regular {
|
.cpd-text-body-md-regular {
|
||||||
font: var(--cpd-font-body-md-regular);
|
font: var(--cpd-font-body-md-regular);
|
||||||
letter-spacing: var(--cpd-font-letter-spacing-body-md);
|
letter-spacing: var(--cpd-font-letter-spacing-body-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cpd-text-primary {
|
.cpd-text-primary {
|
||||||
color: var(--cpd-color-text-primary);
|
color: var(--cpd-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cpd-text-secondary {
|
.cpd-text-secondary {
|
||||||
color: var(--cpd-color-text-secondary);
|
color: var(--cpd-color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.consent-client-icon {
|
.consent-client-icon {
|
||||||
display: block;
|
display: block;
|
||||||
|
height: var(--cpd-space-16x);
|
||||||
|
width: var(--cpd-space-16x);
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
&.generic {
|
||||||
|
background-color: var(--cpd-color-bg-subtle-secondary);
|
||||||
|
border-radius: var(--cpd-radius-pill-effect);
|
||||||
|
color: var(--cpd-color-icon-primary);
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
margin: var(--cpd-space-4x);
|
||||||
|
height: var(--cpd-space-8x);
|
||||||
|
width: var(--cpd-space-8x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.image {
|
||||||
height: var(--cpd-space-16x);
|
height: var(--cpd-space-16x);
|
||||||
width: var(--cpd-space-16x);
|
width: var(--cpd-space-16x);
|
||||||
margin: 0 auto;
|
border-radius: var(--cpd-space-2x);
|
||||||
|
overflow: hidden;
|
||||||
&.generic {
|
}
|
||||||
background-color: var(--cpd-color-bg-subtle-secondary);
|
|
||||||
border-radius: var(--cpd-radius-pill-effect);
|
|
||||||
color: var(--cpd-color-icon-primary);
|
|
||||||
|
|
||||||
& svg {
|
|
||||||
margin: var(--cpd-space-4x);
|
|
||||||
height: var(--cpd-space-8x);
|
|
||||||
width: var(--cpd-space-8x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.image {
|
|
||||||
height: var(--cpd-space-16x);
|
|
||||||
width: var(--cpd-space-16x);
|
|
||||||
border-radius: var(--cpd-space-2x);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.consent-scope-list {
|
.consent-scope-list {
|
||||||
--border-radius: var(--cpd-space-4x);
|
--border-radius: var(--cpd-space-4x);
|
||||||
& ul {
|
& ul {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--cpd-space-1x);
|
gap: var(--cpd-space-1x);
|
||||||
|
|
||||||
& > li {
|
& > li {
|
||||||
font: var(--cpd-font-body-md-regular);
|
font: var(--cpd-font-body-md-regular);
|
||||||
letter-spacing: var(--cpd-font-letter-spacing-body-md);
|
letter-spacing: var(--cpd-font-letter-spacing-body-md);
|
||||||
color: var(--cpd-color-text-primary);
|
color: var(--cpd-color-text-primary);
|
||||||
|
|
||||||
background-color: var(--cpd-color-bg-subtle-secondary);
|
background-color: var(--cpd-color-bg-subtle-secondary);
|
||||||
padding: var(--cpd-space-3x) var(--cpd-space-5x);
|
padding: var(--cpd-space-3x) var(--cpd-space-5x);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--cpd-space-4x);
|
gap: var(--cpd-space-4x);
|
||||||
line-height: var(--cpd-space-6x);
|
line-height: var(--cpd-space-6x);
|
||||||
|
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
border-top-left-radius: var(--border-radius);
|
border-top-left-radius: var(--border-radius);
|
||||||
border-top-right-radius: var(--border-radius);
|
border-top-right-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
border-bottom-left-radius: var(--border-radius);
|
border-bottom-left-radius: var(--border-radius);
|
||||||
border-bottom-right-radius: var(--border-radius);
|
border-bottom-right-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
& > p {
|
& > p {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > svg {
|
& > svg {
|
||||||
display: block;
|
display: block;
|
||||||
height: var(--cpd-space-6x);
|
height: var(--cpd-space-6x);
|
||||||
width: var(--cpd-space-6x);
|
width: var(--cpd-space-6x);
|
||||||
color: var(--cpd-color-icon-quaternary);
|
color: var(--cpd-color-icon-quaternary);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
& hr {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid var(--cpd-color-bg-subtle-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
& p {
|
||||||
|
margin-inline: var(--cpd-space-4x);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font: var(--cpd-font-body-md-regular);
|
||||||
|
letter-spacing: var(--cpd-font-letter-spacing-body-md);
|
||||||
|
color: var(--cpd-color-text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,7 +24,16 @@ limitations under the License.
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>{{ _("app.name") }}</title>
|
<title>{{ _("app.name") }}</title>
|
||||||
<script>
|
<script>
|
||||||
window.APP_CONFIG = JSON.parse("{{ app_config | tojson | add_slashes | safe }}");
|
{% set config = {
|
||||||
|
'branding': {
|
||||||
|
'imprint': branding.imprint,
|
||||||
|
'tosUri': branding.tos_uri,
|
||||||
|
'policyUri': branding.policy_uri,
|
||||||
|
},
|
||||||
|
'graphqlEndpoint': app_config.graphqlEndpoint,
|
||||||
|
'root': app_config.root,
|
||||||
|
} -%}
|
||||||
|
window.APP_CONFIG = JSON.parse("{{ config | tojson | add_slashes | safe }}");
|
||||||
(function () {
|
(function () {
|
||||||
const query = window.matchMedia("(prefers-color-scheme: dark)");
|
const query = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
function handleChange(list) {
|
function handleChange(list) {
|
||||||
|
@ -20,7 +20,6 @@ limitations under the License.
|
|||||||
{% import "components/field.html" as field %}
|
{% import "components/field.html" as field %}
|
||||||
{% import "components/back_to_client.html" as back_to_client %}
|
{% import "components/back_to_client.html" as back_to_client %}
|
||||||
{% import "components/logout.html" as logout %}
|
{% import "components/logout.html" as logout %}
|
||||||
{% import "components/navbar.html" as navbar %}
|
|
||||||
{% import "components/errors.html" as errors %}
|
{% import "components/errors.html" as errors %}
|
||||||
{% import "components/icon.html" as icon %}
|
{% import "components/icon.html" as icon %}
|
||||||
{% import "components/scope.html" as scope %}
|
{% import "components/scope.html" as scope %}
|
||||||
@ -33,7 +32,10 @@ limitations under the License.
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
{{ include_asset('src/templates.css', preload=true) | indent(4) | safe }}
|
{{ include_asset('src/templates.css', preload=true) | indent(4) | safe }}
|
||||||
</head>
|
</head>
|
||||||
<body class="flex flex-col min-h-screen">
|
<body>
|
||||||
{% block content %}{% endblock content %}
|
<div class="layout-container">
|
||||||
|
{% block content %}{% endblock content %}
|
||||||
|
{% include "components/footer.html" %}
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -55,3 +55,11 @@ limitations under the License.
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro separator() %}
|
||||||
|
<div class="separator">
|
||||||
|
<hr />
|
||||||
|
<p>{{ _("mas.or_separator") }}</p>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
41
templates/components/footer.html
Normal file
41
templates/components/footer.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{#
|
||||||
|
Copyright 2023 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.
|
||||||
|
#}
|
||||||
|
|
||||||
|
<footer class="legal-footer">
|
||||||
|
{%- if branding.policy_uri or branding.tos_uri -%}
|
||||||
|
<nav>
|
||||||
|
{%- if branding.policy_uri -%}
|
||||||
|
<a href="{{ branding.policy_uri }}" referrerpolicy="no-referrer" title="{{ _('branding.privacy_policy.alt') }}" class="cpd-link" data-kind="primary">
|
||||||
|
{{- _("branding.privacy_policy.link") -}}
|
||||||
|
</a>
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{%- if branding.policy_uri and branding.tos_uri -%}
|
||||||
|
<div class="separator" aria-hidden="true">•</div>
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{%- if branding.tos_uri -%}
|
||||||
|
<a href="{{ branding.tos_uri }}" referrerpolicy="no-referrer" title="{{ _('branding.terms_and_conditions.alt') }}" class="cpd-link" data-kind="primary">
|
||||||
|
{{- _("branding.terms_and_conditions.link") -}}
|
||||||
|
</a>
|
||||||
|
{%- endif -%}
|
||||||
|
</nav>
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{%- if branding.imprint -%}
|
||||||
|
<p class="imprint">{{ branding.imprint }}</p>
|
||||||
|
{%- endif -%}
|
||||||
|
</footer>
|
@ -15,12 +15,16 @@ limitations under the License.
|
|||||||
#}
|
#}
|
||||||
|
|
||||||
{% macro button(text, csrf_token, as_link=false, post_logout_action={}) %}
|
{% macro button(text, csrf_token, as_link=false, post_logout_action={}) %}
|
||||||
<form method="POST" action="{{ "/logout" | prefix_url }}" class="inline">
|
<form method="POST" action="{{ "/logout" | prefix_url }}" class="inline-flex">
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
{% for key, value in post_logout_action|items %}
|
{% for key, value in post_logout_action|items %}
|
||||||
<input type="hidden" name="{{ key }}" value="{{ value }}" />
|
<input type="hidden" name="{{ key }}" value="{{ value }}" />
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<button class="{% if as_link %}cpd-link{% else %}cpd-button{% endif %}" data-kind="critical" type="submit">{{ text }}</button>
|
{% if as_link %}
|
||||||
|
<button class="cpd-link flex-1" data-kind="critical" type="submit">{{ text }}</button>
|
||||||
|
{% else %}
|
||||||
|
<button class="cpd-button flex-1" data-kind="destructive" data-size="lg" type="submit">{{ text }}</button>
|
||||||
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
{#
|
|
||||||
Copyright 2022 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 top() %}
|
|
||||||
<nav class="container mx-auto py-2 flex-initial flex items-center px-8" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="flex-1"></div>
|
|
||||||
|
|
||||||
<div class="grid grid-flow-col auto-cols-max gap-4 place-items-center">
|
|
||||||
{% if current_session %}
|
|
||||||
<div class="text-grey-200 dark:text-grey-250 mx-2">
|
|
||||||
{{ _("mas.navbar.signed_in_as", username=current_session.user.username) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ button.link(text=_("mas.navbar.my_account"), href="/account/") }}
|
|
||||||
{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token) }}
|
|
||||||
{% else %}
|
|
||||||
{{ button.link(text=_("action.sign_in"), href="/login") }}
|
|
||||||
{{ button.link_outline(text=_("mas.navbar.register"), href="/register") }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
{% endmacro %}
|
|
@ -17,21 +17,19 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="flex-1 flex items-center justify-center">
|
<main class="w-96 flex-1 flex flex-col gap-2 justify-center">
|
||||||
<div class="w-64 flex flex-col gap-2">
|
<h1 class="text-xl font-semibold">{{ _("mas.not_found.heading") }}</h1>
|
||||||
<h1 class="text-xl font-semibold">{{ _("mas.not_found.heading") }}</h1>
|
<p>{{ _("mas.not_found.description") }}</p>
|
||||||
<p>{{ _("mas.not_found.description") }}</p>
|
<div>
|
||||||
<div>
|
{{ button.link_text(text=_("mas.back_to_homepage"), href="/") }}
|
||||||
{{ button.link_text(text=_("mas.back_to_homepage"), href="/") }}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<code>
|
<code>
|
||||||
<pre class="whitespace-pre-wrap break-all">{{ method }} {{ uri }} {{ version }}
|
<pre class="whitespace-pre-wrap break-all">{{ method }} {{ uri }} {{ version }}
|
||||||
|
|
||||||
{{ version }} 404 Not Found</pre>
|
{{ version }} 404 Not Found</pre>
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</main>
|
||||||
</section>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -17,23 +17,19 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ navbar.top() }}
|
<form method="POST" class="flex-1 flex flex-col gap-6 justify-center">
|
||||||
<section class="flex items-center justify-center flex-1">
|
<h1 class="cpd-text-heading-xl-semibold">{{ _("mas.add_email.heading") }}</h1>
|
||||||
<form method="POST" class="grid grid-cols-1 gap-6 w-96 my-2 mx-8">
|
|
||||||
<div class="text-center">
|
|
||||||
<h1 class="text-lg text-center font-medium">{{ _("mas.add_email.heading") }}</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if form.errors is not empty %}
|
{% if form.errors is not empty %}
|
||||||
{% for error in form.errors %}
|
{% for error in form.errors %}
|
||||||
<div class="text-critical font-medium">
|
<div class="text-critical font-medium">
|
||||||
{{ errors.form_error_message(error=error) }}
|
{{ errors.form_error_message(error=error) }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
{{ field.input(label=_("common.email_address"), name="email", type="email", form_state=form, autocomplete="email", required=true) }}
|
{{ field.input(label=_("common.email_address"), name="email", type="email", form_state=form, autocomplete="email", required=true) }}
|
||||||
{{ button.button(text=_("action.continue")) }}
|
{{ button.button(text=_("action.continue")) }}
|
||||||
</section>
|
</form>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -17,25 +17,22 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ navbar.top() }}
|
<form method="POST" class="flex-1 flex flex-col gap-6 justify-center">
|
||||||
<section class="flex items-center justify-center flex-1">
|
<div class="text-center">
|
||||||
<form method="POST" class="grid grid-cols-1 gap-6 w-96 my-2 mx-8">
|
<h1 class="cpd-text-heading-xl-semibold">{{ _("mas.verify_email.headline") }}</h1>
|
||||||
<div class="text-center">
|
<p>{{ _("mas.verify_email.description", email=email.email) }}</p>
|
||||||
<h1 class="text-lg text-center font-medium">{{ _("mas.verify_email.headline") }}</h1>
|
</div>
|
||||||
<p>{{ _("mas.verify_email.description", email=email.email) }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if form.errors is not empty %}
|
{% if form.errors is not empty %}
|
||||||
{% for error in form.errors %}
|
{% for error in form.errors %}
|
||||||
<div class="text-critical font-medium">
|
<div class="text-critical font-medium">
|
||||||
{{ errors.form_error_message(error=error) }}
|
{{ errors.form_error_message(error=error) }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
{{ field.input(label=_("mas.verify_email.code"), name="code", form_state=form, autocomplete="one-time-code", inputmode="numeric") }}
|
{{ field.input(label=_("mas.verify_email.code"), name="code", form_state=form, autocomplete="one-time-code", inputmode="numeric") }}
|
||||||
{{ button.button(text=_("action.submit")) }}
|
{{ button.button(text=_("action.submit")) }}
|
||||||
</form>
|
</form>
|
||||||
</section>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -17,16 +17,13 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ navbar.top() }}
|
<form class="flex-1 flex flex-col justify-center gap-6" method="POST">
|
||||||
<section class="container mx-auto grid gap-4 grid-cols-1 md:grid-cols-2 xl:grid-cols-3 py-2 px-8">
|
<h2 class="cpd-text-heading-xl-semibold">{{ _("mas.change_password.heading") }}</h2>
|
||||||
<form class="rounded border-2 border-grey-50 dark:border-grey-450 p-4 grid gap-4 xl:grid-cols-2 grid-cols-1 place-content-start" method="POST">
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
<h2 class="text-xl font-semibold xl:col-span-2">{{ _("mas.change_password.heading") }}</h2>
|
{{ field.input(label=_("mas.change_password.current"), name="current_password", type="password", autocomplete="current-password") }}
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
{{ field.input(label=_("mas.change_password.new"), name="new_password", type="password", autocomplete="new-password") }}
|
||||||
{{ field.input(label=_("mas.change_password.current"), name="current_password", type="password", autocomplete="current-password", class="xl:col-span-2") }}
|
{{ field.input(label=_("mas.change_password.confirm"), name="new_password_confirm", type="password", autocomplete="new-password") }}
|
||||||
{{ field.input(label=_("mas.change_password.new"), name="new_password", type="password", autocomplete="new-password") }}
|
{{ button.button(text=_("mas.change_password.change"), type="submit") }}
|
||||||
{{ field.input(label=_("mas.change_password.confirm"), name="new_password_confirm", type="password", autocomplete="new-password") }}
|
</form>
|
||||||
{{ button.button(text=_("mas.change_password.change"), type="submit", class="xl:col-span-2 place-self-end") }}
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
@ -18,59 +18,57 @@ limitations under the License.
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% set client_name = client.client_name | default(client.client_id) %}
|
{% set client_name = client.client_name | default(client.client_id) %}
|
||||||
<section class="flex items-center justify-center flex-1">
|
<main class="flex flex-col gap-6">
|
||||||
<div class="w-96 mx-2 my-8 flex flex-col gap-6">
|
<div class="flex flex-col gap-2 text-center">
|
||||||
<div class="flex flex-col gap-2 text-center">
|
{% if client.logo_uri %}
|
||||||
{% if client.logo_uri %}
|
<img class="consent-client-icon image" referrerpolicy="no-referrer" src="{{ client.logo_uri }}" />
|
||||||
<img class="consent-client-icon image" referrerpolicy="no-referrer" src="{{ client.logo_uri }}" />
|
{% else %}
|
||||||
{% else %}
|
<div class="consent-client-icon generic">
|
||||||
<div class="consent-client-icon generic">
|
{{ icon.web_browser() }}
|
||||||
{{ icon.web_browser() }}
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<h1 class="cpd-text-primary cpd-text-heading-xl-semibold"><a target="_blank" href="{{ client.client_uri }}">{{ client_name }}</a></h1>
|
<h1 class="cpd-text-primary cpd-text-heading-xl-semibold"><a target="_blank" href="{{ client.client_uri }}">{{ client_name }}</a></h1>
|
||||||
<p class="cpd-text-secondary cpd-text-body-lg-regular"><span class="whitespace-nowrap">at {{ grant.redirect_uri | simplify_url }}</span> wants to access your account. This will allow <span class="whitespace-nowrap">{{ client_name }}</span> to:</p>
|
<p class="cpd-text-secondary cpd-text-body-lg-regular"><span class="whitespace-nowrap">at {{ grant.redirect_uri | simplify_url }}</span> wants to access your account. This will allow <span class="whitespace-nowrap">{{ client_name }}</span> to:</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="consent-scope-list">
|
|
||||||
{{ scope.list(scopes=grant.scope) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-2 text-center cpd-text-body-md-regular">
|
|
||||||
<span class="font-semibold">Make sure that you trust <span class="whitespace-nowrap">{{ client_name }}</span>.</span>
|
|
||||||
You may be sharing sensitive information with this site or app.
|
|
||||||
{% if client.policy_uri or client.tos_uri %}
|
|
||||||
Find out how {{ client_name }} will handle your data by reviewing its
|
|
||||||
{% if client.policy_uri %}
|
|
||||||
<a target="_blank" href="{{ client.policy_uri }}" class="cpd-link" data-kind="primary">privacy policy</a>{% if not client.tos_uri %}.{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if client.policy_uri and client.tos_uri%}
|
|
||||||
and
|
|
||||||
{% endif %}
|
|
||||||
{% if client.tos_uri %}
|
|
||||||
<a target="_blank" href="{{ client.tos_uri }}" class="cpd-link" data-kind="primary">terms of service</a>.
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="POST" class="flex flex-col">
|
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
|
||||||
{{ button.button(text=_("action.continue")) }}
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{{ back_to_client.link(
|
|
||||||
text=_("action.cancel"),
|
|
||||||
kind="tertiary",
|
|
||||||
uri=grant.redirect_uri,
|
|
||||||
mode=grant.response_mode,
|
|
||||||
params=dict(error="access_denied", state=grant.state)
|
|
||||||
) }}
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
{{ _("mas.not_you", username=current_session.user.username) }}
|
|
||||||
{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=action, as_link=true) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
<div class="consent-scope-list">
|
||||||
|
{{ scope.list(scopes=grant.scope) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-2 text-center cpd-text-body-md-regular">
|
||||||
|
<span class="font-semibold">Make sure that you trust <span class="whitespace-nowrap">{{ client_name }}</span>.</span>
|
||||||
|
You may be sharing sensitive information with this site or app.
|
||||||
|
{% if client.policy_uri or client.tos_uri %}
|
||||||
|
Find out how {{ client_name }} will handle your data by reviewing its
|
||||||
|
{% if client.policy_uri %}
|
||||||
|
<a target="_blank" href="{{ client.policy_uri }}" class="cpd-link" data-kind="primary">privacy policy</a>{% if not client.tos_uri %}.{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if client.policy_uri and client.tos_uri%}
|
||||||
|
and
|
||||||
|
{% endif %}
|
||||||
|
{% if client.tos_uri %}
|
||||||
|
<a target="_blank" href="{{ client.tos_uri }}" class="cpd-link" data-kind="primary">terms of service</a>.
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" class="flex flex-col">
|
||||||
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
|
{{ button.button(text=_("action.continue")) }}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{ back_to_client.link(
|
||||||
|
text=_("action.cancel"),
|
||||||
|
kind="tertiary",
|
||||||
|
uri=grant.redirect_uri,
|
||||||
|
mode=grant.response_mode,
|
||||||
|
params=dict(error="access_denied", state=grant.state)
|
||||||
|
) }}
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
{{ _("mas.not_you", username=current_session.user.username) }}
|
||||||
|
{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=action, as_link=true) }}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -20,8 +20,7 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="flex-1 flex items-center justify-center">
|
<main class="flex flex-col gap-2">
|
||||||
<div class="w-64 flex flex-col gap-2">
|
|
||||||
<h1 class="text-xl font-semibold">{{ _("error.unexpected") }}</h1>
|
<h1 class="text-xl font-semibold">{{ _("error.unexpected") }}</h1>
|
||||||
{% if code %}
|
{% if code %}
|
||||||
<p class="font-semibold font-mono">
|
<p class="font-semibold font-mono">
|
||||||
@ -39,6 +38,5 @@ limitations under the License.
|
|||||||
<pre class="font-mono whitespace-pre-wrap break-all">{{ details }}</pre>
|
<pre class="font-mono whitespace-pre-wrap break-all">{{ details }}</pre>
|
||||||
</code>
|
</code>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</main>
|
||||||
</section>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -17,13 +17,22 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ navbar.top() }}
|
<main class="flex-1 flex flex-col justify-center gap-6">
|
||||||
<section class="flex-1 flex flex-col items-center justify-center">
|
<h1 class="cpd-text-heading-xl-semibold">{{ _("app.human_name") }}</h1>
|
||||||
<div class="my-2 mx-8">
|
<p class="cpd-text-body-md-regular">
|
||||||
<h1 class="my-2 text-5xl font-semibold leading-tight">{{ _("app.human_name") }}</h1>
|
{{ _("app.technical_description", discovery_url=discovery_url) }}
|
||||||
<p class="text-lg">
|
</p>
|
||||||
{{ _("app.technical_description", discovery_url=discovery_url) }}
|
|
||||||
|
{% if current_session %}
|
||||||
|
<p class="cpd-text-body-md-regular">
|
||||||
|
{{ _("mas.navbar.signed_in_as", username=current_session.user.username) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</section>
|
{{ button.link(text=_("mas.navbar.my_account"), href="/account/") }}
|
||||||
|
{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token) }}
|
||||||
|
{% else %}
|
||||||
|
{{ button.link(text=_("action.sign_in"), href="/login") }}
|
||||||
|
{{ button.link_outline(text=_("mas.navbar.register"), href="/register") }}
|
||||||
|
{% endif %}
|
||||||
|
</main>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -17,21 +17,21 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="flex items-center justify-center flex-1">
|
<div class="flex-1 flex flex-col gap-6 justify-center">
|
||||||
<form method="POST" class="grid grid-cols-1 gap-6 w-96 my-2 mx-8">
|
{% if not password_disabled %}
|
||||||
{% if not password_disabled %}
|
{% if next and next.kind == "link_upstream" %}
|
||||||
{% if next and next.kind == "link_upstream" %}
|
<div class="text-center">
|
||||||
<div class="text-center">
|
<h1 class="text-lg text-center font-medium">{{ _("mas.login.link.headline") }}</h1>
|
||||||
<h1 class="text-lg text-center font-medium">{{ _("mas.login.link.headline") }}</h1>
|
<p class="text-sm">{{ _("mas.login.link.description", provider=next.provider.issuer) }}</p>
|
||||||
<p class="text-sm">{{ _("mas.login.link.description", provider=next.provider.issuer) }}</p>
|
</div>
|
||||||
</div>
|
{% else %}
|
||||||
{% else %}
|
<div class="text-center">
|
||||||
<div class="text-center">
|
<h1 class="text-lg text-center font-medium">{{ _("mas.login.headline") }}</h1>
|
||||||
<h1 class="text-lg text-center font-medium">{{ _("mas.login.headline") }}</h1>
|
<p>{{ _("mas.login.description") }}</p>
|
||||||
<p>{{ _("mas.login.description") }}</p>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
<form method="POST" class="flex flex-col gap-6">
|
||||||
{% if form.errors is not empty %}
|
{% if form.errors is not empty %}
|
||||||
{% for error in form.errors %}
|
{% for error in form.errors %}
|
||||||
<div class="text-critical font-medium">
|
<div class="text-critical font-medium">
|
||||||
@ -43,52 +43,43 @@ limitations under the License.
|
|||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
{{ field.input(label=_("common.username"), name="username", form_state=form, autocomplete="username", autocorrect="off", autocapitalize="none") }}
|
{{ field.input(label=_("common.username"), name="username", form_state=form, autocomplete="username", autocorrect="off", autocapitalize="none") }}
|
||||||
{{ field.input(label=_("common.password"), name="password", type="password", form_state=form, autocomplete="password") }}
|
{{ field.input(label=_("common.password"), name="password", type="password", form_state=form, autocomplete="password") }}
|
||||||
{% if next and next.kind == "continue_authorization_grant" %}
|
{{ button.button(text=_("action.continue")) }}
|
||||||
<div class="grid grid-cols-2 gap-4">
|
</form>
|
||||||
{{ back_to_client.link(
|
|
||||||
text=_("action.cancel"),
|
|
||||||
kind="destructive",
|
|
||||||
uri=next.grant.redirect_uri,
|
|
||||||
mode=next.grant.response_mode,
|
|
||||||
params=dict(error="access_denied", state=next.grant.state)
|
|
||||||
) }}
|
|
||||||
{{ button.button(text=_("action.continue")) }}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="grid grid-cols-1 gap-4">
|
|
||||||
{{ button.button(text=_("action.continue")) }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not next or next.kind != "link_upstream" %}
|
{% if not next or next.kind != "link_upstream" %}
|
||||||
<div class="text-center mt-4">
|
<div class="text-center mt-4">
|
||||||
{{ _("mas.login.call_to_register") }}
|
{{ _("mas.login.call_to_register") }}
|
||||||
{% set params = next["params"] | default({}) | to_params(prefix="?") %}
|
|
||||||
{{ button.link_text(text=_("action.create_account"), href="/register" ~ params) }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if providers %}
|
|
||||||
{% if not password_disabled %}
|
|
||||||
<div class="flex items-center">
|
|
||||||
<hr class="flex-1" />
|
|
||||||
<div class="mx-2">{{ _("mas.or_separator") }}</div>
|
|
||||||
<hr class="flex-1" />
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% for provider in providers %}
|
|
||||||
{% set params = next["params"] | default({}) | to_params(prefix="?") %}
|
{% set params = next["params"] | default({}) | to_params(prefix="?") %}
|
||||||
{{ button.link(text=_("mas.login.continue_with_provider", provider=provider.issuer), href="/upstream/authorize/" ~ provider.id ~ params) }}
|
{{ button.link_text(text=_("action.create_account"), href="/register" ~ params) }}
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not providers and password_disabled %}
|
|
||||||
<div class="text-center">
|
|
||||||
{{ _("mas.login.no_login_methods") }}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
{% endif %}
|
||||||
</section>
|
|
||||||
|
{% if providers %}
|
||||||
|
{% if not password_disabled %}
|
||||||
|
{{ field.separator() }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for provider in providers %}
|
||||||
|
{% set params = next["params"] | default({}) | to_params(prefix="?") %}
|
||||||
|
<a class="cpd-button" data-kind="secondary" data-size="lg" href="{{ ('/upstream/authorize/' ~ provider.id ~ params) | prefix_url }}">{{ _("mas.login.continue_with_provider", provider=provider.issuer | simplify_url) }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not providers and password_disabled %}
|
||||||
|
<div class="text-center">
|
||||||
|
{{ _("mas.login.no_login_methods") }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if next and next.kind == "continue_authorization_grant" %}
|
||||||
|
{{ back_to_client.link(
|
||||||
|
text=_("action.cancel"),
|
||||||
|
kind="destructive",
|
||||||
|
uri=next.grant.redirect_uri,
|
||||||
|
mode=next.grant.response_mode,
|
||||||
|
params=dict(error="access_denied", state=next.grant.state)
|
||||||
|
) }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -17,37 +17,33 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="flex items-center justify-center flex-1">
|
<section class="flex-1 flex flex-col gap-10 justify-center">
|
||||||
<div class="w-96 my-2 mx-8">
|
<h1 class="cpd-text-heading-xl-semibold">{{ _("mas.policy_violation.heading") }}</h1>
|
||||||
<div class="grid grid-cols-1 gap-6">
|
<p>{{ _("mas.policy_violation.description") }}</p>
|
||||||
<h1 class="text-xl font-semibold">{{ _("mas.policy_violation.heading") }}</h1>
|
<div class="flex items-center justify-center gap-4">
|
||||||
<p>{{ _("mas.policy_violation.description") }}</p>
|
<div class="bg-white rounded w-16 h-16 overflow-hidden">
|
||||||
<div class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 flex items-center">
|
{% if client.logo_uri %}
|
||||||
<div class="bg-white rounded w-16 h-16 overflow-hidden mx-auto">
|
<img referrerpolicy="no-referrer" class="w-16 h-16" src="{{ client.logo_uri }}" />
|
||||||
{% if client.logo_uri %}
|
{% endif %}
|
||||||
<img referrerpolicy="no-referrer" class="w-16 h-16" src="{{ client.logo_uri }}" />
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<h1 class="text-lg text-center font-medium flex-1"><a target="_blank" href="{{ client.client_uri }}" class="cpd-link" data-kind="primary">{{ client.client_name | default(client.client_id) }}</a></h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 flex items-center">
|
|
||||||
<div class="text-center flex-1">
|
|
||||||
{{ _("mas.policy_violation.logged_as", username=current_session.user.username) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=action) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ back_to_client.link(
|
|
||||||
text=_("action.cancel"),
|
|
||||||
kind="destructive",
|
|
||||||
uri=grant.redirect_uri,
|
|
||||||
mode=grant.response_mode,
|
|
||||||
params=dict(error="access_denied", state=grant.state)
|
|
||||||
) }}
|
|
||||||
</div>
|
</div>
|
||||||
|
<a target="_blank" href="{{ client.client_uri }}" class="cpd-link" data-kind="primary">{{ client.client_name | default(client.client_id) }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-4 justify-center items-center">
|
||||||
|
<p>
|
||||||
|
{{ _("mas.policy_violation.logged_as", username=current_session.user.username) }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=action, as_link=True) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ back_to_client.link(
|
||||||
|
text=_("action.cancel"),
|
||||||
|
kind="destructive",
|
||||||
|
uri=grant.redirect_uri,
|
||||||
|
mode=grant.response_mode,
|
||||||
|
params=dict(error="access_denied", state=grant.state)
|
||||||
|
) }}
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
@ -17,38 +17,33 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="flex items-center justify-center flex-1">
|
<section class="flex-1 flex flex-col gap-6 justify-center">
|
||||||
<div class="w-96 my-2 mx-8">
|
<div class="text-center">
|
||||||
<form method="POST" class="grid grid-cols-1 gap-6">
|
<h1 class="text-lg text-center font-medium">Hi {{ current_session.user.username }}</h1>
|
||||||
<div class="text-center">
|
<p>To continue, please verify it's you:</p>
|
||||||
<h1 class="text-lg text-center font-medium">Hi {{ current_session.user.username }}</h1>
|
</div>
|
||||||
<p>To continue, please verify it's you:</p>
|
|
||||||
</div>
|
<form method="POST" class="flex flex-col gap-6">
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
{# TODO: errors #}
|
{# TODO: errors #}
|
||||||
{{ field.input(label=_("common.password"), name="password", type="password", form_state=form, autocomplete="password") }}
|
{{ field.input(label=_("common.password"), name="password", type="password", form_state=form, autocomplete="password") }}
|
||||||
{% if next and next.kind == "continue_authorization_grant" %}
|
{{ button.button(text=_("action.continue")) }}
|
||||||
<div class="grid grid-cols-2 gap-4">
|
</form>
|
||||||
{{ back_to_client.link(
|
|
||||||
text="Cancel",
|
{% if next and next.kind == "continue_authorization_grant" %}
|
||||||
kind="destructive",
|
{{ back_to_client.link(
|
||||||
uri=next.grant.redirect_uri,
|
text="Cancel",
|
||||||
mode=next.grant.response_mode,
|
kind="destructive",
|
||||||
params=dict(error="access_denied", state=next.grant.state)
|
uri=next.grant.redirect_uri,
|
||||||
) }}
|
mode=next.grant.response_mode,
|
||||||
{{ button.button(text=_("action.continue")) }}
|
params=dict(error="access_denied", state=next.grant.state)
|
||||||
</div>
|
) }}
|
||||||
{% else %}
|
{% endif %}
|
||||||
<div class="grid grid-cols-1 gap-4">
|
|
||||||
{{ button.button(text=_("action.continue")) }}
|
<div class="text-center">
|
||||||
</div>
|
Not {{ current_session.user.username }}?
|
||||||
{% endif %}
|
{% set post_logout_action = next["params"] | default({}) %}
|
||||||
</form>
|
{{ logout.button(text="Sign out", csrf_token=csrf_token, post_logout_action=post_logout_action, as_link=true) }}
|
||||||
<div class="text-center mt-4">
|
|
||||||
Not {{ current_session.user.username }}?
|
|
||||||
{% set post_logout_action = next["params"] | default({}) %}
|
|
||||||
{{ logout.button(text="Sign out", csrf_token=csrf_token, post_logout_action=post_logout_action, as_link=true) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -17,12 +17,13 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="flex items-center justify-center flex-1">
|
<section class="flex-1 flex flex-col gap-6 justify-center">
|
||||||
<form method="POST" class="grid grid-cols-1 gap-6 w-96 my-2 mx-8">
|
<div class="text-center">
|
||||||
<div class="text-center">
|
<h1 class="text-lg text-center font-medium">{{ _("mas.register.create_account.heading") }}</h1>
|
||||||
<h1 class="text-lg text-center font-medium">{{ _("mas.register.create_account.heading") }}</h1>
|
<p>{{ _("mas.register.create_account.description") }}</p>
|
||||||
<p>{{ _("mas.register.create_account.description") }}</p>
|
</div>
|
||||||
</div>
|
|
||||||
|
<form method="POST" class="flex flex-col gap-6">
|
||||||
{% if form.errors is not empty %}
|
{% if form.errors is not empty %}
|
||||||
{% for error in form.errors %}
|
{% for error in form.errors %}
|
||||||
<div class="text-critical font-medium">
|
<div class="text-critical font-medium">
|
||||||
@ -36,29 +37,23 @@ limitations under the License.
|
|||||||
{{ field.input(label=_("common.email_address"), name="email", type="email", form_state=form, autocomplete="email") }}
|
{{ field.input(label=_("common.email_address"), name="email", type="email", form_state=form, autocomplete="email") }}
|
||||||
{{ field.input(label=_("common.password"), name="password", type="password", form_state=form, autocomplete="new-password") }}
|
{{ field.input(label=_("common.password"), name="password", type="password", form_state=form, autocomplete="new-password") }}
|
||||||
{{ field.input(label=_("common.password_confirm"), name="password_confirm", type="password", form_state=form, autocomplete="new-password") }}
|
{{ field.input(label=_("common.password_confirm"), name="password_confirm", type="password", form_state=form, autocomplete="new-password") }}
|
||||||
|
{{ button.button(text=_("action.continue")) }}
|
||||||
{% if next and next.kind == "continue_authorization_grant" %}
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
|
||||||
{{ back_to_client.link(
|
|
||||||
text=_("action.cancel"),
|
|
||||||
kind="destructive",
|
|
||||||
uri=next.grant.redirect_uri,
|
|
||||||
mode=next.grant.response_mode,
|
|
||||||
params=dict(error="access_denied", state=next.grant.state)
|
|
||||||
) }}
|
|
||||||
{{ button.button(text=_("action.continue")) }}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="grid grid-cols-1 gap-4">
|
|
||||||
{{ button.button(text=_("action.continue")) }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="text-center mt-4">
|
|
||||||
{{ _("mas.register.call_to_login") }}
|
|
||||||
{% set params = next["params"] | default({}) | to_params(prefix="?") %}
|
|
||||||
{{ button.link_text(text=_("mas.register.sign_in_instead"), href="/login" ~ params) }}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</section>
|
|
||||||
|
|
||||||
|
{% if next and next.kind == "continue_authorization_grant" %}
|
||||||
|
{{ back_to_client.link(
|
||||||
|
text=_("action.cancel"),
|
||||||
|
kind="destructive",
|
||||||
|
uri=next.grant.redirect_uri,
|
||||||
|
mode=next.grant.response_mode,
|
||||||
|
params=dict(error="access_denied", state=next.grant.state)
|
||||||
|
) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="text-center mt-4">
|
||||||
|
{{ _("mas.register.call_to_login") }}
|
||||||
|
{% set params = next["params"] | default({}) | to_params(prefix="?") %}
|
||||||
|
{{ button.link_text(text=_("mas.register.sign_in_instead"), href="/login" ~ params) }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -18,34 +18,32 @@ limitations under the License.
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% set client_name = login.redirect_uri | simplify_url %}
|
{% set client_name = login.redirect_uri | simplify_url %}
|
||||||
<section class="flex items-center justify-center flex-1">
|
<main class="flex-1 flex flex-col gap-6 justify-center">
|
||||||
<div class="w-96 mx-2 my-8 flex flex-col gap-6">
|
<div class="flex flex-col gap-2 text-center">
|
||||||
<div class="flex flex-col gap-2 text-center">
|
<div class="consent-client-icon generic">
|
||||||
<div class="consent-client-icon generic">
|
{{ icon.web_browser() }}
|
||||||
{{ icon.web_browser() }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="cpd-text-secondary cpd-text-body-lg-regular"><span class="whitespace-nowrap">{{ client_name }}</span> wants to access your account. This will allow <span class="whitespace-nowrap">{{ client_name }}</span> to:</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="consent-scope-list">
|
<p class="cpd-text-secondary cpd-text-body-lg-regular"><span class="whitespace-nowrap">{{ client_name }}</span> wants to access your account. This will allow <span class="whitespace-nowrap">{{ client_name }}</span> to:</p>
|
||||||
{{ scope.list(scopes="openid urn:matrix:org.matrix.msc2967.client:api:*") }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-2 text-center cpd-text-body-md-regular">
|
|
||||||
<span class="font-semibold">Make sure that you trust <span class="whitespace-nowrap">{{ client_name }}</span>.</span>
|
|
||||||
You may be sharing sensitive information with this site or app.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="POST" class="flex flex-col">
|
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
|
||||||
{{ button.button(text=_("action.continue")) }}
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
{{ _("mas.not_you", username=current_session.user.username) }}
|
|
||||||
{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=action, as_link=true) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
<div class="consent-scope-list">
|
||||||
|
{{ scope.list(scopes="openid urn:matrix:org.matrix.msc2967.client:api:*") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-2 text-center cpd-text-body-md-regular">
|
||||||
|
<span class="font-semibold">Make sure that you trust <span class="whitespace-nowrap">{{ client_name }}</span>.</span>
|
||||||
|
You may be sharing sensitive information with this site or app.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" class="flex flex-col">
|
||||||
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
|
{{ button.button(text=_("action.continue")) }}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
{{ _("mas.not_you", username=current_session.user.username) }}
|
||||||
|
{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=action, as_link=true) }}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -17,64 +17,58 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="flex items-center justify-center flex-1">
|
<section class="flex-1 flex flex-col justify-center gap-6">
|
||||||
<div class="grid grid-cols-1 gap-6 w-96 my-2 mx-8">
|
<h1 class="cpd-text-heading-xl-semibold text-center">
|
||||||
<form method="POST" class="grid grid-cols-1 gap-6">
|
{% if force_localpart %}
|
||||||
<h1 class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 text-center font-medium text-lg">
|
{{ _("mas.upstream_oauth2.register.create_account") }}
|
||||||
{% if force_localpart %}
|
{% else %}
|
||||||
{{ _("mas.upstream_oauth2.register.create_account") }}
|
{{ _("mas.upstream_oauth2.register.choose_username") }}
|
||||||
{% else %}
|
{% endif %}
|
||||||
{{ _("mas.upstream_oauth2.register.choose_username") }}
|
</h1>
|
||||||
{% endif %}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<form method="POST" class="flex flex-col gap-6">
|
||||||
<input type="hidden" name="action" value="register" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
{% if force_localpart %}
|
<input type="hidden" name="action" value="register" />
|
||||||
<div class="rounded-lg bg-grey-25 dark:bg-grey-450 p-4">
|
{% if force_localpart %}
|
||||||
<div class="font-medium"> {{ _("mas.upstream_oauth2.register.forced_localpart") }}</div>
|
<div class="rounded-lg bg-grey-25 dark:bg-grey-450 p-4">
|
||||||
<div class="font-mono">{{ suggested_localpart }}</div>
|
<div class="font-medium"> {{ _("mas.upstream_oauth2.register.forced_localpart") }}</div>
|
||||||
|
<div class="font-mono">{{ suggested_localpart }}</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ field.input(label=_("common.username"), name="username", autocomplete="username", autocorrect="off", autocapitalize="none") }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if suggested_email %}
|
||||||
|
<div class="rounded-lg bg-grey-25 dark:bg-grey-450 p-4">
|
||||||
|
<div class="font-medium">
|
||||||
|
{% if force_email %}
|
||||||
|
{{ _("mas.upstream_oauth2.register.forced_email") }}
|
||||||
|
{% else %}
|
||||||
|
<input type="checkbox" name="import_email" id="import_email" checked="checked" />
|
||||||
|
<label for="import_email">{{ _("mas.upstream_oauth2.register.suggested_email") }}</label>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
<div class="font-mono">{{ suggested_email }}</div>
|
||||||
{{ field.input(label=_("common.username"), name="username", autocomplete="username", autocorrect="off", autocapitalize="none") }}
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if suggested_email %}
|
{% if suggested_display_name %}
|
||||||
<div class="rounded-lg bg-grey-25 dark:bg-grey-450 p-4">
|
<div class="rounded-lg bg-grey-25 dark:bg-grey-450 p-4">
|
||||||
<div class="font-medium">
|
<div class="font-medium">
|
||||||
{% if force_email %}
|
{% if force_display_name %}
|
||||||
{{ _("mas.upstream_oauth2.register.forced_email") }}
|
{{ _("mas.upstream_oauth2.register.forced_display_name") }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<input type="checkbox" name="import_email" id="import_email" checked="checked" />
|
<input type="checkbox" name="import_display_name" id="import_display_name" checked="checked" />
|
||||||
<label for="import_email">{{ _("mas.upstream_oauth2.register.suggested_email") }}</label>
|
<label for="import_display_name">{{ _("mas.upstream_oauth2.register.suggested_display_name") }}</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
<div class="font-mono">{{ suggested_email }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
<div class="font-mono">{{ suggested_display_name }}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if suggested_display_name %}
|
{{ button.button(text=_("action.create_account")) }}
|
||||||
<div class="rounded-lg bg-grey-25 dark:bg-grey-450 p-4">
|
</form>
|
||||||
<div class="font-medium">
|
{{ field.separator() }}
|
||||||
{% if force_display_name %}
|
{{ button.link_outline(text=_("mas.upstream_oauth2.register.link_existing"), href=login_link) }}
|
||||||
{{ _("mas.upstream_oauth2.register.forced_display_name") }}
|
|
||||||
{% else %}
|
|
||||||
<input type="checkbox" name="import_display_name" id="import_display_name" checked="checked" />
|
|
||||||
<label for="import_display_name">{{ _("mas.upstream_oauth2.register.suggested_display_name") }}</label>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="font-mono">{{ suggested_display_name }}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{{ button.button(text=_("action.create_account")) }}
|
|
||||||
</form>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<hr class="flex-1" />
|
|
||||||
<div class="mx-2">{{ _("mas.or_separator") }}</div>
|
|
||||||
<hr class="flex-1" />
|
|
||||||
</div>
|
|
||||||
{{ button.link_outline(text=_("mas.upstream_oauth2.register.link_existing"), href=login_link) }}
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -17,14 +17,11 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ navbar.top() }}
|
<section class="flex-1 flex flex-col gap-6 justify-center">
|
||||||
<section class="flex items-center justify-center flex-1">
|
<h1 class="cpd-text-heading-xl-semibold text-center">
|
||||||
<div class="grid grid-cols-1 gap-6 w-96 my-2 mx-8">
|
{{ _("mas.upstream_oauth2.link_mismatch.heading") }}
|
||||||
<h1 class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 flex flex-col font-medium text-lg text-center">
|
</h1>
|
||||||
{{ _("mas.upstream_oauth2.link_mismatch.heading") }}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div>{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token) }}</div>
|
{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token) }}
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -17,10 +17,8 @@ limitations under the License.
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ navbar.top() }}
|
<section class="flex-1 flex flex-col gap-6 justify-center">
|
||||||
<section class="flex items-center justify-center flex-1">
|
<h1 class="cpd-text-heading-xl-semibold text-center">
|
||||||
<div class="grid grid-cols-1 gap-6 w-96 my-2 mx-8">
|
|
||||||
<h1 class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 flex flex-col font-medium text-lg text-center">
|
|
||||||
{{ _("mas.upstream_oauth2.suggest_link.heading") }}
|
{{ _("mas.upstream_oauth2.suggest_link.heading") }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@ -31,13 +29,8 @@ limitations under the License.
|
|||||||
{{ button.button(text=_("mas.upstream_oauth2.suggest_link.action"), class="flex-1") }}
|
{{ button.button(text=_("mas.upstream_oauth2.suggest_link.action"), class="flex-1") }}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="flex items-center">
|
{{ field.separator() }}
|
||||||
<hr class="flex-1" />
|
|
||||||
<div class="mx-2">{{ _("mas.or_separator") }}</div>
|
|
||||||
<hr class="flex-1" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=post_logout_action) }}
|
{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=post_logout_action) }}
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -2,68 +2,90 @@
|
|||||||
"action": {
|
"action": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"@cancel": {
|
"@cancel": {
|
||||||
"context": "pages/consent.html:63:13-31, pages/login.html:49:19-37, pages/policy_violation.html:43:15-33, pages/register.html:43:17-35"
|
"context": "pages/consent.html:62:11-29, pages/login.html:77:13-31, pages/policy_violation.html:41:11-29, pages/register.html:45:15-33"
|
||||||
},
|
},
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"@continue": {
|
"@continue": {
|
||||||
"context": "pages/account/emails/add.html:37:28-48, pages/consent.html:59:30-50, pages/login.html:55:34-54, pages/login.html:59:34-54, pages/reauth.html:39:34-54, pages/reauth.html:43:34-54, pages/register.html:49:32-52, pages/register.html:53:32-52, pages/sso.html:42:30-50"
|
"context": "pages/account/emails/add.html:33:26-46, pages/consent.html:58:28-48, pages/login.html:46:30-50, pages/reauth.html:30:28-48, pages/register.html:40:28-48, pages/sso.html:41:28-48"
|
||||||
},
|
},
|
||||||
"create_account": "Create Account",
|
"create_account": "Create Account",
|
||||||
"@create_account": {
|
"@create_account": {
|
||||||
"context": "pages/login.html:67:37-63, pages/upstream_oauth2/do_register.html:70:30-56"
|
"context": "pages/login.html:53:35-61, pages/upstream_oauth2/do_register.html:69:28-54"
|
||||||
},
|
},
|
||||||
"sign_in": "Sign in",
|
"sign_in": "Sign in",
|
||||||
"@sign_in": {
|
"@sign_in": {
|
||||||
"context": "components/navbar.html:30:28-47"
|
"context": "pages/index.html:34:26-45"
|
||||||
},
|
},
|
||||||
"sign_out": "Sign out",
|
"sign_out": "Sign out",
|
||||||
"@sign_out": {
|
"@sign_out": {
|
||||||
"context": "components/navbar.html:28:30-50, pages/consent.html:72:30-50, pages/policy_violation.html:39:32-52, pages/sso.html:47:30-50, pages/upstream_oauth2/link_mismatch.html:27:33-53, pages/upstream_oauth2/suggest_link.html:40:28-48"
|
"context": "pages/consent.html:71:28-48, pages/index.html:32:28-48, pages/policy_violation.html:37:28-48, pages/sso.html:46:28-48, pages/upstream_oauth2/link_mismatch.html:25:26-46, pages/upstream_oauth2/suggest_link.html:34:28-48"
|
||||||
},
|
},
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"@submit": {
|
"@submit": {
|
||||||
"context": "pages/account/emails/verify.html:38:28-46"
|
"context": "pages/account/emails/verify.html:36:26-44"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"human_name": "Matrix Authentication Service",
|
"human_name": "Matrix Authentication Service",
|
||||||
"@human_name": {
|
"@human_name": {
|
||||||
"context": "pages/index.html:23:63-82",
|
"context": "pages/index.html:21:48-67",
|
||||||
"description": "Human readable name of the application"
|
"description": "Human readable name of the application"
|
||||||
},
|
},
|
||||||
"name": "matrix-authentication-service",
|
"name": "matrix-authentication-service",
|
||||||
"@name": {
|
"@name": {
|
||||||
"context": "app.html:25:14-27, base.html:32:31-44",
|
"context": "app.html:25:14-27, base.html:31:31-44",
|
||||||
"description": "Name of the application"
|
"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>",
|
"technical_description": "OpenID Connect discovery document: <a class=\"cpd-link\" data-kind=\"primary\" href=\"%(discovery_url)s\">%(discovery_url)s</a>",
|
||||||
"@technical_description": {
|
"@technical_description": {
|
||||||
"context": "pages/index.html:25:11-70",
|
"context": "pages/index.html:23:9-68",
|
||||||
"description": "Introduction text displayed on the home page"
|
"description": "Introduction text displayed on the home page"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"branding": {
|
||||||
|
"privacy_policy": {
|
||||||
|
"alt": "Link to the service privacy policy",
|
||||||
|
"@alt": {
|
||||||
|
"context": "components/footer.html:21:83-115"
|
||||||
|
},
|
||||||
|
"link": "Privacy Policy",
|
||||||
|
"@link": {
|
||||||
|
"context": "components/footer.html:22:14-47"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"terms_and_conditions": {
|
||||||
|
"alt": "Link to the service terms and conditions",
|
||||||
|
"@alt": {
|
||||||
|
"context": "components/footer.html:31:80-118"
|
||||||
|
},
|
||||||
|
"link": "Terms & Conditions",
|
||||||
|
"@link": {
|
||||||
|
"context": "components/footer.html:32:14-53"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"email_address": "Email address",
|
"email_address": "Email address",
|
||||||
"@email_address": {
|
"@email_address": {
|
||||||
"context": "pages/account/emails/add.html:36:27-52, pages/register.html:36:27-52"
|
"context": "pages/account/emails/add.html:32:25-50, pages/register.html:37:27-52"
|
||||||
},
|
},
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"@password": {
|
"@password": {
|
||||||
"context": "pages/login.html:45:29-49, pages/reauth.html:29:29-49, pages/register.html:37:27-47"
|
"context": "pages/login.html:45:29-49, pages/reauth.html:29:27-47, pages/register.html:38:27-47"
|
||||||
},
|
},
|
||||||
"password_confirm": "Confirm password",
|
"password_confirm": "Confirm password",
|
||||||
"@password_confirm": {
|
"@password_confirm": {
|
||||||
"context": "pages/register.html:38:27-55"
|
"context": "pages/register.html:39:27-55"
|
||||||
},
|
},
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"@username": {
|
"@username": {
|
||||||
"context": "pages/login.html:44:29-49, pages/register.html:35:27-47, pages/upstream_oauth2/do_register.html:39:31-51"
|
"context": "pages/login.html:44:29-49, pages/register.html:36:27-47, pages/upstream_oauth2/do_register.html:38:29-49"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unexpected": "Unexpected error",
|
"unexpected": "Unexpected error",
|
||||||
"@unexpected": {
|
"@unexpected": {
|
||||||
"context": "pages/error.html:25:41-62",
|
"context": "pages/error.html:24:41-62",
|
||||||
"description": "Error message displayed when an unexpected error occurs"
|
"description": "Error message displayed when an unexpected error occurs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -71,38 +93,38 @@
|
|||||||
"add_email": {
|
"add_email": {
|
||||||
"heading": "Add an email address",
|
"heading": "Add an email address",
|
||||||
"@heading": {
|
"@heading": {
|
||||||
"context": "pages/account/emails/add.html:24:55-81",
|
"context": "pages/account/emails/add.html:21:48-74",
|
||||||
"description": "Heading for the page to add an email address"
|
"description": "Heading for the page to add an email address"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"back_to_homepage": "Go back to the homepage",
|
"back_to_homepage": "Go back to the homepage",
|
||||||
"@back_to_homepage": {
|
"@back_to_homepage": {
|
||||||
"context": "pages/404.html:25:37-62"
|
"context": "pages/404.html:24:29-54"
|
||||||
},
|
},
|
||||||
"change_password": {
|
"change_password": {
|
||||||
"change": "Change password",
|
"change": "Change password",
|
||||||
"@change": {
|
"@change": {
|
||||||
"context": "pages/account/password.html:28:28-59",
|
"context": "pages/account/password.html:26:26-57",
|
||||||
"description": "Button to change the user's password"
|
"description": "Button to change the user's password"
|
||||||
},
|
},
|
||||||
"confirm": "Confirm password",
|
"confirm": "Confirm password",
|
||||||
"@confirm": {
|
"@confirm": {
|
||||||
"context": "pages/account/password.html:27:27-59",
|
"context": "pages/account/password.html:25:25-57",
|
||||||
"description": "Confirmation field for the new password"
|
"description": "Confirmation field for the new password"
|
||||||
},
|
},
|
||||||
"current": "Current password",
|
"current": "Current password",
|
||||||
"@current": {
|
"@current": {
|
||||||
"context": "pages/account/password.html:25:27-59",
|
"context": "pages/account/password.html:23:25-57",
|
||||||
"description": "Field for the user's current password"
|
"description": "Field for the user's current password"
|
||||||
},
|
},
|
||||||
"heading": "Change my password",
|
"heading": "Change my password",
|
||||||
"@heading": {
|
"@heading": {
|
||||||
"context": "pages/account/password.html:23:57-89",
|
"context": "pages/account/password.html:21:48-80",
|
||||||
"description": "Heading on the change password page"
|
"description": "Heading on the change password page"
|
||||||
},
|
},
|
||||||
"new": "New password",
|
"new": "New password",
|
||||||
"@new": {
|
"@new": {
|
||||||
"context": "pages/account/password.html:26:27-55",
|
"context": "pages/account/password.html:24:25-53",
|
||||||
"description": "Field for the user's new password"
|
"description": "Field for the user's new password"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -155,106 +177,106 @@
|
|||||||
"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:65:15-46"
|
"context": "pages/login.html:51:13-44"
|
||||||
},
|
},
|
||||||
"continue_with_provider": "Continue with %(provider)s",
|
"continue_with_provider": "Continue with %(provider)s",
|
||||||
"@continue_with_provider": {
|
"@continue_with_provider": {
|
||||||
"context": "pages/login.html:83:30-93",
|
"context": "pages/login.html:65:144-222",
|
||||||
"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:31:18-44"
|
"context": "pages/login.html:30:16-42"
|
||||||
},
|
},
|
||||||
"headline": "Sign in",
|
"headline": "Sign in",
|
||||||
"@headline": {
|
"@headline": {
|
||||||
"context": "pages/login.html:30:59-82"
|
"context": "pages/login.html:29:57-80"
|
||||||
},
|
},
|
||||||
"link": {
|
"link": {
|
||||||
"description": "Linking your <span class=\"break-keep text-links\">%(provider)s</span> account",
|
"description": "Linking your <span class=\"break-keep text-links\">%(provider)s</span> account",
|
||||||
"@description": {
|
"@description": {
|
||||||
"context": "pages/login.html:26:34-96"
|
"context": "pages/login.html:25:32-94"
|
||||||
},
|
},
|
||||||
"headline": "Sign in to link",
|
"headline": "Sign in to link",
|
||||||
"@headline": {
|
"@headline": {
|
||||||
"context": "pages/login.html:25:59-87"
|
"context": "pages/login.html:24:57-85"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"no_login_methods": "No login methods available.",
|
"no_login_methods": "No login methods available.",
|
||||||
"@no_login_methods": {
|
"@no_login_methods": {
|
||||||
"context": "pages/login.html:89:13-44"
|
"context": "pages/login.html:71:11-42"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"navbar": {
|
"navbar": {
|
||||||
"my_account": "My account",
|
"my_account": "My account",
|
||||||
"@my_account": {
|
"@my_account": {
|
||||||
"context": "components/navbar.html:27:28-54"
|
"context": "pages/index.html:31:26-52"
|
||||||
},
|
},
|
||||||
"register": "Create an account",
|
"register": "Create an account",
|
||||||
"@register": {
|
"@register": {
|
||||||
"context": "components/navbar.html:31:36-60"
|
"context": "pages/index.html:35:34-58"
|
||||||
},
|
},
|
||||||
"signed_in_as": "Signed in as <span class=\"font-semibold\">%(username)s</span>.",
|
"signed_in_as": "Signed in as <span class=\"font-semibold\">%(username)s</span>.",
|
||||||
"@signed_in_as": {
|
"@signed_in_as": {
|
||||||
"context": "components/navbar.html:24:13-81",
|
"context": "pages/index.html:28:11-79",
|
||||||
"description": "Displayed in the navbar when the user is signed in"
|
"description": "Displayed in the navbar when the user is signed in"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"not_found": {
|
"not_found": {
|
||||||
"description": "The page you were looking for doesn't exist or has been moved",
|
"description": "The page you were looking for doesn't exist or has been moved",
|
||||||
"@description": {
|
"@description": {
|
||||||
"context": "pages/404.html:23:14-44"
|
"context": "pages/404.html:22:8-38"
|
||||||
},
|
},
|
||||||
"heading": "Page not found",
|
"heading": "Page not found",
|
||||||
"@heading": {
|
"@heading": {
|
||||||
"context": "pages/404.html:22:45-71"
|
"context": "pages/404.html:21:39-65"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"not_you": "Not %(username)s?",
|
"not_you": "Not %(username)s?",
|
||||||
"@not_you": {
|
"@not_you": {
|
||||||
"context": "pages/consent.html:71:11-67, pages/sso.html:46:11-67",
|
"context": "pages/consent.html:70:9-65, pages/sso.html:45:9-65",
|
||||||
"description": "Suggestions for the user to log in as a different user"
|
"description": "Suggestions for the user to log in as a different user"
|
||||||
},
|
},
|
||||||
"or_separator": "Or",
|
"or_separator": "Or",
|
||||||
"@or_separator": {
|
"@or_separator": {
|
||||||
"context": "pages/login.html:76:33-54, pages/upstream_oauth2/do_register.html:74:29-50, pages/upstream_oauth2/suggest_link.html:36:29-50",
|
"context": "components/field.html:62:10-31",
|
||||||
"description": "Separator between the login methods"
|
"description": "Separator between the login methods"
|
||||||
},
|
},
|
||||||
"policy_violation": {
|
"policy_violation": {
|
||||||
"description": "This might be because of the client which authored the request, the currently logged in user, or the request itself.",
|
"description": "This might be because of the client which authored the request, the currently logged in user, or the request itself.",
|
||||||
"@description": {
|
"@description": {
|
||||||
"context": "pages/policy_violation.html:24:14-51",
|
"context": "pages/policy_violation.html:22:10-47",
|
||||||
"description": "Displayed when an authorization request is denied by the policy"
|
"description": "Displayed when an authorization request is denied by the policy"
|
||||||
},
|
},
|
||||||
"heading": "The authorization request was denied the policy enforced by this service",
|
"heading": "The authorization request was denied the policy enforced by this service",
|
||||||
"@heading": {
|
"@heading": {
|
||||||
"context": "pages/policy_violation.html:23:45-78",
|
"context": "pages/policy_violation.html:21:48-81",
|
||||||
"description": "Displayed when an authorization request is denied by the policy"
|
"description": "Displayed when an authorization request is denied by the policy"
|
||||||
},
|
},
|
||||||
"logged_as": "Logged as <span class=\"font-semibold\">%(username)s</span>",
|
"logged_as": "Logged as <span class=\"font-semibold\">%(username)s</span>",
|
||||||
"@logged_as": {
|
"@logged_as": {
|
||||||
"context": "pages/policy_violation.html:36:15-90"
|
"context": "pages/policy_violation.html:34:11-86"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"register": {
|
"register": {
|
||||||
"call_to_login": "Already have an account?",
|
"call_to_login": "Already have an account?",
|
||||||
"@call_to_login": {
|
"@call_to_login": {
|
||||||
"context": "pages/register.html:57:11-42",
|
"context": "pages/register.html:54:9-40",
|
||||||
"description": "Displayed on the registration page to suggest to log in instead"
|
"description": "Displayed on the registration page to suggest to log in instead"
|
||||||
},
|
},
|
||||||
"create_account": {
|
"create_account": {
|
||||||
"description": "Please create an account to get started:",
|
"description": "Please create an account to get started:",
|
||||||
"@description": {
|
"@description": {
|
||||||
"context": "pages/register.html:24:14-58"
|
"context": "pages/register.html:23:12-56"
|
||||||
},
|
},
|
||||||
"heading": "Create an account",
|
"heading": "Create an account",
|
||||||
"@heading": {
|
"@heading": {
|
||||||
"context": "pages/register.html:23:55-95"
|
"context": "pages/register.html:22:53-93"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sign_in_instead": "Sign in instead",
|
"sign_in_instead": "Sign in instead",
|
||||||
"@sign_in_instead": {
|
"@sign_in_instead": {
|
||||||
"context": "pages/register.html:59:33-66"
|
"context": "pages/register.html:56:31-64"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scope": {
|
"scope": {
|
||||||
@ -297,75 +319,75 @@
|
|||||||
"link_mismatch": {
|
"link_mismatch": {
|
||||||
"heading": "This upstream account is already linked to another account.",
|
"heading": "This upstream account is already linked to another account.",
|
||||||
"@heading": {
|
"@heading": {
|
||||||
"context": "pages/upstream_oauth2/link_mismatch.html:24:11-57",
|
"context": "pages/upstream_oauth2/link_mismatch.html:22:9-55",
|
||||||
"description": "Page shown when the user tries to link an upstream account that is already linked to another account"
|
"description": "Page shown when the user tries to link an upstream account that is already linked to another account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"register": {
|
"register": {
|
||||||
"choose_username": "Choose your username",
|
"choose_username": "Choose your username",
|
||||||
"@choose_username": {
|
"@choose_username": {
|
||||||
"context": "pages/upstream_oauth2/do_register.html:27:15-64",
|
"context": "pages/upstream_oauth2/do_register.html:25:11-60",
|
||||||
"description": "Displayed when creating a new account from an SSO login, and the username is not forced"
|
"description": "Displayed when creating a new account from an SSO login, and the username is not forced"
|
||||||
},
|
},
|
||||||
"create_account": "Create a new account",
|
"create_account": "Create a new account",
|
||||||
"@create_account": {
|
"@create_account": {
|
||||||
"context": "pages/upstream_oauth2/do_register.html:25:15-63",
|
"context": "pages/upstream_oauth2/do_register.html:23:11-59",
|
||||||
"description": "Displayed when creating a new account from an SSO login, and the username is pre-filled and forced"
|
"description": "Displayed when creating a new account from an SSO login, and the username is pre-filled and forced"
|
||||||
},
|
},
|
||||||
"forced_display_name": "Will use the following display name",
|
"forced_display_name": "Will use the following display name",
|
||||||
"@forced_display_name": {
|
"@forced_display_name": {
|
||||||
"context": "pages/upstream_oauth2/do_register.html:60:19-72",
|
"context": "pages/upstream_oauth2/do_register.html:59:17-70",
|
||||||
"description": "Tells the user what display name will be imported"
|
"description": "Tells the user what display name will be imported"
|
||||||
},
|
},
|
||||||
"forced_email": "Will use the following email address",
|
"forced_email": "Will use the following email address",
|
||||||
"@forced_email": {
|
"@forced_email": {
|
||||||
"context": "pages/upstream_oauth2/do_register.html:46:19-65",
|
"context": "pages/upstream_oauth2/do_register.html:45:17-63",
|
||||||
"description": "Tells the user which email address will be imported"
|
"description": "Tells the user which email address will be imported"
|
||||||
},
|
},
|
||||||
"forced_localpart": "Will use the following username",
|
"forced_localpart": "Will use the following username",
|
||||||
"@forced_localpart": {
|
"@forced_localpart": {
|
||||||
"context": "pages/upstream_oauth2/do_register.html:35:41-91",
|
"context": "pages/upstream_oauth2/do_register.html:34:39-89",
|
||||||
"description": "Tells the user which username will be used"
|
"description": "Tells the user which username will be used"
|
||||||
},
|
},
|
||||||
"link_existing": "Link to an existing account",
|
"link_existing": "Link to an existing account",
|
||||||
"@link_existing": {
|
"@link_existing": {
|
||||||
"context": "pages/upstream_oauth2/do_register.html:77:34-81",
|
"context": "pages/upstream_oauth2/do_register.html:72:32-79",
|
||||||
"description": "Button to link an existing account after an SSO login"
|
"description": "Button to link an existing account after an SSO login"
|
||||||
},
|
},
|
||||||
"suggested_display_name": "Import display name",
|
"suggested_display_name": "Import display name",
|
||||||
"@suggested_display_name": {
|
"@suggested_display_name": {
|
||||||
"context": "pages/upstream_oauth2/do_register.html:63:50-106",
|
"context": "pages/upstream_oauth2/do_register.html:62:48-104",
|
||||||
"description": "Option to let the user import their display name after an SSO login"
|
"description": "Option to let the user import their display name after an SSO login"
|
||||||
},
|
},
|
||||||
"suggested_email": "Import email address",
|
"suggested_email": "Import email address",
|
||||||
"@suggested_email": {
|
"@suggested_email": {
|
||||||
"context": "pages/upstream_oauth2/do_register.html:49:45-94",
|
"context": "pages/upstream_oauth2/do_register.html:48:43-92",
|
||||||
"description": "Option to let the user import their email address after an SSO login"
|
"description": "Option to let the user import their email address after an SSO login"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"suggest_link": {
|
"suggest_link": {
|
||||||
"action": "Link",
|
"action": "Link",
|
||||||
"@action": {
|
"@action": {
|
||||||
"context": "pages/upstream_oauth2/suggest_link.html:31:30-74"
|
"context": "pages/upstream_oauth2/suggest_link.html:29:30-74"
|
||||||
},
|
},
|
||||||
"heading": "Link to your existing account",
|
"heading": "Link to your existing account",
|
||||||
"@heading": {
|
"@heading": {
|
||||||
"context": "pages/upstream_oauth2/suggest_link.html:24:11-56"
|
"context": "pages/upstream_oauth2/suggest_link.html:22:11-56"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"verify_email": {
|
"verify_email": {
|
||||||
"code": "Code",
|
"code": "Code",
|
||||||
"@code": {
|
"@code": {
|
||||||
"context": "pages/account/emails/verify.html:37:27-53"
|
"context": "pages/account/emails/verify.html:35:25-51"
|
||||||
},
|
},
|
||||||
"description": "Please enter the 6-digit code sent to: <span class=\"font-semibold\">%(email)s</span>",
|
"description": "Please enter the 6-digit code sent to: <span class=\"font-semibold\">%(email)s</span>",
|
||||||
"@description": {
|
"@description": {
|
||||||
"context": "pages/account/emails/verify.html:25:14-66"
|
"context": "pages/account/emails/verify.html:23:12-64"
|
||||||
},
|
},
|
||||||
"headline": "Email verification",
|
"headline": "Email verification",
|
||||||
"@headline": {
|
"@headline": {
|
||||||
"context": "pages/account/emails/verify.html:24:55-85"
|
"context": "pages/account/emails/verify.html:22:50-80"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user