graphql_starter/auth/
extractor.rs1use axum::extract::{FromRequestParts, OptionalFromRequestParts};
2use http::request::Parts;
3
4use super::{AuthErrorCode, AuthState, AuthenticationService, Subject};
5use crate::error::{err, ApiError, MapToErr, OkOrErr, Result};
6
7pub struct Auth<S: Subject>(pub S);
12
13impl<S, St> OptionalFromRequestParts<St> for Auth<S>
14where
15 S: Subject,
16 St: AuthState<S> + Send + Sync,
17{
18 type Rejection = Box<ApiError>;
19
20 async fn from_request_parts(parts: &mut Parts, state: &St) -> Result<Option<Self>, Self::Rejection> {
21 let auth_header_name = state.authn().header_name();
23 let auth_token = parts
24 .headers
25 .get(auth_header_name)
26 .map(|v| {
27 v.to_str().map_err(|err| {
28 err!(
29 AuthErrorCode::AuthMalformedAuthHeader {
30 auth_header: auth_header_name.into(),
31 },
32 "Couldn't parse auth header value"
33 )
34 .with_source(err)
35 })
36 })
37 .transpose()?
38 .filter(|t| !t.is_empty());
39
40 let auth_cookie_name = state.authn().cookie_name();
42 let auth_cookie_value = parts
43 .headers
44 .get(http::header::COOKIE)
45 .map(|v| {
46 v.to_str()
47 .map_to_err_with(AuthErrorCode::AuthMalformedCookies, "Couldn't parse request cookies")
48 })
49 .transpose()?
50 .and_then(|cookies| {
51 cookies
52 .split("; ")
53 .find_map(|cookie| cookie.strip_prefix(&format!("{auth_cookie_name}=")))
54 })
55 .filter(|c| !c.is_empty());
56
57 if auth_token.is_none() && auth_cookie_value.is_none() {
59 Ok(None)
60 } else {
61 let subject = match state.authn().authenticate(auth_token, auth_cookie_value).await {
62 Ok(s) => s,
63 Err(err) => {
64 let is_invalid_token = err.info().code() == "AUTH_INVALID_TOKEN";
65 let mut err: Box<ApiError> = err.into();
66 if auth_cookie_value.is_some() && is_invalid_token {
69 err = err.with_header(
70 "Set-Cookie",
71 format!("{auth_cookie_name}=invalid; Expires=Thu, 01 Jan 1970 00:00:00 GMT"),
72 );
73 }
74 return Err(err);
75 }
76 };
77 tracing::trace!("Authenticated as {subject}");
78 Ok(Some(Self(subject)))
79 }
80 }
81}
82
83impl<S, St> FromRequestParts<St> for Auth<S>
84where
85 S: Subject,
86 St: AuthState<S> + Send + Sync,
87{
88 type Rejection = Box<ApiError>;
89
90 async fn from_request_parts(parts: &mut Parts, state: &St) -> Result<Self, Self::Rejection> {
91 Ok(<Self as OptionalFromRequestParts<St>>::from_request_parts(parts, state)
92 .await?
93 .ok_or_err_with(AuthErrorCode::AuthMissing, "The subject must be authenticated")?)
94 }
95}