feat(apikeys): optional ExpiresUtc — create + verifier enforcement + sqlite v3 migration (archreview G-2)
ApiKeyRecord/ApiKeyListItem gain a nullable ExpiresUtc (NULL = never expires); ApiKeyFailure gains KeyExpired. ApiKeyVerifier rejects a key whose ExpiresUtc is at-or-before now (inclusive, injected clock), before the secret comparison. CreateKeyAsync gets an expiresUtc overload; rotate preserves existing expiry. SQLite schema bumps to v3: a nullable expires_utc column, added to fresh DBs via CREATE and to existing v1/v2 DBs via an idempotent guarded ALTER — donor v2 gateway-auth.db upgrades in place, no key invalidated. Version 0.1.3 -> 0.1.4 (not yet published; nuget push is human-gated). Consumers (HistorianGateway, mxaccessgw) bump to 0.1.4 to set/enforce expiry. 146 Auth.ApiKeys tests pass.
This commit is contained in:
@@ -56,6 +56,14 @@ public sealed class ApiKeyVerifier(
|
||||
return Fail(ApiKeyFailure.KeyRevoked);
|
||||
}
|
||||
|
||||
// 4b. Reject expired keys. Expiry is absolute and inclusive: a key whose ExpiresUtc is at or
|
||||
// before "now" is expired. A NULL ExpiresUtc never expires (the pre-expiry default), so
|
||||
// upgrading a store leaves existing keys valid. Uses the injected clock for testability.
|
||||
if (record.ExpiresUtc is { } expiresUtc && expiresUtc <= _timeProvider.GetUtcNow())
|
||||
{
|
||||
return Fail(ApiKeyFailure.KeyExpired);
|
||||
}
|
||||
|
||||
// 5. Constant-time secret comparison.
|
||||
if (!ApiKeySecretHasher.Verify(parsed.Secret, pepper, record.SecretHash))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user