feat(auth.apikeys): add IApiKeyAdminStore.SetScopesAsync + SetEnabledAsync (editable scopes + reversible enable, no schema change); bump 0.1.3
This commit is contained in:
@@ -4,7 +4,8 @@ using ZB.MOM.WW.Auth.Abstractions.ApiKeys;
|
||||
namespace ZB.MOM.WW.Auth.ApiKeys.Sqlite;
|
||||
|
||||
/// <summary>
|
||||
/// SQLite-backed administration store for API keys (create, revoke, rotate, delete).
|
||||
/// SQLite-backed administration store for API keys (create, revoke, rotate, delete,
|
||||
/// set-scopes, enable/disable).
|
||||
/// </summary>
|
||||
public sealed class SqliteApiKeyAdminStore(AuthSqliteConnectionFactory connectionFactory) : IApiKeyAdminStore
|
||||
{
|
||||
@@ -85,6 +86,67 @@ public sealed class SqliteApiKeyAdminStore(AuthSqliteConnectionFactory connectio
|
||||
return rows > 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> SetScopesAsync(string keyId, IReadOnlySet<string> scopes, CancellationToken ct)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(keyId);
|
||||
ArgumentNullException.ThrowIfNull(scopes);
|
||||
|
||||
await using SqliteConnection connection =
|
||||
await connectionFactory.OpenConnectionAsync(ct).ConfigureAwait(false);
|
||||
|
||||
await using SqliteCommand command = connection.CreateCommand();
|
||||
command.CommandText = """
|
||||
UPDATE api_keys
|
||||
SET scopes = $scopes
|
||||
WHERE key_id = $key_id;
|
||||
""";
|
||||
command.Parameters.AddWithValue("$key_id", keyId);
|
||||
command.Parameters.AddWithValue("$scopes", ScopeSerializer.Serialize(scopes));
|
||||
|
||||
int rows = await command.ExecuteNonQueryAsync(ct).ConfigureAwait(false);
|
||||
|
||||
return rows > 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> SetEnabledAsync(string keyId, bool enabled, DateTimeOffset whenUtc, CancellationToken ct)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(keyId);
|
||||
|
||||
await using SqliteConnection connection =
|
||||
await connectionFactory.OpenConnectionAsync(ct).ConfigureAwait(false);
|
||||
|
||||
await using SqliteCommand command = connection.CreateCommand();
|
||||
|
||||
// Reversible toggle: NO `revoked_utc IS NULL` guard (unlike RevokeAsync), so it works
|
||||
// regardless of current state. Deliberately leaves secret_hash and last_used_utc untouched
|
||||
// — that is what distinguishes re-enable from RotateAsync.
|
||||
if (enabled)
|
||||
{
|
||||
command.CommandText = """
|
||||
UPDATE api_keys
|
||||
SET revoked_utc = NULL
|
||||
WHERE key_id = $key_id;
|
||||
""";
|
||||
command.Parameters.AddWithValue("$key_id", keyId);
|
||||
}
|
||||
else
|
||||
{
|
||||
command.CommandText = """
|
||||
UPDATE api_keys
|
||||
SET revoked_utc = $revoked_utc
|
||||
WHERE key_id = $key_id;
|
||||
""";
|
||||
command.Parameters.AddWithValue("$key_id", keyId);
|
||||
command.Parameters.AddWithValue("$revoked_utc", whenUtc.ToString("O"));
|
||||
}
|
||||
|
||||
int rows = await command.ExecuteNonQueryAsync(ct).ConfigureAwait(false);
|
||||
|
||||
return rows > 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> DeleteAsync(string keyId, CancellationToken ct)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user