flabk/src/servek/html.rs

278 lines
8.4 KiB
Rust

use std;
use axum::{
body::Full,
extract::Path,
http::{header, HeaderValue, StatusCode},
response::{self, IntoResponse, Response},
routing, Extension, Form, Router,
};
use mime_guess::mime;
use tower_cookies::{Cookie, Cookies};
use crate::svc::auth::{AuthError, Claims};
use super::{
servek::{Server, ServerError},
CreateProfileRequest, LoginRequest, NavType, Notification, Redirect, WithNav,
};
use rust_embed::RustEmbed;
const AUTH_COOKIE_NAME: &str = "flabk_token";
#[derive(RustEmbed)]
#[folder = "static"]
struct StaticData;
impl Server {
pub(super) fn register_html(&self, router: &Router) -> Router {
router
.clone()
.route("/favicon.svg", routing::get(Self::favicon))
.route("/", routing::get(Self::index))
.route("/login", routing::get(Self::login_page).post(Self::login))
.route("/logout", routing::get(Self::logout))
.route(
"/signup",
routing::get(Self::signup_page).post(Self::create_user),
)
.route("/@/:username", routing::get(Self::profile))
.route("/static/*file", routing::get(Self::static_handler))
.fallback(routing::get(Self::handler_404))
}
fn from_cookies(&self, cookies: Cookies) -> Result<WithNav<Option<Claims>>, ServerError> {
const LOGGED_OUT: Result<WithNav<Option<Claims>>, ServerError> = Ok(WithNav {
obj: None,
nav_type: NavType::LoggedOut,
});
if let Some(cookie) = cookies.get(AUTH_COOKIE_NAME) {
let claims = match self.auth.get_claims(cookie.value().to_owned()) {
Ok(claims) => claims,
Err(e) => {
if e.clone().expired() {
cookies.remove(Cookie::new(AUTH_COOKIE_NAME, ""));
return LOGGED_OUT;
} else {
return Err(e.into());
}
}
};
Ok(WithNav::new(Some(claims.clone()), claims.into()))
} else {
LOGGED_OUT
}
}
async fn index(
Extension(srv): Extension<Server>,
cookies: Cookies,
) -> Result<impl IntoResponse, ServerError> {
Ok((
StatusCode::OK,
response::Html(srv.hb.render("index", &srv.from_cookies(cookies)?)?),
))
}
async fn favicon() -> impl IntoResponse {
(
StatusCode::OK,
(
[(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::IMAGE_SVG.as_ref()),
)],
axum::body::boxed(Full::from(
include_bytes!("../../flabk_icon_placeholder.svg").as_ref(),
)),
),
)
}
async fn handler_404(
Extension(srv): Extension<Server>,
cookies: Cookies,
) -> Result<impl IntoResponse, ServerError> {
Ok((
StatusCode::NOT_FOUND,
response::Html(srv.hb.render("err404", &srv.from_cookies(cookies)?)?),
))
}
async fn logout(
Extension(srv): Extension<Server>,
cookies: Cookies,
) -> Result<impl IntoResponse, ServerError> {
cookies.remove(Cookie::new(AUTH_COOKIE_NAME, ""));
Ok((
StatusCode::OK,
response::Html(srv.hb.render(
"redirect",
&Redirect {
location: "/".to_owned(),
},
)?),
))
}
fn login_page_with(
&self,
tag_name: String,
message: String,
) -> Result<response::Html<String>, ServerError> {
Ok(self
.hb
.render(
"login",
&serde_json::json!(Notification { message, tag_name }),
)
.map(|html| response::Html(html))?)
}
async fn login_page(
Extension(srv): Extension<Server>,
) -> Result<impl IntoResponse, ServerError> {
Ok((
StatusCode::OK,
response::Html(srv.hb.render("login", &serde_json::json!(()))?),
))
}
async fn login(
cookies: Cookies,
Extension(srv): Extension<Server>,
Form(login): Form<LoginRequest>,
) -> Result<impl IntoResponse, ServerError> {
if login.username == "" || login.password == "" {
return Ok((
StatusCode::BAD_REQUEST,
srv.login_page_with(
"error-partial".to_owned(),
"credentials required".to_owned(),
)?,
));
}
let token = match srv.auth.login(login.username, login.password).await {
Ok(token) => token,
Err(e) => match e {
AuthError::InvalidCredentials => {
return Ok((
StatusCode::UNAUTHORIZED,
srv.login_page_with(
"error-partial".to_owned(),
"invalid credentials".to_owned(),
)?,
))
}
e => return Err(e.into()),
},
};
if cookies.get(AUTH_COOKIE_NAME).is_some() {
cookies.remove(Cookie::new(AUTH_COOKIE_NAME, ""));
}
cookies.add(Cookie::new(AUTH_COOKIE_NAME, token));
Ok((
StatusCode::OK,
response::Html(srv.hb.render(
"redirect",
&Redirect {
location: "/".to_owned(),
},
)?),
))
}
async fn profile(
cookies: Cookies,
Extension(srv): Extension<Server>,
Path(username): Path<String>,
) -> Result<impl IntoResponse, ServerError> {
Ok((
StatusCode::OK,
response::Html(srv.hb.render(
"profile",
&WithNav::new(
srv.profiler.profile(username).await?,
srv.from_cookies(cookies)?.nav_type,
),
)?),
))
}
async fn create_user(
Extension(srv): Extension<Server>,
Form(body): Form<CreateProfileRequest>,
) -> Result<impl IntoResponse, ServerError> {
if body.username == "" {
return srv.signup_page_with("error-partial".to_owned(), "empty username".to_owned());
}
if body.password == "" {
return srv.signup_page_with("error-partial".to_owned(), "empty password".to_owned());
}
if body.email == "" {
return srv.signup_page_with("error-partial".to_owned(), "empty email".to_owned());
}
srv.profiler
.create_user(body.username, body.password, body.email)
.await?;
srv.signup_page_with("success-partial".to_owned(), "signup successful".to_owned())
}
async fn signup_page(
Extension(srv): Extension<Server>,
) -> Result<impl IntoResponse, ServerError> {
Ok((
StatusCode::OK,
response::Html(srv.hb.render("signup", &serde_json::json!(()))?),
))
}
fn signup_page_with(
&self,
tag_name: String,
message: String,
) -> Result<impl IntoResponse, ServerError> {
Ok((
StatusCode::OK,
response::Html(self.hb.render(
"signup",
&serde_json::json!(Notification { message, tag_name }),
)?),
))
}
async fn static_handler(Path(mut path): Path<String>) -> impl IntoResponse {
path.remove(0);
println!("getting path: {}", path);
StaticFile(path)
}
}
pub struct StaticFile<T>(pub T);
impl<T> IntoResponse for StaticFile<T>
where
T: Into<String>,
{
fn into_response(self) -> axum::response::Response {
let path = self.0.into();
match StaticData::get(path.as_str()) {
Some(content) => {
let body = axum::body::boxed(Full::from(content.data));
let mime = mime_guess::from_path(path).first_or_octet_stream();
Response::builder()
.header(header::CONTENT_TYPE, mime.as_ref())
.body(body)
.unwrap()
}
None => Response::builder()
.status(StatusCode::NOT_FOUND)
.body(axum::body::boxed(Full::from("404")))
.unwrap(),
}
}
}