Skip to main content

rainier_auth/
middleware.rs

1//! The [`Authenticate`] middleware — `->middleware("auth")`.
2//!
3//! Resolves the user once, puts them in the request's extensions, and rejects
4//! the request if there is nobody there. Doing it in middleware rather than in
5//! each controller means the guard runs exactly once per request, and a handler
6//! that is behind `auth` can read [`AuthenticatedUser`] without re-checking.
7
8use std::sync::Arc;
9
10use rainier_http::{FromRequest, IntoResponse, Request, Response};
11use rainier_middleware::{Middleware, MiddlewareStack, Next};
12use rainier_support::{BoxedFuture, Error, Result};
13
14use crate::abilities::Abilities;
15use crate::guard::AuthManager;
16use crate::user::Authenticatable;
17
18/// The user resolved for this request, stored in its extensions.
19///
20/// A newtype rather than a bare `U` so it cannot be confused with any other
21/// value of the same type an application might attach.
22#[derive(Debug)]
23pub struct AuthenticatedUser<U>(pub Arc<U>);
24
25/// Cloning shares the user rather than copying it, so this holds whatever `U`
26/// is — a derived `Clone` would have demanded `U: Clone` for no reason.
27impl<U> Clone for AuthenticatedUser<U> {
28    fn clone(&self) -> Self {
29        Self(Arc::clone(&self.0))
30    }
31}
32
33impl<U> AuthenticatedUser<U> {
34    /// The user.
35    pub fn get(&self) -> &U {
36        &self.0
37    }
38}
39
40impl<U> std::ops::Deref for AuthenticatedUser<U> {
41    type Target = U;
42
43    fn deref(&self) -> &U {
44        &self.0
45    }
46}
47
48/// The authenticated user, as a **controller parameter**.
49///
50/// The framework supplies it, which is why an action can read
51///
52/// ```ignore
53/// pub async fn me(user: AuthenticatedUser<User>) -> Result<Response> {
54///     Ok(Response::json(user.get()))
55/// }
56/// ```
57///
58/// rather than taking the whole request and digging the user out of it.
59///
60/// Fails with `401` when there is none — which can only happen on a route with
61/// no [`Authenticate`] middleware in front, so the message says so rather than
62/// blaming the caller for a wiring mistake.
63impl<U: Send + Sync + 'static> FromRequest for AuthenticatedUser<U> {
64    fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
65        Box::pin(async move {
66            request
67                .extension::<AuthenticatedUser<U>>()
68                .cloned()
69                .ok_or_else(|| Error::unauthenticated("Unauthenticated."))
70        })
71    }
72}
73
74/// Rejects unauthenticated requests, and attaches the user to authenticated
75/// ones.
76pub struct Authenticate<U: Authenticatable> {
77    auth: Arc<AuthManager<U>>,
78    /// The guard to use, or `None` for the manager's default — this is the
79    /// `api` in `->middleware("auth:api")`.
80    guard: Option<String>,
81}
82
83impl<U: Authenticatable> Authenticate<U> {
84    /// Authenticate with the manager's default guard.
85    pub fn new(auth: Arc<AuthManager<U>>) -> Self {
86        Self { auth, guard: None }
87    }
88
89    /// Authenticate with a named guard.
90    pub fn with_guard(auth: Arc<AuthManager<U>>, guard: impl Into<String>) -> Self {
91        Self { auth, guard: Some(guard.into()) }
92    }
93
94    /// A stack that builds this middleware from the container.
95    ///
96    /// Routes are declared before the container is populated, so the
97    /// `AuthManager` cannot be handed over at declaration time. This defers the
98    /// resolve to when the router compiles:
99    ///
100    /// ```ignore
101    /// router.get("/me", me).middleware(Authenticate::<User>::resolved());
102    /// ```
103    ///
104    /// The `auth` middleware, attached by value. The user type is
105    /// named explicitly, which is the thing a string alias could never say.
106    pub fn resolved() -> MiddlewareStack {
107        MiddlewareStack::new().resolved(|auth: Arc<AuthManager<U>>| Self::new(auth))
108    }
109
110    /// [`resolved`](Self::resolved) with a named guard — `->middleware('auth:api')`.
111    pub fn resolved_with_guard(guard: impl Into<String>) -> MiddlewareStack {
112        let guard = guard.into();
113        MiddlewareStack::new()
114            .resolved(move |auth: Arc<AuthManager<U>>| Self::with_guard(auth, guard.clone()))
115    }
116}
117
118#[async_trait::async_trait]
119impl<U: Authenticatable> Middleware for Authenticate<U> {
120    async fn handle(&self, mut request: Request, next: Next) -> Response {
121        let resolved = match &self.guard {
122            Some(name) => self.auth.user_via(name, &request).await,
123            None => self.auth.user(&request).await,
124        };
125
126        match resolved {
127            Ok(Some(user)) => {
128                // The abilities as well as the identity: a route behind
129                // `auth` may still need to know the *token* was issued for
130                // less than its owner can do. Resolved here rather than in
131                // `RequireAbility`, so the guard is asked once.
132                let abilities = match &self.guard {
133                    Some(name) => self.auth.abilities_via(name, &request).await,
134                    None => self.auth.abilities(&request).await,
135                };
136
137                match abilities {
138                    Ok(abilities) => {
139                        request.extensions_mut().insert(TokenAbilities(abilities));
140                    }
141                    Err(e) => {
142                        tracing::error!(error = %e, "could not read the token's abilities");
143                        return e.into_response();
144                    }
145                }
146
147                request.extensions_mut().insert(AuthenticatedUser(Arc::new(user)));
148                next.run(request).await
149            }
150            Ok(None) => Error::unauthenticated("Unauthenticated.").into_response(),
151            // A guard failure is a 500, not a 401: the user may well be
152            // authenticated, and telling them they are not would be a lie that
153            // sends them to a login page which will also fail.
154            Err(e) => {
155                tracing::error!(error = %e, "the authentication guard failed");
156                e.into_response()
157            }
158        }
159    }
160
161    fn name(&self) -> &'static str {
162        "Authenticate"
163    }
164}
165
166/// What the token behind this request was issued to do.
167///
168/// Put on the request by [`Authenticate`], so a handler or a later middleware
169/// can read it without asking the guard again.
170///
171/// **Absent means unauthenticated**, not unrestricted. A route that has not
172/// been through `auth` has no abilities on it at all, and
173/// [`RequireAbility`] refuses rather than assuming.
174#[derive(Debug, Clone)]
175pub struct TokenAbilities(pub Abilities);
176
177impl std::ops::Deref for TokenAbilities {
178    type Target = Abilities;
179
180    fn deref(&self) -> &Self::Target {
181        &self.0
182    }
183}
184
185#[async_trait::async_trait]
186impl FromRequest for TokenAbilities {
187    fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
188        Box::pin(async move {
189            request.extension::<TokenAbilities>().cloned().ok_or_else(|| {
190                Error::internal(
191                    "no token abilities on the request — is this route behind the `auth` \
192                     middleware?",
193                )
194            })
195        })
196    }
197}
198
199/// Reading the abilities off a request.
200pub trait AbilitiesRequestExt {
201    /// What this request's token may do, if it went through [`Authenticate`].
202    fn token_abilities(&self) -> Option<&Abilities>;
203
204    /// Whether this request's token may `ability`.
205    ///
206    /// `false` for a request that never authenticated — see
207    /// [`TokenAbilities`].
208    fn token_can(&self, ability: &str) -> bool;
209}
210
211impl AbilitiesRequestExt for Request {
212    fn token_abilities(&self) -> Option<&Abilities> {
213        self.extension::<TokenAbilities>().map(|abilities| &abilities.0)
214    }
215
216    fn token_can(&self, ability: &str) -> bool {
217        self.token_abilities().is_some_and(|abilities| abilities.can(ability))
218    }
219}
220
221/// Refuses a request whose token was not issued for what it is asking.
222///
223/// ```ignore
224/// router.post("/api/posts", store)
225///     .middleware((Authenticate::<User>::resolved(), RequireAbility::any(["posts:write"])));
226/// ```
227///
228/// Order matters: it must run **after** [`Authenticate`], which is what puts
229/// the abilities on the request. Before it, there is nothing to read and this
230/// refuses everything — loudly rather than silently, because a guard that
231/// passes when it is misordered is not a guard.
232#[derive(Debug, Clone)]
233pub struct RequireAbility {
234    required: Vec<String>,
235    /// Whether every ability is needed, or any one of them.
236    all: bool,
237}
238
239impl RequireAbility {
240    /// Require **any one** of these.
241    ///
242    /// The usual shape: several ability names that each permit this endpoint.
243    pub fn any(abilities: impl IntoIterator<Item = impl Into<String>>) -> Self {
244        Self { required: abilities.into_iter().map(Into::into).collect(), all: false }
245    }
246
247    /// Require **all** of these.
248    pub fn all(abilities: impl IntoIterator<Item = impl Into<String>>) -> Self {
249        Self { required: abilities.into_iter().map(Into::into).collect(), all: true }
250    }
251
252    /// What it requires.
253    pub fn required(&self) -> &[String] {
254        &self.required
255    }
256
257    fn satisfied_by(&self, abilities: &Abilities) -> bool {
258        let required = self.required.iter().map(String::as_str);
259
260        if self.all {
261            abilities.can_all(required)
262        } else {
263            abilities.can_any(required)
264        }
265    }
266}
267
268#[async_trait::async_trait]
269impl Middleware for RequireAbility {
270    async fn handle(&self, request: Request, next: Next) -> Response {
271        let Some(abilities) = request.token_abilities() else {
272            // Misordered, or the route is not behind `auth` at all. Either
273            // way this cannot make a decision, and the safe answer is no.
274            tracing::error!(
275                required = ?self.required,
276                "`RequireAbility` ran without `Authenticate` before it, so the request has no \
277                 abilities to check — refusing"
278            );
279            return Error::unauthorized("This action is unauthorized.").into_response();
280        };
281
282        if !self.satisfied_by(abilities) {
283            return Error::unauthorized(format!(
284                "This token is not allowed to {}.",
285                self.required.join(" or ")
286            ))
287            .into_response();
288        }
289
290        next.run(request).await
291    }
292
293    fn name(&self) -> &'static str {
294        "RequireAbility"
295    }
296}
297
298/// Rejects *authenticated* requests — the `guest` middleware, for a login page
299/// an already-logged-in user should not see.
300pub struct RedirectIfAuthenticated<U: Authenticatable> {
301    auth: Arc<AuthManager<U>>,
302    to: String,
303}
304
305impl<U: Authenticatable> RedirectIfAuthenticated<U> {
306    /// Send authenticated users to `to`.
307    pub fn new(auth: Arc<AuthManager<U>>, to: impl Into<String>) -> Self {
308        Self { auth, to: to.into() }
309    }
310}
311
312#[async_trait::async_trait]
313impl<U: Authenticatable> Middleware for RedirectIfAuthenticated<U> {
314    async fn handle(&self, request: Request, next: Next) -> Response {
315        match self.auth.user(&request).await {
316            Ok(Some(_)) => rainier_http::Redirect::to(self.to.clone()).into_response(),
317            // A guard failure here is not a reason to block a login attempt —
318            // failing open to the login page is the safe direction.
319            Ok(None) | Err(_) => next.run(request).await,
320        }
321    }
322
323    fn name(&self) -> &'static str {
324        "RedirectIfAuthenticated"
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331    use crate::guard::TokenGuard;
332    use crate::user::{Credentials, UserProvider};
333    use rainier_http::StatusCode;
334    use rainier_middleware::Pipeline;
335    use rainier_support::Result;
336
337    #[derive(Debug, Clone, PartialEq)]
338    struct User {
339        id: u64,
340    }
341
342    impl Authenticatable for User {
343        fn auth_identifier(&self) -> String {
344            self.id.to_string()
345        }
346        fn auth_password_hash(&self) -> Option<&str> {
347            None
348        }
349    }
350
351    struct Provider {
352        fail: bool,
353    }
354
355    #[async_trait::async_trait]
356    impl UserProvider<User> for Provider {
357        async fn retrieve_by_id(&self, _: &str) -> Result<Option<User>> {
358            Ok(None)
359        }
360        async fn retrieve_by_credentials(&self, _: &Credentials) -> Result<Option<User>> {
361            Ok(None)
362        }
363        async fn validate_credentials(&self, _: &User, _: &Credentials) -> Result<bool> {
364            Ok(false)
365        }
366        async fn retrieve_by_token(&self, token: &str) -> Result<Option<User>> {
367            if self.fail {
368                return Err(Error::internal("database is down"));
369            }
370            Ok((token == "good").then_some(User { id: 7 }))
371        }
372    }
373
374    fn manager(fail: bool) -> Arc<AuthManager<User>> {
375        Arc::new(
376            AuthManager::new("api")
377                .register(Arc::new(TokenGuard::new("api", Arc::new(Provider { fail })))),
378        )
379    }
380
381    async fn reveal_user(request: Request) -> Response {
382        match request.extension::<AuthenticatedUser<User>>() {
383            Some(user) => Response::text(user.id.to_string()),
384            None => Response::text("anonymous"),
385        }
386    }
387
388    async fn body_of(response: Response) -> String {
389        String::from_utf8(response.into_http().into_body().collect().await.unwrap().to_vec())
390            .unwrap()
391    }
392
393    fn bearer(token: &str) -> Request {
394        Request::builder().header("authorization", &format!("Bearer {token}")).build()
395    }
396
397    #[tokio::test]
398    async fn an_authenticated_request_reaches_the_handler_with_its_user() {
399        let response = Pipeline::new()
400            .through(Authenticate::new(manager(false)))
401            .then(reveal_user)
402            .run(bearer("good"))
403            .await;
404
405        assert_eq!(response.status(), StatusCode::OK);
406        assert_eq!(body_of(response).await, "7");
407    }
408
409    #[tokio::test]
410    async fn an_unauthenticated_request_is_rejected_before_the_handler() {
411        let response = Pipeline::new()
412            .through(Authenticate::new(manager(false)))
413            .then(|_: Request| async { panic!("the handler must not run") })
414            .run(bearer("bad"))
415            .await;
416
417        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
418    }
419
420    #[tokio::test]
421    async fn a_guard_failure_is_a_500_not_a_401() {
422        // Telling an authenticated user they are not sends them to a login
423        // page that will also fail. Report the outage instead.
424        let response = Pipeline::new()
425            .through(Authenticate::new(manager(true)))
426            .then(reveal_user)
427            .run(bearer("good"))
428            .await;
429
430        assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
431    }
432
433    #[tokio::test]
434    async fn a_named_guard_can_be_selected() {
435        let middleware = Authenticate::with_guard(manager(false), "api");
436        let response =
437            Pipeline::new().through(middleware).then(reveal_user).run(bearer("good")).await;
438
439        assert_eq!(body_of(response).await, "7");
440    }
441
442    #[tokio::test]
443    async fn an_unknown_named_guard_fails_loudly() {
444        let middleware = Authenticate::with_guard(manager(false), "nope");
445        let response =
446            Pipeline::new().through(middleware).then(reveal_user).run(bearer("good")).await;
447
448        assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
449    }
450
451    #[tokio::test]
452    async fn the_guest_middleware_redirects_an_authenticated_user() {
453        let response = Pipeline::new()
454            .through(RedirectIfAuthenticated::new(manager(false), "/dashboard"))
455            .then(|_: Request| async { Response::text("login form") })
456            .run(bearer("good"))
457            .await;
458
459        assert_eq!(response.status(), StatusCode::FOUND);
460        assert_eq!(response.header("location"), Some("/dashboard"));
461    }
462
463    #[tokio::test]
464    async fn the_guest_middleware_lets_a_guest_through() {
465        let response = Pipeline::new()
466            .through(RedirectIfAuthenticated::new(manager(false), "/dashboard"))
467            .then(|_: Request| async { Response::text("login form") })
468            .run(Request::builder().build())
469            .await;
470
471        assert_eq!(body_of(response).await, "login form");
472    }
473
474    #[tokio::test]
475    async fn the_guest_middleware_fails_open_to_the_login_page() {
476        // If the guard is broken, blocking the login page helps nobody.
477        let response = Pipeline::new()
478            .through(RedirectIfAuthenticated::new(manager(true), "/dashboard"))
479            .then(|_: Request| async { Response::text("login form") })
480            .run(bearer("good"))
481            .await;
482
483        assert_eq!(body_of(response).await, "login form");
484    }
485}