From 8b3f8e8b2858f4ab65daf8c20be6917e96bc0ff8 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Sun, 23 Mar 2025 14:00:24 +0100 Subject: openid client secret and email whitelist --- src/error.rs | 3 +++ src/modules/auth/openid.rs | 28 +++++++++++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/error.rs b/src/error.rs index 1c6beb1..1b83220 100644 --- a/src/error.rs +++ b/src/error.rs @@ -39,6 +39,8 @@ pub enum ServiceError { BadPath, #[error("bad auth")] BadAuth, + #[error("unauthorized")] + Unauthorized, #[error("bad base64: {0}")] BadBase64(#[from] base64::DecodeError), #[error("connection upgrade failed")] @@ -78,6 +80,7 @@ impl ServiceError { ServiceError::BadUtf83(_) => StatusCode::BAD_REQUEST, ServiceError::BadPath => StatusCode::BAD_REQUEST, ServiceError::BadAuth => StatusCode::UNAUTHORIZED, + ServiceError::Unauthorized => StatusCode::UNAUTHORIZED, ServiceError::BadBase64(_) => StatusCode::BAD_REQUEST, ServiceError::UpgradeFailed => StatusCode::UPGRADE_REQUIRED, ServiceError::Custom(_) => StatusCode::BAD_REQUEST, diff --git a/src/modules/auth/openid.rs b/src/modules/auth/openid.rs index 4bf0de7..ba45fe9 100644 --- a/src/modules/auth/openid.rs +++ b/src/modules/auth/openid.rs @@ -33,7 +33,7 @@ use rustls::RootCertStore; use serde::Deserialize; use serde_yml::Value; use sha2::{Digest, Sha256}; -use std::{io::Read, pin::Pin, str::FromStr, sync::Arc, time::SystemTime}; +use std::{collections::HashSet, io::Read, pin::Pin, str::FromStr, sync::Arc, time::SystemTime}; use tokio::net::TcpStream; use webpki::types::ServerName; @@ -51,9 +51,11 @@ impl NodeKind for OpenIDAuthKind { pub struct OpenIDAuth { salt: String, client_id: String, + client_secret: String, authorize_endpoint: String, token_endpoint: String, scope: String, + authorized_emails: HashSet, next: DynNode, } @@ -98,7 +100,7 @@ impl Node for OpenIDAuth { return Err(ServiceError::CustomStatic("salt invalid")); } } else { - return Err(ServiceError::CustomStatic("aead invalid")); + debug!("aead invalid"); } } else { debug!("no auth cookie"); @@ -148,6 +150,7 @@ impl Node for OpenIDAuth { let resp = token_request( &self.token_endpoint, &self.client_id, + &self.client_secret, &redirect_uri, &code, &verif_plain, @@ -156,6 +159,10 @@ impl Node for OpenIDAuth { let jwt_pay = parse_jwt(&resp.id_token)?; + if !self.authorized_emails.contains(&jwt_pay.email) { + return Err(ServiceError::Unauthorized); + } + let nonce = [(); 12].map(|_| random::()); let mut plaintext = Vec::new(); plaintext.extend(self.salt.as_bytes()); @@ -173,7 +180,7 @@ impl Node for OpenIDAuth { ) .unwrap(); ciphertext.extend(nonce); - let auth = base64::engine::general_purpose::URL_SAFE.encode(ciphertext); + let auth = BASE64_URL_SAFE_NO_PAD.encode(ciphertext); let mut resp = Response::new("".to_string()).map(|b| b.map_err(|e| match e {}).boxed()); @@ -181,7 +188,7 @@ impl Node for OpenIDAuth { resp.headers_mut().append( SET_COOKIE, HeaderValue::from_str(&format!( - "gnix_oauth_username={}; Secure", + "gnix_oauth_email={}; Secure", percent_encode(jwt_pay.email.as_bytes(), NON_ALPHANUMERIC) )) .map_err(|_| ServiceError::InvalidHeader)?, @@ -262,21 +269,23 @@ fn redirect_uri(request: &NodeRequest) -> Result { } async fn token_request( - provider: &str, + endpoint: &str, client_id: &str, + client_secret: &str, redirect_uri: &str, code: &str, verifier: &str, ) -> Result { - let url = Uri::from_str(provider).unwrap(); + let url = Uri::from_str(endpoint).unwrap(); let body = format!( - "client_id={}&redirect_uri={}&code={}&code_verifier={}&grant_type=authorization_code", + "client_id={}&client_secret={}&redirect_uri={}&code={}&code_verifier={}&grant_type=authorization_code", utf8_percent_encode(client_id, NON_ALPHANUMERIC), + utf8_percent_encode(client_secret, NON_ALPHANUMERIC), utf8_percent_encode(redirect_uri, NON_ALPHANUMERIC), utf8_percent_encode(code, NON_ALPHANUMERIC), utf8_percent_encode(verifier, NON_ALPHANUMERIC), ); - info!("validate {url} {body:?}"); + info!("token {url} {body:?}"); let authority = url.authority().unwrap().clone(); eprintln!("connect {}", authority.as_str()); @@ -337,13 +346,10 @@ fn parse_jwt(s: &str) -> Result { .split_once(".") .ok_or(ServiceError::CustomStatic("jwt invalid format"))?; - eprintln!("{header:?}"); let header: JwtHeader = serde_json::from_slice(&BASE64_URL_SAFE_NO_PAD.decode(header)?) .map_err(|_| ServiceError::CustomStatic("jwt invalid header"))?; - eprintln!("{payload:?}"); let payload: JwtPayload = serde_json::from_slice(&BASE64_URL_SAFE_NO_PAD.decode(payload)?) .map_err(|_| ServiceError::CustomStatic("jwt invalid payload"))?; - eprintln!("a"); if header.typ != "JWT" { return Err(ServiceError::CustomStatic("jwt type is not jwt (duh)")); -- cgit v1.2.3-70-g09d2