mod.rs
⎇
Raw
1use std::sync::Arc;
2
3use api_types::{
4 ADMIN_SETTINGS, ADMIN_USERS, AUTH_LOGIN, AUTH_LOGOUT, AUTH_ME, AUTH_SETUP, FILES, SHARE, SHARES,
5};
6use axum::Router;
7use axum::http::HeaderValue;
8use axum::routing::{delete, get, post, put};
9use tower_http::set_header::SetResponseHeaderLayer;
10
11use crate::error::AppState;
12
13/// Content-Security-Policy tuned to the built Trunk frontend.
14///
15/// * `script-src 'unsafe-inline'` — Trunk emits one inline bootstrap module
16/// in index.html; every real JS file also carries an SRI integrity hash.
17/// * `'wasm-unsafe-eval'` — compiling the same-origin WASM module.
18/// * `style-src 'unsafe-inline'` — the context menu sets an inline `style=`.
19/// * Everything else locked to the same origin; frames/plugins banned.
20const CSP: &str = "default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; connect-src 'self'; font-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';";
21
22mod admin;
23mod auth;
24mod common;
25mod files;
26mod shares;
27mod spa;
28
29pub fn router(state: Arc<AppState>) -> Router {
30 // Route patterns: the server side of the shared endpoint strings in
31 // `api_types` (axum copies them into the route table on insert).
32 let files_root = format!("{FILES}/{{root_id}}");
33 let files_item = format!("{FILES}/{{root_id}}/{{*path}}");
34 let shares_id = format!("{SHARES}/{{id}}");
35 let share_token = format!("{SHARE}/{{token}}");
36 let admin_user_id = format!("{ADMIN_USERS}/{{id}}");
37
38 Router::new()
39 .route(AUTH_LOGIN, post(auth::login))
40 .route(AUTH_LOGOUT, post(auth::logout))
41 .route(AUTH_ME, get(auth::me))
42 .route(AUTH_SETUP, post(auth::setup))
43 .route(&files_root, get(files::list_root))
44 .route(&files_item, get(files::file_get))
45 .route(&files_item, put(files::file_put))
46 .route(&files_root, post(files::dispatch_root))
47 .route(&files_item, post(files::dispatch))
48 .route(&files_item, delete(files::delete))
49 .route(SHARES, get(shares::list))
50 .route(SHARES, post(shares::create))
51 .route(&shares_id, delete(shares::delete))
52 .route(&share_token, get(shares::resolve))
53 .route(ADMIN_USERS, get(admin::list_users))
54 .route(ADMIN_USERS, post(admin::create_user))
55 .route(&admin_user_id, put(admin::update_user))
56 .route(&admin_user_id, delete(admin::delete_user))
57 .route(ADMIN_SETTINGS, get(admin::get_settings))
58 .route(ADMIN_SETTINGS, put(admin::update_settings))
59 .fallback(spa::fallback)
60 .with_state(state)
61 // Hard security headers on every response (API and static alike).
62 .layer(SetResponseHeaderLayer::overriding(
63 "content-security-policy".parse().unwrap(),
64 HeaderValue::from_static(CSP),
65 ))
66 .layer(SetResponseHeaderLayer::overriding(
67 "x-content-type-options".parse().unwrap(),
68 HeaderValue::from_static("nosniff"),
69 ))
70 .layer(SetResponseHeaderLayer::overriding(
71 "x-frame-options".parse().unwrap(),
72 HeaderValue::from_static("DENY"),
73 ))
74 .layer(SetResponseHeaderLayer::overriding(
75 "referrer-policy".parse().unwrap(),
76 HeaderValue::from_static("no-referrer"),
77 ))
78}
79