59 lines
1.9 KiB
C#
59 lines
1.9 KiB
C#
using System.Security.Claims;
|
|
using MiaoJiZhang.Api.Contracts;
|
|
using MiaoJiZhang.Infrastructure.Persistence;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Mvc.Filters;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace MiaoJiZhang.Api.Services;
|
|
|
|
public static class FeaturePermissionKeys
|
|
{
|
|
public const string Ai = "ai";
|
|
}
|
|
|
|
public sealed class AiPermissionService(AppDbContext db)
|
|
{
|
|
public async Task<bool> IsEnabledAsync(long userId, CancellationToken ct = default)
|
|
{
|
|
var value = await db.UserFeaturePermissions
|
|
.AsNoTracking()
|
|
.Where(x => x.UserId == userId && x.PermissionKey == FeaturePermissionKeys.Ai)
|
|
.Select(x => (bool?)x.IsEnabled)
|
|
.FirstOrDefaultAsync(ct);
|
|
// Existing installations remain compatible if a migration has not backfilled yet.
|
|
return value ?? true;
|
|
}
|
|
}
|
|
|
|
public sealed class RequireAiPermissionAttribute : TypeFilterAttribute
|
|
{
|
|
public RequireAiPermissionAttribute() : base(typeof(AiPermissionFilter))
|
|
{
|
|
}
|
|
}
|
|
|
|
public sealed class AiPermissionFilter(AiPermissionService permissions) : IAsyncActionFilter
|
|
{
|
|
public async Task OnActionExecutionAsync(
|
|
ActionExecutingContext context,
|
|
ActionExecutionDelegate next)
|
|
{
|
|
var value = context.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)
|
|
?? context.HttpContext.User.FindFirstValue("sub");
|
|
if (!long.TryParse(value, out var userId) ||
|
|
!await permissions.IsEnabledAsync(userId, context.HttpContext.RequestAborted))
|
|
{
|
|
context.Result = new ObjectResult(new ApiError(
|
|
"AI_PERMISSION_DENIED",
|
|
"当前账号暂无 AI 功能权限"))
|
|
{
|
|
StatusCode = StatusCodes.Status403Forbidden,
|
|
};
|
|
return;
|
|
}
|
|
|
|
await next();
|
|
}
|
|
}
|