axum_gate/
roles.rs

1//! Pre-defined hierarchical role system for access control.
2//!
3//! This module provides a built-in role system with four levels arranged in a hierarchy
4//! where higher roles inherit access from lower roles. The hierarchy is:
5//!
6//! **Admin > Moderator > Reporter > User**
7//!
8//! # Role Hierarchy
9//!
10//! When using `AccessPolicy::require_role_or_supervisor()`, higher roles automatically
11//! inherit access from lower roles:
12//!
13//! ```rust
14//! use axum_gate::prelude::Role;
15//! use axum_gate::authz::AccessPolicy;
16//! use axum_gate::prelude::Group;
17//!
18//! // Allow User role and all supervisor roles (Reporter, Moderator, Admin)
19//! let policy = AccessPolicy::<Role, Group>::require_role_or_supervisor(Role::User);
20//!
21//! // Allow only Moderator role and supervisor roles (Admin)
22//! let policy = AccessPolicy::<Role, Group>::require_role_or_supervisor(Role::Moderator);
23//!
24//! // Allow only Admin role (no supervisors above Admin)
25//! let policy = AccessPolicy::<Role, Group>::require_role_or_supervisor(Role::Admin);
26//! ```
27//!
28//! # Using Roles with Gates
29//!
30//! ```rust
31//! use axum_gate::prelude::*;
32//! use axum_gate::authz::AccessPolicy;
33//! use axum_gate::codecs::jwt::{JsonWebToken, JwtClaims};
34//! use axum_gate::accounts::Account;
35//! use std::sync::Arc;
36//!
37//! # let jwt_codec = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
38//! // Exact role match (only Admin)
39//! let admin_gate = Gate::cookie("my-app", Arc::clone(&jwt_codec))
40//!     .with_policy(AccessPolicy::<Role, Group>::require_role(Role::Admin));
41//!
42//! // Multiple specific roles
43//! let staff_gate = Gate::cookie("my-app", Arc::clone(&jwt_codec))
44//!     .with_policy(
45//!         AccessPolicy::<Role, Group>::require_role(Role::Admin)
46//!             .or_require_role(Role::Moderator)
47//!     );
48//!
49//! // Hierarchical access (User + all supervisors)
50//! let user_gate = Gate::cookie("my-app", jwt_codec)
51//!     .with_policy(AccessPolicy::<Role, Group>::require_role_or_supervisor(Role::User));
52//! ```
53//!
54//! # Creating Custom Roles
55//!
56//! Create your own role hierarchy by implementing the required traits:
57//!
58//! ```rust
59//! use serde::{Deserialize, Serialize};
60//! use axum_gate::authz::AccessHierarchy;
61//!
62//! #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
63//! enum CompanyRole {
64//!     #[default]
65//!     Employee,      // Lowest privilege
66//!     TeamLead,
67//!     Manager,
68//!     Director,      // Highest privilege
69//! }
70//!
71//! impl std::fmt::Display for CompanyRole {
72//!     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73//!         match self {
74//!             CompanyRole::Employee => write!(f, "Employee"),
75//!             CompanyRole::TeamLead => write!(f, "TeamLead"),
76//!             CompanyRole::Manager => write!(f, "Manager"),
77//!             CompanyRole::Director => write!(f, "Director"),
78//!         }
79//!     }
80//! }
81//!
82//! impl AccessHierarchy for CompanyRole {}
83//! ```
84
85use crate::authz::AccessHierarchy;
86#[cfg(all(
87    feature = "server",
88    any(feature = "storage-seaorm", feature = "storage-seaorm-v2")
89))]
90use crate::comma_separated_value::CommaSeparatedValue;
91use serde::{Deserialize, Serialize};
92#[cfg(all(
93    feature = "server",
94    any(feature = "storage-seaorm", feature = "storage-seaorm-v2")
95))]
96use std::str::FromStr;
97
98/// Pre-defined roles with hierarchical access control.
99///
100/// These roles are arranged in a hierarchy where higher roles automatically
101/// inherit access from lower roles when using `AccessPolicy::require_role_or_supervisor()`.
102///
103/// **Hierarchy (highest to lowest):**
104/// - `Admin` - Full system access
105/// - `Moderator` - Content moderation and user management
106/// - `Reporter` - Read access with reporting capabilities
107/// - `User` - Basic user access
108///
109/** Updated example imports to reflect current public modules */
110/// # Example Usage
111/// ```rust
112/// use axum_gate::prelude::{Role, Group};
113/// use axum_gate::authz::AccessPolicy;
114///
115/// // Grant access to Moderators and all supervisor roles (Admin)
116/// let policy = AccessPolicy::<Role, Group>::require_role_or_supervisor(Role::Moderator);
117///
118/// // Grant access to specific roles only
119/// let policy = AccessPolicy::<Role, Group>::require_role(Role::Admin)
120///     .or_require_role(Role::Moderator);
121/// ```
122#[derive(
123    Debug,
124    Default,
125    Clone,
126    Copy,
127    Eq,
128    PartialEq,
129    Ord,
130    PartialOrd,
131    Serialize,
132    Deserialize,
133    strum::Display,
134    strum::EnumString,
135)]
136pub enum Role {
137    /// Basic user role with standard application access.
138    ///
139    /// Users have access to core application features but limited
140    /// administrative capabilities.
141    #[default]
142    User,
143    /// Reporter role with read access and reporting capabilities.
144    ///
145    /// Reporters can typically view system information, generate reports,
146    /// and access analytics data.
147    Reporter,
148    /// Moderator role with elevated privileges for content and user management.
149    ///
150    /// Moderators can typically manage content, moderate discussions,
151    /// and have elevated access to user-facing features.
152    Moderator,
153    /// Administrator role with the highest level of access.
154    ///
155    /// Administrators typically have full system access and can perform
156    /// any operation within the application.
157    Admin,
158}
159
160impl AccessHierarchy for Role {}
161
162#[cfg(all(
163    feature = "server",
164    any(feature = "storage-seaorm", feature = "storage-seaorm-v2")
165))]
166impl CommaSeparatedValue for Vec<Role> {
167    fn from_csv(value: &str) -> Result<Self, String> {
168        let mut role_str = value.split(',').collect::<Vec<&str>>();
169        let mut roles = Vec::with_capacity(role_str.len());
170        while let Some(r) = role_str.pop() {
171            roles.push(Role::from_str(r).map_err(|e| e.to_string())?);
172        }
173        Ok(roles)
174    }
175
176    fn into_csv(self) -> String {
177        self.into_iter()
178            .map(|g| g.to_string())
179            .collect::<Vec<String>>()
180            .join(",")
181    }
182}