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}