From c630a3d95fcba117eeeeb03a0c656ef26bea3072 Mon Sep 17 00:00:00 2001 From: mia Date: Fri, 3 May 2024 18:25:03 -0700 Subject: refactor scope access endpoints --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/server/account.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/server/falx.rs | 88 ------------------------------------------ src/server/mod.rs | 11 +----- src/server/panel.rs | 30 --------------- 6 files changed, 108 insertions(+), 129 deletions(-) create mode 100644 src/server/account.rs delete mode 100644 src/server/falx.rs delete mode 100644 src/server/panel.rs diff --git a/Cargo.lock b/Cargo.lock index 1fa23b2..1914f79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -488,7 +488,7 @@ dependencies = [ [[package]] name = "dissociate" -version = "0.2.3" +version = "0.3.0" dependencies = [ "argon2", "axum", diff --git a/Cargo.toml b/Cargo.toml index 794e6d9..0298a01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dissociate" -version = "0.2.3" +version = "0.3.0" edition = "2021" [dependencies] diff --git a/src/server/account.rs b/src/server/account.rs new file mode 100644 index 0000000..b2c294f --- /dev/null +++ b/src/server/account.rs @@ -0,0 +1,104 @@ +use std::time::SystemTime; + +use axum::{ + body::Body, + extract::State, + http::{HeaderMap, StatusCode, Uri}, + response::{IntoResponse, Response}, + routing::get, + Router, +}; +use axum_extra::extract::CookieJar; +use maud::html; +use tap::Pipe; + +use crate::server::{store::Store, Handoffs, Nevermind}; + +use super::{account_auth, render_html, ApiState}; + +pub fn bind(app: Router) -> Router { + app.route("/", get(get_panel)) + .route("/scopes", get(get_scopes)) + .route("/handoff", get(get_handoff)) +} + +#[axum::debug_handler(state = ApiState)] +async fn get_panel(jar: CookieJar, State(store): State) -> Result { + Ok(account_auth(&jar, &store) + .await + .prompt_login()? + .pipe(render_normal_panel)) +} + +fn render_normal_panel(name: String) -> Response { + render_html( + html!(title { "dissociate" }), + html! { + p { "currently logged in as " (name) } + a href="/logout" { button { "log out" } } + }, + ) +} + +#[axum::debug_handler(state = ApiState)] +async fn get_scopes(jar: CookieJar, State(store): State) -> Result { + let name = account_auth(&jar, &store).await.prompt_login()?; + let account = store.get_account(&name).await.prompt_logout()?; + let body = account.scopes.join(" "); + + Ok(Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "text/plain") + .body(body.into()) + .unwrap()) +} + +#[axum::debug_handler(state = ApiState)] +async fn get_handoff( + jar: CookieJar, + State(Handoffs(handoffs)): State, + State(store): State, + headers: HeaderMap, +) -> Response { + let Some(origin_header) = headers.get("Origin") else { + return (StatusCode::BAD_REQUEST, "Missing Origin header").into_response(); + }; + let Some(origin) = origin_header + .to_str() + .ok() + .and_then(|origin| origin.parse::().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 builder = Response::builder() + .header("Access-Control-Allow-Credentials", "true") + .header("Access-Control-Allow-Origin", origin_header); + + if let Some(token) = jar.get("dissociate-token") { + if let Some((_, expires)) = store.check_token(token.value()).await { + let expires_in = expires + .duration_since(SystemTime::now()) + .unwrap_or_default() + .as_secs(); + + return builder + .status(StatusCode::OK) + .body(Body::from(format!( + r#"{{"token":"{}","expiresIn":{}}}"#, + token.value().to_string(), + expires_in, + ))) + .unwrap(); + } + } + + builder + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .unwrap() +} diff --git a/src/server/falx.rs b/src/server/falx.rs deleted file mode 100644 index 9783cef..0000000 --- a/src/server/falx.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::time::SystemTime; - -use axum::{ - body::Body, - 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) -> Router { - 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, -) -> 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, - State(store): State, - headers: HeaderMap, -) -> Response { - let Some(origin_header) = headers.get("Origin") else { - return (StatusCode::BAD_REQUEST, "Missing Origin header").into_response(); - }; - let Some(origin) = origin_header - .to_str() - .ok() - .and_then(|origin| origin.parse::().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 builder = Response::builder() - .header("Access-Control-Allow-Credentials", "true") - .header("Access-Control-Allow-Origin", origin_header); - - if let Some(token) = jar.get("dissociate-token") { - if let Some((_, expires)) = store.check_token(token.value()).await { - let expires_in = expires - .duration_since(SystemTime::now()) - .unwrap_or_default() - .as_secs(); - - return builder - .status(StatusCode::OK) - .body(Body::from(format!( - r#"{{"token":"{}","expiresIn":{}}}"#, - token.value().to_string(), - expires_in, - ))) - .unwrap(); - } - } - - builder - .status(StatusCode::UNAUTHORIZED) - .body(Body::empty()) - .unwrap() -} diff --git a/src/server/mod.rs b/src/server/mod.rs index a583f85..b6dd451 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,8 +1,7 @@ +mod account; mod admin; mod config; -mod falx; mod login; -mod panel; mod store; use std::{future::IntoFuture, path::PathBuf, sync::Arc}; @@ -42,12 +41,10 @@ pub async fn serve() -> eyre::Result<()> { let app = Router::new() .pipe(login::bind) - .pipe(falx::bind) - .pipe(panel::bind) + .pipe(account::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 { @@ -73,16 +70,12 @@ pub async fn serve() -> eyre::Result<()> { struct ApiState { pub store: Store, pub cookie_domain: CookieDomain, - pub web_base: WebBase, pub handoffs: Handoffs, } #[derive(Clone)] struct CookieDomain(Option); -#[derive(Clone)] -struct WebBase(String); - #[derive(Clone)] struct Handoffs(Arc>); diff --git a/src/server/panel.rs b/src/server/panel.rs deleted file mode 100644 index addb0d8..0000000 --- a/src/server/panel.rs +++ /dev/null @@ -1,30 +0,0 @@ -use axum::{extract::State, response::Response, routing::get, Router}; -use axum_extra::extract::CookieJar; -use maud::html; -use tap::Pipe; - -use crate::server::{store::Store, Nevermind}; - -use super::{account_auth, render_html, ApiState}; - -pub fn bind(app: Router) -> Router { - app.route("/", get(get_panel)) -} - -#[axum::debug_handler(state = ApiState)] -async fn get_panel(jar: CookieJar, State(store): State) -> Result { - Ok(account_auth(&jar, &store) - .await - .prompt_login()? - .pipe(render_normal_panel)) -} - -fn render_normal_panel(name: String) -> Response { - render_html( - html!(title { "dissociate" }), - html! { - p { "currently logged in as " (name) } - a href="/logout" { button { "log out" } } - }, - ) -} -- cgit 1.4.1