registering works

This commit is contained in:
FrenchGithubUser
2025-03-13 15:17:03 +01:00
parent e0b5ba8a9f
commit d23421b0f2
14 changed files with 388 additions and 54 deletions

8
.env
View File

@@ -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

189
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"] }
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"

View File

@@ -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
);

View File

@@ -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<Register>,
pool: web::Data<PgPool>,
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()),
}
}

View File

@@ -1 +1,2 @@
pub mod auth_handler;
pub mod user_handler;

View File

@@ -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<CreateUser>) -> 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<u32>) -> 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<u32>) -> 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!")

View File

@@ -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()

View File

@@ -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,
}

View File

@@ -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<PgPool>,
user: &Register,
from_ip: &IpAddr,
password_hash: &str,
) -> Result<(), Box<dyn Error>> {
// 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()),
}
}

1
src/repositories/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod auth_repository;

View File

@@ -1 +1 @@
pub mod user_routes;
pub mod routes;

10
src/routes/routes.rs Normal file
View File

@@ -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)),
);
}

View File

@@ -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)),
);
}