Improve error handling
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
Cargo.lock
generated
23
Cargo.lock
generated
@@ -632,7 +632,7 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||
|
||||
[[package]]
|
||||
name = "light-openid"
|
||||
version = "1.0.4"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"base64",
|
||||
@@ -642,6 +642,7 @@ dependencies = [
|
||||
"rkyv",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
@@ -1273,6 +1274,26 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "light-openid"
|
||||
version = "1.0.4"
|
||||
version = "1.1.0"
|
||||
edition = "2021"
|
||||
repository = "https://gitea.communiquons.org/pierre/light-openid"
|
||||
authors = ["Pierre HUBERT <pierre.git@communiquons.org>"]
|
||||
@@ -17,6 +17,7 @@ base64 = "0.22.0"
|
||||
serde = { version = "1.0.198", features = ["derive"] }
|
||||
serde_json = "1.0.115"
|
||||
urlencoding = "2.1.3"
|
||||
thiserror = "2"
|
||||
|
||||
# Dependencies for crypto wrapper
|
||||
rkyv = { version = "0.8.12", optional = true }
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
//! address of the client in an encrypted way, and expires 15
|
||||
//! minutes after issuance.
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use crate::crypto_wrapper::CryptoWrapper;
|
||||
use crate::errors::OpenIdError;
|
||||
use crate::time_utils::time;
|
||||
use crate::Res;
|
||||
use std::net::IpAddr;
|
||||
|
||||
#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
|
||||
@@ -28,20 +27,6 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
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:?}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Basic state manager. Can be used to prevent CRSF by encrypting
|
||||
/// a token containing a lifetime and the IP address of the user
|
||||
pub struct BasicStateManager(CryptoWrapper);
|
||||
@@ -59,22 +44,22 @@ impl BasicStateManager {
|
||||
}
|
||||
|
||||
/// Generate a new state
|
||||
pub fn gen_state(&self, ip: IpAddr) -> Result<String, Box<dyn Error>> {
|
||||
pub fn gen_state(&self, ip: IpAddr) -> Res<String> {
|
||||
let state = State::new(ip);
|
||||
|
||||
self.0.encrypt(&state)
|
||||
}
|
||||
|
||||
/// Validate given state on callback URL
|
||||
pub fn validate_state(&self, ip: IpAddr, state: &str) -> Result<(), Box<dyn Error>> {
|
||||
pub fn validate_state(&self, ip: IpAddr, state: &str) -> Res {
|
||||
let state: State = self.0.decrypt(state)?;
|
||||
|
||||
if state.ip != ip {
|
||||
return Err(Box::new(StateError::InvalidIp));
|
||||
return Err(OpenIdError::StateErrorInvalidIP);
|
||||
}
|
||||
|
||||
if state.expire < time() {
|
||||
return Err(Box::new(StateError::Expired));
|
||||
return Err(OpenIdError::StateErrorExpired);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::primitives::{OpenIDConfig, OpenIDTokenResponse, OpenIDUserInfo};
|
||||
use crate::Res;
|
||||
|
||||
impl OpenIDConfig {
|
||||
/// Load OpenID configuration from a given .well-known/openid-configuration URL
|
||||
pub async fn load_from_url(url: &str) -> Result<Self, Box<dyn Error>> {
|
||||
pub async fn load_from_url(url: &str) -> Res<Self> {
|
||||
Ok(reqwest::get(url).await?.json().await?)
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ impl OpenIDConfig {
|
||||
client_secret: &str,
|
||||
code: &str,
|
||||
redirect_uri: &str,
|
||||
) -> Result<(OpenIDTokenResponse, String), Box<dyn Error>> {
|
||||
) -> Res<(OpenIDTokenResponse, String)> {
|
||||
let authorization = BASE64_STANDARD.encode(format!("{client_id}:{client_secret}"));
|
||||
|
||||
let mut params = HashMap::new();
|
||||
@@ -66,7 +66,7 @@ impl OpenIDConfig {
|
||||
pub async fn request_user_info(
|
||||
&self,
|
||||
token: &OpenIDTokenResponse,
|
||||
) -> Result<(OpenIDUserInfo, String), Box<dyn Error>> {
|
||||
) -> Res<(OpenIDUserInfo, String)> {
|
||||
let response = reqwest::Client::new()
|
||||
.get(self.userinfo_endpoint.as_ref().expect(
|
||||
"This client only support information retrieval through userinfo endpoint!",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::errors::OpenIdError;
|
||||
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;
|
||||
@@ -10,7 +12,6 @@ use rkyv::rancor::Strategy;
|
||||
use rkyv::ser::allocator::ArenaHandle;
|
||||
use rkyv::util::AlignedVec;
|
||||
use rkyv::{Archive, Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
|
||||
/// The length of the nonce used to initialize encryption
|
||||
const NONCE_LEN: usize = 12;
|
||||
@@ -33,7 +34,7 @@ impl CryptoWrapper {
|
||||
pub fn encrypt(
|
||||
&self,
|
||||
data: &impl for<'a> Serialize<HighSerializer<AlignedVec, ArenaHandle<'a>, rkyv::rancor::Error>>,
|
||||
) -> Result<String, Box<dyn Error>> {
|
||||
) -> Res<String> {
|
||||
let aes_key = Aes256Gcm::new(&self.key);
|
||||
let nonce_bytes = rand::rng().random::<[u8; NONCE_LEN]>();
|
||||
|
||||
@@ -48,7 +49,7 @@ impl CryptoWrapper {
|
||||
}
|
||||
|
||||
/// Decrypt some data previously encrypted using the [`CryptoWrapper::encrypt`] method
|
||||
pub fn decrypt<T>(&self, input: &str) -> Result<T, Box<dyn Error>>
|
||||
pub fn decrypt<T>(&self, input: &str) -> Res<T>
|
||||
where
|
||||
T: Archive,
|
||||
T::Archived: for<'a> CheckBytes<HighValidator<'a, rkyv::rancor::Error>>
|
||||
@@ -57,9 +58,7 @@ impl CryptoWrapper {
|
||||
let bytes = BASE64_STANDARD.decode(input)?;
|
||||
|
||||
if bytes.len() < NONCE_LEN {
|
||||
return Err(Box::new(std::io::Error::other(
|
||||
"Input string is smaller than nonce!",
|
||||
)));
|
||||
return Err(OpenIdError::DecInputStringSmallerThanNonce);
|
||||
}
|
||||
|
||||
let (enc, nonce) = bytes.split_at(bytes.len() - NONCE_LEN);
|
||||
@@ -71,9 +70,7 @@ impl CryptoWrapper {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
log::error!("Failed to decrypt wrapped data! {e:#?}");
|
||||
return Err(Box::new(std::io::Error::other(
|
||||
"Failed to decrypt wrapped data!",
|
||||
)));
|
||||
return Err(OpenIdError::DecryptWrappedData);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
24
src/errors.rs
Normal file
24
src/errors.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum OpenIdError {
|
||||
#[error("Reqwest error: {0}")]
|
||||
Reqwest(#[from] reqwest::Error),
|
||||
#[error("Serde json error: {0}")]
|
||||
SerdeJson(#[from] serde_json::Error),
|
||||
#[error("Base64 decoding error: {0}")]
|
||||
Base64Decode(#[from] base64::DecodeError),
|
||||
#[cfg(feature = "crypto-wrapper")]
|
||||
#[error("State error: invalid IP")]
|
||||
StateErrorInvalidIP,
|
||||
#[cfg(feature = "crypto-wrapper")]
|
||||
#[error("State error: expired")]
|
||||
StateErrorExpired,
|
||||
#[cfg(feature = "crypto-wrapper")]
|
||||
#[error("Rkyv error: {0}")]
|
||||
RkyvError(#[from] rkyv::rancor::Error),
|
||||
#[cfg(feature = "crypto-wrapper")]
|
||||
#[error("Input string is smaller than nonce!")]
|
||||
DecInputStringSmallerThanNonce,
|
||||
#[cfg(feature = "crypto-wrapper")]
|
||||
#[error("Failed to decrypt wrapped data!")]
|
||||
DecryptWrappedData,
|
||||
}
|
||||
@@ -4,9 +4,14 @@
|
||||
//!
|
||||
//! See https://gitea.communiquons.org/pierre/oidc-test-client for an example of usage of this library
|
||||
|
||||
use crate::errors::OpenIdError;
|
||||
|
||||
pub mod client;
|
||||
pub mod errors;
|
||||
pub mod primitives;
|
||||
|
||||
pub type Res<T = ()> = Result<T, OpenIdError>;
|
||||
|
||||
#[cfg(feature = "crypto-wrapper")]
|
||||
mod time_utils;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user