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