reasonkit-web 0.1.7

High-performance MCP server for browser automation, web capture, and content extraction. Rust-powered CDP client for AI agents.
Documentation
//! # ReasonKit Portal Module
//!
//! User portal functionality for account management, settings synchronization,
//! and data export capabilities.
//!
//! ## Architecture
//!
//! ```text
//! Portal Module
//! ├── auth       - Authentication (JWT, OAuth, 2FA)
//! ├── profile    - User profile management
//! ├── settings   - Settings storage and sync
//! ├── export     - Data export (JSON, CSV, PDF)
//! ├── sync       - Real-time synchronization
//! ├── api_keys   - API key management
//! └── db         - PostgreSQL database layer (feature: portal)
//! ```
//!
//! ## Integration Points
//!
//! - **Stripe Module**: Subscription management
//! - **Security Module**: Rate limiting, CORS
//! - **MCP Server**: CLI authentication
//!
//! ## Feature Flags
//!
//! - `portal`: Enable PostgreSQL database support

pub mod api_keys;
pub mod auth;
pub mod export;
pub mod profile;
pub mod settings;
pub mod sync;

/// Database layer (requires `portal` feature)
#[cfg(feature = "portal")]
pub mod db;

/// Database-backed authentication handlers (requires `portal` feature)
#[cfg(feature = "portal")]
pub mod auth_db;

/// Database-backed profile handlers (requires `portal` feature)
#[cfg(feature = "portal")]
pub mod profile_db;

/// Database-backed settings handlers (requires `portal` feature)
#[cfg(feature = "portal")]
pub mod settings_db;

/// Database-backed API key handlers (requires `portal` feature)
#[cfg(feature = "portal")]
pub mod api_keys_db;

/// Rate limiting middleware (requires `portal` feature)
#[cfg(feature = "portal")]
pub mod rate_limiting;

/// Usage tracking middleware and handlers (requires `portal` feature)
#[cfg(feature = "portal")]
pub mod usage_tracking;

/// Database-backed export handlers (requires `portal` feature)
#[cfg(feature = "portal")]
pub mod export_db;

/// Database-backed sync state management (requires `portal` feature)
#[cfg(feature = "portal")]
pub mod sync_db;

/// WebSocket handler for real-time synchronization (requires `portal` feature)
#[cfg(feature = "portal")]
pub mod sync_ws;

/// Authentication middleware
pub mod middleware;

use axum::{
    routing::{delete, get, post, put},
    Router,
};

pub use api_keys::{ApiKey, ApiKeyScope};
pub use auth::{AuthConfig, AuthService, Claims, TokenPair};
pub use export::{ExportConfig, ExportFormat, ExportJob, ExportService};
pub use profile::{Profile, ProfileService, ProfileUpdate};
pub use settings::{SettingsService, UserSettings};
pub use sync::{SyncConfig, SyncEvent, SyncService};

#[cfg(feature = "portal")]
pub use db::{DatabasePool, DbError};

#[cfg(feature = "portal")]
pub use auth_db::PortalState;

#[cfg(feature = "portal")]
pub use sync_ws::{SyncWsState, WsState};

#[cfg(feature = "portal")]
pub use sync_db::{
    DbDeviceSession, DeviceSessionRepository, SyncConflict, SyncConflictRepository, SyncQueueItem,
    SyncQueueRepository, SyncState, SyncStateRepository,
};

pub use middleware::{optional_auth, require_auth, AuthClaims};

