2018-02-10 01:00:55 +01:00
use std ::path ::{ Path , PathBuf } ;
2022-12-02 17:58:27 +01:00
use rocket ::{ fs ::NamedFile , http ::ContentType , response ::content ::RawHtml as Html , serde ::json ::Json , Catcher , Route } ;
2018-10-10 20:40:39 +02:00
use serde_json ::Value ;
2018-02-10 01:00:55 +01:00
2021-07-15 21:52:17 +02:00
use crate ::{
2023-03-04 19:19:09 -08:00
api ::{ core ::now , ApiResult , EmptyResult } ,
2023-07-03 19:58:14 +02:00
auth ::decode_file_download ,
2021-07-15 21:52:17 +02:00
error ::Error ,
util ::{ Cached , SafeString } ,
CONFIG ,
} ;
2018-02-10 01:00:55 +01:00
pub fn routes ( ) -> Vec < Route > {
2023-10-05 20:08:26 +03:00
// If adding more routes here, consider also adding them to
2019-12-06 22:19:07 +01:00
// crate::utils::LOGGED_ROUTES to make sure they appear in the log
2023-07-27 22:51:22 +02:00
let mut routes = routes! [ attachments , alive , alive_head , static_files ] ;
2019-01-25 18:23:51 +01:00
if CONFIG . web_vault_enabled ( ) {
2023-07-27 22:51:22 +02:00
routes . append ( & mut routes! [ web_index , web_index_head , app_id , web_files ] ) ;
}
#[ cfg(debug_assertions) ]
if CONFIG . reload_templates ( ) {
routes . append ( & mut routes! [ _static_files_dev ] ) ;
2018-06-12 21:09:42 +02:00
}
2023-07-27 22:51:22 +02:00
routes
2018-02-10 01:00:55 +01:00
}
2022-09-25 04:02:16 +02:00
pub fn catchers ( ) -> Vec < Catcher > {
if CONFIG . web_vault_enabled ( ) {
catchers! [ not_found ]
} else {
catchers! [ ]
}
}
#[ catch(404) ]
2022-12-02 17:58:27 +01:00
fn not_found ( ) -> ApiResult < Html < String > > {
// Return the page
let json = json! ( {
" urlpath " : CONFIG . domain_path ( )
} ) ;
let text = CONFIG . render_template ( " 404 " , & json ) ? ;
Ok ( Html ( text ) )
2022-09-25 04:02:16 +02:00
}
2018-02-10 01:00:55 +01:00
#[ get( " / " ) ]
2021-11-07 18:53:39 +01:00
async fn web_index ( ) -> Cached < Option < NamedFile > > {
Cached ::short ( NamedFile ::open ( Path ::new ( & CONFIG . web_vault_folder ( ) ) . join ( " index.html " ) ) . await . ok ( ) , false )
2018-02-10 01:00:55 +01:00
}
2023-03-04 19:19:09 -08:00
#[ head( " / " ) ]
fn web_index_head ( ) -> EmptyResult {
// Add an explicit HEAD route to prevent uptime monitoring services from
// generating "No matching routes for HEAD /" error messages.
//
// Rocket automatically implements a HEAD route when there's a matching GET
// route, but relying on this behavior also means a spurious error gets
// logged due to <https://github.com/SergioBenitez/Rocket/issues/1098>.
Ok ( ( ) )
}
2018-07-12 21:46:50 +02:00
#[ get( " /app-id.json " ) ]
2021-11-07 18:53:39 +01:00
fn app_id ( ) -> Cached < ( ContentType , Json < Value > ) > {
2018-07-13 15:05:00 +02:00
let content_type = ContentType ::new ( " application " , " fido.trusted-apps+json " ) ;
2021-12-28 16:24:42 +00:00
Cached ::long (
2021-11-07 18:53:39 +01:00
(
2021-12-28 16:24:42 +00:00
content_type ,
Json ( json! ( {
" trustedFacets " : [
{
" version " : { " major " : 1 , " minor " : 0 } ,
" ids " : [
// Per <https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-appid-and-facets-v2.0-id-20180227.html#determining-the-facetid-of-a-calling-application>:
//
// "In the Web case, the FacetID MUST be the Web Origin [RFC6454]
// of the web page triggering the FIDO operation, written as
// a URI with an empty path. Default ports are omitted and any
// path component is ignored."
//
// This leaves it unclear as to whether the path must be empty,
// or whether it can be non-empty and will be ignored. To be on
// the safe side, use a proper web origin (with empty path).
& CONFIG . domain_origin ( ) ,
" ios:bundle-id:com.8bit.bitwarden " ,
" android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI " ]
} ]
} ) ) ,
) ,
true ,
)
2018-07-12 21:46:50 +02:00
}
2019-01-19 21:36:34 +01:00
#[ get( " /<p..> " , rank = 10) ] // Only match this if the other routes don't match
2021-11-07 18:53:39 +01:00
async fn web_files ( p : PathBuf ) -> Cached < Option < NamedFile > > {
Cached ::long ( NamedFile ::open ( Path ::new ( & CONFIG . web_vault_folder ( ) ) . join ( p ) ) . await . ok ( ) , true )
2018-02-10 01:00:55 +01:00
}
2023-07-03 19:58:14 +02:00
#[ get( " /attachments/<uuid>/<file_id>?<token> " ) ]
async fn attachments ( uuid : SafeString , file_id : SafeString , token : String ) -> Option < NamedFile > {
2023-08-28 16:48:42 +02:00
let Ok ( claims ) = decode_file_download ( & token ) else {
return None ;
} ;
2023-07-03 19:58:14 +02:00
if claims . sub ! = * uuid | | claims . file_id ! = * file_id {
return None ;
}
2021-11-07 18:53:39 +01:00
NamedFile ::open ( Path ::new ( & CONFIG . attachments_folder ( ) ) . join ( uuid ) . join ( file_id ) ) . await . ok ( )
2018-02-10 01:00:55 +01:00
}
2021-10-09 14:16:27 +02:00
// We use DbConn here to let the alive healthcheck also verify the database connection.
use crate ::db ::DbConn ;
2018-02-10 01:00:55 +01:00
#[ get( " /alive " ) ]
2021-10-09 14:16:27 +02:00
fn alive ( _conn : DbConn ) -> Json < String > {
2022-04-23 23:47:49 -07:00
now ( )
2018-02-10 01:00:55 +01:00
}
2019-02-16 03:44:30 +01:00
2023-03-04 19:19:09 -08:00
#[ head( " /alive " ) ]
fn alive_head ( _conn : DbConn ) -> EmptyResult {
// Avoid logging spurious "No matching routes for HEAD /alive" errors
// due to <https://github.com/SergioBenitez/Rocket/issues/1098>.
Ok ( ( ) )
}
2023-07-27 22:51:22 +02:00
// This endpoint/function is used during development and development only.
// It allows to easily develop the admin interface by always loading the files from disk instead from a slice of bytes
// This will only be active during a debug build and only when `RELOAD_TEMPLATES` is set to `true`
// NOTE: Do not forget to add any new files added to the `static_files` function below!
#[ cfg(debug_assertions) ]
#[ get( " /vw_static/<filename> " , rank = 1) ]
pub async fn _static_files_dev ( filename : PathBuf ) -> Option < NamedFile > {
warn! ( " LOADING STATIC FILES FROM DISK " ) ;
let file = filename . to_str ( ) . unwrap_or_default ( ) ;
let ext = filename . extension ( ) . unwrap_or_default ( ) ;
let path = if ext = = " png " | | ext = = " svg " {
tokio ::fs ::canonicalize ( Path ::new ( file! ( ) ) . parent ( ) . unwrap ( ) . join ( " ../static/images/ " ) . join ( file ) ) . await
} else {
tokio ::fs ::canonicalize ( Path ::new ( file! ( ) ) . parent ( ) . unwrap ( ) . join ( " ../static/scripts/ " ) . join ( file ) ) . await
} ;
if let Ok ( path ) = path {
return NamedFile ::open ( path ) . await . ok ( ) ;
} ;
None
}
#[ get( " /vw_static/<filename> " , rank = 2) ]
2023-04-30 17:18:12 +02:00
pub fn static_files ( filename : & str ) -> Result < ( ContentType , & 'static [ u8 ] ) , Error > {
match filename {
2022-12-02 17:58:27 +01:00
" 404.png " = > Ok ( ( ContentType ::PNG , include_bytes! ( " ../static/images/404.png " ) ) ) ,
2021-11-07 18:53:39 +01:00
" mail-github.png " = > Ok ( ( ContentType ::PNG , include_bytes! ( " ../static/images/mail-github.png " ) ) ) ,
" logo-gray.png " = > Ok ( ( ContentType ::PNG , include_bytes! ( " ../static/images/logo-gray.png " ) ) ) ,
" error-x.svg " = > Ok ( ( ContentType ::SVG , include_bytes! ( " ../static/images/error-x.svg " ) ) ) ,
" hibp.png " = > Ok ( ( ContentType ::PNG , include_bytes! ( " ../static/images/hibp.png " ) ) ) ,
" vaultwarden-icon.png " = > Ok ( ( ContentType ::PNG , include_bytes! ( " ../static/images/vaultwarden-icon.png " ) ) ) ,
2022-12-09 17:32:59 -05:00
" vaultwarden-favicon.png " = > Ok ( ( ContentType ::PNG , include_bytes! ( " ../static/images/vaultwarden-favicon.png " ) ) ) ,
2022-12-28 20:05:10 +01:00
" 404.css " = > Ok ( ( ContentType ::CSS , include_bytes! ( " ../static/scripts/404.css " ) ) ) ,
" admin.css " = > Ok ( ( ContentType ::CSS , include_bytes! ( " ../static/scripts/admin.css " ) ) ) ,
" admin.js " = > Ok ( ( ContentType ::JavaScript , include_bytes! ( " ../static/scripts/admin.js " ) ) ) ,
" admin_settings.js " = > Ok ( ( ContentType ::JavaScript , include_bytes! ( " ../static/scripts/admin_settings.js " ) ) ) ,
" admin_users.js " = > Ok ( ( ContentType ::JavaScript , include_bytes! ( " ../static/scripts/admin_users.js " ) ) ) ,
" admin_organizations.js " = > {
Ok ( ( ContentType ::JavaScript , include_bytes! ( " ../static/scripts/admin_organizations.js " ) ) )
}
" admin_diagnostics.js " = > {
Ok ( ( ContentType ::JavaScript , include_bytes! ( " ../static/scripts/admin_diagnostics.js " ) ) )
}
2021-11-07 18:53:39 +01:00
" bootstrap.css " = > Ok ( ( ContentType ::CSS , include_bytes! ( " ../static/scripts/bootstrap.css " ) ) ) ,
2023-07-27 22:51:22 +02:00
" bootstrap.bundle.js " = > Ok ( ( ContentType ::JavaScript , include_bytes! ( " ../static/scripts/bootstrap.bundle.js " ) ) ) ,
2024-05-25 15:39:36 +02:00
" jdenticon-3.3.0.js " = > Ok ( ( ContentType ::JavaScript , include_bytes! ( " ../static/scripts/jdenticon-3.3.0.js " ) ) ) ,
2021-11-07 18:53:39 +01:00
" datatables.js " = > Ok ( ( ContentType ::JavaScript , include_bytes! ( " ../static/scripts/datatables.js " ) ) ) ,
" datatables.css " = > Ok ( ( ContentType ::CSS , include_bytes! ( " ../static/scripts/datatables.css " ) ) ) ,
2024-03-02 13:09:36 -05:00
" jquery-3.7.1.slim.js " = > {
2024-02-25 23:26:46 +01:00
Ok ( ( ContentType ::JavaScript , include_bytes! ( " ../static/scripts/jquery-3.7.1.slim.js " ) ) )
2021-04-06 21:54:42 +01:00
}
2022-12-29 14:11:52 +01:00
_ = > err! ( format! ( " Static file not found: {filename} " ) ) ,
2019-02-16 03:44:30 +01:00
}
2019-12-27 18:37:14 +01:00
}