100 lines
3.9 KiB
C#
100 lines
3.9 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using Xunit;
|
|
|
|
namespace D3ROVoice.Api.Tests;
|
|
|
|
[Collection("Api server integration")]
|
|
public sealed class SttGatewayAuthorizationE2ETests : IClassFixture<AdminAuthorizationE2ETests.ApiFactory>
|
|
{
|
|
private const string Secret = "admin-e2e-jwt-secret-0123456789-abcdef";
|
|
private const string Issuer = "https://admin-e2e.test";
|
|
private const string Audience = "d3ro-admin-e2e";
|
|
private const string GatewayToken = "fixture-internal-gateway-token-32-bytes-minimum";
|
|
private readonly AdminAuthorizationE2ETests.ApiFactory _factory;
|
|
|
|
public SttGatewayAuthorizationE2ETests(AdminAuthorizationE2ETests.ApiFactory factory)
|
|
{
|
|
_factory = factory;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UserEndpointRequiresJwtThenStillRefusesQuotaBypass()
|
|
{
|
|
using var client = _factory.CreateClient();
|
|
using var anonymousBody = JsonBody();
|
|
Assert.Equal(HttpStatusCode.Unauthorized,
|
|
(await client.PostAsync("/api/stt/transcribe", anonymousBody)).StatusCode);
|
|
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", UserToken());
|
|
using var authenticatedBody = JsonBody();
|
|
var response = await client.PostAsync("/api/stt/transcribe", authenticatedBody);
|
|
|
|
Assert.Equal(HttpStatusCode.Gone, response.StatusCode);
|
|
Assert.Contains("stt_edge_gateway_required", await response.Content.ReadAsStringAsync(), StringComparison.Ordinal);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("one-character-off")]
|
|
public async Task InternalEndpointRejectsMissingOrWrongGatewaySecret(string? suppliedToken)
|
|
{
|
|
using var client = _factory.CreateClient();
|
|
if (suppliedToken != null)
|
|
client.DefaultRequestHeaders.Add("X-D3RO-STT-Gateway-Token", suppliedToken);
|
|
using var body = AudioBody();
|
|
|
|
var response = await client.PostAsync("/api/stt/internal/transcribe", body);
|
|
|
|
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
|
Assert.Contains("stt_gateway_unauthorized", await response.Content.ReadAsStringAsync(), StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InternalEndpointAcceptsExactSecretButFailsClosedWithoutProvider()
|
|
{
|
|
using var client = _factory.CreateClient();
|
|
client.DefaultRequestHeaders.Add("X-D3RO-STT-Gateway-Token", GatewayToken);
|
|
using var body = AudioBody();
|
|
|
|
var response = await client.PostAsync("/api/stt/internal/transcribe", body);
|
|
|
|
Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
|
|
Assert.Contains("stt_provider_unavailable", await response.Content.ReadAsStringAsync(), StringComparison.Ordinal);
|
|
}
|
|
|
|
private static StringContent JsonBody() =>
|
|
new("{\"audioBase64\":\"AQID\"}", Encoding.UTF8, "application/json");
|
|
|
|
private static MultipartFormDataContent AudioBody()
|
|
{
|
|
var body = new MultipartFormDataContent();
|
|
var audio = new ByteArrayContent([1, 2, 3, 4]);
|
|
audio.Headers.ContentType = new MediaTypeHeaderValue("audio/wav");
|
|
body.Add(audio, "file", "fixture.wav");
|
|
return body;
|
|
}
|
|
|
|
private static string UserToken()
|
|
{
|
|
var credentials = new SigningCredentials(
|
|
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Secret)),
|
|
SecurityAlgorithms.HmacSha256);
|
|
var token = new JwtSecurityToken(
|
|
issuer: Issuer,
|
|
audience: Audience,
|
|
claims:
|
|
[
|
|
new Claim(ClaimTypes.NameIdentifier, "1"),
|
|
new Claim(ClaimTypes.Email, "user@example.com"),
|
|
new Claim(ClaimTypes.Role, "user")
|
|
],
|
|
expires: DateTime.UtcNow.AddMinutes(5),
|
|
signingCredentials: credentials);
|
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
}
|
|
}
|