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",
|
"indoc",
|
||||||
"mas-config",
|
"mas-config",
|
||||||
"mas-core",
|
"mas-core",
|
||||||
|
"mas-templates",
|
||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"opentelemetry-http",
|
"opentelemetry-http",
|
||||||
"opentelemetry-jaeger",
|
"opentelemetry-jaeger",
|
||||||
@ -1517,6 +1518,7 @@ dependencies = [
|
|||||||
"k256",
|
"k256",
|
||||||
"mas-config",
|
"mas-config",
|
||||||
"mas-data-model",
|
"mas-data-model",
|
||||||
|
"mas-templates",
|
||||||
"mime",
|
"mime",
|
||||||
"oauth2-types",
|
"oauth2-types",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -1552,6 +1554,24 @@ dependencies = [
|
|||||||
"thiserror",
|
"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]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
@ -33,6 +33,7 @@ opentelemetry-zipkin = { version = "0.14.0", features = ["reqwest-client", "reqw
|
|||||||
|
|
||||||
mas-config = { path = "../config" }
|
mas-config = { path = "../config" }
|
||||||
mas-core = { path = "../core" }
|
mas-core = { path = "../core" }
|
||||||
|
mas-templates = { path = "../templates" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
indoc = "1.0.3"
|
indoc = "1.0.3"
|
||||||
|
@ -24,8 +24,8 @@ use mas_config::RootConfig;
|
|||||||
use mas_core::{
|
use mas_core::{
|
||||||
storage::MIGRATOR,
|
storage::MIGRATOR,
|
||||||
tasks::{self, TaskQueue},
|
tasks::{self, TaskQueue},
|
||||||
templates::Templates,
|
|
||||||
};
|
};
|
||||||
|
use mas_templates::Templates;
|
||||||
use opentelemetry_http::HeaderExtractor;
|
use opentelemetry_http::HeaderExtractor;
|
||||||
use tower::{make::Shared, ServiceBuilder};
|
use tower::{make::Shared, ServiceBuilder};
|
||||||
use tower_http::{
|
use tower_http::{
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
use mas_core::templates::Templates;
|
use mas_templates::Templates;
|
||||||
|
|
||||||
use super::RootCommand;
|
use super::RootCommand;
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ futures-util = "0.3.17"
|
|||||||
|
|
||||||
# Logging and tracing
|
# Logging and tracing
|
||||||
tracing = "0.1.29"
|
tracing = "0.1.29"
|
||||||
|
opentelemetry = "0.16.0"
|
||||||
|
|
||||||
# Error management
|
# Error management
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
@ -63,12 +64,12 @@ rand = "0.8.4"
|
|||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
headers = "0.3.4"
|
headers = "0.3.4"
|
||||||
cookie = "0.15.1"
|
cookie = "0.15.1"
|
||||||
|
once_cell = "1.8.0"
|
||||||
|
|
||||||
oauth2-types = { path = "../oauth2-types", features = ["sqlx_type"] }
|
oauth2-types = { path = "../oauth2-types", features = ["sqlx_type"] }
|
||||||
mas-config = { path = "../config" }
|
mas-config = { path = "../config" }
|
||||||
mas-data-model = { path = "../data-model" }
|
mas-data-model = { path = "../data-model" }
|
||||||
opentelemetry = "0.16.0"
|
mas-templates = { path = "../templates" }
|
||||||
once_cell = "1.8.0"
|
|
||||||
|
|
||||||
[dependencies.jwt-compact]
|
[dependencies.jwt-compact]
|
||||||
# Waiting on the next release because of the bump of the `rsa` dependency
|
# 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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::{collections::HashMap, fmt::Debug, hash::Hash};
|
|
||||||
|
|
||||||
use serde::{ser::SerializeMap, Serialize};
|
|
||||||
use warp::{reject::Reject, Rejection};
|
use warp::{reject::Reject, Rejection};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -38,105 +35,3 @@ where
|
|||||||
self.map_err(|e| warp::reject::custom(WrappedError(e.into())))
|
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
|
/// Notify the CORS filter what opentelemetry propagators are being used. This
|
||||||
/// helps whitelisting headers in CORS requests.
|
/// helps whitelisting headers in CORS requests.
|
||||||
pub fn set_propagator(propagator: &dyn opentelemetry::propagation::TextMapPropagator) {
|
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
|
PROPAGATOR_HEADERS
|
||||||
.set(propagator.fields().map(ToString::to_string).collect())
|
.set(headers)
|
||||||
.expect(concat!(module_path!(), "::set_propagator was called twice"));
|
.expect(concat!(module_path!(), "::set_propagator was called twice"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,13 +28,11 @@ pub mod session;
|
|||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
|
||||||
|
use mas_templates::Templates;
|
||||||
use warp::{Filter, Rejection};
|
use warp::{Filter, Rejection};
|
||||||
|
|
||||||
pub use self::csrf::CsrfToken;
|
pub use self::csrf::CsrfToken;
|
||||||
use crate::{
|
use crate::config::{KeySet, OAuth2Config};
|
||||||
config::{KeySet, OAuth2Config},
|
|
||||||
templates::Templates,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Get the [`Templates`]
|
/// Get the [`Templates`]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -14,10 +14,11 @@
|
|||||||
|
|
||||||
#![allow(clippy::unused_async)] // Some warp filters need that
|
#![allow(clippy::unused_async)] // Some warp filters need that
|
||||||
|
|
||||||
|
use mas_templates::Templates;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use warp::{Filter, Rejection, Reply};
|
use warp::{Filter, Rejection, Reply};
|
||||||
|
|
||||||
use crate::{config::RootConfig, templates::Templates};
|
use crate::config::RootConfig;
|
||||||
|
|
||||||
mod health;
|
mod health;
|
||||||
mod oauth2;
|
mod oauth2;
|
||||||
|
@ -25,6 +25,7 @@ use hyper::{
|
|||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use mas_data_model::BrowserSession;
|
use mas_data_model::BrowserSession;
|
||||||
|
use mas_templates::{FormPostContext, Templates};
|
||||||
use oauth2_types::{
|
use oauth2_types::{
|
||||||
errors::{ErrorResponse, InvalidRequest, OAuth2Error},
|
errors::{ErrorResponse, InvalidRequest, OAuth2Error},
|
||||||
pkce,
|
pkce,
|
||||||
@ -62,7 +63,6 @@ use crate::{
|
|||||||
},
|
},
|
||||||
PostgresqlBackend,
|
PostgresqlBackend,
|
||||||
},
|
},
|
||||||
templates::{FormPostContext, Templates},
|
|
||||||
tokens::{AccessToken, RefreshToken},
|
tokens::{AccessToken, RefreshToken},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,13 +12,11 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
use mas_templates::Templates;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use warp::{Filter, Rejection, Reply};
|
use warp::{Filter, Rejection, Reply};
|
||||||
|
|
||||||
use crate::{
|
use crate::config::{CookiesConfig, OAuth2Config};
|
||||||
config::{CookiesConfig, OAuth2Config},
|
|
||||||
templates::Templates,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod authorization;
|
mod authorization;
|
||||||
mod discovery;
|
mod discovery;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use mas_data_model::BrowserSession;
|
use mas_data_model::BrowserSession;
|
||||||
|
use mas_templates::{IndexContext, TemplateContext, Templates};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
@ -26,7 +27,6 @@ use crate::{
|
|||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
storage::PostgresqlBackend,
|
storage::PostgresqlBackend,
|
||||||
templates::{IndexContext, TemplateContext, Templates},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) fn filter(
|
pub(super) fn filter(
|
||||||
@ -56,7 +56,7 @@ async fn get(
|
|||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let ctx = IndexContext::new(discovery_url)
|
let ctx = IndexContext::new(discovery_url)
|
||||||
.maybe_with_session(maybe_session)
|
.maybe_with_session(maybe_session)
|
||||||
.with_csrf(&csrf_token);
|
.with_csrf(csrf_token.form_value());
|
||||||
|
|
||||||
let content = templates.render_index(&ctx)?;
|
let content = templates.render_index(&ctx)?;
|
||||||
let reply = html(content);
|
let reply = html(content);
|
||||||
|
@ -15,14 +15,15 @@
|
|||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
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 serde::{Deserialize, Serialize};
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{CookiesConfig, CsrfConfig},
|
config::{CookiesConfig, CsrfConfig},
|
||||||
errors::{WrapError, WrapFormError},
|
errors::WrapError,
|
||||||
filters::{
|
filters::{
|
||||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||||
csrf::{protected_form, updated_csrf_token},
|
csrf::{protected_form, updated_csrf_token},
|
||||||
@ -31,7 +32,6 @@ use crate::{
|
|||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
storage::{login, PostgresqlBackend},
|
storage::{login, PostgresqlBackend},
|
||||||
templates::{LoginContext, LoginFormField, TemplateContext, Templates},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -114,7 +114,7 @@ async fn get(
|
|||||||
if maybe_session.is_some() {
|
if maybe_session.is_some() {
|
||||||
Ok(Box::new(query.redirect()?))
|
Ok(Box::new(query.redirect()?))
|
||||||
} else {
|
} 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 content = templates.render_login(&ctx)?;
|
||||||
let reply = html(content);
|
let reply = html(content);
|
||||||
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
||||||
@ -145,7 +145,8 @@ async fn post(
|
|||||||
LoginError::Authentication { .. } => e.on_field(LoginFormField::Password),
|
LoginError::Authentication { .. } => e.on_field(LoginFormField::Password),
|
||||||
LoginError::Other(_) => e.on_form(),
|
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 content = templates.render_login(&ctx)?;
|
||||||
let reply = html(content);
|
let reply = html(content);
|
||||||
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
||||||
|
@ -12,13 +12,11 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
use mas_templates::Templates;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use warp::{Filter, Rejection, Reply};
|
use warp::{Filter, Rejection, Reply};
|
||||||
|
|
||||||
use crate::{
|
use crate::config::{CookiesConfig, CsrfConfig, OAuth2Config};
|
||||||
config::{CookiesConfig, CsrfConfig, OAuth2Config},
|
|
||||||
templates::Templates,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod index;
|
mod index;
|
||||||
mod login;
|
mod login;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use mas_data_model::BrowserSession;
|
use mas_data_model::BrowserSession;
|
||||||
|
use mas_templates::{EmptyContext, TemplateContext, Templates};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::{PgPool, Postgres, Transaction};
|
use sqlx::{PgPool, Postgres, Transaction};
|
||||||
use warp::{hyper::Uri, reply::html, Filter, Rejection, Reply};
|
use warp::{hyper::Uri, reply::html, Filter, Rejection, Reply};
|
||||||
@ -28,7 +29,6 @@ use crate::{
|
|||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
storage::{user::authenticate_session, PostgresqlBackend},
|
storage::{user::authenticate_session, PostgresqlBackend},
|
||||||
templates::{EmptyContext, TemplateContext, Templates},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
@ -64,7 +64,9 @@ async fn get(
|
|||||||
csrf_token: CsrfToken,
|
csrf_token: CsrfToken,
|
||||||
session: BrowserSession<PostgresqlBackend>,
|
session: BrowserSession<PostgresqlBackend>,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> 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 content = templates.render_reauth(&ctx)?;
|
||||||
let reply = html(content);
|
let reply = html(content);
|
||||||
|
@ -17,6 +17,7 @@ use std::convert::TryFrom;
|
|||||||
use argon2::Argon2;
|
use argon2::Argon2;
|
||||||
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
||||||
use mas_data_model::BrowserSession;
|
use mas_data_model::BrowserSession;
|
||||||
|
use mas_templates::{EmptyContext, TemplateContext, Templates};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
@ -32,7 +33,6 @@ use crate::{
|
|||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
storage::{register_user, user::start_session, PostgresqlBackend},
|
storage::{register_user, user::start_session, PostgresqlBackend},
|
||||||
templates::{EmptyContext, TemplateContext, Templates},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -116,7 +116,7 @@ async fn get(
|
|||||||
if maybe_session.is_some() {
|
if maybe_session.is_some() {
|
||||||
Ok(Box::new(query.redirect()?))
|
Ok(Box::new(query.redirect()?))
|
||||||
} else {
|
} else {
|
||||||
let ctx = EmptyContext.with_csrf(&csrf_token);
|
let ctx = EmptyContext.with_csrf(csrf_token.form_value());
|
||||||
let content = templates.render_register(&ctx)?;
|
let content = templates.render_register(&ctx)?;
|
||||||
let reply = html(content);
|
let reply = html(content);
|
||||||
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
||||||
|
@ -29,5 +29,4 @@ pub mod handlers;
|
|||||||
pub mod reply;
|
pub mod reply;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
pub mod tasks;
|
pub mod tasks;
|
||||||
pub mod templates;
|
|
||||||
pub mod tokens;
|
pub mod tokens;
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
#![allow(clippy::used_underscore_binding)] // This is needed by sqlx macros
|
#![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 serde::Serialize;
|
||||||
use sqlx::migrate::Migrator;
|
use sqlx::migrate::Migrator;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@ -38,6 +38,8 @@ impl StorageBackend for PostgresqlBackend {
|
|||||||
type UserData = i64;
|
type UserData = i64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StorageBackendMarker for PostgresqlBackend {}
|
||||||
|
|
||||||
pub mod oauth2;
|
pub mod oauth2;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ use std::{borrow::BorrowMut, convert::TryInto};
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use argon2::Argon2;
|
use argon2::Argon2;
|
||||||
use chrono::{DateTime, Utc};
|
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 password_hash::{PasswordHash, PasswordHasher, SaltString};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use sqlx::{Acquire, Executor, FromRow, Postgres, Transaction};
|
use sqlx::{Acquire, Executor, FromRow, Postgres, Transaction};
|
||||||
@ -27,7 +27,6 @@ use tracing::{info_span, Instrument};
|
|||||||
use warp::reject::Reject;
|
use warp::reject::Reject;
|
||||||
|
|
||||||
use super::{DatabaseInconsistencyError, PostgresqlBackend};
|
use super::{DatabaseInconsistencyError, PostgresqlBackend};
|
||||||
use crate::errors::HtmlError;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromRow)]
|
#[derive(Debug, Clone, FromRow)]
|
||||||
struct UserLookup {
|
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 oauth2_types::{pkce::CodeChallengeMethod, scope::Scope};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
pub mod errors;
|
||||||
|
|
||||||
|
pub trait StorageBackendMarker: StorageBackend {}
|
||||||
|
|
||||||
pub trait StorageBackend {
|
pub trait StorageBackend {
|
||||||
type UserData: Clone + std::fmt::Debug + PartialEq;
|
type UserData: Clone + std::fmt::Debug + PartialEq;
|
||||||
type AuthenticationData: 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;
|
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)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
#[serde(bound = "T: StorageBackend")]
|
||||||
pub struct User<T: StorageBackend> {
|
pub struct User<T: StorageBackend> {
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub data: T::UserData,
|
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)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
#[serde(bound = "T: StorageBackend")]
|
||||||
pub struct Authentication<T: StorageBackend> {
|
pub struct Authentication<T: StorageBackend> {
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub data: T::AuthenticationData,
|
pub data: T::AuthenticationData,
|
||||||
pub created_at: DateTime<Utc>,
|
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)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
#[serde(bound = "T: StorageBackend")]
|
||||||
pub struct BrowserSession<T: StorageBackend> {
|
pub struct BrowserSession<T: StorageBackend> {
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub data: T::BrowserSessionData,
|
pub data: T::BrowserSessionData,
|
||||||
@ -63,6 +99,17 @@ pub struct BrowserSession<T: StorageBackend> {
|
|||||||
pub last_authentication: Option<Authentication<T>>,
|
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>
|
impl<T: StorageBackend> BrowserSession<T>
|
||||||
where
|
where
|
||||||
T::BrowserSessionData: Default,
|
T::BrowserSessionData: Default,
|
||||||
@ -82,13 +129,24 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
#[serde(bound = "T: StorageBackend")]
|
||||||
pub struct Client<T: StorageBackend> {
|
pub struct Client<T: StorageBackend> {
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub data: T::ClientData,
|
pub data: T::ClientData,
|
||||||
pub client_id: String,
|
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)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
#[serde(bound = "T: StorageBackend")]
|
||||||
pub struct Session<T: StorageBackend> {
|
pub struct Session<T: StorageBackend> {
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub data: T::SessionData,
|
pub data: T::SessionData,
|
||||||
@ -97,6 +155,17 @@ pub struct Session<T: StorageBackend> {
|
|||||||
pub scope: Scope,
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
pub struct Pkce {
|
pub struct Pkce {
|
||||||
challenge_method: CodeChallengeMethod,
|
challenge_method: CodeChallengeMethod,
|
||||||
@ -117,6 +186,7 @@ impl Pkce {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
|
#[serde(bound = "T: StorageBackend")]
|
||||||
pub struct AuthorizationCode<T: StorageBackend> {
|
pub struct AuthorizationCode<T: StorageBackend> {
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub data: T::AuthorizationCodeData,
|
pub data: T::AuthorizationCodeData,
|
||||||
@ -124,6 +194,16 @@ pub struct AuthorizationCode<T: StorageBackend> {
|
|||||||
pub pkce: Pkce,
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct AccessToken<T: StorageBackend> {
|
pub struct AccessToken<T: StorageBackend> {
|
||||||
pub data: T::AccessTokenData,
|
pub data: T::AccessTokenData,
|
||||||
@ -132,3 +212,15 @@ pub struct AccessToken<T: StorageBackend> {
|
|||||||
pub expires_after: Duration,
|
pub expires_after: Duration,
|
||||||
pub created_at: DateTime<Utc>,
|
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
|
//! Contexts used in templates
|
||||||
|
|
||||||
use mas_data_model::BrowserSession;
|
use mas_data_model::{errors::ErroredForm, BrowserSession, StorageBackend};
|
||||||
use oauth2_types::errors::OAuth2Error;
|
use oauth2_types::errors::OAuth2Error;
|
||||||
use serde::{ser::SerializeStruct, Serialize};
|
use serde::{ser::SerializeStruct, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{errors::ErroredForm, filters::CsrfToken, storage::PostgresqlBackend};
|
|
||||||
|
|
||||||
/// Helper trait to construct context wrappers
|
/// Helper trait to construct context wrappers
|
||||||
pub trait TemplateContext {
|
pub trait TemplateContext: Serialize {
|
||||||
/// Attach a user session to the template context
|
/// 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
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
|
BrowserSession<S>: Into<BrowserSession<()>>,
|
||||||
{
|
{
|
||||||
WithSession {
|
WithSession {
|
||||||
current_session,
|
current_session: current_session.into(),
|
||||||
inner: self,
|
inner: self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attach an optional user session to the template context
|
/// Attach an optional user session to the template context
|
||||||
fn maybe_with_session(
|
fn maybe_with_session<S: StorageBackend>(
|
||||||
self,
|
self,
|
||||||
current_session: Option<BrowserSession<PostgresqlBackend>>,
|
current_session: Option<BrowserSession<S>>,
|
||||||
) -> WithOptionalSession<Self>
|
) -> WithOptionalSession<Self>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
|
BrowserSession<S>: Into<BrowserSession<()>>,
|
||||||
{
|
{
|
||||||
WithOptionalSession {
|
WithOptionalSession {
|
||||||
current_session,
|
current_session: current_session.map(Into::into),
|
||||||
inner: self,
|
inner: self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attach a CSRF token to the template context
|
/// 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
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
|
// TODO: make this method use a CsrfToken again
|
||||||
WithCsrf {
|
WithCsrf {
|
||||||
csrf_token: token.form_value(),
|
csrf_token,
|
||||||
inner: self,
|
inner: self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,7 +99,7 @@ impl<T: TemplateContext> TemplateContext for WithCsrf<T> {
|
|||||||
/// Context with a user session in it
|
/// Context with a user session in it
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct WithSession<T> {
|
pub struct WithSession<T> {
|
||||||
current_session: BrowserSession<PostgresqlBackend>,
|
current_session: BrowserSession<()>,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
inner: T,
|
inner: T,
|
||||||
@ -106,7 +110,7 @@ impl<T: TemplateContext> TemplateContext for WithSession<T> {
|
|||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
BrowserSession::<PostgresqlBackend>::samples()
|
BrowserSession::samples()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|session| {
|
.flat_map(|session| {
|
||||||
T::sample().into_iter().map(move |inner| WithSession {
|
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
|
/// Context with an optional user session in it
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct WithOptionalSession<T> {
|
pub struct WithOptionalSession<T> {
|
||||||
current_session: Option<BrowserSession<PostgresqlBackend>>,
|
current_session: Option<BrowserSession<()>>,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
inner: T,
|
inner: T,
|
||||||
@ -132,7 +136,7 @@ impl<T: TemplateContext> TemplateContext for WithOptionalSession<T> {
|
|||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
BrowserSession::<PostgresqlBackend>::samples()
|
BrowserSession::samples()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Some) // Wrap all samples in an Option
|
.map(Some) // Wrap all samples in an Option
|
||||||
.chain(std::iter::once(None)) // Add the "None" option
|
.chain(std::iter::once(None)) // Add the "None" option
|
@ -12,7 +12,14 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
#![deny(missing_docs)]
|
#![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
|
//! Templates rendering
|
||||||
|
|
||||||
@ -25,7 +32,6 @@ use tera::{Context, Error as TeraError, Tera};
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::{fs::OpenOptions, io::AsyncWriteExt};
|
use tokio::{fs::OpenOptions, io::AsyncWriteExt};
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
use warp::reject::Reject;
|
|
||||||
|
|
||||||
#[allow(missing_docs)] // TODO
|
#[allow(missing_docs)] // TODO
|
||||||
mod context;
|
mod context;
|
||||||
@ -181,7 +187,7 @@ pub enum TemplateError {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Reject for TemplateError {}
|
impl warp::reject::Reject for TemplateError {}
|
||||||
|
|
||||||
register_templates! {
|
register_templates! {
|
||||||
extra = { "base.html" };
|
extra = { "base.html" };
|
Reference in New Issue
Block a user