vaguely added login/logout? gunna try killing warp
This commit is contained in:
parent
1d9802530d
commit
1c5c9caf2a
|
@ -23,6 +23,17 @@ version = "1.0.64"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2",
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.57"
|
||||
|
@ -62,7 +73,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f28ebd71b3e708e895b83ec2d35c6e2ef96e34945706bf4d73826354e84f89b2"
|
||||
dependencies = [
|
||||
"failure",
|
||||
"num-bigint",
|
||||
"num-bigint 0.2.6",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
@ -73,12 +84,27 @@ version = "0.13.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388"
|
||||
dependencies = [
|
||||
"digest 0.10.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
|
@ -107,6 +133,12 @@ dependencies = [
|
|||
"safemem",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
|
@ -212,10 +244,13 @@ name = "flabk"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
"base-62",
|
||||
"handlebars",
|
||||
"jsonwebtoken",
|
||||
"mime_guess",
|
||||
"rand",
|
||||
"rand_core",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -496,6 +531,29 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "8.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aa4b4af834c6cfd35d8763d359661b90f2e45d8f750a0849156c7f4671af09c"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"pem",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.132"
|
||||
|
@ -602,6 +660,17 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
|
@ -631,6 +700,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.29.0"
|
||||
|
@ -675,6 +753,26 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4"
|
||||
dependencies = [
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
|
@ -884,6 +982,21 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "6.4.0"
|
||||
|
@ -1068,6 +1181,18 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple_asn1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
|
||||
dependencies = [
|
||||
"num-bigint 0.4.3",
|
||||
"num-traits",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.10"
|
||||
|
@ -1099,6 +1224,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "stringprep"
|
||||
version = "0.1.2"
|
||||
|
@ -1172,6 +1303,24 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"libc",
|
||||
"num_threads",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
|
@ -1404,6 +1553,12 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.3.1"
|
||||
|
@ -1495,6 +1650,70 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
|
@ -7,10 +7,13 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.64"
|
||||
argon2 = "0.4.1"
|
||||
base-62 = "0.1.1"
|
||||
handlebars = "4.3.3"
|
||||
jsonwebtoken = "8.1.1"
|
||||
mime_guess = "2.0.4"
|
||||
rand = "0.8.5"
|
||||
rand_core = { version = "0.6.3", features = ["std"] }
|
||||
rust-embed = "6.4.0"
|
||||
serde = { version = "1.0.144", features = ["derive", "std", "serde_derive"]}
|
||||
serde_json = "1.0.85"
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
CREATE TABLE users (
|
||||
id CHAR(22) NOT NULL PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
host TEXT,
|
||||
display_name TEXT
|
||||
display_name TEXT,
|
||||
password_hash TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX u_username_host ON users (username, host);
|
||||
CREATE UNIQUE INDEX u_username_local ON users (username) WHERE host IS NULL;
|
||||
|
||||
--id UUID NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
CREATE TABLE keys (
|
||||
key TEXT NOT NULL PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use super::users::Users;
|
||||
use super::{keys::Keys, users::Users};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_postgres::{tls::NoTlsStream, Client, Connection, NoTls, Socket};
|
||||
use tokio_postgres::{Client, NoTls};
|
||||
|
||||
const DBERR_UNIQUE: &str = "23505";
|
||||
|
||||
|
@ -30,10 +30,16 @@ impl DB {
|
|||
pub fn users(&self) -> Users {
|
||||
Users::new(self.client.clone())
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> Keys {
|
||||
Keys::new(self.client.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DBError {
|
||||
Duplicate,
|
||||
NotFound,
|
||||
Other(tokio_postgres::Error),
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_postgres::Client;
|
||||
|
||||
use super::db::DBError;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Keys(Arc<Mutex<Client>>);
|
||||
impl Keys {
|
||||
pub fn new(client: Arc<Mutex<Client>>) -> Self {
|
||||
Self(client)
|
||||
}
|
||||
|
||||
pub async fn get_key(&self, key: &str) -> Result<String, DBError> {
|
||||
Ok(self
|
||||
.0
|
||||
.lock()
|
||||
.await
|
||||
.query("select value from keys where key = $1", &[&key])
|
||||
.await?
|
||||
.first()
|
||||
.ok_or_else(|| DBError::NotFound)?
|
||||
.get("value"))
|
||||
}
|
||||
|
||||
pub async fn set_key(&self, key: &str, value: &str) -> Result<(), DBError> {
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.execute(
|
||||
"insert into keys (key, value) values ($1, $2)",
|
||||
&[&key, &value],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
pub mod db;
|
||||
pub mod keys;
|
||||
pub mod users;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use rand::Rng;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_postgres::{Client, Row};
|
||||
|
||||
use crate::sec;
|
||||
|
||||
use super::db;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -14,25 +15,21 @@ impl Users {
|
|||
Self(client)
|
||||
}
|
||||
|
||||
fn new_id() -> String {
|
||||
let bytes = rand::thread_rng().gen::<[u8; 16]>();
|
||||
base_62::encode(&bytes)
|
||||
}
|
||||
|
||||
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) values ($1, $2, $3, $4) returning id",
|
||||
&[&Self::new_id(), &u.username, &u.host, &u.display_name],
|
||||
"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],
|
||||
).await?;
|
||||
Ok(User {
|
||||
id: row.get("id"),
|
||||
username: u.username,
|
||||
host: u.host,
|
||||
display_name: u.display_name,
|
||||
password_hash: u.password_hash,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn user(&self, by: UserSelect) -> Result<Option<User>, anyhow::Error> {
|
||||
pub async fn user(&self, by: UserSelect) -> Result<User, db::DBError> {
|
||||
let where_param: String;
|
||||
let where_clause = match by {
|
||||
UserSelect::ID(id) => {
|
||||
|
@ -54,7 +51,7 @@ impl Users {
|
|||
.await
|
||||
.query(
|
||||
format!(
|
||||
"select id, username, host, display_name from users where {}",
|
||||
"select id, username, host, display_name, password_hash from users where {}",
|
||||
where_clause
|
||||
)
|
||||
.as_str(),
|
||||
|
@ -63,9 +60,9 @@ impl Users {
|
|||
.await?;
|
||||
|
||||
if let Some(row) = rows.first() && rows.len() == 1 {
|
||||
Ok(Some(User::from(row)))
|
||||
Ok(User::from(row))
|
||||
} else {
|
||||
Ok(None)
|
||||
Err(db::DBError::NotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +72,7 @@ pub struct User {
|
|||
pub username: String,
|
||||
pub host: Option<String>,
|
||||
pub display_name: Option<String>,
|
||||
pub password_hash: String,
|
||||
}
|
||||
|
||||
impl From<&Row> for User {
|
||||
|
@ -84,6 +82,7 @@ impl From<&Row> for User {
|
|||
username: row.get("username"),
|
||||
host: row.get("host"),
|
||||
display_name: row.get("display_name"),
|
||||
password_hash: row.get("password_hash"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
use warp::hyper::StatusCode;
|
||||
|
||||
pub(crate) fn html_with_status(body: String, status: StatusCode) -> warp::http::Response<String> {
|
||||
warp::http::Response::builder()
|
||||
.header("Content-Type", "text/html; charset=utf-8")
|
||||
.status(status)
|
||||
.body(body)
|
||||
.expect("failed marshalling html response")
|
||||
}
|
||||
|
||||
pub(crate) fn html(body: String) -> warp::http::Response<String> {
|
||||
html_with_status(body, StatusCode::OK)
|
||||
}
|
||||
|
||||
// pub(crate) fn html(body: String) -> Result<warp::http::Response<String>, warp::http::Error> {
|
||||
// warp::http::Response::builder()
|
||||
// .header("Content-Type", "text/html; charset=utf-8")
|
||||
// .status(StatusCode::OK)
|
||||
// .body(body)
|
||||
// }
|
|
@ -1,12 +1,13 @@
|
|||
mod database;
|
||||
mod model;
|
||||
mod sec;
|
||||
mod servek;
|
||||
mod svc;
|
||||
|
||||
use database::db::DB;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use servek::servek::Server;
|
||||
use svc::profiles::Profiler;
|
||||
use svc::{auth::Auth, profiles::Profiler};
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum ActivityKind {
|
||||
|
@ -43,6 +44,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
)
|
||||
.await?;
|
||||
let profiler = Profiler::new(db.users());
|
||||
Server::new(profiler).listen_and_serve(8008).await;
|
||||
let auth = Auth::new(db.keys(), db.users()).await;
|
||||
Server::new(profiler, auth).listen_and_serve(8008).await;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
use argon2::{
|
||||
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
||||
Argon2,
|
||||
};
|
||||
use rand::Rng;
|
||||
|
||||
pub fn hash(password: String) -> String {
|
||||
let password = password.as_bytes();
|
||||
// Hash password to PHC string ($argon2id$v=19$...)
|
||||
Argon2::default()
|
||||
.hash_password(password, &SaltString::generate(&mut OsRng))
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn compare(password: &str, password_hash: &str) -> bool {
|
||||
let hash = PasswordHash::new(&password_hash).unwrap();
|
||||
Argon2::default()
|
||||
.verify_password(password.as_bytes(), &hash)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn new_id() -> String {
|
||||
let bytes = rand::thread_rng().gen::<[u8; 16]>();
|
||||
base_62::encode(&bytes)
|
||||
}
|
|
@ -1,8 +1,19 @@
|
|||
use std::str::FromStr;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use warp::{http::HeaderValue, hyper::Uri, path::Tail, reply::Response, Filter, Rejection, Reply};
|
||||
use warp::{
|
||||
http::HeaderValue,
|
||||
hyper::Uri,
|
||||
path::Tail,
|
||||
reply::{Html, Response},
|
||||
Filter, Rejection, Reply,
|
||||
};
|
||||
|
||||
use super::servek::{Server, ServerError};
|
||||
use crate::svc::auth::AuthError;
|
||||
|
||||
use super::{
|
||||
servek::{Server, ServerError},
|
||||
CreateProfileRequest, ErrorTemplate,
|
||||
};
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
|
@ -14,8 +25,12 @@ impl Server {
|
|||
&self,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
Self::index()
|
||||
.or(self.profile().await)
|
||||
.or(self.create_profile().await.or(Server::static_files()))
|
||||
.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 {
|
||||
|
@ -24,13 +39,63 @@ impl Server {
|
|||
}))
|
||||
}
|
||||
|
||||
fn handler_404() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::get().and(warp::path::end().map(move || {
|
||||
warp::reply::html(include_str!("../../templates/html/404.html").to_owned())
|
||||
}))
|
||||
}
|
||||
|
||||
fn login_with_error(&self, error: String) -> Result<Html<String>, Rejection> {
|
||||
self.hb
|
||||
.render("login-error", &serde_json::json!(ErrorTemplate { error }))
|
||||
.map(|html| warp::reply::html(html))
|
||||
.map_err(|e| ServerError::from(e).rejection())
|
||||
}
|
||||
|
||||
fn login_page(
|
||||
&self,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::get().and(warp::path::path("login").map(move || {
|
||||
warp::reply::html(include_str!("../../templates/html/login.html").to_owned())
|
||||
}))
|
||||
}
|
||||
|
||||
fn login(&self) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::post().and(
|
||||
warp::body::content_length_limit(8192).and(
|
||||
warp::path::path("login")
|
||||
.and(Self::with_server(self.clone()))
|
||||
.and(warp::body::form())
|
||||
.and_then(|srv: Server, body: HashMap<String, String>| async move {
|
||||
let user = body.get("username").ok_or(
|
||||
ServerError::BadRequest("no username provided".to_owned()).rejection(),
|
||||
)?;
|
||||
let pass = body.get("password").ok_or(
|
||||
ServerError::BadRequest("no password provided".to_owned()).rejection(),
|
||||
)?;
|
||||
let token = srv
|
||||
.auth
|
||||
.login(user.clone(), pass.clone())
|
||||
.await
|
||||
.map(|html| warp::reply::html(html));
|
||||
if let Err(e) = &token {
|
||||
if let AuthError::InvalidCredentials = e {
|
||||
return srv.login_with_error("invalid credentials".to_owned());
|
||||
}
|
||||
}
|
||||
token.map_err(|e| ServerError::from(e).rejection())
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn with_server(
|
||||
srv: Server,
|
||||
) -> impl Filter<Extract = (Server,), Error = std::convert::Infallible> + Clone {
|
||||
warp::any().map(move || srv.clone())
|
||||
}
|
||||
|
||||
async 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::path!("@" / String)
|
||||
.and(Self::with_server(self.clone()))
|
||||
|
@ -45,28 +110,39 @@ impl Server {
|
|||
.map_err(|e| ServerError::from(e))?),
|
||||
)
|
||||
.map(|html| warp::reply::html(html))
|
||||
.map_err(|e| ServerError::from(e).reject_self())
|
||||
.map_err(|e| ServerError::from(e).rejection())
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async fn create_profile(&self) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
fn create_profile(&self) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
warp::post().and(
|
||||
warp::path!("@" / String)
|
||||
.and(Self::with_server(self.clone()))
|
||||
.and_then(|username: String, srv: Server| async move {
|
||||
let user = srv
|
||||
.profiler
|
||||
.create_user(username, None)
|
||||
.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.reject_self()),
|
||||
}
|
||||
}),
|
||||
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()),
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -75,7 +151,7 @@ impl Server {
|
|||
|path: Tail| async move {
|
||||
let asset = match StaticData::get(path.as_str()) {
|
||||
Some(a) => a,
|
||||
None => return Err(ServerError::NotFound.reject_self()),
|
||||
None => return Err(ServerError::NotFound.rejection()),
|
||||
};
|
||||
let mime = mime_guess::from_path(path.as_str()).first_or_octet_stream();
|
||||
|
||||
|
|
|
@ -1,2 +1,19 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod html;
|
||||
pub mod servek;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct ErrorTemplate {
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CreateProfileRequest {
|
||||
pub password_hash: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Login {
|
||||
pub token: String,
|
||||
}
|
||||
|
|
|
@ -9,24 +9,32 @@ use warp::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
database::users::User,
|
||||
model,
|
||||
svc::profiles::{Profiler, UserError},
|
||||
svc::{
|
||||
auth::{Auth, AuthError},
|
||||
profiles::{Profiler, UserError},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Server {
|
||||
pub(super) hb: Handlebars<'static>,
|
||||
pub(super) profiler: Profiler,
|
||||
pub(super) auth: Auth,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new(profiler: Profiler) -> Self {
|
||||
pub fn new(profiler: Profiler, auth: Auth) -> Self {
|
||||
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");
|
||||
|
||||
Self { hb, profiler }
|
||||
Self { hb, profiler, auth }
|
||||
}
|
||||
|
||||
pub async fn listen_and_serve(self, port: u16) -> ! {
|
||||
|
@ -55,9 +63,9 @@ impl Server {
|
|||
code = StatusCode::NOT_FOUND;
|
||||
message = "not found";
|
||||
}
|
||||
ServerError::Duplicate => {
|
||||
ServerError::BadRequest(err) => {
|
||||
code = StatusCode::BAD_REQUEST;
|
||||
message = "duplicate entry exists";
|
||||
message = err;
|
||||
}
|
||||
}
|
||||
} else if let Some(err) = err.find::<MethodNotAllowed>() {
|
||||
|
@ -82,11 +90,11 @@ impl Server {
|
|||
pub(super) enum ServerError {
|
||||
Internal(String),
|
||||
NotFound,
|
||||
Duplicate,
|
||||
BadRequest(String),
|
||||
}
|
||||
|
||||
impl ServerError {
|
||||
pub(super) fn reject_self(self) -> Rejection {
|
||||
pub(super) fn rejection(self) -> Rejection {
|
||||
warp::reject::custom(self)
|
||||
}
|
||||
}
|
||||
|
@ -102,13 +110,24 @@ impl From<RenderError> for ServerError {
|
|||
impl From<UserError> for ServerError {
|
||||
fn from(u: UserError) -> Self {
|
||||
match u {
|
||||
UserError::Duplicate => Self::Duplicate,
|
||||
UserError::Duplicate => Self::BadRequest("duplicate entry exists".to_owned()),
|
||||
UserError::NotFound => Self::NotFound,
|
||||
UserError::Other(o) => Self::Internal(format!("UserError: {}", o)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuthError> for ServerError {
|
||||
fn from(a: AuthError) -> Self {
|
||||
match a {
|
||||
AuthError::InvalidCredentials => {
|
||||
ServerError::BadRequest("invalid credentials".to_owned())
|
||||
}
|
||||
AuthError::ServerError(err) => ServerError::Internal(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ServerError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use jsonwebtoken::{EncodingKey, Header};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
database::{
|
||||
db::DBError,
|
||||
keys::Keys,
|
||||
users::{self, UserSelect, Users},
|
||||
},
|
||||
sec,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Auth {
|
||||
secret: String,
|
||||
users: Users,
|
||||
}
|
||||
|
||||
const KEY_JWT_SECRET: &str = "JWT_SECRET";
|
||||
const SECS_JWT_EXPIRE: u64 = 60 * 60; // 1hr
|
||||
|
||||
impl Auth {
|
||||
pub async fn new(db: Keys, users: Users) -> Self {
|
||||
Self {
|
||||
secret: match db.get_key(KEY_JWT_SECRET).await {
|
||||
Ok(secret) => secret,
|
||||
Err(_) => {
|
||||
// Create new secret and store to db
|
||||
// If that fails, crash the application
|
||||
let secret = sec::new_id();
|
||||
db.set_key(KEY_JWT_SECRET, &secret).await.unwrap();
|
||||
secret
|
||||
}
|
||||
},
|
||||
users,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn login(&self, username: String, password: String) -> Result<String, AuthError> {
|
||||
let user = self.users.user(UserSelect::Username(username)).await?;
|
||||
if !sec::compare(&password, &user.password_hash) {
|
||||
return Err(AuthError::InvalidCredentials);
|
||||
}
|
||||
|
||||
Ok(jsonwebtoken::encode(
|
||||
&Header::default(),
|
||||
&Claims::from(user),
|
||||
&EncodingKey::from_secret("secret".as_ref()),
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
pub sub: String,
|
||||
pub exp: u64,
|
||||
pub iat: u64,
|
||||
}
|
||||
|
||||
impl From<users::User> for Claims {
|
||||
fn from(u: users::User) -> Self {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
Claims {
|
||||
sub: u.id,
|
||||
exp: now + SECS_JWT_EXPIRE,
|
||||
iat: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AuthError {
|
||||
InvalidCredentials,
|
||||
ServerError(String),
|
||||
}
|
||||
|
||||
impl From<DBError> for AuthError {
|
||||
fn from(_: DBError) -> Self {
|
||||
Self::InvalidCredentials
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jsonwebtoken::errors::Error> for AuthError {
|
||||
fn from(e: jsonwebtoken::errors::Error) -> Self {
|
||||
Self::ServerError(e.to_string())
|
||||
}
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
pub mod auth;
|
||||
pub mod profiles;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use serde::Serialize;
|
||||
use warp::{reject::Reject, Rejection};
|
||||
|
||||
use crate::database::{
|
||||
db,
|
||||
users::{self, UserSelect, Users},
|
||||
use crate::{
|
||||
database::{
|
||||
db,
|
||||
users::{self, UserSelect, Users},
|
||||
},
|
||||
sec,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -22,28 +25,19 @@ impl Profiler {
|
|||
} else {
|
||||
UserSelect::Username(username)
|
||||
};
|
||||
match self.db.user(select).await? {
|
||||
Some(user) => Ok(User::from(user)),
|
||||
None => Err(UserError::NotFound),
|
||||
}
|
||||
Ok(User::from(self.db.user(select).await?))
|
||||
}
|
||||
|
||||
pub async fn create_user(
|
||||
&self,
|
||||
username: String,
|
||||
display_name: Option<String>,
|
||||
) -> Result<User, UserError> {
|
||||
pub async fn create_user(&self, username: String, password: String) -> Result<User, UserError> {
|
||||
let result = self
|
||||
.db
|
||||
.create_user(
|
||||
User {
|
||||
id: String::new(),
|
||||
username,
|
||||
display_name,
|
||||
host: None,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.create_user(users::User {
|
||||
id: String::new(),
|
||||
username,
|
||||
host: None,
|
||||
display_name: None,
|
||||
password_hash: sec::hash(password),
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(User::from(result))
|
||||
|
@ -69,17 +63,6 @@ impl From<users::User> for User {
|
|||
}
|
||||
}
|
||||
|
||||
impl Into<users::User> for User {
|
||||
fn into(self) -> users::User {
|
||||
users::User {
|
||||
id: self.id,
|
||||
username: self.username,
|
||||
display_name: self.display_name,
|
||||
host: self.host,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UserError {
|
||||
Duplicate,
|
||||
|
@ -97,6 +80,7 @@ impl From<db::DBError> for UserError {
|
|||
fn from(err: db::DBError) -> Self {
|
||||
match err {
|
||||
db::DBError::Duplicate => Self::Duplicate,
|
||||
db::DBError::NotFound => Self::NotFound,
|
||||
db::DBError::Other(e) => Self::Other(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -6,3 +6,17 @@ body {
|
|||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
input {
|
||||
background-color: black;
|
||||
color: white;
|
||||
border-color: rebeccapurple;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
|
|
@ -3,10 +3,13 @@
|
|||
|
||||
<head>
|
||||
<title>flabk - not found</title>
|
||||
<link rel="stylesheet" href="/static/style/main.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>not found</h1>
|
||||
<h1>404 not found</h1>
|
||||
<br />
|
||||
<h1><a href="/">return</a></h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
<body>
|
||||
<h1>hi</h1>
|
||||
<h1><a href="/login">login</a></h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<!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>
|
|
@ -0,0 +1,39 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<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>
|
||||
<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>
|
Loading…
Reference in New Issue