1use std::fmt;
2use std::future::Future;
3use std::pin::Pin;
4use std::sync::Arc;
5use std::task::{Context, Poll};
6
7use axum::extract::{FromRequestParts, Request};
8use axum::http::{HeaderMap, StatusCode, request::Parts};
9use axum::response::{IntoResponse, Response};
10use serde_json::{Value, json};
11use tower_layer::Layer;
12use tower_service::Service;
13
14use crate::adapter::{Adapter, Inner, bearer_token, json_response};
15use crate::cookies::{KIND_ACCESS, raw_cookie, read_cookie};
16use crate::jwt::{Claims, str_claim, unverified_claims};
17use crate::origin::check_origin;
18
19#[derive(Clone, PartialEq, Eq)]
22#[non_exhaustive]
23pub struct User {
24 pub id: String,
25 pub roles: Vec<String>,
26 pub email: Option<String>,
27 pub phone: Option<String>,
28 pub token: String,
30}
31
32impl fmt::Debug for User {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 f.debug_struct("User")
36 .field("id", &self.id)
37 .field("roles", &self.roles)
38 .field("email", &self.email)
39 .field("phone", &self.phone)
40 .field("token", &"[redacted]")
41 .finish()
42 }
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub struct Unauthenticated;
48
49impl fmt::Display for Unauthenticated {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 f.write_str("unauthenticated")
52 }
53}
54
55impl std::error::Error for Unauthenticated {}
56
57impl IntoResponse for Unauthenticated {
58 fn into_response(self) -> Response {
59 json_response(
60 StatusCode::UNAUTHORIZED,
61 &json!({ "error": "unauthenticated" }),
62 )
63 }
64}
65
66impl<S: Send + Sync> FromRequestParts<S> for User {
67 type Rejection = Unauthenticated;
68
69 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
70 parts
71 .extensions
72 .get::<User>()
73 .cloned()
74 .ok_or(Unauthenticated)
75 }
76}
77
78impl Adapter {
79 pub async fn authenticate(&self, headers: &HeaderMap) -> Result<User, Unauthenticated> {
85 self.inner.authenticate(headers).await.map(|(user, _)| user)
86 }
87
88 pub fn require_auth(&self) -> RequireAuthLayer {
91 RequireAuthLayer {
92 inner: self.inner.clone(),
93 }
94 }
95}
96
97impl Inner {
98 async fn authenticate(&self, headers: &HeaderMap) -> Result<(User, bool), Unauthenticated> {
101 let name = &self.config.access_cookie_name;
102 if raw_cookie(headers, name).is_some_and(|v| !v.is_empty()) {
103 let claims =
104 read_cookie(&self.config, headers, name, KIND_ACCESS).ok_or(Unauthenticated)?;
105 let token = str_claim(&claims, "token");
106 if str_claim(&claims, "sub").is_empty() || !is_access_token(token) {
107 return Err(Unauthenticated);
108 }
109 return Ok((user_from(&claims, token), true));
110 }
111
112 let token = bearer_token(headers).ok_or(Unauthenticated)?;
113 let claims = self
114 .jwks
115 .verify(
116 token,
117 &self.config.auth_server_issuer,
118 &self.config.audience,
119 )
120 .await
121 .map_err(|_| Unauthenticated)?;
122 if str_claim(&claims, "typ") != "access" || str_claim(&claims, "sub").is_empty() {
125 return Err(Unauthenticated);
126 }
127 Ok((user_from(&claims, token), false))
128 }
129}
130
131fn is_access_token(token: &str) -> bool {
135 unverified_claims(token).is_some_and(|claims| str_claim(&claims, "typ") == "access")
136}
137
138fn user_from(claims: &Claims, token: &str) -> User {
139 let optional = |name: &str| {
140 Some(str_claim(claims, name))
141 .filter(|s| !s.is_empty())
142 .map(str::to_string)
143 };
144 User {
145 id: str_claim(claims, "sub").to_string(),
146 roles: claims
147 .get("roles")
148 .and_then(Value::as_array)
149 .map(|roles| {
150 roles
151 .iter()
152 .filter_map(Value::as_str)
153 .map(str::to_string)
154 .collect()
155 })
156 .unwrap_or_default(),
157 email: optional("email"),
158 phone: optional("phone"),
159 token: token.to_string(),
160 }
161}
162
163#[derive(Clone)]
165pub struct RequireAuthLayer {
166 inner: Arc<Inner>,
167}
168
169impl fmt::Debug for RequireAuthLayer {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 f.debug_struct("RequireAuthLayer").finish_non_exhaustive()
172 }
173}
174
175impl<S> Layer<S> for RequireAuthLayer {
176 type Service = RequireAuth<S>;
177
178 fn layer(&self, service: S) -> Self::Service {
179 RequireAuth {
180 adapter: self.inner.clone(),
181 service,
182 }
183 }
184}
185
186#[derive(Clone)]
188pub struct RequireAuth<S> {
189 adapter: Arc<Inner>,
190 service: S,
191}
192
193impl<S> fmt::Debug for RequireAuth<S> {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 f.debug_struct("RequireAuth").finish_non_exhaustive()
196 }
197}
198
199impl<S> Service<Request> for RequireAuth<S>
200where
201 S: Service<Request> + Clone + Send + 'static,
202 S::Response: IntoResponse,
203 S::Future: Send + 'static,
204{
205 type Response = Response;
206 type Error = S::Error;
207 type Future = Pin<Box<dyn Future<Output = Result<Response, S::Error>> + Send>>;
208
209 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
210 self.service.poll_ready(cx)
211 }
212
213 fn call(&mut self, mut req: Request) -> Self::Future {
214 let clone = self.service.clone();
217 let mut service = std::mem::replace(&mut self.service, clone);
218 let adapter = self.adapter.clone();
219
220 Box::pin(async move {
221 match adapter.authenticate(req.headers()).await {
222 Ok((_, true))
225 if check_origin(&adapter.config, req.method(), req.headers()).is_some() =>
226 {
227 Ok(json_response(
228 StatusCode::FORBIDDEN,
229 &json!({ "error": "cross_site_request_blocked" }),
230 ))
231 }
232 Ok((user, _)) => {
233 req.extensions_mut().insert(user);
234 service.call(req).await.map(IntoResponse::into_response)
235 }
236 Err(rejected) => Ok(rejected.into_response()),
237 }
238 })
239 }
240}