books/backend/Books.Api/EventFlow/Repositories/ApiKeyRepository.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

55 lines
1.8 KiB
C#

using Dapper;
using Npgsql;
namespace Books.Api.EventFlow.Repositories;
public class ApiKeyRepository(NpgsqlDataSource dataSource) : IApiKeyRepository
{
public async Task<ApiKeyValidationDto?> GetByIdForValidationAsync(
string apiKeyId,
CancellationToken cancellationToken = default)
{
await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);
const string sql = """
SELECT
aggregate_id AS ApiKeyId,
name AS Name,
key_hash AS KeyHash,
company_id AS CompanyId,
is_active AS IsActive
FROM apikey_read_models
WHERE aggregate_id = @ApiKeyId
AND is_active = true
""";
return await connection.QuerySingleOrDefaultAsync<ApiKeyValidationDto>(
sql,
new { ApiKeyId = apiKeyId });
}
public async Task<IReadOnlyList<ApiKeyDto>> GetByCompanyIdAsync(
string companyId,
CancellationToken cancellationToken = default)
{
await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);
const string sql = """
SELECT
aggregate_id AS Id,
name AS Name,
company_id AS CompanyId,
created_by AS CreatedBy,
create_time AS CreatedAt,
is_active AS IsActive,
revoked_time AS RevokedAt,
revoked_by AS RevokedBy
FROM apikey_read_models
WHERE company_id = @CompanyId
ORDER BY create_time DESC
""";
var result = await connection.QueryAsync<ApiKeyDto>(sql, new { CompanyId = companyId });
return result.ToList();
}
}