summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-03-23 14:00:24 +0100
committermetamuffin <metamuffin@disroot.org>2025-03-23 14:00:24 +0100
commit8b3f8e8b2858f4ab65daf8c20be6917e96bc0ff8 (patch)
treedc3ea70659be759c391c7da3d630b79125dcdcc9
parent88d66144a2816efd5e544783842177a140685e1b (diff)
downloadgnix-8b3f8e8b2858f4ab65daf8c20be6917e96bc0ff8.tar
gnix-8b3f8e8b2858f4ab65daf8c20be6917e96bc0ff8.tar.bz2
gnix-8b3f8e8b2858f4ab65daf8c20be6917e96bc0ff8.tar.zst
openid client secret and email whitelist
-rw-r--r--src/error.rs3
-rw-r--r--src/modules/auth/openid.rs28
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<String>,
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::<u8>());
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<Uri, ServiceError> {
}
async fn token_request(
- provider: &str,
+ endpoint: &str,
client_id: &str,
+ client_secret: &str,
redirect_uri: &str,
code: &str,
verifier: &str,
) -> Result<OAuthTokenResponse, ServiceError> {
- 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<JwtPayload, ServiceError> {
.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)"));