b453d5d
use std::sync::Arc;
use axum::Extension;
use axum::extract::State;
use axum::http::HeaderMap;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use crate::errors::ApiError;
use crate::oauth::{REFRESH_LOCK_TTL_SECS, append_cookie, read_cookie_header, refresh_grant};
use crate::services::AuthService;
pub async fn refresh(
State(service): State<AuthService>,
Extension(verifier): Extension<Arc<jwt::JwtVerifier>>,
headers: HeaderMap,
) -> Result<Response, ApiError> {
let config = service.config();
let refresh_token = read_cookie_header(&headers, &config.oauth.refresh_cookie)
.map(str::to_owned)
.ok_or_else(ApiError::unauthorized)?;
let lock_key = format!("oauth:refresh:{}", token_fingerprint(&refresh_token));
let mut acquired = service.state.redis.set_nx_ex(&lock_key, REFRESH_LOCK_TTL_SECS).await;
if !acquired {
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
acquired = service.state.redis.set_nx_ex(&lock_key, REFRESH_LOCK_TTL_SECS).await;
}
if !acquired {
return Err(ApiError::conflict("another refresh is already in flight"));
}
let result = refresh_grant(&service, &refresh_token).await;
let _ = export::redis::delete(&service.state.redis, &lock_key).await;
let tokens = match result {
Ok(tokens) => tokens,
Err(_) => {
let mut response = StatusCode::UNAUTHORIZED.into_response();
append_cookie(&mut response, &jwt::clear_session_cookie(&config.oauth));
append_cookie(&mut response, &jwt::clear_refresh_cookie(&config.oauth));
return Ok(response);
}
};
if verifier.verify_access(&tokens.access_token).await.is_err() {
return Err(ApiError::from(crate::errors::AuthError::Unauthorized));
}
let mut response = StatusCode::NO_CONTENT.into_response();
append_cookie(
&mut response,
&jwt::session_cookie(&config.oauth, &tokens.access_token, tokens.expires_in.max(60)),
);
if let Some(refresh) = tokens.refresh_token {
append_cookie(&mut response, &jwt::refresh_cookie(&config.oauth, &refresh));
}
Ok(response)
}
fn token_fingerprint(token: &str) -> String {
use base64::Engine as _;
use sha2::{Digest, Sha256};
let digest = Sha256::digest(token.as_bytes());
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(digest)
}