You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-31 09:24:31 +03:00
templates: translate a lot more stuff
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3267,9 +3267,11 @@ dependencies = [
|
|||||||
"futures-lite",
|
"futures-lite",
|
||||||
"mas-data-model",
|
"mas-data-model",
|
||||||
"mas-email",
|
"mas-email",
|
||||||
|
"mas-i18n",
|
||||||
"mas-matrix",
|
"mas-matrix",
|
||||||
"mas-storage",
|
"mas-storage",
|
||||||
"mas-storage-pg",
|
"mas-storage-pg",
|
||||||
|
"mas-templates",
|
||||||
"mas-tower",
|
"mas-tower",
|
||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
@ -18,7 +18,7 @@ use lettre::{
|
|||||||
message::{Mailbox, MessageBuilder, MultiPart},
|
message::{Mailbox, MessageBuilder, MultiPart},
|
||||||
AsyncTransport, Message,
|
AsyncTransport, Message,
|
||||||
};
|
};
|
||||||
use mas_templates::{EmailVerificationContext, Templates};
|
use mas_templates::{EmailVerificationContext, Templates, WithLanguage};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::MailTransport;
|
use crate::MailTransport;
|
||||||
@ -66,7 +66,7 @@ impl Mailer {
|
|||||||
fn prepare_verification_email(
|
fn prepare_verification_email(
|
||||||
&self,
|
&self,
|
||||||
to: Mailbox,
|
to: Mailbox,
|
||||||
context: &EmailVerificationContext,
|
context: &WithLanguage<EmailVerificationContext>,
|
||||||
) -> Result<Message, Error> {
|
) -> Result<Message, Error> {
|
||||||
let plain = self.templates.render_email_verification_txt(context)?;
|
let plain = self.templates.render_email_verification_txt(context)?;
|
||||||
|
|
||||||
@ -95,6 +95,7 @@ impl Mailer {
|
|||||||
skip_all,
|
skip_all,
|
||||||
fields(
|
fields(
|
||||||
email.to = %to,
|
email.to = %to,
|
||||||
|
email.language = %context.language(),
|
||||||
user.id = %context.user().id,
|
user.id = %context.user().id,
|
||||||
user_email_verification.id = %context.verification().id,
|
user_email_verification.id = %context.verification().id,
|
||||||
user_email_verification.code = context.verification().code,
|
user_email_verification.code = context.verification().code,
|
||||||
@ -104,7 +105,7 @@ impl Mailer {
|
|||||||
pub async fn send_verification_email(
|
pub async fn send_verification_email(
|
||||||
&self,
|
&self,
|
||||||
to: Mailbox,
|
to: Mailbox,
|
||||||
context: &EmailVerificationContext,
|
context: &WithLanguage<EmailVerificationContext>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let message = self.prepare_verification_email(to, context)?;
|
let message = self.prepare_verification_email(to, context)?;
|
||||||
self.transport.send(message).await?;
|
self.transport.send(message).await?;
|
||||||
|
@ -449,6 +449,7 @@ impl UserEmailMutations {
|
|||||||
.mark_as_verified(&state.clock(), user_email)
|
.mark_as_verified(&state.clock(), user_email)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: figure out the locale
|
||||||
repo.job()
|
repo.job()
|
||||||
.schedule_job(VerifyEmailJob::new(&user_email))
|
.schedule_job(VerifyEmailJob::new(&user_email))
|
||||||
.await?;
|
.await?;
|
||||||
@ -490,6 +491,7 @@ impl UserEmailMutations {
|
|||||||
// Schedule a job to verify the email address if needed
|
// Schedule a job to verify the email address if needed
|
||||||
let needs_verification = user_email.confirmed_at.is_none();
|
let needs_verification = user_email.confirmed_at.is_none();
|
||||||
if needs_verification {
|
if needs_verification {
|
||||||
|
// TODO: figure out the locale
|
||||||
repo.job()
|
repo.job()
|
||||||
.schedule_job(VerifyEmailJob::new(&user_email))
|
.schedule_job(VerifyEmailJob::new(&user_email))
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -105,7 +105,8 @@ pub async fn get(
|
|||||||
if clock.now() > login.created_at + Duration::minutes(30) {
|
if clock.now() > login.created_at + Duration::minutes(30) {
|
||||||
let ctx = ErrorContext::new()
|
let ctx = ErrorContext::new()
|
||||||
.with_code("compat_sso_login_expired")
|
.with_code("compat_sso_login_expired")
|
||||||
.with_description("This login session expired.".to_owned());
|
.with_description("This login session expired.".to_owned())
|
||||||
|
.with_language(&locale);
|
||||||
|
|
||||||
let content = templates.render_error(&ctx)?;
|
let content = templates.render_error(&ctx)?;
|
||||||
return Ok((cookie_jar, Html(content)).into_response());
|
return Ok((cookie_jar, Html(content)).into_response());
|
||||||
@ -131,6 +132,7 @@ pub async fn post(
|
|||||||
mut rng: BoxRng,
|
mut rng: BoxRng,
|
||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
Path(id): Path<Ulid>,
|
Path(id): Path<Ulid>,
|
||||||
@ -173,7 +175,8 @@ pub async fn post(
|
|||||||
if clock.now() > login.created_at + Duration::minutes(30) {
|
if clock.now() > login.created_at + Duration::minutes(30) {
|
||||||
let ctx = ErrorContext::new()
|
let ctx = ErrorContext::new()
|
||||||
.with_code("compat_sso_login_expired")
|
.with_code("compat_sso_login_expired")
|
||||||
.with_description("This login session expired.".to_owned());
|
.with_description("This login session expired.".to_owned())
|
||||||
|
.with_language(&locale);
|
||||||
|
|
||||||
let content = templates.render_error(&ctx)?;
|
let content = templates.render_error(&ctx)?;
|
||||||
return Ok((cookie_jar, Html(content)).into_response());
|
return Ok((cookie_jar, Html(content)).into_response());
|
||||||
|
@ -77,6 +77,7 @@ pub(crate) async fn post(
|
|||||||
mut rng: BoxRng,
|
mut rng: BoxRng,
|
||||||
clock: BoxClock,
|
clock: BoxClock,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
mut policy: Policy,
|
mut policy: Policy,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
activity_tracker: BoundActivityTracker,
|
activity_tracker: BoundActivityTracker,
|
||||||
@ -124,7 +125,7 @@ pub(crate) async fn post(
|
|||||||
// verify page
|
// verify page
|
||||||
let next = if user_email.confirmed_at.is_none() {
|
let next = if user_email.confirmed_at.is_none() {
|
||||||
repo.job()
|
repo.job()
|
||||||
.schedule_job(VerifyEmailJob::new(&user_email))
|
.schedule_job(VerifyEmailJob::new(&user_email).with_language(locale.to_string()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let next = mas_router::AccountVerifyEmail::new(user_email.id);
|
let next = mas_router::AccountVerifyEmail::new(user_email.id);
|
||||||
|
@ -34,7 +34,6 @@ pub async fn get(
|
|||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
) -> Result<impl IntoResponse, FancyError> {
|
) -> Result<impl IntoResponse, FancyError> {
|
||||||
tracing::info!("{locale}");
|
|
||||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
||||||
let (session_info, cookie_jar) = cookie_jar.session_info();
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
let session = session_info.load_session(&mut repo).await?;
|
let session = session_info.load_session(&mut repo).await?;
|
||||||
@ -52,7 +51,5 @@ pub async fn get(
|
|||||||
|
|
||||||
let content = templates.render_index(&ctx)?;
|
let content = templates.render_index(&ctx)?;
|
||||||
|
|
||||||
tracing::info!("rendered index page");
|
|
||||||
|
|
||||||
Ok((cookie_jar, Html(content)))
|
Ok((cookie_jar, Html(content)))
|
||||||
}
|
}
|
||||||
|
@ -225,7 +225,7 @@ pub(crate) async fn post(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
repo.job()
|
repo.job()
|
||||||
.schedule_job(VerifyEmailJob::new(&user_email))
|
.schedule_job(VerifyEmailJob::new(&user_email).with_language(locale.to_string()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
repo.job()
|
repo.job()
|
||||||
|
@ -19,6 +19,7 @@ pub mod sprintf;
|
|||||||
pub mod translations;
|
pub mod translations;
|
||||||
mod translator;
|
mod translator;
|
||||||
|
|
||||||
|
pub use icu_locid::locale;
|
||||||
pub use icu_provider::DataLocale;
|
pub use icu_provider::DataLocale;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
|
@ -18,7 +18,7 @@ use ulid::Ulid;
|
|||||||
pub use crate::traits::*;
|
pub use crate::traits::*;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "snake_case", tag = "next")]
|
#[serde(rename_all = "snake_case", tag = "kind")]
|
||||||
pub enum PostAuthAction {
|
pub enum PostAuthAction {
|
||||||
ContinueAuthorizationGrant {
|
ContinueAuthorizationGrant {
|
||||||
id: Ulid,
|
id: Ulid,
|
||||||
|
@ -239,6 +239,7 @@ mod jobs {
|
|||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct VerifyEmailJob {
|
pub struct VerifyEmailJob {
|
||||||
user_email_id: Ulid,
|
user_email_id: Ulid,
|
||||||
|
language: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VerifyEmailJob {
|
impl VerifyEmailJob {
|
||||||
@ -247,9 +248,23 @@ mod jobs {
|
|||||||
pub fn new(user_email: &UserEmail) -> Self {
|
pub fn new(user_email: &UserEmail) -> Self {
|
||||||
Self {
|
Self {
|
||||||
user_email_id: user_email.id,
|
user_email_id: user_email.id,
|
||||||
|
language: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the language to use for the email.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_language(mut self, language: String) -> Self {
|
||||||
|
self.language = Some(language);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The language to use for the email.
|
||||||
|
#[must_use]
|
||||||
|
pub fn language(&self) -> Option<&str> {
|
||||||
|
self.language.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
/// The ID of the email address to verify.
|
/// The ID of the email address to verify.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn user_email_id(&self) -> Ulid {
|
pub fn user_email_id(&self) -> Ulid {
|
||||||
|
@ -32,7 +32,9 @@ serde_json.workspace = true
|
|||||||
|
|
||||||
mas-data-model = { path = "../data-model" }
|
mas-data-model = { path = "../data-model" }
|
||||||
mas-email = { path = "../email" }
|
mas-email = { path = "../email" }
|
||||||
|
mas-i18n = { path = "../i18n" }
|
||||||
mas-matrix = { path = "../matrix" }
|
mas-matrix = { path = "../matrix" }
|
||||||
mas-storage = { path = "../storage" }
|
mas-storage = { path = "../storage" }
|
||||||
mas-storage-pg = { path = "../storage-pg" }
|
mas-storage-pg = { path = "../storage-pg" }
|
||||||
|
mas-templates = { path = "../templates" }
|
||||||
mas-tower = { path = "../tower" }
|
mas-tower = { path = "../tower" }
|
||||||
|
@ -15,8 +15,10 @@
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use apalis_core::{context::JobContext, executor::TokioExecutor, monitor::Monitor};
|
use apalis_core::{context::JobContext, executor::TokioExecutor, monitor::Monitor};
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use mas_email::{Address, EmailVerificationContext, Mailbox};
|
use mas_email::{Address, Mailbox};
|
||||||
|
use mas_i18n::locale;
|
||||||
use mas_storage::job::{JobWithSpanContext, VerifyEmailJob};
|
use mas_storage::job::{JobWithSpanContext, VerifyEmailJob};
|
||||||
|
use mas_templates::{EmailVerificationContext, TemplateContext};
|
||||||
use rand::{distributions::Uniform, Rng};
|
use rand::{distributions::Uniform, Rng};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
@ -38,6 +40,11 @@ async fn verify_email(
|
|||||||
let mailer = state.mailer();
|
let mailer = state.mailer();
|
||||||
let clock = state.clock();
|
let clock = state.clock();
|
||||||
|
|
||||||
|
let language = job
|
||||||
|
.language()
|
||||||
|
.and_then(|l| l.parse().ok())
|
||||||
|
.unwrap_or(locale!("en").into());
|
||||||
|
|
||||||
// Lookup the user email
|
// Lookup the user email
|
||||||
let user_email = repo
|
let user_email = repo
|
||||||
.user_email()
|
.user_email()
|
||||||
@ -68,7 +75,8 @@ async fn verify_email(
|
|||||||
// And send the verification email
|
// And send the verification email
|
||||||
let mailbox = Mailbox::new(Some(user.username.clone()), address);
|
let mailbox = Mailbox::new(Some(user.username.clone()), address);
|
||||||
|
|
||||||
let context = EmailVerificationContext::new(user.clone(), verification.clone());
|
let context =
|
||||||
|
EmailVerificationContext::new(user.clone(), verification.clone()).with_language(language);
|
||||||
|
|
||||||
mailer.send_verification_email(mailbox, &context).await?;
|
mailer.send_verification_email(mailbox, &context).await?;
|
||||||
|
|
||||||
|
@ -109,6 +109,21 @@ pub struct WithLanguage<T> {
|
|||||||
inner: T,
|
inner: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> WithLanguage<T> {
|
||||||
|
/// Get the language of this context
|
||||||
|
pub fn language(&self) -> &str {
|
||||||
|
&self.lang
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::ops::Deref for WithLanguage<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: TemplateContext> TemplateContext for WithLanguage<T> {
|
impl<T: TemplateContext> TemplateContext for WithLanguage<T> {
|
||||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||||
where
|
where
|
||||||
@ -984,6 +999,7 @@ pub struct ErrorContext {
|
|||||||
code: Option<&'static str>,
|
code: Option<&'static str>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
details: Option<String>,
|
details: Option<String>,
|
||||||
|
lang: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ErrorContext {
|
impl std::fmt::Display for ErrorContext {
|
||||||
@ -1047,6 +1063,13 @@ impl ErrorContext {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add the language to the context
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_language(mut self, lang: &DataLocale) -> Self {
|
||||||
|
self.lang = Some(lang.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the error code, if any
|
/// Get the error code, if any
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn code(&self) -> Option<&'static str> {
|
pub fn code(&self) -> Option<&'static str> {
|
||||||
|
@ -29,7 +29,7 @@ use std::{collections::HashSet, sync::Arc};
|
|||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
use camino::{Utf8Path, Utf8PathBuf};
|
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 rand::Rng;
|
use rand::Rng;
|
||||||
@ -346,13 +346,13 @@ register_templates! {
|
|||||||
pub fn render_error(ErrorContext) { "pages/error.html" }
|
pub fn render_error(ErrorContext) { "pages/error.html" }
|
||||||
|
|
||||||
/// Render the email verification email (plain text variant)
|
/// Render the email verification email (plain text variant)
|
||||||
pub fn render_email_verification_txt(EmailVerificationContext) { "emails/verification.txt" }
|
pub fn render_email_verification_txt(WithLanguage<EmailVerificationContext>) { "emails/verification.txt" }
|
||||||
|
|
||||||
/// Render the email verification email (HTML text variant)
|
/// Render the email verification email (HTML text variant)
|
||||||
pub fn render_email_verification_html(EmailVerificationContext) { "emails/verification.html" }
|
pub fn render_email_verification_html(WithLanguage<EmailVerificationContext>) { "emails/verification.html" }
|
||||||
|
|
||||||
/// Render the email verification subject
|
/// Render the email verification subject
|
||||||
pub fn render_email_verification_subject(EmailVerificationContext) { "emails/verification.subject" }
|
pub fn render_email_verification_subject(WithLanguage<EmailVerificationContext>) { "emails/verification.subject" }
|
||||||
|
|
||||||
/// Render the upstream link mismatch message
|
/// Render the upstream link mismatch message
|
||||||
pub fn render_upstream_oauth2_link_mismatch(WithLanguage<WithCsrf<WithSession<UpstreamExistingLinkContext>>>) { "pages/upstream_oauth2/link_mismatch.html" }
|
pub fn render_upstream_oauth2_link_mismatch(WithLanguage<WithCsrf<WithSession<UpstreamExistingLinkContext>>>) { "pages/upstream_oauth2/link_mismatch.html" }
|
||||||
|
@ -16,9 +16,9 @@ limitations under the License.
|
|||||||
|
|
||||||
{% macro form_error_message(error) -%}
|
{% macro form_error_message(error) -%}
|
||||||
{% if error.kind == "invalid_credentials" %}
|
{% if error.kind == "invalid_credentials" %}
|
||||||
Invalid credentials
|
{{ _("mas.errors.invalid_credentials") }}
|
||||||
{% elif error.kind == "password_mismatch" %}
|
{% elif error.kind == "password_mismatch" %}
|
||||||
Password fields don't match
|
{{ _("mas.errors.password_mismatch") }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ error.kind }}
|
{{ error.kind }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -26,6 +26,7 @@ limitations under the License.
|
|||||||
<input name="{{ name }}"
|
<input name="{{ name }}"
|
||||||
class="cpd-control"
|
class="cpd-control"
|
||||||
{% if state.errors is not empty %} data-invalid {% endif %}
|
{% if state.errors is not empty %} data-invalid {% endif %}
|
||||||
|
{% if state.value %} value="{{ state.value }}" {% endif %}
|
||||||
type="{{ type }}"
|
type="{{ type }}"
|
||||||
inputmode="{{ inputmode }}"
|
inputmode="{{ inputmode }}"
|
||||||
{% if required %} required {% endif %}
|
{% if required %} required {% endif %}
|
||||||
@ -40,11 +41,11 @@ limitations under the License.
|
|||||||
{% if error.kind != "unspecified" %}
|
{% if error.kind != "unspecified" %}
|
||||||
<div class="text-sm text-critical">
|
<div class="text-sm text-critical">
|
||||||
{% if error.kind == "required" %}
|
{% if error.kind == "required" %}
|
||||||
This field is required
|
{{ _("mas.errors.field_required") }}
|
||||||
{% elif error.kind == "exists" and name == "username" %}
|
{% elif error.kind == "exists" and name == "username" %}
|
||||||
This username is already taken
|
{{ _("mas.errors.username_taken") }}
|
||||||
{% elif error.kind == "policy" %}
|
{% elif error.kind == "policy" %}
|
||||||
Denied by policy: {{ error.message }}
|
{{ _("mas.errors.denied_policy", message=error.message) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ error.kind }}
|
{{ error.kind }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -15,7 +15,6 @@ limitations under the License.
|
|||||||
#}
|
#}
|
||||||
|
|
||||||
{% macro top() %}
|
{% macro top() %}
|
||||||
<!-- {{ lang }} -->
|
|
||||||
<nav class="container mx-auto py-2 flex-initial flex items-center px-8" role="navigation" aria-label="main navigation">
|
<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="flex-1"></div>
|
||||||
|
|
||||||
|
@ -12,8 +12,10 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
|||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
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.
|
||||||
#}
|
-#}
|
||||||
|
|
||||||
Hello {{ user.username }},<br />
|
{%- set _ = translator(lang) -%}
|
||||||
|
|
||||||
|
{{ _("mas.emails.greeting", username=user.username) }}<br />
|
||||||
<br />
|
<br />
|
||||||
Your verification code to confirm this email address is: <strong>{{ verification.code }}</strong><br />
|
{{ _("mas.emails.verify.body_html", code=verification.code) }}<br />
|
||||||
|
@ -12,6 +12,8 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
|||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
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.
|
||||||
#}
|
-#}
|
||||||
|
|
||||||
Your email verification code is: {{ verification.code }}
|
{%- set _ = translator(lang) -%}
|
||||||
|
|
||||||
|
{{ _("mas.emails.verify.subject", code=verification.code) }}
|
||||||
|
@ -12,9 +12,10 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
|||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
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.
|
||||||
#}
|
-#}
|
||||||
|
|
||||||
|
{%- set _ = translator(lang) -%}
|
||||||
|
|
||||||
Hello {{ user.username }},
|
{{ _("mas.emails.greeting", username=user.username) }}
|
||||||
|
|
||||||
Your verification code to confirm this email address is: {{ verification.code }}
|
{{ _("mas.emails.verify.body_text", code=verification.code) }}
|
||||||
|
@ -21,7 +21,7 @@ limitations under the License.
|
|||||||
<section class="flex items-center justify-center flex-1">
|
<section class="flex items-center justify-center flex-1">
|
||||||
<form method="POST" class="grid grid-cols-1 gap-6 w-96 my-2 mx-8">
|
<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">Add an email address</h1>
|
<h1 class="text-lg text-center font-medium">{{ _("mas.add_email.heading") }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if form.errors is not empty %}
|
{% if form.errors is not empty %}
|
||||||
@ -33,7 +33,7 @@ limitations under the License.
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
{{ field.input(label="Email", 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="Next") }}
|
{{ button.button(text=_("action.continue")) }}
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -21,8 +21,8 @@ limitations under the License.
|
|||||||
<section class="flex items-center justify-center flex-1">
|
<section class="flex items-center justify-center flex-1">
|
||||||
<form method="POST" class="grid grid-cols-1 gap-6 w-96 my-2 mx-8">
|
<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">Email verification</h1>
|
<h1 class="text-lg text-center font-medium">{{ _("mas.verify_email.headline") }}</h1>
|
||||||
<p>Please enter the 6-digit code sent to: <span class="font-semibold">{{ email.email }}</span></p>
|
<p>{{ _("mas.verify_email.description", email=email.email) }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if form.errors is not empty %}
|
{% if form.errors is not empty %}
|
||||||
@ -34,7 +34,8 @@ limitations under the License.
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
{{ field.input(label="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="Submit") }}
|
{{ button.button(text=_("action.submit")) }}
|
||||||
|
</form>
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -20,12 +20,12 @@ limitations under the License.
|
|||||||
{{ navbar.top() }}
|
{{ navbar.top() }}
|
||||||
<section class="container mx-auto grid gap-4 grid-cols-1 md:grid-cols-2 xl:grid-cols-3 py-2 px-8">
|
<section class="container mx-auto grid gap-4 grid-cols-1 md:grid-cols-2 xl:grid-cols-3 py-2 px-8">
|
||||||
<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">
|
<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">
|
||||||
<h2 class="text-xl font-semibold xl:col-span-2">Change my password</h2>
|
<h2 class="text-xl font-semibold xl:col-span-2">{{ _("mas.change_password.heading") }}</h2>
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
{{ field.input(label="Current password", name="current_password", type="password", autocomplete="current-password", class="xl:col-span-2") }}
|
{{ field.input(label=_("mas.change_password.current"), name="current_password", type="password", autocomplete="current-password", class="xl:col-span-2") }}
|
||||||
{{ field.input(label="New password", name="new_password", type="password", autocomplete="new-password") }}
|
{{ field.input(label=_("mas.change_password.new"), name="new_password", type="password", autocomplete="new-password") }}
|
||||||
{{ field.input(label="Confirm password", name="new_password_confirm", type="password", autocomplete="new-password") }}
|
{{ field.input(label=_("mas.change_password.confirm"), name="new_password_confirm", type="password", autocomplete="new-password") }}
|
||||||
{{ button.button(text="Change password", type="submit", class="xl:col-span-2 place-self-end") }}
|
{{ button.button(text=_("mas.change_password.change"), type="submit", class="xl:col-span-2 place-self-end") }}
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -14,6 +14,9 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
#}
|
#}
|
||||||
|
|
||||||
|
{# Sometimes we don't have the language set, so we default to english #}
|
||||||
|
{% set lang = lang | default("en") %}
|
||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
@ -22,13 +22,13 @@ limitations under the License.
|
|||||||
{% 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">Sign in to link</h1>
|
<h1 class="text-lg text-center font-medium">{{ _("mas.login.link.headline") }}</h1>
|
||||||
<p class="text-sm">Linking your <span class="break-keep text-links">{{ next.provider.issuer }}</span> account</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">Sign in</h1>
|
<h1 class="text-lg text-center font-medium">{{ _("mas.login.headline") }}</h1>
|
||||||
<p>Please sign in to continue:</p>
|
<p>{{ _("mas.login.description") }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -41,30 +41,30 @@ limitations under the License.
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
{{ field.input(label="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="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" %}
|
{% if next and next.kind == "continue_authorization_grant" %}
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
{{ back_to_client.link(
|
{{ back_to_client.link(
|
||||||
text="Cancel",
|
text=_("action.cancel"),
|
||||||
kind="destructive",
|
kind="destructive",
|
||||||
uri=next.grant.redirect_uri,
|
uri=next.grant.redirect_uri,
|
||||||
mode=next.grant.response_mode,
|
mode=next.grant.response_mode,
|
||||||
params=dict(error="access_denied", state=next.grant.state)
|
params=dict(error="access_denied", state=next.grant.state)
|
||||||
) }}
|
) }}
|
||||||
{{ button.button(text="Next") }}
|
{{ button.button(text=_("action.continue")) }}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="grid grid-cols-1 gap-4">
|
<div class="grid grid-cols-1 gap-4">
|
||||||
{{ button.button(text="Next") }}
|
{{ button.button(text=_("action.continue")) }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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">
|
||||||
Don't have an account yet?
|
{{ _("mas.login.call_to_register") }}
|
||||||
{% set params = next["params"] | default({}) | to_params(prefix="?") %}
|
{% set params = next["params"] | default({}) | to_params(prefix="?") %}
|
||||||
{{ button.link_text(text="Create an account", href="/register" ~ params) }}
|
{{ button.link_text(text=_("action.create_account"), href="/register" ~ params) }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -73,20 +73,20 @@ limitations under the License.
|
|||||||
{% if not password_disabled %}
|
{% if not password_disabled %}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<hr class="flex-1" />
|
<hr class="flex-1" />
|
||||||
<div class="mx-2">Or</div>
|
<div class="mx-2">{{ _("mas.or_separator") }}</div>
|
||||||
<hr class="flex-1" />
|
<hr class="flex-1" />
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for provider in providers %}
|
{% for provider in providers %}
|
||||||
{% set params = next["params"] | default({}) | to_params(prefix="?") %}
|
{% set params = next["params"] | default({}) | to_params(prefix="?") %}
|
||||||
{{ button.link(text="Continue with " ~ provider.issuer, href="/upstream/authorize/" ~ provider.id ~ params) }}
|
{{ button.link(text=_("mas.login.continue_with_provider", provider=provider.issuer), href="/upstream/authorize/" ~ provider.id ~ params) }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if not providers and password_disabled %}
|
{% if not providers and password_disabled %}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
No login method available.
|
{{ _("mas.login.no_login_methods") }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
@ -20,8 +20,8 @@ limitations under the License.
|
|||||||
<section class="flex items-center justify-center flex-1">
|
<section class="flex items-center justify-center flex-1">
|
||||||
<div class="w-96 my-2 mx-8">
|
<div class="w-96 my-2 mx-8">
|
||||||
<div class="grid grid-cols-1 gap-6">
|
<div class="grid grid-cols-1 gap-6">
|
||||||
<h1 class="text-xl font-semibold">The authorization request was denied the policy enforced by this service.</h1>
|
<h1 class="text-xl font-semibold">{{ _("mas.policy_violation.heading") }}</h1>
|
||||||
<p>This might be because of the client which authored the request, the currently logged in user, or the request itself.</p>
|
<p>{{ _("mas.policy_violation.description") }}</p>
|
||||||
<div class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 flex items-center">
|
<div class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 flex items-center">
|
||||||
<div class="bg-white rounded w-16 h-16 overflow-hidden mx-auto">
|
<div class="bg-white rounded w-16 h-16 overflow-hidden mx-auto">
|
||||||
{% if client.logo_uri %}
|
{% if client.logo_uri %}
|
||||||
@ -33,14 +33,14 @@ limitations under the License.
|
|||||||
|
|
||||||
<div class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 flex items-center">
|
<div class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 flex items-center">
|
||||||
<div class="text-center flex-1">
|
<div class="text-center flex-1">
|
||||||
Logged as <span class="font-semibold">{{ current_session.user.username }}</span>
|
{{ _("mas.policy_violation.logged_as", username=current_session.user.username) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ logout.button(text="Sign out", csrf_token=csrf_token, post_logout_action=action) }}
|
{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token, post_logout_action=action) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ back_to_client.link(
|
{{ back_to_client.link(
|
||||||
text="Cancel",
|
text=_("action.cancel"),
|
||||||
kind="destructive",
|
kind="destructive",
|
||||||
uri=grant.redirect_uri,
|
uri=grant.redirect_uri,
|
||||||
mode=grant.response_mode,
|
mode=grant.response_mode,
|
||||||
|
@ -26,7 +26,7 @@ limitations under the License.
|
|||||||
</div>
|
</div>
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
{# TODO: errors #}
|
{# TODO: errors #}
|
||||||
{{ field.input(label="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" %}
|
{% if next and next.kind == "continue_authorization_grant" %}
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
{{ back_to_client.link(
|
{{ back_to_client.link(
|
||||||
@ -36,11 +36,11 @@ limitations under the License.
|
|||||||
mode=next.grant.response_mode,
|
mode=next.grant.response_mode,
|
||||||
params=dict(error="access_denied", state=next.grant.state)
|
params=dict(error="access_denied", state=next.grant.state)
|
||||||
) }}
|
) }}
|
||||||
{{ button.button(text="Next") }}
|
{{ button.button(text=_("action.continue")) }}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="grid grid-cols-1 gap-4">
|
<div class="grid grid-cols-1 gap-4">
|
||||||
{{ button.button(text="Next") }}
|
{{ button.button(text=_("action.continue")) }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
@ -54,7 +54,7 @@ limitations under the License.
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="text-center mt-4">
|
<div class="text-center mt-4">
|
||||||
{{ _("mas.register.already_have_account") }}
|
{{ _("mas.register.call_to_login") }}
|
||||||
{% set params = next["params"] | default({}) | to_params(prefix="?") %}
|
{% set params = next["params"] | default({}) | to_params(prefix="?") %}
|
||||||
{{ button.link_text(text=_("mas.register.sign_in_instead"), href="/login" ~ params) }}
|
{{ button.link_text(text=_("mas.register.sign_in_instead"), href="/login" ~ params) }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,9 +22,9 @@ limitations under the License.
|
|||||||
<form method="POST" class="grid grid-cols-1 gap-6">
|
<form method="POST" class="grid grid-cols-1 gap-6">
|
||||||
<h1 class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 text-center font-medium text-lg">
|
<h1 class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 text-center font-medium text-lg">
|
||||||
{% if force_localpart %}
|
{% if force_localpart %}
|
||||||
Create a new account
|
{{ _("mas.upstream_oauth2.register.create_account") }}
|
||||||
{% else %}
|
{% else %}
|
||||||
Choose your username
|
{{ _("mas.upstream_oauth2.register.choose_username") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@ -32,21 +32,21 @@ limitations under the License.
|
|||||||
<input type="hidden" name="action" value="register" />
|
<input type="hidden" name="action" value="register" />
|
||||||
{% if force_localpart %}
|
{% if force_localpart %}
|
||||||
<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">Will use the following username</div>
|
<div class="font-medium"> {{ _("mas.upstream_oauth2.register.forced_localpart") }}</div>
|
||||||
<div class="font-mono">{{ suggested_localpart }}</div>
|
<div class="font-mono">{{ suggested_localpart }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ field.input(label="Username", name="username", autocomplete="username", autocorrect="off", autocapitalize="none") }}
|
{{ field.input(label=_("common.username"), name="username", autocomplete="username", autocorrect="off", autocapitalize="none") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if suggested_email %}
|
{% if suggested_email %}
|
||||||
<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_email %}
|
||||||
Will import the following email address
|
{{ _("mas.upstream_oauth2.register.forced_email") }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<input type="checkbox" name="import_email" id="import_email" checked="checked" />
|
<input type="checkbox" name="import_email" id="import_email" checked="checked" />
|
||||||
<label for="import_email">Import email address</label>
|
<label for="import_email">{{ _("mas.upstream_oauth2.register.suggested_email") }}</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-mono">{{ suggested_email }}</div>
|
<div class="font-mono">{{ suggested_email }}</div>
|
||||||
@ -57,24 +57,24 @@ limitations under the License.
|
|||||||
<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_display_name %}
|
{% if force_display_name %}
|
||||||
Will import the following display name
|
{{ _("mas.upstream_oauth2.register.forced_display_name") }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<input type="checkbox" name="import_display_name" id="import_display_name" checked="checked" />
|
<input type="checkbox" name="import_display_name" id="import_display_name" checked="checked" />
|
||||||
<label for="import_display_name">Import display name</label>
|
<label for="import_display_name">{{ _("mas.upstream_oauth2.register.suggested_display_name") }}</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-mono">{{ suggested_display_name }}</div>
|
<div class="font-mono">{{ suggested_display_name }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{ button.button(text="Create a new account") }}
|
{{ button.button(text=_("action.create_account")) }}
|
||||||
</form>
|
</form>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<hr class="flex-1" />
|
<hr class="flex-1" />
|
||||||
<div class="mx-2">Or</div>
|
<div class="mx-2">{{ _("mas.or_separator") }}</div>
|
||||||
<hr class="flex-1" />
|
<hr class="flex-1" />
|
||||||
</div>
|
</div>
|
||||||
{{ button.link_outline(text="Link to an existing account", href=login_link) }}
|
{{ button.link_outline(text=_("mas.upstream_oauth2.register.link_existing"), href=login_link) }}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -21,10 +21,10 @@ limitations under the License.
|
|||||||
<section class="flex items-center justify-center flex-1">
|
<section class="flex items-center justify-center flex-1">
|
||||||
<div class="grid grid-cols-1 gap-6 w-96 my-2 mx-8">
|
<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">
|
<h1 class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 flex flex-col font-medium text-lg text-center">
|
||||||
This upstream account is already linked to another account.
|
{{ _("mas.upstream_oauth2.link_mismatch.heading") }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div>{{ logout.button(text="Logout", csrf_token=csrf_token) }}</div>
|
<div>{{ logout.button(text=_("action.sign_out"), csrf_token=csrf_token) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -21,17 +21,23 @@ limitations under the License.
|
|||||||
<section class="flex items-center justify-center flex-1">
|
<section class="flex items-center justify-center flex-1">
|
||||||
<div class="grid grid-cols-1 gap-6 w-96 my-2 mx-8">
|
<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">
|
<h1 class="rounded-lg bg-grey-25 dark:bg-grey-450 p-2 flex flex-col font-medium text-lg text-center">
|
||||||
Link to your existing account
|
{{ _("mas.upstream_oauth2.suggest_link.heading") }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<form method="POST" class="flex">
|
<form method="POST" class="flex">
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||||
<input type="hidden" name="action" value="link" />
|
<input type="hidden" name="action" value="link" />
|
||||||
|
|
||||||
{{ button.button(text="Link", class="flex-1") }}
|
{{ button.button(text=_("mas.upstream_oauth2.suggest_link.action"), class="flex-1") }}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div>Or {{ logout.button(text="Logout", csrf_token=csrf_token, post_logout_action=post_logout_action, as_link=true) }}</div>
|
<div class="flex items-center">
|
||||||
|
<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) }}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -2,19 +2,27 @@
|
|||||||
"action": {
|
"action": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"@cancel": {
|
"@cancel": {
|
||||||
"context": "pages/consent.html:63:13-31, pages/register.html:43:17-35"
|
"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"
|
||||||
},
|
},
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"@continue": {
|
"@continue": {
|
||||||
"context": "pages/consent.html:59:30-50, pages/register.html:49:32-52, pages/register.html:53:32-52, pages/sso.html:42:30-50"
|
"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"
|
||||||
|
},
|
||||||
|
"create_account": "Create Account",
|
||||||
|
"@create_account": {
|
||||||
|
"context": "pages/login.html:67:37-63, pages/upstream_oauth2/do_register.html:70:30-56"
|
||||||
},
|
},
|
||||||
"sign_in": "Sign in",
|
"sign_in": "Sign in",
|
||||||
"@sign_in": {
|
"@sign_in": {
|
||||||
"context": "components/navbar.html:32:28-47"
|
"context": "components/navbar.html:30:28-47"
|
||||||
},
|
},
|
||||||
"sign_out": "Sign out",
|
"sign_out": "Sign out",
|
||||||
"@sign_out": {
|
"@sign_out": {
|
||||||
"context": "components/navbar.html:30:30-50, pages/consent.html:72:30-50, pages/sso.html:47:30-50"
|
"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"
|
||||||
|
},
|
||||||
|
"submit": "Submit",
|
||||||
|
"@submit": {
|
||||||
|
"context": "pages/account/emails/verify.html:38:28-46"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
@ -25,7 +33,7 @@
|
|||||||
},
|
},
|
||||||
"name": "matrix-authentication-service",
|
"name": "matrix-authentication-service",
|
||||||
"@name": {
|
"@name": {
|
||||||
"context": "app.html:24:14-27, base.html:30:31-44",
|
"context": "app.html:25:14-27, base.html:32: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>",
|
||||||
@ -37,11 +45,11 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"email_address": "Email address",
|
"email_address": "Email address",
|
||||||
"@email_address": {
|
"@email_address": {
|
||||||
"context": "pages/register.html:36:27-52"
|
"context": "pages/account/emails/add.html:36:27-52, pages/register.html:36:27-52"
|
||||||
},
|
},
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"@password": {
|
"@password": {
|
||||||
"context": "pages/register.html:37:27-47"
|
"context": "pages/login.html:45:29-49, pages/reauth.html:29:29-49, pages/register.html:37:27-47"
|
||||||
},
|
},
|
||||||
"password_confirm": "Confirm password",
|
"password_confirm": "Confirm password",
|
||||||
"@password_confirm": {
|
"@password_confirm": {
|
||||||
@ -49,33 +57,146 @@
|
|||||||
},
|
},
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"@username": {
|
"@username": {
|
||||||
"context": "pages/register.html:35:27-47"
|
"context": "pages/login.html:44:29-49, pages/register.html:35:27-47, pages/upstream_oauth2/do_register.html:39:31-51"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unexpected": "Unexpected error",
|
"unexpected": "Unexpected error",
|
||||||
"@unexpected": {
|
"@unexpected": {
|
||||||
"context": "pages/error.html:22:41-62",
|
"context": "pages/error.html:25:41-62",
|
||||||
"description": "Error message displayed when an unexpected error occurs"
|
"description": "Error message displayed when an unexpected error occurs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mas": {
|
"mas": {
|
||||||
|
"add_email": {
|
||||||
|
"heading": "Add an email address",
|
||||||
|
"@heading": {
|
||||||
|
"context": "pages/account/emails/add.html:24:55-81",
|
||||||
|
"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:64-89"
|
"context": "pages/404.html:25:64-89"
|
||||||
},
|
},
|
||||||
|
"change_password": {
|
||||||
|
"change": "Change password",
|
||||||
|
"@change": {
|
||||||
|
"context": "pages/account/password.html:28:28-59",
|
||||||
|
"description": "Button to change the user's password"
|
||||||
|
},
|
||||||
|
"confirm": "Confirm password",
|
||||||
|
"@confirm": {
|
||||||
|
"context": "pages/account/password.html:27:27-59",
|
||||||
|
"description": "Confirmation field for the new password"
|
||||||
|
},
|
||||||
|
"current": "Current password",
|
||||||
|
"@current": {
|
||||||
|
"context": "pages/account/password.html:25:27-59",
|
||||||
|
"description": "Field for the user's current password"
|
||||||
|
},
|
||||||
|
"heading": "Change my password",
|
||||||
|
"@heading": {
|
||||||
|
"context": "pages/account/password.html:23:57-89",
|
||||||
|
"description": "Heading on the change password page"
|
||||||
|
},
|
||||||
|
"new": "New password",
|
||||||
|
"@new": {
|
||||||
|
"context": "pages/account/password.html:26:27-55",
|
||||||
|
"description": "Field for the user's new password"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"emails": {
|
||||||
|
"greeting": "Hello %(username)s,",
|
||||||
|
"@greeting": {
|
||||||
|
"context": "emails/verification.html:19:3-51, emails/verification.txt:19:3-51",
|
||||||
|
"description": "Greeting at the top of emails sent to the user"
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"body_html": "Your verification code to confirm this email address is: <strong>%(code)s</strong>",
|
||||||
|
"@body_html": {
|
||||||
|
"context": "emails/verification.html:21:3-59",
|
||||||
|
"description": "The body of the email sent to verify an email address (HTML)"
|
||||||
|
},
|
||||||
|
"body_text": "Your verification code to confirm this email address is: %(code)s",
|
||||||
|
"@body_text": {
|
||||||
|
"context": "emails/verification.txt:21:3-59",
|
||||||
|
"description": "The body of the email sent to verify an email address (text)"
|
||||||
|
},
|
||||||
|
"subject": "Your email verification code is: %(code)s",
|
||||||
|
"@subject": {
|
||||||
|
"context": "emails/verification.subject:19:3-57",
|
||||||
|
"description": "The subject line of the email sent to verify an email address"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"denied_policy": "Denied by policy: %(policy)s",
|
||||||
|
"@denied_policy": {
|
||||||
|
"context": "components/field.html:48:17-69"
|
||||||
|
},
|
||||||
|
"field_required": "This field is required",
|
||||||
|
"@field_required": {
|
||||||
|
"context": "components/field.html:44:17-47"
|
||||||
|
},
|
||||||
|
"invalid_credentials": "Invalid credentials",
|
||||||
|
"@invalid_credentials": {
|
||||||
|
"context": "components/errors.html:19:7-42"
|
||||||
|
},
|
||||||
|
"password_mismatch": "Password fields don't match",
|
||||||
|
"@password_mismatch": {
|
||||||
|
"context": "components/errors.html:21:7-40"
|
||||||
|
},
|
||||||
|
"username_taken": "This username is already taken",
|
||||||
|
"@username_taken": {
|
||||||
|
"context": "components/field.html:46:17-47"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"call_to_register": "Don't have an account yet?",
|
||||||
|
"@call_to_register": {
|
||||||
|
"context": "pages/login.html:65:15-46"
|
||||||
|
},
|
||||||
|
"continue_with_provider": "Continue with %(provider)s",
|
||||||
|
"@continue_with_provider": {
|
||||||
|
"context": "pages/login.html:83:30-93",
|
||||||
|
"description": "Button to log in with an upstream provider"
|
||||||
|
},
|
||||||
|
"description": "Please sign in to continue:",
|
||||||
|
"@description": {
|
||||||
|
"context": "pages/login.html:31:18-44"
|
||||||
|
},
|
||||||
|
"headline": "Sign in",
|
||||||
|
"@headline": {
|
||||||
|
"context": "pages/login.html:30:59-82"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"description": "Linking your <span class=\"break-keep text-links\">%(provider)s</span> account",
|
||||||
|
"@description": {
|
||||||
|
"context": "pages/login.html:26:34-96"
|
||||||
|
},
|
||||||
|
"headline": "Sign in to link",
|
||||||
|
"@headline": {
|
||||||
|
"context": "pages/login.html:25:59-87"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"no_login_methods": "No login methods available.",
|
||||||
|
"@no_login_methods": {
|
||||||
|
"context": "pages/login.html:89:13-44"
|
||||||
|
}
|
||||||
|
},
|
||||||
"navbar": {
|
"navbar": {
|
||||||
"my_account": "My account",
|
"my_account": "My account",
|
||||||
"@my_account": {
|
"@my_account": {
|
||||||
"context": "components/navbar.html:29:28-54"
|
"context": "components/navbar.html:27:28-54"
|
||||||
},
|
},
|
||||||
"register": "Create an account",
|
"register": "Create an account",
|
||||||
"@register": {
|
"@register": {
|
||||||
"context": "components/navbar.html:33:36-60"
|
"context": "components/navbar.html:31:36-60"
|
||||||
},
|
},
|
||||||
"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:25:13-81",
|
"context": "components/navbar.html:24:13-81",
|
||||||
"description": "Displayed in the navbar when the user is signed in"
|
"description": "Displayed in the navbar when the user is signed in"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -94,10 +215,32 @@
|
|||||||
"context": "pages/consent.html:71:11-67, pages/sso.html:46:11-67",
|
"context": "pages/consent.html:71:11-67, pages/sso.html:46:11-67",
|
||||||
"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": {
|
||||||
|
"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",
|
||||||
|
"description": "Separator between the login methods"
|
||||||
|
},
|
||||||
|
"policy_violation": {
|
||||||
|
"description": "This might be because of the client which authored the request, the currently logged in user, or the request itself.",
|
||||||
|
"@description": {
|
||||||
|
"context": "pages/policy_violation.html:24:14-51",
|
||||||
|
"description": "Displayed when an authorization request is denied by the policy"
|
||||||
|
},
|
||||||
|
"heading": "The authorization request was denied the policy enforced by this service",
|
||||||
|
"@heading": {
|
||||||
|
"context": "pages/policy_violation.html:23:45-78",
|
||||||
|
"description": "Displayed when an authorization request is denied by the policy"
|
||||||
|
},
|
||||||
|
"logged_as": "Logged as <span class=\"font-semibold\">%(username)s</span>",
|
||||||
|
"@logged_as": {
|
||||||
|
"context": "pages/policy_violation.html:36:15-90"
|
||||||
|
}
|
||||||
|
},
|
||||||
"register": {
|
"register": {
|
||||||
"already_have_account": "Already have an account?",
|
"call_to_login": "Already have an account?",
|
||||||
"@already_have_account": {
|
"@call_to_login": {
|
||||||
"context": "pages/register.html:57:11-49"
|
"context": "pages/register.html:57:11-42",
|
||||||
|
"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:",
|
||||||
@ -149,6 +292,81 @@
|
|||||||
"context": "components/scope.html:21:43-70",
|
"context": "components/scope.html:21:43-70",
|
||||||
"description": "Displayed when the 'openid' scope is requested"
|
"description": "Displayed when the 'openid' scope is requested"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"upstream_oauth2": {
|
||||||
|
"link_mismatch": {
|
||||||
|
"heading": "This upstream account is already linked to another account.",
|
||||||
|
"@heading": {
|
||||||
|
"context": "pages/upstream_oauth2/link_mismatch.html:24:11-57",
|
||||||
|
"description": "Page shown when the user tries to link an upstream account that is already linked to another account"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"choose_username": "Choose your username",
|
||||||
|
"@choose_username": {
|
||||||
|
"context": "pages/upstream_oauth2/do_register.html:27:15-64",
|
||||||
|
"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": {
|
||||||
|
"context": "pages/upstream_oauth2/do_register.html:25:15-63",
|
||||||
|
"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": {
|
||||||
|
"context": "pages/upstream_oauth2/do_register.html:60:19-72",
|
||||||
|
"description": "Tells the user what display name will be imported"
|
||||||
|
},
|
||||||
|
"forced_email": "Will use the following email address",
|
||||||
|
"@forced_email": {
|
||||||
|
"context": "pages/upstream_oauth2/do_register.html:46:19-65",
|
||||||
|
"description": "Tells the user which email address will be imported"
|
||||||
|
},
|
||||||
|
"forced_localpart": "Will use the following username",
|
||||||
|
"@forced_localpart": {
|
||||||
|
"context": "pages/upstream_oauth2/do_register.html:35:41-91",
|
||||||
|
"description": "Tells the user which username will be used"
|
||||||
|
},
|
||||||
|
"link_existing": "Link to an existing account",
|
||||||
|
"@link_existing": {
|
||||||
|
"context": "pages/upstream_oauth2/do_register.html:77:34-81",
|
||||||
|
"description": "Button to link an existing account after an SSO login"
|
||||||
|
},
|
||||||
|
"suggested_display_name": "Import display name",
|
||||||
|
"@suggested_display_name": {
|
||||||
|
"context": "pages/upstream_oauth2/do_register.html:63:50-106",
|
||||||
|
"description": "Option to let the user import their display name after an SSO login"
|
||||||
|
},
|
||||||
|
"suggested_email": "Import email address",
|
||||||
|
"@suggested_email": {
|
||||||
|
"context": "pages/upstream_oauth2/do_register.html:49:45-94",
|
||||||
|
"description": "Option to let the user import their email address after an SSO login"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"suggest_link": {
|
||||||
|
"action": "Link",
|
||||||
|
"@action": {
|
||||||
|
"context": "pages/upstream_oauth2/suggest_link.html:31:30-74"
|
||||||
|
},
|
||||||
|
"heading": "Link to your existing account",
|
||||||
|
"@heading": {
|
||||||
|
"context": "pages/upstream_oauth2/suggest_link.html:24:11-56"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verify_email": {
|
||||||
|
"code": "Code",
|
||||||
|
"@code": {
|
||||||
|
"context": "pages/account/emails/verify.html:37:27-53"
|
||||||
|
},
|
||||||
|
"description": "Please enter the 6-digit code sent to: <span class=\"font-semibold\">%(email)s</span>",
|
||||||
|
"@description": {
|
||||||
|
"context": "pages/account/emails/verify.html:25:14-66"
|
||||||
|
},
|
||||||
|
"headline": "Email verification",
|
||||||
|
"@headline": {
|
||||||
|
"context": "pages/account/emails/verify.html:24:55-85"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user