Verify API auth token
This commit is contained in:
113
src/extractors/client_auth.rs
Normal file
113
src/extractors/client_auth.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use crate::user::{APIClient, APIClientID, UserConfig, UserID};
|
||||
use actix_web::dev::Payload;
|
||||
use actix_web::{FromRequest, HttpRequest};
|
||||
use jwt_simple::common::VerificationOptions;
|
||||
use jwt_simple::prelude::{HS256Key, MACLike};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub struct APIClientAuth {
|
||||
pub user: UserConfig,
|
||||
client: APIClient,
|
||||
payload: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
struct JWTClaims {}
|
||||
|
||||
impl APIClientAuth {
|
||||
async fn extract_auth(req: &HttpRequest) -> Result<Self, actix_web::Error> {
|
||||
let Some(token) = req.headers().get("x-client-auth") else {
|
||||
return Err(actix_web::error::ErrorBadRequest(
|
||||
"Missing authentication header!",
|
||||
));
|
||||
};
|
||||
let Ok(jwt_token) = token.to_str() else {
|
||||
return Err(actix_web::error::ErrorBadRequest(
|
||||
"Failed to decode token as string!",
|
||||
));
|
||||
};
|
||||
|
||||
let metadata = match jwt_simple::token::Token::decode_metadata(jwt_token) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
log::error!("Failed to decode JWT header metadata! {e}");
|
||||
return Err(actix_web::error::ErrorBadRequest(
|
||||
"Failed to decode JWT header metadata!",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let Some(kid) = metadata.key_id() else {
|
||||
return Err(actix_web::error::ErrorBadRequest(
|
||||
"Missing key id in request!",
|
||||
));
|
||||
};
|
||||
|
||||
let Some((user_id, client_id)) = kid.split_once("#") else {
|
||||
return Err(actix_web::error::ErrorBadRequest(
|
||||
"Invalid key format (missing part)!",
|
||||
));
|
||||
};
|
||||
|
||||
let (Ok(user_id), Ok(client_id)) =
|
||||
(urlencoding::decode(user_id), urlencoding::decode(client_id))
|
||||
else {
|
||||
return Err(actix_web::error::ErrorBadRequest(
|
||||
"Invalid key format (decoding failed)!",
|
||||
));
|
||||
};
|
||||
|
||||
// Fetch user
|
||||
const USER_NOT_FOUND_ERROR: &str = "User not found!";
|
||||
let user = match UserConfig::load(&UserID(user_id.to_string()), false).await {
|
||||
Ok(u) => u,
|
||||
Err(e) => {
|
||||
log::error!("Failed to get user information! {e}");
|
||||
return Err(actix_web::error::ErrorForbidden(USER_NOT_FOUND_ERROR));
|
||||
}
|
||||
};
|
||||
|
||||
// Find client
|
||||
let Ok(client_id) = APIClientID::from_str(&client_id) else {
|
||||
return Err(actix_web::error::ErrorBadRequest("Invalid token format!"));
|
||||
};
|
||||
let Some(client) = user.find_client_by_id(&client_id) else {
|
||||
log::error!("Client not found for user!");
|
||||
return Err(actix_web::error::ErrorForbidden(USER_NOT_FOUND_ERROR));
|
||||
};
|
||||
|
||||
// Decode JWT
|
||||
let key = HS256Key::from_bytes(client.secret.as_bytes());
|
||||
let claims =
|
||||
match key.verify_token::<JWTClaims>(jwt_token, Some(VerificationOptions::default())) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
log::error!("JWT validation failed! {e}");
|
||||
return Err(actix_web::error::ErrorForbidden("JWT validation failed!"));
|
||||
}
|
||||
};
|
||||
|
||||
// TODO : check timing
|
||||
// TODO : check URI & verb
|
||||
// TODO : handle payload
|
||||
// TODO : check read only access
|
||||
// TODO : update last use (if required)
|
||||
// TODO : check for IP restriction
|
||||
|
||||
Ok(Self {
|
||||
client: client.clone(),
|
||||
payload: None,
|
||||
user,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRequest for APIClientAuth {
|
||||
type Error = actix_web::Error;
|
||||
type Future = futures_util::future::LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
||||
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
let req = req.clone();
|
||||
Box::pin(async move { Self::extract_auth(&req).await })
|
||||
}
|
||||
}
|
1
src/extractors/mod.rs
Normal file
1
src/extractors/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod client_auth;
|
Reference in New Issue
Block a user