2021-04-01 14:58:40 +02:00
extern crate sqlx ;
pub mod forms ;
pub mod models ;
pub mod queries ;
2021-04-18 09:41:17 +02:00
mod views ;
2021-04-01 14:58:40 +02:00
2021-05-04 11:29:36 +02:00
use actix_files ::Files ;
2021-04-18 09:41:17 +02:00
use actix_identity ::{ CookieIdentityPolicy , IdentityService } ;
2021-04-01 14:58:40 +02:00
use actix_web ::HttpResponse ;
2021-04-18 09:41:17 +02:00
use actix_web ::{ web , App , HttpServer } ;
2021-06-12 16:42:29 +02:00
use fluent_templates ::static_loader ;
2021-04-01 14:58:40 +02:00
use qrcode ::types ::QrError ;
2021-05-20 11:40:44 +02:00
use shared ::datatypes ::Secret ;
2021-04-01 14:58:40 +02:00
use sqlx ::{ Pool , Sqlite } ;
2021-04-18 09:41:17 +02:00
use std ::{ fmt ::Display , path ::PathBuf , str ::FromStr } ;
2021-04-01 15:40:19 +02:00
use thiserror ::Error ;
2021-04-18 09:41:17 +02:00
use tracing ::{ error , info , trace } ;
use tracing_actix_web ::TracingLogger ;
2021-04-01 15:40:19 +02:00
#[ derive(Error, Debug) ]
2021-04-01 14:58:40 +02:00
pub enum ServerError {
2021-04-01 15:40:19 +02:00
#[ error( " Failed to encrypt the password {0} - aborting! " ) ]
Argonautica ( argonautica ::Error ) ,
#[ error( " The database could not be used: {0} " ) ]
Database ( #[ from ] sqlx ::Error ) ,
#[ error( " The database could not be migrated: {0} " ) ]
DatabaseMigration ( #[ from ] sqlx ::migrate ::MigrateError ) ,
#[ error( " The environment file could not be read " ) ]
Environment ( #[ from ] std ::env ::VarError ) ,
#[ error( " The qr-code could not be generated: {0} " ) ]
Qr ( #[ from ] QrError ) ,
#[ error( " Some error happened during input and output: {0} " ) ]
Io ( #[ from ] std ::io ::Error ) ,
#[ error( " Error: {0} " ) ]
2021-04-01 14:58:40 +02:00
User ( String ) ,
}
2021-04-01 15:40:19 +02:00
impl From < argonautica ::Error > for ServerError {
fn from ( e : argonautica ::Error ) -> Self {
Self ::Argonautica ( e )
2021-04-01 14:58:40 +02:00
}
}
2021-04-10 12:30:05 +02:00
impl ServerError {
fn render_error ( title : & str , content : & str ) -> String {
format! (
" <!DOCTYPE html>
< html lang = \ " en \" >
< head >
< meta charset = \ " utf-8 \" >
< title > { 0 } < / title >
< meta name = \ " author \" content= \" Franz Dietrich \" >
< meta http - equiv = \ " robots \" content= \" [noindex|nofollow] \" >
< link rel = \ " stylesheet \" href= \" /static/style.css \" >
< / head >
< body >
< section class = \ " centered \" >
< h1 > { 0 } < / h1 >
< div class = \ " container \" >
{ 1 }
< / div >
< / section >
< / body >
< / html > " ,
title , content
)
}
}
2021-04-01 14:58:40 +02:00
impl actix_web ::error ::ResponseError for ServerError {
fn error_response ( & self ) -> HttpResponse {
match self {
2021-04-10 12:30:05 +02:00
Self ::Argonautica ( e ) = > {
eprintln! ( " Argonautica Error happened: {:?} " , e ) ;
HttpResponse ::InternalServerError ( )
. body ( " Failed to encrypt the password - Aborting! " )
}
2021-04-01 14:58:40 +02:00
Self ::Database ( e ) = > {
2021-04-10 12:30:05 +02:00
eprintln! ( " Database Error happened: {:?} " , e ) ;
HttpResponse ::InternalServerError ( ) . body ( & Self ::render_error (
" Server Error " ,
" Database could not be accessed! - It could be that this value already was in the database! If you are the admin look into the logs for a more detailed error. " ,
) )
2021-04-01 14:58:40 +02:00
}
2021-04-10 12:30:05 +02:00
Self ::DatabaseMigration ( e ) = > {
eprintln! ( " Migration Error happened: {:?} " , e ) ;
2021-04-01 14:58:40 +02:00
unimplemented! ( " A migration error should never be rendered " )
}
2021-04-10 12:30:05 +02:00
Self ::Environment ( e ) = > {
eprintln! ( " Environment Error happened: {:?} " , e ) ;
HttpResponse ::InternalServerError ( ) . body ( & Self ::render_error (
" Server Error " ,
" This Server is not properly configured, if you are the admin look into the installation- or update instructions! " ,
) )
}
2021-04-01 14:58:40 +02:00
Self ::Qr ( e ) = > {
2021-04-10 12:30:05 +02:00
eprintln! ( " QR Error happened: {:?} " , e ) ;
HttpResponse ::InternalServerError ( ) . body ( & Self ::render_error (
" Server Error " ,
" Could not generate the QR-code! " ,
) )
}
Self ::Io ( e ) = > {
eprintln! ( " Io Error happened: {:?} " , e ) ;
HttpResponse ::InternalServerError ( ) . body ( & Self ::render_error (
" Server Error " ,
" Some Files could not be read or written. If you are the admin look into the logfiles for more details. " ,
) )
}
Self ::User ( data ) = > {
eprintln! ( " User Error happened: {:?} " , data ) ;
HttpResponse ::InternalServerError ( ) . body ( & Self ::render_error (
" Server Error " ,
& format! ( " An error happened: {} " , data ) ,
) )
2021-04-01 14:58:40 +02:00
}
}
}
}
#[ derive(Debug, Clone) ]
pub enum Protocol {
Http ,
Https ,
}
impl Display for Protocol {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
match self {
Self ::Http = > f . write_str ( " http " ) ,
Self ::Https = > f . write_str ( " https " ) ,
}
}
}
impl FromStr for Protocol {
type Err = ServerError ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
match s {
" http " = > Ok ( Self ::Http ) ,
" https " = > Ok ( Self ::Https ) ,
_ = > Err ( ServerError ::User ( " Failed to parse Protocol " . to_owned ( ) ) ) ,
}
}
}
#[ derive(Debug, Clone) ]
pub struct ServerConfig {
2021-04-18 09:41:17 +02:00
pub secret : Secret ,
2021-04-01 14:58:40 +02:00
pub db : PathBuf ,
pub db_pool : Pool < Sqlite > ,
pub public_url : String ,
pub internal_ip : String ,
pub port : u32 ,
pub protocol : Protocol ,
pub empty_forward_url : String ,
pub brand_name : String ,
}
impl ServerConfig {
#[ must_use ]
pub fn to_env_strings ( & self ) -> Vec < String > {
vec! [
format! ( " PSLINK_DATABASE= \" {} \" \n " , self . db . display ( ) ) ,
format! ( " PSLINK_PORT= {} \n " , self . port ) ,
format! ( " PSLINK_PUBLIC_URL= \" {} \" \n " , self . public_url ) ,
format! ( " PSLINK_EMPTY_FORWARD_URL= \" {} \" \n " , self . empty_forward_url ) ,
format! ( " PSLINK_BRAND_NAME= \" {} \" \n " , self . brand_name ) ,
format! ( " PSLINK_IP= \" {} \" \n " , self . internal_ip ) ,
format! ( " PSLINK_PROTOCOL= \" {} \" \n " , self . protocol ) ,
concat! (
" # The SECRET_KEY variable is used for password encryption. \n " ,
" # If it is changed all existing passwords are invalid. \n "
)
. to_owned ( ) ,
2021-05-20 11:40:44 +02:00
format! (
" PSLINK_SECRET= \" {} \" \n " ,
self . secret
. secret
. as_ref ( )
. expect ( " A Secret was not specified! " )
) ,
2021-04-01 14:58:40 +02:00
]
}
}
2021-04-18 09:41:17 +02:00
include! ( concat! ( env! ( " OUT_DIR " ) , " /generated.rs " ) ) ;
static_loader! {
static LOCALES = {
locales : " ./locales " ,
fallback_language : " en " ,
} ;
}
/// Launch the pslink-webservice
///
/// # Errors
/// This produces a [`ServerError`] if:
/// * Tera failed to build its templates
/// * The server failed to bind to the designated port.
#[ allow(clippy::future_not_send, clippy::too_many_lines) ]
pub async fn webservice (
server_config : ServerConfig ,
2021-04-18 11:38:07 +02:00
) -> Result < actix_web ::dev ::Server , std ::io ::Error > {
2021-04-18 09:41:17 +02:00
let host_port = format! ( " {} : {} " , & server_config . internal_ip , & server_config . port ) ;
info! (
" Running on: {}://{}/admin/login/ " ,
& server_config . protocol , host_port
) ;
info! (
" If the public url is set up correctly it should be accessible via: {}://{}/admin/login/ " ,
& server_config . protocol , & server_config . public_url
) ;
trace! ( " The tera templates are ready " ) ;
let server = HttpServer ::new ( move | | {
let generated = generate ( ) ;
App ::new ( )
. data ( server_config . clone ( ) )
. wrap ( TracingLogger )
. wrap ( IdentityService ::new (
CookieIdentityPolicy ::new ( & [ 0 ; 32 ] )
. name ( " auth-cookie " )
2021-05-26 20:10:01 +02:00
. secure ( true ) ,
2021-04-18 09:41:17 +02:00
) )
. service ( actix_web_static_files ::ResourceFiles ::new (
" /static " , generated ,
) )
// directly go to the main page set the target with the environment variable.
. route ( " / " , web ::get ( ) . to ( views ::redirect_empty ) )
// admin block
. service (
web ::scope ( " /admin " )
2021-06-13 12:58:55 +02:00
. route ( " /logout/ " , web ::to ( views ::logout ) )
2021-04-18 09:41:17 +02:00
. service (
web ::scope ( " /download " )
. route ( " /png/{redirect_id} " , web ::get ( ) . to ( views ::download_png ) ) ,
)
2021-05-04 11:29:36 +02:00
. service (
web ::scope ( " /json " )
. route ( " /list_links/ " , web ::post ( ) . to ( views ::index_json ) )
2021-06-13 12:58:55 +02:00
. route ( " /get_language/ " , web ::get ( ) . to ( views ::get_language ) )
. route ( " /change_language/ " , web ::post ( ) . to ( views ::set_language ) )
2021-05-20 11:40:44 +02:00
. route (
" /create_link/ " ,
web ::post ( ) . to ( views ::process_create_link_json ) ,
)
. route (
" /edit_link/ " ,
web ::post ( ) . to ( views ::process_update_link_json ) ,
)
. route (
" /delete_link/ " ,
web ::post ( ) . to ( views ::process_delete_link_json ) ,
)
2021-05-14 18:28:33 +02:00
. route ( " /list_users/ " , web ::post ( ) . to ( views ::index_users_json ) )
. route (
" /create_user/ " ,
web ::post ( ) . to ( views ::process_create_user_json ) ,
2021-05-15 19:20:18 +02:00
)
. route (
" /update_user/ " ,
web ::post ( ) . to ( views ::process_update_user_json ) ,
2021-05-20 11:40:44 +02:00
)
. route (
" /get_logged_user/ " ,
web ::post ( ) . to ( views ::get_logged_user_json ) ,
2021-05-26 20:10:01 +02:00
)
2021-05-28 17:22:27 +02:00
. route ( " /login_user/ " , web ::post ( ) . to ( views ::process_login_json ) ) ,
2021-06-12 16:42:29 +02:00
) ,
2021-04-18 09:41:17 +02:00
)
2021-06-12 16:42:29 +02:00
// Serve the Wasm App for the admin interface.
2021-05-04 11:29:36 +02:00
. service (
web ::scope ( " /app " )
. service ( Files ::new ( " /pkg " , " ./app/pkg " ) )
. default_service ( web ::get ( ) . to ( views ::wasm_app ) ) ,
)
2021-04-18 09:41:17 +02:00
// redirect to the url hidden behind the code
. route ( " /{redirect_id} " , web ::get ( ) . to ( views ::redirect ) )
} )
. bind ( host_port )
. map_err ( | e | {
error! ( " Failed to bind to port! " ) ;
e
} ) ?
. run ( ) ;
Ok ( server )
}