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