Use light-openid to reduce code base size (#4)
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			Refactor code to publicly share "redundant" code Reviewed-on: #4
This commit is contained in:
		
							
								
								
									
										27
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										27
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -1041,6 +1041,23 @@ version = "0.2.6"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
 | 
					checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "light-openid"
 | 
				
			||||||
 | 
					version = "1.0.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "608aa1b7148a6eeab631c6267deca33407ff851ab50eea115e52c13a9bb184ee"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aes-gcm",
 | 
				
			||||||
 | 
					 "base64",
 | 
				
			||||||
 | 
					 "bincode",
 | 
				
			||||||
 | 
					 "log",
 | 
				
			||||||
 | 
					 "rand",
 | 
				
			||||||
 | 
					 "reqwest",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					 "serde_json",
 | 
				
			||||||
 | 
					 "urlencoding",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "linux-raw-sys"
 | 
					name = "linux-raw-sys"
 | 
				
			||||||
version = "0.3.4"
 | 
					version = "0.3.4"
 | 
				
			||||||
@@ -1185,20 +1202,16 @@ name = "oidc-test-client"
 | 
				
			|||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "actix-web",
 | 
					 "actix-web",
 | 
				
			||||||
 "aes-gcm",
 | 
					 | 
				
			||||||
 "askama",
 | 
					 "askama",
 | 
				
			||||||
 "base64",
 | 
					 | 
				
			||||||
 "bincode",
 | 
					 | 
				
			||||||
 "clap",
 | 
					 "clap",
 | 
				
			||||||
 "env_logger",
 | 
					 "env_logger",
 | 
				
			||||||
 "futures-util",
 | 
					 "futures-util",
 | 
				
			||||||
 "lazy_static",
 | 
					 "lazy_static",
 | 
				
			||||||
 | 
					 "light-openid",
 | 
				
			||||||
 "log",
 | 
					 "log",
 | 
				
			||||||
 "rand",
 | 
					 | 
				
			||||||
 "reqwest",
 | 
					 "reqwest",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
 "serde_json",
 | 
					 "serde_json",
 | 
				
			||||||
 "urlencoding",
 | 
					 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@@ -1413,9 +1426,9 @@ checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "reqwest"
 | 
					name = "reqwest"
 | 
				
			||||||
version = "0.11.16"
 | 
					version = "0.11.17"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
 | 
					checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "base64",
 | 
					 "base64",
 | 
				
			||||||
 "bytes",
 | 
					 "bytes",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ edition = "2021"
 | 
				
			|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
 | 
					light-openid = { version = "1.0.1", features=["crypto-wrapper"] }
 | 
				
			||||||
log = "0.4.17"
 | 
					log = "0.4.17"
 | 
				
			||||||
env_logger = "0.10.0"
 | 
					env_logger = "0.10.0"
 | 
				
			||||||
clap = { version = "4.2.4", features = ["derive", "env"] }
 | 
					clap = { version = "4.2.4", features = ["derive", "env"] }
 | 
				
			||||||
@@ -15,9 +16,4 @@ askama = "0.12.0"
 | 
				
			|||||||
serde = { version = "1.0.160", features = ["derive"] }
 | 
					serde = { version = "1.0.160", features = ["derive"] }
 | 
				
			||||||
serde_json = "1.0.96"
 | 
					serde_json = "1.0.96"
 | 
				
			||||||
reqwest = { version = "0.11.16", features = ["json"] }
 | 
					reqwest = { version = "0.11.16", features = ["json"] }
 | 
				
			||||||
urlencoding = "2.1.2"
 | 
					 | 
				
			||||||
futures-util = "0.3.28"
 | 
					futures-util = "0.3.28"
 | 
				
			||||||
aes-gcm = "0.10.1"
 | 
					 | 
				
			||||||
base64 = "0.21.0"
 | 
					 | 
				
			||||||
rand = "0.8.5"
 | 
					 | 
				
			||||||
bincode = {version="2.0.0-rc.3",features=["serde"]}
 | 
					 | 
				
			||||||
@@ -34,6 +34,10 @@ main {
 | 
				
			|||||||
 * Header
 | 
					 * Header
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.title {
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.nav-masthead .nav-link {
 | 
					.nav-masthead .nav-link {
 | 
				
			||||||
  color: rgba(255, 255, 255, .5);
 | 
					  color: rgba(255, 255, 255, .5);
 | 
				
			||||||
  border-bottom: .25rem solid transparent;
 | 
					  border-bottom: .25rem solid transparent;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,97 +0,0 @@
 | 
				
			|||||||
use std::io::ErrorKind;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::Res;
 | 
					 | 
				
			||||||
use aes_gcm::aead::{Aead, OsRng};
 | 
					 | 
				
			||||||
use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
 | 
					 | 
				
			||||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
 | 
					 | 
				
			||||||
use base64::Engine as _;
 | 
					 | 
				
			||||||
use bincode::{Decode, Encode};
 | 
					 | 
				
			||||||
use rand::Rng;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const NONCE_LEN: usize = 12;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct CryptoWrapper {
 | 
					 | 
				
			||||||
    key: Key<Aes256Gcm>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl CryptoWrapper {
 | 
					 | 
				
			||||||
    /// Generate a new memory wrapper
 | 
					 | 
				
			||||||
    pub fn new_random() -> Self {
 | 
					 | 
				
			||||||
        Self {
 | 
					 | 
				
			||||||
            key: Aes256Gcm::generate_key(&mut OsRng),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Encrypt some data
 | 
					 | 
				
			||||||
    pub fn encrypt<T: Encode + Decode>(&self, data: &T) -> Res<String> {
 | 
					 | 
				
			||||||
        let aes_key = Aes256Gcm::new(&self.key);
 | 
					 | 
				
			||||||
        let nonce_bytes = rand::thread_rng().gen::<[u8; NONCE_LEN]>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let serialized_data = bincode::encode_to_vec(data, bincode::config::standard())?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut enc = aes_key
 | 
					 | 
				
			||||||
            .encrypt(Nonce::from_slice(&nonce_bytes), serialized_data.as_slice())
 | 
					 | 
				
			||||||
            .unwrap();
 | 
					 | 
				
			||||||
        enc.extend_from_slice(&nonce_bytes);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(BASE64_STANDARD.encode(enc))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Decrypt some data previously encrypted using the [`CryptoWrapper::encrypt`] method
 | 
					 | 
				
			||||||
    pub fn decrypt<T: Decode>(&self, input: &str) -> Res<T> {
 | 
					 | 
				
			||||||
        let bytes = BASE64_STANDARD.decode(input)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if bytes.len() < NONCE_LEN {
 | 
					 | 
				
			||||||
            return Err(Box::new(std::io::Error::new(
 | 
					 | 
				
			||||||
                ErrorKind::Other,
 | 
					 | 
				
			||||||
                "Input string is smaller than nonce!",
 | 
					 | 
				
			||||||
            )));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let (enc, nonce) = bytes.split_at(bytes.len() - NONCE_LEN);
 | 
					 | 
				
			||||||
        assert_eq!(nonce.len(), NONCE_LEN);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let aes_key = Aes256Gcm::new(&self.key);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let dec = match aes_key.decrypt(Nonce::from_slice(nonce), enc) {
 | 
					 | 
				
			||||||
            Ok(d) => d,
 | 
					 | 
				
			||||||
            Err(e) => {
 | 
					 | 
				
			||||||
                log::error!("Failed to decrypt wrapped data! {:#?}", e);
 | 
					 | 
				
			||||||
                return Err(Box::new(std::io::Error::new(
 | 
					 | 
				
			||||||
                    ErrorKind::Other,
 | 
					 | 
				
			||||||
                    "Failed to decrypt wrapped data!",
 | 
					 | 
				
			||||||
                )));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(bincode::decode_from_slice(&dec, bincode::config::standard())?.0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[cfg(test)]
 | 
					 | 
				
			||||||
mod test {
 | 
					 | 
				
			||||||
    use crate::crypto_wrapper::CryptoWrapper;
 | 
					 | 
				
			||||||
    use bincode::{Decode, Encode};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[derive(Encode, Decode, Eq, PartialEq, Debug)]
 | 
					 | 
				
			||||||
    struct Message(String);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[test]
 | 
					 | 
				
			||||||
    fn encrypt_and_decrypt() {
 | 
					 | 
				
			||||||
        let wrapper = CryptoWrapper::new_random();
 | 
					 | 
				
			||||||
        let msg = Message("Pierre was here".to_string());
 | 
					 | 
				
			||||||
        let enc = wrapper.encrypt(&msg).unwrap();
 | 
					 | 
				
			||||||
        let dec: Message = wrapper.decrypt(&enc).unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        assert_eq!(dec, msg)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[test]
 | 
					 | 
				
			||||||
    fn encrypt_and_decrypt_invalid() {
 | 
					 | 
				
			||||||
        let wrapper_1 = CryptoWrapper::new_random();
 | 
					 | 
				
			||||||
        let wrapper_2 = CryptoWrapper::new_random();
 | 
					 | 
				
			||||||
        let msg = Message("Pierre was here".to_string());
 | 
					 | 
				
			||||||
        let enc = wrapper_1.encrypt(&msg).unwrap();
 | 
					 | 
				
			||||||
        wrapper_2.decrypt::<Message>(&enc).unwrap_err();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -3,8 +3,4 @@ use std::error::Error;
 | 
				
			|||||||
pub type Res<A = ()> = Result<A, Box<dyn Error>>;
 | 
					pub type Res<A = ()> = Result<A, Box<dyn Error>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod app_config;
 | 
					pub mod app_config;
 | 
				
			||||||
pub mod crypto_wrapper;
 | 
					 | 
				
			||||||
pub mod openid_primitives;
 | 
					 | 
				
			||||||
pub mod remote_ip;
 | 
					pub mod remote_ip;
 | 
				
			||||||
pub mod state_manager;
 | 
					 | 
				
			||||||
pub mod time_utils;
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										27
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,11 +1,11 @@
 | 
				
			|||||||
use actix_web::middleware::Logger;
 | 
					use actix_web::middleware::Logger;
 | 
				
			||||||
use actix_web::{get, web, App, HttpResponse, HttpServer};
 | 
					use actix_web::{get, web, App, HttpResponse, HttpServer};
 | 
				
			||||||
use askama::Template;
 | 
					use askama::Template;
 | 
				
			||||||
 | 
					use light_openid::basic_state_manager::BasicStateManager;
 | 
				
			||||||
 | 
					use light_openid::primitives::OpenIDConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use oidc_test_client::app_config::AppConfig;
 | 
					use oidc_test_client::app_config::AppConfig;
 | 
				
			||||||
use oidc_test_client::openid_primitives::OpenIDConfig;
 | 
					 | 
				
			||||||
use oidc_test_client::remote_ip::RemoteIP;
 | 
					use oidc_test_client::remote_ip::RemoteIP;
 | 
				
			||||||
use oidc_test_client::state_manager::StateManager;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[get("/assets/bootstrap.min.css")]
 | 
					#[get("/assets/bootstrap.min.css")]
 | 
				
			||||||
async fn bootstrap() -> HttpResponse {
 | 
					async fn bootstrap() -> HttpResponse {
 | 
				
			||||||
@@ -60,8 +60,8 @@ async fn home() -> HttpResponse {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[get("/start")]
 | 
					#[get("/start")]
 | 
				
			||||||
async fn start(remote_ip: RemoteIP) -> HttpResponse {
 | 
					async fn start(remote_ip: RemoteIP, state_manager: web::Data<BasicStateManager>) -> HttpResponse {
 | 
				
			||||||
    let config = match OpenIDConfig::load_from(&AppConfig::get().configuration_url).await {
 | 
					    let config = match OpenIDConfig::load_from_url(&AppConfig::get().configuration_url).await {
 | 
				
			||||||
        Ok(c) => c,
 | 
					        Ok(c) => c,
 | 
				
			||||||
        Err(e) => {
 | 
					        Err(e) => {
 | 
				
			||||||
            log::error!("Failed to load OpenID configuration! {e}");
 | 
					            log::error!("Failed to load OpenID configuration! {e}");
 | 
				
			||||||
@@ -69,7 +69,7 @@ async fn start(remote_ip: RemoteIP) -> HttpResponse {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let state = match StateManager::gen_state(&remote_ip) {
 | 
					    let state = match state_manager.gen_state(remote_ip.0) {
 | 
				
			||||||
        Ok(s) => s,
 | 
					        Ok(s) => s,
 | 
				
			||||||
        Err(e) => {
 | 
					        Err(e) => {
 | 
				
			||||||
            log::error!("Failed to generate state! {:?}", e);
 | 
					            log::error!("Failed to generate state! {:?}", e);
 | 
				
			||||||
@@ -77,7 +77,7 @@ async fn start(remote_ip: RemoteIP) -> HttpResponse {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let authorization_url = config.authorization_url(
 | 
					    let authorization_url = config.gen_authorization_url(
 | 
				
			||||||
        &AppConfig::get().client_id,
 | 
					        &AppConfig::get().client_id,
 | 
				
			||||||
        &state,
 | 
					        &state,
 | 
				
			||||||
        &AppConfig::get().redirect_url(),
 | 
					        &AppConfig::get().redirect_url(),
 | 
				
			||||||
@@ -95,15 +95,19 @@ struct RedirectQuery {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[get("/redirect")]
 | 
					#[get("/redirect")]
 | 
				
			||||||
async fn redirect(remote_ip: RemoteIP, query: web::Query<RedirectQuery>) -> HttpResponse {
 | 
					async fn redirect(
 | 
				
			||||||
 | 
					    remote_ip: RemoteIP,
 | 
				
			||||||
 | 
					    query: web::Query<RedirectQuery>,
 | 
				
			||||||
 | 
					    state_manager: web::Data<BasicStateManager>,
 | 
				
			||||||
 | 
					) -> HttpResponse {
 | 
				
			||||||
    // First, validate state
 | 
					    // First, validate state
 | 
				
			||||||
    if let Err(e) = StateManager::validate_state(&remote_ip, &query.state) {
 | 
					    if let Err(e) = state_manager.validate_state(remote_ip.0, &query.state) {
 | 
				
			||||||
        log::error!("Failed to validate state {}: {:?}", query.state, e);
 | 
					        log::error!("Failed to validate state {}: {:?}", query.state, e);
 | 
				
			||||||
        return ErrorTemplate::build("State could not be validated!");
 | 
					        return ErrorTemplate::build("State could not be validated!");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Then, load OpenID configuration
 | 
					    // Then, load OpenID configuration
 | 
				
			||||||
    let config = match OpenIDConfig::load_from(&AppConfig::get().configuration_url).await {
 | 
					    let config = match OpenIDConfig::load_from_url(&AppConfig::get().configuration_url).await {
 | 
				
			||||||
        Ok(c) => c,
 | 
					        Ok(c) => c,
 | 
				
			||||||
        Err(e) => {
 | 
					        Err(e) => {
 | 
				
			||||||
            log::error!("Failed to load OpenID configuration! {e}");
 | 
					            log::error!("Failed to load OpenID configuration! {e}");
 | 
				
			||||||
@@ -158,13 +162,14 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
 | 
					    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    log::info!("Init state manager");
 | 
					    log::info!("Init state manager");
 | 
				
			||||||
    StateManager::init();
 | 
					    let state_manager = web::Data::new(BasicStateManager::new());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    log::info!("Will listen on {}", AppConfig::get().listen_addr);
 | 
					    log::info!("Will listen on {}", AppConfig::get().listen_addr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    HttpServer::new(|| {
 | 
					    HttpServer::new(move || {
 | 
				
			||||||
        App::new()
 | 
					        App::new()
 | 
				
			||||||
            .wrap(Logger::default())
 | 
					            .wrap(Logger::default())
 | 
				
			||||||
 | 
					            .app_data(state_manager.clone())
 | 
				
			||||||
            .service(bootstrap)
 | 
					            .service(bootstrap)
 | 
				
			||||||
            .service(cover)
 | 
					            .service(cover)
 | 
				
			||||||
            .service(home)
 | 
					            .service(home)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,97 +0,0 @@
 | 
				
			|||||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
 | 
					 | 
				
			||||||
use base64::Engine;
 | 
					 | 
				
			||||||
use std::collections::HashMap;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::Res;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, serde::Deserialize)]
 | 
					 | 
				
			||||||
pub struct OpenIDConfig {
 | 
					 | 
				
			||||||
    pub authorization_endpoint: String,
 | 
					 | 
				
			||||||
    pub token_endpoint: String,
 | 
					 | 
				
			||||||
    pub userinfo_endpoint: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
					 | 
				
			||||||
pub struct TokenResponse {
 | 
					 | 
				
			||||||
    pub access_token: String,
 | 
					 | 
				
			||||||
    pub token_type: String,
 | 
					 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					 | 
				
			||||||
    pub refresh_token: Option<String>,
 | 
					 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					 | 
				
			||||||
    pub expires_in: Option<u64>,
 | 
					 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					 | 
				
			||||||
    pub id_token: Option<String>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
					 | 
				
			||||||
pub struct UserInfo {
 | 
					 | 
				
			||||||
    pub sub: String,
 | 
					 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					 | 
				
			||||||
    pub name: Option<String>,
 | 
					 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					 | 
				
			||||||
    pub given_name: Option<String>,
 | 
					 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					 | 
				
			||||||
    pub family_name: Option<String>,
 | 
					 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					 | 
				
			||||||
    pub preferred_username: Option<String>,
 | 
					 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					 | 
				
			||||||
    pub email: Option<String>,
 | 
					 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					 | 
				
			||||||
    pub email_verified: Option<bool>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl OpenIDConfig {
 | 
					 | 
				
			||||||
    /// Load OpenID configuration from a given URL
 | 
					 | 
				
			||||||
    pub async fn load_from(url: &str) -> Res<Self> {
 | 
					 | 
				
			||||||
        Ok(reqwest::get(url).await?.json().await?)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Get the authorization URL where a user should be redirect
 | 
					 | 
				
			||||||
    pub fn authorization_url(&self, client_id: &str, state: &str, redirect_uri: &str) -> String {
 | 
					 | 
				
			||||||
        let client_id = urlencoding::encode(client_id);
 | 
					 | 
				
			||||||
        let state = urlencoding::encode(state);
 | 
					 | 
				
			||||||
        let redirect_uri = urlencoding::encode(redirect_uri);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        format!("{}?response_type=code&scope=openid%20profile%20email&client_id={client_id}&state={state}&redirect_uri={redirect_uri}", self.authorization_endpoint)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Query the token endpoint
 | 
					 | 
				
			||||||
    pub async fn request_token(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        client_id: &str,
 | 
					 | 
				
			||||||
        client_secret: &str,
 | 
					 | 
				
			||||||
        code: &str,
 | 
					 | 
				
			||||||
        redirect_uri: &str,
 | 
					 | 
				
			||||||
    ) -> Res<(TokenResponse, String)> {
 | 
					 | 
				
			||||||
        let authorization = BASE64_STANDARD.encode(format!("{}:{}", client_id, client_secret));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut params = HashMap::new();
 | 
					 | 
				
			||||||
        params.insert("grant_type", "authorization_code");
 | 
					 | 
				
			||||||
        params.insert("code", code);
 | 
					 | 
				
			||||||
        params.insert("redirect_uri", redirect_uri);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let response = reqwest::Client::new()
 | 
					 | 
				
			||||||
            .post(&self.token_endpoint)
 | 
					 | 
				
			||||||
            .header("Authorization", format!("Basic {authorization}"))
 | 
					 | 
				
			||||||
            .form(¶ms)
 | 
					 | 
				
			||||||
            .send()
 | 
					 | 
				
			||||||
            .await?
 | 
					 | 
				
			||||||
            .text()
 | 
					 | 
				
			||||||
            .await?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok((serde_json::from_str(&response)?, response))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Query the UserInfo endpoint
 | 
					 | 
				
			||||||
    pub async fn request_user_info(&self, token: &TokenResponse) -> Res<(UserInfo, String)> {
 | 
					 | 
				
			||||||
        let response = reqwest::Client::new()
 | 
					 | 
				
			||||||
            .get(&self.userinfo_endpoint)
 | 
					 | 
				
			||||||
            .header("Authorization", format!("Bearer {}", token.access_token))
 | 
					 | 
				
			||||||
            .send()
 | 
					 | 
				
			||||||
            .await?
 | 
					 | 
				
			||||||
            .text()
 | 
					 | 
				
			||||||
            .await?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok((serde_json::from_str(&response)?, response))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,72 +0,0 @@
 | 
				
			|||||||
use std::error::Error;
 | 
					 | 
				
			||||||
use std::fmt;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::crypto_wrapper::CryptoWrapper;
 | 
					 | 
				
			||||||
use crate::remote_ip::RemoteIP;
 | 
					 | 
				
			||||||
use crate::time_utils::time;
 | 
					 | 
				
			||||||
use crate::Res;
 | 
					 | 
				
			||||||
use bincode::{Decode, Encode};
 | 
					 | 
				
			||||||
use std::net::IpAddr;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct StateManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static mut WRAPPER: Option<CryptoWrapper> = None;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Encode, Decode, Debug)]
 | 
					 | 
				
			||||||
struct State {
 | 
					 | 
				
			||||||
    ip: IpAddr,
 | 
					 | 
				
			||||||
    expire: u64,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl State {
 | 
					 | 
				
			||||||
    pub fn new(ip: IpAddr) -> Self {
 | 
					 | 
				
			||||||
        Self {
 | 
					 | 
				
			||||||
            ip,
 | 
					 | 
				
			||||||
            expire: time() + 15 * 60,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Copy, Clone)]
 | 
					 | 
				
			||||||
enum StateError {
 | 
					 | 
				
			||||||
    InvalidIp,
 | 
					 | 
				
			||||||
    Expired,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Error for StateError {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl fmt::Display for StateError {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 | 
					 | 
				
			||||||
        write!(f, "StateManager error {:?}", self)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl StateManager {
 | 
					 | 
				
			||||||
    pub fn init() {
 | 
					 | 
				
			||||||
        unsafe {
 | 
					 | 
				
			||||||
            WRAPPER = Some(CryptoWrapper::new_random());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Generate a new state
 | 
					 | 
				
			||||||
    pub fn gen_state(ip: &RemoteIP) -> Res<String> {
 | 
					 | 
				
			||||||
        let state = State::new(ip.0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        unsafe { WRAPPER.as_ref().unwrap() }.encrypt(&state)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Validate generated state
 | 
					 | 
				
			||||||
    pub fn validate_state(ip: &RemoteIP, state: &str) -> Res {
 | 
					 | 
				
			||||||
        let state: State = unsafe { WRAPPER.as_ref().unwrap() }.decrypt(state)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if state.ip != ip.0 {
 | 
					 | 
				
			||||||
            return Err(Box::new(StateError::InvalidIp));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if state.expire < time() {
 | 
					 | 
				
			||||||
            return Err(Box::new(StateError::Expired));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,9 +0,0 @@
 | 
				
			|||||||
use std::time::{SystemTime, UNIX_EPOCH};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Get current time since epoch
 | 
					 | 
				
			||||||
pub fn time() -> u64 {
 | 
					 | 
				
			||||||
    SystemTime::now()
 | 
					 | 
				
			||||||
        .duration_since(UNIX_EPOCH)
 | 
					 | 
				
			||||||
        .unwrap()
 | 
					 | 
				
			||||||
        .as_secs()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -20,7 +20,7 @@
 | 
				
			|||||||
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
 | 
					<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
 | 
				
			||||||
  <header class="mb-auto">
 | 
					  <header class="mb-auto">
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
      <h3 class="float-md-start mb-0">OIDC test client</h3>
 | 
					      <a href="/"><h3 class="float-md-start mb-0 title">OIDC test client</h3></a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </header>
 | 
					  </header>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user