diff --git a/allstarr/Controllers/JellyfinController.cs b/allstarr/Controllers/JellyfinController.cs
index ed48ac6..ea3d893 100644
--- a/allstarr/Controllers/JellyfinController.cs
+++ b/allstarr/Controllers/JellyfinController.cs
@@ -1979,9 +1979,10 @@ public class JellyfinController : ControllerBase
///
/// Manual trigger endpoint to force fetch Spotify missing tracks.
- /// GET /spotify/sync
+ /// GET /spotify/sync?api_key=YOUR_KEY
///
[HttpGet("spotify/sync")]
+ [ServiceFilter(typeof(ApiKeyAuthFilter))]
public async Task TriggerSpotifySync()
{
if (!_spotifySettings.Enabled)
@@ -2110,9 +2111,10 @@ public class JellyfinController : ControllerBase
///
/// Clear Spotify playlist cache to force re-matching.
- /// GET /spotify/clear-cache
+ /// GET /spotify/clear-cache?api_key=YOUR_KEY
///
[HttpGet("spotify/clear-cache")]
+ [ServiceFilter(typeof(ApiKeyAuthFilter))]
public async Task ClearSpotifyCache()
{
if (!_spotifySettings.Enabled)
diff --git a/allstarr/Filters/ApiKeyAuthFilter.cs b/allstarr/Filters/ApiKeyAuthFilter.cs
new file mode 100644
index 0000000..ef403e4
--- /dev/null
+++ b/allstarr/Filters/ApiKeyAuthFilter.cs
@@ -0,0 +1,52 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.Extensions.Options;
+using allstarr.Models.Settings;
+
+namespace allstarr.Filters;
+
+///
+/// Simple API key authentication filter for admin endpoints.
+/// Validates against Jellyfin API key via query parameter or header.
+///
+public class ApiKeyAuthFilter : IAsyncActionFilter
+{
+ private readonly JellyfinSettings _settings;
+ private readonly ILogger _logger;
+
+ public ApiKeyAuthFilter(
+ IOptions settings,
+ ILogger logger)
+ {
+ _settings = settings.Value;
+ _logger = logger;
+ }
+
+ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
+ {
+ var request = context.HttpContext.Request;
+
+ // Extract API key from query parameter or header
+ var apiKey = request.Query["api_key"].FirstOrDefault()
+ ?? request.Headers["X-Api-Key"].FirstOrDefault()
+ ?? request.Headers["X-Emby-Token"].FirstOrDefault();
+
+ // Validate API key
+ if (string.IsNullOrEmpty(apiKey) || !string.Equals(apiKey, _settings.ApiKey, StringComparison.Ordinal))
+ {
+ _logger.LogWarning("Unauthorized access attempt to {Path} from {IP}",
+ request.Path,
+ context.HttpContext.Connection.RemoteIpAddress);
+
+ context.Result = new UnauthorizedObjectResult(new
+ {
+ error = "Unauthorized",
+ message = "Valid API key required. Provide via ?api_key=YOUR_KEY or X-Api-Key header."
+ });
+ return;
+ }
+
+ _logger.LogDebug("API key authentication successful for {Path}", request.Path);
+ await next();
+ }
+}
diff --git a/allstarr/Program.cs b/allstarr/Program.cs
index 5234713..44c1684 100644
--- a/allstarr/Program.cs
+++ b/allstarr/Program.cs
@@ -174,6 +174,7 @@ if (backendType == BackendType.Jellyfin)
builder.Services.AddSingleton();
builder.Services.AddScoped();
builder.Services.AddScoped();
+ builder.Services.AddScoped();
}
else
{