admin.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. use once_cell::sync::Lazy;
  2. use serde::de::DeserializeOwned;
  3. use serde_json::Value;
  4. use std::{env, time::Duration};
  5. use rocket::{
  6. http::{Cookie, Cookies, SameSite},
  7. request::{self, FlashMessage, Form, FromRequest, Outcome, Request},
  8. response::{content::Html, Flash, Redirect},
  9. Route,
  10. };
  11. use rocket_contrib::json::Json;
  12. use crate::{
  13. api::{ApiResult, EmptyResult, NumberOrString},
  14. auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
  15. config::ConfigBuilder,
  16. db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType},
  17. error::{Error, MapResult},
  18. mail,
  19. util::{format_naive_datetime_local, get_display_size, is_running_in_docker, get_reqwest_client},
  20. CONFIG,
  21. };
  22. pub fn routes() -> Vec<Route> {
  23. if !CONFIG.disable_admin_token() && !CONFIG.is_admin_token_set() {
  24. return routes![admin_disabled];
  25. }
  26. routes![
  27. admin_login,
  28. get_users_json,
  29. post_admin_login,
  30. admin_page,
  31. invite_user,
  32. logout,
  33. delete_user,
  34. deauth_user,
  35. disable_user,
  36. enable_user,
  37. remove_2fa,
  38. update_user_org_type,
  39. update_revision_users,
  40. post_config,
  41. delete_config,
  42. backup_db,
  43. test_smtp,
  44. users_overview,
  45. organizations_overview,
  46. delete_organization,
  47. diagnostics,
  48. get_diagnostics_config
  49. ]
  50. }
  51. static DB_TYPE: Lazy<&str> = Lazy::new(|| {
  52. DbConnType::from_url(&CONFIG.database_url())
  53. .map(|t| match t {
  54. DbConnType::sqlite => "SQLite",
  55. DbConnType::mysql => "MySQL",
  56. DbConnType::postgresql => "PostgreSQL",
  57. })
  58. .unwrap_or("Unknown")
  59. });
  60. static CAN_BACKUP: Lazy<bool> =
  61. Lazy::new(|| DbConnType::from_url(&CONFIG.database_url()).map(|t| t == DbConnType::sqlite).unwrap_or(false));
  62. #[get("/")]
  63. fn admin_disabled() -> &'static str {
  64. "The admin panel is disabled, please configure the 'ADMIN_TOKEN' variable to enable it"
  65. }
  66. const COOKIE_NAME: &str = "BWRS_ADMIN";
  67. const ADMIN_PATH: &str = "/admin";
  68. const BASE_TEMPLATE: &str = "admin/base";
  69. const VERSION: Option<&str> = option_env!("BWRS_VERSION");
  70. fn admin_path() -> String {
  71. format!("{}{}", CONFIG.domain_path(), ADMIN_PATH)
  72. }
  73. struct Referer(Option<String>);
  74. impl<'a, 'r> FromRequest<'a, 'r> for Referer {
  75. type Error = ();
  76. fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
  77. Outcome::Success(Referer(request.headers().get_one("Referer").map(str::to_string)))
  78. }
  79. }
  80. #[derive(Debug)]
  81. struct IpHeader(Option<String>);
  82. impl<'a, 'r> FromRequest<'a, 'r> for IpHeader {
  83. type Error = ();
  84. fn from_request(req: &'a Request<'r>) -> Outcome<Self, Self::Error> {
  85. if req.headers().get_one(&CONFIG.ip_header()).is_some() {
  86. Outcome::Success(IpHeader(Some(CONFIG.ip_header())))
  87. } else if req.headers().get_one("X-Client-IP").is_some() {
  88. Outcome::Success(IpHeader(Some(String::from("X-Client-IP"))))
  89. } else if req.headers().get_one("X-Real-IP").is_some() {
  90. Outcome::Success(IpHeader(Some(String::from("X-Real-IP"))))
  91. } else if req.headers().get_one("X-Forwarded-For").is_some() {
  92. Outcome::Success(IpHeader(Some(String::from("X-Forwarded-For"))))
  93. } else {
  94. Outcome::Success(IpHeader(None))
  95. }
  96. }
  97. }
  98. /// Used for `Location` response headers, which must specify an absolute URI
  99. /// (see https://tools.ietf.org/html/rfc2616#section-14.30).
  100. fn admin_url(referer: Referer) -> String {
  101. // If we get a referer use that to make it work when, DOMAIN is not set
  102. if let Some(mut referer) = referer.0 {
  103. if let Some(start_index) = referer.find(ADMIN_PATH) {
  104. referer.truncate(start_index + ADMIN_PATH.len());
  105. return referer;
  106. }
  107. }
  108. if CONFIG.domain_set() {
  109. // Don't use CONFIG.domain() directly, since the user may want to keep a
  110. // trailing slash there, particularly when running under a subpath.
  111. format!("{}{}{}", CONFIG.domain_origin(), CONFIG.domain_path(), ADMIN_PATH)
  112. } else {
  113. // Last case, when no referer or domain set, technically invalid but better than nothing
  114. ADMIN_PATH.to_string()
  115. }
  116. }
  117. #[get("/", rank = 2)]
  118. fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> {
  119. // If there is an error, show it
  120. let msg = flash.map(|msg| format!("{}: {}", msg.name(), msg.msg()));
  121. let json = json!({
  122. "page_content": "admin/login",
  123. "version": VERSION,
  124. "error": msg,
  125. "urlpath": CONFIG.domain_path()
  126. });
  127. // Return the page
  128. let text = CONFIG.render_template(BASE_TEMPLATE, &json)?;
  129. Ok(Html(text))
  130. }
  131. #[derive(FromForm)]
  132. struct LoginForm {
  133. token: String,
  134. }
  135. #[post("/", data = "<data>")]
  136. fn post_admin_login(
  137. data: Form<LoginForm>,
  138. mut cookies: Cookies,
  139. ip: ClientIp,
  140. referer: Referer,
  141. ) -> Result<Redirect, Flash<Redirect>> {
  142. let data = data.into_inner();
  143. // If the token is invalid, redirect to login page
  144. if !_validate_token(&data.token) {
  145. error!("Invalid admin token. IP: {}", ip.ip);
  146. Err(Flash::error(Redirect::to(admin_url(referer)), "Invalid admin token, please try again."))
  147. } else {
  148. // If the token received is valid, generate JWT and save it as a cookie
  149. let claims = generate_admin_claims();
  150. let jwt = encode_jwt(&claims);
  151. let cookie = Cookie::build(COOKIE_NAME, jwt)
  152. .path(admin_path())
  153. .max_age(time::Duration::minutes(20))
  154. .same_site(SameSite::Strict)
  155. .http_only(true)
  156. .finish();
  157. cookies.add(cookie);
  158. Ok(Redirect::to(admin_url(referer)))
  159. }
  160. }
  161. fn _validate_token(token: &str) -> bool {
  162. match CONFIG.admin_token().as_ref() {
  163. None => false,
  164. Some(t) => crate::crypto::ct_eq(t.trim(), token.trim()),
  165. }
  166. }
  167. #[derive(Serialize)]
  168. struct AdminTemplateData {
  169. page_content: String,
  170. version: Option<&'static str>,
  171. users: Option<Vec<Value>>,
  172. organizations: Option<Vec<Value>>,
  173. diagnostics: Option<Value>,
  174. config: Value,
  175. can_backup: bool,
  176. logged_in: bool,
  177. urlpath: String,
  178. }
  179. impl AdminTemplateData {
  180. fn new() -> Self {
  181. Self {
  182. page_content: String::from("admin/settings"),
  183. version: VERSION,
  184. config: CONFIG.prepare_json(),
  185. can_backup: *CAN_BACKUP,
  186. logged_in: true,
  187. urlpath: CONFIG.domain_path(),
  188. users: None,
  189. organizations: None,
  190. diagnostics: None,
  191. }
  192. }
  193. fn users(users: Vec<Value>) -> Self {
  194. Self {
  195. page_content: String::from("admin/users"),
  196. version: VERSION,
  197. users: Some(users),
  198. config: CONFIG.prepare_json(),
  199. can_backup: *CAN_BACKUP,
  200. logged_in: true,
  201. urlpath: CONFIG.domain_path(),
  202. organizations: None,
  203. diagnostics: None,
  204. }
  205. }
  206. fn organizations(organizations: Vec<Value>) -> Self {
  207. Self {
  208. page_content: String::from("admin/organizations"),
  209. version: VERSION,
  210. organizations: Some(organizations),
  211. config: CONFIG.prepare_json(),
  212. can_backup: *CAN_BACKUP,
  213. logged_in: true,
  214. urlpath: CONFIG.domain_path(),
  215. users: None,
  216. diagnostics: None,
  217. }
  218. }
  219. fn diagnostics(diagnostics: Value) -> Self {
  220. Self {
  221. page_content: String::from("admin/diagnostics"),
  222. version: VERSION,
  223. organizations: None,
  224. config: CONFIG.prepare_json(),
  225. can_backup: *CAN_BACKUP,
  226. logged_in: true,
  227. urlpath: CONFIG.domain_path(),
  228. users: None,
  229. diagnostics: Some(diagnostics),
  230. }
  231. }
  232. fn render(self) -> Result<String, Error> {
  233. CONFIG.render_template(BASE_TEMPLATE, &self)
  234. }
  235. }
  236. #[get("/", rank = 1)]
  237. fn admin_page(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
  238. let text = AdminTemplateData::new().render()?;
  239. Ok(Html(text))
  240. }
  241. #[derive(Deserialize, Debug)]
  242. #[allow(non_snake_case)]
  243. struct InviteData {
  244. email: String,
  245. }
  246. #[post("/invite", data = "<data>")]
  247. fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -> EmptyResult {
  248. let data: InviteData = data.into_inner();
  249. let email = data.email.clone();
  250. if User::find_by_mail(&data.email, &conn).is_some() {
  251. err!("User already exists")
  252. }
  253. let mut user = User::new(email);
  254. user.save(&conn)?;
  255. if CONFIG.mail_enabled() {
  256. mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None)
  257. } else {
  258. let invitation = Invitation::new(data.email);
  259. invitation.save(&conn)
  260. }
  261. }
  262. #[post("/test/smtp", data = "<data>")]
  263. fn test_smtp(data: Json<InviteData>, _token: AdminToken) -> EmptyResult {
  264. let data: InviteData = data.into_inner();
  265. if CONFIG.mail_enabled() {
  266. mail::send_test(&data.email)
  267. } else {
  268. err!("Mail is not enabled")
  269. }
  270. }
  271. #[get("/logout")]
  272. fn logout(mut cookies: Cookies, referer: Referer) -> Redirect {
  273. cookies.remove(Cookie::named(COOKIE_NAME));
  274. Redirect::to(admin_url(referer))
  275. }
  276. #[get("/users")]
  277. fn get_users_json(_token: AdminToken, conn: DbConn) -> Json<Value> {
  278. let users = User::get_all(&conn);
  279. let users_json: Vec<Value> = users.iter().map(|u| u.to_json(&conn)).collect();
  280. Json(Value::Array(users_json))
  281. }
  282. #[get("/users/overview")]
  283. fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
  284. let users = User::get_all(&conn);
  285. let dt_fmt = "%Y-%m-%d %H:%M:%S %Z";
  286. let users_json: Vec<Value> = users
  287. .iter()
  288. .map(|u| {
  289. let mut usr = u.to_json(&conn);
  290. usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &conn));
  291. usr["attachment_count"] = json!(Attachment::count_by_user(&u.uuid, &conn));
  292. usr["attachment_size"] = json!(get_display_size(Attachment::size_by_user(&u.uuid, &conn) as i32));
  293. usr["user_enabled"] = json!(u.enabled);
  294. usr["created_at"] = json!(format_naive_datetime_local(&u.created_at, dt_fmt));
  295. usr["last_active"] = match u.last_active(&conn) {
  296. Some(dt) => json!(format_naive_datetime_local(&dt, dt_fmt)),
  297. None => json!("Never"),
  298. };
  299. usr
  300. })
  301. .collect();
  302. let text = AdminTemplateData::users(users_json).render()?;
  303. Ok(Html(text))
  304. }
  305. #[post("/users/<uuid>/delete")]
  306. fn delete_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
  307. let user = User::find_by_uuid(&uuid, &conn).map_res("User doesn't exist")?;
  308. user.delete(&conn)
  309. }
  310. #[post("/users/<uuid>/deauth")]
  311. fn deauth_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
  312. let mut user = User::find_by_uuid(&uuid, &conn).map_res("User doesn't exist")?;
  313. Device::delete_all_by_user(&user.uuid, &conn)?;
  314. user.reset_security_stamp();
  315. user.save(&conn)
  316. }
  317. #[post("/users/<uuid>/disable")]
  318. fn disable_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
  319. let mut user = User::find_by_uuid(&uuid, &conn).map_res("User doesn't exist")?;
  320. Device::delete_all_by_user(&user.uuid, &conn)?;
  321. user.reset_security_stamp();
  322. user.enabled = false;
  323. user.save(&conn)
  324. }
  325. #[post("/users/<uuid>/enable")]
  326. fn enable_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
  327. let mut user = User::find_by_uuid(&uuid, &conn).map_res("User doesn't exist")?;
  328. user.enabled = true;
  329. user.save(&conn)
  330. }
  331. #[post("/users/<uuid>/remove-2fa")]
  332. fn remove_2fa(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
  333. let mut user = User::find_by_uuid(&uuid, &conn).map_res("User doesn't exist")?;
  334. TwoFactor::delete_all_by_user(&user.uuid, &conn)?;
  335. user.totp_recover = None;
  336. user.save(&conn)
  337. }
  338. #[derive(Deserialize, Debug)]
  339. struct UserOrgTypeData {
  340. user_type: NumberOrString,
  341. user_uuid: String,
  342. org_uuid: String,
  343. }
  344. #[post("/users/org_type", data = "<data>")]
  345. fn update_user_org_type(data: Json<UserOrgTypeData>, _token: AdminToken, conn: DbConn) -> EmptyResult {
  346. let data: UserOrgTypeData = data.into_inner();
  347. let mut user_to_edit = match UserOrganization::find_by_user_and_org(&data.user_uuid, &data.org_uuid, &conn) {
  348. Some(user) => user,
  349. None => err!("The specified user isn't member of the organization"),
  350. };
  351. let new_type = match UserOrgType::from_str(&data.user_type.into_string()) {
  352. Some(new_type) => new_type as i32,
  353. None => err!("Invalid type"),
  354. };
  355. if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner {
  356. // Removing owner permmission, check that there are at least another owner
  357. let num_owners = UserOrganization::find_by_org_and_type(&data.org_uuid, UserOrgType::Owner as i32, &conn).len();
  358. if num_owners <= 1 {
  359. err!("Can't change the type of the last owner")
  360. }
  361. }
  362. user_to_edit.atype = new_type as i32;
  363. user_to_edit.save(&conn)
  364. }
  365. #[post("/users/update_revision")]
  366. fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
  367. User::update_all_revisions(&conn)
  368. }
  369. #[get("/organizations/overview")]
  370. fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
  371. let organizations = Organization::get_all(&conn);
  372. let organizations_json: Vec<Value> = organizations
  373. .iter()
  374. .map(|o| {
  375. let mut org = o.to_json();
  376. org["user_count"] = json!(UserOrganization::count_by_org(&o.uuid, &conn));
  377. org["cipher_count"] = json!(Cipher::count_by_org(&o.uuid, &conn));
  378. org["attachment_count"] = json!(Attachment::count_by_org(&o.uuid, &conn));
  379. org["attachment_size"] = json!(get_display_size(Attachment::size_by_org(&o.uuid, &conn) as i32));
  380. org
  381. })
  382. .collect();
  383. let text = AdminTemplateData::organizations(organizations_json).render()?;
  384. Ok(Html(text))
  385. }
  386. #[post("/organizations/<uuid>/delete")]
  387. fn delete_organization(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
  388. let org = Organization::find_by_uuid(&uuid, &conn).map_res("Organization doesn't exist")?;
  389. org.delete(&conn)
  390. }
  391. #[derive(Deserialize)]
  392. struct WebVaultVersion {
  393. version: String,
  394. }
  395. #[derive(Deserialize)]
  396. struct GitRelease {
  397. tag_name: String,
  398. }
  399. #[derive(Deserialize)]
  400. struct GitCommit {
  401. sha: String,
  402. }
  403. fn get_github_api<T: DeserializeOwned>(url: &str) -> Result<T, Error> {
  404. let github_api = get_reqwest_client();
  405. Ok(github_api
  406. .get(url)
  407. .timeout(Duration::from_secs(10))
  408. .send()?
  409. .error_for_status()?
  410. .json::<T>()?)
  411. }
  412. fn has_http_access() -> bool {
  413. let http_access = get_reqwest_client();
  414. match http_access
  415. .head("https://github.com/dani-garcia/bitwarden_rs")
  416. .timeout(Duration::from_secs(10))
  417. .send()
  418. {
  419. Ok(r) => r.status().is_success(),
  420. _ => false,
  421. }
  422. }
  423. #[get("/diagnostics")]
  424. fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResult<Html<String>> {
  425. use crate::util::read_file_string;
  426. use chrono::prelude::*;
  427. use std::net::ToSocketAddrs;
  428. // Get current running versions
  429. let web_vault_version: WebVaultVersion =
  430. match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "bwrs-version.json")) {
  431. Ok(s) => serde_json::from_str(&s)?,
  432. _ => match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "version.json")) {
  433. Ok(s) => serde_json::from_str(&s)?,
  434. _ => WebVaultVersion {
  435. version: String::from("Version file missing"),
  436. },
  437. },
  438. };
  439. // Execute some environment checks
  440. let running_within_docker = is_running_in_docker();
  441. let has_http_access = has_http_access();
  442. let uses_proxy = env::var_os("HTTP_PROXY").is_some()
  443. || env::var_os("http_proxy").is_some()
  444. || env::var_os("HTTPS_PROXY").is_some()
  445. || env::var_os("https_proxy").is_some();
  446. // Check if we are able to resolve DNS entries
  447. let dns_resolved = match ("github.com", 0).to_socket_addrs().map(|mut i| i.next()) {
  448. Ok(Some(a)) => a.ip().to_string(),
  449. _ => "Could not resolve domain name.".to_string(),
  450. };
  451. // If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway.
  452. // TODO: Maybe we need to cache this using a LazyStatic or something. Github only allows 60 requests per hour, and we use 3 here already.
  453. let (latest_release, latest_commit, latest_web_build) = if has_http_access {
  454. (
  455. match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/bitwarden_rs/releases/latest")
  456. {
  457. Ok(r) => r.tag_name,
  458. _ => "-".to_string(),
  459. },
  460. match get_github_api::<GitCommit>("https://api.github.com/repos/dani-garcia/bitwarden_rs/commits/master") {
  461. Ok(mut c) => {
  462. c.sha.truncate(8);
  463. c.sha
  464. }
  465. _ => "-".to_string(),
  466. },
  467. // Do not fetch the web-vault version when running within Docker.
  468. // The web-vault version is embedded within the container it self, and should not be updated manually
  469. if running_within_docker {
  470. "-".to_string()
  471. } else {
  472. match get_github_api::<GitRelease>(
  473. "https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest",
  474. ) {
  475. Ok(r) => r.tag_name.trim_start_matches('v').to_string(),
  476. _ => "-".to_string(),
  477. }
  478. },
  479. )
  480. } else {
  481. ("-".to_string(), "-".to_string(), "-".to_string())
  482. };
  483. let ip_header_name = match &ip_header.0 {
  484. Some(h) => h,
  485. _ => "",
  486. };
  487. let diagnostics_json = json!({
  488. "dns_resolved": dns_resolved,
  489. "latest_release": latest_release,
  490. "latest_commit": latest_commit,
  491. "web_vault_enabled": &CONFIG.web_vault_enabled(),
  492. "web_vault_version": web_vault_version.version,
  493. "latest_web_build": latest_web_build,
  494. "running_within_docker": running_within_docker,
  495. "has_http_access": has_http_access,
  496. "ip_header_exists": &ip_header.0.is_some(),
  497. "ip_header_match": ip_header_name == CONFIG.ip_header(),
  498. "ip_header_name": ip_header_name,
  499. "ip_header_config": &CONFIG.ip_header(),
  500. "uses_proxy": uses_proxy,
  501. "db_type": *DB_TYPE,
  502. "db_version": get_sql_server_version(&conn),
  503. "admin_url": format!("{}/diagnostics", admin_url(Referer(None))),
  504. "server_time_local": Local::now().format("%Y-%m-%d %H:%M:%S %Z").to_string(),
  505. "server_time": Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(), // Run the date/time check as the last item to minimize the difference
  506. });
  507. let text = AdminTemplateData::diagnostics(diagnostics_json).render()?;
  508. Ok(Html(text))
  509. }
  510. #[get("/diagnostics/config")]
  511. fn get_diagnostics_config(_token: AdminToken) -> Json<Value> {
  512. let support_json = CONFIG.get_support_json();
  513. Json(support_json)
  514. }
  515. #[post("/config", data = "<data>")]
  516. fn post_config(data: Json<ConfigBuilder>, _token: AdminToken) -> EmptyResult {
  517. let data: ConfigBuilder = data.into_inner();
  518. CONFIG.update_config(data)
  519. }
  520. #[post("/config/delete")]
  521. fn delete_config(_token: AdminToken) -> EmptyResult {
  522. CONFIG.delete_user_config()
  523. }
  524. #[post("/config/backup_db")]
  525. fn backup_db(_token: AdminToken, conn: DbConn) -> EmptyResult {
  526. if *CAN_BACKUP {
  527. backup_database(&conn)
  528. } else {
  529. err!("Can't back up current DB (Only SQLite supports this feature)");
  530. }
  531. }
  532. pub struct AdminToken {}
  533. impl<'a, 'r> FromRequest<'a, 'r> for AdminToken {
  534. type Error = &'static str;
  535. fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
  536. if CONFIG.disable_admin_token() {
  537. Outcome::Success(AdminToken {})
  538. } else {
  539. let mut cookies = request.cookies();
  540. let access_token = match cookies.get(COOKIE_NAME) {
  541. Some(cookie) => cookie.value(),
  542. None => return Outcome::Forward(()), // If there is no cookie, redirect to login
  543. };
  544. let ip = match request.guard::<ClientIp>() {
  545. Outcome::Success(ip) => ip.ip,
  546. _ => err_handler!("Error getting Client IP"),
  547. };
  548. if decode_admin(access_token).is_err() {
  549. // Remove admin cookie
  550. cookies.remove(Cookie::named(COOKIE_NAME));
  551. error!("Invalid or expired admin JWT. IP: {}.", ip);
  552. return Outcome::Forward(());
  553. }
  554. Outcome::Success(AdminToken {})
  555. }
  556. }
  557. }