80 lines
2.6 KiB
Rust
80 lines
2.6 KiB
Rust
// [TSM.ID].[11031972] — All Rights Reserved. Proprietary & Confidential.
|
|
use axum::{
|
|
extract::Request,
|
|
middleware::Next,
|
|
response::Response,
|
|
http::StatusCode,
|
|
};
|
|
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
|
|
use serde::{Deserialize, Serialize};
|
|
use tracing::warn;
|
|
use std::sync::OnceLock;
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct IamClaims {
|
|
pub sub: String, // User/Tenant ID
|
|
pub role: String, // "supreme_admin" or "tenant"
|
|
pub packages: Vec<String>, // ["phase-1", "phase-72", "billing"]
|
|
pub exp: usize,
|
|
}
|
|
|
|
/// JWT Secret WAJIB dari environment variable — DILARANG HARDCODE
|
|
/// Env: XCU_IAM_SECRET (harus sama dengan secret JUMPA.ID IAM)
|
|
static IAM_SECRET: OnceLock<String> = OnceLock::new();
|
|
|
|
fn get_iam_secret() -> &'static str {
|
|
IAM_SECRET.get_or_init(|| {
|
|
std::env::var("XCU_IAM_SECRET").unwrap_or_else(|_| {
|
|
warn!("[IAM] ⚠️ XCU_IAM_SECRET env var NOT SET! Using fallback. SET THIS IN PRODUCTION!");
|
|
"XCU_IAM_SECRET_NOT_CONFIGURED".to_string()
|
|
})
|
|
})
|
|
}
|
|
|
|
pub async fn xcu_gatekeeper(
|
|
mut req: Request,
|
|
next: Next,
|
|
) -> Result<Response, StatusCode> {
|
|
// 1. Ekstrak Token dari Header atau Query Params (untuk SSE)
|
|
let auth_header = req.headers().get("Authorization").and_then(|h| h.to_str().ok());
|
|
|
|
let token = if let Some(auth) = auth_header {
|
|
if auth.starts_with("Bearer ") {
|
|
Some(auth.trim_start_matches("Bearer "))
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
// Fallback ke query param (misal untuk SSE)
|
|
req.uri().query().and_then(|q| {
|
|
q.split('&').find(|kv| kv.starts_with("token=")).map(|kv| kv.trim_start_matches("token="))
|
|
})
|
|
};
|
|
|
|
if let Some(t) = token {
|
|
let validation = Validation::new(Algorithm::HS256);
|
|
// validate_exp = true (default) — expired tokens WAJIB ditolak
|
|
|
|
match decode::<IamClaims>(
|
|
t,
|
|
&DecodingKey::from_secret(get_iam_secret().as_bytes()),
|
|
&validation,
|
|
) {
|
|
Ok(token_data) => {
|
|
// Token Sah! Masukkan Identitas Lintas-Dimensi ke dalam mesin.
|
|
req.extensions_mut().insert(token_data.claims);
|
|
}
|
|
Err(e) => {
|
|
warn!("IAM Matrix Breach Detected: Invalid JWT Token. {:?}", e);
|
|
return Err(StatusCode::UNAUTHORIZED);
|
|
}
|
|
}
|
|
} else {
|
|
// TANPA TOKEN = REJECTED. Tidak ada guest bypass.
|
|
warn!("[IAM] No token provided — ACCESS DENIED");
|
|
return Err(StatusCode::UNAUTHORIZED);
|
|
}
|
|
|
|
Ok(next.run(req).await)
|
|
}
|