Saving changes

This commit is contained in:
kdeng00
2025-07-30 19:08:44 -04:00
parent 9a60b84538
commit c8ffd8411d
3 changed files with 110 additions and 74 deletions
Generated
+35
View File
@@ -108,6 +108,29 @@ dependencies = [
"tracing", "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]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.75" version = "0.3.75"
@@ -243,6 +266,17 @@ dependencies = [
"unicode-xid", "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]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@@ -788,6 +822,7 @@ name = "icarus"
version = "0.1.100" version = "0.1.100"
dependencies = [ dependencies = [
"axum", "axum",
"axum-extra",
"base64 0.21.7", "base64 0.21.7",
"common-multipart-rfc7578", "common-multipart-rfc7578",
"futures", "futures",
+1
View File
@@ -6,6 +6,7 @@ rust-version = "1.88"
[dependencies] [dependencies]
axum = { version = "0.8.4", features = ["multipart"] } axum = { version = "0.8.4", features = ["multipart"] }
axum-extra = { version = "0.10.1", features = ["cookie"] }
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = { version = "1.0.140" } serde_json = { version = "1.0.140" }
tower = { version = "0.5.2", features = ["full"] } tower = { version = "0.5.2", features = ["full"] }
+74 -74
View File
@@ -1,24 +1,25 @@
use std::collections::BTreeMap; // use std::collections::BTreeMap;
// use std::sync::Arc;
use axum::{ use axum::{
// extract::State,
http::{Request, StatusCode}, http::{Request, StatusCode},
middleware::Next, middleware::Next,
response::{IntoResponse, Response}, response::{IntoResponse},
Json, Json,
}; };
use josekit::{ use axum_extra::extract::cookie::CookieJar;
jwt::{self, JwtPayload}, use jsonwebtoken::{decode, DecodingKey, Validation};
jws::{self, JwsHeader, JwsSigner, JwsVerifier},
jwk::Jwk,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Value}; // use serde_json::{json, Value};
use thiserror::Error; use thiserror::Error;
use time::OffsetDateTime; // use time::OffsetDateTime;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct UserClaims { pub struct UserClaims {
pub aud: String, // Audience
pub sub: String, // Subject (user ID) pub sub: String, // Subject (user ID)
pub exp: i64, // Expiration time (UTC timestamp) pub exp: i64, // Expiration time (UTC timestamp)
pub iat: i64, // Issued at (UTC timestamp) pub iat: i64, // Issued at (UTC timestamp)
@@ -27,8 +28,6 @@ pub struct UserClaims {
} }
// use time::OffsetDateTime;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum JwtError { pub enum JwtError {
#[error("Token creation failed")] #[error("Token creation failed")]
@@ -43,71 +42,72 @@ pub enum JwtError {
InvalidKey, InvalidKey,
} }
/*
pub struct JwtAuth { pub struct JwtAuth {
key: Jwk, // Now properly parameterized key: Jwk, // Now properly parameterized
} }
*/
impl JwtAuth {
pub fn new(secret: &str) -> Result<Self, JwtError> {
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<String, JwtError> { #[derive(Debug, Serialize)]
let mut header = JwsHeader::new(); pub struct ErrorResponse {
header.set_token_type("JWT"); pub status: &'static str,
pub message: String,
let claims_value = serde_json::to_value(claims) }
.map_err(|_| JwtError::SerializationError)?;
let claims_map = claims_value.as_object() pub async fn auth<B>(
.ok_or(JwtError::SerializationError)? cookie_jar: CookieJar,
.to_owned(); // State(data): State<Arc<AppState>>,
mut req: Request<axum::body::Body>,
let mut payload = JwtPayload::new(); next: Next,
for (k, v) in claims_map { ) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
payload.set_claim(&k, Some(v)) let token = cookie_jar
.map_err(|_| JwtError::TokenCreation)?; .get("token")
} .map(|cookie| cookie.value().to_string())
.or_else(|| {
let signer = jws::HS256.signer_from_jwk(&self.key) req.headers()
.map_err(|_| JwtError::TokenCreation)?; .get(axum::http::header::AUTHORIZATION)
.and_then(|auth_header| auth_header.to_str().ok())
jwt::encode_with_signer(&payload, &header, &signer) .and_then(|auth_value| {
.map_err(|_| JwtError::TokenCreation) if auth_value.starts_with("Bearer ") {
} Some(auth_value[7..].to_owned())
} else {
pub fn verify_token(&self, token: &str) -> Result<UserClaims, JwtError> { None
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)?; let token = token.ok_or_else(|| {
let json_error = ErrorResponse {
// Convert payload to JSON value status: "fail",
let mut claims_value = json!({}); message: "You are not logged in, please provide token".to_string(),
if let Some(Value::Object(map)) = claims_value.as_object_mut() { };
for key in payload.claim_names() { (StatusCode::UNAUTHORIZED, Json(json_error))
if let Some(value) = payload.claim(key) { })?;
map.insert(key.to_string(), value.clone());
} let claims = decode::<UserClaims>(
} &token,
} // TODO: Replace with code to get secret from env
&DecodingKey::from_secret("my_super_secret".as_ref()),
let claims = serde_json::from_value(claims_value) &Validation::default(),
.map_err(|_| JwtError::InvalidToken)?; )
.map_err(|_| {
// Check expiration let json_error = ErrorResponse {
if let Some(Value::Number(exp)) = payload.claim("exp") { status: "fail",
if let Some(exp) = exp.as_i64() { message: "Invalid token".to_string(),
let now = OffsetDateTime::now_utc().unix_timestamp(); };
if exp < now { (StatusCode::UNAUTHORIZED, Json(json_error))
return Err(JwtError::ExpiredToken); })?
} .claims;
}
} let user_id = uuid::Uuid::parse_str(&claims.sub).map_err(|_| {
let json_error = ErrorResponse {
Ok(claims) status: "fail",
} message: "Invalid token".to_string(),
};
(StatusCode::UNAUTHORIZED, Json(json_error))
})?;
req.extensions_mut().insert(user_id);
Ok(next.run(req).await)
} }