Initial Commit
This commit is contained in:
commit
c04b8996bf
6 changed files with 146 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
target/
|
||||||
|
Cargo.lock
|
||||||
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "keycloak-helper"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { version = "0.13.2", features = ["json", "form"] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
thiserror = "2.0.18"
|
||||||
16
src/errors.rs
Normal file
16
src/errors.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum TokenValidationError {
|
||||||
|
#[error("Missing Environment Variables: {0}")]
|
||||||
|
MissingEnvs(String),
|
||||||
|
|
||||||
|
#[error("Token Inactive")]
|
||||||
|
Inactive,
|
||||||
|
|
||||||
|
#[error("Insufficient Permissions")]
|
||||||
|
Forbidden,
|
||||||
|
|
||||||
|
#[error("Keycloak Request Failed: {0}")]
|
||||||
|
RequestFailed(String),
|
||||||
|
}
|
||||||
3
src/lib.rs
Normal file
3
src/lib.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod errors;
|
||||||
|
pub mod types;
|
||||||
|
pub mod validate;
|
||||||
43
src/types.rs
Normal file
43
src/types.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct TokenRealmAccess {
|
||||||
|
pub roles: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct TokenAccountResourceAccess {
|
||||||
|
pub roles: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct TokenResourceAccess {
|
||||||
|
pub account: TokenAccountResourceAccess,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct TokenValidationResponse {
|
||||||
|
pub exp: u64, // Token expiry time
|
||||||
|
pub iat: u64, // Token issue time
|
||||||
|
pub auth_time: u64,
|
||||||
|
pub jti: String,
|
||||||
|
pub iss: String,
|
||||||
|
pub aud: String,
|
||||||
|
pub sub: String,
|
||||||
|
pub sid: String,
|
||||||
|
pub acr: String,
|
||||||
|
pub allowed_origins: Option<Vec<String>>,
|
||||||
|
pub realm_access: TokenRealmAccess,
|
||||||
|
pub resource_access: TokenResourceAccess,
|
||||||
|
pub scope: String,
|
||||||
|
pub email_verified: bool,
|
||||||
|
pub name: String,
|
||||||
|
pub preferred_username: String,
|
||||||
|
pub given_name: String,
|
||||||
|
pub family_name: String,
|
||||||
|
pub email: String,
|
||||||
|
pub client_id: String, // Client that issued the token
|
||||||
|
pub username: String,
|
||||||
|
pub token_type: String,
|
||||||
|
pub active: bool,
|
||||||
|
}
|
||||||
72
src/validate.rs
Normal file
72
src/validate.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
use reqwest::Client;
|
||||||
|
use std::env;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::errors;
|
||||||
|
use crate::types;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct TokenActiveCheck {
|
||||||
|
active: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn validate_token(token: &str, required_roles: Option<Vec<&str>>, needs_all_roles: bool) -> Result<types::TokenValidationResponse, errors::TokenValidationError> {
|
||||||
|
let client = Client::new();
|
||||||
|
|
||||||
|
let keycloak_url = env::var("KEYCLOAK_URL")
|
||||||
|
.map_err(|_| errors::TokenValidationError::MissingEnvs("KEYCLOAK_URL".to_string()))?;
|
||||||
|
let realm = env::var("KEYCLOAK_REALM")
|
||||||
|
.map_err(|_| errors::TokenValidationError::MissingEnvs("KEYCLOAK_REALM".to_string()))?;
|
||||||
|
let client_id = env::var("KEYCLOAK_CLIENT_ID")
|
||||||
|
.map_err(|_| errors::TokenValidationError::MissingEnvs("KEYCLOAK_CLIENT_ID".to_string()))?;
|
||||||
|
let client_secret = env::var("KEYCLOAK_CLIENT_SECRET")
|
||||||
|
.map_err(|_| errors::TokenValidationError::MissingEnvs("KEYCLOAK_CLIENT_SECRET".to_string()))?;
|
||||||
|
|
||||||
|
let url = format!("{}/realms/{}/protocol/openid-connect/token/introspect",
|
||||||
|
keycloak_url,
|
||||||
|
realm
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut form = HashMap::new();
|
||||||
|
form.insert("token", token);
|
||||||
|
form.insert("client_id", &client_id);
|
||||||
|
form.insert("client_secret", &client_secret);
|
||||||
|
|
||||||
|
let res = client.post(url).form(&form).send().await
|
||||||
|
.map_err(|e| errors::TokenValidationError::RequestFailed(format!("{e}")))?;
|
||||||
|
|
||||||
|
let text = res.text().await
|
||||||
|
.map_err(|_| errors::TokenValidationError::RequestFailed("Failed to Get Response Text".to_string()))?;
|
||||||
|
|
||||||
|
|
||||||
|
let json: TokenActiveCheck = serde_json::from_str(&text)
|
||||||
|
.map_err(|e| errors::TokenValidationError::RequestFailed(format!("Failed to Map Response to Active Validation JSON: {e}")))?;
|
||||||
|
|
||||||
|
if !json.active { return Err(errors::TokenValidationError::Inactive) }
|
||||||
|
|
||||||
|
|
||||||
|
let json: types::TokenValidationResponse = serde_json::from_str(&text)
|
||||||
|
.map_err(|e| errors::TokenValidationError::RequestFailed(format!("Failed to Map Response to Response JSON: {e}")))?;
|
||||||
|
|
||||||
|
|
||||||
|
match required_roles {
|
||||||
|
Some(required_roles) => {
|
||||||
|
let roles = json.realm_access.roles.clone();
|
||||||
|
|
||||||
|
let mut included = 0;
|
||||||
|
|
||||||
|
for role in required_roles {
|
||||||
|
if !roles.contains(&role.to_string()) {match needs_all_roles {
|
||||||
|
true => return Err(errors::TokenValidationError::Forbidden),
|
||||||
|
false => (),
|
||||||
|
}} else { included += 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
if included <= 0 { return Err(errors::TokenValidationError::Forbidden) }
|
||||||
|
},
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(json)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue