diff --git a/FictionArchive.Service.NovelService/GraphQL/Mutation.cs b/FictionArchive.Service.NovelService/GraphQL/Mutation.cs index b870439..af7724f 100644 --- a/FictionArchive.Service.NovelService/GraphQL/Mutation.cs +++ b/FictionArchive.Service.NovelService/GraphQL/Mutation.cs @@ -13,7 +13,7 @@ namespace FictionArchive.Service.NovelService.GraphQL; public class Mutation { - [Authorize(Roles = "admin")] + [Authorize] public async Task ImportNovel(string novelUrl, NovelUpdateService service) { return await service.QueueNovelImport(novelUrl); diff --git a/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs b/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs index 3e0ff6f..195af57 100644 --- a/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs +++ b/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs @@ -11,21 +11,21 @@ public class Mutation { [Error] [Error] - [Authorize(Roles = "admin")] + [Authorize(Roles = new[] { "admin" })] public async Task ScheduleEventJob(string key, string description, string eventType, string eventData, string cronSchedule, JobManagerService jobManager) { return await jobManager.ScheduleEventJob(key, description, eventType, eventData, cronSchedule); } [Error] - [Authorize(Roles = "admin")] + [Authorize(Roles = new[] { "admin" })] public async Task RunJob(string jobKey, JobManagerService jobManager) { return await jobManager.TriggerJob(jobKey); } [Error] - [Authorize(Roles = "admin")] + [Authorize(Roles = new[] { "admin" })] public async Task DeleteJob(string jobKey, JobManagerService jobManager) { bool deleted = await jobManager.DeleteJob(jobKey); diff --git a/FictionArchive.Service.Shared/Extensions/AuthenticationExtensions.cs b/FictionArchive.Service.Shared/Extensions/AuthenticationExtensions.cs index 331f9f6..eeb5503 100644 --- a/FictionArchive.Service.Shared/Extensions/AuthenticationExtensions.cs +++ b/FictionArchive.Service.Shared/Extensions/AuthenticationExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; using FictionArchive.Service.Shared.Models.Authentication; +using System.Linq; namespace FictionArchive.Service.Shared.Extensions; @@ -16,6 +17,8 @@ public static class AuthenticationExtensions { throw new InvalidOperationException("OIDC configuration is required but not found in app settings"); } + + ValidateOidcConfiguration(oidcConfig); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => @@ -45,6 +48,8 @@ public static class AuthenticationExtensions { throw new InvalidOperationException("OIDC configuration is required but not found in app settings"); } + + ValidateOidcConfiguration(oidcConfig); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => @@ -88,4 +93,29 @@ public static class AuthenticationExtensions return services; } + + private static void ValidateOidcConfiguration(OidcConfiguration config) + { + var errors = new List(); + + if (string.IsNullOrWhiteSpace(config.Authority)) + errors.Add("OIDC Authority is required"); + + if (string.IsNullOrWhiteSpace(config.ClientId)) + errors.Add("OIDC ClientId is required"); + + if (string.IsNullOrWhiteSpace(config.Audience)) + errors.Add("OIDC Audience is required"); + + if (!Uri.TryCreate(config.Authority, UriKind.Absolute, out var authorityUri)) + errors.Add($"OIDC Authority '{config.Authority}' is not a valid URI"); + else if (!authorityUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase) && + !authorityUri.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) + errors.Add("OIDC Authority must use HTTPS unless running on localhost"); + + if (errors.Any()) + { + throw new InvalidOperationException($"OIDC configuration validation failed:{Environment.NewLine}{string.Join(Environment.NewLine, errors)}"); + } + } } \ No newline at end of file