You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-11-21 23:00:50 +03:00
Move templates to their own crate
This commit is contained in:
@@ -14,6 +14,7 @@ futures-util = "0.3.17"
|
||||
|
||||
# Logging and tracing
|
||||
tracing = "0.1.29"
|
||||
opentelemetry = "0.16.0"
|
||||
|
||||
# Error management
|
||||
thiserror = "1.0.30"
|
||||
@@ -63,12 +64,12 @@ rand = "0.8.4"
|
||||
bincode = "1.3.3"
|
||||
headers = "0.3.4"
|
||||
cookie = "0.15.1"
|
||||
once_cell = "1.8.0"
|
||||
|
||||
oauth2-types = { path = "../oauth2-types", features = ["sqlx_type"] }
|
||||
mas-config = { path = "../config" }
|
||||
mas-data-model = { path = "../data-model" }
|
||||
opentelemetry = "0.16.0"
|
||||
once_cell = "1.8.0"
|
||||
mas-templates = { path = "../templates" }
|
||||
|
||||
[dependencies.jwt-compact]
|
||||
# Waiting on the next release because of the bump of the `rsa` dependency
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{collections::HashMap, fmt::Debug, hash::Hash};
|
||||
|
||||
use serde::{ser::SerializeMap, Serialize};
|
||||
use warp::{reject::Reject, Rejection};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -38,105 +35,3 @@ where
|
||||
self.map_err(|e| warp::reject::custom(WrappedError(e.into())))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HtmlError: Debug + Send + Sync + 'static {
|
||||
fn html_display(&self) -> String;
|
||||
}
|
||||
|
||||
pub trait WrapFormError<FieldType> {
|
||||
fn on_form(self) -> ErroredForm<FieldType>;
|
||||
fn on_field(self, field: FieldType) -> ErroredForm<FieldType>;
|
||||
}
|
||||
|
||||
impl<E, FieldType> WrapFormError<FieldType> for E
|
||||
where
|
||||
E: HtmlError,
|
||||
{
|
||||
fn on_form(self) -> ErroredForm<FieldType> {
|
||||
let mut f = ErroredForm::new();
|
||||
f.form.push(FormError {
|
||||
error: Box::new(self),
|
||||
});
|
||||
f
|
||||
}
|
||||
|
||||
fn on_field(self, field: FieldType) -> ErroredForm<FieldType> {
|
||||
let mut f = ErroredForm::new();
|
||||
f.fields.push(FieldError {
|
||||
field,
|
||||
error: Box::new(self),
|
||||
});
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FormError {
|
||||
error: Box<dyn HtmlError>,
|
||||
}
|
||||
|
||||
impl Serialize for FormError {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.error.html_display())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FieldError<FieldType> {
|
||||
field: FieldType,
|
||||
error: Box<dyn HtmlError>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ErroredForm<FieldType> {
|
||||
form: Vec<FormError>,
|
||||
fields: Vec<FieldError<FieldType>>,
|
||||
}
|
||||
|
||||
impl<T> Default for ErroredForm<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
form: Vec::new(),
|
||||
fields: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ErroredForm<T> {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
form: Vec::new(),
|
||||
fields: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Reject for ErroredForm<T> where T: Debug + Send + Sync + 'static {}
|
||||
|
||||
impl<FieldType: Copy + Serialize + Hash + Eq> Serialize for ErroredForm<FieldType> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut map = serializer.serialize_map(Some(2))?;
|
||||
let has_errors = !self.form.is_empty() || !self.fields.is_empty();
|
||||
map.serialize_entry("has_errors", &has_errors)?;
|
||||
map.serialize_entry("form_errors", &self.form)?;
|
||||
|
||||
let fields: HashMap<FieldType, Vec<String>> =
|
||||
self.fields.iter().fold(HashMap::new(), |mut map, err| {
|
||||
map.entry(err.field)
|
||||
.or_default()
|
||||
.push(err.error.html_display());
|
||||
map
|
||||
});
|
||||
|
||||
map.serialize_entry("fields_errors", &fields)?;
|
||||
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,13 @@ static PROPAGATOR_HEADERS: OnceCell<Vec<String>> = OnceCell::new();
|
||||
/// Notify the CORS filter what opentelemetry propagators are being used. This
|
||||
/// helps whitelisting headers in CORS requests.
|
||||
pub fn set_propagator(propagator: &dyn opentelemetry::propagation::TextMapPropagator) {
|
||||
let headers = propagator.fields().map(ToString::to_string).collect();
|
||||
tracing::debug!(
|
||||
?headers,
|
||||
"Headers allowed in CORS requests for trace propagators set"
|
||||
);
|
||||
PROPAGATOR_HEADERS
|
||||
.set(propagator.fields().map(ToString::to_string).collect())
|
||||
.set(headers)
|
||||
.expect(concat!(module_path!(), "::set_propagator was called twice"));
|
||||
}
|
||||
|
||||
|
||||
@@ -28,13 +28,11 @@ pub mod session;
|
||||
|
||||
use std::convert::Infallible;
|
||||
|
||||
use mas_templates::Templates;
|
||||
use warp::{Filter, Rejection};
|
||||
|
||||
pub use self::csrf::CsrfToken;
|
||||
use crate::{
|
||||
config::{KeySet, OAuth2Config},
|
||||
templates::Templates,
|
||||
};
|
||||
use crate::config::{KeySet, OAuth2Config};
|
||||
|
||||
/// Get the [`Templates`]
|
||||
#[must_use]
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
|
||||
#![allow(clippy::unused_async)] // Some warp filters need that
|
||||
|
||||
use mas_templates::Templates;
|
||||
use sqlx::PgPool;
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
|
||||
use crate::{config::RootConfig, templates::Templates};
|
||||
use crate::config::RootConfig;
|
||||
|
||||
mod health;
|
||||
mod oauth2;
|
||||
|
||||
@@ -25,6 +25,7 @@ use hyper::{
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use mas_data_model::BrowserSession;
|
||||
use mas_templates::{FormPostContext, Templates};
|
||||
use oauth2_types::{
|
||||
errors::{ErrorResponse, InvalidRequest, OAuth2Error},
|
||||
pkce,
|
||||
@@ -62,7 +63,6 @@ use crate::{
|
||||
},
|
||||
PostgresqlBackend,
|
||||
},
|
||||
templates::{FormPostContext, Templates},
|
||||
tokens::{AccessToken, RefreshToken},
|
||||
};
|
||||
|
||||
|
||||
@@ -12,13 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use mas_templates::Templates;
|
||||
use sqlx::PgPool;
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
|
||||
use crate::{
|
||||
config::{CookiesConfig, OAuth2Config},
|
||||
templates::Templates,
|
||||
};
|
||||
use crate::config::{CookiesConfig, OAuth2Config};
|
||||
|
||||
mod authorization;
|
||||
mod discovery;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
use mas_data_model::BrowserSession;
|
||||
use mas_templates::{IndexContext, TemplateContext, Templates};
|
||||
use sqlx::PgPool;
|
||||
use url::Url;
|
||||
use warp::{reply::html, Filter, Rejection, Reply};
|
||||
@@ -26,7 +27,6 @@ use crate::{
|
||||
with_templates, CsrfToken,
|
||||
},
|
||||
storage::PostgresqlBackend,
|
||||
templates::{IndexContext, TemplateContext, Templates},
|
||||
};
|
||||
|
||||
pub(super) fn filter(
|
||||
@@ -56,7 +56,7 @@ async fn get(
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let ctx = IndexContext::new(discovery_url)
|
||||
.maybe_with_session(maybe_session)
|
||||
.with_csrf(&csrf_token);
|
||||
.with_csrf(csrf_token.form_value());
|
||||
|
||||
let content = templates.render_index(&ctx)?;
|
||||
let reply = html(content);
|
||||
|
||||
@@ -15,14 +15,15 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
||||
use mas_data_model::BrowserSession;
|
||||
use mas_data_model::{errors::WrapFormError, BrowserSession};
|
||||
use mas_templates::{LoginContext, LoginFormField, TemplateContext, Templates};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||
use warp::{reply::html, Filter, Rejection, Reply};
|
||||
|
||||
use crate::{
|
||||
config::{CookiesConfig, CsrfConfig},
|
||||
errors::{WrapError, WrapFormError},
|
||||
errors::WrapError,
|
||||
filters::{
|
||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||
csrf::{protected_form, updated_csrf_token},
|
||||
@@ -31,7 +32,6 @@ use crate::{
|
||||
with_templates, CsrfToken,
|
||||
},
|
||||
storage::{login, PostgresqlBackend},
|
||||
templates::{LoginContext, LoginFormField, TemplateContext, Templates},
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -114,7 +114,7 @@ async fn get(
|
||||
if maybe_session.is_some() {
|
||||
Ok(Box::new(query.redirect()?))
|
||||
} else {
|
||||
let ctx = LoginContext::default().with_csrf(&csrf_token);
|
||||
let ctx = LoginContext::default().with_csrf(csrf_token.form_value());
|
||||
let content = templates.render_login(&ctx)?;
|
||||
let reply = html(content);
|
||||
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
||||
@@ -145,7 +145,8 @@ async fn post(
|
||||
LoginError::Authentication { .. } => e.on_field(LoginFormField::Password),
|
||||
LoginError::Other(_) => e.on_form(),
|
||||
};
|
||||
let ctx = LoginContext::with_form_error(errored_form).with_csrf(&csrf_token);
|
||||
let ctx =
|
||||
LoginContext::with_form_error(errored_form).with_csrf(csrf_token.form_value());
|
||||
let content = templates.render_login(&ctx)?;
|
||||
let reply = html(content);
|
||||
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
||||
|
||||
@@ -12,13 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use mas_templates::Templates;
|
||||
use sqlx::PgPool;
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
|
||||
use crate::{
|
||||
config::{CookiesConfig, CsrfConfig, OAuth2Config},
|
||||
templates::Templates,
|
||||
};
|
||||
use crate::config::{CookiesConfig, CsrfConfig, OAuth2Config};
|
||||
|
||||
mod index;
|
||||
mod login;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
use mas_data_model::BrowserSession;
|
||||
use mas_templates::{EmptyContext, TemplateContext, Templates};
|
||||
use serde::Deserialize;
|
||||
use sqlx::{PgPool, Postgres, Transaction};
|
||||
use warp::{hyper::Uri, reply::html, Filter, Rejection, Reply};
|
||||
@@ -28,7 +29,6 @@ use crate::{
|
||||
with_templates, CsrfToken,
|
||||
},
|
||||
storage::{user::authenticate_session, PostgresqlBackend},
|
||||
templates::{EmptyContext, TemplateContext, Templates},
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -64,7 +64,9 @@ async fn get(
|
||||
csrf_token: CsrfToken,
|
||||
session: BrowserSession<PostgresqlBackend>,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let ctx = EmptyContext.with_session(session).with_csrf(&csrf_token);
|
||||
let ctx = EmptyContext
|
||||
.with_session(session)
|
||||
.with_csrf(csrf_token.form_value());
|
||||
|
||||
let content = templates.render_reauth(&ctx)?;
|
||||
let reply = html(content);
|
||||
|
||||
@@ -17,6 +17,7 @@ use std::convert::TryFrom;
|
||||
use argon2::Argon2;
|
||||
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
||||
use mas_data_model::BrowserSession;
|
||||
use mas_templates::{EmptyContext, TemplateContext, Templates};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||
use warp::{reply::html, Filter, Rejection, Reply};
|
||||
@@ -32,7 +33,6 @@ use crate::{
|
||||
with_templates, CsrfToken,
|
||||
},
|
||||
storage::{register_user, user::start_session, PostgresqlBackend},
|
||||
templates::{EmptyContext, TemplateContext, Templates},
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -116,7 +116,7 @@ async fn get(
|
||||
if maybe_session.is_some() {
|
||||
Ok(Box::new(query.redirect()?))
|
||||
} else {
|
||||
let ctx = EmptyContext.with_csrf(&csrf_token);
|
||||
let ctx = EmptyContext.with_csrf(csrf_token.form_value());
|
||||
let content = templates.render_register(&ctx)?;
|
||||
let reply = html(content);
|
||||
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
||||
|
||||
@@ -29,5 +29,4 @@ pub mod handlers;
|
||||
pub mod reply;
|
||||
pub mod storage;
|
||||
pub mod tasks;
|
||||
pub mod templates;
|
||||
pub mod tokens;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
#![allow(clippy::used_underscore_binding)] // This is needed by sqlx macros
|
||||
|
||||
use mas_data_model::StorageBackend;
|
||||
use mas_data_model::{StorageBackend, StorageBackendMarker};
|
||||
use serde::Serialize;
|
||||
use sqlx::migrate::Migrator;
|
||||
use thiserror::Error;
|
||||
@@ -38,6 +38,8 @@ impl StorageBackend for PostgresqlBackend {
|
||||
type UserData = i64;
|
||||
}
|
||||
|
||||
impl StorageBackendMarker for PostgresqlBackend {}
|
||||
|
||||
pub mod oauth2;
|
||||
pub mod user;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::{borrow::BorrowMut, convert::TryInto};
|
||||
use anyhow::Context;
|
||||
use argon2::Argon2;
|
||||
use chrono::{DateTime, Utc};
|
||||
use mas_data_model::{Authentication, BrowserSession, User};
|
||||
use mas_data_model::{errors::HtmlError, Authentication, BrowserSession, User};
|
||||
use password_hash::{PasswordHash, PasswordHasher, SaltString};
|
||||
use rand::rngs::OsRng;
|
||||
use sqlx::{Acquire, Executor, FromRow, Postgres, Transaction};
|
||||
@@ -27,7 +27,6 @@ use tracing::{info_span, Instrument};
|
||||
use warp::reject::Reject;
|
||||
|
||||
use super::{DatabaseInconsistencyError, PostgresqlBackend};
|
||||
use crate::errors::HtmlError;
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
struct UserLookup {
|
||||
|
||||
@@ -1,332 +0,0 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
//! Contexts used in templates
|
||||
|
||||
use mas_data_model::BrowserSession;
|
||||
use oauth2_types::errors::OAuth2Error;
|
||||
use serde::{ser::SerializeStruct, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::{errors::ErroredForm, filters::CsrfToken, storage::PostgresqlBackend};
|
||||
|
||||
/// Helper trait to construct context wrappers
|
||||
pub trait TemplateContext {
|
||||
/// Attach a user session to the template context
|
||||
fn with_session(self, current_session: BrowserSession<PostgresqlBackend>) -> WithSession<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
WithSession {
|
||||
current_session,
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach an optional user session to the template context
|
||||
fn maybe_with_session(
|
||||
self,
|
||||
current_session: Option<BrowserSession<PostgresqlBackend>>,
|
||||
) -> WithOptionalSession<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
WithOptionalSession {
|
||||
current_session,
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach a CSRF token to the template context
|
||||
fn with_csrf(self, token: &CsrfToken) -> WithCsrf<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
WithCsrf {
|
||||
csrf_token: token.form_value(),
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate sample values for this context type
|
||||
///
|
||||
/// This is then used to check for template validity in unit tests and in
|
||||
/// the CLI (`cargo run -- templates check`)
|
||||
fn sample() -> Vec<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// Context with a CSRF token in it
|
||||
#[derive(Serialize)]
|
||||
pub struct WithCsrf<T> {
|
||||
csrf_token: String,
|
||||
|
||||
#[serde(flatten)]
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: TemplateContext> TemplateContext for WithCsrf<T> {
|
||||
fn sample() -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
T::sample()
|
||||
.into_iter()
|
||||
.map(|inner| WithCsrf {
|
||||
csrf_token: "fake_csrf_token".into(),
|
||||
inner,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Context with a user session in it
|
||||
#[derive(Serialize)]
|
||||
pub struct WithSession<T> {
|
||||
current_session: BrowserSession<PostgresqlBackend>,
|
||||
|
||||
#[serde(flatten)]
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: TemplateContext> TemplateContext for WithSession<T> {
|
||||
fn sample() -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
BrowserSession::<PostgresqlBackend>::samples()
|
||||
.into_iter()
|
||||
.flat_map(|session| {
|
||||
T::sample().into_iter().map(move |inner| WithSession {
|
||||
current_session: session.clone(),
|
||||
inner,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Context with an optional user session in it
|
||||
#[derive(Serialize)]
|
||||
pub struct WithOptionalSession<T> {
|
||||
current_session: Option<BrowserSession<PostgresqlBackend>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: TemplateContext> TemplateContext for WithOptionalSession<T> {
|
||||
fn sample() -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
BrowserSession::<PostgresqlBackend>::samples()
|
||||
.into_iter()
|
||||
.map(Some) // Wrap all samples in an Option
|
||||
.chain(std::iter::once(None)) // Add the "None" option
|
||||
.flat_map(|session| {
|
||||
T::sample()
|
||||
.into_iter()
|
||||
.map(move |inner| WithOptionalSession {
|
||||
current_session: session.clone(),
|
||||
inner,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// An empty context used for composition
|
||||
pub struct EmptyContext;
|
||||
|
||||
impl Serialize for EmptyContext {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut s = serializer.serialize_struct("EmptyContext", 0)?;
|
||||
// FIXME: for some reason, serde seems to not like struct flattening with empty
|
||||
// stuff
|
||||
s.serialize_field("__UNUSED", &())?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl TemplateContext for EmptyContext {
|
||||
fn sample() -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
vec![EmptyContext]
|
||||
}
|
||||
}
|
||||
|
||||
/// Context used by the `index.html` template
|
||||
#[derive(Serialize)]
|
||||
pub struct IndexContext {
|
||||
discovery_url: Url,
|
||||
}
|
||||
|
||||
impl IndexContext {
|
||||
#[must_use]
|
||||
pub fn new(discovery_url: Url) -> Self {
|
||||
Self { discovery_url }
|
||||
}
|
||||
}
|
||||
|
||||
impl TemplateContext for IndexContext {
|
||||
fn sample() -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
vec![Self {
|
||||
discovery_url: "https://example.com/.well-known/openid-configuration"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum LoginFormField {
|
||||
Username,
|
||||
Password,
|
||||
}
|
||||
|
||||
/// Context used by the `login.html` template
|
||||
#[derive(Serialize)]
|
||||
pub struct LoginContext {
|
||||
form: ErroredForm<LoginFormField>,
|
||||
}
|
||||
|
||||
impl TemplateContext for LoginContext {
|
||||
fn sample() -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// TODO: samples with errors
|
||||
vec![LoginContext {
|
||||
form: ErroredForm::default(),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
impl LoginContext {
|
||||
#[must_use]
|
||||
pub fn with_form_error(form: ErroredForm<LoginFormField>) -> Self {
|
||||
Self { form }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoginContext {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
form: ErroredForm::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Context used by the `form_post.html` template
|
||||
#[derive(Serialize)]
|
||||
pub struct FormPostContext<T> {
|
||||
redirect_uri: Url,
|
||||
params: T,
|
||||
}
|
||||
|
||||
impl<T: TemplateContext> TemplateContext for FormPostContext<T> {
|
||||
fn sample() -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let sample_params = T::sample();
|
||||
sample_params
|
||||
.into_iter()
|
||||
.map(|params| FormPostContext {
|
||||
redirect_uri: "https://example.com/callback".parse().unwrap(),
|
||||
params,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FormPostContext<T> {
|
||||
pub fn new(redirect_uri: Url, params: T) -> Self {
|
||||
Self {
|
||||
redirect_uri,
|
||||
params,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Context used by the `error.html` template
|
||||
#[derive(Default, Serialize)]
|
||||
pub struct ErrorContext {
|
||||
code: Option<&'static str>,
|
||||
description: Option<String>,
|
||||
details: Option<String>,
|
||||
}
|
||||
|
||||
impl TemplateContext for ErrorContext {
|
||||
fn sample() -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
vec![
|
||||
Self::new()
|
||||
.with_code("sample_error")
|
||||
.with_description("A fancy description".into())
|
||||
.with_details("Something happened".into()),
|
||||
Self::new().with_code("another_error"),
|
||||
Self::new(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorContext {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_code(mut self, code: &'static str) -> Self {
|
||||
self.code = Some(code);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_description(mut self, description: String) -> Self {
|
||||
self.description = Some(description);
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[must_use]
|
||||
pub fn with_details(mut self, details: String) -> Self {
|
||||
self.details = Some(details);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn OAuth2Error>> for ErrorContext {
|
||||
fn from(err: Box<dyn OAuth2Error>) -> Self {
|
||||
let mut ctx = ErrorContext::new().with_code(err.error());
|
||||
if let Some(desc) = err.description() {
|
||||
ctx = ctx.with_description(desc);
|
||||
}
|
||||
ctx
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
/// Count the number of tokens. Used to have a fixed-sized array for the
|
||||
/// templates list.
|
||||
macro_rules! count {
|
||||
() => (0_usize);
|
||||
( $x:tt $($xs:tt)* ) => (1_usize + count!($($xs)*));
|
||||
}
|
||||
|
||||
/// Macro that helps generating helper function that renders a specific template
|
||||
/// with a strongly-typed context. It also register the template in a static
|
||||
/// array to help detecting missing templates at startup time.
|
||||
///
|
||||
/// The syntax looks almost like a function to confuse syntax highlighter as
|
||||
/// little as possible.
|
||||
#[macro_export]
|
||||
macro_rules! register_templates {
|
||||
{
|
||||
$(
|
||||
extra = { $( $extra_template:expr ),* };
|
||||
)?
|
||||
|
||||
$(
|
||||
// Match any attribute on the function, such as #[doc], #[allow(dead_code)], etc.
|
||||
$( #[ $attr:meta ] )*
|
||||
// The function name
|
||||
pub fn $name:ident
|
||||
// Optional list of generics. Taken from
|
||||
// https://newbedev.com/rust-macro-accepting-type-with-generic-parameters
|
||||
$(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)?
|
||||
// Type of context taken by the template
|
||||
( $param:ty )
|
||||
{
|
||||
// The name of the template file
|
||||
$template:expr
|
||||
}
|
||||
)*
|
||||
} => {
|
||||
/// List of registered templates
|
||||
static TEMPLATES: [(&'static str, &'static str); count!( $( $template )* )] = [
|
||||
$( ($template, include_str!(concat!("res/", $template))) ),*
|
||||
];
|
||||
|
||||
/// List of extra templates used by other templates
|
||||
static EXTRA_TEMPLATES: [(&'static str, &'static str); count!( $( $( $extra_template )* )? )] = [
|
||||
$( $( ($extra_template, include_str!(concat!("res/", $extra_template))) ),* )?
|
||||
];
|
||||
|
||||
impl Templates {
|
||||
$(
|
||||
$(#[$attr])?
|
||||
pub fn $name
|
||||
$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?
|
||||
(&self, context: &$param)
|
||||
-> Result<String, TemplateError> {
|
||||
let ctx = Context::from_serialize(context)
|
||||
.map_err(|source| TemplateError::Context { template: $template, source })?;
|
||||
|
||||
self.0.render($template, &ctx)
|
||||
.map_err(|source| TemplateError::Render { template: $template, source })
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
/// Helps rendering each template with sample data
|
||||
pub mod check {
|
||||
use super::*;
|
||||
|
||||
$(
|
||||
#[doc = concat!("Render the `", $template, "` template with sample contexts")]
|
||||
pub fn $name
|
||||
$(< $( $lt $( : $clt $(+ $dlt )* + TemplateContext )? ),+ >)?
|
||||
(templates: &Templates)
|
||||
-> anyhow::Result<()> {
|
||||
let samples: Vec< $param > = TemplateContext::sample();
|
||||
|
||||
let name = $template;
|
||||
for sample in samples {
|
||||
let context = serde_json::to_value(&sample)?;
|
||||
::tracing::info!(name, %context, "Rendering template");
|
||||
templates. $name (&sample)
|
||||
.with_context(|| format!("Failed to render template {:?} with context {}", name, context))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Templates rendering
|
||||
|
||||
use std::{collections::HashSet, io::Cursor, path::Path, string::ToString, sync::Arc};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use mas_config::TemplatesConfig;
|
||||
use serde::Serialize;
|
||||
use tera::{Context, Error as TeraError, Tera};
|
||||
use thiserror::Error;
|
||||
use tokio::{fs::OpenOptions, io::AsyncWriteExt};
|
||||
use tracing::{debug, info, warn};
|
||||
use warp::reject::Reject;
|
||||
|
||||
#[allow(missing_docs)] // TODO
|
||||
mod context;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
pub use self::context::{
|
||||
EmptyContext, ErrorContext, FormPostContext, IndexContext, LoginContext, LoginFormField,
|
||||
TemplateContext, WithCsrf, WithOptionalSession, WithSession,
|
||||
};
|
||||
|
||||
/// Wrapper around [`tera::Tera`] helping rendering the various templates
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Templates(Arc<Tera>);
|
||||
|
||||
/// There was an issue while loading the templates
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TemplateLoadingError {
|
||||
/// Some templates failed to compile
|
||||
#[error("could not load and compile some templates")]
|
||||
Compile(#[from] TeraError),
|
||||
|
||||
/// There are essential templates missing
|
||||
#[error("missing templates {missing:?}")]
|
||||
MissingTemplates {
|
||||
/// List of missing templates
|
||||
missing: HashSet<String>,
|
||||
/// List of templates that were loaded
|
||||
loaded: HashSet<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Templates {
|
||||
/// Load the templates from [the config][`TemplatesConfig`]
|
||||
pub fn load_from_config(config: &TemplatesConfig) -> Result<Self, TemplateLoadingError> {
|
||||
Self::load(config.path.as_deref(), config.builtin)
|
||||
}
|
||||
|
||||
/// Load the templates and check all needed templates are properly loaded
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - An optional path to where templates should be loaded
|
||||
/// * `builtin` - Set to `true` to load the builtin templates as well
|
||||
pub fn load(path: Option<&str>, builtin: bool) -> Result<Self, TemplateLoadingError> {
|
||||
let tera = {
|
||||
let mut tera = Tera::default();
|
||||
|
||||
if builtin {
|
||||
info!("Loading builtin templates");
|
||||
|
||||
for (name, source) in EXTRA_TEMPLATES {
|
||||
tera.add_raw_template(name, source)?;
|
||||
}
|
||||
|
||||
for (name, source) in TEMPLATES {
|
||||
tera.add_raw_template(name, source)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = path {
|
||||
let path = format!("{}/**/*.{{html,txt}}", path);
|
||||
info!(%path, "Loading templates from filesystem");
|
||||
tera.extend(&Tera::parse(&path)?)?;
|
||||
}
|
||||
|
||||
tera.build_inheritance_chains()?;
|
||||
tera.check_macro_files()?;
|
||||
|
||||
tera
|
||||
};
|
||||
|
||||
let loaded: HashSet<_> = tera.get_template_names().collect();
|
||||
let needed: HashSet<_> = std::array::IntoIter::new(TEMPLATES)
|
||||
.map(|(name, _)| name)
|
||||
.collect();
|
||||
debug!(?loaded, ?needed, "Templates loaded");
|
||||
let missing: HashSet<_> = needed.difference(&loaded).collect();
|
||||
|
||||
if missing.is_empty() {
|
||||
Ok(Self(Arc::new(tera)))
|
||||
} else {
|
||||
let missing = missing.into_iter().map(ToString::to_string).collect();
|
||||
let loaded = loaded.into_iter().map(ToString::to_string).collect();
|
||||
Err(TemplateLoadingError::MissingTemplates { missing, loaded })
|
||||
}
|
||||
}
|
||||
|
||||
/// Save the builtin templates to a folder
|
||||
pub async fn save(path: &Path, overwrite: bool) -> anyhow::Result<()> {
|
||||
tokio::fs::create_dir_all(&path)
|
||||
.await
|
||||
.context("could not create destination folder")?;
|
||||
|
||||
let templates = std::array::IntoIter::new(TEMPLATES).chain(EXTRA_TEMPLATES);
|
||||
|
||||
let mut options = OpenOptions::new();
|
||||
if overwrite {
|
||||
options.create(true).truncate(true).write(true);
|
||||
} else {
|
||||
// With the `create_new` flag, `open` fails with an `AlreadyExists` error to
|
||||
// avoid overwriting
|
||||
options.create_new(true).write(true);
|
||||
};
|
||||
|
||||
for (name, source) in templates {
|
||||
let path = path.join(name);
|
||||
|
||||
let mut file = match options.open(&path).await {
|
||||
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
|
||||
// Not overwriting a template is a soft error
|
||||
warn!(?path, "Not overwriting template");
|
||||
continue;
|
||||
}
|
||||
x => x.context(format!("could not open file {:?}", path))?,
|
||||
};
|
||||
|
||||
let mut buffer = Cursor::new(source);
|
||||
file.write_all_buf(&mut buffer)
|
||||
.await
|
||||
.context(format!("could not write file {:?}", path))?;
|
||||
info!(?path, "Wrote template");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Failed to render a template
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TemplateError {
|
||||
/// Failed to prepare the context used by this template
|
||||
#[error("could not prepare context for template {template:?}")]
|
||||
Context {
|
||||
/// The name of the template being rendered
|
||||
template: &'static str,
|
||||
|
||||
/// The underlying error
|
||||
#[source]
|
||||
source: TeraError,
|
||||
},
|
||||
|
||||
/// Failed to render the template
|
||||
#[error("could not render template {template:?}")]
|
||||
Render {
|
||||
/// The name of the template being rendered
|
||||
template: &'static str,
|
||||
|
||||
/// The underlying error
|
||||
#[source]
|
||||
source: TeraError,
|
||||
},
|
||||
}
|
||||
|
||||
impl Reject for TemplateError {}
|
||||
|
||||
register_templates! {
|
||||
extra = { "base.html" };
|
||||
|
||||
/// Render the login page
|
||||
pub fn render_login(WithCsrf<LoginContext>) { "login.html" }
|
||||
|
||||
/// Render the registration page
|
||||
pub fn render_register(WithCsrf<EmptyContext>) { "register.html" }
|
||||
|
||||
/// Render the home page
|
||||
pub fn render_index(WithCsrf<WithOptionalSession<IndexContext>>) { "index.html" }
|
||||
|
||||
/// Render the re-authentication form
|
||||
pub fn render_reauth(WithCsrf<WithSession<EmptyContext>>) { "reauth.html" }
|
||||
|
||||
/// Render the form used by the form_post response mode
|
||||
pub fn render_form_post<T: Serialize>(FormPostContext<T>) { "form_post.html" }
|
||||
|
||||
/// Render the HTML error page
|
||||
pub fn render_error(ErrorContext) { "error.html" }
|
||||
}
|
||||
|
||||
impl Templates {
|
||||
/// Render all templates with the generated samples to check if they render
|
||||
/// properly
|
||||
pub fn check_render(&self) -> anyhow::Result<()> {
|
||||
check::render_login(self)?;
|
||||
check::render_register(self)?;
|
||||
check::render_index(self)?;
|
||||
check::render_reauth(self)?;
|
||||
check::render_form_post::<EmptyContext>(self)?;
|
||||
check::render_error(self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_builtin_templates() {
|
||||
let templates = Templates::load(None, true).unwrap();
|
||||
templates.check_render().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
{#
|
||||
Copyright 2021 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.
|
||||
#}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{% block title %}matrix-authentication-service{% endblock title %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
matrix-authentication-service
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
{% if current_session %}
|
||||
<div class="navbar-item">
|
||||
Howdy {{ current_session.user.username }}!
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
<form method="POST" action="/logout">
|
||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||
<button class="button is-light" action="submit">
|
||||
Log out
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="navbar-item">
|
||||
<a class="button is-light" href="/login">
|
||||
Log in
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-item">
|
||||
<a class="button is-light" href="/register">
|
||||
Register
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{% block content %}{% endblock content %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,39 +0,0 @@
|
||||
{#
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
#}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="hero is-danger">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
{% if code %}
|
||||
<p class="title">
|
||||
{{ code }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if description %}
|
||||
<p class="subtitle">
|
||||
{{ description }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if details %}
|
||||
<pre><code>{{ details }}</code></pre>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -1,27 +0,0 @@
|
||||
{#
|
||||
Copyright 2021 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.
|
||||
#}
|
||||
|
||||
{% if code %}
|
||||
{{- code }}
|
||||
{% endif %}
|
||||
|
||||
{%- if description %}
|
||||
{{ description }}
|
||||
{% endif %}
|
||||
|
||||
{%- if details %}
|
||||
{{ details }}
|
||||
{% endif %}
|
||||
@@ -1,31 +0,0 @@
|
||||
{#
|
||||
Copyright 2021 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.
|
||||
#}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Redirecting to client</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body onload="javascript:document.forms[0].submit()">
|
||||
<form method="post" action="{{ redirect_uri }}">
|
||||
{% for key, value in params %}
|
||||
<input type="hidden" name="{{ key }}" value="{{ value }}" />
|
||||
{% endfor %}
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,28 +0,0 @@
|
||||
{#
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
#}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container content">
|
||||
<h1>Matrix Authentication Service</h1>
|
||||
<p>
|
||||
OpenID Connect discovery document: <a href="{{ discovery_url }}">{{ discovery_url }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock content %}
|
||||
@@ -1,70 +0,0 @@
|
||||
{#
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
#}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container is-max-desktop">
|
||||
<div class="columns">
|
||||
<div class="column is-half is-offset-one-quarter">
|
||||
{% if form.has_errors %}
|
||||
<article class="message is-danger">
|
||||
<div class="message-body">
|
||||
{% for message in form.form_errors %}
|
||||
<p>{{ message | safe }}</p>
|
||||
{% else %}
|
||||
<p>Login failed, check the fields below for more details.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</article>
|
||||
{% endif %}
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||
<div class="field">
|
||||
<label class="label" for="login-username">Username</label>
|
||||
<div class="control">
|
||||
<input class="input{% if 'username' in form.fields_errors %} is-danger{% endif %}" name="username" id="login-username" type="text">
|
||||
</div>
|
||||
{% if 'username' in form.fields_errors %}
|
||||
{% for message in form.fields_errors.username %}
|
||||
<p class="help is-danger">{{ message | safe }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="login-password">Password</label>
|
||||
<div class="control">
|
||||
<input class="input{% if 'password' in form.fields_errors %} is-danger{% endif %}" name="password" id="login-password" type="password">
|
||||
</div>
|
||||
{% if 'password' in form.fields_errors %}
|
||||
{% for message in form.fields_errors.password %}
|
||||
<p class="help is-danger">{{ message | safe }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-link">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock content %}
|
||||
@@ -1,45 +0,0 @@
|
||||
{#
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
#}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container is-max-desktop">
|
||||
<div class="columns">
|
||||
<div class="column is-one-third">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||
<div class="field">
|
||||
<label class="label" for="login-password">Password</label>
|
||||
<div class="control">
|
||||
<input class="input" name="password" id="login-password" type="password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-link">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="column is-two-third">
|
||||
<pre><code>{{ current_session | json_encode(pretty=True) | safe }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
{#
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
#}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container is-max-desktop">
|
||||
<div class="columns">
|
||||
<div class="column is-one-third">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||
<div class="field">
|
||||
<label class="label" for="register-username">Username</label>
|
||||
<div class="control">
|
||||
<input class="input" name="username" id="register-username" type="text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="register-password">Password</label>
|
||||
<div class="control">
|
||||
<input class="input" name="password" id="register-password" type="password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="register-password">Confirm password</label>
|
||||
<div class="control">
|
||||
<input class="input" name="password_confirm" id="register-password-confirm" type="password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-link">Register</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock content %}
|
||||
Reference in New Issue
Block a user