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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
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]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.66"
|
version = "0.3.66"
|
||||||
|
@ -245,6 +292,7 @@ version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
"axum",
|
||||||
"base-62",
|
"base-62",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
|
@ -257,7 +305,6 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-postgres",
|
"tokio-postgres",
|
||||||
"warp",
|
"warp",
|
||||||
"warp-embed",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -460,6 +507,12 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"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]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -579,6 +632,12 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchit"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "md-5"
|
name = "md-5"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
|
@ -1257,6 +1316,12 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sync_wrapper"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "synstructure"
|
name = "synstructure"
|
||||||
version = "0.12.6"
|
version = "0.12.6"
|
||||||
|
@ -1444,6 +1509,47 @@ dependencies = [
|
||||||
"tracing",
|
"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]]
|
[[package]]
|
||||||
name = "tower-service"
|
name = "tower-service"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -1633,17 +1739,6 @@ dependencies = [
|
||||||
"tracing",
|
"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]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
|
|
@ -8,6 +8,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.64"
|
anyhow = "1.0.64"
|
||||||
argon2 = "0.4.1"
|
argon2 = "0.4.1"
|
||||||
|
axum = "0.5.16"
|
||||||
base-62 = "0.1.1"
|
base-62 = "0.1.1"
|
||||||
handlebars = "4.3.3"
|
handlebars = "4.3.3"
|
||||||
jsonwebtoken = "8.1.1"
|
jsonwebtoken = "8.1.1"
|
||||||
|
@ -20,4 +21,3 @@ serde_json = "1.0.85"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-postgres = { version = "0.7.7", features = ["with-serde_json-1"] }
|
tokio-postgres = { version = "0.7.7", features = ["with-serde_json-1"] }
|
||||||
warp = { version = "0.3.2" }
|
warp = { version = "0.3.2" }
|
||||||
warp-embed = "0.4.0"
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ CREATE TABLE users (
|
||||||
username TEXT NOT NULL,
|
username TEXT NOT NULL,
|
||||||
host TEXT,
|
host TEXT,
|
||||||
display_name TEXT,
|
display_name TEXT,
|
||||||
|
email TEXT NOT NULL,
|
||||||
password_hash 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> {
|
pub async fn create_user(&self, u: User) -> Result<User, db::DBError> {
|
||||||
let row = self.0.lock().await.query_one(
|
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",
|
"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],
|
&[&sec::new_id(), &u.username, &u.host, &u.display_name, &u.password_hash, &u.email],
|
||||||
).await?;
|
).await?;
|
||||||
Ok(User {
|
Ok(User {
|
||||||
id: row.get("id"),
|
id: row.get("id"),
|
||||||
|
@ -26,6 +26,7 @@ impl Users {
|
||||||
host: u.host,
|
host: u.host,
|
||||||
display_name: u.display_name,
|
display_name: u.display_name,
|
||||||
password_hash: u.password_hash,
|
password_hash: u.password_hash,
|
||||||
|
email: u.email,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ pub struct User {
|
||||||
pub host: Option<String>,
|
pub host: Option<String>,
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
pub password_hash: String,
|
pub password_hash: String,
|
||||||
|
pub email: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Row> for User {
|
impl From<&Row> for User {
|
||||||
|
@ -83,6 +85,7 @@ impl From<&Row> for User {
|
||||||
host: row.get("host"),
|
host: row.get("host"),
|
||||||
display_name: row.get("display_name"),
|
display_name: row.get("display_name"),
|
||||||
password_hash: row.get("password_hash"),
|
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 std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
use warp::{
|
use axum::{
|
||||||
http::HeaderValue,
|
body::Full,
|
||||||
hyper::Uri,
|
extract::Path,
|
||||||
path::Tail,
|
handler::Handler,
|
||||||
reply::{Html, Response},
|
http::{header, StatusCode},
|
||||||
Filter, Rejection, Reply,
|
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 crate::svc::auth::AuthError;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
servek::{Server, ServerError},
|
servek::{Server, ServerError},
|
||||||
CreateProfileRequest, ErrorTemplate,
|
CreateProfileRequest, LoginRequest, Notification,
|
||||||
};
|
};
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
|
@ -21,147 +23,190 @@ use rust_embed::RustEmbed;
|
||||||
struct StaticData;
|
struct StaticData;
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
pub(super) async fn html(
|
async fn index() -> impl IntoResponse {
|
||||||
&self,
|
(
|
||||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
StatusCode::OK,
|
||||||
Self::index()
|
response::Html(include_str!("../../templates/html/index.html")),
|
||||||
.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())
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
fn with_server(
|
||||||
srv: Server,
|
srv: Server,
|
||||||
) -> impl Filter<Extract = (Server,), Error = std::convert::Infallible> + Clone {
|
) -> impl Filter<Extract = (Server,), Error = std::convert::Infallible> + Clone {
|
||||||
warp::any().map(move || srv.clone())
|
warp::any().map(move || srv.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn profile(&self) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
// fn profile(&self) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||||
warp::get().and(
|
// warp::get().and(
|
||||||
warp::path!("@" / String)
|
// warp::path!("@" / String)
|
||||||
.and(Self::with_server(self.clone()))
|
// .and(Self::with_server(self.clone()))
|
||||||
.and_then(|username: String, srv: Server| async move {
|
// .and_then(|username: String, srv: Server| async move {
|
||||||
srv.hb
|
// srv.hb
|
||||||
.render(
|
// .render(
|
||||||
"profile",
|
// "profile",
|
||||||
&serde_json::json!(srv
|
// &serde_json::json!(srv
|
||||||
.profiler
|
// .profiler
|
||||||
.profile(username)
|
// .profile(username)
|
||||||
.await
|
// .await
|
||||||
.map_err(|e| ServerError::from(e))?),
|
// .map_err(|e| ServerError::from(e))?),
|
||||||
)
|
// )
|
||||||
.map(|html| warp::reply::html(html))
|
// .map(|html| warp::reply::html(html))
|
||||||
.map_err(|e| ServerError::from(e).rejection())
|
// .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 {
|
async fn signup_page(
|
||||||
warp::post().and(
|
Extension(srv): Extension<Server>,
|
||||||
warp::body::content_length_limit(8192).and(
|
) -> Result<impl IntoResponse, ServerError> {
|
||||||
warp::path!("@" / String)
|
Ok((
|
||||||
.and(Self::with_server(self.clone()))
|
StatusCode::OK,
|
||||||
.and(warp::body::form())
|
response::Html(srv.hb.render("signup", &serde_json::json!(()))?),
|
||||||
.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)
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
pub mod servek;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
struct ErrorTemplate {
|
struct Notification {
|
||||||
pub error: String,
|
pub message: String,
|
||||||
|
pub tag_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct LoginRequest {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct CreateProfileRequest {
|
pub struct CreateProfileRequest {
|
||||||
pub password_hash: String,
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
pub email: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
use core::panic;
|
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 handlebars::{Handlebars, RenderError};
|
||||||
use warp::{
|
use warp::{
|
||||||
hyper::StatusCode,
|
hyper::StatusCode,
|
||||||
|
@ -28,62 +33,33 @@ impl Server {
|
||||||
let mut hb = Handlebars::new();
|
let mut hb = Handlebars::new();
|
||||||
hb.register_template_string("profile", include_str!("../../templates/html/profile.html"))
|
hb.register_template_string("profile", include_str!("../../templates/html/profile.html"))
|
||||||
.expect("profile template");
|
.expect("profile template");
|
||||||
hb.register_template_string(
|
hb.register_template_string("login", include_str!("../../templates/html/login.html"))
|
||||||
"login-error",
|
.expect("login template");
|
||||||
include_str!("../../templates/html/login-error.html"),
|
hb.register_template_string("signup", include_str!("../../templates/html/signup.html"))
|
||||||
)
|
.expect("login template");
|
||||||
.expect("login-error 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 }
|
Self { hb, profiler, auth }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn listen_and_serve(self, port: u16) -> ! {
|
pub async fn listen_and_serve(self, port: u16) -> ! {
|
||||||
println!("starting server on port {}", port);
|
let router = Router::new();
|
||||||
warp::serve(self.html().await.recover(Self::handle_rejection))
|
let router = self
|
||||||
.run(([127, 0, 0, 1], port))
|
.register_html(&router)
|
||||||
.await;
|
.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")
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -93,12 +69,6 @@ pub(super) enum ServerError {
|
||||||
BadRequest(String),
|
BadRequest(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerError {
|
|
||||||
pub(super) fn rejection(self) -> Rejection {
|
|
||||||
warp::reject::custom(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Reject for ServerError {}
|
impl Reject for ServerError {}
|
||||||
|
|
||||||
impl From<RenderError> for ServerError {
|
impl From<RenderError> for ServerError {
|
||||||
|
@ -133,3 +103,13 @@ impl Display for ServerError {
|
||||||
write!(f, "{}", self)
|
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?))
|
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
|
let result = self
|
||||||
.db
|
.db
|
||||||
.create_user(users::User {
|
.create_user(users::User {
|
||||||
|
@ -37,6 +42,7 @@ impl Profiler {
|
||||||
host: None,
|
host: None,
|
||||||
display_name: None,
|
display_name: None,
|
||||||
password_hash: sec::hash(password),
|
password_hash: sec::hash(password),
|
||||||
|
email,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -70,12 +76,6 @@ pub enum UserError {
|
||||||
Other(String),
|
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 {
|
impl From<db::DBError> for UserError {
|
||||||
fn from(err: db::DBError) -> Self {
|
fn from(err: db::DBError) -> Self {
|
||||||
match err {
|
match err {
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -14,9 +14,39 @@ input {
|
||||||
border-color: rebeccapurple;
|
border-color: rebeccapurple;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form>div {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
color: rgba(255, 0, 0, 0.7);
|
color: rgba(255, 0, 0, 0.7);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border: 5px solid rgba(99, 0, 0, 0.2);
|
border: 5px solid rgba(99, 0, 0, 0.2);
|
||||||
background-color: rgba(95, 0, 0, 0.5);
|
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>
|
<body>
|
||||||
<h1>hi</h1>
|
<h1>hi</h1>
|
||||||
<h1><a href="/login">login</a></h1>
|
<h1><a href="/login">login</a></h1>
|
||||||
|
<h1><a href="/signup">sign up</a></h1>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</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>
|
<head>
|
||||||
<title>flabk - login</title>
|
<title>flabk - login</title>
|
||||||
<link rel="stylesheet" href="/static/style/main.css">
|
<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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1>login</h1>
|
<h1>login</h1>
|
||||||
<div id="login-main">
|
<div id="central">
|
||||||
<p>Login form</p>
|
<p>login form</p>
|
||||||
|
{{> (lookup this "tag_name")}}
|
||||||
<form id="login" method="post" action="/login">
|
<form id="login" method="post" action="/login">
|
||||||
<input type="text" name="username">
|
<input type="text" name="username">
|
||||||
<input type="password" name="password">
|
<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