Merge pull request #398 from EdouardVanbelle/feat/openapi-auth

This commit is contained in:
Dionisio Pozo
2026-05-28 01:15:28 +02:00
committed by GitHub
15 changed files with 349 additions and 40 deletions
@@ -108,6 +108,7 @@ async fn admin_guard(state: &AppState, headers: &HeaderMap) -> Result<(Uuid, Str
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn get_oidc_settings( pub async fn get_oidc_settings(
@@ -138,6 +139,7 @@ pub async fn get_oidc_settings(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn save_oidc_settings( pub async fn save_oidc_settings(
@@ -198,6 +200,7 @@ async fn test_oidc_connection(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn get_storage_settings( pub async fn get_storage_settings(
@@ -228,6 +231,7 @@ pub async fn get_storage_settings(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn save_storage_settings( pub async fn save_storage_settings(
@@ -288,6 +292,7 @@ async fn test_storage_connection(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn get_migration_status( pub async fn get_migration_status(
@@ -309,6 +314,7 @@ pub async fn get_migration_status(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn start_migration( pub async fn start_migration(
@@ -386,6 +392,7 @@ pub async fn start_migration(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn pause_migration( pub async fn pause_migration(
@@ -416,6 +423,7 @@ pub async fn pause_migration(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn resume_migration( pub async fn resume_migration(
@@ -447,6 +455,7 @@ pub async fn resume_migration(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn complete_migration( pub async fn complete_migration(
@@ -486,6 +495,7 @@ pub async fn complete_migration(
(status = 403, description = "Admin required"), (status = 403, description = "Admin required"),
(status = 500, description = "Verification failed") (status = 500, description = "Verification failed")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn verify_migration( pub async fn verify_migration(
@@ -563,6 +573,7 @@ fn migration_state_to_dto(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn generate_encryption_key( pub async fn generate_encryption_key(
@@ -619,6 +630,7 @@ fn build_backend_from_config(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn get_general_settings( pub async fn get_general_settings(
@@ -660,6 +672,7 @@ pub async fn get_general_settings(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn get_dashboard_stats( pub async fn get_dashboard_stats(
@@ -750,6 +763,7 @@ pub async fn get_dashboard_stats(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn list_users( pub async fn list_users(
@@ -798,6 +812,7 @@ pub async fn list_users(
(status = 403, description = "Admin required"), (status = 403, description = "Admin required"),
(status = 404, description = "User not found") (status = 404, description = "User not found")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn get_user( pub async fn get_user(
@@ -834,6 +849,7 @@ pub async fn get_user(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn delete_user( pub async fn delete_user(
@@ -883,6 +899,7 @@ pub async fn delete_user(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn update_user_role( pub async fn update_user_role(
@@ -933,6 +950,7 @@ pub async fn update_user_role(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn update_user_active( pub async fn update_user_active(
@@ -987,6 +1005,7 @@ pub async fn update_user_active(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn update_user_quota( pub async fn update_user_quota(
@@ -1032,6 +1051,7 @@ pub async fn update_user_quota(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn create_user( pub async fn create_user(
@@ -1072,6 +1092,7 @@ pub async fn create_user(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn reset_user_password( pub async fn reset_user_password(
@@ -1121,6 +1142,7 @@ pub async fn reset_user_password(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn get_registration_setting( pub async fn get_registration_setting(
@@ -1151,6 +1173,7 @@ pub async fn get_registration_setting(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 403, description = "Admin required") (status = 403, description = "Admin required")
), ),
security(("bearerAuth" = [])),
tag = "admin" tag = "admin"
)] )]
pub async fn set_registration_setting( pub async fn set_registration_setting(
+197 -37
View File
@@ -6,11 +6,12 @@ use axum::{
routing::{get, post, put}, routing::{get, post, put},
}; };
use std::sync::Arc; use std::sync::Arc;
use utoipa::ToSchema;
use uuid::Uuid; use uuid::Uuid;
use crate::application::dtos::user_dto::{ use crate::application::dtos::user_dto::{
ChangePasswordDto, LoginDto, OidcCallbackQueryDto, OidcExchangeDto, OidcProviderInfoDto, AuthResponseDto, ChangePasswordDto, LoginDto, OidcCallbackQueryDto, OidcExchangeDto,
RefreshTokenDto, RegisterDto, SetupAdminDto, OidcProviderInfoDto, RefreshTokenDto, RegisterDto, SetupAdminDto, UserDto,
}; };
use crate::application::services::auth_application_service::OidcCallbackResult; use crate::application::services::auth_application_service::OidcCallbackResult;
use crate::common::di::AppState; use crate::common::di::AppState;
@@ -18,7 +19,6 @@ use crate::interfaces::api::cookie_auth;
use crate::interfaces::errors::AppError; use crate::interfaces::errors::AppError;
use crate::interfaces::middleware::auth::CurrentUserId; use crate::interfaces::middleware::auth::CurrentUserId;
use serde::Deserialize; use serde::Deserialize;
use utoipa::ToSchema;
/// Public auth routes — no authentication required. /// Public auth routes — no authentication required.
pub fn auth_public_routes() -> Router<Arc<AppState>> { pub fn auth_public_routes() -> Router<Arc<AppState>> {
@@ -60,7 +60,20 @@ pub fn setup_route() -> Router<Arc<AppState>> {
Router::new().route("/setup", post(setup_admin)) Router::new().route("/setup", post(setup_admin))
} }
async fn register( /// Register a new user account.
#[utoipa::path(
post,
path = "/api/auth/register",
request_body = RegisterDto,
responses(
(status = 201, description = "User registered successfully", body = UserDto),
(status = 400, description = "Validation error"),
(status = 403, description = "Registration disabled"),
(status = 409, description = "Username or email already taken"),
),
tag = "auth"
)]
pub async fn register(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
Json(dto): Json<RegisterDto>, Json(dto): Json<RegisterDto>,
) -> Result<impl IntoResponse, AppError> { ) -> Result<impl IntoResponse, AppError> {
@@ -122,7 +135,23 @@ async fn register(
} }
} }
async fn login( /// Authenticate with username and password.
///
/// On success, sets `oxicloud_access`, `oxicloud_refresh`, and `oxicloud_csrf`
/// HttpOnly cookies in addition to returning the tokens in the JSON body.
#[utoipa::path(
post,
path = "/api/auth/login",
request_body = LoginDto,
responses(
(status = 200, description = "Login successful — tokens in body and cookies", body = AuthResponseDto),
(status = 401, description = "Invalid credentials or password login disabled"),
(status = 403, description = "Account disabled"),
(status = 429, description = "Account temporarily locked (too many failed attempts)"),
),
tag = "auth"
)]
pub async fn login(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
headers: HeaderMap, headers: HeaderMap,
Json(dto): Json<LoginDto>, Json(dto): Json<LoginDto>,
@@ -238,10 +267,25 @@ async fn login(
} }
} }
/// Token refresh — accepts the refresh token from **either**: /// Refresh an access token.
/// 1. JSON body `{ "refresh_token": "..." }` (API clients, backward compat) ///
/// 2. HttpOnly cookie `oxicloud_refresh` (browsers) /// Accepts the refresh token from **either**:
async fn refresh_token( /// 1. JSON body `{ "refresh_token": "..." }` (API clients / backward compat)
/// 2. HttpOnly `oxicloud_refresh` cookie (browsers)
///
/// Issues new access + refresh tokens and rotates all three auth cookies.
#[utoipa::path(
post,
path = "/api/auth/refresh",
request_body(content = inline(RefreshTokenDto),
description = "Optional — omit when using the HttpOnly cookie"),
responses(
(status = 200, description = "New tokens issued", body = AuthResponseDto),
(status = 401, description = "Refresh token missing, expired, or revoked"),
),
tag = "auth"
)]
pub async fn refresh_token(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
headers: HeaderMap, headers: HeaderMap,
body: axum::body::Bytes, body: axum::body::Bytes,
@@ -283,7 +327,18 @@ async fn refresh_token(
Ok(response) Ok(response)
} }
async fn get_current_user( /// Return the authenticated user's profile, including live storage usage.
#[utoipa::path(
get,
path = "/api/auth/me",
responses(
(status = 200, description = "Current user profile", body = UserDto),
(status = 401, description = "Not authenticated"),
),
security(("bearerAuth" = [])),
tag = "auth"
)]
pub async fn get_current_user(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
CurrentUserId(user_id): CurrentUserId, CurrentUserId(user_id): CurrentUserId,
) -> Result<impl IntoResponse, AppError> { ) -> Result<impl IntoResponse, AppError> {
@@ -330,7 +385,20 @@ pub struct UpdateUserImageDto {
pub image: Option<String>, pub image: Option<String>,
} }
async fn change_password( /// Change the current user's password.
#[utoipa::path(
put,
path = "/api/auth/change-password",
request_body = ChangePasswordDto,
responses(
(status = 200, description = "Password changed successfully"),
(status = 400, description = "New password does not meet requirements"),
(status = 401, description = "Not authenticated or current password incorrect"),
),
security(("bearerAuth" = [])),
tag = "auth"
)]
pub async fn change_password(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
CurrentUserId(user_id): CurrentUserId, CurrentUserId(user_id): CurrentUserId,
Json(dto): Json<ChangePasswordDto>, Json(dto): Json<ChangePasswordDto>,
@@ -348,6 +416,7 @@ async fn change_password(
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
// TODO: add utoipa
pub async fn update_user_image( pub async fn update_user_image(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
CurrentUserId(user_id): CurrentUserId, CurrentUserId(user_id): CurrentUserId,
@@ -371,7 +440,24 @@ pub async fn update_user_image(
} }
} }
async fn logout( /// Revoke the current session and clear auth cookies.
///
/// Accepts the refresh token from **either** a JSON body
/// `{ "refresh_token": "..." }` (API clients) or the `oxicloud_refresh`
/// HttpOnly cookie (browsers).
#[utoipa::path(
post,
path = "/api/auth/logout",
request_body(content = inline(RefreshTokenDto),
description = "Optional — omit when using the HttpOnly cookie"),
responses(
(status = 200, description = "Logged out, auth cookies cleared"),
(status = 401, description = "Not authenticated or refresh token missing"),
),
security(("bearerAuth" = [])),
tag = "auth"
)]
pub async fn logout(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
CurrentUserId(user_id): CurrentUserId, CurrentUserId(user_id): CurrentUserId,
headers: HeaderMap, headers: HeaderMap,
@@ -405,16 +491,23 @@ async fn logout(
Ok(response) Ok(response)
} }
/// POST /api/setup — One-time endpoint to create the first admin user. /// One-time endpoint to create the first admin user.
/// ///
/// Available only when the system is not yet initialized (no admin exists). /// Available only when the system is not yet initialized (no admin exists).
/// Once the admin is created, the system is marked as initialized and this /// Once the admin is created the endpoint permanently returns 403.
/// endpoint returns 403 for all subsequent requests. /// Uses an atomic "claim" operation so concurrent requests cannot both succeed.
/// #[utoipa::path(
/// Uses an atomic "claim" operation to prevent race conditions: even if two post,
/// requests arrive simultaneously, only one will succeed in marking the path = "/api/setup",
/// system as initialized and creating the admin. request_body = SetupAdminDto,
async fn setup_admin( responses(
(status = 201, description = "First admin created and system initialized", body = UserDto),
(status = 403, description = "System already initialized"),
(status = 503, description = "Auth service not configured"),
),
tag = "auth"
)]
pub async fn setup_admin(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
Json(dto): Json<SetupAdminDto>, Json(dto): Json<SetupAdminDto>,
) -> Result<impl IntoResponse, AppError> { ) -> Result<impl IntoResponse, AppError> {
@@ -498,19 +591,28 @@ async fn setup_admin(
Ok((StatusCode::CREATED, Json(user))) Ok((StatusCode::CREATED, Json(user)))
} }
/// Get system status - returns whether admin is configured /// System initialisation state, returned by `GET /api/auth/status`.
/// This is a public endpoint used to determine if setup is needed #[derive(serde::Serialize, ToSchema)]
#[derive(serde::Serialize)] pub struct SystemStatus {
struct SystemStatus { /// Whether the system has been set up with an admin.
/// Whether the system has been set up with an admin
initialized: bool, initialized: bool,
/// Number of admin users in the system /// Number of admin users in the system.
admin_count: i64, admin_count: i64,
/// Whether registration is allowed (only if admin exists) /// Whether self-registration is allowed.
registration_allowed: bool, registration_allowed: bool,
} }
async fn get_system_status( /// Return the system initialisation state (used by the UI before setup).
#[utoipa::path(
get,
path = "/api/auth/status",
responses(
(status = 200, description = "System status", body = SystemStatus),
(status = 503, description = "Auth service not configured"),
),
tag = "auth"
)]
pub async fn get_system_status(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
) -> Result<impl IntoResponse, AppError> { ) -> Result<impl IntoResponse, AppError> {
let auth_service = state let auth_service = state
@@ -552,8 +654,21 @@ async fn get_system_status(
// OIDC Handlers // OIDC Handlers
// ============================================================================ // ============================================================================
/// GET /api/auth/oidc/providers — Returns OIDC provider info for the UI /// Return OIDC provider information for the login UI.
async fn oidc_providers(State(state): State<Arc<AppState>>) -> Result<impl IntoResponse, AppError> { ///
/// Returns `enabled: false` when OIDC is not configured.
#[utoipa::path(
get,
path = "/api/auth/oidc/providers",
responses(
(status = 200, description = "OIDC provider info (enabled=false when OIDC not configured)", body = OidcProviderInfoDto),
(status = 503, description = "Auth service not configured"),
),
tag = "auth"
)]
pub async fn oidc_providers(
State(state): State<Arc<AppState>>,
) -> Result<impl IntoResponse, AppError> {
let auth_service = state let auth_service = state
.auth_service .auth_service
.as_ref() .as_ref()
@@ -580,8 +695,23 @@ async fn oidc_providers(State(state): State<Arc<AppState>>) -> Result<impl IntoR
})) }))
} }
/// GET /api/auth/oidc/authorize — Redirects user to the OIDC provider /// Initiate OIDC authorization — redirects to the configured identity provider.
async fn oidc_authorize(State(state): State<Arc<AppState>>) -> Result<impl IntoResponse, AppError> { ///
/// Generates PKCE, CSRF state, and nonce then issues a 302 redirect to the
/// provider's authorization endpoint.
#[utoipa::path(
get,
path = "/api/auth/oidc/authorize",
responses(
(status = 302, description = "Redirect to OIDC provider authorization URL"),
(status = 404, description = "OIDC not enabled"),
(status = 503, description = "Auth service not configured"),
),
tag = "auth"
)]
pub async fn oidc_authorize(
State(state): State<Arc<AppState>>,
) -> Result<impl IntoResponse, AppError> {
let auth_service = state let auth_service = state
.auth_service .auth_service
.as_ref() .as_ref()
@@ -605,8 +735,26 @@ async fn oidc_authorize(State(state): State<Arc<AppState>>) -> Result<impl IntoR
Ok(Redirect::temporary(&authorize_url)) Ok(Redirect::temporary(&authorize_url))
} }
/// GET /api/auth/oidc/callback?code=...&state=... — Handles OIDC callback /// Handle the OIDC provider callback.
async fn oidc_callback( ///
/// Validates the `state` / PKCE / nonce, exchanges the code for tokens, then
/// redirects the browser to the frontend with a short-lived exchange code
/// (`/?oidc_code=…`).
#[utoipa::path(
get,
path = "/api/auth/oidc/callback",
params(
("code" = String, Query, description = "Authorization code from the OIDC provider"),
("state" = String, Query, description = "CSRF state value echoed by the provider"),
),
responses(
(status = 302, description = "Redirect to frontend with one-time exchange code"),
(status = 401, description = "OIDC validation failed (bad state, nonce, or code)"),
(status = 404, description = "OIDC not enabled"),
),
tag = "auth"
)]
pub async fn oidc_callback(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
Query(query): Query<OidcCallbackQueryDto>, Query(query): Query<OidcCallbackQueryDto>,
) -> Result<impl IntoResponse, AppError> { ) -> Result<impl IntoResponse, AppError> {
@@ -694,9 +842,21 @@ async fn oidc_callback(
} }
} }
/// POST /api/auth/oidc/exchange — Exchange one-time code for auth tokens /// Exchange a one-time OIDC code for access + refresh tokens.
/// Request body: { "code": "<one_time_code>" } ///
async fn oidc_exchange( /// The frontend calls this after being redirected back with `?oidc_code=…`.
/// The exchange code is valid for a single use and expires in 60 s.
#[utoipa::path(
post,
path = "/api/auth/oidc/exchange",
request_body = OidcExchangeDto,
responses(
(status = 200, description = "Tokens issued, auth cookies set", body = AuthResponseDto),
(status = 401, description = "Exchange code invalid or expired"),
),
tag = "auth"
)]
pub async fn oidc_exchange(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
Json(body): Json<OidcExchangeDto>, Json(body): Json<OidcExchangeDto>,
) -> Result<Response, AppError> { ) -> Result<Response, AppError> {
@@ -145,6 +145,7 @@ where
(status = 400, description = "Bad request"), (status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized") (status = 401, description = "Unauthorized")
), ),
security(("bearerAuth" = [])),
tag = "batch" tag = "batch"
)] )]
pub async fn move_files_batch( pub async fn move_files_batch(
@@ -212,6 +213,7 @@ pub async fn move_files_batch(
(status = 400, description = "Bad request"), (status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized") (status = 401, description = "Unauthorized")
), ),
security(("bearerAuth" = [])),
tag = "batch" tag = "batch"
)] )]
pub async fn copy_files_batch( pub async fn copy_files_batch(
@@ -279,6 +281,7 @@ pub async fn copy_files_batch(
(status = 400, description = "Bad request"), (status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized") (status = 401, description = "Unauthorized")
), ),
security(("bearerAuth" = [])),
tag = "batch" tag = "batch"
)] )]
pub async fn delete_files_batch( pub async fn delete_files_batch(
@@ -354,6 +357,7 @@ pub async fn delete_files_batch(
(status = 400, description = "Bad request"), (status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized") (status = 401, description = "Unauthorized")
), ),
security(("bearerAuth" = [])),
tag = "batch" tag = "batch"
)] )]
pub async fn delete_folders_batch( pub async fn delete_folders_batch(
@@ -429,6 +433,7 @@ pub async fn delete_folders_batch(
(status = 400, description = "Bad request"), (status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized") (status = 401, description = "Unauthorized")
), ),
security(("bearerAuth" = [])),
tag = "batch" tag = "batch"
)] )]
pub async fn create_folders_batch( pub async fn create_folders_batch(
@@ -503,6 +508,7 @@ pub async fn create_folders_batch(
(status = 400, description = "Bad request"), (status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized") (status = 401, description = "Unauthorized")
), ),
security(("bearerAuth" = [])),
tag = "batch" tag = "batch"
)] )]
pub async fn get_files_batch( pub async fn get_files_batch(
@@ -570,6 +576,7 @@ pub async fn get_files_batch(
(status = 400, description = "Bad request"), (status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized") (status = 401, description = "Unauthorized")
), ),
security(("bearerAuth" = [])),
tag = "batch" tag = "batch"
)] )]
pub async fn get_folders_batch( pub async fn get_folders_batch(
@@ -677,6 +684,7 @@ impl From<BatchDownloadQuery> for BatchDownloadRequest {
(status = 400, description = "Bad request"), (status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized") (status = 401, description = "Unauthorized")
), ),
security(("bearerAuth" = [])),
tag = "batch" tag = "batch"
)] )]
pub async fn trash_batch( pub async fn trash_batch(
@@ -800,6 +808,7 @@ pub async fn trash_batch(
(status = 400, description = "Bad request"), (status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized") (status = 401, description = "Unauthorized")
), ),
security(("bearerAuth" = [])),
tag = "batch" tag = "batch"
)] )]
pub async fn move_folders_batch( pub async fn move_folders_batch(
@@ -884,6 +893,7 @@ impl From<CopyFolderTreeResult> for CopiedFolderDto {
(status = 400, description = "Bad request"), (status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized") (status = 401, description = "Unauthorized")
), ),
security(("bearerAuth" = [])),
tag = "batch" tag = "batch"
)] )]
pub async fn copy_folders_batch( pub async fn copy_folders_batch(
@@ -951,6 +961,7 @@ pub async fn copy_folders_batch(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 500, description = "ZIP creation failed") (status = 500, description = "ZIP creation failed")
), ),
security(("bearerAuth" = [])),
tag = "batch" tag = "batch"
)] )]
pub async fn download_batch_querystring( pub async fn download_batch_querystring(
@@ -973,6 +984,7 @@ pub async fn download_batch_querystring(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 500, description = "ZIP creation failed") (status = 500, description = "ZIP creation failed")
), ),
security(("bearerAuth" = [])),
tag = "batch" tag = "batch"
)] )]
pub async fn download_batch_post( pub async fn download_batch_post(
@@ -221,6 +221,7 @@ fn user_to_contact(user: UserDto) -> ContactDto {
(status = 200, description = "List of address books"), (status = 200, description = "List of address books"),
(status = 500, description = "Internal server error"), (status = 500, description = "Internal server error"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn list_address_books( pub async fn list_address_books(
@@ -289,6 +290,7 @@ pub async fn list_address_books(
(status = 201, description = "Address book created", body = AddressBookResponse), (status = 201, description = "Address book created", body = AddressBookResponse),
(status = 400, description = "Invalid input"), (status = 400, description = "Invalid input"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn create_address_book( pub async fn create_address_book(
@@ -336,6 +338,7 @@ pub async fn create_address_book(
(status = 403, description = "Only the owner can update"), (status = 403, description = "Only the owner can update"),
(status = 404, description = "Address book not found"), (status = 404, description = "Address book not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn update_address_book( pub async fn update_address_book(
@@ -391,6 +394,7 @@ pub async fn update_address_book(
(status = 403, description = "Only the owner can delete"), (status = 403, description = "Only the owner can delete"),
(status = 404, description = "Address book not found"), (status = 404, description = "Address book not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn delete_address_book( pub async fn delete_address_book(
@@ -431,6 +435,7 @@ pub async fn delete_address_book(
(status = 403, description = "Access denied"), (status = 403, description = "Access denied"),
(status = 404, description = "Address book not found"), (status = 404, description = "Address book not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn list_contacts( pub async fn list_contacts(
@@ -487,6 +492,7 @@ pub async fn list_contacts(
(status = 403, description = "Access denied or read-only book"), (status = 403, description = "Access denied or read-only book"),
(status = 404, description = "Address book not found"), (status = 404, description = "Address book not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn create_contact( pub async fn create_contact(
@@ -541,6 +547,7 @@ pub async fn create_contact(
(status = 403, description = "Access denied"), (status = 403, description = "Access denied"),
(status = 404, description = "Contact not found"), (status = 404, description = "Contact not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn get_contact( pub async fn get_contact(
@@ -607,6 +614,7 @@ pub async fn get_contact(
(status = 404, description = "Contact not found"), (status = 404, description = "Contact not found"),
(status = 412, description = "ETag mismatch — contact was modified"), (status = 412, description = "ETag mismatch — contact was modified"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn update_contact( pub async fn update_contact(
@@ -687,6 +695,7 @@ pub async fn update_contact(
(status = 404, description = "Contact not found"), (status = 404, description = "Contact not found"),
(status = 412, description = "ETag mismatch"), (status = 412, description = "ETag mismatch"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn delete_contact( pub async fn delete_contact(
@@ -747,6 +756,7 @@ pub async fn delete_contact(
(status = 403, description = "Access denied"), (status = 403, description = "Access denied"),
(status = 404, description = "Address book not found"), (status = 404, description = "Address book not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn list_groups( pub async fn list_groups(
@@ -780,6 +790,7 @@ pub async fn list_groups(
(status = 403, description = "Access denied or read-only book"), (status = 403, description = "Access denied or read-only book"),
(status = 404, description = "Address book not found"), (status = 404, description = "Address book not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn create_group( pub async fn create_group(
@@ -818,6 +829,7 @@ pub async fn create_group(
(status = 403, description = "Access denied"), (status = 403, description = "Access denied"),
(status = 404, description = "Group not found"), (status = 404, description = "Group not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn get_group( pub async fn get_group(
@@ -858,6 +870,7 @@ pub async fn get_group(
(status = 403, description = "Access denied or read-only book"), (status = 403, description = "Access denied or read-only book"),
(status = 404, description = "Group not found"), (status = 404, description = "Group not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn update_group( pub async fn update_group(
@@ -895,6 +908,7 @@ pub async fn update_group(
(status = 403, description = "Access denied or read-only book"), (status = 403, description = "Access denied or read-only book"),
(status = 404, description = "Group not found"), (status = 404, description = "Group not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn delete_group( pub async fn delete_group(
@@ -933,6 +947,7 @@ pub async fn delete_group(
(status = 403, description = "Access denied"), (status = 403, description = "Access denied"),
(status = 404, description = "Group not found"), (status = 404, description = "Group not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn list_contacts_in_group( pub async fn list_contacts_in_group(
@@ -974,6 +989,7 @@ pub async fn list_contacts_in_group(
(status = 403, description = "Access denied or read-only book"), (status = 403, description = "Access denied or read-only book"),
(status = 404, description = "Group or contact not found"), (status = 404, description = "Group or contact not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn add_contact_to_group( pub async fn add_contact_to_group(
@@ -1016,6 +1032,7 @@ pub async fn add_contact_to_group(
(status = 403, description = "Access denied or read-only book"), (status = 403, description = "Access denied or read-only book"),
(status = 404, description = "Group or contact not found"), (status = 404, description = "Group or contact not found"),
), ),
security(("bearerAuth" = [])),
tag = "contacts" tag = "contacts"
)] )]
pub async fn remove_contact_from_group( pub async fn remove_contact_from_group(
@@ -33,6 +33,7 @@ pub struct BatchFavoritesRequest {
responses( responses(
(status = 200, description = "List of favorites", body = Vec<crate::application::dtos::favorites_dto::FavoriteItemDto>) (status = 200, description = "List of favorites", body = Vec<crate::application::dtos::favorites_dto::FavoriteItemDto>)
), ),
security(("bearerAuth" = [])),
tag = "favorites" tag = "favorites"
)] )]
pub async fn get_favorites( pub async fn get_favorites(
@@ -75,6 +76,7 @@ pub async fn get_favorites(
(status = 201, description = "Item added to favorites"), (status = 201, description = "Item added to favorites"),
(status = 400, description = "Invalid item type") (status = 400, description = "Invalid item type")
), ),
security(("bearerAuth" = [])),
tag = "favorites" tag = "favorites"
)] )]
pub async fn add_favorite( pub async fn add_favorite(
@@ -131,6 +133,7 @@ pub async fn add_favorite(
(status = 200, description = "Item removed from favorites"), (status = 200, description = "Item removed from favorites"),
(status = 404, description = "Item not in favorites") (status = 404, description = "Item not in favorites")
), ),
security(("bearerAuth" = [])),
tag = "favorites" tag = "favorites"
)] )]
pub async fn remove_favorite( pub async fn remove_favorite(
@@ -184,6 +187,7 @@ pub async fn remove_favorite(
(status = 200, description = "Batch add result", body = crate::application::dtos::favorites_dto::BatchFavoritesResult), (status = 200, description = "Batch add result", body = crate::application::dtos::favorites_dto::BatchFavoritesResult),
(status = 400, description = "Invalid request") (status = 400, description = "Invalid request")
), ),
security(("bearerAuth" = [])),
tag = "favorites" tag = "favorites"
)] )]
pub async fn batch_add_favorites( pub async fn batch_add_favorites(
@@ -1080,6 +1080,7 @@ pub(super) fn build_content_disposition(name: &str, mime: &str, force_inline: bo
(status = 200, description = "List of files", body = Vec<FileDto>), (status = 200, description = "List of files", body = Vec<FileDto>),
(status = 304, description = "Not modified"), (status = 304, description = "Not modified"),
), ),
security(("bearerAuth" = [])),
tag = "files" tag = "files"
)] )]
pub async fn list_files_query( pub async fn list_files_query(
@@ -1100,6 +1101,7 @@ pub async fn list_files_query(
(status = 400, description = "Invalid request"), (status = 400, description = "Invalid request"),
(status = 507, description = "Storage quota exceeded"), (status = 507, description = "Storage quota exceeded"),
), ),
security(("bearerAuth" = [])),
tag = "files" tag = "files"
)] )]
pub async fn upload_file_with_thumbnails( pub async fn upload_file_with_thumbnails(
@@ -1125,6 +1127,7 @@ pub async fn upload_file_with_thumbnails(
(status = 304, description = "Not modified"), (status = 304, description = "Not modified"),
(status = 404, description = "File not found"), (status = 404, description = "File not found"),
), ),
security(("bearerAuth" = [])),
tag = "files" tag = "files"
)] )]
pub async fn download_file( pub async fn download_file(
@@ -1150,6 +1153,7 @@ pub async fn download_file(
(status = 304, description = "Not modified"), (status = 304, description = "Not modified"),
(status = 404, description = "File not found"), (status = 404, description = "File not found"),
), ),
security(("bearerAuth" = [])),
tag = "files" tag = "files"
)] )]
pub async fn get_thumbnail( pub async fn get_thumbnail(
@@ -1174,6 +1178,7 @@ pub async fn get_thumbnail(
(status = 400, description = "Invalid image or size too large"), (status = 400, description = "Invalid image or size too large"),
(status = 404, description = "File not found"), (status = 404, description = "File not found"),
), ),
security(("bearerAuth" = [])),
tag = "files" tag = "files"
)] )]
pub async fn upload_thumbnail( pub async fn upload_thumbnail(
@@ -1193,6 +1198,7 @@ pub async fn upload_thumbnail(
(status = 200, description = "File metadata (EXIF, dimensions, duration, etc.)"), (status = 200, description = "File metadata (EXIF, dimensions, duration, etc.)"),
(status = 404, description = "File not found"), (status = 404, description = "File not found"),
), ),
security(("bearerAuth" = [])),
tag = "files" tag = "files"
)] )]
pub async fn get_file_metadata( pub async fn get_file_metadata(
@@ -1211,6 +1217,7 @@ pub async fn get_file_metadata(
(status = 204, description = "File deleted (moved to trash if enabled)"), (status = 204, description = "File deleted (moved to trash if enabled)"),
(status = 404, description = "File not found"), (status = 404, description = "File not found"),
), ),
security(("bearerAuth" = [])),
tag = "files" tag = "files"
)] )]
pub async fn delete_file( pub async fn delete_file(
@@ -1230,6 +1237,7 @@ pub async fn delete_file(
(status = 200, description = "Renamed file", body = FileDto), (status = 200, description = "Renamed file", body = FileDto),
(status = 404, description = "File not found"), (status = 404, description = "File not found"),
), ),
security(("bearerAuth" = [])),
tag = "files" tag = "files"
)] )]
pub async fn rename_file( pub async fn rename_file(
@@ -1250,6 +1258,7 @@ pub async fn rename_file(
(status = 200, description = "Moved file", body = FileDto), (status = 200, description = "Moved file", body = FileDto),
(status = 404, description = "File or destination not found"), (status = 404, description = "File or destination not found"),
), ),
security(("bearerAuth" = [])),
tag = "files" tag = "files"
)] )]
pub async fn move_file_simple( pub async fn move_file_simple(
@@ -420,6 +420,7 @@ impl FolderHandler {
(status = 201, description = "Folder created", body = FolderDto), (status = 201, description = "Folder created", body = FolderDto),
(status = 400, description = "Invalid request"), (status = 400, description = "Invalid request"),
), ),
security(("bearerAuth" = [])),
tag = "folders" tag = "folders"
)] )]
pub async fn create_folder( pub async fn create_folder(
@@ -438,6 +439,7 @@ pub async fn create_folder(
(status = 200, description = "Folder", body = FolderDto), (status = 200, description = "Folder", body = FolderDto),
(status = 404, description = "Folder not found"), (status = 404, description = "Folder not found"),
), ),
security(("bearerAuth" = [])),
tag = "folders" tag = "folders"
)] )]
pub async fn get_folder( pub async fn get_folder(
@@ -454,6 +456,7 @@ pub async fn get_folder(
responses( responses(
(status = 200, description = "List of root folders", body = Vec<FolderDto>), (status = 200, description = "List of root folders", body = Vec<FolderDto>),
), ),
security(("bearerAuth" = [])),
tag = "folders" tag = "folders"
)] )]
pub async fn list_root_folders( pub async fn list_root_folders(
@@ -471,6 +474,7 @@ pub async fn list_root_folders(
(status = 200, description = "List of sub-folders", body = Vec<FolderDto>), (status = 200, description = "List of sub-folders", body = Vec<FolderDto>),
(status = 404, description = "Folder not found"), (status = 404, description = "Folder not found"),
), ),
security(("bearerAuth" = [])),
tag = "folders" tag = "folders"
)] )]
pub async fn list_folder_contents( pub async fn list_folder_contents(
@@ -488,6 +492,7 @@ pub async fn list_folder_contents(
responses( responses(
(status = 200, description = "Paginated list of root folders"), (status = 200, description = "Paginated list of root folders"),
), ),
security(("bearerAuth" = [])),
tag = "folders" tag = "folders"
)] )]
pub async fn list_root_folders_paginated( pub async fn list_root_folders_paginated(
@@ -509,6 +514,7 @@ pub async fn list_root_folders_paginated(
(status = 200, description = "Paginated list of sub-folders"), (status = 200, description = "Paginated list of sub-folders"),
(status = 404, description = "Folder not found"), (status = 404, description = "Folder not found"),
), ),
security(("bearerAuth" = [])),
tag = "folders" tag = "folders"
)] )]
pub async fn list_folder_contents_paginated( pub async fn list_folder_contents_paginated(
@@ -529,6 +535,7 @@ pub async fn list_folder_contents_paginated(
(status = 304, description = "Not modified"), (status = 304, description = "Not modified"),
(status = 404, description = "Folder not found"), (status = 404, description = "Folder not found"),
), ),
security(("bearerAuth" = [])),
tag = "folders" tag = "folders"
)] )]
pub async fn list_folder_listing( pub async fn list_folder_listing(
@@ -549,6 +556,7 @@ pub async fn list_folder_listing(
(status = 200, description = "Renamed folder", body = FolderDto), (status = 200, description = "Renamed folder", body = FolderDto),
(status = 404, description = "Folder not found"), (status = 404, description = "Folder not found"),
), ),
security(("bearerAuth" = [])),
tag = "folders" tag = "folders"
)] )]
pub async fn rename_folder( pub async fn rename_folder(
@@ -569,6 +577,7 @@ pub async fn rename_folder(
(status = 200, description = "Moved folder", body = FolderDto), (status = 200, description = "Moved folder", body = FolderDto),
(status = 404, description = "Folder or destination not found"), (status = 404, description = "Folder or destination not found"),
), ),
security(("bearerAuth" = [])),
tag = "folders" tag = "folders"
)] )]
pub async fn move_folder( pub async fn move_folder(
@@ -588,6 +597,7 @@ pub async fn move_folder(
(status = 204, description = "Folder deleted"), (status = 204, description = "Folder deleted"),
(status = 404, description = "Folder not found"), (status = 404, description = "Folder not found"),
), ),
security(("bearerAuth" = [])),
tag = "folders" tag = "folders"
)] )]
pub async fn delete_folder_with_trash( pub async fn delete_folder_with_trash(
@@ -607,6 +617,7 @@ pub async fn delete_folder_with_trash(
(status = 404, description = "Folder not found"), (status = 404, description = "Folder not found"),
(status = 501, description = "ZIP service not available"), (status = 501, description = "ZIP service not available"),
), ),
security(("bearerAuth" = [])),
tag = "folders" tag = "folders"
)] )]
pub async fn download_folder_zip( pub async fn download_folder_zip(
@@ -50,6 +50,7 @@ type AppStateRef = Arc<AppState>;
(status = 400, description = "Invalid input (both/neither of permissions+role provided)"), (status = 400, description = "Invalid input (both/neither of permissions+role provided)"),
(status = 404, description = "Resource not found OR caller lacks Share permission"), (status = 404, description = "Resource not found OR caller lacks Share permission"),
), ),
security(("bearerAuth" = [])),
tag = "grants" tag = "grants"
)] )]
pub async fn create_grant( pub async fn create_grant(
@@ -125,6 +126,7 @@ pub async fn create_grant(
(status = 204, description = "Grant revoked (or did not exist)"), (status = 204, description = "Grant revoked (or did not exist)"),
(status = 404, description = "Caller lacks Share permission on the underlying resource"), (status = 404, description = "Caller lacks Share permission on the underlying resource"),
), ),
security(("bearerAuth" = [])),
tag = "grants" tag = "grants"
)] )]
pub async fn revoke_grant( pub async fn revoke_grant(
@@ -174,6 +176,7 @@ pub async fn revoke_grant(
(status = 200, description = "Role applied; returns the new full grant set", body = Vec<GrantDto>), (status = 200, description = "Role applied; returns the new full grant set", body = Vec<GrantDto>),
(status = 404, description = "Resource not found or caller lacks Share"), (status = 404, description = "Resource not found or caller lacks Share"),
), ),
security(("bearerAuth" = [])),
tag = "grants" tag = "grants"
)] )]
pub async fn set_role( pub async fn set_role(
@@ -261,6 +264,7 @@ pub struct IncomingQuery {
responses( responses(
(status = 200, description = "Direct grants targeting the caller", body = Vec<GrantDto>), (status = 200, description = "Direct grants targeting the caller", body = Vec<GrantDto>),
), ),
security(("bearerAuth" = [])),
tag = "grants" tag = "grants"
)] )]
pub async fn list_incoming( pub async fn list_incoming(
@@ -298,6 +302,7 @@ pub async fn list_incoming(
last page.", last page.",
body = SharedWithMeDto), body = SharedWithMeDto),
), ),
security(("bearerAuth" = [])),
tag = "grants" tag = "grants"
)] )]
pub async fn list_shared_with_me( pub async fn list_shared_with_me(
@@ -456,6 +461,7 @@ pub async fn list_shared_with_me(
responses( responses(
(status = 200, description = "Grants the caller has created", body = Vec<GrantDto>), (status = 200, description = "Grants the caller has created", body = Vec<GrantDto>),
), ),
security(("bearerAuth" = [])),
tag = "grants" tag = "grants"
)] )]
pub async fn list_outgoing( pub async fn list_outgoing(
@@ -491,6 +497,7 @@ pub struct OnResourceQuery {
(status = 200, description = "Grants on the specified resource", body = Vec<GrantDto>), (status = 200, description = "Grants on the specified resource", body = Vec<GrantDto>),
(status = 404, description = "Resource not found or caller lacks Share"), (status = 404, description = "Resource not found or caller lacks Share"),
), ),
security(("bearerAuth" = [])),
tag = "grants" tag = "grants"
)] )]
pub async fn list_on_resource( pub async fn list_on_resource(
@@ -31,6 +31,7 @@ pub struct PaginationQuery {
(status = 400, description = "Bad request"), (status = 400, description = "Bad request"),
(status = 401, description = "Unauthorized") (status = 401, description = "Unauthorized")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn create_playlist( pub async fn create_playlist(
@@ -53,6 +54,7 @@ pub async fn create_playlist(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 404, description = "Playlist not found") (status = 404, description = "Playlist not found")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn get_playlist( pub async fn get_playlist(
@@ -73,6 +75,7 @@ pub async fn get_playlist(
(status = 200, description = "List of playlists"), (status = 200, description = "List of playlists"),
(status = 401, description = "Unauthorized") (status = 401, description = "Unauthorized")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn list_playlists( pub async fn list_playlists(
@@ -101,6 +104,7 @@ pub struct IncludeSharedQuery {
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 404, description = "Playlist not found") (status = 404, description = "Playlist not found")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn update_playlist( pub async fn update_playlist(
@@ -127,6 +131,7 @@ pub async fn update_playlist(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 404, description = "Playlist not found") (status = 404, description = "Playlist not found")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn delete_playlist( pub async fn delete_playlist(
@@ -152,6 +157,7 @@ pub async fn delete_playlist(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 404, description = "Playlist not found") (status = 404, description = "Playlist not found")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn add_tracks( pub async fn add_tracks(
@@ -181,6 +187,7 @@ pub async fn add_tracks(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 404, description = "Playlist or track not found") (status = 404, description = "Playlist or track not found")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn remove_track( pub async fn remove_track(
@@ -206,6 +213,7 @@ pub async fn remove_track(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 404, description = "Playlist not found") (status = 404, description = "Playlist not found")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn reorder_tracks( pub async fn reorder_tracks(
@@ -232,6 +240,7 @@ pub async fn reorder_tracks(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 404, description = "Playlist not found") (status = 404, description = "Playlist not found")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn list_playlist_tracks( pub async fn list_playlist_tracks(
@@ -257,6 +266,7 @@ pub async fn list_playlist_tracks(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 404, description = "Playlist not found") (status = 404, description = "Playlist not found")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn share_playlist( pub async fn share_playlist(
@@ -286,6 +296,7 @@ pub async fn share_playlist(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 404, description = "Playlist or share not found") (status = 404, description = "Playlist or share not found")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn remove_share( pub async fn remove_share(
@@ -311,6 +322,7 @@ pub async fn remove_share(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 404, description = "Playlist not found") (status = 404, description = "Playlist not found")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn get_playlist_shares( pub async fn get_playlist_shares(
@@ -336,6 +348,7 @@ pub async fn get_playlist_shares(
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 404, description = "File not found") (status = 404, description = "File not found")
), ),
security(("bearerAuth" = [])),
tag = "playlists" tag = "playlists"
)] )]
pub async fn get_audio_metadata( pub async fn get_audio_metadata(
@@ -38,6 +38,7 @@ pub struct PhotosQueryParams {
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 500, description = "Internal server error") (status = 500, description = "Internal server error")
), ),
security(("bearerAuth" = [])),
tag = "photos" tag = "photos"
)] )]
pub async fn list_photos( pub async fn list_photos(
@@ -26,6 +26,7 @@ pub struct GetRecentParams {
responses( responses(
(status = 200, description = "List of recent items", body = Vec<crate::application::dtos::recent_dto::RecentItemDto>) (status = 200, description = "List of recent items", body = Vec<crate::application::dtos::recent_dto::RecentItemDto>)
), ),
security(("bearerAuth" = [])),
tag = "recent" tag = "recent"
)] )]
pub async fn get_recent_items( pub async fn get_recent_items(
@@ -65,6 +66,7 @@ pub async fn get_recent_items(
(status = 200, description = "Access recorded"), (status = 200, description = "Access recorded"),
(status = 400, description = "Invalid item type") (status = 400, description = "Invalid item type")
), ),
security(("bearerAuth" = [])),
tag = "recent" tag = "recent"
)] )]
pub async fn record_item_access( pub async fn record_item_access(
@@ -124,6 +126,7 @@ pub async fn record_item_access(
(status = 200, description = "Item removed from recents"), (status = 200, description = "Item removed from recents"),
(status = 404, description = "Item not in recents") (status = 404, description = "Item not in recents")
), ),
security(("bearerAuth" = [])),
tag = "recent" tag = "recent"
)] )]
pub async fn remove_from_recent( pub async fn remove_from_recent(
@@ -178,6 +181,7 @@ pub async fn remove_from_recent(
responses( responses(
(status = 200, description = "Recent items cleared") (status = 200, description = "Recent items cleared")
), ),
security(("bearerAuth" = [])),
tag = "recent" tag = "recent"
)] )]
pub async fn clear_recent_items( pub async fn clear_recent_items(
@@ -299,6 +299,7 @@ pub struct SuggestParams {
(status = 200, description = "Search results", body = SearchResultsDto), (status = 200, description = "Search results", body = SearchResultsDto),
(status = 503, description = "Search service unavailable"), (status = 503, description = "Search service unavailable"),
), ),
security(("bearerAuth" = [])),
tag = "search" tag = "search"
)] )]
pub async fn search_files_get( pub async fn search_files_get(
@@ -317,6 +318,7 @@ pub async fn search_files_get(
(status = 200, description = "Search results", body = SearchResultsDto), (status = 200, description = "Search results", body = SearchResultsDto),
(status = 503, description = "Search service unavailable"), (status = 503, description = "Search service unavailable"),
), ),
security(("bearerAuth" = [])),
tag = "search" tag = "search"
)] )]
pub async fn search_files_post( pub async fn search_files_post(
@@ -339,6 +341,7 @@ pub async fn search_files_post(
(status = 200, description = "Suggestions", body = SearchSuggestionsDto), (status = 200, description = "Suggestions", body = SearchSuggestionsDto),
(status = 503, description = "Search service unavailable"), (status = 503, description = "Search service unavailable"),
), ),
security(("bearerAuth" = [])),
tag = "search" tag = "search"
)] )]
pub async fn suggest_files( pub async fn suggest_files(
@@ -355,6 +358,7 @@ pub async fn suggest_files(
(status = 200, description = "Cache cleared"), (status = 200, description = "Cache cleared"),
(status = 503, description = "Search service unavailable"), (status = 503, description = "Search service unavailable"),
), ),
security(("bearerAuth" = [])),
tag = "search" tag = "search"
)] )]
pub async fn clear_search_cache(state: State<Arc<AppState>>) -> impl IntoResponse { pub async fn clear_search_cache(state: State<Arc<AppState>>) -> impl IntoResponse {
@@ -63,6 +63,7 @@ pub struct VerifyPasswordRequest {
(status = 201, description = "Share created", body = crate::application::dtos::share_dto::ShareDto), (status = 201, description = "Share created", body = crate::application::dtos::share_dto::ShareDto),
(status = 400, description = "Bad request") (status = 400, description = "Bad request")
), ),
security(("bearerAuth" = [])),
tag = "shares" tag = "shares"
)] )]
pub async fn create_shared_link( pub async fn create_shared_link(
@@ -85,6 +86,7 @@ pub async fn create_shared_link(
(status = 200, description = "Share details", body = crate::application::dtos::share_dto::ShareDto), (status = 200, description = "Share details", body = crate::application::dtos::share_dto::ShareDto),
(status = 404, description = "Share not found") (status = 404, description = "Share not found")
), ),
security(("bearerAuth" = [])),
tag = "shares" tag = "shares"
)] )]
pub async fn get_shared_link( pub async fn get_shared_link(
@@ -110,6 +112,7 @@ pub async fn get_shared_link(
responses( responses(
(status = 200, description = "List of shares", body = Vec<crate::application::dtos::share_dto::ShareDto>) (status = 200, description = "List of shares", body = Vec<crate::application::dtos::share_dto::ShareDto>)
), ),
security(("bearerAuth" = [])),
tag = "shares" tag = "shares"
)] )]
pub async fn get_user_shares( pub async fn get_user_shares(
@@ -163,6 +166,7 @@ pub async fn get_user_shares(
(status = 200, description = "Share updated", body = crate::application::dtos::share_dto::ShareDto), (status = 200, description = "Share updated", body = crate::application::dtos::share_dto::ShareDto),
(status = 404, description = "Share not found") (status = 404, description = "Share not found")
), ),
security(("bearerAuth" = [])),
tag = "shares" tag = "shares"
)] )]
pub async fn update_shared_link( pub async fn update_shared_link(
@@ -193,6 +197,7 @@ pub async fn update_shared_link(
(status = 204, description = "Share deleted"), (status = 204, description = "Share deleted"),
(status = 404, description = "Share not found") (status = 404, description = "Share not found")
), ),
security(("bearerAuth" = [])),
tag = "shares" tag = "shares"
)] )]
pub async fn delete_shared_link( pub async fn delete_shared_link(
@@ -17,6 +17,7 @@ use std::sync::Arc;
(status = 200, description = "List of trashed items"), (status = 200, description = "List of trashed items"),
(status = 501, description = "Trash feature not enabled") (status = 501, description = "Trash feature not enabled")
), ),
security(("bearerAuth" = [])),
tag = "trash" tag = "trash"
)] )]
#[instrument(skip_all)] #[instrument(skip_all)]
@@ -71,6 +72,7 @@ pub async fn get_trash_items(
(status = 200, description = "File moved to trash"), (status = 200, description = "File moved to trash"),
(status = 501, description = "Trash feature not enabled") (status = 501, description = "Trash feature not enabled")
), ),
security(("bearerAuth" = [])),
tag = "trash" tag = "trash"
)] )]
#[instrument(skip_all)] #[instrument(skip_all)]
@@ -132,6 +134,7 @@ pub async fn move_file_to_trash(
(status = 200, description = "Folder moved to trash"), (status = 200, description = "Folder moved to trash"),
(status = 501, description = "Trash feature not enabled") (status = 501, description = "Trash feature not enabled")
), ),
security(("bearerAuth" = [])),
tag = "trash" tag = "trash"
)] )]
#[instrument(skip_all)] #[instrument(skip_all)]
@@ -195,6 +198,7 @@ pub async fn move_folder_to_trash(
(status = 200, description = "Item restored from trash"), (status = 200, description = "Item restored from trash"),
(status = 501, description = "Trash feature not enabled") (status = 501, description = "Trash feature not enabled")
), ),
security(("bearerAuth" = [])),
tag = "trash" tag = "trash"
)] )]
#[instrument(skip_all)] #[instrument(skip_all)]
@@ -266,6 +270,7 @@ pub async fn restore_from_trash(
(status = 200, description = "Item permanently deleted"), (status = 200, description = "Item permanently deleted"),
(status = 501, description = "Trash feature not enabled") (status = 501, description = "Trash feature not enabled")
), ),
security(("bearerAuth" = [])),
tag = "trash" tag = "trash"
)] )]
#[instrument(skip_all)] #[instrument(skip_all)]
@@ -338,6 +343,7 @@ pub async fn delete_permanently(
(status = 200, description = "Trash emptied successfully"), (status = 200, description = "Trash emptied successfully"),
(status = 501, description = "Trash feature not enabled") (status = 501, description = "Trash feature not enabled")
), ),
security(("bearerAuth" = [])),
tag = "trash" tag = "trash"
)] )]
#[instrument(skip_all)] #[instrument(skip_all)]
+36 -3
View File
@@ -7,7 +7,8 @@ pub use routes::create_api_routes;
pub use routes::create_health_routes; pub use routes::create_health_routes;
pub use routes::create_public_api_routes; pub use routes::create_public_api_routes;
use utoipa::OpenApi; use utoipa::openapi::security::{Http, HttpAuthScheme, SecurityScheme};
use utoipa::{Modify, OpenApi};
use crate::application::dtos::contact_dto::{ use crate::application::dtos::contact_dto::{
AddressDto, ContactDto, ContactGroupDto, EmailDto, PhoneDto, AddressDto, ContactDto, ContactGroupDto, EmailDto, PhoneDto,
@@ -40,12 +41,13 @@ use crate::application::dtos::trash_dto::{
DeletePermanentlyRequest, MoveToTrashRequest, RestoreFromTrashRequest, TrashedItemDto, DeletePermanentlyRequest, MoveToTrashRequest, RestoreFromTrashRequest, TrashedItemDto,
}; };
use crate::application::dtos::user_dto::{ use crate::application::dtos::user_dto::{
AuthResponseDto, ChangePasswordDto, LoginDto, RefreshTokenDto, RegisterDto, SetupAdminDto, AuthResponseDto, ChangePasswordDto, LoginDto, OidcExchangeDto, OidcProviderInfoDto,
UserDto, RefreshTokenDto, RegisterDto, SetupAdminDto, UserDto,
}; };
use crate::application::ports::chunked_upload_ports::{ use crate::application::ports::chunked_upload_ports::{
ChunkUploadResponseDto, CreateUploadResponseDto, UploadStatusResponseDto, ChunkUploadResponseDto, CreateUploadResponseDto, UploadStatusResponseDto,
}; };
use crate::interfaces::api::handlers::auth_handler::SystemStatus;
use crate::interfaces::api::handlers::chunked_upload_handler::{ use crate::interfaces::api::handlers::chunked_upload_handler::{
CompleteUploadResponse, CreateUploadRequest, CompleteUploadResponse, CreateUploadRequest,
}; };
@@ -60,7 +62,21 @@ use crate::interfaces::api::handlers::file_handler::MoveFilePayload;
#[derive(OpenApi)] #[derive(OpenApi)]
#[openapi( #[openapi(
modifiers(&SecurityAddon),
paths( paths(
// Auth handlers (public, protected, OIDC)
handlers::auth_handler::register,
handlers::auth_handler::login,
handlers::auth_handler::refresh_token,
handlers::auth_handler::get_current_user,
handlers::auth_handler::change_password,
handlers::auth_handler::logout,
handlers::auth_handler::setup_admin,
handlers::auth_handler::get_system_status,
handlers::auth_handler::oidc_providers,
handlers::auth_handler::oidc_authorize,
handlers::auth_handler::oidc_callback,
handlers::auth_handler::oidc_exchange,
// File handlers (free functions — see file_handler.rs for why) // File handlers (free functions — see file_handler.rs for why)
handlers::file_handler::list_files_query, handlers::file_handler::list_files_query,
handlers::file_handler::upload_file_with_thumbnails, handlers::file_handler::upload_file_with_thumbnails,
@@ -236,6 +252,9 @@ use crate::interfaces::api::handlers::file_handler::MoveFilePayload;
AuthResponseDto, AuthResponseDto,
ChangePasswordDto, ChangePasswordDto,
RefreshTokenDto, RefreshTokenDto,
SystemStatus,
OidcProviderInfoDto,
OidcExchangeDto,
// Share schemas // Share schemas
ShareDto, ShareDto,
SharePermissionsDto, SharePermissionsDto,
@@ -302,6 +321,7 @@ use crate::interfaces::api::handlers::file_handler::MoveFilePayload;
) )
), ),
tags( tags(
(name = "auth", description = "Authentication and session management endpoints"),
(name = "files", description = "File management endpoints"), (name = "files", description = "File management endpoints"),
(name = "folders", description = "Folder management endpoints"), (name = "folders", description = "Folder management endpoints"),
(name = "trash", description = "Trash / recycle bin endpoints"), (name = "trash", description = "Trash / recycle bin endpoints"),
@@ -328,6 +348,19 @@ use crate::interfaces::api::handlers::file_handler::MoveFilePayload;
)] )]
pub struct ApiDoc; pub struct ApiDoc;
/// Injects the `bearerAuth` HTTP Bearer security scheme into the generated spec.
struct SecurityAddon;
impl Modify for SecurityAddon {
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
let components = openapi.components.get_or_insert_with(Default::default);
components.add_security_scheme(
"bearerAuth",
SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)),
);
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;