pinakes-server: add utoipa annotations to all routes; fix tests
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I28cf5b7b7cff8e90e123d624d97cf9656a6a6964
This commit is contained in:
parent
67b8322705
commit
9d58927cb4
60 changed files with 3493 additions and 242 deletions
|
|
@ -17,6 +17,19 @@ const DUMMY_HASH: &str =
|
|||
"$argon2id$v=19$m=19456,t=2,\
|
||||
p=1$VGltaW5nU2FmZUR1bW15$c2ltdWxhdGVkX2hhc2hfZm9yX3RpbWluZ19zYWZldHk";
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/auth/login",
|
||||
tag = "auth",
|
||||
request_body = LoginRequest,
|
||||
responses(
|
||||
(status = 200, description = "Login successful", body = LoginResponse),
|
||||
(status = 400, description = "Bad request"),
|
||||
(status = 401, description = "Invalid credentials"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security()
|
||||
)]
|
||||
pub async fn login(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<LoginRequest>,
|
||||
|
|
@ -82,6 +95,7 @@ pub async fn login(
|
|||
let user = user.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
// Generate session token using unbiased uniform distribution
|
||||
#[expect(clippy::expect_used)]
|
||||
let token: String = {
|
||||
use rand::seq::IndexedRandom;
|
||||
const CHARSET: &[u8] =
|
||||
|
|
@ -134,39 +148,64 @@ pub async fn login(
|
|||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/auth/logout",
|
||||
tag = "auth",
|
||||
responses(
|
||||
(status = 200, description = "Logged out"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn logout(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
) -> StatusCode {
|
||||
if let Some(token) = extract_bearer_token(&headers) {
|
||||
// Get username before deleting session
|
||||
let username = match state.storage.get_session(token).await {
|
||||
Ok(Some(session)) => Some(session.username),
|
||||
_ => None,
|
||||
};
|
||||
let Some(token) = extract_bearer_token(&headers) else {
|
||||
return StatusCode::UNAUTHORIZED;
|
||||
};
|
||||
|
||||
// Delete session from database
|
||||
if let Err(e) = state.storage.delete_session(token).await {
|
||||
tracing::error!(error = %e, "failed to delete session from database");
|
||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
// Get username before deleting session
|
||||
let username = match state.storage.get_session(token).await {
|
||||
Ok(Some(session)) => Some(session.username),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Record logout in audit log
|
||||
if let Some(user) = username
|
||||
&& let Err(e) = pinakes_core::audit::record_action(
|
||||
&state.storage,
|
||||
None,
|
||||
pinakes_core::model::AuditAction::Logout,
|
||||
Some(format!("username: {user}")),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(error = %e, "failed to record logout audit");
|
||||
}
|
||||
// Delete session from database
|
||||
if let Err(e) = state.storage.delete_session(token).await {
|
||||
tracing::error!(error = %e, "failed to delete session from database");
|
||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
// Record logout in audit log
|
||||
if let Some(user) = username
|
||||
&& let Err(e) = pinakes_core::audit::record_action(
|
||||
&state.storage,
|
||||
None,
|
||||
pinakes_core::model::AuditAction::Logout,
|
||||
Some(format!("username: {user}")),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(error = %e, "failed to record logout audit");
|
||||
}
|
||||
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/auth/me",
|
||||
tag = "auth",
|
||||
responses(
|
||||
(status = 200, description = "Current user info", body = UserInfoResponse),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn me(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
|
|
@ -204,6 +243,17 @@ fn extract_bearer_token(headers: &HeaderMap) -> Option<&str> {
|
|||
|
||||
/// Refresh the current session, extending its expiry by the configured
|
||||
/// duration.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/auth/refresh",
|
||||
tag = "auth",
|
||||
responses(
|
||||
(status = 200, description = "Session refreshed"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn refresh(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
|
|
@ -232,6 +282,17 @@ pub async fn refresh(
|
|||
}
|
||||
|
||||
/// Revoke all sessions for the current user
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/auth/revoke-all",
|
||||
tag = "auth",
|
||||
responses(
|
||||
(status = 200, description = "All sessions revoked"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn revoke_all_sessions(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
|
|
@ -280,12 +341,12 @@ pub async fn revoke_all_sessions(
|
|||
}
|
||||
|
||||
/// List all active sessions (admin only)
|
||||
#[derive(serde::Serialize)]
|
||||
#[derive(serde::Serialize, utoipa::ToSchema)]
|
||||
pub struct SessionListResponse {
|
||||
pub sessions: Vec<SessionInfo>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[derive(serde::Serialize, utoipa::ToSchema)]
|
||||
pub struct SessionInfo {
|
||||
pub username: String,
|
||||
pub role: String,
|
||||
|
|
@ -294,6 +355,18 @@ pub struct SessionInfo {
|
|||
pub expires_at: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/auth/sessions",
|
||||
tag = "auth",
|
||||
responses(
|
||||
(status = 200, description = "Active sessions", body = SessionListResponse),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 403, description = "Forbidden"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn list_active_sessions(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<SessionListResponse>, StatusCode> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue