this repo has no description
1use serde::{Deserialize, Serialize};
2use sqlx::PgPool;
3use std::fmt;
4use std::sync::Arc;
5use std::time::Duration;
6
7use crate::cache::Cache;
8
9pub mod extractor;
10pub mod token;
11pub mod verify;
12
13pub use extractor::{
14 AuthError, BearerAuth, BearerAuthAdmin, BearerAuthAllowDeactivated, ExtractedToken,
15 extract_auth_token_from_header, extract_bearer_token_from_header,
16};
17pub use token::{
18 SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED, SCOPE_REFRESH, TOKEN_TYPE_ACCESS,
19 TOKEN_TYPE_REFRESH, TOKEN_TYPE_SERVICE, TokenWithMetadata, create_access_token,
20 create_access_token_with_metadata, create_refresh_token, create_refresh_token_with_metadata,
21 create_service_token,
22};
23pub use verify::{
24 get_did_from_token, get_jti_from_token, verify_access_token, verify_refresh_token, verify_token,
25};
26
27const KEY_CACHE_TTL_SECS: u64 = 300;
28const SESSION_CACHE_TTL_SECS: u64 = 60;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum TokenValidationError {
32 AccountDeactivated,
33 AccountTakedown,
34 KeyDecryptionFailed,
35 AuthenticationFailed,
36}
37
38impl fmt::Display for TokenValidationError {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 Self::AccountDeactivated => write!(f, "AccountDeactivated"),
42 Self::AccountTakedown => write!(f, "AccountTakedown"),
43 Self::KeyDecryptionFailed => write!(f, "KeyDecryptionFailed"),
44 Self::AuthenticationFailed => write!(f, "AuthenticationFailed"),
45 }
46 }
47}
48
49pub struct AuthenticatedUser {
50 pub did: String,
51 pub key_bytes: Option<Vec<u8>>,
52 pub is_oauth: bool,
53 pub is_admin: bool,
54}
55
56pub async fn validate_bearer_token(
57 db: &PgPool,
58 token: &str,
59) -> Result<AuthenticatedUser, TokenValidationError> {
60 validate_bearer_token_with_options_internal(db, None, token, false).await
61}
62
63pub async fn validate_bearer_token_allow_deactivated(
64 db: &PgPool,
65 token: &str,
66) -> Result<AuthenticatedUser, TokenValidationError> {
67 validate_bearer_token_with_options_internal(db, None, token, true).await
68}
69
70pub async fn validate_bearer_token_cached(
71 db: &PgPool,
72 cache: &Arc<dyn Cache>,
73 token: &str,
74) -> Result<AuthenticatedUser, TokenValidationError> {
75 validate_bearer_token_with_options_internal(db, Some(cache), token, false).await
76}
77
78pub async fn validate_bearer_token_cached_allow_deactivated(
79 db: &PgPool,
80 cache: &Arc<dyn Cache>,
81 token: &str,
82) -> Result<AuthenticatedUser, TokenValidationError> {
83 validate_bearer_token_with_options_internal(db, Some(cache), token, true).await
84}
85
86async fn validate_bearer_token_with_options_internal(
87 db: &PgPool,
88 cache: Option<&Arc<dyn Cache>>,
89 token: &str,
90 allow_deactivated: bool,
91) -> Result<AuthenticatedUser, TokenValidationError> {
92 let did_from_token = get_did_from_token(token).ok();
93
94 if let Some(ref did) = did_from_token {
95 let key_cache_key = format!("auth:key:{}", did);
96 let mut cached_key: Option<Vec<u8>> = None;
97
98 if let Some(c) = cache {
99 cached_key = c.get_bytes(&key_cache_key).await;
100 if cached_key.is_some() {
101 crate::metrics::record_auth_cache_hit("key");
102 } else {
103 crate::metrics::record_auth_cache_miss("key");
104 }
105 }
106
107 let (decrypted_key, deactivated_at, takedown_ref, is_admin) = if let Some(key) = cached_key {
108 let user_status = sqlx::query!(
109 "SELECT deactivated_at, takedown_ref, is_admin FROM users WHERE did = $1",
110 did
111 )
112 .fetch_optional(db)
113 .await
114 .ok()
115 .flatten();
116
117 match user_status {
118 Some(status) => (Some(key), status.deactivated_at, status.takedown_ref, status.is_admin),
119 None => (None, None, None, false),
120 }
121 } else if let Some(user) = sqlx::query!(
122 "SELECT k.key_bytes, k.encryption_version, u.deactivated_at, u.takedown_ref, u.is_admin
123 FROM users u
124 JOIN user_keys k ON u.id = k.user_id
125 WHERE u.did = $1",
126 did
127 )
128 .fetch_optional(db)
129 .await
130 .ok()
131 .flatten()
132 {
133 let key = crate::config::decrypt_key(&user.key_bytes, user.encryption_version)
134 .map_err(|_| TokenValidationError::KeyDecryptionFailed)?;
135
136 if let Some(c) = cache {
137 let _ = c
138 .set_bytes(
139 &key_cache_key,
140 &key,
141 Duration::from_secs(KEY_CACHE_TTL_SECS),
142 )
143 .await;
144 }
145
146 (Some(key), user.deactivated_at, user.takedown_ref, user.is_admin)
147 } else {
148 (None, None, None, false)
149 };
150
151 if let Some(decrypted_key) = decrypted_key {
152 if !allow_deactivated && deactivated_at.is_some() {
153 return Err(TokenValidationError::AccountDeactivated);
154 }
155
156 if takedown_ref.is_some() {
157 return Err(TokenValidationError::AccountTakedown);
158 }
159
160 if let Ok(token_data) = verify_access_token(token, &decrypted_key) {
161 let jti = &token_data.claims.jti;
162 let session_cache_key = format!("auth:session:{}:{}", did, jti);
163 let mut session_valid = false;
164
165 if let Some(c) = cache {
166 if let Some(cached_value) = c.get(&session_cache_key).await {
167 session_valid = cached_value == "1";
168 crate::metrics::record_auth_cache_hit("session");
169 } else {
170 crate::metrics::record_auth_cache_miss("session");
171 }
172 }
173
174 if !session_valid {
175 let session_exists = sqlx::query_scalar!(
176 "SELECT 1 as one FROM session_tokens WHERE did = $1 AND access_jti = $2 AND access_expires_at > NOW()",
177 did,
178 jti
179 )
180 .fetch_optional(db)
181 .await
182 .ok()
183 .flatten();
184
185 session_valid = session_exists.is_some();
186
187 if session_valid
188 && let Some(c) = cache {
189 let _ = c
190 .set(
191 &session_cache_key,
192 "1",
193 Duration::from_secs(SESSION_CACHE_TTL_SECS),
194 )
195 .await;
196 }
197 }
198
199 if session_valid {
200 return Ok(AuthenticatedUser {
201 did: did.clone(),
202 key_bytes: Some(decrypted_key),
203 is_oauth: false,
204 is_admin,
205 });
206 }
207 }
208 }
209 }
210
211 if let Ok(oauth_info) = crate::oauth::verify::extract_oauth_token_info(token)
212 && let Some(oauth_token) = sqlx::query!(
213 r#"SELECT t.did, t.expires_at, u.deactivated_at, u.takedown_ref, u.is_admin,
214 k.key_bytes as "key_bytes?", k.encryption_version as "encryption_version?"
215 FROM oauth_token t
216 JOIN users u ON t.did = u.did
217 LEFT JOIN user_keys k ON u.id = k.user_id
218 WHERE t.token_id = $1"#,
219 oauth_info.token_id
220 )
221 .fetch_optional(db)
222 .await
223 .ok()
224 .flatten()
225 {
226 if !allow_deactivated && oauth_token.deactivated_at.is_some() {
227 return Err(TokenValidationError::AccountDeactivated);
228 }
229
230 if oauth_token.takedown_ref.is_some() {
231 return Err(TokenValidationError::AccountTakedown);
232 }
233
234 let now = chrono::Utc::now();
235 if oauth_token.expires_at > now {
236 let key_bytes = if let (Some(kb), Some(ev)) =
237 (&oauth_token.key_bytes, oauth_token.encryption_version)
238 {
239 crate::config::decrypt_key(kb, Some(ev)).ok()
240 } else {
241 None
242 };
243 return Ok(AuthenticatedUser {
244 did: oauth_token.did,
245 key_bytes,
246 is_oauth: true,
247 is_admin: oauth_token.is_admin,
248 });
249 }
250 }
251
252 Err(TokenValidationError::AuthenticationFailed)
253}
254
255pub async fn invalidate_auth_cache(cache: &Arc<dyn Cache>, did: &str) {
256 let key_cache_key = format!("auth:key:{}", did);
257 let _ = cache.delete(&key_cache_key).await;
258}
259
260pub async fn validate_token_with_dpop(
261 db: &PgPool,
262 token: &str,
263 is_dpop_token: bool,
264 dpop_proof: Option<&str>,
265 http_method: &str,
266 http_uri: &str,
267 allow_deactivated: bool,
268) -> Result<AuthenticatedUser, TokenValidationError> {
269 if !is_dpop_token {
270 if allow_deactivated {
271 return validate_bearer_token_allow_deactivated(db, token).await;
272 } else {
273 return validate_bearer_token(db, token).await;
274 }
275 }
276 match crate::oauth::verify::verify_oauth_access_token(
277 db,
278 token,
279 dpop_proof,
280 http_method,
281 http_uri,
282 )
283 .await
284 {
285 Ok(result) => {
286 let user_info = sqlx::query!(
287 r#"SELECT u.deactivated_at, u.takedown_ref, u.is_admin,
288 k.key_bytes as "key_bytes?", k.encryption_version as "encryption_version?"
289 FROM users u
290 LEFT JOIN user_keys k ON u.id = k.user_id
291 WHERE u.did = $1"#,
292 result.did
293 )
294 .fetch_optional(db)
295 .await
296 .ok()
297 .flatten();
298 let Some(user_info) = user_info else {
299 return Err(TokenValidationError::AuthenticationFailed);
300 };
301 if !allow_deactivated && user_info.deactivated_at.is_some() {
302 return Err(TokenValidationError::AccountDeactivated);
303 }
304 if user_info.takedown_ref.is_some() {
305 return Err(TokenValidationError::AccountTakedown);
306 }
307 let key_bytes = if let (Some(kb), Some(ev)) = (&user_info.key_bytes, user_info.encryption_version) {
308 crate::config::decrypt_key(kb, Some(ev)).ok()
309 } else {
310 None
311 };
312 Ok(AuthenticatedUser {
313 did: result.did,
314 key_bytes,
315 is_oauth: true,
316 is_admin: user_info.is_admin,
317 })
318 }
319 Err(_) => Err(TokenValidationError::AuthenticationFailed),
320 }
321}
322
323#[derive(Debug, Serialize, Deserialize)]
324pub struct Claims {
325 pub iss: String,
326 pub sub: String,
327 pub aud: String,
328 pub exp: usize,
329 pub iat: usize,
330 #[serde(skip_serializing_if = "Option::is_none")]
331 pub scope: Option<String>,
332 #[serde(skip_serializing_if = "Option::is_none")]
333 pub lxm: Option<String>,
334 pub jti: String,
335}
336
337#[derive(Debug, Serialize, Deserialize)]
338pub struct Header {
339 pub alg: String,
340 pub typ: String,
341}
342
343#[derive(Debug, Serialize, Deserialize)]
344pub struct UnsafeClaims {
345 pub iss: String,
346 pub sub: Option<String>,
347}
348
349pub struct TokenData<T> {
350 pub claims: T,
351}