You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-28 11:02:02 +03:00
Move templates to their own crate
This commit is contained in:
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -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 {
|
||||
|
117
crates/data-model/src/errors.rs
Normal file
117
crates/data-model/src/errors.rs
Normal file
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
24
crates/templates/Cargo.toml
Normal file
24
crates/templates/Cargo.toml
Normal file
@ -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" };
|
Reference in New Issue
Block a user