diff --git a/Cargo.lock b/Cargo.lock index 81cb9df..9e442bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,8 +74,11 @@ dependencies = [ "axum", "chrono", "dotenv", + "hmac", + "jwt", "serde", "serde_json", + "sha2", "sqlx", "tokio", "tower", @@ -214,6 +217,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.22.1" @@ -835,6 +844,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest", + "hmac", + "serde", + "serde_json", + "sha2", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1649,7 +1673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags", "byteorder", "bytes", @@ -1693,7 +1717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags", "byteorder", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 0b1cd9a..c55ea94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,6 @@ uuid = "1.11.0" chrono = "0.4.39" dotenv = "0.15.0" argon2 = "0.5.3" +jwt = "0.16.0" +hmac = "0.12.1" +sha2 = "0.10.8" diff --git a/src/main.rs b/src/main.rs index 077f171..4b2925f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,10 +56,11 @@ async fn index() -> &'static str { #[tokio::main] async fn main() -> anyhow::Result<()> { dotenv::dotenv().ok(); - let db = db::db().await?; - let state = state::AppState { db: Arc::new(db) }; - let port: u16 = std::env::var("PORT").unwrap_or_else(|_| "8080".to_string()).parse()?; + let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "secret".to_string()); + + let db = db::db().await?; + let state = state::AppState { db: Arc::new(db), jwt_secret }; let (router, api) = OpenApiRouter::with_openapi(ApiDoc::openapi()) .routes(routes!(health_check)) diff --git a/src/state.rs b/src/state.rs index db36638..b8a0a09 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,6 +3,7 @@ use std::sync::Arc; #[derive(Clone)] pub struct AppState { pub db: DB, + pub jwt_secret: String, } pub type DB = Arc>; diff --git a/src/v1/auth/login.rs b/src/v1/auth/login.rs index e76ccad..07e7607 100644 --- a/src/v1/auth/login.rs +++ b/src/v1/auth/login.rs @@ -3,7 +3,11 @@ use argon2::{ password_hash::{PasswordHash, PasswordVerifier}, Argon2, }; +use hmac::{Hmac, Mac}; +use jwt::SignWithKey; +use sha2::Sha256; use sqlx::query; +use std::collections::BTreeMap; #[derive(Serialize, Deserialize, ToSchema)] pub struct LoginBody { @@ -36,10 +40,14 @@ pub async fn login( return Err(AppError::Error(Errors::Unauthorized)); } - // technically I should be using a JWT and returning it to the client - // since this will be a mobile app. + let key: Hmac = + Hmac::new_from_slice(state.jwt_secret.as_bytes()).context("Failed to create HMAC")?; + let mut claims = BTreeMap::new(); + claims.insert("id", user.id.to_string()); + claims.insert("username", user.username); + claims.insert("real_name", user.real_name); + claims.insert("email", user.email); + let token_str = claims.sign_with_key(&key).context("Failed to sign JWT")?; - // let jwt = ... - - Ok(user.id.to_string()) + Ok(token_str) } diff --git a/src/v1/auth/signup.rs b/src/v1/auth/signup.rs index 863314f..f2406f2 100644 --- a/src/v1/auth/signup.rs +++ b/src/v1/auth/signup.rs @@ -6,6 +6,11 @@ use argon2::{ Argon2, }; +use hmac::{Hmac, Mac}; +use jwt::SignWithKey; +use sha2::Sha256; +use std::collections::BTreeMap; + #[derive(Serialize, Deserialize, ToSchema)] pub struct SignupBody { real_name: String, @@ -40,5 +45,14 @@ pub async fn signup( .fetch_one(&*state.db) .await?; - Ok(user.id.to_string()) + let key: Hmac = + Hmac::new_from_slice(state.jwt_secret.as_bytes()).context("Failed to create HMAC")?; + let mut claims = BTreeMap::new(); + claims.insert("id", user.id.to_string()); + claims.insert("username", user.username); + claims.insert("real_name", user.real_name); + claims.insert("email", user.email); + let token_str = claims.sign_with_key(&key).context("Failed to sign JWT")?; + + Ok(token_str) }