#[macro_use] extern crate lazy_static; #[macro_use] extern crate log; use std::env; use std::path::PathBuf; use warp::Filter; lazy_static! { static ref CERTS: Certs = Certs{ private: env::var("CERTS_PRIVATE").expect("CERTS_PRIVATE environment variable is required. Example: tls/key.rsa").into(), public: env::var("CERTS_PUBLIC").expect("CERTS_PUBLIC environment variable is required. Example: tls/cert.pem").into(), }; } struct Certs { pub private: String, pub public: String, } // Example (default): ROOTDIR=/var/lib/www lazy_static! { static ref PATH: PathBuf = match env::var("ROOTDIR") { Ok(root_dir) => root_dir.into(), Err(_) => "/var/lib/www".to_string().into(), }; } #[tokio::main] async fn main() { use handlers::{ CAS, DOMAIN }; pretty_env_logger::init(); let api = filters::api(&PATH, &DOMAIN, &CAS); let routes = api.with(warp::log("mdepot")); warp::serve(routes) .tls() .cert_path(&CERTS.public) .key_path(&CERTS.private) .run(([127, 0, 0, 1], 8443)) .await; } mod filters { use std::path::PathBuf; use warp::Filter; use super::handlers::{ self, Cas, Domain }; pub fn api(path: &PathBuf, domain: &Domain, cas: &Cas) -> impl Filter + Clone { warp::get().and(health().or(logout(cas, domain))) } fn health() -> impl Filter + Clone { warp::path!("health") .map(|| "Up") } fn logout(cas: &Cas, domain: &Domain) -> impl Filter + Clone { warp::path!("logout") .and(with_cas(cas.clone())) .and(with_domain(domain.clone())) .map(|cas, domain| handlers::cas_logout(cas, domain)) } fn with_path(path: String) -> impl Filter + Clone { warp::any().map(move || path.clone()) } fn with_cas(cas: Cas) -> impl Filter + Clone { warp::any().map(move || cas.clone()) } fn with_domain(domain: Domain) -> impl Filter + Clone { warp::any().map(move || domain.clone()) } } // API handlers terminate filter chains mod handlers { use std::env; //use std::convert::Infallible; use warp::http::{ Uri, StatusCode, header, uri::PathAndQuery }; use warp::Filter; // Example: DOMAIN=https://mdepot.example.com lazy_static! { pub static ref DOMAIN: Domain = (&env::var("DOMAIN").expect("DOMAIN environment variable is required. Example: https://mediadepot.its.txstate.edu")).parse::().unwrap() as Domain; } // Example: CAS=https://login.example.com/cas lazy_static! { pub static ref CAS: Cas = Cas(&env::var("CAS").expect("CASURL environment variable is required. Example: https://login.example.com/cas").parse::().unwrap()); } pub type Domain = Uri; #[derive(Debug, Clone)] pub struct Cas(Uri); pub struct CasQueryTicket { ticket: String, } pub fn cas_logout(Cas(cas): Cas, domain: Domain) -> impl warp::Reply { warp::any().map(|| { let mut logout = cas.into_parts(); logout.path_and_query = match logout.path_and_query { Some(path_and_query) => Some((path_and_query.path().to_string() + "/logout?url=" + &format!("{}", domain)).parse::().unwrap()), None => Some(("/?url=" + format!("{}", domain)).parse::()), }; let redirect = Uri::from_parts(logout).unwrap(); warp::reply::with_header( warp::reply::with_header( StatusCode::TEMPORARY_REDIRECT, header::LOCATION, redirect.to_str(), ), header::SET_COOKIE, &format!("__HOST-id=; Domain={}; Path=/; Max-Age=0; Secure; HttpOnly; SameSite=Lax", domain), ) }) } }