87 lines
3.5 KiB
C#
87 lines
3.5 KiB
C#
using Microsoft.Data.Sqlite;
|
|
|
|
namespace MxGateway.Server.Security.Authentication;
|
|
|
|
public sealed class SqliteApiKeyStore(AuthSqliteConnectionFactory connectionFactory) : IApiKeyStore
|
|
{
|
|
public Task<ApiKeyRecord?> FindByKeyIdAsync(string keyId, CancellationToken cancellationToken)
|
|
{
|
|
return FindByKeyIdAsync(keyId, requireActive: false, cancellationToken);
|
|
}
|
|
|
|
public Task<ApiKeyRecord?> 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<ApiKeyRecord?> 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 ReadApiKeyRecord(reader);
|
|
}
|
|
|
|
private static ApiKeyRecord ReadApiKeyRecord(SqliteDataReader reader)
|
|
{
|
|
return new ApiKeyRecord(
|
|
KeyId: reader.GetString(0),
|
|
KeyPrefix: reader.GetString(1),
|
|
SecretHash: (byte[])reader["secret_hash"],
|
|
DisplayName: reader.GetString(3),
|
|
Scopes: ApiKeyScopeSerializer.Deserialize(reader.GetString(4)),
|
|
CreatedUtc: DateTimeOffset.Parse(reader.GetString(5), System.Globalization.CultureInfo.InvariantCulture),
|
|
LastUsedUtc: ReadNullableDateTimeOffset(reader, 6),
|
|
RevokedUtc: ReadNullableDateTimeOffset(reader, 7));
|
|
}
|
|
|
|
private static DateTimeOffset? ReadNullableDateTimeOffset(SqliteDataReader reader, int ordinal)
|
|
{
|
|
return reader.IsDBNull(ordinal)
|
|
? null
|
|
: DateTimeOffset.Parse(reader.GetString(ordinal), System.Globalization.CultureInfo.InvariantCulture);
|
|
}
|
|
}
|