this repo has no description

JWT token refresh good error

lewis ec1f430c f95bdcae

Changed files
+170 -42
src
+1
src/api/error.rs
··· 192 192 crate::auth::TokenValidationError::AccountTakedown => Self::AccountTakedown, 193 193 crate::auth::TokenValidationError::KeyDecryptionFailed => Self::InternalError, 194 194 crate::auth::TokenValidationError::AuthenticationFailed => Self::AuthenticationFailed, 195 + crate::auth::TokenValidationError::TokenExpired => Self::ExpiredToken, 195 196 } 196 197 } 197 198 }
+18 -2
src/auth/extractor.rs
··· 19 19 MissingToken, 20 20 InvalidFormat, 21 21 AuthenticationFailed, 22 + TokenExpired, 22 23 AccountDeactivated, 23 24 AccountTakedown, 24 25 AdminRequired, ··· 39 40 ), 40 41 AuthError::AuthenticationFailed => ( 41 42 StatusCode::UNAUTHORIZED, 42 - "AuthenticationFailed", 43 - "Invalid or expired token", 43 + "InvalidToken", 44 + "Token could not be verified", 45 + ), 46 + AuthError::TokenExpired => ( 47 + StatusCode::UNAUTHORIZED, 48 + "ExpiredToken", 49 + "Token has expired", 44 50 ), 45 51 AuthError::AccountDeactivated => ( 46 52 StatusCode::UNAUTHORIZED, ··· 174 180 Ok(user) => Ok(BearerAuth(user)), 175 181 Err(TokenValidationError::AccountDeactivated) => Err(AuthError::AccountDeactivated), 176 182 Err(TokenValidationError::AccountTakedown) => Err(AuthError::AccountTakedown), 183 + Err(TokenValidationError::TokenExpired) => Err(AuthError::TokenExpired), 177 184 Err(_) => Err(AuthError::AuthenticationFailed), 178 185 } 179 186 } else { ··· 181 188 Ok(user) => Ok(BearerAuth(user)), 182 189 Err(TokenValidationError::AccountDeactivated) => Err(AuthError::AccountDeactivated), 183 190 Err(TokenValidationError::AccountTakedown) => Err(AuthError::AccountTakedown), 191 + Err(TokenValidationError::TokenExpired) => Err(AuthError::TokenExpired), 184 192 Err(_) => Err(AuthError::AuthenticationFailed), 185 193 } 186 194 } ··· 224 232 { 225 233 Ok(user) => Ok(BearerAuthAllowDeactivated(user)), 226 234 Err(TokenValidationError::AccountTakedown) => Err(AuthError::AccountTakedown), 235 + Err(TokenValidationError::TokenExpired) => Err(AuthError::TokenExpired), 227 236 Err(_) => Err(AuthError::AuthenticationFailed), 228 237 } 229 238 } else { ··· 236 245 { 237 246 Ok(user) => Ok(BearerAuthAllowDeactivated(user)), 238 247 Err(TokenValidationError::AccountTakedown) => Err(AuthError::AccountTakedown), 248 + Err(TokenValidationError::TokenExpired) => Err(AuthError::TokenExpired), 239 249 Err(_) => Err(AuthError::AuthenticationFailed), 240 250 } 241 251 } ··· 284 294 Err(TokenValidationError::AccountTakedown) => { 285 295 return Err(AuthError::AccountTakedown); 286 296 } 297 + Err(TokenValidationError::TokenExpired) => { 298 + return Err(AuthError::TokenExpired); 299 + } 287 300 Err(_) => return Err(AuthError::AuthenticationFailed), 288 301 } 289 302 } else { ··· 294 307 } 295 308 Err(TokenValidationError::AccountTakedown) => { 296 309 return Err(AuthError::AccountTakedown); 310 + } 311 + Err(TokenValidationError::TokenExpired) => { 312 + return Err(AuthError::TokenExpired); 297 313 } 298 314 Err(_) => return Err(AuthError::AuthenticationFailed), 299 315 }
+51 -40
src/auth/mod.rs
··· 28 28 create_service_token, 29 29 }; 30 30 pub use verify::{ 31 - get_did_from_token, get_jti_from_token, verify_access_token, verify_refresh_token, verify_token, 31 + TokenVerifyError, get_did_from_token, get_jti_from_token, verify_access_token, 32 + verify_access_token_typed, verify_refresh_token, verify_token, 32 33 }; 33 34 34 35 const KEY_CACHE_TTL_SECS: u64 = 300; ··· 40 41 AccountTakedown, 41 42 KeyDecryptionFailed, 42 43 AuthenticationFailed, 44 + TokenExpired, 43 45 } 44 46 45 47 impl fmt::Display for TokenValidationError { ··· 49 51 Self::AccountTakedown => write!(f, "AccountTakedown"), 50 52 Self::KeyDecryptionFailed => write!(f, "KeyDecryptionFailed"), 51 53 Self::AuthenticationFailed => write!(f, "AuthenticationFailed"), 54 + Self::TokenExpired => write!(f, "ExpiredToken"), 52 55 } 53 56 } 54 57 } ··· 193 196 return Err(TokenValidationError::AccountTakedown); 194 197 } 195 198 196 - if let Ok(token_data) = verify_access_token(token, &decrypted_key) { 197 - let jti = &token_data.claims.jti; 198 - let session_cache_key = format!("auth:session:{}:{}", did, jti); 199 - let mut session_valid = false; 199 + match verify_access_token_typed(token, &decrypted_key) { 200 + Ok(token_data) => { 201 + let jti = &token_data.claims.jti; 202 + let session_cache_key = format!("auth:session:{}:{}", did, jti); 203 + let mut session_valid = false; 200 204 201 - if let Some(c) = cache { 202 - if let Some(cached_value) = c.get(&session_cache_key).await { 203 - session_valid = cached_value == "1"; 204 - crate::metrics::record_auth_cache_hit("session"); 205 - } else { 206 - crate::metrics::record_auth_cache_miss("session"); 205 + if let Some(c) = cache { 206 + if let Some(cached_value) = c.get(&session_cache_key).await { 207 + session_valid = cached_value == "1"; 208 + crate::metrics::record_auth_cache_hit("session"); 209 + } else { 210 + crate::metrics::record_auth_cache_miss("session"); 211 + } 207 212 } 208 - } 209 213 210 - if !session_valid { 211 - let session_exists = sqlx::query_scalar!( 212 - "SELECT 1 as one FROM session_tokens WHERE did = $1 AND access_jti = $2 AND access_expires_at > NOW()", 213 - did, 214 - jti 215 - ) 216 - .fetch_optional(db) 217 - .await 218 - .ok() 219 - .flatten(); 214 + if !session_valid { 215 + let session_exists = sqlx::query_scalar!( 216 + "SELECT 1 as one FROM session_tokens WHERE did = $1 AND access_jti = $2 AND access_expires_at > NOW()", 217 + did, 218 + jti 219 + ) 220 + .fetch_optional(db) 221 + .await 222 + .ok() 223 + .flatten(); 220 224 221 - session_valid = session_exists.is_some(); 225 + session_valid = session_exists.is_some(); 226 + 227 + if session_valid && let Some(c) = cache { 228 + let _ = c 229 + .set( 230 + &session_cache_key, 231 + "1", 232 + Duration::from_secs(SESSION_CACHE_TTL_SECS), 233 + ) 234 + .await; 235 + } 236 + } 222 237 223 - if session_valid && let Some(c) = cache { 224 - let _ = c 225 - .set( 226 - &session_cache_key, 227 - "1", 228 - Duration::from_secs(SESSION_CACHE_TTL_SECS), 229 - ) 230 - .await; 238 + if session_valid { 239 + return Ok(AuthenticatedUser { 240 + did: did.clone(), 241 + key_bytes: Some(decrypted_key), 242 + is_oauth: false, 243 + is_admin, 244 + scope: None, 245 + }); 231 246 } 232 247 } 233 - 234 - if session_valid { 235 - return Ok(AuthenticatedUser { 236 - did: did.clone(), 237 - key_bytes: Some(decrypted_key), 238 - is_oauth: false, 239 - is_admin, 240 - scope: None, 241 - }); 248 + Err(verify::TokenVerifyError::Expired) => { 249 + return Err(TokenValidationError::TokenExpired); 242 250 } 251 + Err(verify::TokenVerifyError::Invalid) => {} 243 252 } 244 253 } 245 254 } ··· 283 292 is_admin: oauth_token.is_admin, 284 293 scope: oauth_info.scope, 285 294 }); 295 + } else { 296 + return Err(TokenValidationError::TokenExpired); 286 297 } 287 298 } 288 299
+100
src/auth/verify.rs
··· 10 10 use hmac::{Hmac, Mac}; 11 11 use k256::ecdsa::{Signature, SigningKey, VerifyingKey, signature::Verifier}; 12 12 use sha2::Sha256; 13 + use std::fmt; 13 14 use subtle::ConstantTimeEq; 14 15 15 16 type HmacSha256 = Hmac<Sha256>; 17 + 18 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 19 + pub enum TokenVerifyError { 20 + Expired, 21 + Invalid, 22 + } 23 + 24 + impl fmt::Display for TokenVerifyError { 25 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 + match self { 27 + Self::Expired => write!(f, "Token expired"), 28 + Self::Invalid => write!(f, "Token invalid"), 29 + } 30 + } 31 + } 32 + 33 + impl std::error::Error for TokenVerifyError {} 16 34 17 35 pub fn get_did_from_token(token: &str) -> Result<String, String> { 18 36 let parts: Vec<&str> = token.split('.').collect(); ··· 228 246 let token_scope = claims.scope.as_deref().unwrap_or(""); 229 247 if !scopes.contains(&token_scope) { 230 248 return Err(anyhow!("Invalid token scope: {}", token_scope)); 249 + } 250 + } 251 + 252 + Ok(TokenData { claims }) 253 + } 254 + 255 + pub fn verify_access_token_typed( 256 + token: &str, 257 + key_bytes: &[u8], 258 + ) -> Result<TokenData<Claims>, TokenVerifyError> { 259 + verify_token_typed_internal( 260 + token, 261 + key_bytes, 262 + Some(TOKEN_TYPE_ACCESS), 263 + Some(&[SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED]), 264 + ) 265 + } 266 + 267 + fn verify_token_typed_internal( 268 + token: &str, 269 + key_bytes: &[u8], 270 + expected_typ: Option<&str>, 271 + allowed_scopes: Option<&[&str]>, 272 + ) -> Result<TokenData<Claims>, TokenVerifyError> { 273 + let parts: Vec<&str> = token.split('.').collect(); 274 + if parts.len() != 3 { 275 + return Err(TokenVerifyError::Invalid); 276 + } 277 + 278 + let header_b64 = parts[0]; 279 + let claims_b64 = parts[1]; 280 + let signature_b64 = parts[2]; 281 + 282 + let Ok(header_bytes) = URL_SAFE_NO_PAD.decode(header_b64) else { 283 + return Err(TokenVerifyError::Invalid); 284 + }; 285 + 286 + let Ok(header) = serde_json::from_slice::<Header>(&header_bytes) else { 287 + return Err(TokenVerifyError::Invalid); 288 + }; 289 + 290 + if let Some(expected) = expected_typ 291 + && header.typ != expected 292 + { 293 + return Err(TokenVerifyError::Invalid); 294 + } 295 + 296 + let Ok(signature_bytes) = URL_SAFE_NO_PAD.decode(signature_b64) else { 297 + return Err(TokenVerifyError::Invalid); 298 + }; 299 + 300 + let Ok(signature) = Signature::from_slice(&signature_bytes) else { 301 + return Err(TokenVerifyError::Invalid); 302 + }; 303 + 304 + let Ok(signing_key) = SigningKey::from_slice(key_bytes) else { 305 + return Err(TokenVerifyError::Invalid); 306 + }; 307 + let verifying_key = VerifyingKey::from(&signing_key); 308 + 309 + let message = format!("{}.{}", header_b64, claims_b64); 310 + if verifying_key.verify(message.as_bytes(), &signature).is_err() { 311 + return Err(TokenVerifyError::Invalid); 312 + } 313 + 314 + let Ok(claims_bytes) = URL_SAFE_NO_PAD.decode(claims_b64) else { 315 + return Err(TokenVerifyError::Invalid); 316 + }; 317 + 318 + let Ok(claims) = serde_json::from_slice::<Claims>(&claims_bytes) else { 319 + return Err(TokenVerifyError::Invalid); 320 + }; 321 + 322 + let now = Utc::now().timestamp() as usize; 323 + if claims.exp < now { 324 + return Err(TokenVerifyError::Expired); 325 + } 326 + 327 + if let Some(scopes) = allowed_scopes { 328 + let token_scope = claims.scope.as_deref().unwrap_or(""); 329 + if !scopes.contains(&token_scope) { 330 + return Err(TokenVerifyError::Invalid); 231 331 } 232 332 } 233 333