#![allow(clippy::unused_async)]
use axum::extract::State;
use axum::http::{HeaderMap, StatusCode};
use axum::response::IntoResponse;
use crate::proxy::{AppState, error_response, extract_admin_bearer, is_admin_authorised};
use crate::token::{ADMIN_SCOPE, IssueRequest};
pub async fn issue_token(
State(state): State<AppState>,
headers: HeaderMap,
axum::Json(req): axum::Json<IssueTokenRequest>,
) -> impl IntoResponse {
if !is_admin_authorised(&state, &headers) {
return error_response(
StatusCode::UNAUTHORIZED,
"authentication_error",
"missing or invalid admin Bearer key",
);
}
let ttl = req.ttl_hours.unwrap_or(24);
let label = req.label.unwrap_or_default();
let scope = req.scope.unwrap_or_default();
if !scope.is_empty() && scope != ADMIN_SCOPE {
return error_response(
StatusCode::BAD_REQUEST,
"invalid_request_error",
&format!("unknown scope '{scope}'; expected '{ADMIN_SCOPE}' or none"),
);
}
match state.token_manager.issue(&IssueRequest {
ttl_hours: ttl,
label: &label,
account: req.account.as_deref(),
max_requests: req.max_requests,
scope: &scope,
}) {
Ok(token) => {
state.metrics.record_token_issued();
(
StatusCode::OK,
axum::Json(serde_json::json!({
"token": token,
"ttl_hours": ttl,
"label": label,
"account": req.account,
"max_requests": req.max_requests,
"scope": scope,
})),
)
.into_response()
}
Err(e) => error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"api_error",
&format!("Failed to issue token: {e}"),
),
}
}
pub async fn list_tokens(State(state): State<AppState>, headers: HeaderMap) -> impl IntoResponse {
if !is_admin_authorised(&state, &headers) {
return error_response(
StatusCode::UNAUTHORIZED,
"authentication_error",
"admin Bearer key required",
);
}
match state.token_manager.list_tokens() {
Ok(records) => (
StatusCode::OK,
axum::Json(serde_json::json!({"data": records})),
)
.into_response(),
Err(e) => error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"api_error",
&format!("{e}"),
),
}
}
pub async fn revoke_token(
State(state): State<AppState>,
headers: HeaderMap,
axum::Json(req): axum::Json<RevokeTokenRequest>,
) -> impl IntoResponse {
if !is_admin_authorised(&state, &headers) {
return error_response(
StatusCode::UNAUTHORIZED,
"authentication_error",
"admin Bearer key required",
);
}
match state.token_manager.revoke_token(&req.id) {
Ok(()) => {
state.metrics.record_token_revoked();
(
StatusCode::OK,
axum::Json(serde_json::json!({"revoked": req.id})),
)
.into_response()
}
Err(e) => error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"api_error",
&format!("{e}"),
),
}
}
pub async fn rotate_admin_token(
State(state): State<AppState>,
headers: HeaderMap,
axum::Json(req): axum::Json<RotateTokenRequest>,
) -> impl IntoResponse {
let Some(bearer) = extract_admin_bearer(&headers) else {
return error_response(
StatusCode::UNAUTHORIZED,
"authentication_error",
"admin Bearer token required",
);
};
let Ok(claims) = state.token_manager.validate_admin_token(bearer) else {
return error_response(
StatusCode::BAD_REQUEST,
"invalid_request_error",
"rotation requires an admin-scoped token; the flat admin key has no subject to revoke",
);
};
let ttl = req.ttl_hours.unwrap_or(24);
let label = req.label.unwrap_or_else(|| claims.label.clone());
match state
.token_manager
.rotate_admin_token(&claims.sub, ttl, &label)
{
Ok(token) => {
state.metrics.record_token_issued();
state.metrics.record_token_revoked();
(
StatusCode::OK,
axum::Json(serde_json::json!({
"token": token,
"ttl_hours": ttl,
"label": label,
"scope": ADMIN_SCOPE,
"revoked": claims.sub,
})),
)
.into_response()
}
Err(e) => error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"api_error",
&format!("Failed to rotate admin token: {e}"),
),
}
}
#[derive(serde::Deserialize)]
pub struct IssueTokenRequest {
pub ttl_hours: Option<i64>,
pub label: Option<String>,
pub account: Option<String>,
pub max_requests: Option<u64>,
pub scope: Option<String>,
}
#[derive(serde::Deserialize, Default)]
pub struct RotateTokenRequest {
pub ttl_hours: Option<i64>,
pub label: Option<String>,
}
#[derive(serde::Deserialize)]
pub struct RevokeTokenRequest {
pub id: String,
}