|
|
|
@ -3,7 +3,6 @@ use serde::de::DeserializeOwned;
|
|
|
|
|
use serde_json::Value;
|
|
|
|
|
use std::{env, time::Duration};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use rocket::{
|
|
|
|
|
http::{Cookie, Cookies, SameSite},
|
|
|
|
|
request::{self, FlashMessage, Form, FromRequest, Outcome, Request},
|
|
|
|
@ -19,7 +18,7 @@ use crate::{
|
|
|
|
|
db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType},
|
|
|
|
|
error::{Error, MapResult},
|
|
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -64,11 +63,8 @@ static DB_TYPE: Lazy<&str> = Lazy::new(|| {
|
|
|
|
|
.unwrap_or("Unknown")
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
static CAN_BACKUP: Lazy<bool> = Lazy::new(|| {
|
|
|
|
|
DbConnType::from_url(&CONFIG.database_url())
|
|
|
|
|
.map(|t| t == DbConnType::sqlite)
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
});
|
|
|
|
|
static CAN_BACKUP: Lazy<bool> =
|
|
|
|
|
Lazy::new(|| DbConnType::from_url(&CONFIG.database_url()).map(|t| t == DbConnType::sqlite).unwrap_or(false));
|
|
|
|
|
|
|
|
|
|
#[get("/")]
|
|
|
|
|
fn admin_disabled() -> &'static str {
|
|
|
|
@ -141,7 +137,12 @@ fn admin_url(referer: Referer) -> String {
|
|
|
|
|
fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> {
|
|
|
|
|
// If there is an error, show it
|
|
|
|
|
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
|
|
|
|
|
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 !_validate_token(&data.token) {
|
|
|
|
|
error!("Invalid admin token. IP: {}", ip.ip);
|
|
|
|
|
Err(Flash::error(
|
|
|
|
|
Redirect::to(admin_url(referer)),
|
|
|
|
|
"Invalid admin token, please try again.",
|
|
|
|
|
))
|
|
|
|
|
Err(Flash::error(Redirect::to(admin_url(referer)), "Invalid admin token, please try again."))
|
|
|
|
|
} else {
|
|
|
|
|
// If the token received is valid, generate JWT and save it as a cookie
|
|
|
|
|
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>> {
|
|
|
|
|
let users = User::get_all(&conn);
|
|
|
|
|
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| {
|
|
|
|
|
let mut usr = u.to_json(&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["last_active"] = match u.last_active(&conn) {
|
|
|
|
|
Some(dt) => json!(format_naive_datetime_local(&dt, dt_fmt)),
|
|
|
|
|
None => json!("Never")
|
|
|
|
|
None => json!("Never"),
|
|
|
|
|
};
|
|
|
|
|
usr
|
|
|
|
|
})
|
|
|
|
@ -423,7 +422,6 @@ fn update_user_org_type(data: Json<UserOrgTypeData>, _token: AdminToken, conn: D
|
|
|
|
|
user_to_edit.save(&conn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[post("/users/update_revision")]
|
|
|
|
|
fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
|
|
|
|
|
User::update_all_revisions(&conn)
|
|
|
|
@ -432,7 +430,8 @@ fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
|
|
|
|
|
#[get("/organizations/overview")]
|
|
|
|
|
fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
|
|
|
|
|
let organizations = Organization::get_all(&conn);
|
|
|
|
|
let organizations_json: Vec<Value> = organizations.iter()
|
|
|
|
|
let organizations_json: Vec<Value> = organizations
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|o| {
|
|
|
|
|
let mut org = o.to_json();
|
|
|
|
|
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> {
|
|
|
|
|
let github_api = get_reqwest_client();
|
|
|
|
|
|
|
|
|
|
Ok(github_api
|
|
|
|
|
.get(url)
|
|
|
|
|
.timeout(Duration::from_secs(10))
|
|
|
|
|
.send()?
|
|
|
|
|
.error_for_status()?
|
|
|
|
|
.json::<T>()?)
|
|
|
|
|
Ok(github_api.get(url).timeout(Duration::from_secs(10)).send()?.error_for_status()?.json::<T>()?)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn has_http_access() -> bool {
|
|
|
|
|
let http_access = get_reqwest_client();
|
|
|
|
|
|
|
|
|
|
match http_access
|
|
|
|
|
.head("https://github.com/dani-garcia/bitwarden_rs")
|
|
|
|
|
.timeout(Duration::from_secs(10))
|
|
|
|
|
.send()
|
|
|
|
|
{
|
|
|
|
|
match http_access.head("https://github.com/dani-garcia/bitwarden_rs").timeout(Duration::from_secs(10)).send() {
|
|
|
|
|
Ok(r) => r.status().is_success(),
|
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
@ -499,15 +489,14 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
|
|
|
|
|
use std::net::ToSocketAddrs;
|
|
|
|
|
|
|
|
|
|
// 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)?,
|
|
|
|
|
_ => {
|
|
|
|
|
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)?,
|
|
|
|
|
_ => {
|
|
|
|
|
WebVaultVersion{version: String::from("Version file missing")}
|
|
|
|
|
_ => WebVaultVersion {
|
|
|
|
|
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.
|
|
|
|
|
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,
|
|
|
|
|
_ => "-".to_string(),
|
|
|
|
|
},
|
|
|
|
@ -545,7 +535,9 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
|
|
|
|
|
if running_within_docker {
|
|
|
|
|
"-".to_string()
|
|
|
|
|
} 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(),
|
|
|
|
|
_ => "-".to_string(),
|
|
|
|
|
}
|
|
|
|
@ -557,7 +549,7 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
|
|
|
|
|
|
|
|
|
|
let ip_header_name = match &ip_header.0 {
|
|
|
|
|
Some(h) => h,
|
|
|
|
|
_ => ""
|
|
|
|
|
_ => "",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let diagnostics_json = json!({
|
|
|
|
|