Add IP location service
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		@@ -23,7 +23,7 @@ use crate::data::user::User;
 | 
			
		||||
use crate::utils::string_utils::rand_str;
 | 
			
		||||
use crate::utils::time::time;
 | 
			
		||||
 | 
			
		||||
pub async fn get_configuration(req: HttpRequest, app_conf: web::Data<AppConfig>) -> impl Responder {
 | 
			
		||||
pub async fn get_configuration(req: HttpRequest) -> impl Responder {
 | 
			
		||||
    let is_secure_request = req
 | 
			
		||||
        .headers()
 | 
			
		||||
        .get("HTTP_X_FORWARDED_PROTO")
 | 
			
		||||
@@ -45,8 +45,8 @@ pub async fn get_configuration(req: HttpRequest, app_conf: web::Data<AppConfig>)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    HttpResponse::Ok().json(OpenIDConfig {
 | 
			
		||||
        issuer: app_conf.website_origin.clone(),
 | 
			
		||||
        authorization_endpoint: app_conf.full_url(AUTHORIZE_URI),
 | 
			
		||||
        issuer: AppConfig::get().website_origin.clone(),
 | 
			
		||||
        authorization_endpoint: AppConfig::get().full_url(AUTHORIZE_URI),
 | 
			
		||||
        token_endpoint: curr_origin.clone() + TOKEN_URI,
 | 
			
		||||
        userinfo_endpoint: curr_origin.clone() + USERINFO_URI,
 | 
			
		||||
        jwks_uri: curr_origin + CERT_URI,
 | 
			
		||||
@@ -263,7 +263,6 @@ pub async fn token(
 | 
			
		||||
    req: HttpRequest,
 | 
			
		||||
    query: web::Form<TokenQuery>,
 | 
			
		||||
    clients: web::Data<ClientManager>,
 | 
			
		||||
    app_config: web::Data<AppConfig>,
 | 
			
		||||
    sessions: web::Data<Addr<OpenIDSessionsActor>>,
 | 
			
		||||
    users: web::Data<Addr<UsersActor>>,
 | 
			
		||||
    jwt_signer: web::Data<JWTSigner>,
 | 
			
		||||
@@ -416,7 +415,7 @@ pub async fn token(
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            session.regenerate_access_and_refresh_tokens(&app_config, &jwt_signer)?;
 | 
			
		||||
            session.regenerate_access_and_refresh_tokens(AppConfig::get(), &jwt_signer)?;
 | 
			
		||||
 | 
			
		||||
            sessions
 | 
			
		||||
                .send(openid_sessions_actor::UpdateSession(session.clone()))
 | 
			
		||||
@@ -435,7 +434,7 @@ pub async fn token(
 | 
			
		||||
 | 
			
		||||
            // Generate id token
 | 
			
		||||
            let id_token = IdToken {
 | 
			
		||||
                issuer: app_config.website_origin.to_string(),
 | 
			
		||||
                issuer: AppConfig::get().website_origin.to_string(),
 | 
			
		||||
                subject_identifier: session.user.0,
 | 
			
		||||
                audience: session.client.0.to_string(),
 | 
			
		||||
                expiration_time: session.access_token_expire_at,
 | 
			
		||||
@@ -488,7 +487,7 @@ pub async fn token(
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            session.regenerate_access_and_refresh_tokens(&app_config, &jwt_signer)?;
 | 
			
		||||
            session.regenerate_access_and_refresh_tokens(AppConfig::get(), &jwt_signer)?;
 | 
			
		||||
 | 
			
		||||
            sessions
 | 
			
		||||
                .send(openid_sessions_actor::UpdateSession(session.clone()))
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ use crate::actors::bruteforce_actor::BruteForceActor;
 | 
			
		||||
use crate::actors::users_actor::UsersActor;
 | 
			
		||||
use crate::actors::{bruteforce_actor, users_actor};
 | 
			
		||||
use crate::constants::{APP_NAME, MAX_FAILED_LOGIN_ATTEMPTS, MIN_PASS_LEN};
 | 
			
		||||
use crate::data::app_config::AppConfig;
 | 
			
		||||
use crate::data::current_user::CurrentUser;
 | 
			
		||||
use crate::data::remote_ip::RemoteIP;
 | 
			
		||||
use crate::data::user::User;
 | 
			
		||||
@@ -18,6 +19,7 @@ pub(crate) struct BaseSettingsPage {
 | 
			
		||||
    pub is_admin: bool,
 | 
			
		||||
    pub user_name: String,
 | 
			
		||||
    pub version: &'static str,
 | 
			
		||||
    pub ip_location_api: Option<&'static str>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl BaseSettingsPage {
 | 
			
		||||
@@ -35,6 +37,7 @@ impl BaseSettingsPage {
 | 
			
		||||
            is_admin: user.admin,
 | 
			
		||||
            user_name: user.username.to_string(),
 | 
			
		||||
            version: env!("CARGO_PKG_VERSION"),
 | 
			
		||||
            ip_location_api: AppConfig::get().ip_location_service.as_deref(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
use std::ops::Deref;
 | 
			
		||||
 | 
			
		||||
use actix_web::{web, HttpResponse, Responder};
 | 
			
		||||
use actix_web::{HttpResponse, Responder};
 | 
			
		||||
use askama::Template;
 | 
			
		||||
use qrcode_generator::QrCodeEcc;
 | 
			
		||||
 | 
			
		||||
@@ -48,14 +48,14 @@ pub async fn two_factors_route(user: CurrentUser) -> impl Responder {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Configure a new TOTP authentication factor
 | 
			
		||||
pub async fn add_totp_factor_route(
 | 
			
		||||
    user: CurrentUser,
 | 
			
		||||
    app_conf: web::Data<AppConfig>,
 | 
			
		||||
) -> impl Responder {
 | 
			
		||||
pub async fn add_totp_factor_route(user: CurrentUser) -> impl Responder {
 | 
			
		||||
    let key = TotpKey::new_random();
 | 
			
		||||
 | 
			
		||||
    let qr_code =
 | 
			
		||||
        qrcode_generator::to_png_to_vec(key.url_for_user(&user, &app_conf), QrCodeEcc::Low, 1024);
 | 
			
		||||
    let qr_code = qrcode_generator::to_png_to_vec(
 | 
			
		||||
        key.url_for_user(&user, AppConfig::get()),
 | 
			
		||||
        QrCodeEcc::Low,
 | 
			
		||||
        1024,
 | 
			
		||||
    );
 | 
			
		||||
    let qr_code = match qr_code {
 | 
			
		||||
        Ok(q) => q,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
@@ -68,7 +68,7 @@ pub async fn add_totp_factor_route(
 | 
			
		||||
        AddTotpPage {
 | 
			
		||||
            _p: BaseSettingsPage::get("New authenticator app", &user, None, None),
 | 
			
		||||
            qr_code: base64::encode(qr_code),
 | 
			
		||||
            account_name: key.account_name(&user, &app_conf),
 | 
			
		||||
            account_name: key.account_name(&user, AppConfig::get()),
 | 
			
		||||
            secret_key: key.get_secret(),
 | 
			
		||||
        }
 | 
			
		||||
        .render()
 | 
			
		||||
 
 | 
			
		||||
@@ -27,9 +27,35 @@ pub struct AppConfig {
 | 
			
		||||
    /// Proxy IP, might end with a star "*"
 | 
			
		||||
    #[clap(short, long, env)]
 | 
			
		||||
    pub proxy_ip: Option<String>,
 | 
			
		||||
 | 
			
		||||
    /// IP location service API
 | 
			
		||||
    ///
 | 
			
		||||
    /// Up instance of IP location service : https://gitlab.com/pierre42100/iplocationserver
 | 
			
		||||
    ///
 | 
			
		||||
    /// Example: "https://api.geoip.rs"
 | 
			
		||||
    #[arg(long, short, env)]
 | 
			
		||||
    pub ip_location_service: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
lazy_static::lazy_static! {
 | 
			
		||||
    static ref ARGS: AppConfig = {
 | 
			
		||||
        let mut config = AppConfig::parse();
 | 
			
		||||
 | 
			
		||||
        // In debug mode only, use dummy token
 | 
			
		||||
        if cfg!(debug_assertions) && config.token_key.is_empty() {
 | 
			
		||||
            config.token_key = String::from_utf8_lossy(&[32; 64]).to_string();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        config
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AppConfig {
 | 
			
		||||
    /// Get parsed command line arguments
 | 
			
		||||
    pub fn get() -> &'static AppConfig {
 | 
			
		||||
        &ARGS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn secure_cookie(&self) -> bool {
 | 
			
		||||
        self.website_origin.starts_with("https:")
 | 
			
		||||
    }
 | 
			
		||||
@@ -58,3 +84,14 @@ impl AppConfig {
 | 
			
		||||
        self.website_origin.split('/').nth(2).unwrap_or(APP_NAME)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use crate::data::app_config::AppConfig;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn verify_cli() {
 | 
			
		||||
        use clap::CommandFactory;
 | 
			
		||||
        AppConfig::command().debug_assert()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
use std::net::IpAddr;
 | 
			
		||||
 | 
			
		||||
use actix_web::dev::Payload;
 | 
			
		||||
use actix_web::{web, Error, FromRequest, HttpRequest};
 | 
			
		||||
use actix_web::{Error, FromRequest, HttpRequest};
 | 
			
		||||
use futures_util::future::{ready, Ready};
 | 
			
		||||
 | 
			
		||||
use crate::data::app_config::AppConfig;
 | 
			
		||||
@@ -22,7 +22,9 @@ impl FromRequest for RemoteIP {
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
 | 
			
		||||
        let config: &web::Data<AppConfig> = req.app_data().expect("AppData undefined!");
 | 
			
		||||
        ready(Ok(RemoteIP(get_remote_ip(req, config.proxy_ip.as_deref()))))
 | 
			
		||||
        ready(Ok(RemoteIP(get_remote_ip(
 | 
			
		||||
            req,
 | 
			
		||||
            AppConfig::get().proxy_ip.as_deref(),
 | 
			
		||||
        ))))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -9,7 +9,6 @@ use actix_session::SessionMiddleware;
 | 
			
		||||
use actix_web::cookie::{Key, SameSite};
 | 
			
		||||
use actix_web::middleware::Logger;
 | 
			
		||||
use actix_web::{get, middleware, web, App, HttpResponse, HttpServer};
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
 | 
			
		||||
use basic_oidc::actors::bruteforce_actor::BruteForceActor;
 | 
			
		||||
use basic_oidc::actors::openid_sessions_actor::OpenIDSessionsActor;
 | 
			
		||||
@@ -34,12 +33,7 @@ async fn health() -> &'static str {
 | 
			
		||||
async fn main() -> std::io::Result<()> {
 | 
			
		||||
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
 | 
			
		||||
 | 
			
		||||
    let mut config: AppConfig = AppConfig::parse();
 | 
			
		||||
 | 
			
		||||
    // In debug mode only, use dummy token
 | 
			
		||||
    if cfg!(debug_assertions) && config.token_key.is_empty() {
 | 
			
		||||
        config.token_key = String::from_utf8_lossy(&[32; 64]).to_string();
 | 
			
		||||
    }
 | 
			
		||||
    let config = AppConfig::get();
 | 
			
		||||
 | 
			
		||||
    if !config.storage_path().exists() {
 | 
			
		||||
        log::error!(
 | 
			
		||||
@@ -73,7 +67,7 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
    let bruteforce_actor = BruteForceActor::default().start();
 | 
			
		||||
    let openid_sessions_actor = OpenIDSessionsActor::default().start();
 | 
			
		||||
    let jwt_signer = JWTSigner::gen_from_memory().expect("Failed to generate JWKS key");
 | 
			
		||||
    let webauthn_manager = Arc::new(WebAuthManager::init(&config));
 | 
			
		||||
    let webauthn_manager = Arc::new(WebAuthManager::init(config));
 | 
			
		||||
 | 
			
		||||
    log::info!("Server will listen on {}", config.listen_address);
 | 
			
		||||
    let listen_address = config.listen_address.to_string();
 | 
			
		||||
@@ -102,7 +96,6 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
            .app_data(web::Data::new(users_actor.clone()))
 | 
			
		||||
            .app_data(web::Data::new(bruteforce_actor.clone()))
 | 
			
		||||
            .app_data(web::Data::new(openid_sessions_actor.clone()))
 | 
			
		||||
            .app_data(web::Data::new(config.clone()))
 | 
			
		||||
            .app_data(web::Data::new(clients))
 | 
			
		||||
            .app_data(web::Data::new(jwt_signer.clone()))
 | 
			
		||||
            .app_data(web::Data::new(webauthn_manager.clone()))
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ use actix_web::body::EitherBody;
 | 
			
		||||
use actix_web::http::{header, Method};
 | 
			
		||||
use actix_web::{
 | 
			
		||||
    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
 | 
			
		||||
    web, Error, HttpResponse,
 | 
			
		||||
    Error, HttpResponse,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::constants::{
 | 
			
		||||
@@ -87,14 +87,16 @@ where
 | 
			
		||||
 | 
			
		||||
        // Forward request
 | 
			
		||||
        Box::pin(async move {
 | 
			
		||||
            let config: &web::Data<AppConfig> = req.app_data().expect("AppData undefined!");
 | 
			
		||||
 | 
			
		||||
            // Check if POST request comes from another website (block invalid origins)
 | 
			
		||||
            let origin = req.headers().get(header::ORIGIN);
 | 
			
		||||
            if req.method() == Method::POST && req.path() != TOKEN_URI && req.path() != USERINFO_URI
 | 
			
		||||
            {
 | 
			
		||||
                if let Some(o) = origin {
 | 
			
		||||
                    if !o.to_str().unwrap_or("bad").eq(&config.website_origin) {
 | 
			
		||||
                    if !o
 | 
			
		||||
                        .to_str()
 | 
			
		||||
                        .unwrap_or("bad")
 | 
			
		||||
                        .eq(&AppConfig::get().website_origin)
 | 
			
		||||
                    {
 | 
			
		||||
                        log::warn!(
 | 
			
		||||
                            "Blocked POST request from invalid origin! Origin given {:?}",
 | 
			
		||||
                            o
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user