Skip to main content

chamber_api/
server.rs

1use axum::{
2    Router,
3    extract::DefaultBodyLimit,
4    http::{Method, header::CONTENT_TYPE},
5    middleware,
6    routing::{delete, get, patch, post, put},
7};
8use std::sync::Arc;
9use tokio::net::TcpListener;
10use tower_http::cors::{Any, CorsLayer};
11use tracing::{info, warn};
12
13use crate::auth::{AuthState, auth_middleware};
14use crate::handlers;
15use chamber_vault::{Vault, VaultManager};
16
17pub struct ApiServer {
18    app: Router,
19    listener: TcpListener,
20}
21
22#[derive(Clone)]
23pub struct AppState {
24    pub vault: Arc<tokio::sync::Mutex<Vault>>,
25    pub vault_manager: Arc<tokio::sync::Mutex<VaultManager>>,
26    pub auth: AuthState,
27}
28
29impl ApiServer {
30    /// # Errors
31    /// This function will return an error if:
32    /// - The TCP binding to the specified address fails.
33    /// - There are issues configuring the router.
34    pub async fn new(vault: Vault, vault_manager: VaultManager, bind_address: &str) -> color_eyre::Result<Self> {
35        let state = AppState {
36            vault: Arc::new(tokio::sync::Mutex::new(vault)),
37            vault_manager: Arc::new(tokio::sync::Mutex::new(vault_manager)),
38            auth: AuthState::new(),
39        };
40
41        let app = build_router(Arc::new(state))?;
42
43        let listener = TcpListener::bind(bind_address).await?;
44        info!("API server will bind to: {}", bind_address);
45
46        Ok(Self { app, listener })
47    }
48
49    /// # Errors
50    /// This function will return an error if:
51    /// - Retrieving the local socket's address fails.
52    /// - Axum fails to serve the application.
53    pub async fn serve(self) -> color_eyre::Result<()> {
54        let addr = self.listener.local_addr()?;
55        info!("API server listening on http://{}", addr);
56        warn!("API server is running on localhost only - not accessible from other machines");
57
58        axum::serve(self.listener, self.app).await?;
59        Ok(())
60    }
61
62    /// # Errors
63    ///
64    /// Returns an error if getting the local address from the TCP listener fails.
65    pub fn local_addr(&self) -> Result<std::net::SocketAddr, std::io::Error> {
66        self.listener.local_addr()
67    }
68}
69
70/// # Errors
71///
72/// Returns an error if there are any issues configuring the router or applying the middleware.
73pub fn build_router(state: Arc<AppState>) -> color_eyre::Result<Router> {
74    let cors = CorsLayer::new()
75        .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE, Method::PATCH])
76        .allow_headers([CONTENT_TYPE])
77        .allow_origin(Any);
78
79    let app = Router::new()
80        .route("/api/v1/health", get(handlers::health))
81        // Authentication endpoints (no auth middleware needed)
82        .route("/api/v1/auth/login", post(handlers::login))
83        .route("/api/v1/session/unlock", post(handlers::session_unlock))
84        // Protected endpoints - these will have the auth middleware applied
85        .route("/api/v1/auth/logout", post(handlers::logout))
86        .route("/api/v1/session/lock", post(handlers::session_lock))
87        // Items
88        .route("/api/v1/items", get(handlers::list_items))
89        .route("/api/v1/items", post(handlers::create_item))
90        .route("/api/v1/items/search", get(handlers::search_items))
91        .route("/api/v1/items/counts", get(handlers::get_counts))
92        .route("/api/v1/items/{id}", get(handlers::get_item))
93        .route("/api/v1/items/{id}", put(handlers::update_item))
94        .route("/api/v1/items/{id}", delete(handlers::delete_item))
95        .route("/api/v1/items/{id}/value", get(handlers::get_item_value))
96        .route("/api/v1/items/{id}/copy", post(handlers::copy_item_to_clipboard))
97        // Password generation
98        .route("/api/v1/passwords/generate", post(handlers::generate_password))
99        .route(
100            "/api/v1/passwords/memorable",
101            post(handlers::generate_memorable_password_handler),
102        )
103        // Import/Export
104        .route("/api/v1/import", post(handlers::import_items_handler))
105        .route("/api/v1/export", post(handlers::export_items_handler))
106        .route("/api/v1/import/dry-run", post(handlers::dry_run_import))
107        // Vault management
108        .route("/api/v1/vaults", get(handlers::list_vaults))
109        .route("/api/v1/vaults", post(handlers::create_vault))
110        .route("/api/v1/vaults/{id}/switch", post(handlers::switch_vault))
111        .route("/api/v1/vaults/{id}", patch(handlers::update_vault))
112        .route("/api/v1/vaults/{id}", delete(handlers::delete_vault))
113        .route("/api/v1/health/report", get(handlers::health_report))
114        .route("/api/v1/stats", get(handlers::stats))
115        .layer(middleware::from_fn_with_state(Arc::clone(&state), auth_middleware))
116        .layer(cors)
117        .layer(DefaultBodyLimit::max(1024 * 1024))
118        .with_state(state);
119
120    Ok(app)
121}