use crate::handler::OidcCookieInsecure;
use crate::provider::OidcProvider;
use crate::rauthy_error::RauthyError;
use crate::tokens::jwks::jwks_handler;
use base64::{Engine as _, engine, engine::general_purpose};
use rand::{RngExt, distr};
pub use reqwest::Certificate as RootCertificate;
use sha2::{Digest, Sha256};
use tracing::{error, warn};
#[cfg(feature = "backchannel-logout")]
pub mod backchannel_logout;
pub mod cookie_state;
#[cfg(feature = "device-code")]
pub mod device_code;
pub mod handler;
pub mod oidc_config;
pub mod principal;
pub mod provider;
pub mod token_set;
pub mod rauthy_error;
#[cfg(feature = "scim")]
pub mod scim;
pub mod tokens;
pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION");
const B64_URL_SAFE_NO_PAD: engine::GeneralPurpose = general_purpose::URL_SAFE_NO_PAD;
pub(crate) fn b64_decode(value: &str) -> Result<Vec<u8>, RauthyError> {
Ok(general_purpose::STANDARD.decode(value)?)
}
#[inline]
pub(crate) fn b64_encode(value: &[u8]) -> String {
general_purpose::STANDARD.encode(value)
}
#[inline]
pub(crate) fn base64_url_encode(input: &[u8]) -> String {
let b64 = general_purpose::STANDARD.encode(input);
b64.chars()
.filter_map(|c| match c {
'=' => None,
'+' => Some('-'),
'/' => Some('_'),
x => Some(x),
})
.collect()
}
pub(crate) fn base64_url_no_pad_decode(b64: &str) -> Result<Vec<u8>, RauthyError> {
Ok(B64_URL_SAFE_NO_PAD.decode(b64)?)
}
#[inline(always)]
pub fn base64_url_no_pad_decode_buf(b64: &str, buf: &mut Vec<u8>) -> Result<(), RauthyError> {
Ok(B64_URL_SAFE_NO_PAD.decode_vec(b64, buf)?)
}
#[inline]
fn build_lax_cookie_300(name: &str, value: &str, insecure: OidcCookieInsecure) -> String {
if insecure != OidcCookieInsecure::Yes {
format!("{name}={value}; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=300")
} else {
warn!("Building an INSECURE cookie - DO NOT USE IN PRODUCTION");
format!("{name}={value}; Path=/; HttpOnly; SameSite=Lax; Max-Age=300")
}
}
pub fn extract_token_claims<T>(token: &str) -> Result<T, RauthyError>
where
T: for<'a> serde::Deserialize<'a>,
{
let body = match token.split_once('.') {
None => None,
Some((_metadata, rest)) => rest.split_once('.').map(|(body, _validation_str)| body),
};
if body.is_none() {
return Err(RauthyError::MalformedJwt("Invalid or malformed JWT Token"));
}
let body = body.unwrap();
let b64 = match B64_URL_SAFE_NO_PAD.decode(body) {
Ok(values) => values,
Err(err) => {
error!("Error decoding JWT token body '{body}' from base64: {err}");
return Err(RauthyError::InvalidJwt("Invalid JWT Token body"));
}
};
let s = String::from_utf8_lossy(b64.as_slice());
let claims = match serde_json::from_str::<T>(s.as_ref()) {
Ok(claims) => claims,
Err(err) => {
error!("Error deserializing JWT Token claims: {err}");
return Err(RauthyError::InvalidJwt("Invalid JWT Token claims"));
}
};
Ok(claims)
}
#[inline]
pub fn generate_pkce_challenge() -> (String, String) {
let plain = secure_random(32);
let mut hasher = Sha256::new();
hasher.update(plain.as_bytes());
let s256 = hasher.finalize();
let challenge = base64_url_encode(s256.as_ref());
(plain, challenge)
}
#[derive(Debug, Clone, PartialEq)]
pub enum RauthyHttpsOnly {
Yes,
No,
}
impl RauthyHttpsOnly {
pub fn bool(&self) -> bool {
self == &Self::Yes
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum DangerAcceptInvalidCerts {
Yes,
No,
}
impl DangerAcceptInvalidCerts {
pub fn bool(&self) -> bool {
self == &Self::Yes
}
}
pub async fn init() -> Result<(), RauthyError> {
OidcProvider::init_client(None, RauthyHttpsOnly::Yes, DangerAcceptInvalidCerts::No)?;
jwks_handler().await;
Ok(())
}
pub async fn init_with(
root_certificate: Option<RootCertificate>,
https_only: RauthyHttpsOnly,
danger_accept_invalid_certs: DangerAcceptInvalidCerts,
) -> Result<(), RauthyError> {
OidcProvider::init_client(root_certificate, https_only, danger_accept_invalid_certs)?;
jwks_handler().await;
Ok(())
}
#[inline]
pub fn secure_random(count: usize) -> String {
rand::rng()
.sample_iter(&distr::Alphanumeric)
.take(count)
.map(char::from)
.collect::<String>()
}