This commit is contained in:
Gvidas Juknevičius 2025-09-07 00:12:52 +03:00
parent 81e68770c6
commit 111bcedf2c
Signed by: MCorange
GPG Key ID: 5BE6B533CB76FE86
7 changed files with 256 additions and 17 deletions

118
Cargo.lock generated
View File

@ -44,6 +44,18 @@ version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
[[package]]
name = "argon2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
dependencies = [
"base64ct",
"blake2",
"cpufeatures",
"password-hash",
]
[[package]]
name = "askama"
version = "0.14.0"
@ -233,6 +245,15 @@ dependencies = [
"serde",
]
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -626,6 +647,15 @@ dependencies = [
"version_check",
]
[[package]]
name = "getopts"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
dependencies = [
"unicode-width",
]
[[package]]
name = "getrandom"
version = "0.2.16"
@ -1121,7 +1151,7 @@ dependencies = [
"num-integer",
"num-iter",
"num-traits",
"rand",
"rand 0.8.5",
"smallvec",
"zeroize",
]
@ -1206,6 +1236,17 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@ -1226,11 +1267,16 @@ name = "persmgr-gui"
version = "0.1.0"
dependencies = [
"anyhow",
"argon2",
"askama",
"axum",
"base64",
"pulldown-cmark",
"rand 0.9.2",
"serde",
"serde_json",
"sqlx",
"time",
"tokio",
"toml",
"tower",
@ -1314,6 +1360,25 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "pulldown-cmark"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0"
dependencies = [
"bitflags",
"getopts",
"memchr",
"pulldown-cmark-escape",
"unicase",
]
[[package]]
name = "pulldown-cmark-escape"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
[[package]]
name = "quote"
version = "1.0.40"
@ -1336,8 +1401,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
]
[[package]]
@ -1347,7 +1422,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.3",
]
[[package]]
@ -1359,6 +1444,15 @@ dependencies = [
"getrandom 0.2.16",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.3",
]
[[package]]
name = "redox_syscall"
version = "0.5.17"
@ -1381,7 +1475,7 @@ dependencies = [
"num-traits",
"pkcs1",
"pkcs8",
"rand_core",
"rand_core 0.6.4",
"signature",
"spki",
"subtle",
@ -1534,7 +1628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
@ -1695,7 +1789,7 @@ dependencies = [
"memchr",
"once_cell",
"percent-encoding",
"rand",
"rand 0.8.5",
"rsa",
"serde",
"sha1",
@ -1733,7 +1827,7 @@ dependencies = [
"md-5",
"memchr",
"once_cell",
"rand",
"rand 0.8.5",
"serde",
"serde_json",
"sha2",
@ -2121,7 +2215,7 @@ dependencies = [
"futures",
"http",
"parking_lot",
"rand",
"rand 0.8.5",
"serde",
"serde_json",
"thiserror",
@ -2239,6 +2333,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
[[package]]
name = "unicode-width"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
[[package]]
name = "url"
version = "2.5.7"

View File

@ -5,11 +5,16 @@ edition = "2024"
[dependencies]
anyhow = "1.0.99"
argon2 = { version = "0.5.3", features = ["simple", "std"] }
askama = "0.14.0"
axum = "0.8.4"
base64 = "0.22.1"
pulldown-cmark = "0.13.0"
rand = "0.9.2"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.143"
sqlx = { version = "0.8.6", features = ["macros", "postgres", "runtime-tokio"] }
time = "0.3.43"
tokio = { version = "1.47.1", features = ["full"] }
toml = "0.9.5"
tower = { version = "0.5.2", features = ["full"] }

View File

@ -0,0 +1,3 @@
-- Add down migration script here
DROP TABLE IF EXISTS sessions;

View File

@ -0,0 +1,7 @@
-- Add up migration script here
CREATE TABLE IF NOT EXISTS sessions (
user_id BIGINT NOT NULL,
session_key TEXT NOT NULL UNIQUE,
expires BIGINT NOT NULL
)

View File

@ -1,17 +1,82 @@
use axum::{
body::Body,
extract::State,
http::{HeaderMap, HeaderValue, StatusCode},
response::{IntoResponse, Response},
use argon2::{
Argon2, Params,
password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
};
use axum::{
extract::{Json, State},
http::{Response, StatusCode},
};
use base64::{Engine as _, engine::general_purpose};
use serde::Deserialize;
use time::{Duration, OffsetDateTime};
use crate::db::Database;
pub async fn route(State(db): State<Database>) -> Response {
#[derive(Debug, Clone, Deserialize)]
pub struct ReqBody {
email: String,
username: String,
password: String,
}
pub async fn route(State(db): State<Database>, Json(body): Json<ReqBody>) -> Response<String> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::new(
argon2::Algorithm::Argon2id,
argon2::Version::V0x13,
Params::DEFAULT,
);
let hash = argon2
.hash_password(body.password.as_bytes(), salt.as_salt())
.unwrap()
.to_string();
let mut user = crate::db::tables::user::User::default();
user.username = body.username;
user.email = body.email;
user.pw_salt = salt.to_string();
user.pw_hash = hash;
if let Err(e) = user.insert_new(&db.pool()).await {
return Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(format!("ERROR: Failed to create user: {e}"))
.unwrap();
}
let Ok(user) = crate::db::tables::user::User::get_by_username(&db.pool(), user.username).await
else {
return Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(String::from("ERROR: Failed to get created user"))
.unwrap();
};
let session_key = {
let mut buf = [0u8; 32];
rand::fill(&mut buf);
general_purpose::STANDARD.encode(&buf)
};
let mut session = crate::db::tables::sessions::Session::default();
session.user_id = user.id;
session.session_key = session_key;
session.expires = OffsetDateTime::now_utc()
.saturating_add(Duration::days(30))
.unix_timestamp();
if let Err(e) = session.insert_new(db.pool()).await {
return Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(format!("ERROR: Failed to create session for user: {e}",))
.unwrap();
};
Response::builder()
.header("Location", "/")
.header("Set-Cookie", &format!("session=meowmeowmeow"))
.header("Set-Cookie", &format!("session={}", session.session_key))
.status(StatusCode::SEE_OTHER)
.body(Body::empty())
.body(String::new())
.unwrap()
}

View File

@ -1 +1,2 @@
pub mod sessions;
pub mod user;

58
src/db/tables/sessions.rs Normal file
View File

@ -0,0 +1,58 @@
use anyhow::Result;
use crate::db::CurrPool;
#[derive(Debug, Default, Clone)]
pub struct Session {
pub user_id: i64,
pub session_key: String,
pub expires: i64,
}
impl Session {
pub async fn insert_new(&self, pool: &CurrPool) -> Result<Self> {
let session = sqlx::query_as!(
Session,
r#"
INSERT INTO sessions (user_id, session_key, expires)
VALUES ($1, $2, $3)
RETURNING *
"#,
self.user_id,
self.session_key,
self.expires
)
.fetch_one(pool)
.await?;
Ok(session)
}
pub async fn get_by_username(pool: &CurrPool, user_id: i64) -> anyhow::Result<Self> {
let session = sqlx::query_as!(
Session,
"SELECT * FROM sessions WHERE user_id = $1",
user_id
)
.fetch_one(pool)
.await?;
Ok(session)
}
pub async fn get_by_session_key(pool: &CurrPool, session_key: String) -> anyhow::Result<Self> {
let session = sqlx::query_as!(
Session,
"SELECT * FROM sessions WHERE session_key = $1",
session_key
)
.fetch_one(pool)
.await?;
Ok(session)
}
pub async fn remove_old_sessions(pool: &CurrPool) -> anyhow::Result<()> {
let curr_time = time::OffsetDateTime::now_utc().unix_timestamp();
sqlx::query!("DELETE FROM sessions WHERE expires < $1", curr_time)
.execute(pool)
.await?;
Ok(())
}
}