using Microsoft.Data.Sqlite; namespace MxGateway.Server.Security.Authentication; public sealed class SqliteApiKeyStore(AuthSqliteConnectionFactory connectionFactory) : IApiKeyStore { public Task FindByKeyIdAsync(string keyId, CancellationToken cancellationToken) { return FindByKeyIdAsync(keyId, requireActive: false, cancellationToken); } public Task FindActiveByKeyIdAsync(string keyId, CancellationToken cancellationToken) { return FindByKeyIdAsync(keyId, requireActive: true, cancellationToken); } public async Task MarkKeyUsedAsync(string keyId, DateTimeOffset usedUtc, CancellationToken cancellationToken) { await using SqliteConnection connection = connectionFactory.CreateConnection(); await connection.OpenAsync(cancellationToken).ConfigureAwait(false); await using SqliteCommand command = connection.CreateCommand(); command.CommandText = """ UPDATE api_keys SET last_used_utc = $last_used_utc WHERE key_id = $key_id AND revoked_utc IS NULL; """; command.Parameters.AddWithValue("$key_id", keyId); command.Parameters.AddWithValue("$last_used_utc", usedUtc.ToString("O")); await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); } private async Task FindByKeyIdAsync( string keyId, bool requireActive, CancellationToken cancellationToken) { await using SqliteConnection connection = connectionFactory.CreateConnection(); await connection.OpenAsync(cancellationToken).ConfigureAwait(false); await using SqliteCommand command = connection.CreateCommand(); command.CommandText = requireActive ? """ SELECT key_id, key_prefix, secret_hash, display_name, scopes, created_utc, last_used_utc, revoked_utc FROM api_keys WHERE key_id = $key_id AND revoked_utc IS NULL; """ : """ SELECT key_id, key_prefix, secret_hash, display_name, scopes, created_utc, last_used_utc, revoked_utc FROM api_keys WHERE key_id = $key_id; """; command.Parameters.AddWithValue("$key_id", keyId); await using SqliteDataReader reader = await command.ExecuteReaderAsync(cancellationToken) .ConfigureAwait(false); if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { return null; } return ApiKeyRecordReader.Read(reader); } }