pub mod auth;
pub mod domains;
pub mod routes;
pub mod upstreams;
pub mod wellknown;
use crate::admin::api::ApiResponse;
use crate::error::{ProxyError, ProxyResult};
use crate::saas::api_keys::ApiKeyService;
use crate::saas::auth::SaasAuthLayer;
use crate::saas::manager::DomainManager;
use crate::saas::verification::DomainVerificationService;
use crate::tls::CertificateStore;
use axum::{
middleware,
routing::{delete, get, post, put},
Router,
};
use sqlx::PgPool;
use std::sync::Arc;
#[derive(Clone)]
pub struct SaasState {
pub domain_manager: Arc<DomainManager>,
pub api_key_service: Arc<ApiKeyService>,
pub auth_layer: Arc<SaasAuthLayer>,
pub pool: PgPool,
pub cert_store: Arc<CertificateStore>,
}
impl SaasState {
pub async fn new(
pool: PgPool,
jwt_secret: Option<String>,
cert_dir: impl AsRef<std::path::Path>,
) -> ProxyResult<Self> {
let verification_service = Arc::new(DomainVerificationService::new());
let domain_manager = Arc::new(DomainManager::new(pool.clone(), verification_service));
let api_key_service = Arc::new(ApiKeyService::new(pool.clone()));
let pool_for_handlers = pool.clone();
let auth_layer = Arc::new(SaasAuthLayer::new(pool, jwt_secret));
let cert_store =
Arc::new(CertificateStore::new(pool_for_handlers.clone(), cert_dir).await?);
Ok(Self {
domain_manager,
api_key_service,
auth_layer,
pool: pool_for_handlers,
cert_store,
})
}
}
pub fn saas_router(state: SaasState) -> Router {
let auth_layer = state.auth_layer.clone();
let public_routes = Router::new().route(
"/.well-known/postrust-verification/{token}",
get(wellknown::handle_verification_challenge),
);
#[cfg(feature = "acme")]
let public_routes = public_routes.route(
"/.well-known/acme-challenge/{token}",
get(wellknown::handle_acme_challenge),
);
let protected_routes = Router::new();
#[cfg(feature = "acme")]
let protected_routes =
protected_routes.route("/domains/{id}/ssl/provision", post(domains::provision_ssl));
let protected_routes = protected_routes
.route("/auth/api-keys", post(auth::create_api_key))
.route("/auth/api-keys", get(auth::list_api_keys))
.route("/auth/api-keys/{id}", delete(auth::revoke_api_key))
.route("/tenant/me", get(auth::get_current_tenant))
.route("/tenant/usage", get(auth::get_tenant_usage))
.route("/domains", get(domains::list_domains))
.route("/domains", post(domains::create_domain))
.route("/domains/{id}", get(domains::get_domain))
.route("/domains/{id}", delete(domains::delete_domain))
.route("/domains/{id}/verify", post(domains::verify_domain))
.route("/domains/{id}/enable", post(domains::enable_domain))
.route("/domains/{id}", put(domains::update_domain))
.route("/domains/{id}/disable", post(domains::disable_domain))
.route(
"/domains/{id}/ssl/upload",
post(domains::upload_certificate),
)
.route("/domains/{domain_id}/routes", get(routes::list_routes))
.route("/domains/{domain_id}/routes", post(routes::create_route))
.route("/domains/{domain_id}/routes/{id}", get(routes::get_route))
.route(
"/domains/{domain_id}/routes/{id}",
put(routes::update_route),
)
.route(
"/domains/{domain_id}/routes/{id}",
delete(routes::delete_route),
)
.route("/upstreams", get(upstreams::list_upstreams))
.route("/upstreams", post(upstreams::create_upstream))
.route("/upstreams/{id}", get(upstreams::get_upstream))
.route("/upstreams/{id}", put(upstreams::update_upstream))
.route("/upstreams/{id}", delete(upstreams::delete_upstream))
.route("/upstreams/{id}/backends", post(upstreams::add_backend))
.route(
"/upstreams/{id}/backends/{backend_id}",
delete(upstreams::remove_backend),
)
.layer(middleware::from_fn_with_state(
auth_layer,
crate::saas::auth::auth_middleware,
))
.with_state(state.clone());
Router::new()
.merge(public_routes.with_state(state.clone()))
.nest("/api/v1", protected_routes)
}
pub fn error_response(error: ProxyError) -> (axum::http::StatusCode, axum::Json<ApiResponse<()>>) {
let status = error.status_code();
let response = ApiResponse::<()>::error(error.to_string());
(status, axum::Json(response))
}