Saving changes
This commit is contained in:
Generated
+35
@@ -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",
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user