/// Build the portal router with all endpoints
pub fn portal_router() -> Router {
    Router::new()
        // Authentication
        .route("/auth/register", post(auth::handlers::register))
        .route("/auth/login", post(auth::handlers::login))
        .route("/auth/logout", post(auth::handlers::logout))
        .route("/auth/refresh", post(auth::handlers::refresh_token))
        .route(
            "/auth/password/reset",
            post(auth::handlers::request_password_reset),
        )
        .route(
            "/auth/password/reset/:token",
            post(auth::handlers::reset_password),
        )
        .route("/auth/2fa/setup", post(auth::handlers::setup_2fa))
        .route("/auth/2fa/verify", post(auth::handlers::verify_2fa))
        // Profile
        .route("/profile", get(profile::handlers::get_profile))
        .route("/profile", put(profile::handlers::update_profile))
        .route("/profile/avatar", post(profile::handlers::upload_avatar))
        .route("/profile/delete", delete(profile::handlers::delete_account))
        // Settings
        .route("/settings", get(settings::handlers::get_settings))
        .route("/settings", put(settings::handlers::update_settings))
        .route("/settings/sync", post(settings::handlers::sync_settings))
        // API Keys
        .route("/api-keys", get(api_keys::handlers::list_keys))
        .route("/api-keys", post(api_keys::handlers::create_key))
        .route("/api-keys/:id", delete(api_keys::handlers::revoke_key))
        .route("/api-keys/:id/rotate", post(api_keys::handlers::rotate_key))
        // Export
        .route("/export", post(export::handlers::create_export))
        .route("/export/:id", get(export::handlers::get_export_status))
        .route(
            "/export/:id/download",
            get(export::handlers::download_export),
        )
        .route("/export/schedule", post(export::handlers::schedule_export))
        .route("/export/history", get(export::handlers::export_history))
}

/// Portal configuration
#[derive(Debug, Clone)]
pub struct PortalConfig {
    /// JWT secret for token signing
    pub jwt_secret: String,
    /// Token expiration in seconds
    pub token_expiry_secs: u64,
    /// Refresh token expiration in seconds
    pub refresh_token_expiry_secs: u64,
    /// Enable 2FA requirement for sensitive operations
    pub require_2fa_for_sensitive: bool,
    /// Maximum API keys per user
    pub max_api_keys_per_user: usize,
    /// Export storage path
    pub export_storage_path: String,
    /// Maximum export retention days
    pub export_retention_days: u32,
}

impl Default for PortalConfig {
    fn default() -> Self {
        Self {
            jwt_secret: std::env::var("JWT_SECRET")
                .unwrap_or_else(|_| "change-me-in-production".to_string()),
            token_expiry_secs: 3600,           // 1 hour
            refresh_token_expiry_secs: 604800, // 7 days
            require_2fa_for_sensitive: false,
            max_api_keys_per_user: 10,
            export_storage_path: "/var/lib/reasonkit/exports".to_string(),
            export_retention_days: 30,
        }
    }
}

/// Build the portal router with database-backed authentication.
///
/// This router uses PostgreSQL for user storage and session management.
/// Requires `portal` feature flag.
#[cfg(feature = "portal")]
pub fn portal_router_with_db(state: PortalState) -> Router {
    use axum::middleware as axum_middleware;

    // Public routes (no auth required)
    let public_routes = Router::new()
        .route("/auth/register", post(auth_db::register))
        .route("/auth/login", post(auth_db::login))
        .route("/auth/refresh", post(auth_db::refresh_token))
        // Stub handlers for remaining auth endpoints
        .route(
            "/auth/password/reset",
            post(auth::handlers::request_password_reset),
        )
        .route(
            "/auth/password/reset/:token",
            post(auth::handlers::reset_password),
        );

    // Protected routes (require authentication)
    let protected_routes = Router::new()
        // Auth
        .route("/auth/logout", post(auth_db::logout))
        .route("/auth/2fa/setup", post(auth::handlers::setup_2fa))
        .route("/auth/2fa/verify", post(auth::handlers::verify_2fa))
        // Profile (database-backed)
        .route("/profile", get(profile_db::get_profile))
        .route("/profile", put(profile_db::update_profile))
        .route("/profile/avatar", post(profile_db::upload_avatar))
        .route("/profile/delete", delete(profile_db::delete_account))
        // Settings (database-backed)
        .route("/settings", get(settings_db::get_settings))
        .route("/settings", put(settings_db::update_settings))
        .route("/settings/:key", get(settings_db::get_setting))
        .route("/settings/:key", put(settings_db::set_setting))
        .route("/settings/:key", delete(settings_db::delete_setting))
        .route("/settings/sync", post(settings_db::sync_settings))
        // API Keys (database-backed)
        .route("/api-keys", get(api_keys_db::list_keys))
        .route("/api-keys", post(api_keys_db::create_key))
        .route("/api-keys/:id", delete(api_keys_db::revoke_key))
        .route("/api-keys/:id/rotate", post(api_keys_db::rotate_key))
        // API Key Usage (database-backed)
        .route("/api-keys/:id/usage", get(usage_tracking::get_usage_stats))
        .route(
            "/api-keys/:id/usage/daily",
            get(usage_tracking::get_daily_usage),
        )
        .route(
            "/api-keys/:id/usage/endpoints",
            get(usage_tracking::get_endpoint_usage),
        )
        // Export (database-backed)
        .route("/export", post(export_db::create_export))
        .route("/export/:id", get(export_db::get_export_status))
        .route("/export/:id/download", get(export_db::download_export))
        .route("/export/history", get(export_db::export_history))
        .route_layer(axum_middleware::from_fn(middleware::require_auth));

    // Combine routes
    Router::new()
        .merge(public_routes)
        .merge(protected_routes)
        .with_state(state)
}

