117 lines
4.4 KiB
C#
117 lines
4.4 KiB
C#
using Microsoft.Data.Sqlite;
|
|
|
|
namespace MxGateway.Server.Security.Authentication;
|
|
|
|
public sealed class SqliteApiKeyAdminStore(AuthSqliteConnectionFactory connectionFactory) : IApiKeyAdminStore
|
|
{
|
|
public async Task CreateAsync(ApiKeyCreateRequest request, CancellationToken cancellationToken)
|
|
{
|
|
await using SqliteConnection connection = connectionFactory.CreateConnection();
|
|
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
await using SqliteCommand command = connection.CreateCommand();
|
|
command.CommandText = """
|
|
INSERT INTO api_keys (
|
|
key_id,
|
|
key_prefix,
|
|
secret_hash,
|
|
display_name,
|
|
scopes,
|
|
created_utc,
|
|
last_used_utc,
|
|
revoked_utc)
|
|
VALUES (
|
|
$key_id,
|
|
$key_prefix,
|
|
$secret_hash,
|
|
$display_name,
|
|
$scopes,
|
|
$created_utc,
|
|
NULL,
|
|
NULL);
|
|
""";
|
|
AddCreateParameters(command, request);
|
|
|
|
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<ApiKeyRecord>> ListAsync(CancellationToken cancellationToken)
|
|
{
|
|
await using SqliteConnection connection = connectionFactory.CreateConnection();
|
|
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
await using SqliteCommand command = connection.CreateCommand();
|
|
command.CommandText = """
|
|
SELECT key_id, key_prefix, secret_hash, display_name, scopes, created_utc, last_used_utc, revoked_utc
|
|
FROM api_keys
|
|
ORDER BY key_id;
|
|
""";
|
|
|
|
List<ApiKeyRecord> records = [];
|
|
|
|
await using SqliteDataReader reader = await command.ExecuteReaderAsync(cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
|
|
{
|
|
records.Add(ApiKeyRecordReader.Read(reader));
|
|
}
|
|
|
|
return records;
|
|
}
|
|
|
|
public async Task<bool> RevokeAsync(string keyId, DateTimeOffset revokedUtc, 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 revoked_utc = $revoked_utc
|
|
WHERE key_id = $key_id AND revoked_utc IS NULL;
|
|
""";
|
|
command.Parameters.AddWithValue("$key_id", keyId);
|
|
command.Parameters.AddWithValue("$revoked_utc", revokedUtc.ToString("O"));
|
|
|
|
int rows = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
return rows > 0;
|
|
}
|
|
|
|
public async Task<bool> RotateAsync(
|
|
string keyId,
|
|
byte[] secretHash,
|
|
DateTimeOffset rotatedUtc,
|
|
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 secret_hash = $secret_hash,
|
|
last_used_utc = NULL,
|
|
revoked_utc = NULL
|
|
WHERE key_id = $key_id;
|
|
""";
|
|
command.Parameters.AddWithValue("$key_id", keyId);
|
|
command.Parameters.Add("$secret_hash", SqliteType.Blob).Value = secretHash;
|
|
|
|
int rows = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
return rows > 0;
|
|
}
|
|
|
|
private static void AddCreateParameters(SqliteCommand command, ApiKeyCreateRequest request)
|
|
{
|
|
command.Parameters.AddWithValue("$key_id", request.KeyId);
|
|
command.Parameters.AddWithValue("$key_prefix", request.KeyPrefix);
|
|
command.Parameters.Add("$secret_hash", SqliteType.Blob).Value = request.SecretHash;
|
|
command.Parameters.AddWithValue("$display_name", request.DisplayName);
|
|
command.Parameters.AddWithValue("$scopes", ApiKeyScopeSerializer.Serialize(request.Scopes));
|
|
command.Parameters.AddWithValue("$created_utc", request.CreatedUtc.ToString("O"));
|
|
}
|
|
}
|