Add enable_api config option to disable unauthenticated API

Adds a new `enable_api` configuration option (default: true) that allows
instance administrators to disable unauthenticated API endpoints
(/api/v1/*) to prevent abuse by bots and scrapers.

When set to false, unauthenticated API requests return 403 with a JSON
error message. Authenticated endpoints (/api/v1/auth/*) and the stats
endpoint (/api/v1/stats) remain available.

This is implemented as a Kemal handler (DisableAPIHandler) that
intercepts requests before they reach the route handlers.

Fixes #5599

Signed-off-by: pierreeurope <pierre.europe@pm.me>
This commit is contained in:
pierreeurope 2026-02-16 12:46:15 +01:00
parent 11db343cfb
commit 8151f5fd7f
3 changed files with 28 additions and 0 deletions

View File

@ -217,6 +217,7 @@ end
Kemal.config.powered_by_header = false
add_handler FilteredCompressHandler.new
add_handler APIHandler.new
add_handler DisableAPIHandler.new
add_handler AuthHandler.new
add_handler DenyFrame.new

View File

@ -127,6 +127,10 @@ class Config
property login_enabled : Bool = true
property registration_enabled : Bool = true
property statistics_enabled : Bool = false
# When set to false, disables the unauthenticated API endpoints
# (videos, channels, search, etc.) that can be abused by bots.
# Authenticated API endpoints (/api/v1/auth/*) are unaffected.
property enable_api : Bool = true
property admins : Array(String) = [] of String
property external_port : Int32? = nil
property default_user_preferences : ConfigPreferences = ConfigPreferences.from_yaml("")

View File

@ -133,6 +133,29 @@ class APIHandler < Kemal::Handler
end
end
class DisableAPIHandler < Kemal::Handler
# Blocks unauthenticated API endpoints when `enable_api` is false.
# Authenticated endpoints (/api/v1/auth/*) and stats are excluded.
{% for method in %w(GET POST PUT HEAD DELETE PATCH OPTIONS) %}
only ["/api/v1/*"], {{method}}
{% end %}
exclude ["/api/v1/auth/*"], "GET"
exclude ["/api/v1/auth/*"], "POST"
exclude ["/api/v1/auth/*"], "DELETE"
exclude ["/api/v1/auth/*"], "PATCH"
exclude ["/api/v1/auth/*"], "PUT"
exclude ["/api/v1/stats"], "GET"
def call(env)
if only_match?(env) && !exclude_match?(env) && !CONFIG.enable_api
env.response.content_type = "application/json"
env.response.status_code = 403
return {"error" => "The API has been disabled by the administrator."}.to_json
end
call_next env
end
end
class DenyFrame < Kemal::Handler
exclude ["/embed/*"]