Skip to main content
Table of Contents

Class InMemoryApiKeyStore

Namespace
Stratara.Testing
Assembly
Stratara.Testing.dll

Thread-safe in-memory IApiKeyStore for tests — the drop-in double for the EF-backed key store, mirroring its contract semantics: canonical raw keys, machine keys materialized as memberships of their tenant, personal access tokens guarded by the bound user's active membership, fail-closed validation, and the idempotent import path.

public sealed class InMemoryApiKeyStore : IApiKeyStore
Inheritance
InMemoryApiKeyStore
Implements
Inherited Members
Extension Methods

Examples

Seed a known key and authenticate with it:

var keys = new InMemoryApiKeyStore();
var rawKey = ApiKeyFormat.CreateRawKey();
await keys.ImportAsync(new ApiKeyImportRequest(rawKey, TestTenants.Of("primary"), "ci", ["Deployer"]));
var descriptor = await keys.ValidateAsync(rawKey);

Remarks

The double keeps raw keys in memory instead of hashing them — the hash is a storage concern, and nothing in the contract exposes it. Everything a test can observe (descriptors, memberships, validation outcomes) behaves as it does in production.

Constructors

InMemoryApiKeyStore(ITenantMembershipStore?, TimeProvider?)

Creates the store, optionally sharing the membership store that machine keys materialize into and that personal access tokens are guarded against.

public InMemoryApiKeyStore(ITenantMembershipStore? memberships = null, TimeProvider? clock = null)

Parameters

memberships ITenantMembershipStore

The membership plane to write machine memberships into; a private InMemoryTenantMembershipStore is created when omitted.

clock TimeProvider

Clock backing issuance timestamps and expiry checks.

Properties

Memberships

The membership plane this store materializes machine keys into — inspect it to assert that a key carries the roles it should.

public ITenantMembershipStore Memberships { get; }

Property Value

ITenantMembershipStore

Methods

GetForTenantAsync(Guid, CancellationToken)

Lists the tenant's keys (descriptors only — raw keys are unrecoverable).

public Task<IReadOnlyList<ApiKeyDescriptor>> GetForTenantAsync(Guid tenantId, CancellationToken cancellationToken = default)

Parameters

tenantId Guid

The tenant whose keys to list.

cancellationToken CancellationToken

Token to observe while listing.

Returns

Task<IReadOnlyList<ApiKeyDescriptor>>

The tenant's keys; empty when none exist.

ImportAsync(ApiKeyImportRequest, CancellationToken)

Stores a machine key whose raw value the caller already holds — for setups where the key must be known before the store exists: container orchestration, CI provisioning, self-hosted bundles, end-to-end test hosts. Idempotent, so it can run on every boot.

public Task<ApiKeyDescriptor> ImportAsync(ApiKeyImportRequest request, CancellationToken cancellationToken = default)

Parameters

request ApiKeyImportRequest

The import parameters, including the raw key.

cancellationToken CancellationToken

Token to observe while importing.

Returns

Task<ApiKeyDescriptor>

The stored descriptor — newly created, or the existing one on a repeat import.

Remarks

The supplied value must match ApiKeyFormat — generate it with CreateRawKey(). The shape requirement is load-bearing: stores keep the key's digest unsalted because a generated key carries 256 bits of entropy, and a hand-picked value would quietly invalidate that.

Importing a value that is already stored is a no-op that returns the existing descriptor. The stored key is never mutated — a differing name, role set, or expiry leaves the stored key as it is, so a changed configuration can neither escalate a key's roles nor extend its life unnoticed. Compare the returned descriptor when that matters. Nothing about the "shown once" guarantee of IssueAsync(ApiKeyIssueRequest, CancellationToken) changes: import returns no raw key, because the caller already has it.

Exceptions

ArgumentException

The raw key does not match the canonical format, or the name is empty.

InvalidOperationException

The key value is already stored for a different tenant, as a personal access token, or in a revoked or expired state — none of which an import may silently adopt.

IssueAsync(ApiKeyIssueRequest, CancellationToken)

Issues a new key. The returned RawKey is shown once and never persisted. Personal access tokens (a bound user) require the user to hold an active membership in the target tenant.

public Task<IssuedApiKey> IssueAsync(ApiKeyIssueRequest request, CancellationToken cancellationToken = default)

Parameters

request ApiKeyIssueRequest

The issuance parameters.

cancellationToken CancellationToken

Token to observe while issuing.

Returns

Task<IssuedApiKey>

The raw secret plus the stored descriptor.

Exceptions

InvalidOperationException

A personal access token was requested for a user without an active membership in the tenant, or roles were supplied for a personal access token.

RemoveAllForTenantAsync(Guid, CancellationToken)

Removes every key bound to the tenant — the key-plane step of a tenant-erasure sweep.

public Task RemoveAllForTenantAsync(Guid tenantId, CancellationToken cancellationToken = default)

Parameters

tenantId Guid

The tenant whose keys to remove.

cancellationToken CancellationToken

Token to observe while removing.

Returns

Task

RemoveAllForUserAsync(Guid, CancellationToken)

Removes every personal access token bound to the user — the key-plane step of a user-erasure sweep.

public Task RemoveAllForUserAsync(Guid userId, CancellationToken cancellationToken = default)

Parameters

userId Guid

The user whose personal access tokens to remove.

cancellationToken CancellationToken

Token to observe while removing.

Returns

Task

RevokeAsync(Guid, CancellationToken)

Revokes a key; it never validates again.

public Task RevokeAsync(Guid apiKeyId, CancellationToken cancellationToken = default)

Parameters

apiKeyId Guid

The key to revoke.

cancellationToken CancellationToken

Token to observe while revoking.

Returns

Task

ValidateAsync(string, CancellationToken)

Validates a presented raw key: hash lookup, then revocation and expiry checks.

public Task<ApiKeyDescriptor?> ValidateAsync(string rawKey, CancellationToken cancellationToken = default)

Parameters

rawKey string

The presented plaintext key.

cancellationToken CancellationToken

Token to observe while validating.

Returns

Task<ApiKeyDescriptor>

The key's descriptor, or null for unknown/revoked/expired keys.