refactor
This commit is contained in:
+24
-162
@@ -3,63 +3,19 @@ use axum::{
|
||||
http::StatusCode,
|
||||
Json,
|
||||
};
|
||||
use chrono::{DateTime, NaiveDateTime};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use utoipa::OpenApi;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::auth::{
|
||||
api_tokens::{
|
||||
create_api_token as issue_token, list_api_tokens as load_tokens,
|
||||
regenerate_api_token as rotate_token, revoke_api_token as revoke_token,
|
||||
use crate::{
|
||||
auth::{passkeys::PasskeySummary, TenantScopedConn},
|
||||
error::AppResult,
|
||||
http::responders::JsonResponse,
|
||||
services::profile::{
|
||||
ApiTokenCreatedResponse, ApiTokenResponse, CreateApiTokenRequest, ProfileService,
|
||||
RevokePasskeyQuery,
|
||||
},
|
||||
capability_sets::load_capability_set,
|
||||
passkeys::PasskeySummary,
|
||||
TenantScopedConn,
|
||||
state::AppState,
|
||||
};
|
||||
use crate::error::{AppError, AppResult};
|
||||
use crate::http::responders::{created_json, no_content, ok_json, JsonResponse};
|
||||
use crate::models::ApiToken;
|
||||
use crate::state::{AppState, PgPooledConnection};
|
||||
use crate::utils::time::to_iso;
|
||||
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct ApiTokenResponse {
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
#[schema(nullable)]
|
||||
pub label: Option<String>,
|
||||
pub capability_set_id: Uuid,
|
||||
pub created_at: String,
|
||||
#[schema(nullable)]
|
||||
pub last_used_at: Option<String>,
|
||||
#[schema(nullable)]
|
||||
pub expires_at: Option<String>,
|
||||
#[schema(nullable)]
|
||||
pub revoked_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct ApiTokenCreatedResponse {
|
||||
pub token: String,
|
||||
pub token_info: ApiTokenResponse,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreateApiTokenRequest {
|
||||
#[schema(nullable)]
|
||||
pub label: Option<String>,
|
||||
#[schema(nullable)]
|
||||
pub expires_at: Option<String>,
|
||||
pub capability_set_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct RevokePasskeyQuery {
|
||||
#[serde(default)]
|
||||
#[schema(nullable)]
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
@@ -73,13 +29,7 @@ pub async fn list_passkeys(
|
||||
mut conn, user_id, ..
|
||||
}: TenantScopedConn,
|
||||
) -> AppResult<JsonResponse<Vec<PasskeySummary>>> {
|
||||
let service = state
|
||||
.passkeys
|
||||
.as_ref()
|
||||
.ok_or_else(|| AppError::bad_request("passkey support is disabled"))?;
|
||||
|
||||
let passkeys = service.list_for_user(&mut conn, user_id)?;
|
||||
ok_json(passkeys)
|
||||
ProfileService::new(&state).list_passkeys(&mut conn, user_id)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -89,6 +39,7 @@ pub async fn list_passkeys(
|
||||
tag = "Profile"
|
||||
)]
|
||||
pub async fn list_api_tokens(
|
||||
State(state): State<AppState>,
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
tenant_id,
|
||||
@@ -96,9 +47,7 @@ pub async fn list_api_tokens(
|
||||
..
|
||||
}: TenantScopedConn,
|
||||
) -> AppResult<JsonResponse<Vec<ApiTokenResponse>>> {
|
||||
let tokens = load_tokens(&mut conn, user_id, Some(tenant_id))?;
|
||||
let responses = tokens.into_iter().map(api_token_to_response).collect();
|
||||
ok_json(responses)
|
||||
ProfileService::new(&state).list_api_tokens(&mut conn, tenant_id, user_id)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -109,6 +58,7 @@ pub async fn list_api_tokens(
|
||||
tag = "Profile"
|
||||
)]
|
||||
pub async fn create_api_token(
|
||||
State(state): State<AppState>,
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
tenant_id,
|
||||
@@ -117,31 +67,7 @@ pub async fn create_api_token(
|
||||
}: TenantScopedConn,
|
||||
Json(payload): Json<CreateApiTokenRequest>,
|
||||
) -> AppResult<JsonResponse<ApiTokenCreatedResponse>> {
|
||||
let expires_at = match payload.expires_at {
|
||||
Some(ref value) => Some(parse_timestamp(value)?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let capability_set_id =
|
||||
validate_capability_set(&mut conn, tenant_id, payload.capability_set_id)?;
|
||||
|
||||
let issued = issue_token(
|
||||
&mut conn,
|
||||
user_id,
|
||||
tenant_id,
|
||||
payload.label.clone(),
|
||||
expires_at,
|
||||
capability_set_id,
|
||||
)?;
|
||||
|
||||
let token_info = api_token_to_response(issued.record);
|
||||
|
||||
let response = ApiTokenCreatedResponse {
|
||||
token: issued.token,
|
||||
token_info,
|
||||
};
|
||||
|
||||
created_json(response)
|
||||
ProfileService::new(&state).create_api_token(&mut conn, tenant_id, user_id, payload)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -152,6 +78,7 @@ pub async fn create_api_token(
|
||||
tag = "Profile"
|
||||
)]
|
||||
pub async fn regenerate_api_token(
|
||||
State(state): State<AppState>,
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
tenant_id,
|
||||
@@ -160,14 +87,7 @@ pub async fn regenerate_api_token(
|
||||
}: TenantScopedConn,
|
||||
Path(token_id): Path<Uuid>,
|
||||
) -> AppResult<JsonResponse<ApiTokenCreatedResponse>> {
|
||||
let issued = rotate_token(&mut conn, token_id, user_id, Some(tenant_id))?;
|
||||
let token_info = api_token_to_response(issued.record);
|
||||
let response = ApiTokenCreatedResponse {
|
||||
token: issued.token,
|
||||
token_info,
|
||||
};
|
||||
|
||||
ok_json(response)
|
||||
ProfileService::new(&state).regenerate_api_token(&mut conn, tenant_id, user_id, token_id)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -178,13 +98,13 @@ pub async fn regenerate_api_token(
|
||||
tag = "Profile"
|
||||
)]
|
||||
pub async fn delete_api_token(
|
||||
State(state): State<AppState>,
|
||||
TenantScopedConn {
|
||||
mut conn, user_id, ..
|
||||
}: TenantScopedConn,
|
||||
Path(token_id): Path<Uuid>,
|
||||
) -> AppResult<StatusCode> {
|
||||
revoke_token(&mut conn, token_id, user_id)?;
|
||||
no_content()
|
||||
ProfileService::new(&state).delete_api_token(&mut conn, user_id, token_id)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -205,68 +125,10 @@ pub async fn delete_passkey(
|
||||
Path(passkey_id): Path<Uuid>,
|
||||
Query(query): Query<RevokePasskeyQuery>,
|
||||
) -> AppResult<StatusCode> {
|
||||
let service = state
|
||||
.passkeys
|
||||
.as_ref()
|
||||
.ok_or_else(|| AppError::bad_request("passkey support is disabled"))?;
|
||||
|
||||
let active_count = service.active_passkey_count(&mut conn, user_id)?;
|
||||
if active_count <= 1 {
|
||||
return Err(AppError::bad_request(
|
||||
"cannot revoke the last remaining passkey",
|
||||
));
|
||||
}
|
||||
|
||||
service.revoke_passkey(&mut conn, user_id, passkey_id, query.reason)?;
|
||||
no_content()
|
||||
ProfileService::new(&state).delete_passkey(&mut conn, user_id, passkey_id, query.reason)
|
||||
}
|
||||
|
||||
fn api_token_to_response(token: ApiToken) -> ApiTokenResponse {
|
||||
let ApiToken {
|
||||
id,
|
||||
tenant_id,
|
||||
label,
|
||||
created_at,
|
||||
last_used_at,
|
||||
expires_at,
|
||||
revoked_at,
|
||||
capability_set_id,
|
||||
..
|
||||
} = token;
|
||||
|
||||
ApiTokenResponse {
|
||||
id,
|
||||
tenant_id,
|
||||
label,
|
||||
capability_set_id,
|
||||
created_at: to_iso(created_at),
|
||||
last_used_at: last_used_at.map(to_iso),
|
||||
expires_at: expires_at.map(to_iso),
|
||||
revoked_at: revoked_at.map(to_iso),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_timestamp(value: &str) -> AppResult<NaiveDateTime> {
|
||||
let dt = DateTime::parse_from_rfc3339(value)
|
||||
.map_err(|_| AppError::bad_request("invalid expires_at timestamp"))?;
|
||||
Ok(dt.naive_utc())
|
||||
}
|
||||
|
||||
fn validate_capability_set(
|
||||
conn: &mut PgPooledConnection,
|
||||
tenant_id: Uuid,
|
||||
capability_set_id: Uuid,
|
||||
) -> AppResult<Uuid> {
|
||||
let set = load_capability_set(conn, capability_set_id)?;
|
||||
if set.tenant_id != tenant_id {
|
||||
return Err(AppError::bad_request(
|
||||
"capability set does not belong to the tenant",
|
||||
));
|
||||
}
|
||||
Ok(set.id)
|
||||
}
|
||||
|
||||
#[derive(utoipa::OpenApi)]
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
crate::routes::profile::list_api_tokens,
|
||||
@@ -278,10 +140,10 @@ fn validate_capability_set(
|
||||
),
|
||||
components(schemas(
|
||||
crate::models::ApiCapability,
|
||||
crate::routes::profile::ApiTokenResponse,
|
||||
crate::routes::profile::ApiTokenCreatedResponse,
|
||||
crate::routes::profile::CreateApiTokenRequest,
|
||||
crate::routes::profile::RevokePasskeyQuery,
|
||||
crate::services::profile::ApiTokenResponse,
|
||||
crate::services::profile::ApiTokenCreatedResponse,
|
||||
crate::services::profile::CreateApiTokenRequest,
|
||||
crate::services::profile::RevokePasskeyQuery,
|
||||
crate::auth::passkeys::PasskeySummary
|
||||
))
|
||||
)]
|
||||
|
||||
Reference in New Issue
Block a user