pub struct ApiKeys { /* private fields */ }Expand description
A store of API keys over the Database port; the owning app ships
create_table_sql as a migration.
Timestamps are RFC 3339 TEXT from the Clock port, the house
shape (cratefield_core::SendCooldown documents why lexicographic
order on that format is chronological order).
Implementations§
Source§impl ApiKeys
impl ApiKeys
Sourcepub fn new(
db: Arc<dyn Database>,
clock: Arc<dyn Clock>,
rng: Arc<dyn RandomBytes>,
table: impl Into<String>,
) -> Self
pub fn new( db: Arc<dyn Database>, clock: Arc<dyn Clock>, rng: Arc<dyn RandomBytes>, table: impl Into<String>, ) -> Self
A key store over db, timed by clock, keyed by rng.
Sourcepub fn create_table_sql(&self) -> String
pub fn create_table_sql(&self) -> String
The portable DDL for the key table (SQLite/D1/Postgres, ADR 0004), shipped as a forward-only migration. Only the hash and the public prefix are ever stored.
Sourcepub async fn issue(
&self,
namespace: &str,
subject: &str,
scopes: &[&str],
mode: ApiKeyMode,
) -> Result<IssuedKey, ApiKeyError>
pub async fn issue( &self, namespace: &str, subject: &str, scopes: &[&str], mode: ApiKeyMode, ) -> Result<IssuedKey, ApiKeyError>
Mints a key for subject with scopes, and stores its hash; the
returned IssuedKey::token is shown exactly once.
§Errors
ApiKeyError::Namespace or ApiKeyError::Scope on a caller
mistake, ApiKeyError::Random or ApiKeyError::Db — in
every failure case no key was issued.
Sourcepub async fn verify(
&self,
token: &str,
) -> Result<Option<ApiKeyPrincipal>, DbError>
pub async fn verify( &self, token: &str, ) -> Result<Option<ApiKeyPrincipal>, DbError>
Verifies one presented token: parse, look the prefix up, re-derive
the hash, compare in constant time; on success touch last_used_at
(once per window, best effort — logged, never a failed request).
Ok(None) is every auth failure there is — malformed, unknown,
revoked, wrong secret — deliberately indistinguishable, so probing
a gate built on this learns nothing. A database failure is
Err, and nothing about it says “unauthorized”.
§Errors
DbError when the lookup fails.
Sourcepub async fn revoke(&self, prefix: &str) -> Result<(), ApiKeyError>
pub async fn revoke(&self, prefix: &str) -> Result<(), ApiKeyError>
Marks a key revoked: every later verify answers Ok(None). The
row stays, so a later rotate of the same prefix is refused and an
audit can still see the key existed. Idempotent.
§Errors
ApiKeyError::UnknownKey when the prefix names no key,
ApiKeyError::Db when the write fails.
Sourcepub async fn rotate(&self, prefix: &str) -> Result<IssuedKey, ApiKeyError>
pub async fn rotate(&self, prefix: &str) -> Result<IssuedKey, ApiKeyError>
Rotates a key: mints a fresh token for the same namespace, mode,
subject and scopes and revokes the old one, both statements in
one Database::batch_atomic batch (issue #126) — the old key
never keeps working while the new one does not exist yet, nor the
reverse. The copy is an INSERT ... SELECT keyed to the old row
still being active, so a rotate racing a revoke — or a second
rotate — mints nothing instead of a second live key.
§Errors
ApiKeyError::UnknownKey when the prefix names no active key
(a revoked one included — rotate the replacement instead),
ApiKeyError::Random or ApiKeyError::Db as Self::issue.