pub mod accounts; mod ciphers; mod emergency_access; mod folders; mod organizations; mod sends; pub mod two_factor; pub use ciphers::purge_trashed_ciphers; pub use ciphers::{CipherSyncData, CipherSyncType}; pub use emergency_access::{emergency_notification_reminder_job, emergency_request_timeout_job}; pub use sends::purge_sends; pub use two_factor::send_incomplete_2fa_notifications; pub fn routes() -> Vec { let mut device_token_routes = routes![clear_device_token, put_device_token]; let mut eq_domains_routes = routes![get_eq_domains, post_eq_domains, put_eq_domains]; let mut hibp_routes = routes![hibp_breach]; let mut meta_routes = routes![alive, now, version, config]; let mut routes = Vec::new(); routes.append(&mut accounts::routes()); routes.append(&mut ciphers::routes()); routes.append(&mut emergency_access::routes()); routes.append(&mut folders::routes()); routes.append(&mut organizations::routes()); routes.append(&mut two_factor::routes()); routes.append(&mut sends::routes()); routes.append(&mut device_token_routes); routes.append(&mut eq_domains_routes); routes.append(&mut hibp_routes); routes.append(&mut meta_routes); routes } // // Move this somewhere else // use rocket::serde::json::Json; use rocket::Route; use serde_json::Value; use crate::{ api::{JsonResult, JsonUpcase}, auth::Headers, db::DbConn, error::Error, util::get_reqwest_client, }; #[put("/devices/identifier//clear-token")] fn clear_device_token(uuid: String) -> &'static str { // This endpoint doesn't have auth header let _ = uuid; // uuid is not related to deviceId // This only clears push token // https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109 // https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37 "" } #[put("/devices/identifier//token", data = "")] fn put_device_token(uuid: String, data: JsonUpcase, headers: Headers) -> Json { let _data: Value = data.into_inner().data; // Data has a single string value "PushToken" let _ = uuid; // uuid is not related to deviceId // TODO: This should save the push token, but we don't have push functionality Json(json!({ "Id": headers.device.uuid, "Name": headers.device.name, "Type": headers.device.atype, "Identifier": headers.device.uuid, "CreationDate": crate::util::format_date(&headers.device.created_at), })) } #[derive(Serialize, Deserialize, Debug)] #[allow(non_snake_case)] struct GlobalDomain { Type: i32, Domains: Vec, Excluded: bool, } const GLOBAL_DOMAINS: &str = include_str!("../../static/global_domains.json"); #[get("/settings/domains")] fn get_eq_domains(headers: Headers) -> Json { _get_eq_domains(headers, false) } fn _get_eq_domains(headers: Headers, no_excluded: bool) -> Json { let user = headers.user; use serde_json::from_str; let equivalent_domains: Vec> = from_str(&user.equivalent_domains).unwrap(); let excluded_globals: Vec = from_str(&user.excluded_globals).unwrap(); let mut globals: Vec = from_str(GLOBAL_DOMAINS).unwrap(); for global in &mut globals { global.Excluded = excluded_globals.contains(&global.Type); } if no_excluded { globals.retain(|g| !g.Excluded); } Json(json!({ "EquivalentDomains": equivalent_domains, "GlobalEquivalentDomains": globals, "Object": "domains", })) } #[derive(Deserialize, Debug)] #[allow(non_snake_case)] struct EquivDomainData { ExcludedGlobalEquivalentDomains: Option>, EquivalentDomains: Option>>, } #[post("/settings/domains", data = "")] async fn post_eq_domains(data: JsonUpcase, headers: Headers, conn: DbConn) -> JsonResult { let data: EquivDomainData = data.into_inner().data; let excluded_globals = data.ExcludedGlobalEquivalentDomains.unwrap_or_default(); let equivalent_domains = data.EquivalentDomains.unwrap_or_default(); let mut user = headers.user; use serde_json::to_string; user.excluded_globals = to_string(&excluded_globals).unwrap_or_else(|_| "[]".to_string()); user.equivalent_domains = to_string(&equivalent_domains).unwrap_or_else(|_| "[]".to_string()); user.save(&conn).await?; Ok(Json(json!({}))) } #[put("/settings/domains", data = "")] async fn put_eq_domains(data: JsonUpcase, headers: Headers, conn: DbConn) -> JsonResult { post_eq_domains(data, headers, conn).await } #[get("/hibp/breach?")] async fn hibp_breach(username: String) -> JsonResult { let url = format!( "https://haveibeenpwned.com/api/v3/breachedaccount/{}?truncateResponse=false&includeUnverified=false", username ); if let Some(api_key) = crate::CONFIG.hibp_api_key() { let hibp_client = get_reqwest_client(); let res = hibp_client.get(&url).header("hibp-api-key", api_key).send().await?; // If we get a 404, return a 404, it means no breached accounts if res.status() == 404 { return Err(Error::empty().with_code(404)); } let value: Value = res.error_for_status()?.json().await?; Ok(Json(value)) } else { Ok(Json(json!([{ "Name": "HaveIBeenPwned", "Title": "Manual HIBP Check", "Domain": "haveibeenpwned.com", "BreachDate": "2019-08-18T00:00:00Z", "AddedDate": "2019-08-18T00:00:00Z", "Description": format!("Go to: https://haveibeenpwned.com/account/{account} for a manual check.

HaveIBeenPwned API key not set!
Go to https://haveibeenpwned.com/API/Key to purchase an API key from HaveIBeenPwned.

", account=username), "LogoPath": "vw_static/hibp.png", "PwnCount": 0, "DataClasses": [ "Error - No API key set!" ] }]))) } } // We use DbConn here to let the alive healthcheck also verify the database connection. #[get("/alive")] fn alive(_conn: DbConn) -> Json { now() } #[get("/now")] pub fn now() -> Json { Json(crate::util::format_date(&chrono::Utc::now().naive_utc())) } #[get("/version")] fn version() -> Json<&'static str> { Json(crate::VERSION.unwrap_or_default()) } #[get("/config")] fn config() -> Json { let domain = crate::CONFIG.domain(); Json(json!({ "version": crate::VERSION, "gitHash": env!("GIT_REV"), "server": { "name": "Vaultwarden", "url": "https://github.com/dani-garcia/vaultwarden" }, "environment": { "vault": domain, "api": format!("{domain}/api"), "identity": format!("{domain}/identity"), "admin": format!("{domain}/admin"), "notifications": format!("{domain}/notifications"), "sso": "", }, })) }