Files
iam-service/tests/code2token_modes.rs
2026-02-03 17:31:08 +08:00

181 lines
6.1 KiB
Rust

use axum::body::Body;
use axum::http::{Request, StatusCode};
use iam_service::presentation::http::api;
use iam_service::presentation::http::state::AppState;
use iam_service::infrastructure::repositories::tenant_config_repo::TenantConfigRepoPg;
use iam_service::models::CreateTenantRequest;
use iam_service::models::CreateUserRequest;
use iam_service::application::services::{
AppService, AuthService, AuthorizationService, ClientService, PermissionService, RoleService,
TenantService, UserService,
};
use redis::aio::ConnectionManager;
use sqlx::PgPool;
use tower::ServiceExt;
use uuid::Uuid;
#[tokio::test]
async fn code2token_modes_requirements() -> Result<(), Box<dyn std::error::Error>> {
let database_url = match std::env::var("DATABASE_URL") {
Ok(v) if !v.trim().is_empty() => v,
_ => return Ok(()),
};
let redis_url = match std::env::var("REDIS_URL") {
Ok(v) if !v.trim().is_empty() => v,
_ => return Ok(()),
};
let auth_code_jwt_secret = match std::env::var("AUTH_CODE_JWT_SECRET") {
Ok(v) if !v.trim().is_empty() => v,
_ => return Ok(()),
};
let internal_psk = match std::env::var("INTERNAL_EXCHANGE_PSK") {
Ok(v) if !v.trim().is_empty() => v,
_ => return Ok(()),
};
let pool = PgPool::connect(&database_url).await?;
let redis = redis::Client::open(redis_url)?;
let redis: ConnectionManager = ConnectionManager::new(redis).await?;
let auth_service = AuthService::new(pool.clone(), "test".to_string());
let client_service = ClientService::new(pool.clone(), 30);
let user_service = UserService::new(pool.clone());
let role_service = RoleService::new(pool.clone());
let tenant_service = TenantService::new(pool.clone());
let authorization_service = AuthorizationService::new(pool.clone());
let app_service = AppService::new(pool.clone());
let permission_service = PermissionService::new(pool.clone());
let tenant_config_repo = std::sync::Arc::new(TenantConfigRepoPg::new(pool.clone()));
let state = AppState {
auth_service: auth_service.clone(),
client_service: client_service.clone(),
user_service,
role_service,
tenant_service: tenant_service.clone(),
authorization_service,
app_service,
permission_service,
redis,
auth_code_jwt_secret: auth_code_jwt_secret.clone(),
tenant_config_repo,
};
let tenant = tenant_service
.create_tenant(CreateTenantRequest {
name: format!("t-{}", Uuid::new_v4()),
config: None,
})
.await?;
let email = format!("u{}@example.com", Uuid::new_v4());
let password = "P@ssw0rd123!".to_string();
let _user = auth_service
.register(
tenant.id,
CreateUserRequest {
email: email.clone(),
password: password.clone(),
},
)
.await?;
let client_id = format!("cms{}", Uuid::new_v4().to_string().replace('-', ""));
let client_id = &client_id[..std::cmp::min(24, client_id.len())];
let redirect_uri = "http://localhost:5031/auth/callback".to_string();
let client_secret = client_service
.create_client(
client_id.to_string(),
Some("CMS".to_string()),
Some(vec![redirect_uri.clone()]),
)
.await?;
let app = api::build_app(state);
let login_code_req = serde_json::json!({
"clientId": client_id,
"redirectUri": redirect_uri,
"email": email,
"password": password
});
let resp = app
.clone()
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/auth/login-code")
.header("Content-Type", "application/json")
.header("X-Tenant-ID", tenant.id.to_string())
.body(Body::from(login_code_req.to_string()))?,
)
.await?;
assert_eq!(resp.status(), StatusCode::OK);
let body = axum::body::to_bytes(resp.into_body(), usize::MAX).await?;
let v: serde_json::Value = serde_json::from_slice(&body)?;
let redirect_to = v["data"]["redirectTo"].as_str().unwrap_or_default();
let url = url::Url::parse(redirect_to)?;
let code = url
.query_pairs()
.find(|(k, _)| k == "code")
.map(|(_, v)| v.to_string())
.unwrap_or_default();
assert!(!code.is_empty());
let code2token_req = serde_json::json!({
"code": code,
"clientId": client_id,
"clientSecret": "wrong"
});
let resp = app
.clone()
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/auth/code2token")
.header("Content-Type", "application/json")
.header("X-Tenant-ID", tenant.id.to_string())
.body(Body::from(code2token_req.to_string()))?,
)
.await?;
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
let code2token_req_missing_tenant = serde_json::json!({
"code": url.query_pairs().find(|(k, _)| k == "code").unwrap().1,
"clientId": client_id,
"clientSecret": client_secret
});
let resp = app
.clone()
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/auth/code2token")
.header("Content-Type", "application/json")
.body(Body::from(code2token_req_missing_tenant.to_string()))?,
)
.await?;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let code2token_req_internal = serde_json::json!({
"code": url.query_pairs().find(|(k, _)| k == "code").unwrap().1,
"clientId": client_id,
"clientSecret": client_secret
});
let resp = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/internal/auth/code2token")
.header("Content-Type", "application/json")
.header("X-Internal-Token", internal_psk)
.body(Body::from(code2token_req_internal.to_string()))?,
)
.await?;
assert_eq!(resp.status(), StatusCode::OK);
Ok(())
}