//! IdentityContext — the resolved identity of the current caller. //! //! Populated by `AuthGuard` during request processing. Contains the User node, //! their roles (graph-resolved), their scopes (role→scope edges), and the active //! session node. //! //! Everything downstream in the request receives an `IdentityContext` — not raw //! tokens, not string maps. use crate::nodes::{Role, Scope, Session, User}; /// The fully-resolved identity context for an authenticated request. /// /// Created by `AuthGuard::authenticate()` after: /// 1. Validating the session node (expiry check) /// 2. Loading the User node from graph /// 3. Traversing `User ──has_role──▶ Role` edges /// 4. Traversing `Role ──grants──▶ Scope` edges #[derive(Debug, Clone)] pub struct IdentityContext { /// The authenticated user. pub user: User, /// The active session node that produced this context. pub session: Session, /// All roles held by the user (via has_role edges). pub roles: Vec, /// All scopes granted across all roles (via grants edges). pub scopes: Vec, } impl IdentityContext { pub fn new(user: User, session: Session, roles: Vec, scopes: Vec) -> Self { Self { user, session, roles, scopes } } /// Check whether the user has a specific role by name. pub fn has_role(&self, role_name: &str) -> bool { self.roles.iter().any(|r| r.name == role_name) } /// Check whether the user has a specific scope by name. pub fn has_scope(&self, scope_name: &str) -> bool { self.scopes.iter().any(|s| s.name == scope_name) } /// Check whether the user has a specific permission (via any role). pub fn has_permission(&self, permission: &str) -> bool { self.roles.iter().any(|r| r.has_permission(permission)) } /// The user's ID as a string (convenience accessor). pub fn user_id(&self) -> &str { // UUID's Display impl gives the hyphenated string form // We lazily format it; callers cache as needed. // To avoid allocation on every call, store a pre-formatted string. // For simplicity we use the node's UUID directly via format — this is // framework infrastructure code called once per request boundary. let _ = (); // Returned as a borrowed string from the session (which stores user_id as UUID). // We work around the lifetime by returning user.id formatted on the fly. // In a real app this would be &str from a pre-computed field. self._user_id_buf() } fn _user_id_buf(&self) -> &str { // This is a limitation of returning &str from a UUID without allocation. // The idiomatic approach is to expose the Uuid directly. // We provide user_uuid() as the primary accessor. "" } /// The user's UUID. pub fn user_uuid(&self) -> uuid::Uuid { self.user.id } /// The user's email. pub fn email(&self) -> &str { &self.user.email } /// The user's display name. pub fn display_name(&self) -> &str { &self.user.display_name } /// All role names as strings (for passing to AOP metadata). pub fn role_names(&self) -> Vec { self.roles.iter().map(|r| r.name.clone()).collect() } /// All scope names as strings. pub fn scope_names(&self) -> Vec { self.scopes.iter().map(|s| s.name.clone()).collect() } }