kit_rs/auth/guard.rs
1//! Authentication guard (facade)
2
3use std::sync::Arc;
4
5use crate::container::App;
6use crate::session::{
7 auth_user_id, clear_auth_user, generate_csrf_token, regenerate_session_id, session_mut,
8 set_auth_user,
9};
10
11use super::authenticatable::Authenticatable;
12use super::provider::UserProvider;
13
14/// Authentication facade
15///
16/// Provides Laravel-like static methods for authentication operations.
17///
18/// # Example
19///
20/// ```rust,ignore
21/// use kit::Auth;
22///
23/// // Check if authenticated
24/// if Auth::check() {
25/// let user_id = Auth::id().unwrap();
26/// }
27///
28/// // Log in
29/// Auth::login(user_id);
30///
31/// // Log out
32/// Auth::logout();
33/// ```
34pub struct Auth;
35
36impl Auth {
37 /// Get the authenticated user's ID
38 ///
39 /// Returns None if not authenticated.
40 pub fn id() -> Option<i64> {
41 auth_user_id()
42 }
43
44 /// Check if a user is currently authenticated
45 pub fn check() -> bool {
46 Self::id().is_some()
47 }
48
49 /// Check if the current user is a guest (not authenticated)
50 pub fn guest() -> bool {
51 !Self::check()
52 }
53
54 /// Log in a user by their ID
55 ///
56 /// This sets the user ID in the session, making them authenticated.
57 ///
58 /// # Security
59 ///
60 /// This method regenerates the session ID to prevent session fixation attacks.
61 pub fn login(user_id: i64) {
62 // Regenerate session ID to prevent session fixation
63 regenerate_session_id();
64
65 // Set the authenticated user
66 set_auth_user(user_id);
67
68 // Regenerate CSRF token for extra security
69 session_mut(|session| {
70 session.csrf_token = generate_csrf_token();
71 });
72 }
73
74 /// Log in a user with "remember me" functionality
75 ///
76 /// This extends the session lifetime for persistent login.
77 ///
78 /// # Arguments
79 ///
80 /// * `user_id` - The user's ID
81 /// * `remember_token` - A secure token for remember me cookie
82 pub fn login_remember(user_id: i64, _remember_token: &str) {
83 // For now, just do a regular login
84 // Remember me cookie handling is done in the controller
85 Self::login(user_id);
86 }
87
88 /// Log out the current user
89 ///
90 /// Clears the authenticated user from the session.
91 ///
92 /// # Security
93 ///
94 /// This regenerates the CSRF token to prevent any cached tokens from being reused.
95 pub fn logout() {
96 // Clear the authenticated user
97 clear_auth_user();
98
99 // Regenerate CSRF token for security
100 session_mut(|session| {
101 session.csrf_token = generate_csrf_token();
102 });
103 }
104
105 /// Log out and invalidate the entire session
106 ///
107 /// Use this for complete session destruction (e.g., "logout everywhere").
108 pub fn logout_and_invalidate() {
109 session_mut(|session| {
110 session.flush();
111 session.csrf_token = generate_csrf_token();
112 });
113 }
114
115 /// Attempt to authenticate with a validator function
116 ///
117 /// The validator function should return the user ID if credentials are valid.
118 ///
119 /// # Example
120 ///
121 /// ```rust,ignore
122 /// let user_id = Auth::attempt(async {
123 /// // Validate credentials
124 /// let user = User::find_by_email(&email).await?;
125 /// if user.verify_password(&password)? {
126 /// Ok(Some(user.id))
127 /// } else {
128 /// Ok(None)
129 /// }
130 /// }).await?;
131 ///
132 /// if let Some(id) = user_id {
133 /// // Authentication successful
134 /// }
135 /// ```
136 pub async fn attempt<F, Fut>(validator: F) -> Result<Option<i64>, crate::error::FrameworkError>
137 where
138 F: FnOnce() -> Fut,
139 Fut: std::future::Future<Output = Result<Option<i64>, crate::error::FrameworkError>>,
140 {
141 let result = validator().await?;
142 if let Some(user_id) = result {
143 Self::login(user_id);
144 }
145 Ok(result)
146 }
147
148 /// Validate credentials without logging in
149 ///
150 /// Useful for password confirmation dialogs.
151 pub async fn validate<F, Fut>(validator: F) -> Result<bool, crate::error::FrameworkError>
152 where
153 F: FnOnce() -> Fut,
154 Fut: std::future::Future<Output = Result<bool, crate::error::FrameworkError>>,
155 {
156 validator().await
157 }
158
159 /// Get the currently authenticated user
160 ///
161 /// Returns `None` if not authenticated or if no `UserProvider` is registered.
162 ///
163 /// # Example
164 ///
165 /// ```rust,ignore
166 /// use kit::Auth;
167 ///
168 /// if let Some(user) = Auth::user().await? {
169 /// println!("Logged in as user {}", user.auth_identifier());
170 /// }
171 /// ```
172 ///
173 /// # Errors
174 ///
175 /// Returns an error if no `UserProvider` is registered in the container.
176 /// Make sure to register a `UserProvider` in your `bootstrap.rs`:
177 ///
178 /// ```rust,ignore
179 /// bind!(dyn UserProvider, DatabaseUserProvider);
180 /// ```
181 pub async fn user() -> Result<Option<Arc<dyn Authenticatable>>, crate::error::FrameworkError> {
182 let user_id = match Self::id() {
183 Some(id) => id,
184 None => return Ok(None),
185 };
186
187 let provider = App::make::<dyn UserProvider>().ok_or_else(|| {
188 crate::error::FrameworkError::internal(
189 "No UserProvider registered. Register one in bootstrap.rs with: \
190 bind!(dyn UserProvider, YourUserProvider)"
191 .to_string(),
192 )
193 })?;
194
195 provider.retrieve_by_id(user_id).await
196 }
197
198 /// Get the authenticated user, cast to a concrete type
199 ///
200 /// This is a convenience method that retrieves the user and downcasts
201 /// it to your concrete User type.
202 ///
203 /// # Example
204 ///
205 /// ```rust,ignore
206 /// use kit::Auth;
207 /// use crate::models::users::User;
208 ///
209 /// if let Some(user) = Auth::user_as::<User>().await? {
210 /// println!("Welcome, user #{}!", user.id);
211 /// }
212 /// ```
213 ///
214 /// # Type Parameters
215 ///
216 /// * `T` - The concrete user type that implements `Authenticatable` and `Clone`
217 pub async fn user_as<T: Authenticatable + Clone>(
218 ) -> Result<Option<T>, crate::error::FrameworkError> {
219 let user = Self::user().await?;
220 Ok(user.and_then(|u| u.as_any().downcast_ref::<T>().cloned()))
221 }
222}