using System.Security.Claims; using System.Text.Json; using JobTrackerApi.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Identity; namespace JobTrackerApi.Services; public static class ProEntitlement { public const string Policy = "Pro"; public const string RequiredCode = "pro_required"; public const string DisabledCode = "ai_disabled"; } public sealed class ProEntitlementRequirement : IAuthorizationRequirement; public sealed class ProEntitlementHandler(UserManager users) : AuthorizationHandler { protected override async Task HandleRequirementAsync( AuthorizationHandlerContext context, ProEntitlementRequirement requirement) { var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? context.User.FindFirstValue("sub"); var user = string.IsNullOrWhiteSpace(userId) ? null : await users.FindByIdAsync(userId); if (user is null || !AccountPlans.ForRoles(await users.GetRolesAsync(user)).Ai) { context.Fail(new AuthorizationFailureReason(this, ProEntitlement.RequiredCode)); return; } if (!user.AiEnabled) { context.Fail(new AuthorizationFailureReason(this, ProEntitlement.DisabledCode)); return; } context.Succeed(requirement); } } public sealed class ProEntitlementAuthorizationResultHandler : IAuthorizationMiddlewareResultHandler { private readonly AuthorizationMiddlewareResultHandler _fallback = new(); public async Task HandleAsync( RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) { if (authorizeResult.Forbidden && policy.Requirements.OfType().Any()) { var aiDisabled = authorizeResult.AuthorizationFailure?.FailureReasons .Any(reason => reason.Message == ProEntitlement.DisabledCode) == true; context.Response.StatusCode = StatusCodes.Status403Forbidden; context.Response.ContentType = "application/json"; await JsonSerializer.SerializeAsync(context.Response.Body, new { code = aiDisabled ? ProEntitlement.DisabledCode : ProEntitlement.RequiredCode, message = aiDisabled ? "AI is disabled in your privacy settings." : "This AI feature requires Pro.", }, cancellationToken: context.RequestAborted); return; } await _fallback.HandleAsync(next, context, policy, authorizeResult); } }