Compare commits
6 Commits
00f76d4f4b
...
09df915296
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | 09df915296 | |
Savanni D'Gerinel | 529f22f49e | |
Savanni D'Gerinel | 1b161435df | |
Savanni D'Gerinel | f3940111c4 | |
Savanni D'Gerinel | 0209f72a9b | |
Savanni D'Gerinel | 2b1d010540 |
|
@ -4,3 +4,6 @@ node_modules
|
||||||
dist
|
dist
|
||||||
result
|
result
|
||||||
*.tgz
|
*.tgz
|
||||||
|
file-service/*.sqlite
|
||||||
|
file-service/*.sqlite-shm
|
||||||
|
file-service/*.sqlite-wal
|
||||||
|
|
|
@ -74,6 +74,54 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.75"
|
version = "1.0.75"
|
||||||
|
@ -301,7 +349,7 @@ dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde 1.0.188",
|
"serde 1.0.188",
|
||||||
"time",
|
"time 0.1.45",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
@ -338,12 +386,52 @@ dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"strsim",
|
"strsim 0.8.0",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"vec_map",
|
"vec_map",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim 0.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.29",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cloudabi"
|
name = "cloudabi"
|
||||||
version = "0.0.3"
|
version = "0.0.3"
|
||||||
|
@ -359,6 +447,12 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "config"
|
name = "config"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -384,6 +478,16 @@ version = "0.9.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24"
|
||||||
|
dependencies = [
|
||||||
|
"time 0.3.29",
|
||||||
|
"version_check 0.9.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cool_asserts"
|
name = "cool_asserts"
|
||||||
version = "2.0.3"
|
version = "2.0.3"
|
||||||
|
@ -578,6 +682,12 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
@ -754,9 +864,12 @@ dependencies = [
|
||||||
name = "file-service"
|
name = "file-service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
"build_html",
|
"build_html",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"clap 4.4.6",
|
||||||
|
"cookie",
|
||||||
"cool_asserts",
|
"cool_asserts",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hex-string",
|
"hex-string",
|
||||||
|
@ -1674,7 +1787,7 @@ dependencies = [
|
||||||
"log 0.3.9",
|
"log 0.3.9",
|
||||||
"mime 0.2.6",
|
"mime 0.2.6",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"time",
|
"time 0.1.45",
|
||||||
"traitobject",
|
"traitobject",
|
||||||
"typeable",
|
"typeable",
|
||||||
"unicase 1.4.2",
|
"unicase 1.4.2",
|
||||||
|
@ -2108,7 +2221,7 @@ checksum = "6c9172cb4c2f6c52117e25570983edcbb322f130b1031ae5d5d6b1abe7eeb493"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iron",
|
"iron",
|
||||||
"log 0.3.9",
|
"log 0.3.9",
|
||||||
"time",
|
"time 0.1.45",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2447,7 +2560,7 @@ name = "orizentic"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap 2.34.0",
|
||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
"serde 1.0.188",
|
"serde 1.0.188",
|
||||||
|
@ -3712,6 +3825,12 @@ version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -3853,6 +3972,34 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"serde 1.0.188",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
|
||||||
|
dependencies = [
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -4234,6 +4381,12 @@ version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
|
@ -6,10 +6,31 @@ edition = "2018"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
auth-cli = [ "clap" ]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "file_service"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "file-service"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "auth-cli"
|
||||||
|
path = "src/bin/cli.rs"
|
||||||
|
required-features = [ "auth-cli" ]
|
||||||
|
|
||||||
|
[target.auth-cli.dependencies]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
base64ct = { version = "1", features = [ "alloc" ] }
|
||||||
build_html = { version = "2" }
|
build_html = { version = "2" }
|
||||||
bytes = { version = "1" }
|
bytes = { version = "1" }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
clap = { version = "4", features = [ "derive" ], optional = true }
|
||||||
|
cookie = { version = "0.17" }
|
||||||
futures-util = { version = "0.3" }
|
futures-util = { version = "0.3" }
|
||||||
hex-string = "0.1.0"
|
hex-string = "0.1.0"
|
||||||
http = { version = "0.2" }
|
http = { version = "0.2" }
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- Add migration script here
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
token TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
|
token TEXT NOT NULL,
|
||||||
|
user_id INTEGER,
|
||||||
|
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||||
|
);
|
|
@ -0,0 +1,44 @@
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
use file_service::{AuthDB, Username};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
enum Commands {
|
||||||
|
AddUser { username: String },
|
||||||
|
DeleteUser { username: String },
|
||||||
|
ListUsers,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
struct Args {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
let authdb = AuthDB::new(PathBuf::from(&std::env::var("AUTHDB").unwrap()))
|
||||||
|
.await
|
||||||
|
.expect("to be able to open the database");
|
||||||
|
|
||||||
|
match args.command {
|
||||||
|
Commands::AddUser { username } => {
|
||||||
|
match authdb.add_user(Username::from(username.clone())).await {
|
||||||
|
Ok(token) => {
|
||||||
|
println!(
|
||||||
|
"User {} created. Auth token: {}",
|
||||||
|
username,
|
||||||
|
token.to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
println!("Could not create user {}", username);
|
||||||
|
println!("\tError: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Commands::DeleteUser { username } => {}
|
||||||
|
Commands::ListUsers => {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,18 @@
|
||||||
use build_html::Html;
|
use build_html::Html;
|
||||||
use http::{Error, StatusCode};
|
use http::{Error, StatusCode};
|
||||||
use std::{collections::HashMap, future::Future};
|
use std::collections::HashMap;
|
||||||
use warp::http::Response;
|
use warp::http::Response;
|
||||||
|
|
||||||
use crate::{pages, App, AuthToken, FileHandle, FileId, FileInfo, ReadFileError, SessionToken};
|
use crate::{pages, App, AuthToken, FileId, FileInfo, ReadFileError, SessionToken};
|
||||||
|
|
||||||
pub async fn handle_index(
|
pub async fn handle_index(
|
||||||
app: App,
|
app: App,
|
||||||
token: Option<SessionToken>,
|
token: Option<SessionToken>,
|
||||||
) -> Result<Response<String>, Error> {
|
) -> Result<Response<String>, Error> {
|
||||||
match token {
|
match token {
|
||||||
Some(token) => match app.auth_session(token).await {
|
Some(token) => match app.validate_session(token).await {
|
||||||
Ok(_) => render_gallery_page(app).await,
|
Ok(_) => render_gallery_page(app).await,
|
||||||
Err(err) => render_auth_page(Some(format!("authentication refused: {:?}", err))),
|
Err(err) => render_auth_page(Some(format!("session expired: {:?}", err))),
|
||||||
},
|
},
|
||||||
None => render_auth_page(None),
|
None => render_auth_page(None),
|
||||||
}
|
}
|
||||||
|
@ -76,10 +76,21 @@ pub async fn handle_auth(
|
||||||
) -> Result<http::Response<String>, Error> {
|
) -> Result<http::Response<String>, Error> {
|
||||||
match form.get("token") {
|
match form.get("token") {
|
||||||
Some(token) => match app
|
Some(token) => match app
|
||||||
.auth_token(AuthToken::from(AuthToken::from(token.clone())))
|
.authenticate(AuthToken::from(AuthToken::from(token.clone())))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_session_token) => render_gallery_page(app).await,
|
Ok(Some(session_token)) => Response::builder()
|
||||||
|
.header("location", "/")
|
||||||
|
.header(
|
||||||
|
"set-cookie",
|
||||||
|
format!(
|
||||||
|
"session={}; Secure; HttpOnly; SameSite=Strict",
|
||||||
|
session_token.to_string()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.status(StatusCode::SEE_OTHER)
|
||||||
|
.body("".to_owned()),
|
||||||
|
Ok(None) => render_auth_page(Some(format!("no user found"))),
|
||||||
Err(_) => render_auth_page(Some(format!("invalid auth token"))),
|
Err(_) => render_auth_page(Some(format!("invalid auth token"))),
|
||||||
},
|
},
|
||||||
None => render_auth_page(Some(format!("no token available"))),
|
None => render_auth_page(Some(format!("no token available"))),
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
mod store;
|
||||||
|
|
||||||
|
pub use store::{
|
||||||
|
AuthDB, AuthError, AuthToken, FileHandle, FileId, FileInfo, ReadFileError, SessionToken, Store,
|
||||||
|
Username, WriteFileError,
|
||||||
|
};
|
|
@ -6,27 +6,28 @@ use http::status::StatusCode;
|
||||||
// use mustache::{compile_path, Template};
|
// use mustache::{compile_path, Template};
|
||||||
// use orizentic::{Permissions, ResourceName, Secret};
|
// use orizentic::{Permissions, ResourceName, Secret};
|
||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
|
use cookie::Cookie;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::{HashMap, HashSet},
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
io::Read,
|
io::Read,
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Arc, RwLock},
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use store::Username;
|
use tokio::sync::RwLock;
|
||||||
use warp::{filters::multipart::Part, Filter, Rejection};
|
use warp::{filters::multipart::Part, Filter, Rejection};
|
||||||
|
|
||||||
mod handlers;
|
mod handlers;
|
||||||
mod html;
|
mod html;
|
||||||
mod pages;
|
mod pages;
|
||||||
mod store;
|
|
||||||
|
|
||||||
pub use handlers::handle_index;
|
pub use file_service::{
|
||||||
pub use store::{
|
AuthDB, AuthError, AuthToken, FileHandle, FileId, FileInfo, ReadFileError, SessionToken, Store,
|
||||||
App, AuthDB, AuthToken, FileHandle, FileId, FileInfo, ReadFileError, SessionToken, Store,
|
Username, WriteFileError,
|
||||||
};
|
};
|
||||||
|
pub use handlers::handle_index;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
async fn authenticate_user(app: App, auth_token: String) -> Result<Username, warp::Rejection> {
|
async fn authenticate_user(app: App, auth_token: String) -> Result<Username, warp::Rejection> {
|
||||||
|
@ -112,6 +113,48 @@ async fn handle_upload(
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct App {
|
||||||
|
authdb: Arc<RwLock<AuthDB>>,
|
||||||
|
store: Arc<RwLock<Store>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn new(authdb: AuthDB, store: Store) -> Self {
|
||||||
|
Self {
|
||||||
|
authdb: Arc::new(RwLock::new(authdb)),
|
||||||
|
store: Arc::new(RwLock::new(store)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn authenticate(&self, token: AuthToken) -> Result<Option<SessionToken>, AuthError> {
|
||||||
|
self.authdb.read().await.authenticate(token).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn validate_session(
|
||||||
|
&self,
|
||||||
|
token: SessionToken,
|
||||||
|
) -> Result<Option<Username>, AuthError> {
|
||||||
|
self.authdb.read().await.validate_session(token).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_files(&self) -> Result<HashSet<FileId>, ReadFileError> {
|
||||||
|
self.store.read().await.list_files()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_file(&self, id: &FileId) -> Result<FileHandle, ReadFileError> {
|
||||||
|
self.store.read().await.get_file(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_file(
|
||||||
|
&self,
|
||||||
|
filename: String,
|
||||||
|
content: Vec<u8>,
|
||||||
|
) -> Result<FileHandle, WriteFileError> {
|
||||||
|
self.store.write().await.add_file(filename, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn with_app(app: App) -> impl Filter<Extract = (App,), Error = Infallible> + Clone {
|
fn with_app(app: App) -> impl Filter<Extract = (App,), Error = Infallible> + Clone {
|
||||||
warp::any().map(move || app.clone())
|
warp::any().map(move || app.clone())
|
||||||
}
|
}
|
||||||
|
@ -120,9 +163,23 @@ fn maybe_with_session() -> impl Filter<Extract = (Option<SessionToken>,), Error
|
||||||
{
|
{
|
||||||
warp::any()
|
warp::any()
|
||||||
.and(warp::header::optional::<String>("cookie"))
|
.and(warp::header::optional::<String>("cookie"))
|
||||||
.map(|cookies| {
|
.map(|cookies| match cookies {
|
||||||
println!("cookies retrieved: {:?}", cookies);
|
Some(cookies) => {
|
||||||
None
|
let c = Cookie::split_parse(cookies)
|
||||||
|
.collect::<Result<Vec<Cookie>, cookie::ParseError>>();
|
||||||
|
match c {
|
||||||
|
Ok(cookies) => {
|
||||||
|
for c in cookies {
|
||||||
|
if c.name() == "session" {
|
||||||
|
return Some(SessionToken::from(c.value()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +193,9 @@ fn with_session() -> impl Filter<Extract = (SessionToken,), Error = Rejection> +
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
let authdb = AuthDB::new(PathBuf::from(":memory:")).await.unwrap();
|
let authdb = AuthDB::new(PathBuf::from(&std::env::var("AUTHDB").unwrap()))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let store = Store::new(PathBuf::from(&std::env::var("FILE_SHARE_DIR").unwrap()));
|
let store = Store::new(PathBuf::from(&std::env::var("FILE_SHARE_DIR").unwrap()));
|
||||||
|
|
||||||
let app = App::new(authdb, store);
|
let app = App::new(authdb, store);
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use crate::{
|
use crate::html::*;
|
||||||
html::*,
|
|
||||||
store::{FileHandle, FileId, ReadFileError},
|
|
||||||
};
|
|
||||||
use build_html::{self, Container, ContainerType, Html, HtmlContainer};
|
use build_html::{self, Container, ContainerType, Html, HtmlContainer};
|
||||||
|
use file_service::{FileHandle, FileId, ReadFileError};
|
||||||
|
|
||||||
pub fn auth(_message: Option<String>) -> build_html::HtmlPage {
|
pub fn auth(_message: Option<String>) -> build_html::HtmlPage {
|
||||||
build_html::HtmlPage::new()
|
build_html::HtmlPage::new()
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
|
use base64ct::{Base64, Encoding};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::sqlite::SqlitePool;
|
use sha2::{Digest, Sha256};
|
||||||
|
use sqlx::{
|
||||||
|
sqlite::{SqlitePool, SqliteRow},
|
||||||
|
Executor, Row,
|
||||||
|
};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::{ops::Deref, path::PathBuf, sync::Arc};
|
use std::{ops::Deref, path::PathBuf, sync::Arc};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
mod filehandle;
|
mod filehandle;
|
||||||
mod fileinfo;
|
mod fileinfo;
|
||||||
|
@ -61,8 +67,20 @@ pub enum ReadFileError {
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum AuthError {
|
pub enum AuthError {
|
||||||
|
#[error("authentication token is duplicated")]
|
||||||
|
DuplicateAuthToken,
|
||||||
|
|
||||||
|
#[error("session token is duplicated")]
|
||||||
|
DuplicateSessionToken,
|
||||||
|
|
||||||
#[error("database failed")]
|
#[error("database failed")]
|
||||||
SqlError,
|
SqlError(sqlx::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<sqlx::Error> for AuthError {
|
||||||
|
fn from(err: sqlx::Error) -> AuthError {
|
||||||
|
AuthError::SqlError(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
|
||||||
|
@ -80,13 +98,13 @@ impl From<&str> for Username {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Username> for PathBuf {
|
impl From<Username> for String {
|
||||||
fn from(s: Username) -> Self {
|
fn from(s: Username) -> Self {
|
||||||
Self::from(&s)
|
Self::from(&s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Username> for PathBuf {
|
impl From<&Username> for String {
|
||||||
fn from(s: &Username) -> Self {
|
fn from(s: &Username) -> Self {
|
||||||
let Username(s) = s;
|
let Username(s) = s;
|
||||||
Self::from(s)
|
Self::from(s)
|
||||||
|
@ -100,6 +118,13 @@ impl Deref for Username {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl sqlx::FromRow<'_, SqliteRow> for Username {
|
||||||
|
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
||||||
|
let name: String = row.try_get("username")?;
|
||||||
|
Ok(Username::from(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
|
||||||
pub struct AuthToken(String);
|
pub struct AuthToken(String);
|
||||||
|
|
||||||
|
@ -217,45 +242,6 @@ impl FileRoot for Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct App {
|
|
||||||
authdb: Arc<RwLock<AuthDB>>,
|
|
||||||
store: Arc<RwLock<Store>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
pub fn new(authdb: AuthDB, store: Store) -> Self {
|
|
||||||
Self {
|
|
||||||
authdb: Arc::new(RwLock::new(authdb)),
|
|
||||||
store: Arc::new(RwLock::new(store)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn auth_token(&self, token: AuthToken) -> Result<SessionToken, AuthError> {
|
|
||||||
self.authdb.read().await.auth_token(token).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn auth_session(&self, token: SessionToken) -> Result<Username, AuthError> {
|
|
||||||
self.authdb.read().await.auth_session(token).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list_files(&self) -> Result<HashSet<FileId>, ReadFileError> {
|
|
||||||
self.store.read().await.list_files()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_file(&self, id: &FileId) -> Result<FileHandle, ReadFileError> {
|
|
||||||
self.store.read().await.get_file(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add_file(
|
|
||||||
&self,
|
|
||||||
filename: String,
|
|
||||||
content: Vec<u8>,
|
|
||||||
) -> Result<FileHandle, WriteFileError> {
|
|
||||||
self.store.write().await.add_file(filename, content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AuthDB {
|
pub struct AuthDB {
|
||||||
pool: SqlitePool,
|
pool: SqlitePool,
|
||||||
|
@ -263,20 +249,85 @@ pub struct AuthDB {
|
||||||
|
|
||||||
impl AuthDB {
|
impl AuthDB {
|
||||||
pub async fn new(path: PathBuf) -> Result<Self, sqlx::Error> {
|
pub async fn new(path: PathBuf) -> Result<Self, sqlx::Error> {
|
||||||
|
let migrator = sqlx::migrate!("./migrations");
|
||||||
let pool = SqlitePool::connect(&format!("sqlite://{}", path.to_str().unwrap())).await?;
|
let pool = SqlitePool::connect(&format!("sqlite://{}", path.to_str().unwrap())).await?;
|
||||||
|
migrator.run(&pool).await?;
|
||||||
Ok(Self { pool })
|
Ok(Self { pool })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn auth_token(&self, _token: AuthToken) -> Result<SessionToken, AuthError> {
|
pub async fn add_user(&self, username: Username) -> Result<AuthToken, AuthError> {
|
||||||
unimplemented!()
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(Uuid::new_v4().hyphenated().to_string());
|
||||||
|
hasher.update(username.to_string());
|
||||||
|
let auth_token = Base64::encode_string(&hasher.finalize());
|
||||||
|
|
||||||
|
let _ = sqlx::query("INSERT INTO users (username, token) VALUES ($1, $2)")
|
||||||
|
.bind(username.to_string())
|
||||||
|
.bind(auth_token.clone())
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(AuthToken::from(auth_token))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn auth_session(&self, _token: SessionToken) -> Result<Username, AuthError> {
|
pub async fn list_users(&self) -> Result<Vec<Username>, AuthError> {
|
||||||
/*
|
let usernames = sqlx::query_as::<_, Username>("SELECT (username) FROM users")
|
||||||
let conn = self.pool.acquire().await.map_err(|_| AuthError::SqlError)?;
|
.fetch_all(&self.pool)
|
||||||
conn.transaction(|tr| {})
|
.await?;
|
||||||
*/
|
|
||||||
unimplemented!()
|
Ok(usernames)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn authenticate(&self, token: AuthToken) -> Result<Option<SessionToken>, AuthError> {
|
||||||
|
let results = sqlx::query("SELECT * FROM users WHERE token = $1")
|
||||||
|
.bind(token.to_string())
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if results.len() > 1 {
|
||||||
|
return Err(AuthError::DuplicateAuthToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if results.len() == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let user_id: i64 = results[0].try_get("id")?;
|
||||||
|
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(Uuid::new_v4().hyphenated().to_string());
|
||||||
|
hasher.update(token.to_string());
|
||||||
|
let session_token = Base64::encode_string(&hasher.finalize());
|
||||||
|
|
||||||
|
let _ = sqlx::query("INSERT INTO sessions (token, user_id) VALUES ($1, $2)")
|
||||||
|
.bind(session_token.clone())
|
||||||
|
.bind(user_id)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Some(SessionToken::from(session_token)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn validate_session(
|
||||||
|
&self,
|
||||||
|
token: SessionToken,
|
||||||
|
) -> Result<Option<Username>, AuthError> {
|
||||||
|
let rows = sqlx::query(
|
||||||
|
"SELECT users.username FROM sessions INNER JOIN users ON sessions.user_id = users.id WHERE sessions.token = $1",
|
||||||
|
)
|
||||||
|
.bind(token.to_string())
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await?;
|
||||||
|
if rows.len() > 1 {
|
||||||
|
return Err(AuthError::DuplicateSessionToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows.len() == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let username: String = rows[0].try_get("username")?;
|
||||||
|
Ok(Some(Username::from(username)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,3 +468,74 @@ mod test {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod authdb_test {
|
||||||
|
use super::*;
|
||||||
|
use cool_asserts::assert_matches;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn can_create_and_list_users() {
|
||||||
|
let db = AuthDB::new(PathBuf::from(":memory:"))
|
||||||
|
.await
|
||||||
|
.expect("a memory-only database will be created");
|
||||||
|
let _ = db
|
||||||
|
.add_user(Username::from("savanni"))
|
||||||
|
.await
|
||||||
|
.expect("user to be created");
|
||||||
|
assert_matches!(db.list_users().await, Ok(names) => {
|
||||||
|
let names = names.into_iter().collect::<HashSet<Username>>();
|
||||||
|
assert!(names.contains(&Username::from("savanni")));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn unknown_auth_token_returns_nothing() {
|
||||||
|
let db = AuthDB::new(PathBuf::from(":memory:"))
|
||||||
|
.await
|
||||||
|
.expect("a memory-only database will be created");
|
||||||
|
let _ = db
|
||||||
|
.add_user(Username::from("savanni"))
|
||||||
|
.await
|
||||||
|
.expect("user to be created");
|
||||||
|
|
||||||
|
let token = AuthToken::from("0000000000");
|
||||||
|
|
||||||
|
assert_matches!(db.auth_token(token).await, Ok(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn auth_token_becomes_session_token() {
|
||||||
|
let db = AuthDB::new(PathBuf::from(":memory:"))
|
||||||
|
.await
|
||||||
|
.expect("a memory-only database will be created");
|
||||||
|
let token = db
|
||||||
|
.add_user(Username::from("savanni"))
|
||||||
|
.await
|
||||||
|
.expect("user to be created");
|
||||||
|
|
||||||
|
assert_matches!(db.auth_token(token).await, Ok(_));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn can_validate_session_token() {
|
||||||
|
let db = AuthDB::new(PathBuf::from(":memory:"))
|
||||||
|
.await
|
||||||
|
.expect("a memory-only database will be created");
|
||||||
|
let token = db
|
||||||
|
.add_user(Username::from("savanni"))
|
||||||
|
.await
|
||||||
|
.expect("user to be created");
|
||||||
|
let session = db
|
||||||
|
.auth_token(token)
|
||||||
|
.await
|
||||||
|
.expect("token authentication should succeed")
|
||||||
|
.expect("session token should be found");
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
db.auth_session(session).await,
|
||||||
|
Ok(Some(username)) => {
|
||||||
|
assert_eq!(username, Username::from("savanni"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
pkgs.cargo-nextest
|
pkgs.cargo-nextest
|
||||||
pkgs.crate2nix
|
pkgs.crate2nix
|
||||||
pkgs.wasm-pack
|
pkgs.wasm-pack
|
||||||
|
pkgs.sqlx-cli
|
||||||
typeshare.packages."x86_64-linux".default
|
typeshare.packages."x86_64-linux".default
|
||||||
];
|
];
|
||||||
LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib";
|
LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib";
|
||||||
|
|
Loading…
Reference in New Issue