@ -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 ! ( {