flabk/src/servek/html.rs

168 lines
6.4 KiB
Rust

use std::{collections::HashMap, str::FromStr};
use warp::{
http::HeaderValue,
hyper::Uri,
path::Tail,
reply::{Html, Response},
Filter, Rejection, Reply,
};
use crate::svc::auth::AuthError;
use super::{
servek::{Server, ServerError},
CreateProfileRequest, ErrorTemplate,
};
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
#[folder = "static"]
struct StaticData;
impl Server {
pub(super) async fn html(
&self,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
Self::index()
.or(self.profile())
.or(self.create_profile())
.or(Server::static_files())
.or(self.login_page())
.or(self.login())
.or(Self::handler_404())
}
fn index() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::get().and(warp::path::end().map(move || {
warp::reply::html(include_str!("../../templates/html/index.html").to_owned())
}))
}
fn handler_404() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::get().and(warp::path::end().map(move || {
warp::reply::html(include_str!("../../templates/html/404.html").to_owned())
}))
}
fn login_with_error(&self, error: String) -> Result<Html<String>, Rejection> {
self.hb
.render("login-error", &serde_json::json!(ErrorTemplate { error }))
.map(|html| warp::reply::html(html))
.map_err(|e| ServerError::from(e).rejection())
}
fn login_page(
&self,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::get().and(warp::path::path("login").map(move || {
warp::reply::html(include_str!("../../templates/html/login.html").to_owned())
}))
}
fn login(&self) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::post().and(
warp::body::content_length_limit(8192).and(
warp::path::path("login")
.and(Self::with_server(self.clone()))
.and(warp::body::form())
.and_then(|srv: Server, body: HashMap<String, String>| async move {
let user = body.get("username").ok_or(
ServerError::BadRequest("no username provided".to_owned()).rejection(),
)?;
let pass = body.get("password").ok_or(
ServerError::BadRequest("no password provided".to_owned()).rejection(),
)?;
let token = srv
.auth
.login(user.clone(), pass.clone())
.await
.map(|html| warp::reply::html(html));
if let Err(e) = &token {
if let AuthError::InvalidCredentials = e {
return srv.login_with_error("invalid credentials".to_owned());
}
}
token.map_err(|e| ServerError::from(e).rejection())
}),
),
)
}
fn with_server(
srv: Server,
) -> impl Filter<Extract = (Server,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || srv.clone())
}
fn profile(&self) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::get().and(
warp::path!("@" / String)
.and(Self::with_server(self.clone()))
.and_then(|username: String, srv: Server| async move {
srv.hb
.render(
"profile",
&serde_json::json!(srv
.profiler
.profile(username)
.await
.map_err(|e| ServerError::from(e))?),
)
.map(|html| warp::reply::html(html))
.map_err(|e| ServerError::from(e).rejection())
}),
)
}
fn create_profile(&self) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::post().and(
warp::body::content_length_limit(8192).and(
warp::path!("@" / String)
.and(Self::with_server(self.clone()))
.and(warp::body::form())
.and_then(
|username: String, srv: Server, body: CreateProfileRequest| async move {
if body.password_hash.len() == 0 {
return Err(ServerError::BadRequest(
"cannot have an empty password".to_owned(),
)
.rejection());
}
let user = srv
.profiler
.create_user(username, body.password_hash)
.await
.map_err(|e| ServerError::from(e));
match user {
Ok(u) => Ok(warp::redirect(
Uri::from_str(format!("/@/{}", u.username).as_str()).unwrap(),
)),
Err(e) => Err(e.rejection()),
}
},
),
),
)
}
fn static_files() -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::get().and(warp::path("static").and(warp::path::tail()).and_then(
|path: Tail| async move {
let asset = match StaticData::get(path.as_str()) {
Some(a) => a,
None => return Err(ServerError::NotFound.rejection()),
};
let mime = mime_guess::from_path(path.as_str()).first_or_octet_stream();
let mut res = Response::new(asset.data.into());
res.headers_mut().insert(
"Content-Type",
HeaderValue::from_str(mime.as_ref()).unwrap(),
);
Ok(res)
},
))
}
}