vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit 0a5df06e77fb2359dbe0496e1e669e6d5b42cde5
parent 840cf8740ad4b88f8d1c224553c9b912abbfd441
Author: Daniel GarcĂ­a <dani-garcia@users.noreply.github.com>
Date:   Wed, 22 Dec 2021 15:46:30 +0100

Merge pull request #2158 from jjlin/icons

Add support for external icon services
Diffstat:
M.env.template | 20+++++++++++++++++---
Msrc/api/icons.rs | 50+++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/config.rs | 30+++++++++++++++++++++++++++---
3 files changed, 91 insertions(+), 9 deletions(-)

diff --git a/.env.template b/.env.template @@ -129,10 +129,24 @@ ## Number of times to retry the database connection during startup, with 1 second delay between each retry, set to 0 to retry indefinitely # DB_CONNECTION_RETRIES=15 +## Icon service +## The predefined icon services are: internal, bitwarden, duckduckgo, google. +## To specify a custom icon service, set a URL template with exactly one instance of `{}`, +## which is replaced with the domain. For example: `https://icon.example.com/domain/{}`. +## +## `internal` refers to Vaultwarden's built-in icon fetching implementation. +## If an external service is set, an icon request to Vaultwarden will return an HTTP 307 +## redirect to the corresponding icon at the external service. An external service may +## be useful if your Vaultwarden instance has no external network connectivity, or if +## you are concerned that someone may probe your instance to try to detect whether icons +## for certain sites have been cached. +# ICON_SERVICE=internal + ## Disable icon downloading -## Set to true to disable icon downloading, this would still serve icons from $ICON_CACHE_FOLDER, -## but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0, -## otherwise it will delete them and they won't be downloaded again. +## Set to true to disable icon downloading in the internal icon service. +## This still serves existing icons from $ICON_CACHE_FOLDER, without generating any external +## network requests. $ICON_CACHE_TTL must also be set to 0; otherwise, the existing icons +## will be deleted eventually, but won't be downloaded again. # DISABLE_ICON_DOWNLOAD=false ## Icon download timeout diff --git a/src/api/icons.rs b/src/api/icons.rs @@ -10,7 +10,11 @@ use std::{ use once_cell::sync::Lazy; use regex::Regex; use reqwest::{blocking::Client, blocking::Response, header}; -use rocket::{http::ContentType, response::Content, Route}; +use rocket::{ + http::ContentType, + response::{Content, Redirect}, + Route, +}; use crate::{ error::Error, @@ -19,7 +23,13 @@ use crate::{ }; pub fn routes() -> Vec<Route> { - routes![icon] + match CONFIG.icon_service().as_str() { + "internal" => routes![icon_internal], + "bitwarden" => routes![icon_bitwarden], + "duckduckgo" => routes![icon_duckduckgo], + "google" => routes![icon_google], + _ => routes![icon_custom], + } } static CLIENT: Lazy<Client> = Lazy::new(|| { @@ -50,8 +60,42 @@ static ICON_SIZE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?x)(\d+)\D*(\d+ // Special HashMap which holds the user defined Regex to speedup matching the regex. static ICON_BLACKLIST_REGEX: Lazy<RwLock<HashMap<String, Regex>>> = Lazy::new(|| RwLock::new(HashMap::new())); +fn icon_redirect(domain: &str, template: &str) -> Option<Redirect> { + if !is_valid_domain(domain) { + warn!("Invalid domain: {}", domain); + return None; + } + + if is_domain_blacklisted(domain) { + return None; + } + + let url = template.replace("{}", domain); + Some(Redirect::temporary(url)) +} + +#[get("/<domain>/icon.png")] +fn icon_custom(domain: String) -> Option<Redirect> { + icon_redirect(&domain, &CONFIG.icon_service()) +} + +#[get("/<domain>/icon.png")] +fn icon_bitwarden(domain: String) -> Option<Redirect> { + icon_redirect(&domain, "https://icons.bitwarden.net/{}/icon.png") +} + +#[get("/<domain>/icon.png")] +fn icon_duckduckgo(domain: String) -> Option<Redirect> { + icon_redirect(&domain, "https://icons.duckduckgo.com/ip3/{}.ico") +} + +#[get("/<domain>/icon.png")] +fn icon_google(domain: String) -> Option<Redirect> { + icon_redirect(&domain, "https://www.google.com/s2/favicons?domain={}&sz=32") +} + #[get("/<domain>/icon.png")] -fn icon(domain: String) -> Cached<Content<Vec<u8>>> { +fn icon_internal(domain: String) -> Cached<Content<Vec<u8>>> { const FALLBACK_ICON: &[u8] = include_bytes!("../static/images/fallback-icon.png"); if !is_valid_domain(&domain) { diff --git a/src/config.rs b/src/config.rs @@ -406,9 +406,10 @@ make_config! { /// This setting applies globally to all users. incomplete_2fa_time_limit: i64, true, def, 3; - /// Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from - /// $ICON_CACHE_FOLDER, but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0, - /// otherwise it will delete them and they won't be downloaded again. + /// Disable icon downloads |> Set to true to disable icon downloading in the internal icon service. + /// This still serves existing icons from $ICON_CACHE_FOLDER, without generating any external + /// network requests. $ICON_CACHE_TTL must also be set to 0; otherwise, the existing icons + /// will be deleted eventually, but won't be downloaded again. disable_icon_download: bool, true, def, false; /// Allow new signups |> Controls whether new users can register. Users can be invited by the vaultwarden admin even if this is disabled signups_allowed: bool, true, def, true; @@ -449,6 +450,13 @@ make_config! { ip_header: String, true, def, "X-Real-IP".to_string(); /// Internal IP header property, used to avoid recomputing each time _ip_header_enabled: bool, false, gen, |c| &c.ip_header.trim().to_lowercase() != "none"; + /// Icon service |> The predefined icon services are: internal, bitwarden, duckduckgo, google. + /// To specify a custom icon service, set a URL template with exactly one instance of `{}`, + /// which is replaced with the domain. For example: `https://icon.example.com/domain/{}`. + /// `internal` refers to Vaultwarden's built-in icon fetching implementation. If an external + /// service is set, an icon request to Vaultwarden will return an HTTP 307 redirect to the + /// corresponding icon at the external service. + icon_service: String, false, def, "internal".to_string(); /// Positive icon cache expiry |> Number of seconds to consider that an already cached icon is fresh. After this period, the icon will be redownloaded icon_cache_ttl: u64, true, def, 2_592_000; /// Negative icon cache expiry |> Number of seconds before trying to download an icon that failed again. @@ -659,6 +667,22 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { } } + // Check if the icon service is valid + let icon_service = cfg.icon_service.as_str(); + match icon_service { + "internal" | "bitwarden" | "duckduckgo" | "google" => (), + _ => { + if !icon_service.starts_with("http") { + err!(format!("Icon service URL `{}` must start with \"http\"", icon_service)) + } + match icon_service.matches("{}").count() { + 1 => (), // nominal + 0 => err!(format!("Icon service URL `{}` has no placeholder \"{{}}\"", icon_service)), + _ => err!(format!("Icon service URL `{}` has more than one placeholder \"{{}}\"", icon_service)), + } + } + } + Ok(()) }