+1
src/api/error.rs
+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
+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
+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
+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