this repo has no description
1use serde::{Deserialize, Serialize};
2use sqlx::PgPool;
3use std::fmt;
4use std::sync::Arc;
5use std::time::Duration;
6use crate::cache::Cache;
7pub mod extractor;
8pub mod token;
9pub mod verify;
10pub use extractor::{BearerAuth, BearerAuthAllowDeactivated, AuthError, extract_bearer_token_from_header};
11pub use token::{
12 create_access_token, create_refresh_token, create_service_token,
13 create_access_token_with_metadata, create_refresh_token_with_metadata,
14 TokenWithMetadata,
15 TOKEN_TYPE_ACCESS, TOKEN_TYPE_REFRESH, TOKEN_TYPE_SERVICE,
16 SCOPE_ACCESS, SCOPE_REFRESH, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED,
17};
18pub use verify::{get_did_from_token, get_jti_from_token, verify_token, verify_access_token, verify_refresh_token};
19const KEY_CACHE_TTL_SECS: u64 = 300;
20const SESSION_CACHE_TTL_SECS: u64 = 60;
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum TokenValidationError {
23 AccountDeactivated,
24 AccountTakedown,
25 KeyDecryptionFailed,
26 AuthenticationFailed,
27}
28impl fmt::Display for TokenValidationError {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Self::AccountDeactivated => write!(f, "AccountDeactivated"),
32 Self::AccountTakedown => write!(f, "AccountTakedown"),
33 Self::KeyDecryptionFailed => write!(f, "KeyDecryptionFailed"),
34 Self::AuthenticationFailed => write!(f, "AuthenticationFailed"),
35 }
36 }
37}
38pub struct AuthenticatedUser {
39 pub did: String,
40 pub key_bytes: Option<Vec<u8>>,
41 pub is_oauth: bool,
42}
43pub async fn validate_bearer_token(
44 db: &PgPool,
45 token: &str,
46) -> Result<AuthenticatedUser, TokenValidationError> {
47 validate_bearer_token_with_options_internal(db, None, token, false).await
48}
49pub async fn validate_bearer_token_allow_deactivated(
50 db: &PgPool,
51 token: &str,
52) -> Result<AuthenticatedUser, TokenValidationError> {
53 validate_bearer_token_with_options_internal(db, None, token, true).await
54}
55pub async fn validate_bearer_token_cached(
56 db: &PgPool,
57 cache: &Arc<dyn Cache>,
58 token: &str,
59) -> Result<AuthenticatedUser, TokenValidationError> {
60 validate_bearer_token_with_options_internal(db, Some(cache), token, false).await
61}
62pub async fn validate_bearer_token_cached_allow_deactivated(
63 db: &PgPool,
64 cache: &Arc<dyn Cache>,
65 token: &str,
66) -> Result<AuthenticatedUser, TokenValidationError> {
67 validate_bearer_token_with_options_internal(db, Some(cache), token, true).await
68}
69async fn validate_bearer_token_with_options_internal(
70 db: &PgPool,
71 cache: Option<&Arc<dyn Cache>>,
72 token: &str,
73 allow_deactivated: bool,
74) -> Result<AuthenticatedUser, TokenValidationError> {
75 let did_from_token = get_did_from_token(token).ok();
76 if let Some(ref did) = did_from_token {
77 let key_cache_key = format!("auth:key:{}", did);
78 let mut cached_key: Option<Vec<u8>> = None;
79 if let Some(c) = cache {
80 cached_key = c.get_bytes(&key_cache_key).await;
81 if cached_key.is_some() {
82 crate::metrics::record_auth_cache_hit("key");
83 } else {
84 crate::metrics::record_auth_cache_miss("key");
85 }
86 }
87 let (decrypted_key, deactivated_at, takedown_ref) = if let Some(key) = cached_key {
88 let user_status = sqlx::query!(
89 "SELECT deactivated_at, takedown_ref FROM users WHERE did = $1",
90 did
91 )
92 .fetch_optional(db)
93 .await
94 .ok()
95 .flatten();
96 match user_status {
97 Some(status) => (Some(key), status.deactivated_at, status.takedown_ref),
98 None => (None, None, None),
99 }
100 } else {
101 if let Some(user) = sqlx::query!(
102 "SELECT k.key_bytes, k.encryption_version, u.deactivated_at, u.takedown_ref
103 FROM users u
104 JOIN user_keys k ON u.id = k.user_id
105 WHERE u.did = $1",
106 did
107 )
108 .fetch_optional(db)
109 .await
110 .ok()
111 .flatten()
112 {
113 let key = crate::config::decrypt_key(&user.key_bytes, user.encryption_version)
114 .map_err(|_| TokenValidationError::KeyDecryptionFailed)?;
115 if let Some(c) = cache {
116 let _ = c.set_bytes(&key_cache_key, &key, Duration::from_secs(KEY_CACHE_TTL_SECS)).await;
117 }
118 (Some(key), user.deactivated_at, user.takedown_ref)
119 } else {
120 (None, None, None)
121 }
122 };
123 if let Some(decrypted_key) = decrypted_key {
124 if !allow_deactivated && deactivated_at.is_some() {
125 return Err(TokenValidationError::AccountDeactivated);
126 }
127 if takedown_ref.is_some() {
128 return Err(TokenValidationError::AccountTakedown);
129 }
130 if let Ok(token_data) = verify_access_token(token, &decrypted_key) {
131 let jti = &token_data.claims.jti;
132 let session_cache_key = format!("auth:session:{}:{}", did, jti);
133 let mut session_valid = false;
134 if let Some(c) = cache {
135 if let Some(cached_value) = c.get(&session_cache_key).await {
136 session_valid = cached_value == "1";
137 crate::metrics::record_auth_cache_hit("session");
138 } else {
139 crate::metrics::record_auth_cache_miss("session");
140 }
141 }
142 if !session_valid {
143 let session_exists = sqlx::query_scalar!(
144 "SELECT 1 as one FROM session_tokens WHERE did = $1 AND access_jti = $2 AND access_expires_at > NOW()",
145 did,
146 jti
147 )
148 .fetch_optional(db)
149 .await
150 .ok()
151 .flatten();
152 session_valid = session_exists.is_some();
153 if session_valid {
154 if let Some(c) = cache {
155 let _ = c.set(&session_cache_key, "1", Duration::from_secs(SESSION_CACHE_TTL_SECS)).await;
156 }
157 }
158 }
159 if session_valid {
160 return Ok(AuthenticatedUser {
161 did: did.clone(),
162 key_bytes: Some(decrypted_key),
163 is_oauth: false,
164 });
165 }
166 }
167 }
168 }
169 if let Ok(oauth_info) = crate::oauth::verify::extract_oauth_token_info(token) {
170 if let Some(oauth_token) = sqlx::query!(
171 r#"SELECT t.did, t.expires_at, u.deactivated_at, u.takedown_ref
172 FROM oauth_token t
173 JOIN users u ON t.did = u.did
174 WHERE t.token_id = $1"#,
175 oauth_info.token_id
176 )
177 .fetch_optional(db)
178 .await
179 .ok()
180 .flatten()
181 {
182 if !allow_deactivated && oauth_token.deactivated_at.is_some() {
183 return Err(TokenValidationError::AccountDeactivated);
184 }
185 if oauth_token.takedown_ref.is_some() {
186 return Err(TokenValidationError::AccountTakedown);
187 }
188 let now = chrono::Utc::now();
189 if oauth_token.expires_at > now {
190 return Ok(AuthenticatedUser {
191 did: oauth_token.did,
192 key_bytes: None,
193 is_oauth: true,
194 });
195 }
196 }
197 }
198 Err(TokenValidationError::AuthenticationFailed)
199}
200pub async fn invalidate_auth_cache(cache: &Arc<dyn Cache>, did: &str) {
201 let key_cache_key = format!("auth:key:{}", did);
202 let _ = cache.delete(&key_cache_key).await;
203}
204#[derive(Debug, Serialize, Deserialize)]
205pub struct Claims {
206 pub iss: String,
207 pub sub: String,
208 pub aud: String,
209 pub exp: usize,
210 pub iat: usize,
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub scope: Option<String>,
213 #[serde(skip_serializing_if = "Option::is_none")]
214 pub lxm: Option<String>,
215 pub jti: String,
216}
217#[derive(Debug, Serialize, Deserialize)]
218pub struct Header {
219 pub alg: String,
220 pub typ: String,
221}
222#[derive(Debug, Serialize, Deserialize)]
223pub struct UnsafeClaims {
224 pub iss: String,
225 pub sub: Option<String>,
226}
227// fancy boy TokenData equivalent for compatibility/structure
228pub struct TokenData<T> {
229 pub claims: T,
230}