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