diff --git a/Cargo.lock b/Cargo.lock index 6d8cd186..8b71e33a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index aa7d2ea3..741cce37 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -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" diff --git a/crates/cli/src/server.rs b/crates/cli/src/server.rs index 42c0d7d9..e72df73b 100644 --- a/crates/cli/src/server.rs +++ b/crates/cli/src/server.rs @@ -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::{ diff --git a/crates/cli/src/templates.rs b/crates/cli/src/templates.rs index ab07bf32..2f22b254 100644 --- a/crates/cli/src/templates.rs +++ b/crates/cli/src/templates.rs @@ -15,7 +15,7 @@ use std::path::PathBuf; use clap::Clap; -use mas_core::templates::Templates; +use mas_templates::Templates; use super::RootCommand; diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index b823ef3e..cc793139 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -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 diff --git a/crates/core/src/errors.rs b/crates/core/src/errors.rs index 44180222..5eb7d5ba 100644 --- a/crates/core/src/errors.rs +++ b/crates/core/src/errors.rs @@ -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 { - fn on_form(self) -> ErroredForm; - fn on_field(self, field: FieldType) -> ErroredForm; -} - -impl WrapFormError for E -where - E: HtmlError, -{ - fn on_form(self) -> ErroredForm { - let mut f = ErroredForm::new(); - f.form.push(FormError { - error: Box::new(self), - }); - f - } - - fn on_field(self, field: FieldType) -> ErroredForm { - let mut f = ErroredForm::new(); - f.fields.push(FieldError { - field, - error: Box::new(self), - }); - f - } -} - -#[derive(Debug)] -struct FormError { - error: Box, -} - -impl Serialize for FormError { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.error.html_display()) - } -} - -#[derive(Debug)] -struct FieldError { - field: FieldType, - error: Box, -} - -#[derive(Debug)] -pub struct ErroredForm { - form: Vec, - fields: Vec>, -} - -impl Default for ErroredForm { - fn default() -> Self { - Self { - form: Vec::new(), - fields: Vec::new(), - } - } -} - -impl ErroredForm { - #[must_use] - pub fn new() -> Self { - Self { - form: Vec::new(), - fields: Vec::new(), - } - } -} - -impl Reject for ErroredForm where T: Debug + Send + Sync + 'static {} - -impl Serialize for ErroredForm { - fn serialize(&self, serializer: S) -> Result - 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> = - 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() - } -} diff --git a/crates/core/src/filters/cors.rs b/crates/core/src/filters/cors.rs index f1afd86e..5c0e0c2f 100644 --- a/crates/core/src/filters/cors.rs +++ b/crates/core/src/filters/cors.rs @@ -23,8 +23,13 @@ static PROPAGATOR_HEADERS: OnceCell> = 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")); } diff --git a/crates/core/src/filters/mod.rs b/crates/core/src/filters/mod.rs index f75694fa..60a24ec8 100644 --- a/crates/core/src/filters/mod.rs +++ b/crates/core/src/filters/mod.rs @@ -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] diff --git a/crates/core/src/handlers/mod.rs b/crates/core/src/handlers/mod.rs index fcc625c9..e1ca00cf 100644 --- a/crates/core/src/handlers/mod.rs +++ b/crates/core/src/handlers/mod.rs @@ -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; diff --git a/crates/core/src/handlers/oauth2/authorization.rs b/crates/core/src/handlers/oauth2/authorization.rs index 312ae589..12373abf 100644 --- a/crates/core/src/handlers/oauth2/authorization.rs +++ b/crates/core/src/handlers/oauth2/authorization.rs @@ -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}, }; diff --git a/crates/core/src/handlers/oauth2/mod.rs b/crates/core/src/handlers/oauth2/mod.rs index 6769b340..52409d9a 100644 --- a/crates/core/src/handlers/oauth2/mod.rs +++ b/crates/core/src/handlers/oauth2/mod.rs @@ -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; diff --git a/crates/core/src/handlers/views/index.rs b/crates/core/src/handlers/views/index.rs index 33e67093..889cad56 100644 --- a/crates/core/src/handlers/views/index.rs +++ b/crates/core/src/handlers/views/index.rs @@ -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 { 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); diff --git a/crates/core/src/handlers/views/login.rs b/crates/core/src/handlers/views/login.rs index 7f667bb9..605931c2 100644 --- a/crates/core/src/handlers/views/login.rs +++ b/crates/core/src/handlers/views/login.rs @@ -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)?; diff --git a/crates/core/src/handlers/views/mod.rs b/crates/core/src/handlers/views/mod.rs index 4d5a9d5e..59b8e5ef 100644 --- a/crates/core/src/handlers/views/mod.rs +++ b/crates/core/src/handlers/views/mod.rs @@ -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; diff --git a/crates/core/src/handlers/views/reauth.rs b/crates/core/src/handlers/views/reauth.rs index b8bc2f3c..69d167ff 100644 --- a/crates/core/src/handlers/views/reauth.rs +++ b/crates/core/src/handlers/views/reauth.rs @@ -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, ) -> Result { - 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); diff --git a/crates/core/src/handlers/views/register.rs b/crates/core/src/handlers/views/register.rs index 99af9953..8a7daa4f 100644 --- a/crates/core/src/handlers/views/register.rs +++ b/crates/core/src/handlers/views/register.rs @@ -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)?; diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 21717359..b62fa9d7 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -29,5 +29,4 @@ pub mod handlers; pub mod reply; pub mod storage; pub mod tasks; -pub mod templates; pub mod tokens; diff --git a/crates/core/src/storage/mod.rs b/crates/core/src/storage/mod.rs index 14869cc5..522faa17 100644 --- a/crates/core/src/storage/mod.rs +++ b/crates/core/src/storage/mod.rs @@ -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; diff --git a/crates/core/src/storage/user.rs b/crates/core/src/storage/user.rs index 2aa6cdcc..3f8fe2fc 100644 --- a/crates/core/src/storage/user.rs +++ b/crates/core/src/storage/user.rs @@ -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 { diff --git a/crates/data-model/src/errors.rs b/crates/data-model/src/errors.rs new file mode 100644 index 00000000..61d5f011 --- /dev/null +++ b/crates/data-model/src/errors.rs @@ -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 { + fn on_form(self) -> ErroredForm; + fn on_field(self, field: FieldType) -> ErroredForm; +} + +impl WrapFormError for E +where + E: HtmlError, +{ + fn on_form(self) -> ErroredForm { + let mut f = ErroredForm::new(); + f.form.push(FormError { + error: Box::new(self), + }); + f + } + + fn on_field(self, field: FieldType) -> ErroredForm { + let mut f = ErroredForm::new(); + f.fields.push(FieldError { + field, + error: Box::new(self), + }); + f + } +} + +#[derive(Debug)] +struct FormError { + error: Box, +} + +impl Serialize for FormError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.error.html_display()) + } +} + +#[derive(Debug)] +struct FieldError { + field: FieldType, + error: Box, +} + +#[derive(Debug)] +pub struct ErroredForm { + form: Vec, + fields: Vec>, +} + +impl Default for ErroredForm { + fn default() -> Self { + Self { + form: Vec::new(), + fields: Vec::new(), + } + } +} + +impl ErroredForm { + #[must_use] + pub fn new() -> Self { + Self { + form: Vec::new(), + fields: Vec::new(), + } + } +} + +impl Serialize for ErroredForm { + fn serialize(&self, serializer: S) -> Result + 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> = + 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() + } +} diff --git a/crates/data-model/src/lib.rs b/crates/data-model/src/lib.rs index 09ec1b14..827294d9 100644 --- a/crates/data-model/src/lib.rs +++ b/crates/data-model/src/lib.rs @@ -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 { #[serde(skip_serializing)] pub data: T::UserData, @@ -47,14 +62,35 @@ where } } +impl From> for User<()> { + fn from(u: User) -> Self { + User { + data: (), + username: u.username, + sub: u.sub, + } + } +} + #[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(bound = "T: StorageBackend")] pub struct Authentication { #[serde(skip_serializing)] pub data: T::AuthenticationData, pub created_at: DateTime, } +impl From> for Authentication<()> { + fn from(a: Authentication) -> Self { + Authentication { + data: (), + created_at: a.created_at, + } + } +} + #[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(bound = "T: StorageBackend")] pub struct BrowserSession { #[serde(skip_serializing)] pub data: T::BrowserSessionData, @@ -63,6 +99,17 @@ pub struct BrowserSession { pub last_authentication: Option>, } +impl From> for BrowserSession<()> { + fn from(s: BrowserSession) -> Self { + BrowserSession { + data: (), + user: s.user.into(), + created_at: s.created_at, + last_authentication: s.last_authentication.map(Into::into), + } + } +} + impl BrowserSession where T::BrowserSessionData: Default, @@ -82,13 +129,24 @@ where } #[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(bound = "T: StorageBackend")] pub struct Client { #[serde(skip_serializing)] pub data: T::ClientData, pub client_id: String, } +impl From> for Client<()> { + fn from(c: Client) -> Self { + Client { + data: (), + client_id: c.client_id, + } + } +} + #[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(bound = "T: StorageBackend")] pub struct Session { #[serde(skip_serializing)] pub data: T::SessionData, @@ -97,6 +155,17 @@ pub struct Session { pub scope: Scope, } +impl From> for Session<()> { + fn from(s: Session) -> 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 { #[serde(skip_serializing)] pub data: T::AuthorizationCodeData, @@ -124,6 +194,16 @@ pub struct AuthorizationCode { pub pkce: Pkce, } +impl From> for AuthorizationCode<()> { + fn from(c: AuthorizationCode) -> Self { + AuthorizationCode { + data: (), + code: c.code, + pkce: c.pkce, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct AccessToken { pub data: T::AccessTokenData, @@ -132,3 +212,15 @@ pub struct AccessToken { pub expires_after: Duration, pub created_at: DateTime, } + +impl From> for AccessToken<()> { + fn from(t: AccessToken) -> Self { + AccessToken { + data: (), + jti: t.jti, + token: t.token, + expires_after: t.expires_after, + created_at: t.created_at, + } + } +} diff --git a/crates/templates/Cargo.toml b/crates/templates/Cargo.toml new file mode 100644 index 00000000..b6a0a105 --- /dev/null +++ b/crates/templates/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "mas-templates" +version = "0.1.0" +authors = ["Quentin Gliech "] +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" } diff --git a/crates/core/src/templates/context.rs b/crates/templates/src/context.rs similarity index 89% rename from crates/core/src/templates/context.rs rename to crates/templates/src/context.rs index c62bf961..1d521037 100644 --- a/crates/core/src/templates/context.rs +++ b/crates/templates/src/context.rs @@ -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) -> WithSession + fn with_session( + self, + current_session: BrowserSession, + ) -> WithSession where Self: Sized, + BrowserSession: Into>, { 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( self, - current_session: Option>, + current_session: Option>, ) -> WithOptionalSession where Self: Sized, + BrowserSession: Into>, { 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 + fn with_csrf(self, csrf_token: String) -> WithCsrf 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 TemplateContext for WithCsrf { /// Context with a user session in it #[derive(Serialize)] pub struct WithSession { - current_session: BrowserSession, + current_session: BrowserSession<()>, #[serde(flatten)] inner: T, @@ -106,7 +110,7 @@ impl TemplateContext for WithSession { where Self: Sized, { - BrowserSession::::samples() + BrowserSession::samples() .into_iter() .flat_map(|session| { T::sample().into_iter().map(move |inner| WithSession { @@ -121,7 +125,7 @@ impl TemplateContext for WithSession { /// Context with an optional user session in it #[derive(Serialize)] pub struct WithOptionalSession { - current_session: Option>, + current_session: Option>, #[serde(flatten)] inner: T, @@ -132,7 +136,7 @@ impl TemplateContext for WithOptionalSession { where Self: Sized, { - BrowserSession::::samples() + BrowserSession::samples() .into_iter() .map(Some) // Wrap all samples in an Option .chain(std::iter::once(None)) // Add the "None" option diff --git a/crates/core/src/templates/mod.rs b/crates/templates/src/lib.rs similarity index 96% rename from crates/core/src/templates/mod.rs rename to crates/templates/src/lib.rs index 040cd318..3ae30a0c 100644 --- a/crates/core/src/templates/mod.rs +++ b/crates/templates/src/lib.rs @@ -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" }; diff --git a/crates/core/src/templates/macros.rs b/crates/templates/src/macros.rs similarity index 100% rename from crates/core/src/templates/macros.rs rename to crates/templates/src/macros.rs diff --git a/crates/core/src/templates/res/base.html b/crates/templates/src/res/base.html similarity index 100% rename from crates/core/src/templates/res/base.html rename to crates/templates/src/res/base.html diff --git a/crates/core/src/templates/res/error.html b/crates/templates/src/res/error.html similarity index 100% rename from crates/core/src/templates/res/error.html rename to crates/templates/src/res/error.html diff --git a/crates/core/src/templates/res/error.txt b/crates/templates/src/res/error.txt similarity index 100% rename from crates/core/src/templates/res/error.txt rename to crates/templates/src/res/error.txt diff --git a/crates/core/src/templates/res/form_post.html b/crates/templates/src/res/form_post.html similarity index 100% rename from crates/core/src/templates/res/form_post.html rename to crates/templates/src/res/form_post.html diff --git a/crates/core/src/templates/res/index.html b/crates/templates/src/res/index.html similarity index 100% rename from crates/core/src/templates/res/index.html rename to crates/templates/src/res/index.html diff --git a/crates/core/src/templates/res/login.html b/crates/templates/src/res/login.html similarity index 100% rename from crates/core/src/templates/res/login.html rename to crates/templates/src/res/login.html diff --git a/crates/core/src/templates/res/reauth.html b/crates/templates/src/res/reauth.html similarity index 100% rename from crates/core/src/templates/res/reauth.html rename to crates/templates/src/res/reauth.html diff --git a/crates/core/src/templates/res/register.html b/crates/templates/src/res/register.html similarity index 100% rename from crates/core/src/templates/res/register.html rename to crates/templates/src/res/register.html