This commit is contained in:
lemonsh 2022-11-29 23:52:54 +01:00
parent f61a739095
commit d0709646d1
9 changed files with 197 additions and 41 deletions

78
Cargo.lock generated
View File

@ -252,9 +252,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "0.7.19"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
@ -280,6 +280,17 @@ version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
[[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 = "askama"
version = "0.11.1"
@ -341,9 +352,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.58"
version = "0.1.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c"
checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364"
dependencies = [
"proc-macro2",
"quote",
@ -362,12 +373,27 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64ct"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf"
[[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.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.3"
@ -547,9 +573,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "flate2"
version = "1.0.24"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [
"crc32fast",
"miniz_oxide",
@ -869,9 +895,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.5.4"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
@ -957,9 +983,9 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.4"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"
dependencies = [
"cfg-if",
"libc",
@ -968,6 +994,17 @@ 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 = "paste"
version = "1.0.9"
@ -1176,18 +1213,18 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
[[package]]
name = "serde"
version = "1.0.147"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.147"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c"
dependencies = [
"proc-macro2",
"quote",
@ -1196,9 +1233,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.88"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7"
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
dependencies = [
"itoa",
"ryu",
@ -1290,9 +1327,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.103"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce"
dependencies = [
"proc-macro2",
"quote",
@ -1544,6 +1581,7 @@ dependencies = [
"actix-session",
"actix-web",
"anyhow",
"argon2",
"askama",
"askama_actix",
"hex",
@ -1684,9 +1722,9 @@ dependencies = [
[[package]]
name = "zstd-sys"
version = "2.0.1+zstd.1.5.2"
version = "2.0.4+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b"
checksum = "4fa202f2ef00074143e219d15b62ffc317d17cc33909feac471c044087cad7b0"
dependencies = [
"cc",
"libc",

View File

@ -15,6 +15,7 @@ actix-session = { version = "0.7", features = ["cookie-session"] }
askama = { version = "0.11", features = ["with-actix-web"] }
askama_actix = "0.13"
anyhow = "1.0"
argon2 = "0.4"
rusqlite = "0.28"
rust-embed = "6.4"
mime_guess = "2.0"

View File

@ -66,7 +66,7 @@ async fn main() -> anyhow::Result<()> {
let (storage_service, storage) = StorageService::create(&config.database, nsdcpath)?;
let storage_thread = thread::spawn(|| storage_service.run());
let web_backend = WebBackend::new(config).run()?;
let web_backend = WebBackend::new(config, storage).run()?;
let web_task = tokio::spawn(async move {
if let Err(e) = web_backend.await {
let _err = etx.send(e);
@ -87,7 +87,6 @@ async fn main() -> anyhow::Result<()> {
}
}
drop(storage);
let _ = ctx.send(());
web_task.await.unwrap();
storage_thread.join().unwrap();

View File

@ -21,7 +21,12 @@ use std::{
str::FromStr,
};
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use anyhow::{anyhow, Result};
use rusqlite::{ErrorCode, OptionalExtension};
use tokio::sync::{
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
oneshot,
};
use tokio::time::Instant;
mod nsdcontrol;
@ -54,7 +59,7 @@ impl StorageService {
let (tx, rx) = unbounded_channel();
let db = rusqlite::Connection::open(dbpath)?;
db.execute("pragma foreign_keys = on", [])?;
db.execute(include_str!("schema.sql"), [])?;
db.execute_batch(include_str!("schema.sql"))?;
tracing::debug!("Database loaded ({})", dbpath);
Ok((
Self {
@ -70,12 +75,52 @@ impl StorageService {
while let Some(task) = self.rx.blocking_recv() {
let before = Instant::now();
tracing::debug!("got task {:?}", task);
match task {
Task::RegisterAccount(tx, u, h) => tx.send(self.register_account(u, h)).unwrap(),
Task::GetAccountByUsername(tx, u) => {
tx.send(self.get_account_by_username(u)).unwrap()
}
}
tracing::debug!(
"task took {}ms",
Instant::now().duration_since(before).as_secs_f64() / 1000.0
);
}
}
fn register_account(&self, username: String, hash: String) -> Result<Option<i64>> {
let result = self.db.query_row(
"insert into accounts(username,hash) values (?, ?) returning id",
[username, hash],
|v| v.get(0),
);
match result {
Ok(o) => Ok(Some(o)),
Err(err) => {
if let rusqlite::Error::SqliteFailure(serr, _) = err && serr.code == ErrorCode::ConstraintViolation {
Ok(None)
} else {
Err(anyhow!(err))
}
}
}
}
fn get_account_by_username(&self, username: String) -> Result<Option<AccountCredentials>> {
Ok(self
.db
.query_row(
"select id,hash from accounts where username=?",
[username],
|v| {
Ok(AccountCredentials {
id: v.get::<_, i64>(0)?,
hash: v.get::<_, String>(1)?,
})
},
)
.optional()?)
}
}
pub struct StorageConnection {
@ -91,14 +136,31 @@ impl Clone for StorageConnection {
}
#[derive(Debug)]
enum Task {}
pub struct AccountCredentials {
id: i64,
hash: String,
}
type ResultSender<T> = oneshot::Sender<Result<T>>;
#[derive(Debug)]
enum Task {
RegisterAccount(ResultSender<Option<i64>>, String, String),
GetAccountByUsername(ResultSender<Option<AccountCredentials>>, String),
}
impl StorageConnection {
// WARNING: these methods are NOT cancel-safe
// executor_wrapper!(
// add_quote,
// Task::AddQuote,
// rusqlite::Result<()>,
// quote: Quote
// );
executor_wrapper!(
register_account,
Task::RegisterAccount,
Result<Option<i64>>,
username: String,
hash: String
);
executor_wrapper!(
get_account,
Task::GetAccountByUsername,
Result<Option<AccountCredentials>>,
username: String
);
}

View File

@ -1,7 +1,7 @@
create table if not exists accounts(
id integer primary key,
username text not null,
password text not null
hash text not null
);
create table if not exists requests(
@ -30,3 +30,4 @@ create table if not exists records(
);
create unique index if not exists name_index on domains (name);
create unique index if not exists username_index on accounts (username);

View File

@ -16,12 +16,15 @@
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::storage::StorageConnection;
use crate::Config;
use actix_session::storage::CookieSessionStore;
use actix_session::{SessionMiddleware, Session};
use actix_session::{Session, SessionMiddleware};
use actix_web::cookie::Key;
use actix_web::dev::Server;
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use actix_web::web::Data;
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder};
use argon2::Argon2;
use rust_embed::RustEmbed;
use std::sync::Arc;
@ -29,6 +32,7 @@ mod templates;
pub struct WebBackend {
config: Arc<Config>,
db: StorageConnection,
}
#[derive(RustEmbed)]
@ -49,18 +53,38 @@ async fn static_files(path: web::Path<String>) -> impl Responder {
}
#[actix_web::get("/")]
async fn login(session: Session) -> actix_web::Result<impl Responder> {
Ok(templates::LoginTemplate {flash: None})
async fn login(session: Session, req: HttpRequest) -> actix_web::Result<impl Responder> {
if let Some(_) = session.get::<i64>("uid")? {
Ok(HttpResponse::TemporaryRedirect().finish())
} else {
Ok(templates::LoginTemplate { flash: None }.respond_to(&req))
}
}
#[actix_web::get("/register")]
async fn register(session: Session, req: HttpRequest) -> actix_web::Result<impl Responder> {
if let Some(_) = session.get::<i64>("uid")? {
Ok(HttpResponse::TemporaryRedirect().finish())
} else {
Ok(templates::RegisterTemplate { flash: None }.respond_to(&req))
}
}
#[actix_web::post("/")]
async fn login_post(session: Session) -> actix_web::Result<impl Responder> {
Ok(templates::LoginTemplate {flash: Some("wtfjusthappened".into())})
Ok(templates::LoginTemplate {
flash: Some("wtfjusthappened".into()),
})
}
#[actix_web::post("/register")]
async fn register_post(session: Session) -> actix_web::Result<impl Responder> {
Ok("")
}
impl WebBackend {
pub fn new(config: Arc<Config>) -> Self {
Self { config }
pub fn new(config: Arc<Config>, db: StorageConnection) -> Self {
Self { config, db }
}
pub fn run(self) -> anyhow::Result<Server> {
@ -72,8 +96,12 @@ impl WebBackend {
CookieSessionStore::default(),
key.clone(),
))
.app_data(Data::new(Argon2::default()))
.app_data(Data::new(self.db.clone()))
.service(login)
.service(login_post)
.service(register)
.service(register_post)
.service(static_files)
})
.bind(self.config.web.listen)?

View File

@ -21,5 +21,11 @@ use askama::Template;
#[derive(Template)]
#[template(path = "login.html")]
pub struct LoginTemplate {
pub flash: Option<String>
pub flash: Option<String>,
}
#[derive(Template)]
#[template(path = "register.html")]
pub struct RegisterTemplate {
pub flash: Option<String>,
}

View File

@ -2,6 +2,7 @@
{% block title %}Login{% endblock %}
{% block content %}
<h1>Log in</h1>
{% match flash %}
{% when Some with (flash) %}
<small style="color: var(--del-color)">{{flash}}</small>

20
templates/register.html Normal file
View File

@ -0,0 +1,20 @@
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<h1>Register</h1>
{% match flash %}
{% when Some with (flash) %}
<small style="color: var(--del-color)">{{flash}}</small>
{% when None %}
{% endmatch %}
<form action="/register" method="post">
<label for="login">Login</label>
<input type="text" name="login" id="login" required>
<label for="pass">Password</label>
<input type="password" name="password" id="pass" required>
<input type="submit" value="Register">
</form>
<small style="float: right;"><a href="/">Log in instead</a></small>
{% endblock %}