1 parent
96a32ea8fb
commit
a643e7e3ab
106 files changed
+2223
-4669
No files matched your search
+5
-48
@@ -48,14 +48,6 @@ use crate::realtime::events::EventBus;
|
||||
use crate::realtime::idle::IdleManager;
|
||||
use crate::smtp::client::SmtpClient;
|
||||
|
||||
/// Per-IP key extractor that supports trusted reverse proxies.
|
||||
///
|
||||
/// When the direct peer IP is in the configured `trusted_proxies` list,
|
||||
/// `X-Forwarded-For` is walked from right to left. Trusted proxy hops on the
|
||||
/// right are skipped and the nearest untrusted address becomes the client IP.
|
||||
/// If the header is missing or malformed, the direct peer IP is used. Falls
|
||||
/// back to loopback when `ConnectInfo<SocketAddr>` is unavailable (e.g. in
|
||||
/// unit tests).
|
||||
#[derive(Debug, Clone)]
|
||||
struct ProxyAwareIpExtractor {
|
||||
trusted_proxies: Vec<TrustedProxy>,
|
||||
@@ -109,28 +101,6 @@ impl KeyExtractor for ProxyAwareIpExtractor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Assembles all application routes into an Axum Router.
|
||||
///
|
||||
/// Route layout:
|
||||
/// - `GET /api/health` / `/api/ready` — liveness/readiness (public)
|
||||
/// - `POST /api/auth/login` — login (public, CSRF only)
|
||||
/// - `GET /api/auth/session` — get session (auth_guard + CSRF)
|
||||
/// - `POST /api/auth/logout` — logout (auth_guard + CSRF)
|
||||
/// - `GET /api/folders` — list folders (auth_guard + CSRF)
|
||||
/// - `GET /api/folders/:folder/messages` — list messages (auth_guard + CSRF)
|
||||
/// - `GET /api/messages/:folder/:uid` — get message detail (auth_guard + CSRF)
|
||||
/// - `PATCH /api/messages/:folder/:uid/flags` — update flags (auth_guard + CSRF)
|
||||
/// - `GET /api/messages/:folder/:uid/attachments/:attachment_id` — download attachment (auth_guard + CSRF)
|
||||
/// - `POST /api/messages/move` — move message (auth_guard + CSRF)
|
||||
/// - `DELETE /api/messages/:folder/:uid` — delete message (auth_guard + CSRF)
|
||||
///
|
||||
/// All non-API paths are served by the embedded RustBox UI.
|
||||
/// The UI assets are compiled into the same executable; there is no frontend runtime.
|
||||
///
|
||||
/// Middleware layers:
|
||||
/// - tower-http tracing
|
||||
/// - CSRF protection on auth routes
|
||||
/// - auth_guard on protected routes
|
||||
pub fn create_router(
|
||||
config: Arc<AppConfig>,
|
||||
store: Arc<SessionStore>,
|
||||
@@ -140,7 +110,6 @@ pub fn create_router(
|
||||
event_bus: Arc<EventBus>,
|
||||
idle_manager: Arc<IdleManager>,
|
||||
) -> Router {
|
||||
// Rate-limit login: replenish 1 token every 12 s, burst of 5.
|
||||
let governor_conf = GovernorConfigBuilder::default()
|
||||
.key_extractor(ProxyAwareIpExtractor {
|
||||
trusted_proxies: config.parsed_trusted_proxies(),
|
||||
@@ -150,15 +119,12 @@ pub fn create_router(
|
||||
.finish()
|
||||
.expect("valid governor config");
|
||||
|
||||
// Public auth route: GovernorLayer (outermost) -> CSRF -> handler.
|
||||
let public_auth = Router::new()
|
||||
.route("/login", post(auth::login))
|
||||
.route("/policy", get(auth::auth_policy))
|
||||
.layer(middleware::from_fn(csrf_protection))
|
||||
.layer(GovernorLayer::new(governor_conf));
|
||||
|
||||
// Browser-bound routes (no auth_guard, no rate limit).
|
||||
// These routes only need the browser cookie, not full auth.
|
||||
let browser_routes = Router::new()
|
||||
.route("/accounts", get(auth::list_accounts))
|
||||
.route(
|
||||
@@ -166,7 +132,6 @@ pub fn create_router(
|
||||
delete(auth::remove_account).layer(middleware::from_fn(csrf_protection)),
|
||||
);
|
||||
|
||||
// Protected auth routes (auth_guard + CSRF).
|
||||
let protected_auth = Router::new()
|
||||
.route("/session", get(auth::get_session))
|
||||
.route("/sessions", get(auth::list_active_sessions))
|
||||
@@ -181,9 +146,9 @@ pub fn create_router(
|
||||
.merge(browser_routes)
|
||||
.merge(protected_auth);
|
||||
|
||||
// Protected data routes (auth_guard + CSRF).
|
||||
// Multipart framing needs a small allowance on top of the configured per-file limit.
|
||||
let attachment_body_limit = config.max_attachment_size_bytes().saturating_add(1024 * 1024);
|
||||
let attachment_body_limit = config
|
||||
.max_attachment_size_bytes()
|
||||
.saturating_add(1024 * 1024);
|
||||
let protected_data = Router::new()
|
||||
.route(
|
||||
"/folders",
|
||||
@@ -240,7 +205,8 @@ pub fn create_router(
|
||||
)
|
||||
.route(
|
||||
"/drafts/{draft_id}/attachments",
|
||||
post(attachments::upload_attachment).layer(DefaultBodyLimit::max(attachment_body_limit)),
|
||||
post(attachments::upload_attachment)
|
||||
.layer(DefaultBodyLimit::max(attachment_body_limit)),
|
||||
)
|
||||
.route(
|
||||
"/drafts/{draft_id}/attachments/{attachment_id}",
|
||||
@@ -482,8 +448,6 @@ pub fn create_router(
|
||||
.layer(middleware::from_fn(auth_guard))
|
||||
.layer(middleware::from_fn(csrf_protection));
|
||||
|
||||
// WebSocket route — auth is handled inside the handler via cookie,
|
||||
// so it bypasses CSRF and auth_guard middleware.
|
||||
let ws_route = Router::new().route("/ws", get(crate::realtime::ws::ws_handler));
|
||||
|
||||
let api_router = Router::new()
|
||||
@@ -493,21 +457,14 @@ pub fn create_router(
|
||||
.merge(ws_route)
|
||||
.merge(protected_data);
|
||||
|
||||
// RustBox serves API and UI from the same Rust process.
|
||||
let inner = Router::new()
|
||||
.nest("/api", api_router)
|
||||
// An explicit index route is required for the root of a nested base path
|
||||
// (for example `/mail/`). The fallback still handles SPA deep links.
|
||||
.route("/", get(crate::ui::serve_ui))
|
||||
.fallback(crate::ui::serve_ui);
|
||||
|
||||
// Use the same base path for routing and authentication cookies.
|
||||
let router = match config.normalized_base_path() {
|
||||
"/" => inner,
|
||||
base_path => {
|
||||
// Axum nests an inner `/` route at the prefix itself (`/mail`),
|
||||
// not at the trailing-slash variant (`/mail/`). Serve that variant
|
||||
// explicitly so both URLs load the same embedded SPA.
|
||||
let base_path_slash = format!("{}/", base_path.trim_end_matches('/'));
|
||||
Router::new()
|
||||
.route(&base_path_slash, get(crate::ui::serve_ui))
|
||||
|
||||
Reference in new issue
Block a user