Saving changes
This commit is contained in:
Generated
+35
@@ -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",
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
+68
-68
@@ -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<Self, JwtError> {
|
||||
let mut key = Jwk::new("oct");
|
||||
key.set_parameter("k", Some(json!(base64::encode(secret))))
|
||||
.map_err(|_| JwtError::InvalidKey)?;
|
||||
Ok(Self { key })
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ErrorResponse {
|
||||
pub status: &'static str,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub fn create_token(&self, claims: &UserClaims) -> Result<String, JwtError> {
|
||||
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)?;
|
||||
pub async fn auth<B>(
|
||||
cookie_jar: CookieJar,
|
||||
// State(data): State<Arc<AppState>>,
|
||||
mut req: Request<axum::body::Body>,
|
||||
next: Next,
|
||||
) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
|
||||
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 signer = jws::HS256.signer_from_jwk(&self.key)
|
||||
.map_err(|_| JwtError::TokenCreation)?;
|
||||
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))
|
||||
})?;
|
||||
|
||||
jwt::encode_with_signer(&payload, &header, &signer)
|
||||
.map_err(|_| JwtError::TokenCreation)
|
||||
}
|
||||
let claims = decode::<UserClaims>(
|
||||
&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;
|
||||
|
||||
pub fn verify_token(&self, token: &str) -> Result<UserClaims, JwtError> {
|
||||
let verifier = jws::HS256.verifier_from_jwk(&self.key)
|
||||
.map_err(|_| JwtError::TokenVerification)?;
|
||||
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))
|
||||
})?;
|
||||
|
||||
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)
|
||||
}
|
||||
req.extensions_mut().insert(user_id);
|
||||
Ok(next.run(req).await)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user