key

package
v1.9.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package key defines the API key management types and service.

Index

Constants

View Source
const (
	ScopeCompletions = "completions"
	ScopeEmbeddings  = "embeddings"
	ScopeModels      = "models"
	ScopeAdmin       = "admin"
)

The scopes a key can hold. A key with no scopes given gets completions, embeddings and models. Admin is cross-tenant: an admin key administers every tenant, whichever tenant it belongs to.

Variables

View Source
var (
	ErrNotFound = errors.New("nexus: api key not found")
	// ErrRevoked and ErrExpired are returned by Validate only for the right
	// raw key.
	ErrRevoked = errors.New("nexus: api key revoked")
	ErrExpired = errors.New("nexus: api key expired")
	// ErrInvalid wraps every input the service refuses.
	ErrInvalid = errors.New("nexus: invalid api key input")
	// ErrDuplicate is returned by Store.Insert when a key with the same hash
	// is already stored.
	ErrDuplicate = errors.New("nexus: api key already exists")
)

ErrNotFound reports that no API key matched. Stores return it from FindByID, TouchLastUsed and Update, never a nil key with a nil error. FindByPrefix reports none found as an empty slice instead. Validate also returns it for any raw key that matches no stored hash, so a caller cannot tell an unknown prefix from a wrong key.

Functions

func KnownScope

func KnownScope(s string) bool

KnownScope reports whether s is one of the scopes above.

func WellFormed

func WellFormed(raw string) bool

WellFormed reports whether raw has the shape Create gives a key: "nxs_" and 64 lowercase hex digits.

Types

type APIKey

type APIKey struct {
	ID         id.KeyID          `json:"id"`
	TenantID   id.TenantID       `json:"tenant_id"`
	Name       string            `json:"name"`
	Prefix     string            `json:"prefix"` // "nxs_" + first 8 chars (for display)
	Hash       string            `json:"-"`      // SHA-256 of the raw key, hex (never exposed). The raw key carries 256 random bits, so an unsalted hash is enough.
	Scopes     []string          `json:"scopes"` // ["completions", "embeddings", "models"]
	Status     Status            `json:"status"`
	ExpiresAt  *time.Time        `json:"expires_at,omitempty"`
	LastUsedAt *time.Time        `json:"last_used_at,omitempty"`
	Metadata   map[string]string `json:"metadata,omitempty"`
	CreatedAt  time.Time         `json:"created_at"`
}

APIKey represents a Nexus-issued API key.

type CreateInput

type CreateInput struct {
	TenantID string            `json:"tenant_id"`
	Name     string            `json:"name"`
	Scopes   []string          `json:"scopes,omitempty"`
	Metadata map[string]string `json:"metadata,omitempty"`
	// ExpiresAt, when set, must be in the future.
	ExpiresAt *time.Time `json:"expires_at,omitempty"`
}

CreateInput is the input for creating an API key.

type Events

type Events interface {
	EmitKeyCreated(ctx context.Context, keyID id.KeyID, tenantID id.TenantID)
	EmitKeyRevoked(ctx context.Context, keyID id.KeyID)
}

Events receives key lifecycle events. *plugin.Registry satisfies it.

type ListOptions

type ListOptions struct {
	TenantID string `json:"tenant_id,omitempty"`
	Status   Status `json:"status,omitempty"`
	Limit    int    `json:"limit,omitempty"`
	Cursor   string `json:"cursor,omitempty"`

	// Now is the clock reading the status filter derives expiry against.
	// The zero value means the wall clock. key.Service sets it from its own
	// clock, so ListPage and Count agree with Get under WithClock.
	Now time.Time `json:"-"`
}

ListOptions configures key listing. An empty TenantID lists every tenant's keys. Lists are newest first and cursor paged.

func (*ListOptions) At

func (o *ListOptions) At() time.Time

At returns the time expiry is derived against: Now, or the wall clock when Now is unset.

type ListResult

type ListResult struct {
	Items      []*APIKey `json:"items"`
	NextCursor string    `json:"next_cursor"`
}

ListResult is one page of keys. NextCursor is "" on the last page.

type Option

type Option func(*service)

Option configures the key service.

func WithClock

func WithClock(now func() time.Time) Option

WithClock replaces time.Now, for tests.

func WithEvents

func WithEvents(e Events) Option

WithEvents reports created and revoked keys.

func WithTenants

func WithTenants(t TenantFinder) Option

WithTenants makes Create refuse a key for a tenant that does not exist.

