Skip to main content

postrust_proxy/saas/handlers/
mod.rs

1//! API handlers for SaaS domain management.
2
3pub mod auth;
4pub mod domains;
5pub mod routes;
6pub mod upstreams;
7pub mod wellknown;
8
9use crate::admin::api::ApiResponse;
10use crate::error::ProxyError;
11use crate::saas::api_keys::ApiKeyService;
12use crate::saas::auth::SaasAuthLayer;
13use crate::saas::manager::DomainManager;
14use crate::saas::verification::DomainVerificationService;
15use axum::{
16    middleware,
17    routing::{delete, get, post, put},
18    Router,
19};
20use sqlx::PgPool;
21use std::sync::Arc;
22
23/// Shared state for SaaS API handlers.
24#[derive(Clone)]
25pub struct SaasState {
26    pub domain_manager: Arc<DomainManager>,
27    pub api_key_service: Arc<ApiKeyService>,
28    pub auth_layer: Arc<SaasAuthLayer>,
29}
30
31impl SaasState {
32    /// Create new SaaS state.
33    pub fn new(pool: PgPool, jwt_secret: Option<String>) -> Self {
34        let verification_service = Arc::new(DomainVerificationService::new());
35        let domain_manager = Arc::new(DomainManager::new(pool.clone(), verification_service));
36        let api_key_service = Arc::new(ApiKeyService::new(pool.clone()));
37        let auth_layer = Arc::new(SaasAuthLayer::new(pool, jwt_secret));
38
39        Self {
40            domain_manager,
41            api_key_service,
42            auth_layer,
43        }
44    }
45}
46
47/// Create the SaaS API router.
48pub fn saas_router(state: SaasState) -> Router {
49    let auth_layer = state.auth_layer.clone();
50
51    // Public routes (no auth required)
52    let public_routes = Router::new().route(
53        "/.well-known/postrust-verification/:token",
54        get(wellknown::handle_verification_challenge),
55    );
56
57    // Protected routes (require authentication)
58    let protected_routes = Router::new()
59        // Auth / API Keys
60        .route("/auth/api-keys", post(auth::create_api_key))
61        .route("/auth/api-keys", get(auth::list_api_keys))
62        .route("/auth/api-keys/:id", delete(auth::revoke_api_key))
63        // Tenant
64        .route("/tenant/me", get(auth::get_current_tenant))
65        .route("/tenant/usage", get(auth::get_tenant_usage))
66        // Domains
67        .route("/domains", get(domains::list_domains))
68        .route("/domains", post(domains::create_domain))
69        .route("/domains/:id", get(domains::get_domain))
70        .route("/domains/:id", delete(domains::delete_domain))
71        .route("/domains/:id/verify", post(domains::verify_domain))
72        .route("/domains/:id/enable", post(domains::enable_domain))
73        .route("/domains/:id/disable", post(domains::disable_domain))
74        // Domain Routes
75        .route("/domains/:domain_id/routes", get(routes::list_routes))
76        .route("/domains/:domain_id/routes", post(routes::create_route))
77        .route("/domains/:domain_id/routes/:id", get(routes::get_route))
78        .route("/domains/:domain_id/routes/:id", put(routes::update_route))
79        .route(
80            "/domains/:domain_id/routes/:id",
81            delete(routes::delete_route),
82        )
83        // Upstreams
84        .route("/upstreams", get(upstreams::list_upstreams))
85        .route("/upstreams", post(upstreams::create_upstream))
86        .route("/upstreams/:id", get(upstreams::get_upstream))
87        .route("/upstreams/:id", put(upstreams::update_upstream))
88        .route("/upstreams/:id", delete(upstreams::delete_upstream))
89        .route("/upstreams/:id/backends", post(upstreams::add_backend))
90        .route(
91            "/upstreams/:id/backends/:backend_id",
92            delete(upstreams::remove_backend),
93        )
94        .layer(middleware::from_fn_with_state(
95            auth_layer,
96            crate::saas::auth::auth_middleware,
97        ))
98        .with_state(state.clone());
99
100    // Combine public and protected routes
101    Router::new()
102        .merge(public_routes.with_state(state.clone()))
103        .nest("/api/v1", protected_routes)
104}
105
106/// Helper to convert ProxyError to API response.
107pub fn error_response(error: ProxyError) -> (axum::http::StatusCode, axum::Json<ApiResponse<()>>) {
108    let status = error.status_code();
109    let response = ApiResponse::<()>::error(error.to_string());
110    (status, axum::Json(response))
111}