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>
55 lines
1.8 KiB
C#
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();
|
|
}
|
|
}
|