web.rs (5440B)
1 use crate::{ 2 api::core::now, 3 config::{self, Config}, 4 error::Error, 5 util::{Cached, SafeString}, 6 }; 7 use rocket::{fs::NamedFile, http::ContentType, serde::json::Json, Catcher, Route}; 8 use serde_json::Value; 9 use std::path::{Path, PathBuf}; 10 11 pub fn routes() -> Vec<Route> { 12 // If adding more routes here, consider also adding them to 13 // crate::utils::LOGGED_ROUTES to make sure they appear in the log 14 let mut routes = routes![alive, alive_head, attachments, static_files]; 15 if config::get_config().web_vault_enabled { 16 routes.append(&mut routes![app_id, web_files, web_index, web_index_head]); 17 } 18 routes 19 } 20 21 pub fn catchers() -> Vec<Catcher> { 22 if config::get_config().web_vault_enabled { 23 catchers![not_found] 24 } else { 25 Vec::new() 26 } 27 } 28 29 #[catch(404)] 30 const fn not_found() -> &'static str { 31 "Web vault is permanently disabled." 32 } 33 34 #[get("/")] 35 async fn web_index() -> Cached<Option<NamedFile>> { 36 Cached::short( 37 NamedFile::open(Path::new(Config::WEB_VAULT_FOLDER).join("index.html")) 38 .await 39 .ok(), 40 false, 41 ) 42 } 43 #[head("/")] 44 const fn web_index_head() { 45 // Add an explicit HEAD route to prevent uptime monitoring services from 46 // generating "No matching routes for HEAD /" error messages. 47 // 48 // Rocket automatically implements a HEAD route when there's a matching GET 49 // route, but relying on this behavior also means a spurious error gets 50 // logged due to <https://github.com/SergioBenitez/Rocket/issues/1098>. 51 } 52 53 #[get("/app-id.json")] 54 fn app_id() -> Cached<(ContentType, Json<Value>)> { 55 let content_type = ContentType::new("application", "fido.trusted-apps+json"); 56 Cached::long( 57 ( 58 content_type, 59 Json(json!({ 60 "trustedFacets": [ 61 { 62 "version": { "major": 1i32, "minor": 0i32 }, 63 "ids": [ 64 // Per <https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-appid-and-facets-v2.0-id-20180227.html#determining-the-facetid-of-a-calling-application>: 65 // 66 // "In the Web case, the FacetID MUST be the Web Origin [RFC6454] 67 // of the web page triggering the FIDO operation, written as 68 // a URI with an empty path. Default ports are omitted and any 69 // path component is ignored." 70 // 71 // This leaves it unclear as to whether the path must be empty, 72 // or whether it can be non-empty and will be ignored. To be on 73 // the safe side, use a proper web origin (with empty path). 74 config::get_config().domain_origin(), 75 "ios:bundle-id:com.8bit.bitwarden", 76 "android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI" ] 77 }] 78 })), 79 ), 80 true, 81 ) 82 } 83 84 #[get("/<p..>", rank = 10)] // Only match this if the other routes don't match 85 async fn web_files(p: PathBuf) -> Cached<Option<NamedFile>> { 86 Cached::long( 87 NamedFile::open(Path::new(Config::WEB_VAULT_FOLDER).join(p)) 88 .await 89 .ok(), 90 true, 91 ) 92 } 93 94 #[allow(unused_variables, clippy::needless_pass_by_value)] 95 #[get("/attachments/<uuid>/<file_id>?<token>")] 96 fn attachments(uuid: SafeString, file_id: SafeString, token: &str) -> Option<NamedFile> { 97 None 98 } 99 100 #[get("/alive")] 101 fn alive() -> Json<String> { 102 now() 103 } 104 #[head("/alive")] 105 const fn alive_head() {} 106 #[get("/vw_static/<filename>", rank = 2)] 107 fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Error> { 108 match filename { 109 "404.png" => Ok((ContentType::PNG, include_bytes!("../static/images/404.png"))), 110 "logo-gray.png" => Ok(( 111 ContentType::PNG, 112 include_bytes!("../static/images/logo-gray.png"), 113 )), 114 "error-x.svg" => Ok(( 115 ContentType::SVG, 116 include_bytes!("../static/images/error-x.svg"), 117 )), 118 "vaultwarden-icon.png" => Ok(( 119 ContentType::PNG, 120 include_bytes!("../static/images/vaultwarden-icon.png"), 121 )), 122 "vaultwarden-favicon.png" => Ok(( 123 ContentType::PNG, 124 include_bytes!("../static/images/vaultwarden-favicon.png"), 125 )), 126 "404.css" => Ok(( 127 ContentType::CSS, 128 include_bytes!("../static/scripts/404.css"), 129 )), 130 "bootstrap.css" => Ok(( 131 ContentType::CSS, 132 include_bytes!("../static/scripts/bootstrap.css"), 133 )), 134 "bootstrap.bundle.js" => Ok(( 135 ContentType::JavaScript, 136 include_bytes!("../static/scripts/bootstrap.bundle.js"), 137 )), 138 "jdenticon-3.3.0.js" => Ok(( 139 ContentType::JavaScript, 140 include_bytes!("../static/scripts/jdenticon-3.3.0.js"), 141 )), 142 "datatables.js" => Ok(( 143 ContentType::JavaScript, 144 include_bytes!("../static/scripts/datatables.js"), 145 )), 146 "datatables.css" => Ok(( 147 ContentType::CSS, 148 include_bytes!("../static/scripts/datatables.css"), 149 )), 150 "jquery-3.7.1.slim.js" => Ok(( 151 ContentType::JavaScript, 152 include_bytes!("../static/scripts/jquery-3.7.1.slim.js"), 153 )), 154 _ => err!(format!("Static file not found: {filename}")), 155 } 156 }