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}