|
@ -3,7 +3,6 @@ use serde::de::DeserializeOwned; |
|
|
use serde_json::Value; |
|
|
use serde_json::Value; |
|
|
use std::{env, time::Duration}; |
|
|
use std::{env, time::Duration}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use rocket::{ |
|
|
use rocket::{ |
|
|
http::{Cookie, Cookies, SameSite}, |
|
|
http::{Cookie, Cookies, SameSite}, |
|
|
request::{self, FlashMessage, Form, FromRequest, Outcome, Request}, |
|
|
request::{self, FlashMessage, Form, FromRequest, Outcome, Request}, |
|
@ -19,7 +18,7 @@ use crate::{ |
|
|
db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType}, |
|
|
db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType}, |
|
|
error::{Error, MapResult}, |
|
|
error::{Error, MapResult}, |
|
|
mail, |
|
|
mail, |
|
|
util::{format_naive_datetime_local, get_display_size, is_running_in_docker, get_reqwest_client}, |
|
|
util::{format_naive_datetime_local, get_display_size, get_reqwest_client, is_running_in_docker}, |
|
|
CONFIG, |
|
|
CONFIG, |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@ -64,11 +63,8 @@ static DB_TYPE: Lazy<&str> = Lazy::new(|| { |
|
|
.unwrap_or("Unknown") |
|
|
.unwrap_or("Unknown") |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
static CAN_BACKUP: Lazy<bool> = Lazy::new(|| { |
|
|
static CAN_BACKUP: Lazy<bool> = |
|
|
DbConnType::from_url(&CONFIG.database_url()) |
|
|
Lazy::new(|| DbConnType::from_url(&CONFIG.database_url()).map(|t| t == DbConnType::sqlite).unwrap_or(false)); |
|
|
.map(|t| t == DbConnType::sqlite) |
|
|
|
|
|
.unwrap_or(false) |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
#[get("/")] |
|
|
#[get("/")] |
|
|
fn admin_disabled() -> &'static str { |
|
|
fn admin_disabled() -> &'static str { |
|
@ -141,7 +137,12 @@ fn admin_url(referer: Referer) -> String { |
|
|
fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> { |
|
|
fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> { |
|
|
// If there is an error, show it
|
|
|
// If there is an error, show it
|
|
|
let msg = flash.map(|msg| format!("{}: {}", msg.name(), msg.msg())); |
|
|
let msg = flash.map(|msg| format!("{}: {}", msg.name(), msg.msg())); |
|
|
let json = json!({"page_content": "admin/login", "version": VERSION, "error": msg, "urlpath": CONFIG.domain_path()}); |
|
|
let json = json!({ |
|
|
|
|
|
"page_content": "admin/login", |
|
|
|
|
|
"version": VERSION, |
|
|
|
|
|
"error": msg, |
|
|
|
|
|
"urlpath": CONFIG.domain_path() |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
// Return the page
|
|
|
// Return the page
|
|
|
let text = CONFIG.render_template(BASE_TEMPLATE, &json)?; |
|
|
let text = CONFIG.render_template(BASE_TEMPLATE, &json)?; |
|
@ -165,10 +166,7 @@ fn post_admin_login( |
|
|
// If the token is invalid, redirect to login page
|
|
|
// If the token is invalid, redirect to login page
|
|
|
if !_validate_token(&data.token) { |
|
|
if !_validate_token(&data.token) { |
|
|
error!("Invalid admin token. IP: {}", ip.ip); |
|
|
error!("Invalid admin token. IP: {}", ip.ip); |
|
|
Err(Flash::error( |
|
|
Err(Flash::error(Redirect::to(admin_url(referer)), "Invalid admin token, please try again.")) |
|
|
Redirect::to(admin_url(referer)), |
|
|
|
|
|
"Invalid admin token, please try again.", |
|
|
|
|
|
)) |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
// If the token received is valid, generate JWT and save it as a cookie
|
|
|
// If the token received is valid, generate JWT and save it as a cookie
|
|
|
let claims = generate_admin_claims(); |
|
|
let claims = generate_admin_claims(); |
|
@ -328,7 +326,8 @@ fn get_users_json(_token: AdminToken, conn: DbConn) -> Json<Value> { |
|
|
fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> { |
|
|
fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> { |
|
|
let users = User::get_all(&conn); |
|
|
let users = User::get_all(&conn); |
|
|
let dt_fmt = "%Y-%m-%d %H:%M:%S %Z"; |
|
|
let dt_fmt = "%Y-%m-%d %H:%M:%S %Z"; |
|
|
let users_json: Vec<Value> = users.iter() |
|
|
let users_json: Vec<Value> = users |
|
|
|
|
|
.iter() |
|
|
.map(|u| { |
|
|
.map(|u| { |
|
|
let mut usr = u.to_json(&conn); |
|
|
let mut usr = u.to_json(&conn); |
|
|
usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &conn)); |
|
|
usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &conn)); |
|
@ -338,7 +337,7 @@ fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> { |
|
|
usr["created_at"] = json!(format_naive_datetime_local(&u.created_at, dt_fmt)); |
|
|
usr["created_at"] = json!(format_naive_datetime_local(&u.created_at, dt_fmt)); |
|
|
usr["last_active"] = match u.last_active(&conn) { |
|
|
usr["last_active"] = match u.last_active(&conn) { |
|
|
Some(dt) => json!(format_naive_datetime_local(&dt, dt_fmt)), |
|
|
Some(dt) => json!(format_naive_datetime_local(&dt, dt_fmt)), |
|
|
None => json!("Never") |
|
|
None => json!("Never"), |
|
|
}; |
|
|
}; |
|
|
usr |
|
|
usr |
|
|
}) |
|
|
}) |
|
@ -423,7 +422,6 @@ fn update_user_org_type(data: Json<UserOrgTypeData>, _token: AdminToken, conn: D |
|
|
user_to_edit.save(&conn) |
|
|
user_to_edit.save(&conn) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[post("/users/update_revision")] |
|
|
#[post("/users/update_revision")] |
|
|
fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult { |
|
|
fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult { |
|
|
User::update_all_revisions(&conn) |
|
|
User::update_all_revisions(&conn) |
|
@ -432,7 +430,8 @@ fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult { |
|
|
#[get("/organizations/overview")] |
|
|
#[get("/organizations/overview")] |
|
|
fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> { |
|
|
fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> { |
|
|
let organizations = Organization::get_all(&conn); |
|
|
let organizations = Organization::get_all(&conn); |
|
|
let organizations_json: Vec<Value> = organizations.iter() |
|
|
let organizations_json: Vec<Value> = organizations |
|
|
|
|
|
.iter() |
|
|
.map(|o| { |
|
|
.map(|o| { |
|
|
let mut org = o.to_json(); |
|
|
let mut org = o.to_json(); |
|
|
org["user_count"] = json!(UserOrganization::count_by_org(&o.uuid, &conn)); |
|
|
org["user_count"] = json!(UserOrganization::count_by_org(&o.uuid, &conn)); |
|
@ -471,22 +470,13 @@ struct GitCommit { |
|
|
fn get_github_api<T: DeserializeOwned>(url: &str) -> Result<T, Error> { |
|
|
fn get_github_api<T: DeserializeOwned>(url: &str) -> Result<T, Error> { |
|
|
let github_api = get_reqwest_client(); |
|
|
let github_api = get_reqwest_client(); |
|
|
|
|
|
|
|
|
Ok(github_api |
|
|
Ok(github_api.get(url).timeout(Duration::from_secs(10)).send()?.error_for_status()?.json::<T>()?) |
|
|
.get(url) |
|
|
|
|
|
.timeout(Duration::from_secs(10)) |
|
|
|
|
|
.send()? |
|
|
|
|
|
.error_for_status()? |
|
|
|
|
|
.json::<T>()?) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
fn has_http_access() -> bool { |
|
|
fn has_http_access() -> bool { |
|
|
let http_access = get_reqwest_client(); |
|
|
let http_access = get_reqwest_client(); |
|
|
|
|
|
|
|
|
match http_access |
|
|
match http_access.head("https://github.com/dani-garcia/bitwarden_rs").timeout(Duration::from_secs(10)).send() { |
|
|
.head("https://github.com/dani-garcia/bitwarden_rs") |
|
|
|
|
|
.timeout(Duration::from_secs(10)) |
|
|
|
|
|
.send() |
|
|
|
|
|
{ |
|
|
|
|
|
Ok(r) => r.status().is_success(), |
|
|
Ok(r) => r.status().is_success(), |
|
|
_ => false, |
|
|
_ => false, |
|
|
} |
|
|
} |
|
@ -499,15 +489,14 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu |
|
|
use std::net::ToSocketAddrs; |
|
|
use std::net::ToSocketAddrs; |
|
|
|
|
|
|
|
|
// Get current running versions
|
|
|
// Get current running versions
|
|
|
let web_vault_version: WebVaultVersion = match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "bwrs-version.json")) { |
|
|
let web_vault_version: WebVaultVersion = |
|
|
|
|
|
match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "bwrs-version.json")) { |
|
|
Ok(s) => serde_json::from_str(&s)?, |
|
|
Ok(s) => serde_json::from_str(&s)?, |
|
|
_ => { |
|
|
_ => match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "version.json")) { |
|
|
match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "version.json")) { |
|
|
|
|
|
Ok(s) => serde_json::from_str(&s)?, |
|
|
Ok(s) => serde_json::from_str(&s)?, |
|
|
_ => { |
|
|
_ => WebVaultVersion { |
|
|
WebVaultVersion{version: String::from("Version file missing")} |
|
|
version: String::from("Version file missing"), |
|
|
}, |
|
|
}, |
|
|
} |
|
|
|
|
|
}, |
|
|
}, |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@ -529,7 +518,8 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu |
|
|
// 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.
|
|
|
// 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.
|
|
|
let (latest_release, latest_commit, latest_web_build) = if has_http_access { |
|
|
let (latest_release, latest_commit, latest_web_build) = if has_http_access { |
|
|
( |
|
|
( |
|
|
match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/bitwarden_rs/releases/latest") { |
|
|
match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/bitwarden_rs/releases/latest") |
|
|
|
|
|
{ |
|
|
Ok(r) => r.tag_name, |
|
|
Ok(r) => r.tag_name, |
|
|
_ => "-".to_string(), |
|
|
_ => "-".to_string(), |
|
|
}, |
|
|
}, |
|
@ -545,7 +535,9 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu |
|
|
if running_within_docker { |
|
|
if running_within_docker { |
|
|
"-".to_string() |
|
|
"-".to_string() |
|
|
} else { |
|
|
} else { |
|
|
match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest") { |
|
|
match get_github_api::<GitRelease>( |
|
|
|
|
|
"https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest", |
|
|
|
|
|
) { |
|
|
Ok(r) => r.tag_name.trim_start_matches('v').to_string(), |
|
|
Ok(r) => r.tag_name.trim_start_matches('v').to_string(), |
|
|
_ => "-".to_string(), |
|
|
_ => "-".to_string(), |
|
|
} |
|
|
} |
|
@ -557,7 +549,7 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu |
|
|
|
|
|
|
|
|
let ip_header_name = match &ip_header.0 { |
|
|
let ip_header_name = match &ip_header.0 { |
|
|
Some(h) => h, |
|
|
Some(h) => h, |
|
|
_ => "" |
|
|
_ => "", |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
let diagnostics_json = json!({ |
|
|
let diagnostics_json = json!({ |
|
|