orcs_auth/privilege.rs
1//! Privilege level types.
2
3use std::time::{Duration, Instant};
4
5/// The current privilege level of a session.
6///
7/// This implements a sudo-like model where all actors start with
8/// limited permissions and must explicitly elevate to perform
9/// privileged operations.
10///
11/// # Design Rationale
12///
13/// ## Why Not Always Elevated?
14///
15/// Even human users operate in Standard mode by default:
16///
17/// - **Prevents accidents**: `git reset --hard` requires explicit elevation
18/// - **Audit clarity**: Elevated actions are intentional and logged
19/// - **Network safety**: Compromised sessions have limited damage potential
20///
21/// ## Time-Limited Elevation
22///
23/// Elevated privileges automatically expire to minimize the window
24/// of elevated access. This follows the principle of least privilege.
25///
26/// # Example
27///
28/// ```
29/// use orcs_auth::PrivilegeLevel;
30/// use std::time::{Duration, Instant};
31///
32/// // Standard mode (default)
33/// let standard = PrivilegeLevel::Standard;
34/// assert!(!standard.is_elevated());
35///
36/// // Elevated mode (explicit, time-limited)
37/// let until = Instant::now() + Duration::from_secs(300);
38/// let elevated = PrivilegeLevel::Elevated { until };
39/// assert!(elevated.is_elevated());
40/// ```
41#[derive(Debug, Clone, Default)]
42pub enum PrivilegeLevel {
43 /// Normal operations only.
44 ///
45 /// In this mode, the following are **not allowed**:
46 ///
47 /// - Global signals (Veto)
48 /// - Destructive file operations (`rm -rf`, overwrite without backup)
49 /// - Destructive git operations (`reset --hard`, `push --force`)
50 /// - Modifying system configuration
51 ///
52 /// This is the default mode for all principals.
53 #[default]
54 Standard,
55
56 /// Elevated privileges with expiration.
57 ///
58 /// Grants full access to all operations until the specified time.
59 /// After expiration, the session automatically drops to Standard.
60 ///
61 /// # Fields
62 ///
63 /// * `until` - When elevation expires (automatically serializes as duration from now)
64 Elevated {
65 /// Expiration time for elevated privileges.
66 until: Instant,
67 },
68}
69
70impl PrivilegeLevel {
71 /// Creates a new Standard privilege level.
72 #[must_use]
73 pub fn standard() -> Self {
74 Self::Standard
75 }
76
77 /// Creates a new Elevated privilege level with the given duration.
78 ///
79 /// # Example
80 ///
81 /// ```
82 /// use orcs_auth::PrivilegeLevel;
83 /// use std::time::Duration;
84 ///
85 /// let elevated = PrivilegeLevel::elevated_for(Duration::from_secs(60));
86 /// assert!(elevated.is_elevated());
87 /// ```
88 #[must_use]
89 pub fn elevated_for(duration: Duration) -> Self {
90 Self::Elevated {
91 until: Instant::now() + duration,
92 }
93 }
94
95 /// Returns `true` if currently elevated (and not expired).
96 ///
97 /// # Example
98 ///
99 /// ```
100 /// use orcs_auth::PrivilegeLevel;
101 /// use std::time::Duration;
102 ///
103 /// let standard = PrivilegeLevel::Standard;
104 /// assert!(!standard.is_elevated());
105 ///
106 /// let elevated = PrivilegeLevel::elevated_for(Duration::from_secs(60));
107 /// assert!(elevated.is_elevated());
108 /// ```
109 #[must_use]
110 pub fn is_elevated(&self) -> bool {
111 match self {
112 Self::Standard => false,
113 Self::Elevated { until } => Instant::now() < *until,
114 }
115 }
116
117 /// Returns `true` if this is Standard mode or elevation has expired.
118 #[must_use]
119 pub fn is_standard(&self) -> bool {
120 !self.is_elevated()
121 }
122
123 /// Returns the remaining elevation time, or `None` if not elevated.
124 ///
125 /// # Example
126 ///
127 /// ```
128 /// use orcs_auth::PrivilegeLevel;
129 /// use std::time::Duration;
130 ///
131 /// let elevated = PrivilegeLevel::elevated_for(Duration::from_secs(60));
132 /// let remaining = elevated.remaining();
133 /// assert!(remaining.is_some());
134 /// assert!(remaining.unwrap() <= Duration::from_secs(60));
135 /// ```
136 #[must_use]
137 pub fn remaining(&self) -> Option<Duration> {
138 match self {
139 Self::Standard => None,
140 Self::Elevated { until } => {
141 let now = Instant::now();
142 if now < *until {
143 Some(*until - now)
144 } else {
145 None
146 }
147 }
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn standard_is_not_elevated() {
158 let level = PrivilegeLevel::Standard;
159 assert!(!level.is_elevated());
160 assert!(level.is_standard());
161 assert!(level.remaining().is_none());
162 }
163
164 #[test]
165 fn elevated_is_elevated() {
166 let level = PrivilegeLevel::elevated_for(Duration::from_secs(60));
167 assert!(level.is_elevated());
168 assert!(!level.is_standard());
169 assert!(level.remaining().is_some());
170 }
171
172 #[test]
173 fn expired_elevation_is_standard() {
174 let level = PrivilegeLevel::Elevated {
175 until: Instant::now() - Duration::from_secs(1),
176 };
177 assert!(!level.is_elevated());
178 assert!(level.is_standard());
179 assert!(level.remaining().is_none());
180 }
181
182 #[test]
183 fn default_is_standard() {
184 let level = PrivilegeLevel::default();
185 assert!(level.is_standard());
186 }
187
188 #[test]
189 fn remaining_decreases() {
190 let level = PrivilegeLevel::elevated_for(Duration::from_secs(60));
191 let remaining = level
192 .remaining()
193 .expect("elevated level should have remaining duration");
194 assert!(remaining <= Duration::from_secs(60));
195 }
196}