type Service

type Service interface {
	// Create generates a new API key. Returns the full key (only time it's visible).
	Create(ctx context.Context, input *CreateInput) (*APIKey, string, error)

	// Ensure makes sure the key whose raw value is rawKey exists, so an
	// operator can supply the first admin key from config. A key with that
	// value, found by prefix and a constant-time hash compare, is returned
	// with created false, in any status: a revoked key stays revoked.
	// Otherwise Ensure checks in as Create does (tenant exists, scopes known,
	// expiry), stores the key and reports it. A rawKey that is not "nxs_"
	// and 64 lowercase hex digits gives ErrInvalid. No error carries rawKey.
	Ensure(ctx context.Context, rawKey string, in *CreateInput) (k *APIKey, created bool, err error)

	// Validate checks a raw API key and returns the associated key record.
	Validate(ctx context.Context, rawKey string) (*APIKey, error)

	// Get returns one key, with its expiry derived: an active key whose
	// ExpiresAt has passed reads expired. Every store derives expiry the same
	// way in its List and Count, so the status filter and the items agree.
	Get(ctx context.Context, id string) (*APIKey, error)

	// Revoke deactivates an API key.
	Revoke(ctx context.Context, id string) error

	// List returns keys for a tenant (hashed, never shows full key).
	List(ctx context.Context, tenantID string) ([]*APIKey, error)

	// ListPage returns one page of keys, newest first. The store applies the
	// status filter with expiry derived, and every item carries its effective
	// status: an active key whose ExpiresAt has passed is expired.
	ListPage(ctx context.Context, opts *ListOptions) (*ListResult, error)

	// Count returns how many keys match opts, with the same filter semantics
	// as ListPage. Limit and Cursor are ignored.
	Count(ctx context.Context, opts *ListOptions) (int, error)

	// Rotate creates a replacement for an active key and revokes the old one.
	// It is not atomic across backends: if the revoke fails, the new key is
	// revoked too (best effort) and the error is returned.
	Rotate(ctx context.Context, oldKeyID string) (*APIKey, string, error)
}

Service manages API key lifecycle.

func NewService

func NewService(store Store, opts ...Option) Service

type Status

type Status string

Status represents the key's current state.

const (
	KeyActive  Status = "active"
	KeyRevoked Status = "revoked"
	KeyExpired Status = "expired"
)

func Effective

func Effective(k *APIKey, now time.Time) Status

Effective returns the status k has at now. A key stored active whose ExpiresAt is at or before now is expired; every other status is as stored. It is the one rule for expiry: the service and every store use it, and the SQL and Mongo filters mirror it.

type Store

type Store interface {
	// Insert stores k. A key whose Hash is already stored is refused with
	// ErrDuplicate, and nothing is written: the hash is unique in every store.
	Insert(ctx context.Context, k *APIKey) error
	FindByID(ctx context.Context, id string) (*APIKey, error)
	// FindByPrefix returns every key whose prefix is prefix, in any status.
	// Prefixes are not unique, so a caller must check the hash. None found
	// is an empty slice and a nil error.
	FindByPrefix(ctx context.Context, prefix string) ([]*APIKey, error)
	// TouchLastUsed sets only LastUsedAt, so it can never undo a concurrent
	// revoke. It returns ErrNotFound when the key does not exist.
	TouchLastUsed(ctx context.Context, id string, at time.Time) error
	Update(ctx context.Context, k *APIKey) error
	Delete(ctx context.Context, id string) error
	ListByTenant(ctx context.Context, tenantID string) ([]*APIKey, error)
	List(ctx context.Context, opts *ListOptions) (*ListResult, error)
	// Count returns how many keys match opts, with List's filter semantics.
	// Limit and Cursor are ignored. nil opts counts every key.
	Count(ctx context.Context, opts *ListOptions) (int, error)
}

Store is the persistence interface for API keys. Finders and Update return ErrNotFound when no key matches; Delete of an unknown id is a no-op.

List and Count derive expiry. Against a clock reading now:

  • active means stored active and (no ExpiresAt or ExpiresAt after now);
  • expired means stored expired, or stored active with ExpiresAt at or before now;
  • revoked means stored revoked.

Every key List returns carries its effective status (see Effective).

type TenantFinder

type TenantFinder interface {
	FindByID(ctx context.Context, id string) (*tenant.Tenant, error)
}

TenantFinder lets Create check that a key's tenant exists. tenant.Store satisfies it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL