mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-10-23 15:13:37 +00:00
Move templates to their own crate
This commit is contained in:
33 files changed
+319
-156
No files matched your search
Generated
+20
@@ -1448,6 +1448,7 @@ dependencies = [
|
||||
"indoc",
|
||||
"mas-config",
|
||||
"mas-core",
|
||||
"mas-templates",
|
||||
"opentelemetry",
|
||||
"opentelemetry-http",
|
||||
"opentelemetry-jaeger",
|
||||
@@ -1517,6 +1518,7 @@ dependencies = [
|
||||
"k256",
|
||||
"mas-config",
|
||||
"mas-data-model",
|
||||
"mas-templates",
|
||||
"mime",
|
||||
"oauth2-types",
|
||||
"once_cell",
|
||||
@@ -1552,6 +1554,24 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mas-templates"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"mas-config",
|
||||
"mas-data-model",
|
||||
"oauth2-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tera",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"warp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.0.1"
|
||||
|
||||
@@ -33,6 +33,7 @@ opentelemetry-zipkin = { version = "0.14.0", features = ["reqwest-client", "reqw
|
||||
|
||||
mas-config = { path = "../config" }
|
||||
mas-core = { path = "../core" }
|
||||
mas-templates = { path = "../templates" }
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = "1.0.3"
|
||||
|
||||
@@ -24,8 +24,8 @@ use mas_config::RootConfig;
|
||||
use mas_core::{
|
||||
storage::MIGRATOR,
|
||||
tasks::{self, TaskQueue},
|
||||
templates::Templates,
|
||||
};
|
||||
use mas_templates::Templates;
|
||||
use opentelemetry_http::HeaderExtractor;
|
||||
use tower::{make::Shared, ServiceBuilder};
|
||||
use tower_http::{
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Clap;
|
||||
use mas_core::templates::Templates;
|
||||
use mas_templates::Templates;
|
||||
|
||||
use super::RootCommand;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
// 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.
|
||||
|
||||
use std::{collections::HashMap, fmt::Debug, hash::Hash};
|
||||
|
||||
use serde::{ser::SerializeMap, Serialize};
|
||||
|
||||
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<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()
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@ use chrono::{DateTime, Duration, Utc};
|
||||
use oauth2_types::{pkce::CodeChallengeMethod, scope::Scope};
|
||||
use serde::Serialize;
|
||||
|
||||
pub mod errors;
|
||||
|
||||
pub trait StorageBackendMarker: StorageBackend {}
|
||||
|
||||
pub trait StorageBackend {
|
||||
type UserData: Clone + std::fmt::Debug + PartialEq;
|
||||
type AuthenticationData: Clone + std::fmt::Debug + PartialEq;
|
||||
@@ -26,7 +30,18 @@ pub trait StorageBackend {
|
||||
type AccessTokenData: Clone + std::fmt::Debug + PartialEq;
|
||||
}
|
||||
|
||||
impl StorageBackend for () {
|
||||
type AccessTokenData = ();
|
||||
type AuthenticationData = ();
|
||||
type AuthorizationCodeData = ();
|
||||
type BrowserSessionData = ();
|
||||
type ClientData = ();
|
||||
type SessionData = ();
|
||||
type UserData = ();
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub struct User<T: StorageBackend> {
|
||||
#[serde(skip_serializing)]
|
||||
pub data: T::UserData,
|
||||
@@ -47,14 +62,35 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: StorageBackendMarker> From<User<S>> for User<()> {
|
||||
fn from(u: User<S>) -> Self {
|
||||
User {
|
||||
data: (),
|
||||
username: u.username,
|
||||
sub: u.sub,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub struct Authentication<T: StorageBackend> {
|
||||
#[serde(skip_serializing)]
|
||||
pub data: T::AuthenticationData,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl<S: StorageBackendMarker> From<Authentication<S>> for Authentication<()> {
|
||||
fn from(a: Authentication<S>) -> Self {
|
||||
Authentication {
|
||||
data: (),
|
||||
created_at: a.created_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub struct BrowserSession<T: StorageBackend> {
|
||||
#[serde(skip_serializing)]
|
||||
pub data: T::BrowserSessionData,
|
||||
@@ -63,6 +99,17 @@ pub struct BrowserSession<T: StorageBackend> {
|
||||
pub last_authentication: Option<Authentication<T>>,
|
||||
}
|
||||
|
||||
impl<S: StorageBackendMarker> From<BrowserSession<S>> for BrowserSession<()> {
|
||||
fn from(s: BrowserSession<S>) -> Self {
|
||||
BrowserSession {
|
||||
data: (),
|
||||
user: s.user.into(),
|
||||
created_at: s.created_at,
|
||||
last_authentication: s.last_authentication.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: StorageBackend> BrowserSession<T>
|
||||
where
|
||||
T::BrowserSessionData: Default,
|
||||
@@ -82,13 +129,24 @@ where
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub struct Client<T: StorageBackend> {
|
||||
#[serde(skip_serializing)]
|
||||
pub data: T::ClientData,
|
||||
pub client_id: String,
|
||||
}
|
||||
|
||||
impl<S: StorageBackendMarker> From<Client<S>> for Client<()> {
|
||||
fn from(c: Client<S>) -> Self {
|
||||
Client {
|
||||
data: (),
|
||||
client_id: c.client_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub struct Session<T: StorageBackend> {
|
||||
#[serde(skip_serializing)]
|
||||
pub data: T::SessionData,
|
||||
@@ -97,6 +155,17 @@ pub struct Session<T: StorageBackend> {
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
impl<S: StorageBackendMarker> From<Session<S>> for Session<()> {
|
||||
fn from(s: Session<S>) -> Self {
|
||||
Session {
|
||||
data: (),
|
||||
browser_session: s.browser_session.map(Into::into),
|
||||
client: s.client.into(),
|
||||
scope: s.scope,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Pkce {
|
||||
challenge_method: CodeChallengeMethod,
|
||||
@@ -117,6 +186,7 @@ impl Pkce {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub struct AuthorizationCode<T: StorageBackend> {
|
||||
#[serde(skip_serializing)]
|
||||
pub data: T::AuthorizationCodeData,
|
||||
@@ -124,6 +194,16 @@ pub struct AuthorizationCode<T: StorageBackend> {
|
||||
pub pkce: Pkce,
|
||||
}
|
||||
|
||||
impl<S: StorageBackendMarker> From<AuthorizationCode<S>> for AuthorizationCode<()> {
|
||||
fn from(c: AuthorizationCode<S>) -> Self {
|
||||
AuthorizationCode {
|
||||
data: (),
|
||||
code: c.code,
|
||||
pkce: c.pkce,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccessToken<T: StorageBackend> {
|
||||
pub data: T::AccessTokenData,
|
||||
@@ -132,3 +212,15 @@ pub struct AccessToken<T: StorageBackend> {
|
||||
pub expires_after: Duration,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl<S: StorageBackendMarker> From<AccessToken<S>> for AccessToken<()> {
|
||||
fn from(t: AccessToken<S>) -> Self {
|
||||
AccessToken {
|
||||
data: (),
|
||||
jti: t.jti,
|
||||
token: t.token,
|
||||
expires_after: t.expires_after,
|
||||
created_at: t.created_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "mas-templates"
|
||||
version = "0.1.0"
|
||||
authors = ["Quentin Gliech <quenting@element.io>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
tracing = "0.1.29"
|
||||
tokio = "1.12.0"
|
||||
|
||||
anyhow = "1.0.44"
|
||||
thiserror = "1.0.30"
|
||||
|
||||
tera = "1.12.1"
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
serde_json = "1.0.68"
|
||||
|
||||
url = "2.2.2"
|
||||
warp = "0.3.1" # TODO: we depend on warp only for the Reject trait
|
||||
|
||||
oauth2-types = { path = "../oauth2-types" }
|
||||
mas-data-model = { path = "../data-model" }
|
||||
mas-config = { path = "../config" }
|
||||
@@ -14,47 +14,51 @@
|
||||
|
||||
//! Contexts used in templates
|
||||
|
||||
use mas_data_model::BrowserSession;
|
||||
use mas_data_model::{errors::ErroredForm, BrowserSession, StorageBackend};
|
||||
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 {
|
||||
pub trait TemplateContext: Serialize {
|
||||
/// Attach a user session to the template context
|
||||
fn with_session(self, current_session: BrowserSession<PostgresqlBackend>) -> WithSession<Self>
|
||||
fn with_session<S: StorageBackend>(
|
||||
self,
|
||||
current_session: BrowserSession<S>,
|
||||
) -> WithSession<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
BrowserSession<S>: Into<BrowserSession<()>>,
|
||||
{
|
||||
WithSession {
|
||||
current_session,
|
||||
current_session: current_session.into(),
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach an optional user session to the template context
|
||||
fn maybe_with_session(
|
||||
fn maybe_with_session<S: StorageBackend>(
|
||||
self,
|
||||
current_session: Option<BrowserSession<PostgresqlBackend>>,
|
||||
current_session: Option<BrowserSession<S>>,
|
||||
) -> WithOptionalSession<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
BrowserSession<S>: Into<BrowserSession<()>>,
|
||||
{
|
||||
WithOptionalSession {
|
||||
current_session,
|
||||
current_session: current_session.map(Into::into),
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach a CSRF token to the template context
|
||||
fn with_csrf(self, token: &CsrfToken) -> WithCsrf<Self>
|
||||
fn with_csrf(self, csrf_token: String) -> WithCsrf<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// TODO: make this method use a CsrfToken again
|
||||
WithCsrf {
|
||||
csrf_token: token.form_value(),
|
||||
csrf_token,
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
@@ -95,7 +99,7 @@ impl<T: TemplateContext> TemplateContext for WithCsrf<T> {
|
||||
/// Context with a user session in it
|
||||
#[derive(Serialize)]
|
||||
pub struct WithSession<T> {
|
||||
current_session: BrowserSession<PostgresqlBackend>,
|
||||
current_session: BrowserSession<()>,
|
||||
|
||||
#[serde(flatten)]
|
||||
inner: T,
|
||||
@@ -106,7 +110,7 @@ impl<T: TemplateContext> TemplateContext for WithSession<T> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
BrowserSession::<PostgresqlBackend>::samples()
|
||||
BrowserSession::samples()
|
||||
.into_iter()
|
||||
.flat_map(|session| {
|
||||
T::sample().into_iter().map(move |inner| WithSession {
|
||||
@@ -121,7 +125,7 @@ impl<T: TemplateContext> TemplateContext for WithSession<T> {
|
||||
/// Context with an optional user session in it
|
||||
#[derive(Serialize)]
|
||||
pub struct WithOptionalSession<T> {
|
||||
current_session: Option<BrowserSession<PostgresqlBackend>>,
|
||||
current_session: Option<BrowserSession<()>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
inner: T,
|
||||
@@ -132,7 +136,7 @@ impl<T: TemplateContext> TemplateContext for WithOptionalSession<T> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
BrowserSession::<PostgresqlBackend>::samples()
|
||||
BrowserSession::samples()
|
||||
.into_iter()
|
||||
.map(Some) // Wrap all samples in an Option
|
||||
.chain(std::iter::once(None)) // Add the "None" option
|
||||
@@ -12,7 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
#![deny(clippy::all)]
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
#![allow(clippy::missing_panics_doc)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
//! Templates rendering
|
||||
|
||||
@@ -25,7 +32,6 @@ 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;
|
||||
@@ -181,7 +187,7 @@ pub enum TemplateError {
|
||||
},
|
||||
}
|
||||
|
||||
impl Reject for TemplateError {}
|
||||
impl warp::reject::Reject for TemplateError {}
|
||||
|
||||
register_templates! {
|
||||
extra = { "base.html" };
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user