Remove duplicate source files from old ApiKeys/, Authentication/, and Authorization/ folders that were already migrated to Common/Security/. Update test namespace usings to match. Pin EFC Relational 10.0.5 to resolve MSB3277 version conflict with Npgsql's transitive dependency. Add LangVersion and TreatWarningsAsErrors to AppHost and ServiceDefaults.
143 lines
4.5 KiB
C#
Executable File
143 lines
4.5 KiB
C#
Executable File
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using MicCheck.Api.Common.Security.ApiKeys;
|
|
using MicCheck.Api.Data;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using NUnit.Framework;
|
|
|
|
namespace MicCheck.Api.Tests.Unit.Authentication;
|
|
|
|
[TestFixture]
|
|
public class ApiKeyAuthenticationHandlerTests
|
|
{
|
|
private MicCheckWebApplicationFactory _factory = null!;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
_factory = new MicCheckWebApplicationFactory();
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDown() => _factory.Dispose();
|
|
|
|
private async Task<string> SeedActiveApiKeyAsync(int organizationId = 1)
|
|
{
|
|
using var scope = _factory.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<MicCheckDbContext>();
|
|
|
|
var rawKey = ApiKeyHasher.GenerateKey();
|
|
db.ApiKeys.Add(new ApiKey
|
|
{
|
|
Key = ApiKeyHasher.Hash(rawKey),
|
|
Prefix = rawKey[..8],
|
|
Name = "Test Key",
|
|
OrganizationId = organizationId,
|
|
IsActive = true,
|
|
CreatedAt = DateTimeOffset.UtcNow
|
|
});
|
|
await db.SaveChangesAsync();
|
|
|
|
return rawKey;
|
|
}
|
|
|
|
[Test]
|
|
public async Task WhenAuthorizationHeaderIsAbsent_ThenUnauthorizedIsReturned()
|
|
{
|
|
var client = _factory.CreateClient();
|
|
|
|
var response = await client.GetAsync("/api/v1/organisations/1/api-keys/");
|
|
|
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized));
|
|
}
|
|
|
|
[Test]
|
|
public async Task WhenAuthorizationHeaderIsNotApiKeyScheme_ThenUnauthorizedIsReturned()
|
|
{
|
|
var client = _factory.CreateClient();
|
|
client.DefaultRequestHeaders.Authorization =
|
|
new AuthenticationHeaderValue("Bearer", "not-a-jwt");
|
|
|
|
var response = await client.GetAsync("/api/v1/organisations/1/api-keys/");
|
|
|
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized));
|
|
}
|
|
|
|
[Test]
|
|
public async Task WhenApiKeyIsInvalid_ThenUnauthorizedIsReturned()
|
|
{
|
|
var client = _factory.CreateClient();
|
|
client.DefaultRequestHeaders.Add("Authorization", "Api-Key not-a-real-key");
|
|
|
|
var response = await client.GetAsync("/api/v1/organisations/1/api-keys/");
|
|
|
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized));
|
|
}
|
|
|
|
[Test]
|
|
public async Task WhenApiKeyIsInactive_ThenUnauthorizedIsReturned()
|
|
{
|
|
using var scope = _factory.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<MicCheckDbContext>();
|
|
|
|
var rawKey = ApiKeyHasher.GenerateKey();
|
|
db.ApiKeys.Add(new ApiKey
|
|
{
|
|
Key = ApiKeyHasher.Hash(rawKey),
|
|
Prefix = rawKey[..8],
|
|
Name = "Inactive Key",
|
|
OrganizationId = 1,
|
|
IsActive = false,
|
|
CreatedAt = DateTimeOffset.UtcNow
|
|
});
|
|
await db.SaveChangesAsync();
|
|
|
|
var client = _factory.CreateClient();
|
|
client.DefaultRequestHeaders.Add("Authorization", $"Api-Key {rawKey}");
|
|
|
|
var response = await client.GetAsync("/api/v1/organisations/1/api-keys/");
|
|
|
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized));
|
|
}
|
|
|
|
[Test]
|
|
public async Task WhenApiKeyIsExpired_ThenUnauthorizedIsReturned()
|
|
{
|
|
using var scope = _factory.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<MicCheckDbContext>();
|
|
|
|
var rawKey = ApiKeyHasher.GenerateKey();
|
|
db.ApiKeys.Add(new ApiKey
|
|
{
|
|
Key = ApiKeyHasher.Hash(rawKey),
|
|
Prefix = rawKey[..8],
|
|
Name = "Expired Key",
|
|
OrganizationId = 1,
|
|
IsActive = true,
|
|
ExpiresAt = DateTimeOffset.UtcNow.AddDays(-1),
|
|
CreatedAt = DateTimeOffset.UtcNow.AddDays(-10)
|
|
});
|
|
await db.SaveChangesAsync();
|
|
|
|
var client = _factory.CreateClient();
|
|
client.DefaultRequestHeaders.Add("Authorization", $"Api-Key {rawKey}");
|
|
|
|
var response = await client.GetAsync("/api/v1/organisations/1/api-keys/");
|
|
|
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized));
|
|
}
|
|
|
|
[Test]
|
|
public async Task WhenApiKeyIsValid_ThenOkIsReturned()
|
|
{
|
|
var rawKey = await SeedActiveApiKeyAsync(organizationId: 1);
|
|
|
|
var client = _factory.CreateClient();
|
|
client.DefaultRequestHeaders.Add("Authorization", $"Api-Key {rawKey}");
|
|
|
|
var response = await client.GetAsync("/api/v1/organisations/1/api-keys/");
|
|
|
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
|
}
|
|
}
|