diff options
-rw-r--r-- | Cargo.lock | 26 | ||||
-rw-r--r-- | Cargo.toml | 10 | ||||
-rw-r--r-- | src/server/config.rs | 2 | ||||
-rw-r--r-- | src/server/falx.rs | 61 | ||||
-rw-r--r-- | src/server/mod.rs | 12 | ||||
-rw-r--r-- | src/server/nginx_check.rs | 41 |
6 files changed, 90 insertions, 62 deletions
diff --git a/Cargo.lock b/Cargo.lock index 021b24f..9f0d6fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -488,7 +488,7 @@ dependencies = [ [[package]] name = "dissociate" -version = "0.1.0" +version = "0.2.0" dependencies = [ "argon2", "axum", @@ -1369,18 +1369,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", @@ -1389,9 +1389,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -1586,9 +1586,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -1628,9 +1628,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.10" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", @@ -1649,9 +1649,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ "indexmap", "serde", diff --git a/Cargo.toml b/Cargo.toml index 7f5317e..319d190 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dissociate" -version = "0.1.0" +version = "0.2.0" edition = "2021" [dependencies] @@ -19,8 +19,8 @@ oxide-auth = "0.5.4" oxide-auth-axum = "0.4.0" paste = "1.0.14" rand = "0.8.5" -serde = { version = "1.0.197", features = ["derive"] } -serde_json = "1.0.114" +serde = { version = "1.0.198", features = ["derive"] } +serde_json = "1.0.116" tap = "1.0.1" -tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros", "fs"] } -toml = "0.8.10" +tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros", "fs"] } +toml = "0.8.12" diff --git a/src/server/config.rs b/src/server/config.rs index 4563f34..31d1e1f 100644 --- a/src/server/config.rs +++ b/src/server/config.rs @@ -1,6 +1,7 @@ use std::net::SocketAddr; use std::path::{Path, PathBuf}; +use cursive::reexports::ahash::HashSet; use eyre::Context; use tap::Pipe; @@ -10,6 +11,7 @@ pub struct Config { pub web_base: String, pub cookie_domain: Option<String>, pub admin_socket: PathBuf, + pub handoffs: HashSet<String>, pub data: PathBuf, } diff --git a/src/server/falx.rs b/src/server/falx.rs new file mode 100644 index 0000000..03a8a0b --- /dev/null +++ b/src/server/falx.rs @@ -0,0 +1,61 @@ +use axum::{ + extract::{Path, State}, + http::{HeaderMap, StatusCode, Uri}, + response::{IntoResponse, Response}, + routing::get, + Router, +}; +use axum_extra::extract::CookieJar; + +use crate::server::store::Store; + +use super::{ApiState, Handoffs}; + +pub fn bind(app: Router<ApiState>) -> Router<ApiState> { + app.route("/check/:token/:scope", get(check_)) + .route("/handoff", get(handoff)) +} + +#[axum::debug_handler(state = ApiState)] +async fn check_( + Path((token, scope)): Path<(String, String)>, + State(store): State<Store>, +) -> Response { + let Some(name) = store.check_token(&token).await else { + return StatusCode::UNAUTHORIZED.into_response(); + }; + let Some(account) = store.get_account(&name).await else { + return StatusCode::UNAUTHORIZED.into_response(); + }; + if account.scopes.contains(&scope) { + StatusCode::OK.into_response() + } else { + StatusCode::FORBIDDEN.into_response() + } +} + +#[axum::debug_handler(state = ApiState)] +async fn handoff( + jar: CookieJar, + State(Handoffs(handoffs)): State<Handoffs>, + headers: HeaderMap, +) -> Response { + let Some(origin) = headers.get("Origin") else { + return (StatusCode::BAD_REQUEST, "Missing Origin header").into_response(); + }; + let Some(origin) = origin + .to_str() + .ok() + .and_then(|origin| origin.parse::<Uri>().ok()) + .and_then(|origin| origin.host().map(ToString::to_string)) + else { + return (StatusCode::BAD_REQUEST, "Malformed Origin header").into_response(); + }; + if !handoffs.contains(&origin) { + return (StatusCode::FORBIDDEN, "Origin not registered for handoff").into_response(); + } + let Some(token) = jar.get("dissociate-token") else { + return (StatusCode::UNAUTHORIZED, "Authenticate cookie missing").into_response(); + }; + return (StatusCode::OK, token.value().to_string()).into_response(); +} diff --git a/src/server/mod.rs b/src/server/mod.rs index d9b3beb..81dc519 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,11 +1,11 @@ mod admin; mod config; +mod falx; mod login; -mod nginx_check; mod panel; mod store; -use std::{future::IntoFuture, path::PathBuf}; +use std::{future::IntoFuture, path::PathBuf, sync::Arc}; use axum::{ body::Body, @@ -16,6 +16,7 @@ use axum::{ Router, }; use axum_extra::extract::CookieJar; +use cursive::reexports::ahash::HashSet; use eyre::Context; use maud::{html, PreEscaped}; use tap::Pipe; @@ -41,12 +42,13 @@ pub async fn serve() -> eyre::Result<()> { let app = Router::new() .pipe(login::bind) - .pipe(nginx_check::bind) + .pipe(falx::bind) .pipe(panel::bind) .with_state(ApiState { store, cookie_domain: CookieDomain(config.cookie_domain), web_base: WebBase(config.web_base), + handoffs: Handoffs(Arc::new(config.handoffs)), }) .fallback(get(|| async { render_html( @@ -72,6 +74,7 @@ struct ApiState { pub store: Store, pub cookie_domain: CookieDomain, pub web_base: WebBase, + pub handoffs: Handoffs, } #[derive(Clone)] @@ -80,6 +83,9 @@ struct CookieDomain(Option<String>); #[derive(Clone)] struct WebBase(String); +#[derive(Clone)] +struct Handoffs(Arc<HashSet<String>>); + fn render_html(head: PreEscaped<impl AsRef<str>>, body: PreEscaped<impl AsRef<str>>) -> Response { let html = html! { (PreEscaped("<!doctype html>")) diff --git a/src/server/nginx_check.rs b/src/server/nginx_check.rs deleted file mode 100644 index 7b67f26..0000000 --- a/src/server/nginx_check.rs +++ /dev/null @@ -1,41 +0,0 @@ -// for ngx_http_auth_request_module authentication -// make sure you have cookie_domain set properly -// depends on https://git.mia.jetzt/sysconf/tree/patches/nginx_auth_redirect.patch - -use axum::{ - extract::{Path, State}, - http::StatusCode, - response::{IntoResponse, Redirect, Response}, - routing::get, - Router, -}; -use axum_extra::extract::CookieJar; - -use crate::server::{account_auth, store::Store}; - -use super::{ApiState, WebBase}; - -pub fn bind(app: Router<ApiState>) -> Router<ApiState> { - app.route("/nginx_check/:scope", get(nginx_check)) -} - -#[axum::debug_handler(state = ApiState)] -async fn nginx_check( - jar: CookieJar, - Path(scope): Path<String>, - State(store): State<Store>, - State(WebBase(web_base)): State<WebBase>, -) -> Response { - let nevermind = || Redirect::to(&format!("{web_base}/logout")).into_response(); - let Some(name) = account_auth(&jar, &store).await else { - return nevermind(); - }; - let Some(account) = store.get_account(&name).await else { - return nevermind(); - }; - if account.scopes.contains(&scope) { - StatusCode::OK.into_response() - } else { - StatusCode::FORBIDDEN.into_response() - } -} |