75 lines
2.7 KiB
C#
75 lines
2.7 KiB
C#
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<ApplicationUser> users)
|
|
: AuthorizationHandler<ProEntitlementRequirement>
|
|
{
|
|
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<ProEntitlementRequirement>().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);
|
|
}
|
|
}
|