rainier_auth/
middleware.rs1use 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::guard::AuthManager;
15use crate::user::Authenticatable;
16
17#[derive(Debug)]
22pub struct AuthenticatedUser<U>(pub Arc<U>);
23
24impl<U> Clone for AuthenticatedUser<U> {
27 fn clone(&self) -> Self {
28 Self(Arc::clone(&self.0))
29 }
30}
31
32impl<U> AuthenticatedUser<U> {
33 pub fn get(&self) -> &U {
35 &self.0
36 }
37}
38
39impl<U> std::ops::Deref for AuthenticatedUser<U> {
40 type Target = U;
41
42 fn deref(&self) -> &U {
43 &self.0
44 }
45}
46
47impl<U: Send + Sync + 'static> FromRequest for AuthenticatedUser<U> {
63 fn from_request(request: Arc<Request>) -> BoxedFuture<Result<Self>> {
64 Box::pin(async move {
65 request
66 .extension::<AuthenticatedUser<U>>()
67 .cloned()
68 .ok_or_else(|| Error::unauthenticated("Unauthenticated."))
69 })
70 }
71}
72
73pub struct Authenticate<U: Authenticatable> {
76 auth: Arc<AuthManager<U>>,
77 guard: Option<String>,
80}
81
82impl<U: Authenticatable> Authenticate<U> {
83 pub fn new(auth: Arc<AuthManager<U>>) -> Self {
85 Self { auth, guard: None }
86 }
87
88 pub fn with_guard(auth: Arc<AuthManager<U>>, guard: impl Into<String>) -> Self {
90 Self { auth, guard: Some(guard.into()) }
91 }
92
93 pub fn resolved() -> MiddlewareStack {
106 MiddlewareStack::new().resolved(|auth: Arc<AuthManager<U>>| Self::new(auth))
107 }
108
109 pub fn resolved_with_guard(guard: impl Into<String>) -> MiddlewareStack {
111 let guard = guard.into();
112 MiddlewareStack::new()
113 .resolved(move |auth: Arc<AuthManager<U>>| Self::with_guard(auth, guard.clone()))
114 }
115}
116
117#[async_trait::async_trait]
118impl<U: Authenticatable> Middleware for Authenticate<U> {
119 async fn handle(&self, mut request: Request, next: Next) -> Response {
120 let resolved = match &self.guard {
121 Some(name) => self.auth.user_via(name, &request).await,
122 None => self.auth.user(&request).await,
123 };
124
125 match resolved {
126 Ok(Some(user)) => {
127 request.extensions_mut().insert(AuthenticatedUser(Arc::new(user)));
128 next.run(request).await
129 }
130 Ok(None) => Error::unauthenticated("Unauthenticated.").into_response(),
131 Err(e) => {
135 tracing::error!(error = %e, "the authentication guard failed");
136 e.into_response()
137 }
138 }
139 }
140
141 fn name(&self) -> &'static str {
142 "Authenticate"
143 }
144}
145
146pub struct RedirectIfAuthenticated<U: Authenticatable> {
149 auth: Arc<AuthManager<U>>,
150 to: String,
151}
152
153impl<U: Authenticatable> RedirectIfAuthenticated<U> {
154 pub fn new(auth: Arc<AuthManager<U>>, to: impl Into<String>) -> Self {
156 Self { auth, to: to.into() }
157 }
158}
159
160#[async_trait::async_trait]
161impl<U: Authenticatable> Middleware for RedirectIfAuthenticated<U> {
162 async fn handle(&self, request: Request, next: Next) -> Response {
163 match self.auth.user(&request).await {
164 Ok(Some(_)) => rainier_http::Redirect::to(self.to.clone()).into_response(),
165 Ok(None) | Err(_) => next.run(request).await,
168 }
169 }
170
171 fn name(&self) -> &'static str {
172 "RedirectIfAuthenticated"
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use crate::guard::TokenGuard;
180 use crate::user::{Credentials, UserProvider};
181 use rainier_http::StatusCode;
182 use rainier_middleware::Pipeline;
183 use rainier_support::Result;
184
185 #[derive(Debug, Clone, PartialEq)]
186 struct User {
187 id: u64,
188 }
189
190 impl Authenticatable for User {
191 fn auth_identifier(&self) -> String {
192 self.id.to_string()
193 }
194 fn auth_password_hash(&self) -> Option<&str> {
195 None
196 }
197 }
198
199 struct Provider {
200 fail: bool,
201 }
202
203 #[async_trait::async_trait]
204 impl UserProvider<User> for Provider {
205 async fn retrieve_by_id(&self, _: &str) -> Result<Option<User>> {
206 Ok(None)
207 }
208 async fn retrieve_by_credentials(&self, _: &Credentials) -> Result<Option<User>> {
209 Ok(None)
210 }
211 async fn validate_credentials(&self, _: &User, _: &Credentials) -> Result<bool> {
212 Ok(false)
213 }
214 async fn retrieve_by_token(&self, token: &str) -> Result<Option<User>> {
215 if self.fail {
216 return Err(Error::internal("database is down"));
217 }
218 Ok((token == "good").then_some(User { id: 7 }))
219 }
220 }
221
222 fn manager(fail: bool) -> Arc<AuthManager<User>> {
223 Arc::new(
224 AuthManager::new("api")
225 .register(Arc::new(TokenGuard::new("api", Arc::new(Provider { fail })))),
226 )
227 }
228
229 async fn reveal_user(request: Request) -> Response {
230 match request.extension::<AuthenticatedUser<User>>() {
231 Some(user) => Response::text(user.id.to_string()),
232 None => Response::text("anonymous"),
233 }
234 }
235
236 async fn body_of(response: Response) -> String {
237 String::from_utf8(response.into_http().into_body().collect().await.unwrap().to_vec())
238 .unwrap()
239 }
240
241 fn bearer(token: &str) -> Request {
242 Request::builder().header("authorization", &format!("Bearer {token}")).build()
243 }
244
245 #[tokio::test]
246 async fn an_authenticated_request_reaches_the_handler_with_its_user() {
247 let response = Pipeline::new()
248 .through(Authenticate::new(manager(false)))
249 .then(reveal_user)
250 .run(bearer("good"))
251 .await;
252
253 assert_eq!(response.status(), StatusCode::OK);
254 assert_eq!(body_of(response).await, "7");
255 }
256
257 #[tokio::test]
258 async fn an_unauthenticated_request_is_rejected_before_the_handler() {
259 let response = Pipeline::new()
260 .through(Authenticate::new(manager(false)))
261 .then(|_: Request| async { panic!("the handler must not run") })
262 .run(bearer("bad"))
263 .await;
264
265 assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
266 }
267
268 #[tokio::test]
269 async fn a_guard_failure_is_a_500_not_a_401() {
270 let response = Pipeline::new()
273 .through(Authenticate::new(manager(true)))
274 .then(reveal_user)
275 .run(bearer("good"))
276 .await;
277
278 assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
279 }
280
281 #[tokio::test]
282 async fn a_named_guard_can_be_selected() {
283 let middleware = Authenticate::with_guard(manager(false), "api");
284 let response =
285 Pipeline::new().through(middleware).then(reveal_user).run(bearer("good")).await;
286
287 assert_eq!(body_of(response).await, "7");
288 }
289
290 #[tokio::test]
291 async fn an_unknown_named_guard_fails_loudly() {
292 let middleware = Authenticate::with_guard(manager(false), "nope");
293 let response =
294 Pipeline::new().through(middleware).then(reveal_user).run(bearer("good")).await;
295
296 assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
297 }
298
299 #[tokio::test]
300 async fn the_guest_middleware_redirects_an_authenticated_user() {
301 let response = Pipeline::new()
302 .through(RedirectIfAuthenticated::new(manager(false), "/dashboard"))
303 .then(|_: Request| async { Response::text("login form") })
304 .run(bearer("good"))
305 .await;
306
307 assert_eq!(response.status(), StatusCode::FOUND);
308 assert_eq!(response.header("location"), Some("/dashboard"));
309 }
310
311 #[tokio::test]
312 async fn the_guest_middleware_lets_a_guest_through() {
313 let response = Pipeline::new()
314 .through(RedirectIfAuthenticated::new(manager(false), "/dashboard"))
315 .then(|_: Request| async { Response::text("login form") })
316 .run(Request::builder().build())
317 .await;
318
319 assert_eq!(body_of(response).await, "login form");
320 }
321
322 #[tokio::test]
323 async fn the_guest_middleware_fails_open_to_the_login_page() {
324 let response = Pipeline::new()
326 .through(RedirectIfAuthenticated::new(manager(true), "/dashboard"))
327 .then(|_: Request| async { Response::text("login form") })
328 .run(bearer("good"))
329 .await;
330
331 assert_eq!(body_of(response).await, "login form");
332 }
333}