diff --git a/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json b/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json new file mode 100644 index 0000000..0451596 --- /dev/null +++ b/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json @@ -0,0 +1,44 @@ +{ + "db_name": "SQLite", + "query": "SELECT * FROM users WHERE username = $1", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "uuid", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "username", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "hash", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "email", + "ordinal": 4, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100" +} diff --git a/Cargo.toml b/Cargo.toml index 648f0fd..7d4e87a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,13 @@ [package] name = "api-server" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] actix-web = "4.9.0" serde = "1.0.218" serde_json = "1.0.140" -sqlx = "0.8.3" \ No newline at end of file +sqlx = { version = "0.8.3", features = ["sqlite", "sqlx-macros"] } +jsonwebtoken = "9.3.1" +uuid = "1.15.1" +argon2 = "0.6.0-pre.1" \ No newline at end of file diff --git a/database.db b/database.db new file mode 100644 index 0000000..e080200 Binary files /dev/null and b/database.db differ diff --git a/migrations/20250310175116_tables.sql b/migrations/20250310175116_tables.sql new file mode 100644 index 0000000..ee4920c --- /dev/null +++ b/migrations/20250310175116_tables.sql @@ -0,0 +1,41 @@ +-- Add migration script here +CREATE TABLE users ( + 'id' INTEGER PRIMARY KEY NOT NULL , + 'uuid' TEXT UNIQUE NOT NULL, + 'username' TEXT NOT NULL, + 'hash' TEXT NOT NULL, + 'email' TEXT NOT NULL +); + +CREATE TABLE inventories ( + 'id' INTEGER PRIMARY KEY NOT NULL , + 'uuid' TEXT UNIQUE NOT NULL, + 'user' INTEGER NOT NULL, + FOREIGN KEY ('user') REFERENCES users ('id') +); + +CREATE TABLE user_items ( + 'id' INTEGER PRIMARY KEY NOT NULL , + 'uuid' TEXT UNIQUE NOT NULL, + 'inventory' INTEGER NOT NULL, + 'item' INTEGER NOT NULL, + FOREIGN KEY ('inventory') REFERENCES inventories ('id'), + FOREIGN KEY ('item') REFERENCES items ('id') +); + +CREATE TABLE items ( + 'id' INTEGER PRIMARY KEY NOT NULL , + 'uuid' TEXT UNIQUE NOT NULL, + 'name' TEXT NOT NULL, + 'rarity' INTEGER NOT NULL, + 'image' TEXT NOT NULL, + 'case' INTEGER NOT NULL, + FOREIGN KEY ('case') REFERENCES cases ('id') +); + +CREATE TABLE cases ( + 'id' INTEGER PRIMARY KEY NOT NULL , + 'uuid' TEXT UNIQUE NOT NULL, + 'name' TEXT NOT NULL, + 'image' TEXT NOT NULL +); \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e7a11a9..22f0b56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,24 @@ -fn main() { - println!("Hello, world!"); +mod users; + +use std::sync::Mutex; +use users::*; + +use actix_web::{App, HttpServer}; +use actix_web::web::Data; +use sqlx::sqlite::SqlitePool; + +#[derive(Clone)] +struct AppState { + database: SqlitePool, +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let pool = SqlitePool::connect("sqlite:database.db").await.expect("Could not connect to db"); + let app_state = Data::new(AppState { database: pool }); + HttpServer::new(move || { + App::new() + .service(login) + .app_data(app_state.clone()) + }).bind(("127.0.0.1", 8000))?.run().await } diff --git a/src/users.rs b/src/users.rs new file mode 100644 index 0000000..329588a --- /dev/null +++ b/src/users.rs @@ -0,0 +1,46 @@ +use actix_web::web::Data; +use actix_web::{post, HttpResponse, Responder}; +use argon2::password_hash::{SaltString, rand_core::OsRng, PasswordHash, PasswordVerifier, PasswordHasher}; +use argon2::Argon2; +use sqlx::{query, query_as}; +use crate::AppState; + +struct UserLogin { + username: String, + password: String, +} + +struct User { + id: i64, + uuid: String, + username: String, + hash: String, + email: String, +} + +#[post("/login")] +async fn login(user_login: Data, app_state: Data) -> impl Responder { + // Verify that the password is correct + let argon2 = Argon2::default(); + let user = query_as!(User, "SELECT * FROM users WHERE username = $1", user_login.username).fetch_one(&app_state.database).await; + if user.is_err() { + return HttpResponse::BadRequest(); + } + let user = user.unwrap(); + let hash = PasswordHash::new(&user.hash); + if hash.is_err() { + return HttpResponse::BadRequest(); + } + if argon2.verify_password(user_login.password.as_bytes(), &hash.unwrap()).is_err() { + return HttpResponse::BadRequest(); + } + // Create the JWT + + // Send the JWT as cookie + HttpResponse::Ok() +} + +#[post("/logout")] +async fn logout(_token: Data) -> impl Responder { + HttpResponse::Ok() +} \ No newline at end of file