From 7563905e66cbd956c481833fe628a85f812fb2dd Mon Sep 17 00:00:00 2001 From: Simon Bruder Date: Sat, 13 Jul 2024 13:41:23 +0200 Subject: [PATCH] Add authentication --- Cargo.lock | 169 +++++++++++++++++++++++++++++++ Cargo.toml | 6 +- src/frontend/auth.rs | 75 ++++++++++++++ src/frontend/item.rs | 12 ++- src/frontend/item_class.rs | 15 ++- src/frontend/mod.rs | 14 ++- src/frontend/templates/mod.rs | 19 +++- src/lib.rs | 1 + src/main.rs | 36 ++++++- src/middleware/error_handlers.rs | 19 ++++ src/middleware/force_identity.rs | 60 +++++++++++ src/middleware/mod.rs | 8 ++ 12 files changed, 422 insertions(+), 12 deletions(-) create mode 100644 src/frontend/auth.rs create mode 100644 src/middleware/error_handlers.rs create mode 100644 src/middleware/force_identity.rs create mode 100644 src/middleware/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 8cbad17..cea7297 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,22 @@ dependencies = [ "zstd", ] +[[package]] +name = "actix-identity" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c99b7a5614b72a78f04aa2021e5370fc1aef2475fffeffc0c1266b99007062" +dependencies = [ + "actix-service", + "actix-session", + "actix-utils", + "actix-web", + "derive_more", + "futures-core", + "serde", + "tracing", +] + [[package]] name = "actix-macros" version = "0.2.4" @@ -144,6 +160,22 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "actix-session" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b671404ec72194d8af58c2bdaf51e3c477a0595056bd5010148405870dda8df2" +dependencies = [ + "actix-service", + "actix-utils", + "actix-web", + "anyhow", + "derive_more", + "serde", + "serde_json", + "tracing", +] + [[package]] name = "actix-utils" version = "3.0.1" @@ -222,6 +254,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.11" @@ -314,6 +381,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + [[package]] name = "arrayvec" version = "0.7.4" @@ -356,6 +429,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3826fb6e98ec72c0c0db8c9a40af4932d16793021027469857e84c1b50a1e8f" +[[package]] +name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + [[package]] name = "base64" version = "0.21.7" @@ -474,6 +553,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "colorchoice" version = "1.0.1" @@ -498,7 +587,14 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ + "aes-gcm", + "base64 0.20.0", + "hkdf", + "hmac", "percent-encoding", + "rand", + "sha2", + "subtle", "time", "version_check", ] @@ -558,9 +654,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "datamatrix" version = "0.3.1" @@ -789,6 +895,17 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + [[package]] name = "futures-sink" version = "0.3.30" @@ -809,6 +926,7 @@ checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -838,6 +956,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.29.0" @@ -979,6 +1107,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -1029,10 +1166,14 @@ name = "li7y" version = "0.0.0" dependencies = [ "actix-files", + "actix-identity", + "actix-session", "actix-web", "barcoders", + "base64 0.22.1", "datamatrix", "env_logger", + "futures-util", "log", "maud", "mime", @@ -1296,6 +1437,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "owned_ttf_parser" version = "0.19.0" @@ -1388,6 +1535,18 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "pom" version = "3.4.0" @@ -2201,6 +2360,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "url" version = "2.5.2" diff --git a/Cargo.toml b/Cargo.toml index e28526a..82336c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,14 @@ license = "AGPL-3.0-or-later" [dependencies] actix-files = "0.6.6" -actix-web = "4.8.0" +actix-identity = "0.7.1" +actix-session = { version = "0.9.0", features = ["cookie-session"] } +actix-web = { version = "4.8.0", features = ["cookies"] } barcoders = { version = "2.0.0", default-features = false, features = ["std"] } +base64 = "0.22.1" datamatrix = "0.3.1" env_logger = "0.11.3" +futures-util = "0.3.30" log = "0.4.21" maud = { version = "0.26.0", features = ["actix-web"] } mime = "0.3.17" diff --git a/src/frontend/auth.rs b/src/frontend/auth.rs new file mode 100644 index 0000000..0f5bed2 --- /dev/null +++ b/src/frontend/auth.rs @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use actix_identity::Identity; +use actix_web::{error, get, post, web, HttpMessage, HttpRequest, HttpResponse, Responder}; +use maud::html; +use serde::Deserialize; + +use super::templates; + +pub fn config(cfg: &mut web::ServiceConfig) { + cfg.service(login_form).service(login).service(logout); +} + +#[get("/login")] +async fn login_form(req: HttpRequest, user: Option) -> HttpResponse { + if user.is_some() { + return web::Redirect::to("/") + .see_other() + .respond_to(&req) + .map_into_boxed_body(); + } + + templates::base( + templates::TemplateConfig { + path: "/login", + title: Some("Login"), + page_title: Some(Box::new("Login")), + user, + ..Default::default() + }, + html! { + form .w-25.mb-4 method="POST" { + div .mb-3 { + label .form-label for="password" { "Password" }; + input .form-control type="password" id="password" name="password"; + } + button .btn.btn-primary type="submit" { "Login" } + } + }, + ) + .respond_to(&req) + .map_into_boxed_body() +} + +#[derive(Deserialize)] +struct LoginForm { + password: String, +} + +#[post("/login")] +async fn login( + req: HttpRequest, + form: web::Form, +) -> Result { + // Very basic authentication for now (only password, hardcoded in environment variable) + if form.password + == std::env::var("SUPERUSER_PASSWORD") + .map_err(|_| error::ErrorInternalServerError("login disabled (no password set)"))? + { + Identity::login(&req.extensions(), "superuser".into()) + .map_err(error::ErrorInternalServerError)?; + Ok(web::Redirect::to("/".to_owned()).see_other()) + } else { + Ok(web::Redirect::to("/login".to_owned()).see_other()) + } +} + +#[post("/logout")] +async fn logout(user: Identity) -> impl Responder { + user.logout(); + + web::Redirect::to("/login".to_owned()).see_other() +} diff --git a/src/frontend/item.rs b/src/frontend/item.rs index dce2af7..a78026d 100644 --- a/src/frontend/item.rs +++ b/src/frontend/item.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; +use actix_identity::Identity; use actix_web::{error, get, post, web, Responder}; use maud::html; use sqlx::PgPool; @@ -26,6 +27,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { async fn show_item( pool: web::Data, path: web::Path, + user: Identity, ) -> actix_web::Result { let id = path.into_inner(); @@ -66,6 +68,7 @@ async fn show_item( name: "Edit".to_string(), }), ], + user: Some(user), ..Default::default() }, html! { @@ -108,7 +111,7 @@ async fn show_item( } #[get("/items")] -async fn list_items(pool: web::Data) -> actix_web::Result { +async fn list_items(pool: web::Data, user: Identity) -> actix_web::Result { let item_list = manage::item::get_all(&pool) .await .map_err(error::ErrorInternalServerError)?; @@ -150,6 +153,7 @@ async fn list_items(pool: web::Data) -> actix_web::Result) -> actix_web::Result, form: web::Query, + user: Identity, ) -> actix_web::Result { let datalist_items = datalist::items(&pool) .await @@ -199,6 +204,7 @@ async fn add_item( title: Some("Add Item"), page_title: Some(Box::new("Add Item")), datalists: vec![&datalist_items, &datalist_item_classes], + user: Some(user), ..Default::default() }, html! { @@ -238,6 +244,7 @@ async fn add_item( async fn add_item_post( data: web::Form, pool: web::Data, + _user: Identity, ) -> actix_web::Result { let item = manage::item::add(&pool, data.into_inner()) .await @@ -249,6 +256,7 @@ async fn add_item_post( async fn edit_item( pool: web::Data, path: web::Path, + user: Identity, ) -> actix_web::Result { let id = path.into_inner(); @@ -278,6 +286,7 @@ async fn edit_item( title: Some(&title), page_title: Some(Box::new(item_name.clone())), datalists: vec![&datalist_items, &datalist_item_classes], + user: Some(user), ..Default::default() }, html! { @@ -329,6 +338,7 @@ async fn edit_item_post( pool: web::Data, path: web::Path, data: web::Form, + _user: Identity, ) -> actix_web::Result { let id = path.into_inner(); diff --git a/src/frontend/item_class.rs b/src/frontend/item_class.rs index b3c5ff4..3d8b286 100644 --- a/src/frontend/item_class.rs +++ b/src/frontend/item_class.rs @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later +use actix_identity::Identity; use actix_web::{error, get, post, web, Responder}; use maud::html; use sqlx::PgPool; @@ -24,6 +25,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { async fn show_item_class( pool: web::Data, path: web::Path, + user: Identity, ) -> actix_web::Result { let id = path.into_inner(); @@ -70,6 +72,7 @@ async fn show_item_class( title: Some(&title), page_title: Some(Box::new(item_class.name.clone())), page_actions, + user: Some(user), ..Default::default() }, html! { @@ -106,7 +109,10 @@ async fn show_item_class( } #[get("/item-classes")] -async fn list_item_classes(pool: web::Data) -> actix_web::Result { +async fn list_item_classes( + pool: web::Data, + user: Identity, +) -> actix_web::Result { let item_classes_ids = sqlx::query_scalar!("SELECT id FROM item_classes ORDER BY created_at") .fetch_all(pool.as_ref()) .await @@ -127,6 +133,7 @@ async fn list_item_classes(pool: web::Data) -> actix_web::Result) -> actix_web::Result, form: web::Query, + user: Identity, ) -> actix_web::Result { let datalist_item_classes = datalist::item_classes(&pool) .await @@ -173,6 +181,7 @@ async fn add_item_class( title: Some("Add Item Class"), page_title: Some(Box::new("Add Item Class")), datalists: vec![&datalist_item_classes], + user: Some(user), ..Default::default() }, html! { @@ -205,6 +214,7 @@ async fn add_item_class( async fn add_item_class_post( data: web::Form, pool: web::Data, + _user: Identity, ) -> actix_web::Result { let item = manage::item_class::add(&pool, data.into_inner()) .await @@ -216,6 +226,7 @@ async fn add_item_class_post( async fn edit_item_class( pool: web::Data, path: web::Path, + user: Identity, ) -> actix_web::Result { let id = path.into_inner(); @@ -236,6 +247,7 @@ async fn edit_item_class( title: Some(&title), page_title: Some(Box::new(item_class.name.clone())), datalists: vec![&datalist_item_classes], + user: Some(user), ..Default::default() }, html! { @@ -278,6 +290,7 @@ async fn edit_item_class_post( pool: web::Data, path: web::Path, data: web::Form, + _user: Identity, ) -> actix_web::Result { let id = path.into_inner(); diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index fa85a71..bfec59e 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -2,10 +2,12 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later +mod auth; mod item; mod item_class; mod templates; +use actix_identity::Identity; use actix_web::{error, get, web, Responder}; use maud::html; use serde::Deserialize; @@ -18,13 +20,20 @@ use crate::models::EntityType; pub fn config(cfg: &mut web::ServiceConfig) { cfg.service(index) .service(jump) + .configure(auth::config) .configure(item::config) .configure(item_class::config); } #[get("/")] -async fn index() -> impl Responder { - templates::base(templates::TemplateConfig::default(), html! {}) +async fn index(user: Identity) -> impl Responder { + templates::base( + templates::TemplateConfig { + user: Some(user), + ..Default::default() + }, + html! {}, + ) } #[derive(Deserialize)] @@ -36,6 +45,7 @@ struct JumpData { async fn jump( pool: web::Data, data: web::Query, + _user: Identity, // this endpoint leaks information about the existence of items ) -> Result { let mut id = data.id.clone(); diff --git a/src/frontend/templates/mod.rs b/src/frontend/templates/mod.rs index f866468..0b211e6 100644 --- a/src/frontend/templates/mod.rs +++ b/src/frontend/templates/mod.rs @@ -6,6 +6,7 @@ pub mod datalist; pub mod forms; pub mod helpers; +use actix_identity::Identity; use maud::{html, Markup, Render, DOCTYPE}; use datalist::Datalist; @@ -19,7 +20,7 @@ const NAVBAR_ITEMS: &[(&str, &str)] = &[ ("/item-classes", "Item Classes"), ]; -fn navbar(path: &str) -> Markup { +fn navbar(config: &TemplateConfig) -> Markup { html! { nav .navbar.navbar-expand-lg.bg-body-secondary { div .container { @@ -32,13 +33,21 @@ fn navbar(path: &str) -> Markup { div .collapse.navbar-collapse #navbar-expander { ul .navbar-nav.me-md-3.mb-2.mb-md-0 { @for (target, name) in NAVBAR_ITEMS { - li .nav-item { a .nav-link .active[path == *target] href=(target) { (name) } } + li .nav-item { a .nav-link .active[config.path == *target] href=(target) { (name) } } } } form .d-flex.flex-fill role="search" action="/jump" { input .form-control.me-2 type="search" name="id" placeholder="Jump to ID"; } + + @if config.user.is_some() { + form action="/logout" method="POST" { + button .btn.btn-outline-secondary type="submit" { "Logout" } + } + } @else { + a .btn.btn-primary href="/login" { "Login" } + } } } } @@ -63,6 +72,7 @@ pub struct TemplateConfig<'a> { pub extra_css: Vec>, pub extra_js: Vec>, pub datalists: Vec<&'a Datalist>, + pub user: Option, } impl Default for TemplateConfig<'_> { @@ -75,6 +85,7 @@ impl Default for TemplateConfig<'_> { extra_css: Vec::new(), extra_js: Vec::new(), datalists: Vec::new(), + user: None, } } } @@ -95,13 +106,13 @@ pub fn base(config: TemplateConfig, content: Markup) -> Markup { meta name="viewport" content="width=device-width, initial-scale=1"; (Css::File("/static/vendor/bootstrap.min.css")) - @for css in config.extra_css { + @for css in &config.extra_css { (css) } } body .d-flex.flex-column.h-100 { - (navbar(config.path)) + (navbar(&config)) main .container.my-4 { div .d-flex.justify-content-between.mb-3 { diff --git a/src/lib.rs b/src/lib.rs index ff840c2..78bda4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,4 +6,5 @@ pub mod api; pub mod frontend; pub mod label; pub mod manage; +pub mod middleware; pub mod models; diff --git a/src/main.rs b/src/main.rs index 0c5b4b3..9dafa5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,13 +4,30 @@ use std::env; -use actix_web::{web, App, HttpServer}; -use log::{debug, info}; +use actix_identity::IdentityMiddleware; +use actix_session::{storage::CookieSessionStore, SessionMiddleware}; +use actix_web::middleware::ErrorHandlers; +use actix_web::{cookie::Key, http::StatusCode, web, App, HttpServer}; +use base64::prelude::{Engine as _, BASE64_STANDARD}; +use log::{debug, info, warn}; #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + // generate a secret key with head -c 64 /dev/urandom | base64 -w0 + let secret_key = match env::var("SECRET_KEY") { + Ok(encoded) => Key::from( + &BASE64_STANDARD + .decode(encoded) + .expect("failed to decode base64 in SECRET_KEY"), + ), + Err(_) => { + warn!("SECRET_KEY was not specified, using randomly generated key"); + Key::generate() + } + }; + let pool: sqlx::PgPool = sqlx::Pool::::connect( &env::var("DATABASE_URL").expect("DATABASE_URL must be set"), ) @@ -34,9 +51,22 @@ async fn main() -> std::io::Result<()> { HttpServer::new(move || { App::new() .app_data(web::Data::new(pool.clone())) - .service(web::scope("/api/v1").configure(li7y::api::v1::config)) + .service( + web::scope("/api/v1") + .wrap(li7y::middleware::ForceIdentity) + .configure(li7y::api::v1::config), + ) .service(actix_files::Files::new("/static", &static_root)) .configure(li7y::frontend::config) + .wrap(ErrorHandlers::new().handler( + StatusCode::UNAUTHORIZED, + li7y::middleware::error_handlers::redirect_to_login, + )) + .wrap(IdentityMiddleware::default()) + .wrap(SessionMiddleware::new( + CookieSessionStore::default(), + secret_key.clone(), + )) }) .bind((address, port))? .run() diff --git a/src/middleware/error_handlers.rs b/src/middleware/error_handlers.rs new file mode 100644 index 0000000..52c6bbe --- /dev/null +++ b/src/middleware/error_handlers.rs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use actix_web::middleware::ErrorHandlerResponse; +use actix_web::{dev::ServiceResponse, error, HttpResponse}; + +pub fn redirect_to_login( + res: ServiceResponse, +) -> Result, error::Error> { + Ok(ErrorHandlerResponse::Response( + res.into_response( + HttpResponse::SeeOther() + .insert_header(("Location", "/login")) + .finish() + .map_into_right_body(), + ), + )) +} diff --git a/src/middleware/force_identity.rs b/src/middleware/force_identity.rs new file mode 100644 index 0000000..58aec86 --- /dev/null +++ b/src/middleware/force_identity.rs @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use std::future::{ready, Ready}; + +use actix_identity::Identity; +use actix_web::{ + dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, + Error, +}; +use futures_util::future::LocalBoxFuture; + +pub struct ForceIdentity; + +impl Transform for ForceIdentity +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = ForceIdentityMiddleware; + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ready(Ok(ForceIdentityMiddleware { service })) + } +} + +pub struct ForceIdentityMiddleware { + service: S, +} + +impl Service for ForceIdentityMiddleware +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Response = ServiceResponse; + type Error = Error; + type Future = LocalBoxFuture<'static, Result>; + + forward_ready!(service); + + fn call(&self, mut req: ServiceRequest) -> Self::Future { + let user = req.extract::(); + let fut = self.service.call(req); + + Box::pin(async move { + let res = fut.await?; + let _ = user.await?; + + Ok(res) + }) + } +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs new file mode 100644 index 0000000..1fa9fc8 --- /dev/null +++ b/src/middleware/mod.rs @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +pub mod error_handlers; +mod force_identity; + +pub use force_identity::ForceIdentity;