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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7"
|
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]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.57"
|
version = "0.1.57"
|
||||||
|
@ -62,7 +73,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f28ebd71b3e708e895b83ec2d35c6e2ef96e34945706bf4d73826354e84f89b2"
|
checksum = "f28ebd71b3e708e895b83ec2d35c6e2ef96e34945706bf4d73826354e84f89b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"failure",
|
"failure",
|
||||||
"num-bigint",
|
"num-bigint 0.2.6",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
@ -73,12 +84,27 @@ version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64ct"
|
||||||
|
version = "1.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
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]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -107,6 +133,12 @@ dependencies = [
|
||||||
"safemem",
|
"safemem",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.4.3"
|
version = "1.4.3"
|
||||||
|
@ -212,10 +244,13 @@ name = "flabk"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"argon2",
|
||||||
"base-62",
|
"base-62",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
|
"jsonwebtoken",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"rand",
|
"rand",
|
||||||
|
"rand_core",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -496,6 +531,29 @@ version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
|
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]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.132"
|
version = "0.2.132"
|
||||||
|
@ -602,6 +660,17 @@ dependencies = [
|
||||||
"num-traits",
|
"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]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
|
@ -631,6 +700,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
|
@ -675,6 +753,26 @@ dependencies = [
|
||||||
"windows-sys",
|
"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]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
@ -884,6 +982,21 @@ dependencies = [
|
||||||
"winapi",
|
"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]]
|
[[package]]
|
||||||
name = "rust-embed"
|
name = "rust-embed"
|
||||||
version = "6.4.0"
|
version = "6.4.0"
|
||||||
|
@ -1068,6 +1181,18 @@ dependencies = [
|
||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "siphasher"
|
name = "siphasher"
|
||||||
version = "0.3.10"
|
version = "0.3.10"
|
||||||
|
@ -1099,6 +1224,12 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stringprep"
|
name = "stringprep"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -1172,6 +1303,24 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
@ -1404,6 +1553,12 @@ version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.3.1"
|
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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
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]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
|
@ -7,10 +7,13 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.64"
|
anyhow = "1.0.64"
|
||||||
|
argon2 = "0.4.1"
|
||||||
base-62 = "0.1.1"
|
base-62 = "0.1.1"
|
||||||
handlebars = "4.3.3"
|
handlebars = "4.3.3"
|
||||||
|
jsonwebtoken = "8.1.1"
|
||||||
mime_guess = "2.0.4"
|
mime_guess = "2.0.4"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
rand_core = { version = "0.6.3", features = ["std"] }
|
||||||
rust-embed = "6.4.0"
|
rust-embed = "6.4.0"
|
||||||
serde = { version = "1.0.144", features = ["derive", "std", "serde_derive"]}
|
serde = { version = "1.0.144", features = ["derive", "std", "serde_derive"]}
|
||||||
serde_json = "1.0.85"
|
serde_json = "1.0.85"
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||||
|
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id CHAR(22) NOT NULL PRIMARY KEY,
|
id CHAR(22) NOT NULL PRIMARY KEY,
|
||||||
username TEXT NOT NULL,
|
username TEXT NOT NULL,
|
||||||
host TEXT,
|
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_host ON users (username, host);
|
||||||
CREATE UNIQUE INDEX u_username_local ON users (username) WHERE host IS NULL;
|
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 std::sync::Arc;
|
||||||
|
|
||||||
use super::users::Users;
|
use super::{keys::Keys, users::Users};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio_postgres::{tls::NoTlsStream, Client, Connection, NoTls, Socket};
|
use tokio_postgres::{Client, NoTls};
|
||||||
|
|
||||||
const DBERR_UNIQUE: &str = "23505";
|
const DBERR_UNIQUE: &str = "23505";
|
||||||
|
|
||||||
|
@ -30,10 +30,16 @@ impl DB {
|
||||||
pub fn users(&self) -> Users {
|
pub fn users(&self) -> Users {
|
||||||
Users::new(self.client.clone())
|
Users::new(self.client.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn keys(&self) -> Keys {
|
||||||
|
Keys::new(self.client.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum DBError {
|
pub enum DBError {
|
||||||
Duplicate,
|
Duplicate,
|
||||||
|
NotFound,
|
||||||
Other(tokio_postgres::Error),
|
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 db;
|
||||||
|
pub mod keys;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rand::Rng;
|
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio_postgres::{Client, Row};
|
use tokio_postgres::{Client, Row};
|
||||||
|
|
||||||
|
use crate::sec;
|
||||||
|
|
||||||
use super::db;
|
use super::db;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -14,25 +15,21 @@ impl Users {
|
||||||
Self(client)
|
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> {
|
pub async fn create_user(&self, u: User) -> Result<User, db::DBError> {
|
||||||
let row = self.0.lock().await.query_one(
|
let row = self.0.lock().await.query_one(
|
||||||
"insert into users (id, username, host, display_name) values ($1, $2, $3, $4) returning id",
|
"insert into users (id, username, host, display_name, password_hash) values ($1, $2, $3, $4, $5) returning id",
|
||||||
&[&Self::new_id(), &u.username, &u.host, &u.display_name],
|
&[&sec::new_id(), &u.username, &u.host, &u.display_name, &u.password_hash],
|
||||||
).await?;
|
).await?;
|
||||||
Ok(User {
|
Ok(User {
|
||||||
id: row.get("id"),
|
id: row.get("id"),
|
||||||
username: u.username,
|
username: u.username,
|
||||||
host: u.host,
|
host: u.host,
|
||||||
display_name: u.display_name,
|
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_param: String;
|
||||||
let where_clause = match by {
|
let where_clause = match by {
|
||||||
UserSelect::ID(id) => {
|
UserSelect::ID(id) => {
|
||||||
|
@ -54,7 +51,7 @@ impl Users {
|
||||||
.await
|
.await
|
||||||
.query(
|
.query(
|
||||||
format!(
|
format!(
|
||||||
"select id, username, host, display_name from users where {}",
|
"select id, username, host, display_name, password_hash from users where {}",
|
||||||
where_clause
|
where_clause
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
|
@ -63,9 +60,9 @@ impl Users {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(row) = rows.first() && rows.len() == 1 {
|
if let Some(row) = rows.first() && rows.len() == 1 {
|
||||||
Ok(Some(User::from(row)))
|
Ok(User::from(row))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Err(db::DBError::NotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,6 +72,7 @@ pub struct User {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub host: Option<String>,
|
pub host: Option<String>,
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
|
pub password_hash: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Row> for User {
|
impl From<&Row> for User {
|
||||||
|
@ -84,6 +82,7 @@ impl From<&Row> for User {
|
||||||
username: row.get("username"),
|
username: row.get("username"),
|
||||||
host: row.get("host"),
|
host: row.get("host"),
|
||||||
display_name: row.get("display_name"),
|
display_name: row.get("display_name"),
|
||||||
|
password_hash: row.get("password_hash"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 database;
|
||||||
mod model;
|
mod model;
|
||||||
|
mod sec;
|
||||||
mod servek;
|
mod servek;
|
||||||
mod svc;
|
mod svc;
|
||||||
|
|
||||||
use database::db::DB;
|
use database::db::DB;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use servek::servek::Server;
|
use servek::servek::Server;
|
||||||
use svc::profiles::Profiler;
|
use svc::{auth::Auth, profiles::Profiler};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
pub enum ActivityKind {
|
pub enum ActivityKind {
|
||||||
|
@ -43,6 +44,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let profiler = Profiler::new(db.users());
|
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(())
|
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;
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
|
@ -14,8 +25,12 @@ impl Server {
|
||||||
&self,
|
&self,
|
||||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||||
Self::index()
|
Self::index()
|
||||||
.or(self.profile().await)
|
.or(self.profile())
|
||||||
.or(self.create_profile().await.or(Server::static_files()))
|
.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 {
|
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(
|
fn with_server(
|
||||||
srv: Server,
|
srv: Server,
|
||||||
) -> impl Filter<Extract = (Server,), Error = std::convert::Infallible> + Clone {
|
) -> impl Filter<Extract = (Server,), Error = std::convert::Infallible> + Clone {
|
||||||
warp::any().map(move || srv.clone())
|
warp::any().map(move || srv.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
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::get().and(
|
||||||
warp::path!("@" / String)
|
warp::path!("@" / String)
|
||||||
.and(Self::with_server(self.clone()))
|
.and(Self::with_server(self.clone()))
|
||||||
|
@ -45,28 +110,39 @@ impl Server {
|
||||||
.map_err(|e| ServerError::from(e))?),
|
.map_err(|e| ServerError::from(e))?),
|
||||||
)
|
)
|
||||||
.map(|html| warp::reply::html(html))
|
.map(|html| warp::reply::html(html))
|
||||||
.map_err(|e| ServerError::from(e).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::post().and(
|
||||||
|
warp::body::content_length_limit(8192).and(
|
||||||
warp::path!("@" / String)
|
warp::path!("@" / String)
|
||||||
.and(Self::with_server(self.clone()))
|
.and(Self::with_server(self.clone()))
|
||||||
.and_then(|username: String, srv: Server| async move {
|
.and(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
|
let user = srv
|
||||||
.profiler
|
.profiler
|
||||||
.create_user(username, None)
|
.create_user(username, body.password_hash)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ServerError::from(e));
|
.map_err(|e| ServerError::from(e));
|
||||||
match user {
|
match user {
|
||||||
Ok(u) => Ok(warp::redirect(
|
Ok(u) => Ok(warp::redirect(
|
||||||
Uri::from_str(format!("/@/{}", u.username).as_str()).unwrap(),
|
Uri::from_str(format!("/@/{}", u.username).as_str()).unwrap(),
|
||||||
)),
|
)),
|
||||||
Err(e) => Err(e.reject_self()),
|
Err(e) => Err(e.rejection()),
|
||||||
}
|
}
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +151,7 @@ impl Server {
|
||||||
|path: Tail| async move {
|
|path: Tail| async move {
|
||||||
let asset = match StaticData::get(path.as_str()) {
|
let asset = match StaticData::get(path.as_str()) {
|
||||||
Some(a) => a,
|
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();
|
let mime = mime_guess::from_path(path.as_str()).first_or_octet_stream();
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,19 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
mod html;
|
mod html;
|
||||||
pub mod servek;
|
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::{
|
use crate::{
|
||||||
database::users::User,
|
|
||||||
model,
|
model,
|
||||||
svc::profiles::{Profiler, UserError},
|
svc::{
|
||||||
|
auth::{Auth, AuthError},
|
||||||
|
profiles::{Profiler, UserError},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
pub(super) hb: Handlebars<'static>,
|
pub(super) hb: Handlebars<'static>,
|
||||||
pub(super) profiler: Profiler,
|
pub(super) profiler: Profiler,
|
||||||
|
pub(super) auth: Auth,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
pub fn new(profiler: Profiler) -> Self {
|
pub fn new(profiler: Profiler, auth: Auth) -> Self {
|
||||||
let mut hb = Handlebars::new();
|
let mut hb = Handlebars::new();
|
||||||
hb.register_template_string("profile", include_str!("../../templates/html/profile.html"))
|
hb.register_template_string("profile", include_str!("../../templates/html/profile.html"))
|
||||||
.expect("profile template");
|
.expect("profile template");
|
||||||
|
hb.register_template_string(
|
||||||
|
"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) -> ! {
|
pub async fn listen_and_serve(self, port: u16) -> ! {
|
||||||
|
@ -55,9 +63,9 @@ impl Server {
|
||||||
code = StatusCode::NOT_FOUND;
|
code = StatusCode::NOT_FOUND;
|
||||||
message = "not found";
|
message = "not found";
|
||||||
}
|
}
|
||||||
ServerError::Duplicate => {
|
ServerError::BadRequest(err) => {
|
||||||
code = StatusCode::BAD_REQUEST;
|
code = StatusCode::BAD_REQUEST;
|
||||||
message = "duplicate entry exists";
|
message = err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(err) = err.find::<MethodNotAllowed>() {
|
} else if let Some(err) = err.find::<MethodNotAllowed>() {
|
||||||
|
@ -82,11 +90,11 @@ impl Server {
|
||||||
pub(super) enum ServerError {
|
pub(super) enum ServerError {
|
||||||
Internal(String),
|
Internal(String),
|
||||||
NotFound,
|
NotFound,
|
||||||
Duplicate,
|
BadRequest(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerError {
|
impl ServerError {
|
||||||
pub(super) fn reject_self(self) -> Rejection {
|
pub(super) fn rejection(self) -> Rejection {
|
||||||
warp::reject::custom(self)
|
warp::reject::custom(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,13 +110,24 @@ impl From<RenderError> for ServerError {
|
||||||
impl From<UserError> for ServerError {
|
impl From<UserError> for ServerError {
|
||||||
fn from(u: UserError) -> Self {
|
fn from(u: UserError) -> Self {
|
||||||
match u {
|
match u {
|
||||||
UserError::Duplicate => Self::Duplicate,
|
UserError::Duplicate => Self::BadRequest("duplicate entry exists".to_owned()),
|
||||||
UserError::NotFound => Self::NotFound,
|
UserError::NotFound => Self::NotFound,
|
||||||
UserError::Other(o) => Self::Internal(format!("UserError: {}", o)),
|
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 {
|
impl Display for ServerError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self)
|
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;
|
pub mod profiles;
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use warp::{reject::Reject, Rejection};
|
use warp::{reject::Reject, Rejection};
|
||||||
|
|
||||||
use crate::database::{
|
use crate::{
|
||||||
|
database::{
|
||||||
db,
|
db,
|
||||||
users::{self, UserSelect, Users},
|
users::{self, UserSelect, Users},
|
||||||
|
},
|
||||||
|
sec,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -22,28 +25,19 @@ impl Profiler {
|
||||||
} else {
|
} else {
|
||||||
UserSelect::Username(username)
|
UserSelect::Username(username)
|
||||||
};
|
};
|
||||||
match self.db.user(select).await? {
|
Ok(User::from(self.db.user(select).await?))
|
||||||
Some(user) => Ok(User::from(user)),
|
|
||||||
None => Err(UserError::NotFound),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_user(
|
pub async fn create_user(&self, username: String, password: String) -> Result<User, UserError> {
|
||||||
&self,
|
|
||||||
username: String,
|
|
||||||
display_name: Option<String>,
|
|
||||||
) -> Result<User, UserError> {
|
|
||||||
let result = self
|
let result = self
|
||||||
.db
|
.db
|
||||||
.create_user(
|
.create_user(users::User {
|
||||||
User {
|
|
||||||
id: String::new(),
|
id: String::new(),
|
||||||
username,
|
username,
|
||||||
display_name,
|
|
||||||
host: None,
|
host: None,
|
||||||
}
|
display_name: None,
|
||||||
.into(),
|
password_hash: sec::hash(password),
|
||||||
)
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(User::from(result))
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum UserError {
|
pub enum UserError {
|
||||||
Duplicate,
|
Duplicate,
|
||||||
|
@ -97,6 +80,7 @@ impl From<db::DBError> for UserError {
|
||||||
fn from(err: db::DBError) -> Self {
|
fn from(err: db::DBError) -> Self {
|
||||||
match err {
|
match err {
|
||||||
db::DBError::Duplicate => Self::Duplicate,
|
db::DBError::Duplicate => Self::Duplicate,
|
||||||
|
db::DBError::NotFound => Self::NotFound,
|
||||||
db::DBError::Other(e) => Self::Other(e.to_string()),
|
db::DBError::Other(e) => Self::Other(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -6,3 +6,17 @@ body {
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
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>
|
<head>
|
||||||
<title>flabk - not found</title>
|
<title>flabk - not found</title>
|
||||||
|
<link rel="stylesheet" href="/static/style/main.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1>not found</h1>
|
<h1>404 not found</h1>
|
||||||
|
<br />
|
||||||
|
<h1><a href="/">return</a></h1>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1>hi</h1>
|
<h1>hi</h1>
|
||||||
|
<h1><a href="/login">login</a></h1>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</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