82 lines
2.7 KiB
C#
82 lines
2.7 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using w4c_workflows.Filters;
|
|
using w4c_workflows.Services.Quota;
|
|
|
|
namespace w4c_workflows.Controllers;
|
|
|
|
/// <summary>
|
|
/// Per-tenant workflow execution quota. <c>GET</c> returns the current period's
|
|
/// usage (drives the counter on the Workflows page). <c>POST reset</c> is a
|
|
/// platform-admin action: a tenant must not be able to zero its own counter, so
|
|
/// it additionally requires the shared <c>X-Admin-Key</c> matching
|
|
/// <c>Workflows:AdminApiKey</c> (fail-closed when unset).
|
|
/// </summary>
|
|
[ApiController]
|
|
[Route("api/workflows/quota")]
|
|
public class WorkflowQuotaController : ControllerBase
|
|
{
|
|
private readonly WorkflowQuotaService _quota;
|
|
private readonly string? _adminKey;
|
|
|
|
public WorkflowQuotaController(WorkflowQuotaService quota, IConfiguration config)
|
|
{
|
|
_quota = quota;
|
|
_adminKey = string.IsNullOrWhiteSpace(config["Workflows:AdminApiKey"])
|
|
? null
|
|
: config["Workflows:AdminApiKey"];
|
|
}
|
|
|
|
private string TenantId => (string?)HttpContext.Items["TenantId"]
|
|
?? throw new InvalidOperationException("TenantId not resolved by auth middleware");
|
|
|
|
/// <summary>Current execution usage against this tenant's monthly limit.</summary>
|
|
[HttpGet]
|
|
[RequireScope("read")]
|
|
public async Task<IActionResult> Get(CancellationToken ct)
|
|
{
|
|
return Ok(await _quota.GetAsync(TenantId, ct));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the current period's execution counter to zero. Platform admin only:
|
|
/// requires the shared admin key in addition to the operator key.
|
|
/// </summary>
|
|
[HttpPost("reset")]
|
|
[RequireScope("manage")]
|
|
public async Task<IActionResult> Reset(CancellationToken ct)
|
|
{
|
|
if (!IsPlatformAdmin())
|
|
{
|
|
return StatusCode(StatusCodes.Status403Forbidden, new
|
|
{
|
|
error = "Resetting a tenant quota requires the platform admin key " +
|
|
"(header X-Admin-Key, configured as Workflows:AdminApiKey).",
|
|
});
|
|
}
|
|
|
|
return Ok(await _quota.ResetAsync(TenantId, ct));
|
|
}
|
|
|
|
private bool IsPlatformAdmin()
|
|
{
|
|
if (_adminKey == null)
|
|
return false;
|
|
|
|
var provided = Request.Headers["X-Admin-Key"].FirstOrDefault();
|
|
return provided != null && FixedTimeEquals(provided, _adminKey);
|
|
}
|
|
|
|
private static bool FixedTimeEquals(string a, string b)
|
|
{
|
|
var aBytes = System.Text.Encoding.UTF8.GetBytes(a);
|
|
var bBytes = System.Text.Encoding.UTF8.GetBytes(b);
|
|
if (aBytes.Length != bBytes.Length)
|
|
return false;
|
|
|
|
var result = 0;
|
|
for (var i = 0; i < aBytes.Length; i++)
|
|
result |= aBytes[i] ^ bBytes[i];
|
|
return result == 0;
|
|
}
|
|
}
|