Skip to main content

larust_auth/
authenticatable.rs

1use larust_core::AppError;
2
3/// Implemented by an app's `User` model (or whatever it calls its
4/// authenticatable type) so `larust_support::auth`'s guard functions and
5/// the [`crate::Auth`] extractor can look a user up from the id stored in
6/// the session.
7///
8/// `find_for_auth`'s signature deliberately matches what `#[derive(Model)]`
9/// already generates (`Self::find(id: i64) -> Result<Option<Self>,
10/// AppError>` - see `larust-macros`' `model.rs`), so a typical impl is a
11/// two-line delegation:
12///
13/// ```ignore
14/// impl larust_support::auth::Authenticatable for User {
15///     fn auth_id(&self) -> i64 { self.id }
16///     async fn find_for_auth(id: i64) -> Result<Option<Self>, AppError> {
17///         Self::find(id).await
18///     }
19/// }
20/// ```
21///
22/// Declared as `-> impl Future<...> + Send` rather than a plain `async fn` -
23/// native async-fn-in-traits doesn't propagate auto-trait bounds on the
24/// returned future by default, and the [`crate::Auth`] extractor's
25/// `FromRequestParts` impl (itself required to return a `Send` future by
26/// axum-core's own `#[async_trait]` declaration - see GOTCHAS.md) calls
27/// this through an `async` block that needs to stay `Send`. This doesn't
28/// change how an implementation looks: implementing this method with a
29/// plain `async fn find_for_auth(id: i64) -> Result<Option<Self>, AppError>
30/// { Self::find(id).await }` still works, since `async fn`'s desugared
31/// return type already satisfies `-> impl Future<...>` - only the trait's
32/// own declaration needs the explicit spelling.
33///
34/// The two-line delegation above is the easy path for a `#[derive(Model)]`
35/// (SQL-family) app, but it isn't the only path - this trait has zero
36/// dependency on `#[derive(Model)]`, `larust-orm`, or SQL of any kind, so a
37/// user model backed by something else entirely (a document store like
38/// Firestore, an in-memory cache, a call to another service) implements it
39/// exactly the same way, just with a different lookup body:
40///
41/// ```ignore
42/// impl larust_support::auth::Authenticatable for User {
43///     fn auth_id(&self) -> i64 { self.id }
44///     async fn find_for_auth(id: i64) -> Result<Option<Self>, AppError> {
45///         // No `#[derive(Model)]` involved - e.g. a hand-written lookup
46///         // against a `larust_support::repository::Repository` impl, or
47///         // any other store this type knows how to query:
48///         MY_USER_REPOSITORY.find(id).await
49///     }
50/// }
51/// ```
52pub trait Authenticatable: Send + Sync + Sized + 'static {
53    /// This type's guard name (Laravel's `guard('admin')`) - each distinct
54    /// name gets its own session slot, so two `Authenticatable` types (say,
55    /// `User` and `Admin`) can be logged in independently in the same
56    /// browser session rather than overwriting each other's login state.
57    ///
58    /// Defaults to `"web"`, matching Laravel's own default guard name, so a
59    /// single-guard app never has to think about this - and upgrading an
60    /// existing single-guard app to a version of this framework with
61    /// multi-guard support doesn't invalidate already-logged-in sessions
62    /// (see `larust_auth::guard`'s own doc comment for why `"web"` is
63    /// special-cased to the original session key).
64    ///
65    /// A second `Authenticatable` type that forgets to override this
66    /// collides with the first on the same `"web"` session slot - there's
67    /// no compile-time guard against that (a marker trait can't know how
68    /// many other types implement itself), so name every guard beyond the
69    /// first explicitly.
70    const GUARD: &'static str = "web";
71
72    /// The value stored in the session and used to look the user back up
73    /// on a later request - typically the primary key.
74    fn auth_id(&self) -> i64;
75
76    /// Looks up a user by the id [`Authenticatable::auth_id`] returned at
77    /// login time. Returns `Ok(None)` (not an error) if the id no longer
78    /// resolves to a real user - e.g. the account was deleted after login.
79    fn find_for_auth(
80        id: i64,
81    ) -> impl std::future::Future<Output = Result<Option<Self>, AppError>> + Send;
82}