1
0
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:
Quentin Gliech
2021-10-18 17:40:25 +02:00
parent cf8793da27
commit 026bc47c27
33 changed files with 319 additions and 156 deletions

20
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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::{

View File

@ -15,7 +15,7 @@
use std::path::PathBuf;
use clap::Clap;
use mas_core::templates::Templates;
use mas_templates::Templates;
use super::RootCommand;

View File

@ -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

View File

@ -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()
}
}

View File

@ -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"));
}

View File

@ -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]

View File

@ -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;

View File

@ -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},
};

View File

@ -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;

View File

@ -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);

View File

@ -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)?;

View File

@ -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;

View File

@ -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);

View File

@ -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)?;

View File

@ -29,5 +29,4 @@ pub mod handlers;
pub mod reply;
pub mod storage;
pub mod tasks;
pub mod templates;
pub mod tokens;

View File

@ -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;

View File

@ -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 {

View 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()
}
}

View File

@ -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,
}
}
}

View 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" }

View File

@ -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

View File

@ -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" };