/// Build the portal router with WebSocket sync support.
///
/// This router includes all database-backed endpoints plus WebSocket
/// real-time synchronization.
/// Requires `portal` feature flag.
#[cfg(feature = "portal")]
pub fn portal_router_with_sync(portal_state: PortalState, ws_state: WsState) -> Router {
    use axum::middleware as axum_middleware;

    let sync_state = SyncWsState {
        portal: portal_state.clone(),
        ws: ws_state,
    };

    // Public routes (no auth required)
    let public_routes = Router::new()
        .route("/auth/register", post(auth_db::register))
        .route("/auth/login", post(auth_db::login))
        .route("/auth/refresh", post(auth_db::refresh_token))
        .route(
            "/auth/password/reset",
            post(auth::handlers::request_password_reset),
        )
        .route(
            "/auth/password/reset/:token",
            post(auth::handlers::reset_password),
        )
        .with_state(portal_state.clone());

    // WebSocket route (auth via query param token)
    let ws_routes = Router::new()
        .route("/sync/ws", get(sync_ws::ws_handler))
        .with_state(sync_state.clone());

    // Protected sync routes
    let sync_protected = Router::new()
        .route("/sync/status", get(sync_ws::get_sync_status))
        .route("/sync/conflicts", get(sync_ws::get_conflicts))
        .route(
            "/sync/conflicts/:id/resolve",
            post(sync_ws::resolve_conflict),
        )
        .route_layer(axum_middleware::from_fn(middleware::require_auth))
        .with_state(sync_state);

    // Protected routes (require authentication)
    let protected_routes = Router::new()
        // Auth
        .route("/auth/logout", post(auth_db::logout))
        .route("/auth/2fa/setup", post(auth::handlers::setup_2fa))
        .route("/auth/2fa/verify", post(auth::handlers::verify_2fa))
        // Profile (database-backed)
        .route("/profile", get(profile_db::get_profile))
        .route("/profile", put(profile_db::update_profile))
        .route("/profile/avatar", post(profile_db::upload_avatar))
        .route("/profile/delete", delete(profile_db::delete_account))
        // Settings (database-backed)
        .route("/settings", get(settings_db::get_settings))
        .route("/settings", put(settings_db::update_settings))
        .route("/settings/:key", get(settings_db::get_setting))
        .route("/settings/:key", put(settings_db::set_setting))
        .route("/settings/:key", delete(settings_db::delete_setting))
        .route("/settings/sync", post(settings_db::sync_settings))
        // API Keys (database-backed)
        .route("/api-keys", get(api_keys_db::list_keys))
        .route("/api-keys", post(api_keys_db::create_key))
        .route("/api-keys/:id", delete(api_keys_db::revoke_key))
        .route("/api-keys/:id/rotate", post(api_keys_db::rotate_key))
        // API Key Usage (database-backed)
        .route("/api-keys/:id/usage", get(usage_tracking::get_usage_stats))
        .route(
            "/api-keys/:id/usage/daily",
            get(usage_tracking::get_daily_usage),
        )
        .route(
            "/api-keys/:id/usage/endpoints",
            get(usage_tracking::get_endpoint_usage),
        )
        // Export (database-backed)
        .route("/export", post(export_db::create_export))
        .route("/export/:id", get(export_db::get_export_status))
        .route("/export/:id/download", get(export_db::download_export))
        .route("/export/history", get(export_db::export_history))
        .route_layer(axum_middleware::from_fn(middleware::require_auth))
        .with_state(portal_state);

    // Combine all routes
    Router::new()
        .merge(public_routes)
        .merge(ws_routes)
        .merge(sync_protected)
        .merge(protected_routes)
}