books/backend/Books.Api/Domain/ApiKeys/ApiKeyAggregate.cs
Nicolaj Hartmann 926085eeab Add OpenID Connect + API Key authentication
Backend:
- Cookie + OIDC + API Key authentication schemes
- ApiKeyAuthenticationHandler with SHA-256 validation and 24h cache
- AuthController with login/logout/profile endpoints
- API Key domain model (EventFlow aggregate, events, commands)
- ApiKeyReadModel and repository for key validation
- Database migration 002_ApiKeys.sql
- CORS configuration for frontend

Frontend:
- authService.ts for login/logout/profile API calls
- authStore.ts (Zustand) for user context state
- ProtectedRoute component for route guards
- Header updated with user display and logout
- GraphQL client with credentials: include

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 11:49:29 +01:00

49 lines
1.5 KiB
C#

using Books.Api.Domain.ApiKeys.Events;
using EventFlow.Aggregates;
namespace Books.Api.Domain.ApiKeys;
public class ApiKeyAggregate : AggregateRoot<ApiKeyAggregate, ApiKeyId>,
IEmit<ApiKeyCreatedEvent>,
IEmit<ApiKeyRevokedEvent>
{
public new string Name { get; private set; } = string.Empty;
public string KeyHash { get; private set; } = string.Empty;
public string CompanyId { get; private set; } = string.Empty;
public string CreatedBy { get; private set; } = string.Empty;
public bool IsActive { get; private set; } = true;
public string? RevokedBy { get; private set; }
public ApiKeyAggregate(ApiKeyId id) : base(id) { }
public void Create(string name, string keyHash, string companyId, string createdBy)
{
if (!IsNew)
throw new DomainException("APIKEY_EXISTS", "API key already exists", "API nøgle eksisterer allerede");
Emit(new ApiKeyCreatedEvent(name, keyHash, companyId, createdBy));
}
public void Revoke(string revokedBy)
{
if (!IsActive)
throw new DomainException("APIKEY_REVOKED", "API key is already revoked", "API nøgle er allerede tilbagekaldt");
Emit(new ApiKeyRevokedEvent(revokedBy));
}
public void Apply(ApiKeyCreatedEvent e)
{
Name = e.Name;
KeyHash = e.KeyHash;
CompanyId = e.CompanyId;
CreatedBy = e.CreatedBy;
IsActive = true;
}
public void Apply(ApiKeyRevokedEvent e)
{
IsActive = false;
RevokedBy = e.RevokedBy;
}
}