fix: address authentication system issues

- Fix GraphQL authorization attributes to use string[] instead of string for roles
- Remove admin role requirement from ImportNovel endpoint
- Add comprehensive OIDC configuration validation with specific error messages
- Validate Authority, ClientId, and Audience are properly configured
- Ensure HTTPS requirement except for localhost development

Co-authored-by: conco <conco@users.noreply.local>
This commit is contained in:
Claude
2025-11-27 16:20:09 +00:00
parent 78612ea29d
commit 9c82d648cd
3 changed files with 34 additions and 4 deletions

View File

@@ -13,7 +13,7 @@ namespace FictionArchive.Service.NovelService.GraphQL;
public class Mutation public class Mutation
{ {
[Authorize(Roles = "admin")] [Authorize]
public async Task<NovelUpdateRequestedEvent> ImportNovel(string novelUrl, NovelUpdateService service) public async Task<NovelUpdateRequestedEvent> ImportNovel(string novelUrl, NovelUpdateService service)
{ {
return await service.QueueNovelImport(novelUrl); return await service.QueueNovelImport(novelUrl);

View File

@@ -11,21 +11,21 @@ public class Mutation
{ {
[Error<DuplicateNameException>] [Error<DuplicateNameException>]
[Error<FormatException>] [Error<FormatException>]
[Authorize(Roles = "admin")] [Authorize(Roles = new[] { "admin" })]
public async Task<SchedulerJob> ScheduleEventJob(string key, string description, string eventType, string eventData, string cronSchedule, JobManagerService jobManager) public async Task<SchedulerJob> ScheduleEventJob(string key, string description, string eventType, string eventData, string cronSchedule, JobManagerService jobManager)
{ {
return await jobManager.ScheduleEventJob(key, description, eventType, eventData, cronSchedule); return await jobManager.ScheduleEventJob(key, description, eventType, eventData, cronSchedule);
} }
[Error<JobPersistenceException>] [Error<JobPersistenceException>]
[Authorize(Roles = "admin")] [Authorize(Roles = new[] { "admin" })]
public async Task<bool> RunJob(string jobKey, JobManagerService jobManager) public async Task<bool> RunJob(string jobKey, JobManagerService jobManager)
{ {
return await jobManager.TriggerJob(jobKey); return await jobManager.TriggerJob(jobKey);
} }
[Error<KeyNotFoundException>] [Error<KeyNotFoundException>]
[Authorize(Roles = "admin")] [Authorize(Roles = new[] { "admin" })]
public async Task<bool> DeleteJob(string jobKey, JobManagerService jobManager) public async Task<bool> DeleteJob(string jobKey, JobManagerService jobManager)
{ {
bool deleted = await jobManager.DeleteJob(jobKey); bool deleted = await jobManager.DeleteJob(jobKey);

View File

@@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using FictionArchive.Service.Shared.Models.Authentication; using FictionArchive.Service.Shared.Models.Authentication;
using System.Linq;
namespace FictionArchive.Service.Shared.Extensions; 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"); throw new InvalidOperationException("OIDC configuration is required but not found in app settings");
} }
ValidateOidcConfiguration(oidcConfig);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => .AddJwtBearer(options =>
@@ -45,6 +48,8 @@ public static class AuthenticationExtensions
{ {
throw new InvalidOperationException("OIDC configuration is required but not found in app settings"); throw new InvalidOperationException("OIDC configuration is required but not found in app settings");
} }
ValidateOidcConfiguration(oidcConfig);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => .AddJwtBearer(options =>
@@ -88,4 +93,29 @@ public static class AuthenticationExtensions
return services; return services;
} }
private static void ValidateOidcConfiguration(OidcConfiguration config)
{
var errors = new List<string>();
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)}");
}
}
} }