//! IDENTITY namespace (0x0002) — OIDC token validation + authorization. use async_trait::async_trait; use crate::shellstream::{identity, ShellstreamResponse}; use super::NamespaceHandler; pub struct IdentityHandler { dev_mode: bool, } impl IdentityHandler { pub fn new(dev_mode: bool) -> Self { Self { dev_mode } } } #[async_trait] impl NamespaceHandler for IdentityHandler { async fn handle( &self, function_id: u16, payload: &[u8], session_id: &[u8; 16], ) -> ShellstreamResponse { match function_id { identity::AUTHENTICATE => self.authenticate(payload, session_id).await, identity::AUTHORIZE => self.authorize(payload, session_id).await, identity::WHOAMI => self.resolve(payload, session_id).await, _ => ShellstreamResponse::error( *session_id, 0, &format!("Unknown IDENTITY function: {function_id:#06x}"), ), } } } impl IdentityHandler { async fn authenticate(&self, payload: &[u8], session_id: &[u8; 16]) -> ShellstreamResponse { let payload_parsed: serde_json::Value = rmp_serde::from_slice(payload).unwrap_or(serde_json::Value::Null); if self.dev_mode { // Dev mode: accept any token, return synthetic identity let response = rmp_serde::to_vec(&serde_json::json!({ "subject": "dev-user", "email": "dev@guildhouse.local", "issuer": "dev-mode", "verified": true, })) .unwrap_or_default(); return ShellstreamResponse::ok(*session_id, 0, response); } // TODO: Validate JWT via OidcAuthProvider (Stage 3 — after moving to bascule-core) let token = payload_parsed .get("token") .and_then(|v| v.as_str()) .unwrap_or(""); if token.is_empty() { return ShellstreamResponse::denied(*session_id, 0, "No token provided"); } // Placeholder: accept token and return minimal claims let response = rmp_serde::to_vec(&serde_json::json!({ "subject": "unknown", "verified": false, "message": "OIDC validation not yet implemented", })) .unwrap_or_default(); ShellstreamResponse::ok(*session_id, 0, response) } async fn authorize(&self, payload: &[u8], session_id: &[u8; 16]) -> ShellstreamResponse { if self.dev_mode { let response = rmp_serde::to_vec(&serde_json::json!({ "authorized": true, })) .unwrap_or_default(); return ShellstreamResponse::ok(*session_id, 0, response); } let payload_parsed: serde_json::Value = rmp_serde::from_slice(payload).unwrap_or(serde_json::Value::Null); let subject = payload_parsed.get("subject").and_then(|v| v.as_str()).unwrap_or(""); let resource = payload_parsed.get("resource").and_then(|v| v.as_str()).unwrap_or(""); let action = payload_parsed.get("action").and_then(|v| v.as_str()).unwrap_or(""); tracing::debug!(subject, resource, action, "identity.authorize"); // Default: allow in soft mode let response = rmp_serde::to_vec(&serde_json::json!({ "authorized": true, })) .unwrap_or_default(); ShellstreamResponse::ok(*session_id, 0, response) } async fn resolve(&self, _payload: &[u8], session_id: &[u8; 16]) -> ShellstreamResponse { let response = rmp_serde::to_vec(&serde_json::json!({ "message": "resolve not yet implemented", })) .unwrap_or_default(); ShellstreamResponse::ok(*session_id, 0, response) } }