Skip to main content

reasonkit_web/portal/
mod.rs

1//! # ReasonKit Portal Module
2//!
3//! User portal functionality for account management, settings synchronization,
4//! and data export capabilities.
5//!
6//! ## Architecture
7//!
8//! ```text
9//! Portal Module
10//! ├── auth       - Authentication (JWT, OAuth, 2FA)
11//! ├── profile    - User profile management
12//! ├── settings   - Settings storage and sync
13//! ├── export     - Data export (JSON, CSV, PDF)
14//! ├── sync       - Real-time synchronization
15//! ├── api_keys   - API key management
16//! └── db         - PostgreSQL database layer (feature: portal)
17//! ```
18//!
19//! ## Integration Points
20//!
21//! - **Stripe Module**: Subscription management
22//! - **Security Module**: Rate limiting, CORS
23//! - **MCP Server**: CLI authentication
24//!
25//! ## Feature Flags
26//!
27//! - `portal`: Enable PostgreSQL database support
28
29pub mod api_keys;
30pub mod auth;
31pub mod export;
32pub mod profile;
33pub mod settings;
34pub mod sync;
35
36/// Database layer (requires `portal` feature)
37#[cfg(feature = "portal")]
38pub mod db;
39
40/// Database-backed authentication handlers (requires `portal` feature)
41#[cfg(feature = "portal")]
42pub mod auth_db;
43
44/// Database-backed profile handlers (requires `portal` feature)
45#[cfg(feature = "portal")]
46pub mod profile_db;
47
48/// Database-backed settings handlers (requires `portal` feature)
49#[cfg(feature = "portal")]
50pub mod settings_db;
51
52/// Database-backed API key handlers (requires `portal` feature)
53#[cfg(feature = "portal")]
54pub mod api_keys_db;
55
56/// Rate limiting middleware (requires `portal` feature)
57#[cfg(feature = "portal")]
58pub mod rate_limiting;
59
60/// Usage tracking middleware and handlers (requires `portal` feature)
61#[cfg(feature = "portal")]
62pub mod usage_tracking;
63
64/// Database-backed export handlers (requires `portal` feature)
65#[cfg(feature = "portal")]
66pub mod export_db;
67
68/// Database-backed sync state management (requires `portal` feature)
69#[cfg(feature = "portal")]
70pub mod sync_db;
71
72/// WebSocket handler for real-time synchronization (requires `portal` feature)
73#[cfg(feature = "portal")]
74pub mod sync_ws;
75
76/// Authentication middleware
77pub mod middleware;
78
79use axum::{
80    routing::{delete, get, post, put},
81    Router,
82};
83
84pub use api_keys::{ApiKey, ApiKeyScope};
85pub use auth::{AuthConfig, AuthService, Claims, TokenPair};
86pub use export::{ExportConfig, ExportFormat, ExportJob, ExportService};
87pub use profile::{Profile, ProfileService, ProfileUpdate};
88pub use settings::{SettingsService, UserSettings};
89pub use sync::{SyncConfig, SyncEvent, SyncService};
90
91#[cfg(feature = "portal")]
92pub use db::{DatabasePool, DbError};
93
94#[cfg(feature = "portal")]
95pub use auth_db::PortalState;
96
97#[cfg(feature = "portal")]
98pub use sync_ws::{SyncWsState, WsState};
99
100#[cfg(feature = "portal")]
101pub use sync_db::{
102    DbDeviceSession, DeviceSessionRepository, SyncConflict, SyncConflictRepository, SyncQueueItem,
103    SyncQueueRepository, SyncState, SyncStateRepository,
104};
105
106pub use middleware::{optional_auth, require_auth, AuthClaims};
107
108/// Build the portal router with all endpoints
109pub fn portal_router() -> Router {
110    Router::new()
111        // Authentication
112        .route("/auth/register", post(auth::handlers::register))
113        .route("/auth/login", post(auth::handlers::login))
114        .route("/auth/logout", post(auth::handlers::logout))
115        .route("/auth/refresh", post(auth::handlers::refresh_token))
116        .route(
117            "/auth/password/reset",
118            post(auth::handlers::request_password_reset),
119        )
120        .route(
121            "/auth/password/reset/:token",
122            post(auth::handlers::reset_password),
123        )
124        .route("/auth/2fa/setup", post(auth::handlers::setup_2fa))
125        .route("/auth/2fa/verify", post(auth::handlers::verify_2fa))
126        // Profile
127        .route("/profile", get(profile::handlers::get_profile))
128        .route("/profile", put(profile::handlers::update_profile))
129        .route("/profile/avatar", post(profile::handlers::upload_avatar))
130        .route("/profile/delete", delete(profile::handlers::delete_account))
131        // Settings
132        .route("/settings", get(settings::handlers::get_settings))
133        .route("/settings", put(settings::handlers::update_settings))
134        .route("/settings/sync", post(settings::handlers::sync_settings))
135        // API Keys
136        .route("/api-keys", get(api_keys::handlers::list_keys))
137        .route("/api-keys", post(api_keys::handlers::create_key))
138        .route("/api-keys/:id", delete(api_keys::handlers::revoke_key))
139        .route("/api-keys/:id/rotate", post(api_keys::handlers::rotate_key))
140        // Export
141        .route("/export", post(export::handlers::create_export))
142        .route("/export/:id", get(export::handlers::get_export_status))
143        .route(
144            "/export/:id/download",
145            get(export::handlers::download_export),
146        )
147        .route("/export/schedule", post(export::handlers::schedule_export))
148        .route("/export/history", get(export::handlers::export_history))
149}
150
151/// Portal configuration
152#[derive(Debug, Clone)]
153pub struct PortalConfig {
154    /// JWT secret for token signing
155    pub jwt_secret: String,
156    /// Token expiration in seconds
157    pub token_expiry_secs: u64,
158    /// Refresh token expiration in seconds
159    pub refresh_token_expiry_secs: u64,
160    /// Enable 2FA requirement for sensitive operations
161    pub require_2fa_for_sensitive: bool,
162    /// Maximum API keys per user
163    pub max_api_keys_per_user: usize,
164    /// Export storage path
165    pub export_storage_path: String,
166    /// Maximum export retention days
167    pub export_retention_days: u32,
168}
169
170impl Default for PortalConfig {
171    fn default() -> Self {
172        Self {
173            jwt_secret: std::env::var("JWT_SECRET")
174                .unwrap_or_else(|_| "change-me-in-production".to_string()),
175            token_expiry_secs: 3600,           // 1 hour
176            refresh_token_expiry_secs: 604800, // 7 days
177            require_2fa_for_sensitive: false,
178            max_api_keys_per_user: 10,
179            export_storage_path: "/var/lib/reasonkit/exports".to_string(),
180            export_retention_days: 30,
181        }
182    }
183}
184
185/// Build the portal router with database-backed authentication.
186///
187/// This router uses PostgreSQL for user storage and session management.
188/// Requires `portal` feature flag.
189#[cfg(feature = "portal")]
190pub fn portal_router_with_db(state: PortalState) -> Router {
191    use axum::middleware as axum_middleware;
192
193    // Public routes (no auth required)
194    let public_routes = Router::new()
195        .route("/auth/register", post(auth_db::register))
196        .route("/auth/login", post(auth_db::login))
197        .route("/auth/refresh", post(auth_db::refresh_token))
198        // Stub handlers for remaining auth endpoints
199        .route(
200            "/auth/password/reset",
201            post(auth::handlers::request_password_reset),
202        )
203        .route(
204            "/auth/password/reset/:token",
205            post(auth::handlers::reset_password),
206        );
207
208    // Protected routes (require authentication)
209    let protected_routes = Router::new()
210        // Auth
211        .route("/auth/logout", post(auth_db::logout))
212        .route("/auth/2fa/setup", post(auth::handlers::setup_2fa))
213        .route("/auth/2fa/verify", post(auth::handlers::verify_2fa))
214        // Profile (database-backed)
215        .route("/profile", get(profile_db::get_profile))
216        .route("/profile", put(profile_db::update_profile))
217        .route("/profile/avatar", post(profile_db::upload_avatar))
218        .route("/profile/delete", delete(profile_db::delete_account))
219        // Settings (database-backed)
220        .route("/settings", get(settings_db::get_settings))
221        .route("/settings", put(settings_db::update_settings))
222        .route("/settings/:key", get(settings_db::get_setting))
223        .route("/settings/:key", put(settings_db::set_setting))
224        .route("/settings/:key", delete(settings_db::delete_setting))
225        .route("/settings/sync", post(settings_db::sync_settings))
226        // API Keys (database-backed)
227        .route("/api-keys", get(api_keys_db::list_keys))
228        .route("/api-keys", post(api_keys_db::create_key))
229        .route("/api-keys/:id", delete(api_keys_db::revoke_key))
230        .route("/api-keys/:id/rotate", post(api_keys_db::rotate_key))
231        // API Key Usage (database-backed)
232        .route("/api-keys/:id/usage", get(usage_tracking::get_usage_stats))
233        .route(
234            "/api-keys/:id/usage/daily",
235            get(usage_tracking::get_daily_usage),
236        )
237        .route(
238            "/api-keys/:id/usage/endpoints",
239            get(usage_tracking::get_endpoint_usage),
240        )
241        // Export (database-backed)
242        .route("/export", post(export_db::create_export))
243        .route("/export/:id", get(export_db::get_export_status))
244        .route("/export/:id/download", get(export_db::download_export))
245        .route("/export/history", get(export_db::export_history))
246        .route_layer(axum_middleware::from_fn(middleware::require_auth));
247
248    // Combine routes
249    Router::new()
250        .merge(public_routes)
251        .merge(protected_routes)
252        .with_state(state)
253}
254
255/// Build the portal router with WebSocket sync support.
256///
257/// This router includes all database-backed endpoints plus WebSocket
258/// real-time synchronization.
259/// Requires `portal` feature flag.
260#[cfg(feature = "portal")]
261pub fn portal_router_with_sync(portal_state: PortalState, ws_state: WsState) -> Router {
262    use axum::middleware as axum_middleware;
263
264    let sync_state = SyncWsState {
265        portal: portal_state.clone(),
266        ws: ws_state,
267    };
268
269    // Public routes (no auth required)
270    let public_routes = Router::new()
271        .route("/auth/register", post(auth_db::register))
272        .route("/auth/login", post(auth_db::login))
273        .route("/auth/refresh", post(auth_db::refresh_token))
274        .route(
275            "/auth/password/reset",
276            post(auth::handlers::request_password_reset),
277        )
278        .route(
279            "/auth/password/reset/:token",
280            post(auth::handlers::reset_password),
281        )
282        .with_state(portal_state.clone());
283
284    // WebSocket route (auth via query param token)
285    let ws_routes = Router::new()
286        .route("/sync/ws", get(sync_ws::ws_handler))
287        .with_state(sync_state.clone());
288
289    // Protected sync routes
290    let sync_protected = Router::new()
291        .route("/sync/status", get(sync_ws::get_sync_status))
292        .route("/sync/conflicts", get(sync_ws::get_conflicts))
293        .route(
294            "/sync/conflicts/:id/resolve",
295            post(sync_ws::resolve_conflict),
296        )
297        .route_layer(axum_middleware::from_fn(middleware::require_auth))
298        .with_state(sync_state);
299
300    // Protected routes (require authentication)
301    let protected_routes = Router::new()
302        // Auth
303        .route("/auth/logout", post(auth_db::logout))
304        .route("/auth/2fa/setup", post(auth::handlers::setup_2fa))
305        .route("/auth/2fa/verify", post(auth::handlers::verify_2fa))
306        // Profile (database-backed)
307        .route("/profile", get(profile_db::get_profile))
308        .route("/profile", put(profile_db::update_profile))
309        .route("/profile/avatar", post(profile_db::upload_avatar))
310        .route("/profile/delete", delete(profile_db::delete_account))
311        // Settings (database-backed)
312        .route("/settings", get(settings_db::get_settings))
313        .route("/settings", put(settings_db::update_settings))
314        .route("/settings/:key", get(settings_db::get_setting))
315        .route("/settings/:key", put(settings_db::set_setting))
316        .route("/settings/:key", delete(settings_db::delete_setting))
317        .route("/settings/sync", post(settings_db::sync_settings))
318        // API Keys (database-backed)
319        .route("/api-keys", get(api_keys_db::list_keys))
320        .route("/api-keys", post(api_keys_db::create_key))
321        .route("/api-keys/:id", delete(api_keys_db::revoke_key))
322        .route("/api-keys/:id/rotate", post(api_keys_db::rotate_key))
323        // API Key Usage (database-backed)
324        .route("/api-keys/:id/usage", get(usage_tracking::get_usage_stats))
325        .route(
326            "/api-keys/:id/usage/daily",
327            get(usage_tracking::get_daily_usage),
328        )
329        .route(
330            "/api-keys/:id/usage/endpoints",
331            get(usage_tracking::get_endpoint_usage),
332        )
333        // Export (database-backed)
334        .route("/export", post(export_db::create_export))
335        .route("/export/:id", get(export_db::get_export_status))
336        .route("/export/:id/download", get(export_db::download_export))
337        .route("/export/history", get(export_db::export_history))
338        .route_layer(axum_middleware::from_fn(middleware::require_auth))
339        .with_state(portal_state);
340
341    // Combine all routes
342    Router::new()
343        .merge(public_routes)
344        .merge(ws_routes)
345        .merge(sync_protected)
346        .merge(protected_routes)
347}