#![cfg(feature = "test-support")]
use axum::body::Body;
use axum::http::{Request, StatusCode};
use kyma_server::auth::{
passwords::hash_password, AuthLayerState, EnvAuthBackend, Role, SessionAuthBackend,
};
use serde_json::Value;
use std::sync::Arc;
use tower::ServiceExt;
fn build_auth_app(
state: &kyma_server::QueryState,
) -> impl tower::Service<
Request<Body>,
Response = axum::http::Response<Body>,
Error = std::convert::Infallible,
Future = impl std::future::Future<
Output = Result<axum::http::Response<Body>, std::convert::Infallible>,
>,
> {
let catalog = state.catalog.clone();
let backend: Arc<dyn kyma_server::auth::AuthBackend> = Arc::new(
SessionAuthBackend::new(catalog.clone(), EnvAuthBackend::from_str(""), true),
);
let login_router = kyma_server::auth_handler::auth_login_router(catalog.clone());
let session_router =
kyma_server::auth_handler::auth_session_router(catalog.clone()).layer(
axum::middleware::from_fn_with_state(
AuthLayerState {
backend,
required: Role::Read,
},
kyma_server::auth::require_role_middleware,
),
);
login_router.merge(session_router)
}
#[tokio::test]
async fn auth_handler_full_flow() {
let state = kyma_server::test_support::seeded_state_empty().await;
let cat = &state.catalog;
let phc = hash_password("pw123").unwrap();
cat.create_user("admin", &phc, "admin").await.unwrap();
let app = build_auth_app(&state);
let login_body = serde_json::json!({ "username": "admin", "password": "pw123" });
let req = Request::builder()
.method("POST")
.uri("/v1/auth/login")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_string(&login_body).unwrap()))
.unwrap();
let resp = app.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK, "login should succeed");
let body_bytes = axum::body::to_bytes(resp.into_body(), 1024 * 1024)
.await
.unwrap();
let body: Value = serde_json::from_slice(&body_bytes).unwrap();
let token = body["access_token"]
.as_str()
.expect("access_token in login response")
.to_owned();
assert!(!token.is_empty(), "access token must be non-empty");
assert!(
body["refresh_token"]
.as_str()
.is_some_and(|t| !t.is_empty()),
"refresh token must be present and non-empty"
);
let app = build_auth_app(&state);
let req = Request::builder()
.method("GET")
.uri("/v1/auth/me")
.header("Authorization", format!("Bearer {}", token))
.body(Body::empty())
.unwrap();
let resp = app.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK, "/me should return 200");
let body_bytes = axum::body::to_bytes(resp.into_body(), 1024 * 1024)
.await
.unwrap();
let body: Value = serde_json::from_slice(&body_bytes).unwrap();
assert_eq!(body["username"].as_str().unwrap(), "admin");
assert_eq!(body["role"].as_str().unwrap(), "admin");
let app = build_auth_app(&state);
let req = Request::builder()
.method("POST")
.uri("/v1/auth/logout")
.header("Authorization", format!("Bearer {}", token))
.body(Body::empty())
.unwrap();
let resp = app.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::NO_CONTENT, "logout should return 204");
let app = build_auth_app(&state);
let req = Request::builder()
.method("GET")
.uri("/v1/auth/me")
.header("Authorization", format!("Bearer {}", token))
.body(Body::empty())
.unwrap();
let resp = app.oneshot(req).await.unwrap();
assert_eq!(
resp.status(),
StatusCode::UNAUTHORIZED,
"/me with revoked token should return 401"
);
let app = build_auth_app(&state);
let bad_login = serde_json::json!({ "username": "admin", "password": "wrongpw" });
let req = Request::builder()
.method("POST")
.uri("/v1/auth/login")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_string(&bad_login).unwrap()))
.unwrap();
let resp = app.oneshot(req).await.unwrap();
assert_eq!(
resp.status(),
StatusCode::UNAUTHORIZED,
"wrong password should return 401"
);
}
#[tokio::test]
async fn login_nonexistent_user_returns_401() {
let state = kyma_server::test_support::seeded_state_empty().await;
let app = build_auth_app(&state);
let body = serde_json::json!({ "username": "nobody", "password": "any" });
let req = Request::builder()
.method("POST")
.uri("/v1/auth/login")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_string(&body).unwrap()))
.unwrap();
let resp = app.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}