test(web-gateway): add auth middleware and WebSocket token validation tests
- 6 auth middleware tests: missing header, malformed Bearer, invalid JWT, wrong secret, expired token, valid token passthrough - 4 WebSocket token validation tests: valid token, invalid token, wrong secret, expired token - Web-gateway now has 36 tests (up from 26) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,3 +33,145 @@ pub async fn auth_middleware(
|
||||
request.extensions_mut().insert(token_data.claims);
|
||||
Ok(next.run(request).await)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use axum::body::Body;
|
||||
use axum::http::{Request as HttpRequest, StatusCode};
|
||||
use axum::middleware;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||
use tower::ServiceExt;
|
||||
|
||||
use crate::auth::claims::Claims;
|
||||
|
||||
fn test_config(secret: &str) -> Arc<GatewayConfig> {
|
||||
Arc::new(GatewayConfig {
|
||||
jwt_secret: secret.to_string(),
|
||||
..GatewayConfig::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn make_token(secret: &str, claims: &Claims) -> String {
|
||||
let key = EncodingKey::from_secret(secret.as_bytes());
|
||||
encode(&Header::default(), claims, &key).unwrap()
|
||||
}
|
||||
|
||||
fn valid_claims() -> Claims {
|
||||
Claims {
|
||||
sub: "user-1".into(),
|
||||
exp: u64::MAX,
|
||||
iat: 1_700_000_000,
|
||||
jti: "t1".into(),
|
||||
roles: vec!["trader".into()],
|
||||
permissions: vec!["read:data".into()],
|
||||
}
|
||||
}
|
||||
|
||||
fn test_app(config: Arc<GatewayConfig>) -> Router {
|
||||
let handler = get(|| async { "ok" });
|
||||
Router::new()
|
||||
.route("/protected", handler)
|
||||
.layer(middleware::from_fn_with_state(config.clone(), auth_middleware))
|
||||
.with_state(config)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_missing_auth_header_returns_401() {
|
||||
let config = test_config("secret");
|
||||
let app = test_app(config);
|
||||
|
||||
let req = HttpRequest::builder()
|
||||
.uri("/protected")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_malformed_bearer_prefix_returns_401() {
|
||||
let config = test_config("secret");
|
||||
let app = test_app(config);
|
||||
|
||||
let req = HttpRequest::builder()
|
||||
.uri("/protected")
|
||||
.header("Authorization", "Token abc123")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_invalid_jwt_returns_401() {
|
||||
let config = test_config("secret");
|
||||
let app = test_app(config);
|
||||
|
||||
let req = HttpRequest::builder()
|
||||
.uri("/protected")
|
||||
.header("Authorization", "Bearer not.a.valid.jwt")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_wrong_secret_returns_401() {
|
||||
let config = test_config("correct-secret");
|
||||
let app = test_app(config);
|
||||
|
||||
let token = make_token("wrong-secret", &valid_claims());
|
||||
let req = HttpRequest::builder()
|
||||
.uri("/protected")
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_expired_token_returns_401() {
|
||||
let config = test_config("secret");
|
||||
let app = test_app(config);
|
||||
|
||||
let mut claims = valid_claims();
|
||||
claims.exp = 1; // Expired in 1970
|
||||
let token = make_token("secret", &claims);
|
||||
|
||||
let req = HttpRequest::builder()
|
||||
.uri("/protected")
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_valid_token_passes_through() {
|
||||
let config = test_config("secret");
|
||||
let app = test_app(config);
|
||||
|
||||
let token = make_token("secret", &valid_claims());
|
||||
let req = HttpRequest::builder()
|
||||
.uri("/protected")
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,3 +126,74 @@ async fn handle_ws_connection(socket: WebSocket, state: AppState) {
|
||||
|
||||
tracing::debug!("WebSocket connection closed");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||
|
||||
use crate::auth::claims::Claims;
|
||||
|
||||
fn test_config(secret: &str) -> Arc<GatewayConfig> {
|
||||
Arc::new(GatewayConfig {
|
||||
jwt_secret: secret.to_string(),
|
||||
..GatewayConfig::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn make_token(secret: &str, claims: &Claims) -> String {
|
||||
let key = EncodingKey::from_secret(secret.as_bytes());
|
||||
encode(&Header::default(), claims, &key).unwrap()
|
||||
}
|
||||
|
||||
fn valid_claims() -> Claims {
|
||||
Claims {
|
||||
sub: "user-1".into(),
|
||||
exp: u64::MAX,
|
||||
iat: 1_700_000_000,
|
||||
jti: "t1".into(),
|
||||
roles: vec!["trader".into()],
|
||||
permissions: vec!["read:data".into()],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_ws_token_returns_claims() {
|
||||
let config = test_config("ws-secret");
|
||||
let claims = valid_claims();
|
||||
let token = make_token("ws-secret", &claims);
|
||||
|
||||
let result = validate_ws_token(&token, &config);
|
||||
assert!(result.is_ok());
|
||||
let extracted = result.unwrap();
|
||||
assert_eq!(extracted.sub, "user-1");
|
||||
assert_eq!(extracted.roles, vec!["trader"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_ws_token_returns_unauthorized() {
|
||||
let config = test_config("ws-secret");
|
||||
let result = validate_ws_token("not.a.jwt", &config);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_secret_ws_token_returns_unauthorized() {
|
||||
let config = test_config("correct-secret");
|
||||
let token = make_token("wrong-secret", &valid_claims());
|
||||
let result = validate_ws_token(&token, &config);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expired_ws_token_returns_unauthorized() {
|
||||
let config = test_config("ws-secret");
|
||||
let mut claims = valid_claims();
|
||||
claims.exp = 1; // Expired in 1970
|
||||
let token = make_token("ws-secret", &claims);
|
||||
|
||||
let result = validate_ws_token(&token, &config);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user