reinhardt_auth/core/permission.rs
1use async_trait::async_trait;
2
3use crate::core::user::User;
4
5/// Permission context - contains request information for permission checking
6///
7/// This struct provides the context needed for permission classes to make
8/// authorization decisions.
9///
10/// # Examples
11///
12/// ```
13/// use reinhardt_auth::{PermissionContext, AnonymousUser, User};
14/// use reinhardt_http::Request;
15/// use hyper::{Method, Uri, Version, header::HeaderMap};
16/// use bytes::Bytes;
17///
18/// let request = Request::builder()
19/// .method(Method::GET)
20/// .uri("/")
21/// .version(Version::HTTP_11)
22/// .headers(HeaderMap::new())
23/// .body(Bytes::new())
24/// .build()
25/// .unwrap();
26/// let context = PermissionContext {
27/// request: &request,
28/// is_authenticated: false,
29/// is_admin: false,
30/// is_active: false,
31/// user: None,
32/// };
33///
34/// assert!(!context.is_authenticated);
35/// ```
36pub struct PermissionContext<'a> {
37 /// The HTTP request
38 pub request: &'a reinhardt_http::Request,
39 /// Whether the user is authenticated
40 pub is_authenticated: bool,
41 /// Whether the user is an admin
42 pub is_admin: bool,
43 /// Whether the user account is active
44 pub is_active: bool,
45 /// The authenticated user, if any
46 pub user: Option<Box<dyn User>>,
47}
48
49/// Permission trait - defines permission checking interface
50///
51/// Implement this trait to create custom permission classes for your API.
52///
53/// # Examples
54///
55/// Custom permission class:
56///
57/// ```
58/// use reinhardt_auth::{Permission, PermissionContext};
59/// use async_trait::async_trait;
60///
61/// struct IsOwner;
62///
63/// #[async_trait]
64/// impl Permission for IsOwner {
65/// async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
66/// // Check if user is authenticated and owns the resource
67/// if !context.is_authenticated {
68/// return false;
69/// }
70///
71/// // Extract owner_id from request and compare with user.id()
72/// // This is a simplified example
73/// true
74/// }
75/// }
76/// ```
77#[async_trait]
78pub trait Permission: Send + Sync {
79 /// Checks if the user has permission to perform the action
80 ///
81 /// Returns `true` if permission is granted, `false` otherwise.
82 async fn has_permission(&self, context: &PermissionContext<'_>) -> bool;
83}
84
85/// AllowAny - grants permission to all requests
86///
87/// This permission class allows unrestricted access. Use with caution.
88///
89/// # Examples
90///
91/// ```
92/// use reinhardt_auth::{Permission, AllowAny, PermissionContext};
93/// use reinhardt_http::Request;
94/// use hyper::{Method, Uri, Version, header::HeaderMap};
95/// use bytes::Bytes;
96///
97/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
98/// let permission = AllowAny;
99/// let request = Request::builder()
100/// .method(Method::GET)
101/// .uri("/")
102/// .version(Version::HTTP_11)
103/// .headers(HeaderMap::new())
104/// .body(Bytes::new())
105/// .build()
106/// .unwrap();
107/// let context = PermissionContext {
108/// request: &request,
109/// is_authenticated: false,
110/// is_admin: false,
111/// is_active: false,
112/// user: None,
113/// };
114///
115/// assert!(permission.has_permission(&context).await);
116/// # });
117/// ```
118#[derive(Clone, Copy, Default)]
119pub struct AllowAny;
120
121#[async_trait]
122impl Permission for AllowAny {
123 async fn has_permission(&self, _context: &PermissionContext<'_>) -> bool {
124 true
125 }
126}
127
128/// IsAuthenticated - requires the user to be authenticated
129///
130/// # Examples
131///
132/// ```
133/// use reinhardt_auth::{Permission, IsAuthenticated, PermissionContext, SimpleUser, User};
134/// use reinhardt_http::Request;
135/// use hyper::{Method, Uri, Version, header::HeaderMap};
136/// use bytes::Bytes;
137/// use uuid::Uuid;
138///
139/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
140/// let permission = IsAuthenticated;
141/// let request = Request::builder()
142/// .method(Method::GET)
143/// .uri("/")
144/// .version(Version::HTTP_11)
145/// .headers(HeaderMap::new())
146/// .body(Bytes::new())
147/// .build()
148/// .unwrap();
149///
150/// // Anonymous user - permission denied
151/// let context = PermissionContext {
152/// request: &request,
153/// is_authenticated: false,
154/// is_admin: false,
155/// is_active: false,
156/// user: None,
157/// };
158/// assert!(!permission.has_permission(&context).await);
159///
160/// // Authenticated user - permission granted
161/// let user = SimpleUser {
162/// id: Uuid::new_v4(),
163/// username: "alice".to_string(),
164/// email: "alice@example.com".to_string(),
165/// is_active: true,
166/// is_admin: false,
167/// is_staff: false,
168/// is_superuser: false,
169/// };
170/// let context = PermissionContext {
171/// request: &request,
172/// is_authenticated: true,
173/// is_admin: false,
174/// is_active: true,
175/// user: Some(Box::new(user)),
176/// };
177/// assert!(permission.has_permission(&context).await);
178/// # });
179/// ```
180#[derive(Clone, Copy, Default)]
181pub struct IsAuthenticated;
182
183#[async_trait]
184impl Permission for IsAuthenticated {
185 async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
186 context.is_authenticated
187 }
188}
189
190/// IsAdminUser - requires the user to be an admin
191///
192/// # Examples
193///
194/// ```
195/// use reinhardt_auth::{Permission, IsAdminUser, PermissionContext, SimpleUser, User};
196/// use reinhardt_http::Request;
197/// use hyper::{Method, Uri, Version, header::HeaderMap};
198/// use bytes::Bytes;
199/// use uuid::Uuid;
200///
201/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
202/// let permission = IsAdminUser;
203/// let request = Request::builder()
204/// .method(Method::GET)
205/// .uri("/")
206/// .version(Version::HTTP_11)
207/// .headers(HeaderMap::new())
208/// .body(Bytes::new())
209/// .build()
210/// .unwrap();
211///
212/// // Non-admin user - permission denied
213/// let user = SimpleUser {
214/// id: Uuid::new_v4(),
215/// username: "alice".to_string(),
216/// email: "alice@example.com".to_string(),
217/// is_active: true,
218/// is_admin: false,
219/// is_staff: false,
220/// is_superuser: false,
221/// };
222/// let context = PermissionContext {
223/// request: &request,
224/// is_authenticated: true,
225/// is_admin: false,
226/// is_active: true,
227/// user: Some(Box::new(user)),
228/// };
229/// assert!(!permission.has_permission(&context).await);
230///
231/// // Admin user - permission granted
232/// let admin = SimpleUser {
233/// id: Uuid::new_v4(),
234/// username: "admin".to_string(),
235/// email: "admin@example.com".to_string(),
236/// is_active: true,
237/// is_admin: true,
238/// is_staff: true,
239/// is_superuser: true,
240/// };
241/// let context = PermissionContext {
242/// request: &request,
243/// is_authenticated: true,
244/// is_admin: true,
245/// is_active: true,
246/// user: Some(Box::new(admin)),
247/// };
248/// assert!(permission.has_permission(&context).await);
249/// # });
250/// ```
251pub struct IsAdminUser;
252
253#[async_trait]
254impl Permission for IsAdminUser {
255 async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
256 context.is_authenticated && context.is_admin
257 }
258}
259
260/// IsActiveUser - requires the user to be active
261///
262/// # Examples
263///
264/// ```
265/// use reinhardt_auth::{Permission, IsActiveUser, PermissionContext};
266/// use reinhardt_http::Request;
267/// use hyper::{Method, Uri, Version, header::HeaderMap};
268/// use bytes::Bytes;
269///
270/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
271/// let permission = IsActiveUser;
272/// let request = Request::builder()
273/// .method(Method::GET)
274/// .uri("/")
275/// .version(Version::HTTP_11)
276/// .headers(HeaderMap::new())
277/// .body(Bytes::new())
278/// .build()
279/// .unwrap();
280///
281/// // Inactive user - permission denied
282/// let context = PermissionContext {
283/// request: &request,
284/// is_authenticated: true,
285/// is_admin: false,
286/// is_active: false,
287/// user: None,
288/// };
289/// assert!(!permission.has_permission(&context).await);
290///
291/// // Active user - permission granted
292/// let context = PermissionContext {
293/// request: &request,
294/// is_authenticated: true,
295/// is_admin: false,
296/// is_active: true,
297/// user: None,
298/// };
299/// assert!(permission.has_permission(&context).await);
300/// # });
301/// ```
302pub struct IsActiveUser;
303
304#[async_trait]
305impl Permission for IsActiveUser {
306 async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
307 context.is_authenticated && context.is_active
308 }
309}
310
311/// IsAuthenticatedOrReadOnly - authenticated for writes, allow reads
312///
313/// This permission allows read-only access (GET, HEAD, OPTIONS) to all users,
314/// but requires authentication for write operations (POST, PUT, PATCH, DELETE).
315///
316/// # Examples
317///
318/// ```
319/// use reinhardt_auth::{Permission, IsAuthenticatedOrReadOnly, PermissionContext};
320/// use reinhardt_http::Request;
321/// use hyper::{Method, Uri, Version, header::HeaderMap};
322/// use bytes::Bytes;
323///
324/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
325/// let permission = IsAuthenticatedOrReadOnly;
326///
327/// // GET request from anonymous user - allowed
328/// let mut request = Request::builder()
329/// .method(Method::GET)
330/// .uri("/")
331/// .version(Version::HTTP_11)
332/// .headers(HeaderMap::new())
333/// .body(Bytes::new())
334/// .build()
335/// .unwrap();
336/// request.method = Method::GET;
337/// let context = PermissionContext {
338/// request: &request,
339/// is_authenticated: false,
340/// is_admin: false,
341/// is_active: false,
342/// user: None,
343/// };
344/// assert!(permission.has_permission(&context).await);
345///
346/// // POST request from anonymous user - denied
347/// request.method = Method::POST;
348/// let context = PermissionContext {
349/// request: &request,
350/// is_authenticated: false,
351/// is_admin: false,
352/// is_active: false,
353/// user: None,
354/// };
355/// assert!(!permission.has_permission(&context).await);
356///
357/// // POST request from authenticated user - allowed
358/// let context = PermissionContext {
359/// request: &request,
360/// is_authenticated: true,
361/// is_admin: false,
362/// is_active: true,
363/// user: None,
364/// };
365/// assert!(permission.has_permission(&context).await);
366/// # });
367/// ```
368#[derive(Clone, Copy, Default)]
369pub struct IsAuthenticatedOrReadOnly;
370
371#[async_trait]
372impl Permission for IsAuthenticatedOrReadOnly {
373 async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
374 use hyper::Method;
375
376 // Allow read-only methods
377 matches!(
378 context.request.method,
379 Method::GET | Method::HEAD | Method::OPTIONS
380 ) || context.is_authenticated
381 }
382}