use axum::extract::Extension;
use axum::http::{HeaderValue, StatusCode, header};
use axum::response::{IntoResponse, Response};
use chrono::{DateTime, Utc};
use systemprompt_identifiers::{AccessTokenId, UserId};
use systemprompt_models::RequestContext;
use systemprompt_oauth::repository::OAuthRepository;
use tracing::instrument;
use crate::routes::oauth::extractors::OAuthRepo;
use crate::routes::oauth::{OAuthHttpError, internal};
#[instrument(skip(repo, req_ctx))]
pub async fn handle_logout(
Extension(req_ctx): Extension<RequestContext>,
OAuthRepo(repo): OAuthRepo,
) -> Result<Response, OAuthHttpError> {
let jti = req_ctx
.jti()
.ok_or_else(|| OAuthHttpError::invalid_request("Missing bearer token"))?;
let exp_dt = req_ctx
.token_exp()
.and_then(|exp_unix| DateTime::<Utc>::from_timestamp(exp_unix, 0))
.ok_or_else(|| OAuthHttpError::invalid_request("Invalid token expiry"))?;
revoke_jti(&repo, jti, req_ctx.user_id(), exp_dt).await?;
let cookie = HeaderValue::from_str(
"access_token=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Strict",
)
.map_err(|e| internal::server_error("Logout failed", e))?;
let mut response = (StatusCode::NO_CONTENT).into_response();
response.headers_mut().insert(header::SET_COOKIE, cookie);
Ok(response)
}
async fn revoke_jti(
repo: &OAuthRepository,
jti: &AccessTokenId,
user_id: &UserId,
exp: DateTime<Utc>,
) -> Result<(), OAuthHttpError> {
repo.revoke_jti(jti, user_id, exp)
.await
.map_err(|e| internal::server_error("Logout failed", e))
}