mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-05-16 16:35:24 +00:00
WIP: migrate to warp, part 3
This commit is contained in:
@@ -19,6 +19,7 @@ use clap::Clap;
|
||||
|
||||
use super::RootCommand;
|
||||
use crate::config::RootConfig;
|
||||
use crate::templates::Templates;
|
||||
|
||||
#[derive(Clap, Debug, Default)]
|
||||
pub(super) struct ServerCommand;
|
||||
@@ -31,7 +32,7 @@ impl ServerCommand {
|
||||
let pool = config.database.connect().await?;
|
||||
|
||||
// Load and compile the templates
|
||||
let templates = crate::templates::load().context("could not load templates")?;
|
||||
let templates = Templates::load().context("could not load templates")?;
|
||||
|
||||
// Start the server
|
||||
let address: SocketAddr = config.http.address.parse()?;
|
||||
|
||||
@@ -55,10 +55,10 @@ pub struct CsrfConfig {
|
||||
}
|
||||
|
||||
impl CsrfConfig {
|
||||
pub fn into_extract_filter(self) -> BoxedFilter<(CsrfToken,)> {
|
||||
pub fn to_extract_filter(&self) -> BoxedFilter<(CsrfToken,)> {
|
||||
let ttl = self.ttl;
|
||||
// TODO: we should probably not leak here
|
||||
let cookie_name = Box::leak(Box::new(self.cookie_name));
|
||||
let cookie_name = Box::leak(Box::new(self.cookie_name.clone()));
|
||||
extract_or_generate(self.key, cookie_name, ttl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,17 @@
|
||||
pub mod csrf;
|
||||
// mod errors;
|
||||
|
||||
pub use csrf::UnencryptedToken as CsrfToken;
|
||||
use sqlx::PgPool;
|
||||
use warp::{filters::BoxedFilter, Filter};
|
||||
|
||||
use crate::templates::Templates;
|
||||
|
||||
pub use self::csrf::UnencryptedToken as CsrfToken;
|
||||
|
||||
pub fn with_pool(pool: PgPool) -> BoxedFilter<(PgPool,)> {
|
||||
warp::any().map(move || pool.clone()).boxed()
|
||||
}
|
||||
|
||||
pub fn with_templates(templates: Templates) -> BoxedFilter<(Templates,)> {
|
||||
warp::any().map(move || templates.clone()).boxed()
|
||||
}
|
||||
|
||||
@@ -14,11 +14,19 @@
|
||||
|
||||
use sqlx::PgPool;
|
||||
use tracing::{info_span, Instrument};
|
||||
use warp::{Rejection, Reply};
|
||||
use warp::{filters::BoxedFilter, Filter, Rejection, Reply};
|
||||
|
||||
use crate::errors::WrapError;
|
||||
use crate::{errors::WrapError, filters::with_pool};
|
||||
|
||||
pub async fn get(pool: PgPool) -> Result<impl Reply, Rejection> {
|
||||
pub fn filter(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
|
||||
warp::get()
|
||||
.and(warp::path("health"))
|
||||
.and(with_pool(pool))
|
||||
.and_then(get)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
async fn get(pool: PgPool) -> Result<impl Reply, Rejection> {
|
||||
sqlx::query("SELECT $1")
|
||||
.bind(1_i64)
|
||||
.execute(&pool)
|
||||
|
||||
@@ -12,94 +12,24 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{convert::Infallible, sync::Arc};
|
||||
|
||||
use sqlx::PgPool;
|
||||
use tera::Tera;
|
||||
use warp::{filters::BoxedFilter, wrap_fn, Filter, Rejection, Reply};
|
||||
use warp::{filters::BoxedFilter, Filter};
|
||||
|
||||
use crate::{config::RootConfig, filters::csrf::with_csrf};
|
||||
use crate::{config::RootConfig, templates::Templates};
|
||||
|
||||
mod health;
|
||||
mod oauth2;
|
||||
mod views;
|
||||
|
||||
async fn display_error(err: Rejection) -> Result<impl Reply, Infallible> {
|
||||
let ret = format!("{:?}", err);
|
||||
Ok(ret)
|
||||
}
|
||||
use self::{health::filter as health, oauth2::filter as oauth2, views::filter as views};
|
||||
|
||||
pub fn root(
|
||||
pool: PgPool,
|
||||
templates: Tera,
|
||||
templates: Templates,
|
||||
config: &RootConfig,
|
||||
) -> BoxedFilter<(impl warp::Reply,)> {
|
||||
let templates = Arc::new(templates);
|
||||
let with_csrf_token = config.csrf.clone().into_extract_filter();
|
||||
let with_pool = warp::any().map(move || pool.clone());
|
||||
let with_templates = warp::any().map(move || templates.clone());
|
||||
|
||||
// TODO: this is ugly and leaks
|
||||
let csrf_cookie_name = Box::leak(Box::new(config.csrf.cookie_name.clone()));
|
||||
|
||||
let cors = warp::cors().allow_any_origin();
|
||||
|
||||
let health = warp::path("health")
|
||||
.and(warp::get())
|
||||
.and(with_pool.clone())
|
||||
.and_then(self::health::get)
|
||||
.boxed();
|
||||
|
||||
let metadata = warp::path!(".well-known" / "openid-configuration")
|
||||
.and(warp::get())
|
||||
.and(self::oauth2::discovery::get(&config.oauth2))
|
||||
.with(cors);
|
||||
|
||||
let index = warp::path::end()
|
||||
.and(warp::get())
|
||||
.and(with_templates.clone())
|
||||
.and(with_csrf_token.clone())
|
||||
.and(with_pool.clone())
|
||||
.and_then(self::views::index::get)
|
||||
.untuple_one()
|
||||
.with(wrap_fn(with_csrf(config.csrf.key, csrf_cookie_name)));
|
||||
|
||||
let login = warp::path("login")
|
||||
.and(warp::get())
|
||||
.and(with_templates)
|
||||
.and(with_csrf_token)
|
||||
.and(with_pool)
|
||||
.and_then(self::views::login::get)
|
||||
.untuple_one()
|
||||
.with(wrap_fn(with_csrf(config.csrf.key, csrf_cookie_name)));
|
||||
|
||||
health.or(index).or(login).or(metadata).boxed()
|
||||
|
||||
// app.at("/").nest({
|
||||
// let mut views = tide::with_state(state.clone());
|
||||
// views.with(state.session_middleware());
|
||||
// views.with(state.csrf_middleware());
|
||||
// views.with(crate::middlewares::errors);
|
||||
|
||||
// views.at("/").get(self::views::index::get);
|
||||
|
||||
// views
|
||||
// .at("/login")
|
||||
// .get(self::views::login::get)
|
||||
// .post(self::views::login::post);
|
||||
|
||||
// views
|
||||
// .at("/reauth")
|
||||
// .get(self::views::reauth::get)
|
||||
// .post(self::views::reauth::post);
|
||||
|
||||
// views.at("/logout").post(self::views::logout::post);
|
||||
|
||||
// views
|
||||
// .at("oauth2/authorize")
|
||||
// .with(BrowserErrorHandler)
|
||||
// .get(self::oauth2::authorization::get);
|
||||
|
||||
// views
|
||||
// });
|
||||
health(pool.clone())
|
||||
.or(oauth2(&config.oauth2))
|
||||
.or(views(pool, templates, &config.csrf))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use warp::{filters::BoxedFilter, Filter, Reply};
|
||||
|
||||
use crate::config::OAuth2Config;
|
||||
|
||||
pub fn get(config: &OAuth2Config) -> BoxedFilter<(impl Reply,)> {
|
||||
pub(super) fn filter(config: &OAuth2Config) -> BoxedFilter<(impl Reply,)> {
|
||||
let base = config.issuer.clone();
|
||||
let metadata = Metadata {
|
||||
authorization_endpoint: base.join("oauth2/authorize").ok(),
|
||||
@@ -32,7 +32,11 @@ pub fn get(config: &OAuth2Config) -> BoxedFilter<(impl Reply,)> {
|
||||
code_challenge_methods_supported: None,
|
||||
};
|
||||
|
||||
warp::any()
|
||||
let cors = warp::cors().allow_any_origin();
|
||||
|
||||
warp::get()
|
||||
.and(warp::path!(".well-known" / "openid-configuration"))
|
||||
.map(move || warp::reply::json(&metadata))
|
||||
.with(cors)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
@@ -12,5 +12,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use warp::{filters::BoxedFilter, Reply};
|
||||
|
||||
use crate::config::OAuth2Config;
|
||||
|
||||
// pub mod authorization;
|
||||
pub mod discovery;
|
||||
mod discovery;
|
||||
|
||||
use self::discovery::filter as discovery;
|
||||
|
||||
pub fn filter(config: &OAuth2Config) -> BoxedFilter<(impl Reply,)> {
|
||||
discovery(config)
|
||||
}
|
||||
|
||||
@@ -12,22 +12,43 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use sqlx::PgPool;
|
||||
use tera::Tera;
|
||||
use warp::{reply::with_header, Rejection, Reply};
|
||||
use warp::{filters::BoxedFilter, reply::with_header, wrap_fn, Filter, Rejection, Reply};
|
||||
|
||||
use crate::{errors::WrapError, filters::CsrfToken, templates::CommonContext};
|
||||
use crate::{
|
||||
config::CsrfConfig,
|
||||
errors::WrapError,
|
||||
filters::{csrf::with_csrf, with_pool, with_templates, CsrfToken},
|
||||
templates::{CommonContext, Templates},
|
||||
};
|
||||
|
||||
pub async fn get(
|
||||
templates: Arc<Tera>,
|
||||
pub(super) fn filter(
|
||||
pool: PgPool,
|
||||
templates: Templates,
|
||||
csrf_config: &CsrfConfig,
|
||||
) -> BoxedFilter<(impl Reply,)> {
|
||||
// TODO: this is ugly and leaks
|
||||
let csrf_cookie_name = Box::leak(Box::new(csrf_config.cookie_name.clone()));
|
||||
|
||||
warp::get()
|
||||
.and(warp::path::end())
|
||||
.and(with_templates(templates))
|
||||
.and(csrf_config.to_extract_filter())
|
||||
.and(with_pool(pool))
|
||||
.and_then(get)
|
||||
.untuple_one()
|
||||
.with(wrap_fn(with_csrf(csrf_config.key, csrf_cookie_name)))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
async fn get(
|
||||
templates: Templates,
|
||||
csrf_token: CsrfToken,
|
||||
db: PgPool,
|
||||
) -> Result<(CsrfToken, impl Reply), Rejection> {
|
||||
let ctx = CommonContext::default()
|
||||
.with_csrf_token(&csrf_token)
|
||||
.with_session(&db)
|
||||
.load_session(&db)
|
||||
.await
|
||||
.wrap_error()?
|
||||
.finish()
|
||||
|
||||
@@ -12,14 +12,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
use tera::Tera;
|
||||
use warp::{reply::with_header, Rejection, Reply};
|
||||
use warp::{
|
||||
filters::BoxedFilter, hyper::Uri, reply::with_header, wrap_fn, Filter, Rejection, Reply,
|
||||
};
|
||||
|
||||
use crate::{errors::WrapError, filters::CsrfToken, templates::CommonContext};
|
||||
use crate::{
|
||||
config::CsrfConfig,
|
||||
csrf::CsrfForm,
|
||||
errors::WrapError,
|
||||
filters::{csrf::with_csrf, with_pool, with_templates, CsrfToken},
|
||||
storage::login,
|
||||
templates::{CommonContext, Templates},
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LoginForm {
|
||||
@@ -27,14 +33,41 @@ struct LoginForm {
|
||||
password: String,
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
templates: Arc<Tera>,
|
||||
pub(super) fn filter(
|
||||
pool: PgPool,
|
||||
templates: Templates,
|
||||
csrf_config: &CsrfConfig,
|
||||
) -> BoxedFilter<(impl Reply,)> {
|
||||
// TODO: this is ugly and leaks
|
||||
let csrf_cookie_name = Box::leak(Box::new(csrf_config.cookie_name.clone()));
|
||||
|
||||
let get = warp::get()
|
||||
.and(with_templates(templates))
|
||||
.and(csrf_config.to_extract_filter())
|
||||
.and(with_pool(pool.clone()))
|
||||
.and_then(get)
|
||||
.untuple_one()
|
||||
.with(wrap_fn(with_csrf(csrf_config.key, csrf_cookie_name)));
|
||||
|
||||
let post = warp::post()
|
||||
.and(csrf_config.to_extract_filter())
|
||||
.and(with_pool(pool))
|
||||
.and(warp::body::form())
|
||||
.and_then(post)
|
||||
.untuple_one()
|
||||
.with(wrap_fn(with_csrf(csrf_config.key, csrf_cookie_name)));
|
||||
|
||||
warp::path("login").and(get.or(post)).boxed()
|
||||
}
|
||||
|
||||
async fn get(
|
||||
templates: Templates,
|
||||
csrf_token: CsrfToken,
|
||||
db: PgPool,
|
||||
) -> Result<(CsrfToken, impl Reply), Rejection> {
|
||||
let ctx = CommonContext::default()
|
||||
.with_csrf_token(&csrf_token)
|
||||
.with_session(&db)
|
||||
.load_session(&db)
|
||||
.await
|
||||
.wrap_error()?
|
||||
.finish()
|
||||
@@ -48,20 +81,16 @@ pub async fn get(
|
||||
))
|
||||
}
|
||||
|
||||
/*
|
||||
pub async fn post(mut req: Request<State>) -> tide::Result {
|
||||
let form: CsrfForm<LoginForm> = req.body_form().await?;
|
||||
let form = form.verify_csrf(&req)?;
|
||||
let state = req.state();
|
||||
async fn post(
|
||||
csrf_token: CsrfToken,
|
||||
db: PgPool,
|
||||
form: CsrfForm<LoginForm>,
|
||||
) -> Result<(CsrfToken, impl Reply), Rejection> {
|
||||
let form = form.verify_csrf(&csrf_token).wrap_error()?;
|
||||
|
||||
let session_info = state
|
||||
.storage()
|
||||
.login(&form.username, &form.password)
|
||||
.await?;
|
||||
let _session_info = login(&db, &form.username, &form.password)
|
||||
.await
|
||||
.wrap_error()?;
|
||||
|
||||
let session = req.session_mut();
|
||||
session.insert("current_session", session_info.key())?;
|
||||
|
||||
Ok(Redirect::new("/").into())
|
||||
Ok((csrf_token, warp::redirect(Uri::from_static("/"))))
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -12,16 +12,28 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use tide::{Redirect, Request};
|
||||
use warp::{filters::BoxedFilter, hyper::Uri, wrap_fn, Filter, Rejection, Reply};
|
||||
|
||||
use crate::{csrf::CsrfForm, state::State};
|
||||
use crate::{
|
||||
config::CsrfConfig,
|
||||
csrf::CsrfForm,
|
||||
errors::WrapError,
|
||||
filters::{csrf::with_csrf, CsrfToken},
|
||||
};
|
||||
|
||||
pub async fn post(mut req: Request<State>) -> tide::Result {
|
||||
let form: CsrfForm<()> = req.body_form().await?;
|
||||
form.verify_csrf(&req)?;
|
||||
pub(super) fn filter(csrf_config: &CsrfConfig) -> BoxedFilter<(impl Reply,)> {
|
||||
// TODO: this is ugly and leaks
|
||||
let csrf_cookie_name = Box::leak(Box::new(csrf_config.cookie_name.clone()));
|
||||
|
||||
let session = req.session_mut();
|
||||
session.remove("current_session");
|
||||
|
||||
Ok(Redirect::new("/").into())
|
||||
warp::post()
|
||||
.and(warp::path("logout"))
|
||||
.and(csrf_config.to_extract_filter())
|
||||
.and(warp::body::form())
|
||||
.and_then(|token: CsrfToken, form: CsrfForm<()>| async {
|
||||
form.verify_csrf(&token).wrap_error()?;
|
||||
Ok::<_, Rejection>((token, warp::redirect(Uri::from_static("/login"))))
|
||||
})
|
||||
.untuple_one()
|
||||
.with(wrap_fn(with_csrf(csrf_config.key, csrf_cookie_name)))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
@@ -12,7 +12,29 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub(super) mod index;
|
||||
pub(super) mod login;
|
||||
// pub(super) mod logout;
|
||||
// pub(super) mod reauth;
|
||||
use sqlx::PgPool;
|
||||
use warp::{filters::BoxedFilter, Filter, Reply};
|
||||
|
||||
use crate::{config::CsrfConfig, templates::Templates};
|
||||
|
||||
mod index;
|
||||
mod login;
|
||||
mod logout;
|
||||
mod reauth;
|
||||
|
||||
use self::index::filter as index;
|
||||
use self::login::filter as login;
|
||||
use self::logout::filter as logout;
|
||||
use self::reauth::filter as reauth;
|
||||
|
||||
pub(super) fn filter(
|
||||
pool: PgPool,
|
||||
templates: Templates,
|
||||
csrf_config: &CsrfConfig,
|
||||
) -> BoxedFilter<(impl Reply,)> {
|
||||
index(pool.clone(), templates.clone(), csrf_config)
|
||||
.or(login(pool.clone(), templates.clone(), csrf_config))
|
||||
.or(logout(csrf_config))
|
||||
.or(reauth(pool, templates, csrf_config))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
@@ -13,26 +13,91 @@
|
||||
// limitations under the License.
|
||||
|
||||
use serde::Deserialize;
|
||||
use tide::{Redirect, Request, Response};
|
||||
use sqlx::PgPool;
|
||||
use tracing::info;
|
||||
use warp::{filters::BoxedFilter, reply::with_header, wrap_fn, Filter, Rejection, Reply};
|
||||
|
||||
use crate::{csrf::CsrfForm, state::State, templates::common_context};
|
||||
use crate::{
|
||||
config::CsrfConfig,
|
||||
csrf::CsrfForm,
|
||||
errors::WrapError,
|
||||
filters::{csrf::with_csrf, with_pool, with_templates, CsrfToken},
|
||||
templates::{CommonContext, Templates},
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ReauthForm {
|
||||
password: String,
|
||||
}
|
||||
|
||||
pub async fn get(req: Request<State>) -> tide::Result {
|
||||
let state = req.state();
|
||||
let ctx = common_context(&req).await?;
|
||||
pub(super) fn filter(
|
||||
pool: PgPool,
|
||||
templates: Templates,
|
||||
csrf_config: &CsrfConfig,
|
||||
) -> BoxedFilter<(impl Reply,)> {
|
||||
// TODO: this is ugly and leaks
|
||||
let csrf_cookie_name = Box::leak(Box::new(csrf_config.cookie_name.clone()));
|
||||
|
||||
let get = warp::get()
|
||||
.and(with_templates(templates))
|
||||
.and(csrf_config.to_extract_filter())
|
||||
.and(with_pool(pool.clone()))
|
||||
.and_then(get)
|
||||
.untuple_one()
|
||||
.with(wrap_fn(with_csrf(csrf_config.key, csrf_cookie_name)));
|
||||
|
||||
let post = warp::post()
|
||||
.and(csrf_config.to_extract_filter())
|
||||
.and(with_pool(pool))
|
||||
.and(warp::body::form())
|
||||
.and_then(post)
|
||||
.untuple_one()
|
||||
.with(wrap_fn(with_csrf(csrf_config.key, csrf_cookie_name)));
|
||||
|
||||
warp::path("reauth").and(get.or(post)).boxed()
|
||||
}
|
||||
|
||||
async fn get(
|
||||
templates: Templates,
|
||||
csrf_token: CsrfToken,
|
||||
db: PgPool,
|
||||
) -> Result<(CsrfToken, impl Reply), Rejection> {
|
||||
let ctx = CommonContext::default()
|
||||
.with_csrf_token(&csrf_token)
|
||||
.load_session(&db)
|
||||
.await
|
||||
.wrap_error()?
|
||||
.finish()
|
||||
.wrap_error()?;
|
||||
|
||||
// TODO: check if there is an existing session
|
||||
let content = state.templates().render("reauth.html", &ctx)?;
|
||||
let body = Response::builder(200)
|
||||
.body(content)
|
||||
.content_type("text/html")
|
||||
.into();
|
||||
Ok(body)
|
||||
let content = templates.render("reauth.html", &ctx).wrap_error()?;
|
||||
Ok((
|
||||
csrf_token,
|
||||
with_header(content, "Content-Type", "text/html"),
|
||||
))
|
||||
}
|
||||
|
||||
async fn post(
|
||||
csrf_token: CsrfToken,
|
||||
_db: PgPool,
|
||||
form: CsrfForm<ReauthForm>,
|
||||
) -> Result<(CsrfToken, impl Reply), Rejection> {
|
||||
let form = form.verify_csrf(&csrf_token).wrap_error()?;
|
||||
|
||||
info!(?form, "reauth");
|
||||
|
||||
Ok((csrf_token, "unimplemented"))
|
||||
}
|
||||
|
||||
/*
|
||||
let form = form.verify_csrf(&csrf_token).wrap_error()?;
|
||||
|
||||
let _session_info = login(&db, &form.username, &form.password)
|
||||
.await
|
||||
.wrap_error()?;
|
||||
|
||||
Ok((csrf_token, warp::redirect(Uri::from_static("/"))))
|
||||
}
|
||||
|
||||
pub async fn post(mut req: Request<State>) -> tide::Result {
|
||||
@@ -52,3 +117,4 @@ pub async fn post(mut req: Request<State>) -> tide::Result {
|
||||
|
||||
Ok(Redirect::new("/").into())
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -22,7 +22,7 @@ mod user;
|
||||
|
||||
pub use self::{
|
||||
client::{Client, ClientLookupError, InvalidRedirectUriError},
|
||||
user::{lookup_session, SessionInfo, User},
|
||||
user::{login, lookup_session, SessionInfo, User},
|
||||
};
|
||||
|
||||
pub static MIGRATOR: Migrator = sqlx::migrate!();
|
||||
|
||||
@@ -47,12 +47,7 @@ impl SessionInfo {
|
||||
|
||||
impl super::Storage<PgPool> {
|
||||
pub async fn login(&self, username: &str, password: &str) -> anyhow::Result<SessionInfo> {
|
||||
let mut txn = self.pool.begin().await?;
|
||||
let user = lookup_user_by_username(&mut txn, username).await?;
|
||||
let mut session = start_session(&mut txn, user).await?;
|
||||
session.last_authd_at = Some(authenticate_session(&mut txn, session.id, password).await?);
|
||||
txn.commit().await?;
|
||||
Ok(session)
|
||||
login(&self.pool, username, password).await
|
||||
}
|
||||
|
||||
pub async fn register_user(&self, username: &str, password: &str) -> anyhow::Result<User> {
|
||||
@@ -79,6 +74,15 @@ impl super::Storage<PgPool> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn login(pool: &PgPool, username: &str, password: &str) -> anyhow::Result<SessionInfo> {
|
||||
let mut txn = pool.begin().await?;
|
||||
let user = lookup_user_by_username(&mut txn, username).await?;
|
||||
let mut session = start_session(&mut txn, user).await?;
|
||||
session.last_authd_at = Some(authenticate_session(&mut txn, session.id, password).await?);
|
||||
txn.commit().await?;
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
pub async fn lookup_session(
|
||||
executor: impl Executor<'_, Database = Postgres>,
|
||||
id: i32,
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use serde::Serialize;
|
||||
use sqlx::{Executor, Postgres};
|
||||
@@ -23,10 +25,24 @@ use crate::{
|
||||
storage::{lookup_session, SessionInfo},
|
||||
};
|
||||
|
||||
pub fn load() -> Result<Tera, tera::Error> {
|
||||
let path = format!("{}/templates/**/*.{{html,txt}}", env!("CARGO_MANIFEST_DIR"));
|
||||
info!(%path, "Loading templates");
|
||||
Tera::new(&path)
|
||||
#[derive(Clone)]
|
||||
pub struct Templates(Arc<Tera>);
|
||||
|
||||
impl Templates {
|
||||
pub fn load() -> Result<Self, tera::Error> {
|
||||
let path = format!("{}/templates/**/*.{{html,txt}}", env!("CARGO_MANIFEST_DIR"));
|
||||
info!(%path, "Loading templates");
|
||||
let tera = Tera::new(&path)?;
|
||||
Ok(Self(Arc::new(tera)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Templates {
|
||||
type Target = Tera;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
@@ -43,7 +59,14 @@ impl CommonContext {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn with_session<'e>(
|
||||
pub fn with_session(self, session: SessionInfo) -> Self {
|
||||
Self {
|
||||
session: Some(session),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load_session<'e>(
|
||||
self,
|
||||
_executor: impl Executor<'e, Database = Postgres>,
|
||||
) -> anyhow::Result<Self> {
|
||||
|
||||
Reference in New Issue
Block a user