wip: move to axum
This commit is contained in:
parent
1c5c9caf2a
commit
a5eef831c7
|
@ -51,6 +51,53 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.5.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e3356844c4d6a6d6467b8da2cffb4a2820be256f50a3a386c9d152bab31043"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9f0c0a60006f2a293d82d571f635042a72edf927539b7685bd62d361963839b"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"mime",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.66"
|
||||
|
@ -245,6 +292,7 @@ version = "0.0.1"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
"axum",
|
||||
"base-62",
|
||||
"handlebars",
|
||||
"jsonwebtoken",
|
||||
|
@ -257,7 +305,6 @@ dependencies = [
|
|||
"tokio",
|
||||
"tokio-postgres",
|
||||
"warp",
|
||||
"warp-embed",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -460,6 +507,12 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range-header"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
|
@ -579,6 +632,12 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.4"
|
||||
|
@ -1257,6 +1316,12 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.6"
|
||||
|
@ -1444,6 +1509,47 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-range-header",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
|
@ -1633,17 +1739,6 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "warp-embed"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b958139e25f097e0ebde85342a3a2dbb728983ca893ba96b7fb8f448337110af"
|
||||
dependencies = [
|
||||
"mime_guess",
|
||||
"rust-embed",
|
||||
"warp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
|
|
@ -8,6 +8,7 @@ edition = "2021"
|
|||
[dependencies]
|
||||
anyhow = "1.0.64"
|
||||
argon2 = "0.4.1"
|
||||
axum = "0.5.16"
|
||||
base-62 = "0.1.1"
|
||||
handlebars = "4.3.3"
|
||||
jsonwebtoken = "8.1.1"
|
||||
|
@ -20,4 +21,3 @@ serde_json = "1.0.85"
|
|||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-postgres = { version = "0.7.7", features = ["with-serde_json-1"] }
|
||||
warp = { version = "0.3.2" }
|
||||
warp-embed = "0.4.0"
|
||||
|
|
|
@ -5,6 +5,7 @@ CREATE TABLE users (
|
|||
username TEXT NOT NULL,
|
||||
host TEXT,
|
||||
display_name TEXT,
|
||||
email TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL
|
||||
);
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ impl Users {
|
|||
|
||||
pub async fn create_user(&self, u: User) -> Result<User, db::DBError> {
|
||||
let row = self.0.lock().await.query_one(
|
||||
"insert into users (id, username, host, display_name, password_hash) values ($1, $2, $3, $4, $5) returning id",
|
||||
&[&sec::new_id(), &u.username, &u.host, &u.display_name, &u.password_hash],
|
||||
"insert into users (id, username, host, display_name, password_hash, email) values ($1, $2, $3, $4, $5, $6) returning id",
|
||||
&[&sec::new_id(), &u.username, &u.host, &u.display_name, &u.password_hash, &u.email],
|
||||
).await?;
|
||||
Ok(User {
|
||||
id: row.get("id"),
|
||||
|
@ -26,6 +26,7 @@ impl Users {
|
|||
host: u.host,
|
||||
display_name: u.display_name,
|
||||
password_hash: u.password_hash,
|
||||
email: u.email,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -73,6 +74,7 @@ pub struct User {
|
|||
pub host: Option<String>,
|
||||
pub display_name: Option<String>,
|
||||
pub password_hash: String,
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
impl From<&Row> for User {
|
||||
|
@ -83,6 +85,7 @@ impl From<&Row> for User {
|
|||
host: row.get("host"),
|
||||
display_name: row.get("display_name"),
|
||||
password_hash: row.get("password_hash"),
|
||||
email: row.get("email"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
11
src/model.rs
11
src/model.rs
|
@ -1,12 +1 @@
|
|||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct Error<'a> {
|
||||
pub error: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Error<'a> {
|
||||
pub fn error(error: &'a str) -> Self {
|
||||
Self { error }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use warp::{
|
||||
http::HeaderValue,
|
||||
hyper::Uri,
|
||||
path::Tail,
|
||||
reply::{Html, Response},
|
||||
Filter, Rejection, Reply,
|
||||
use axum::{
|
||||
body::Full,
|
||||
extract::Path,
|
||||
handler::Handler,
|
||||
http::{header, StatusCode},
|
||||
response::{self, Html, IntoResponse, Redirect, Response},
|
||||
routing, Extension, Form, Json, Router,
|
||||
};
|
||||
use warp::{http::HeaderValue, hyper::Uri, path::Tail, Filter, Rejection, Reply};
|
||||
|
||||
use crate::svc::auth::AuthError;
|
||||
|
||||
use super::{
|
||||
servek::{Server, ServerError},
|
||||
CreateProfileRequest, ErrorTemplate,
|
||||
CreateProfileRequest, LoginRequest, Notification,
|
||||
};
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
|
@ -21,147 +23,190 @@ use rust_embed::RustEmbed;
|
|||
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())
|
||||
}),
|
||||
),
|
||||
async fn index() -> impl IntoResponse {
|
||||
(
|
||||
StatusCode::OK,
|
||||
response::Html(include_str!("../../templates/html/index.html")),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn register_html(&self, router: &Router) -> Router {
|
||||
router
|
||||
.clone()
|
||||
.route("/", routing::get(Self::index))
|
||||
.route("/login", routing::get(Self::login_page))
|
||||
.route(
|
||||
"/signup",
|
||||
routing::get(Self::signup_page).post(Self::create_user),
|
||||
)
|
||||
.route("/static/*file", routing::get(Self::static_handler))
|
||||
.fallback(routing::get(Self::handler_404))
|
||||
}
|
||||
|
||||
async fn handler_404() -> impl IntoResponse {
|
||||
(
|
||||
StatusCode::OK,
|
||||
response::Html(include_str!("../../templates/html/404.html")),
|
||||
)
|
||||
}
|
||||
|
||||
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(
|
||||
// Extension(srv): Extension<Server>,
|
||||
// Form(login): Form<LoginRequest>,
|
||||
// ) -> Result<impl IntoResponse, ServerError> {
|
||||
// if login.username == "" || login.password == "" {
|
||||
// return srv.login_page_with(
|
||||
// "error-partial".to_owned(),
|
||||
// "credentials required".to_owned(),
|
||||
// );
|
||||
// }
|
||||
// let token = srv.auth.login(login.username, login.password).await?;
|
||||
// }
|
||||
|
||||
// async fn login(
|
||||
// srv: Server,
|
||||
// Form(body): Form<HashMap<String, String>>,
|
||||
// ) -> Result<response::Html<String>, ServerError> {
|
||||
// let user = body
|
||||
// .get("username")
|
||||
// .ok_or(ServerError::BadRequest("no username provided".to_owned()))?;
|
||||
// let pass = body
|
||||
// .get("password")
|
||||
// .ok_or(ServerError::BadRequest("no password provided".to_owned()))?;
|
||||
|
||||
// let token = srv
|
||||
// .auth
|
||||
// .login(user.clone(), pass.clone())
|
||||
// .await
|
||||
// .map(|html| response::Html(html));
|
||||
// if let Err(e) = &token {
|
||||
// if let AuthError::InvalidCredentials = e {
|
||||
// return srv.login_with_error("invalid credentials".to_owned());
|
||||
// }
|
||||
// }
|
||||
// Ok(token?)
|
||||
// }
|
||||
|
||||
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 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())
|
||||
// }),
|
||||
// )
|
||||
// }
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
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)
|
||||
},
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,22 @@ mod html;
|
|||
pub mod servek;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct ErrorTemplate {
|
||||
pub error: String,
|
||||
struct Notification {
|
||||
pub message: String,
|
||||
pub tag_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct LoginRequest {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CreateProfileRequest {
|
||||
pub password_hash: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
use core::panic;
|
||||
use std::{convert::Infallible, fmt::Display};
|
||||
use std::{convert::Infallible, fmt::Display, net::SocketAddr};
|
||||
|
||||
use axum::{
|
||||
http::uri::InvalidUri,
|
||||
response::{self, IntoResponse, Response},
|
||||
routing, Extension, Router,
|
||||
};
|
||||
use handlebars::{Handlebars, RenderError};
|
||||
use warp::{
|
||||
hyper::StatusCode,
|
||||
|
@ -28,62 +33,33 @@ impl Server {
|
|||
let mut hb = Handlebars::new();
|
||||
hb.register_template_string("profile", include_str!("../../templates/html/profile.html"))
|
||||
.expect("profile template");
|
||||
hb.register_template_string(
|
||||
"login-error",
|
||||
include_str!("../../templates/html/login-error.html"),
|
||||
)
|
||||
.expect("login-error template");
|
||||
|
||||
hb.register_template_string("login", include_str!("../../templates/html/login.html"))
|
||||
.expect("login template");
|
||||
hb.register_template_string("signup", include_str!("../../templates/html/signup.html"))
|
||||
.expect("login template");
|
||||
hb.register_template_string("error-partial", r#"<h2 class="error">{{message}}</h2>"#)
|
||||
.expect("error-partial");
|
||||
hb.register_template_string("success-partial", r#"<h2 class="success">{{message}}</h2>"#)
|
||||
.expect("success-partial");
|
||||
Self { hb, profiler, auth }
|
||||
}
|
||||
|
||||
pub async fn listen_and_serve(self, port: u16) -> ! {
|
||||
println!("starting server on port {}", port);
|
||||
warp::serve(self.html().await.recover(Self::handle_rejection))
|
||||
.run(([127, 0, 0, 1], port))
|
||||
.await;
|
||||
let router = Router::new();
|
||||
let router = self
|
||||
.register_html(&router)
|
||||
.layer(Extension::<Server>(self.clone()));
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||
println!("listening on {}", addr);
|
||||
|
||||
axum::Server::bind(&addr)
|
||||
.serve(router.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
panic!("server stopped prematurely")
|
||||
}
|
||||
|
||||
async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> {
|
||||
let code;
|
||||
let message;
|
||||
|
||||
if err.is_not_found() {
|
||||
code = StatusCode::NOT_FOUND;
|
||||
message = "not found";
|
||||
} else if let Some(err) = err.find::<ServerError>() {
|
||||
match err {
|
||||
ServerError::Internal(err) => {
|
||||
println!("internal server error: {}", err);
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
message = "internal server error";
|
||||
}
|
||||
ServerError::NotFound => {
|
||||
code = StatusCode::NOT_FOUND;
|
||||
message = "not found";
|
||||
}
|
||||
ServerError::BadRequest(err) => {
|
||||
code = StatusCode::BAD_REQUEST;
|
||||
message = err;
|
||||
}
|
||||
}
|
||||
} else if let Some(err) = err.find::<MethodNotAllowed>() {
|
||||
println!("MethodNotAllowed: {:#?}", err);
|
||||
code = StatusCode::NOT_FOUND;
|
||||
message = "not found";
|
||||
} else {
|
||||
// We should have expected this... Just log and say its a 500
|
||||
println!("FIXME: unhandled rejection: {:?}", err);
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
message = "internal server error"
|
||||
}
|
||||
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&model::Error::error(message)),
|
||||
code,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -93,12 +69,6 @@ pub(super) enum ServerError {
|
|||
BadRequest(String),
|
||||
}
|
||||
|
||||
impl ServerError {
|
||||
pub(super) fn rejection(self) -> Rejection {
|
||||
warp::reject::custom(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Reject for ServerError {}
|
||||
|
||||
impl From<RenderError> for ServerError {
|
||||
|
@ -133,3 +103,13 @@ impl Display for ServerError {
|
|||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for ServerError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
match self {
|
||||
ServerError::Internal(err) => (StatusCode::INTERNAL_SERVER_ERROR, err).into_response(),
|
||||
ServerError::NotFound => (StatusCode::NOT_FOUND, "").into_response(),
|
||||
ServerError::BadRequest(err) => (StatusCode::BAD_REQUEST, err).into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,12 @@ impl Profiler {
|
|||
Ok(User::from(self.db.user(select).await?))
|
||||
}
|
||||
|
||||
pub async fn create_user(&self, username: String, password: String) -> Result<User, UserError> {
|
||||
pub async fn create_user(
|
||||
&self,
|
||||
username: String,
|
||||
password: String,
|
||||
email: String,
|
||||
) -> Result<User, UserError> {
|
||||
let result = self
|
||||
.db
|
||||
.create_user(users::User {
|
||||
|
@ -37,6 +42,7 @@ impl Profiler {
|
|||
host: None,
|
||||
display_name: None,
|
||||
password_hash: sec::hash(password),
|
||||
email,
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
@ -70,12 +76,6 @@ pub enum UserError {
|
|||
Other(String),
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for UserError {
|
||||
fn from(err: anyhow::Error) -> Self {
|
||||
Self::Other(format!("UserError: {}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<db::DBError> for UserError {
|
||||
fn from(err: db::DBError) -> Self {
|
||||
match err {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -14,9 +14,39 @@ input {
|
|||
border-color: rebeccapurple;
|
||||
}
|
||||
|
||||
form>div {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: rgba(255, 0, 0, 0.7);
|
||||
font-weight: bold;
|
||||
border: 5px solid rgba(99, 0, 0, 0.2);
|
||||
background-color: rgba(95, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.success {
|
||||
color: rgba(0, 202, 0, 0.7);
|
||||
font-weight: bold;
|
||||
border: 5px solid rgba(0, 99, 0, 0.2);
|
||||
background-color: rgba(0, 99, 0, 0.5);
|
||||
}
|
||||
|
||||
#central {
|
||||
width: 30%;
|
||||
border: 5px solid rebeccapurple;
|
||||
height: 30vw;
|
||||
padding: 10px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(13, 6, 19, 0.4);
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rebeccapurple;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<body>
|
||||
<h1>hi</h1>
|
||||
<h1><a href="/login">login</a></h1>
|
||||
<h1><a href="/signup">sign up</a></h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>flabk - login error</title>
|
||||
<link rel="stylesheet" href="/static/style/main.css">
|
||||
<style>
|
||||
#login-main {
|
||||
width: 30%;
|
||||
border: 5px solid rebeccapurple;
|
||||
height: 30vw;
|
||||
padding: 10px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(13, 6, 19, 0.4);
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>login</h1>
|
||||
<div id="login-main">
|
||||
<p>Login form</p>
|
||||
<h2 class="error">error: {{error}}</h2>
|
||||
<form id="login" method="post" action="/login">
|
||||
<input type="text" name="username">
|
||||
<input type="password" name="password">
|
||||
<input type="submit" value="Submit" hidden>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -4,29 +4,13 @@
|
|||
<head>
|
||||
<title>flabk - login</title>
|
||||
<link rel="stylesheet" href="/static/style/main.css">
|
||||
<style>
|
||||
#login-main {
|
||||
width: 30%;
|
||||
border: 5px solid rebeccapurple;
|
||||
height: 30vw;
|
||||
padding: 10px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(13, 6, 19, 0.4);
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>login</h1>
|
||||
<div id="login-main">
|
||||
<p>Login form</p>
|
||||
<div id="central">
|
||||
<p>login form</p>
|
||||
{{> (lookup this "tag_name")}}
|
||||
<form id="login" method="post" action="/login">
|
||||
<input type="text" name="username">
|
||||
<input type="password" name="password">
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>logging in</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
window.localStorage.setItem('token', '{{token}}');
|
||||
document.location.href = "/";
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>flabk - signup</title>
|
||||
<link rel="stylesheet" href="/static/style/main.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>signup</h1>
|
||||
<div id="central">
|
||||
<p>signup form</p>
|
||||
{{> (lookup this "tag_name")}}
|
||||
<form id="signup" method="post" action="/signup">
|
||||
<div>
|
||||
<label for="username">username</label>
|
||||
<input type="text" name="username">
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<label for="email">email</label>
|
||||
<input type="email" name="email">
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<label for="password">password</label>
|
||||
<input type="password" name="password">
|
||||
</div>
|
||||
<input type="submit" value="Submit" hidden>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue