From c8ffd8411df09d1a65b3a8a4d838d0191bb7267b Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Wed, 30 Jul 2025 19:08:44 -0400 Subject: [PATCH] Saving changes --- Cargo.lock | 35 +++++++++++++ Cargo.toml | 1 + src/auth.rs | 148 ++++++++++++++++++++++++++-------------------------- 3 files changed, 110 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 159cbfb..8c093dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,6 +108,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-extra" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d" +dependencies = [ + "axum", + "axum-core", + "bytes", + "cookie", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "serde", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -243,6 +266,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -788,6 +822,7 @@ name = "icarus" version = "0.1.100" dependencies = [ "axum", + "axum-extra", "base64 0.21.7", "common-multipart-rfc7578", "futures", diff --git a/Cargo.toml b/Cargo.toml index cafbc2f..f9d5876 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ rust-version = "1.88" [dependencies] axum = { version = "0.8.4", features = ["multipart"] } +axum-extra = { version = "0.10.1", features = ["cookie"] } serde = { version = "1.0.219", features = ["derive"] } serde_json = { version = "1.0.140" } tower = { version = "0.5.2", features = ["full"] } diff --git a/src/auth.rs b/src/auth.rs index 18ae3ef..caa2c58 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,24 +1,25 @@ -use std::collections::BTreeMap; - +// use std::collections::BTreeMap; +// use std::sync::Arc; use axum::{ + // extract::State, http::{Request, StatusCode}, middleware::Next, - response::{IntoResponse, Response}, + response::{IntoResponse}, Json, }; -use josekit::{ - jwt::{self, JwtPayload}, - jws::{self, JwsHeader, JwsSigner, JwsVerifier}, - jwk::Jwk, -}; +use axum_extra::extract::cookie::CookieJar; +use jsonwebtoken::{decode, DecodingKey, Validation}; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +// use serde_json::{json, Value}; use thiserror::Error; -use time::OffsetDateTime; +// use time::OffsetDateTime; + + #[derive(Debug, Serialize, Deserialize)] pub struct UserClaims { + pub aud: String, // Audience pub sub: String, // Subject (user ID) pub exp: i64, // Expiration time (UTC timestamp) pub iat: i64, // Issued at (UTC timestamp) @@ -27,8 +28,6 @@ pub struct UserClaims { } -// use time::OffsetDateTime; - #[derive(Error, Debug)] pub enum JwtError { #[error("Token creation failed")] @@ -43,71 +42,72 @@ pub enum JwtError { InvalidKey, } +/* pub struct JwtAuth { key: Jwk, // Now properly parameterized } +*/ -impl JwtAuth { - pub fn new(secret: &str) -> Result { - let mut key = Jwk::new("oct"); - key.set_parameter("k", Some(json!(base64::encode(secret)))) - .map_err(|_| JwtError::InvalidKey)?; - Ok(Self { key }) - } - pub fn create_token(&self, claims: &UserClaims) -> Result { - let mut header = JwsHeader::new(); - header.set_token_type("JWT"); - - let claims_value = serde_json::to_value(claims) - .map_err(|_| JwtError::SerializationError)?; - let claims_map = claims_value.as_object() - .ok_or(JwtError::SerializationError)? - .to_owned(); - - let mut payload = JwtPayload::new(); - for (k, v) in claims_map { - payload.set_claim(&k, Some(v)) - .map_err(|_| JwtError::TokenCreation)?; - } - - let signer = jws::HS256.signer_from_jwk(&self.key) - .map_err(|_| JwtError::TokenCreation)?; - - jwt::encode_with_signer(&payload, &header, &signer) - .map_err(|_| JwtError::TokenCreation) - } - - pub fn verify_token(&self, token: &str) -> Result { - let verifier = jws::HS256.verifier_from_jwk(&self.key) - .map_err(|_| JwtError::TokenVerification)?; - - let (payload, _) = jwt::decode_with_verifier(token, &verifier) - .map_err(|_| JwtError::InvalidToken)?; - - // Convert payload to JSON value - let mut claims_value = json!({}); - if let Some(Value::Object(map)) = claims_value.as_object_mut() { - for key in payload.claim_names() { - if let Some(value) = payload.claim(key) { - map.insert(key.to_string(), value.clone()); - } - } - } - - let claims = serde_json::from_value(claims_value) - .map_err(|_| JwtError::InvalidToken)?; - - // Check expiration - if let Some(Value::Number(exp)) = payload.claim("exp") { - if let Some(exp) = exp.as_i64() { - let now = OffsetDateTime::now_utc().unix_timestamp(); - if exp < now { - return Err(JwtError::ExpiredToken); - } - } - } - - Ok(claims) - } +#[derive(Debug, Serialize)] +pub struct ErrorResponse { + pub status: &'static str, + pub message: String, +} + +pub async fn auth( + cookie_jar: CookieJar, + // State(data): State>, + mut req: Request, + next: Next, +) -> Result)> { + let token = cookie_jar + .get("token") + .map(|cookie| cookie.value().to_string()) + .or_else(|| { + req.headers() + .get(axum::http::header::AUTHORIZATION) + .and_then(|auth_header| auth_header.to_str().ok()) + .and_then(|auth_value| { + if auth_value.starts_with("Bearer ") { + Some(auth_value[7..].to_owned()) + } else { + None + } + }) + }); + + let token = token.ok_or_else(|| { + let json_error = ErrorResponse { + status: "fail", + message: "You are not logged in, please provide token".to_string(), + }; + (StatusCode::UNAUTHORIZED, Json(json_error)) + })?; + + let claims = decode::( + &token, + // TODO: Replace with code to get secret from env + &DecodingKey::from_secret("my_super_secret".as_ref()), + &Validation::default(), + ) + .map_err(|_| { + let json_error = ErrorResponse { + status: "fail", + message: "Invalid token".to_string(), + }; + (StatusCode::UNAUTHORIZED, Json(json_error)) + })? + .claims; + + let user_id = uuid::Uuid::parse_str(&claims.sub).map_err(|_| { + let json_error = ErrorResponse { + status: "fail", + message: "Invalid token".to_string(), + }; + (StatusCode::UNAUTHORIZED, Json(json_error)) + })?; + + req.extensions_mut().insert(user_id); + Ok(next.run(req).await) }