From d23421b0f23d70fa508df4e4795f00edbf4c627a Mon Sep 17 00:00:00 2001 From: FrenchGithubUser Date: Thu, 13 Mar 2025 15:17:03 +0100 Subject: [PATCH] registering works --- .env | 8 +- Cargo.lock | 189 +++++++++++++++++++++++++++ Cargo.toml | 8 +- migrations/20250312215600_initdb.sql | 32 ++++- src/handlers/auth_handler.rs | 43 ++++++ src/handlers/mod.rs | 1 + src/handlers/user_handler.rs | 38 ++---- src/main.rs | 21 ++- src/models/user.rs | 45 ++++++- src/repositories/auth_repository.rs | 32 +++++ src/repositories/mod.rs | 1 + src/routes/mod.rs | 2 +- src/routes/routes.rs | 10 ++ src/routes/user_routes.rs | 12 -- 14 files changed, 388 insertions(+), 54 deletions(-) create mode 100644 src/handlers/auth_handler.rs create mode 100644 src/repositories/auth_repository.rs create mode 100644 src/repositories/mod.rs create mode 100644 src/routes/routes.rs delete mode 100644 src/routes/user_routes.rs diff --git a/.env b/.env index ccbeba37..ee68b94f 100644 --- a/.env +++ b/.env @@ -1,8 +1,12 @@ # actix config -HOST=127.0.0.1 -PORT=8080 +ACTIX_HOST=127.0.0.1 +ACTIX_PORT=8080 +# auth + +JWT_SECRET=sensitivejwtsecret +PASSWORD_HASH_SECRET=sensitivepasswordhashsecret # Postgre config diff --git a/Cargo.lock b/Cargo.lock index 8971778c..33deb384 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,14 +230,45 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "arcadia-index" version = "0.1.0" dependencies = [ "actix-web", + "argon2", + "chrono", "dotenv", + "jsonwebtoken", "serde", "sqlx", + "uuid", +] + +[[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]] @@ -445,6 +476,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" @@ -532,6 +572,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -558,6 +613,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -938,8 +999,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1085,6 +1148,29 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -1285,6 +1371,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -1434,6 +1535,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -1531,6 +1642,27 @@ 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" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1764,6 +1896,20 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rsa" version = "0.9.8" @@ -1939,6 +2085,18 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "slab" version = "0.4.9" @@ -2419,6 +2577,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -2442,6 +2606,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" +dependencies = [ + "getrandom 0.3.1", + "serde", +] + [[package]] name = "value-bag" version = "1.10.0" @@ -2600,6 +2774,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-link" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index dbc4d6ea..6b43707d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,10 @@ edition = "2024" [dependencies] actix-web = "4" # there is a possibility to add TLS support (https://github.com/launchbadge/sqlx?tab=readme-ov-file#install) -sqlx = { version = "0.8", features = [ "runtime-async-std" ] } +sqlx = { version = "0.8", features = ["runtime-async-std", "postgres"] } dotenv = "0.15.0" -serde = { version = "1.0", features = ["derive"] } \ No newline at end of file +serde = { version = "1.0", features = ["derive"] } +chrono = { version = "0.4", features = ["serde"] } +uuid = { version = "1.15.1", features = ["serde", "v4"] } +jsonwebtoken = "9.3.1" +argon2 = "0.5.3" diff --git a/migrations/20250312215600_initdb.sql b/migrations/20250312215600_initdb.sql index 43017250..06e83500 100644 --- a/migrations/20250312215600_initdb.sql +++ b/migrations/20250312215600_initdb.sql @@ -1,8 +1,32 @@ CREATE TABLE users ( id SERIAL PRIMARY KEY, - username VARCHAR(15) NOT NULL, - -- max length of 15 chars, change to your liking - email TEXT UNIQUE NOT NULL, - created_from_ip CIDR + username VARCHAR(20) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + registered_from_ip INET NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + description TEXT NOT NULL DEFAULT '', + uploaded BIGINT NOT NULL DEFAULT 1, + -- 1 byte uploaded + downloaded BIGINT NOT NULL DEFAULT 0, + ratio FLOAT NOT NULL DEFAULT 0.0, + required_ratio FLOAT NOT NULL DEFAULT 0.0, + last_seen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + class VARCHAR(50) NOT NULL DEFAULT 'newbie', + forum_posts INTEGER NOT NULL DEFAULT 0, + forum_threads INTEGER NOT NULL DEFAULT 0, + group_comments INTEGER NOT NULL DEFAULT 0, + torrent_comments INTEGER NOT NULL DEFAULT 0, + request_comments INTEGER NOT NULL DEFAULT 0, + artist_comments BIGINT NOT NULL DEFAULT 0, + seeding INTEGER NOT NULL DEFAULT 0, + leeching INTEGER NOT NULL DEFAULT 0, + snatched INTEGER NOT NULL DEFAULT 0, + seeding_size BIGINT NOT NULL DEFAULT 0, + requests_filled BIGINT NOT NULL DEFAULT 0, + collages_started BIGINT NOT NULL DEFAULT 0, + requests_voted BIGINT NOT NULL DEFAULT 0, + average_seeding_time BIGINT NOT NULL DEFAULT 0, + invited BIGINT NOT NULL DEFAULT 0 ); diff --git a/src/handlers/auth_handler.rs b/src/handlers/auth_handler.rs new file mode 100644 index 00000000..76e250c0 --- /dev/null +++ b/src/handlers/auth_handler.rs @@ -0,0 +1,43 @@ +use std::net::IpAddr; + +use actix_web::{HttpRequest, HttpResponse, web}; +use sqlx::PgPool; + +use argon2::{ + Argon2, + password_hash::{PasswordHasher, SaltString, rand_core::OsRng}, +}; + +use crate::{models::user::Register, repositories::auth_repository::create_user}; + +pub async fn register( + user_register: web::Json, + pool: web::Data, + req: HttpRequest, +) -> HttpResponse { + let client_ip: IpAddr = req + .connection_info() + .realip_remote_addr() + .and_then(|ip| ip.parse().ok()) + .unwrap(); + + let new_user = Register { + ..user_register.into_inner() + }; + + let salt = SaltString::generate(&mut OsRng); + + // Argon2 with default params (Argon2id v19) + let argon2 = Argon2::default(); + + // Hash password to PHC string ($argon2id$v=19$...) + let password_hash = argon2 + .hash_password(new_user.password.as_bytes(), &salt) + .unwrap() + .to_string(); + + match create_user(&pool, &new_user, &client_ip, &password_hash).await { + Ok(user) => HttpResponse::Created().json(user), + Err(err) => HttpResponse::InternalServerError().json(err.to_string()), + } +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 9091f4b9..48477be5 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1 +1,2 @@ +pub mod auth_handler; pub mod user_handler; diff --git a/src/handlers/user_handler.rs b/src/handlers/user_handler.rs index 8c48cced..2d4ae20e 100644 --- a/src/handlers/user_handler.rs +++ b/src/handlers/user_handler.rs @@ -1,31 +1,21 @@ -use std::{net::IpAddr, str::FromStr}; +use std::{net::IpAddr, str::FromStr, time::Duration}; -use actix_web::{HttpResponse, web}; +use actix_web::{HttpRequest, HttpResponse, web}; +use chrono::Utc; -use crate::models::user::{CreateUser, User}; +use crate::models::user::{Register, User}; -pub async fn create_user(user: web::Json) -> HttpResponse { - let new_user = User { - id: 0, //TODO - username: user.username.clone(), - email: user.email.clone(), - created_from_ip: IpAddr::from_str("127.0.0.1").unwrap(), - }; +// pub async fn get_user(user_id: web::Data) -> HttpResponse { +// // For now, we'll just mock a user response. +// let mock_user = User { +// id: **user_id, +// username: String::from("mock_user"), +// email: String::from("mock_user@example.com"), +// created_from_ip: IpAddr::from_str("127.0.0.1").unwrap(), +// }; - HttpResponse::Created().json(new_user) -} - -pub async fn get_user(user_id: web::Data) -> HttpResponse { - // For now, we'll just mock a user response. - let mock_user = User { - id: **user_id, - username: String::from("mock_user"), - email: String::from("mock_user@example.com"), - created_from_ip: IpAddr::from_str("127.0.0.1").unwrap(), - }; - - HttpResponse::Ok().json(mock_user) -} +// HttpResponse::Ok().json(mock_user) +// } pub async fn health_check() -> HttpResponse { HttpResponse::Ok().json("API is up and running!") diff --git a/src/main.rs b/src/main.rs index 5ad7f674..12797c0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,32 @@ mod handlers; mod models; +mod repositories; mod routes; -use actix_web::{App, HttpServer}; +use actix_web::{App, HttpServer, web::Data}; use dotenv; +use sqlx::postgres::PgPoolOptions; use std::env; -use routes::user_routes::init; +use routes::routes::init; #[actix_web::main] async fn main() -> std::io::Result<()> { dotenv::from_filename(".env.local").ok(); - let host = env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); - let port = env::var("PORT").unwrap_or_else(|_| "8080".to_string()); + let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + let pool = PgPoolOptions::new() + .max_connections(5) + .connect(&database_url) + .await + .expect("Error building a connection pool"); + + let host = env::var("ACTIX_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); + let port = env::var("ACTIX_PORT").unwrap_or_else(|_| "8080".to_string()); println!("Server running at http://{}:{}", host, port); - HttpServer::new(|| { - App::new().configure(init) // Initialize routes + HttpServer::new(move || { + App::new().app_data(Data::new(pool.clone())).configure(init) // Initialize routes }) .bind(format!("{}:{}", host, port))? .run() diff --git a/src/models/user.rs b/src/models/user.rs index 02d00ad1..c1dd3eb7 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -1,4 +1,4 @@ -use std::net::IpAddr; +use std::{net::IpAddr, time::Duration}; use serde::{Deserialize, Serialize}; @@ -7,11 +7,50 @@ pub struct User { pub id: u32, pub username: String, pub email: String, - pub created_from_ip: IpAddr, + pub password_hash: String, + pub registered_from_ip: IpAddr, + pub created_at: chrono::NaiveDateTime, + pub description: String, + pub uploaded: u64, + pub downloaded: u64, + pub ratio: f64, + pub required_ratio: f64, + pub last_seen: chrono::NaiveDateTime, + pub class: String, + pub forum_posts: u32, + pub forum_threads: u32, + pub group_comments: u32, + pub torrent_comments: u32, + pub request_comments: u32, + pub artist_comments: u64, + pub seeding: u32, + pub leeching: u32, + pub snatched: u32, + pub seeding_size: u64, + pub requests_filled: u64, + pub collages_started: u64, + pub requests_voted: u64, + pub average_seeding_time: Duration, + pub invited: u64, } #[derive(Debug, Serialize, Deserialize)] -pub struct CreateUser { +pub struct Register { pub username: String, + pub password: String, + pub password_verify: String, pub email: String, } + +#[derive(Serialize, Deserialize, Debug)] +pub struct Login { + pub email: String, + pub password: String, + pub remember_me: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Claims { + pub sub: String, + pub exp: usize, +} diff --git a/src/repositories/auth_repository.rs b/src/repositories/auth_repository.rs new file mode 100644 index 00000000..3bed62a1 --- /dev/null +++ b/src/repositories/auth_repository.rs @@ -0,0 +1,32 @@ +use crate::models::user::Register; +use actix_web::web; +use sqlx::PgPool; +use std::error::Error; +use std::net::IpAddr; + +pub async fn create_user( + pool: &web::Data, + user: &Register, + from_ip: &IpAddr, + password_hash: &str, +) -> Result<(), Box> { + // let user = user.into_inner(); + + let query = r#" + INSERT INTO users (username, email, password_hash, registered_from_ip) + VALUES ($1, $2, $3, $4::inet) + "#; + + let result = sqlx::query(query) + .bind(&user.username) + .bind(&user.email) + .bind(password_hash) + .bind(from_ip.to_string()) + .execute(pool.get_ref()) + .await; + + match result { + Ok(_) => Ok(()), + Err(e) => Err(format!("Failed to create user: {:?}", e).into()), + } +} diff --git a/src/repositories/mod.rs b/src/repositories/mod.rs new file mode 100644 index 00000000..cd1cf226 --- /dev/null +++ b/src/repositories/mod.rs @@ -0,0 +1 @@ +pub mod auth_repository; diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 68b591f1..6a664abd 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1 +1 @@ -pub mod user_routes; +pub mod routes; diff --git a/src/routes/routes.rs b/src/routes/routes.rs new file mode 100644 index 00000000..614dc784 --- /dev/null +++ b/src/routes/routes.rs @@ -0,0 +1,10 @@ +use actix_web::web; + +use crate::handlers::auth_handler::register; + +pub fn init(cfg: &mut web::ServiceConfig) { + cfg.service( + web::scope("/api").route("/register", web::post().to(register)), // .route("/users/{id}", web::get().to(get_user)) + // .route("/health", web::get().to(health_check)), + ); +} diff --git a/src/routes/user_routes.rs b/src/routes/user_routes.rs deleted file mode 100644 index a32b6a05..00000000 --- a/src/routes/user_routes.rs +++ /dev/null @@ -1,12 +0,0 @@ -use actix_web::web; - -use crate::handlers::user_handler::{create_user, get_user, health_check}; - -pub fn init(cfg: &mut web::ServiceConfig) { - cfg.service( - web::scope("/api") - .route("/users", web::post().to(create_user)) - .route("/users/{id}", web::get().to(get_user)) - .route("/health", web::get().to(health_check)), - ); -}