using Microsoft.AspNetCore.Mvc; using w4c_workflows.Filters; using w4c_workflows.Services.Quota; namespace w4c_workflows.Controllers; /// /// Per-tenant workflow execution quota. GET returns the current period's /// usage (drives the counter on the Workflows page). POST reset is a /// platform-admin action: a tenant must not be able to zero its own counter, so /// it additionally requires the shared X-Admin-Key matching /// Workflows:AdminApiKey (fail-closed when unset). /// [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"); /// Current execution usage against this tenant's monthly limit. [HttpGet] [RequireScope("read")] public async Task Get(CancellationToken ct) { return Ok(await _quota.GetAsync(TenantId, ct)); } /// /// Resets the current period's execution counter to zero. Platform admin only: /// requires the shared admin key in addition to the operator key. /// [HttpPost("reset")] [RequireScope("manage")] public async Task 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; } }