secrets

package
v0.36.11 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package secrets is the persistence layer on top of [`internal/crypto`](../crypto). It hides the Seal/Open plumbing and the user_secrets schema behind a tiny Put/Get/Delete API so feature code (rivian session, future AI keys, future push subscription private key) doesn't have to know either exists.

Why a package, not a function on the sealer

  • The sealer is a pure crypto primitive; it has no opinion on where blobs live. Keeping storage in a separate package lets the phase-3 KMSSealer swap in without touching any caller.
  • Each caller wants the same behaviour ("store this blob under this user, that name"); inlining sql.Exec at every site would duplicate the kek_id audit stamp, the ON CONFLICT upsert, and the GDPR-delete handling.

Naming convention for secret names

The `name` column is free-text but callers are expected to use dot-delimited namespaced identifiers so a grep of the codebase turns up every producer/consumer of a given secret:

  • rivian.session — rivian.Session JSON blob
  • ai.openai_key — OpenAI API key (future)
  • push.vapid_priv — VAPID private key per user (future)

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("secrets: not found")

ErrNotFound is returned by Get when no row exists for (userID, name). Distinct from an open/decrypt failure — a missing row is expected (user hasn't linked Rivian yet, etc.) and callers usually translate it to a zero value.

Functions

func ClearPendingMFA added in v0.23.1

func ClearPendingMFA(ctx context.Context, s *Store, userID uuid.UUID) error

ClearPendingMFA removes any stored OTP challenge for the user.

func LoadPendingMFA added in v0.23.1

func LoadPendingMFA(ctx context.Context, s *Store, userID uuid.UUID) (rivian.PendingMFA, bool)

LoadPendingMFA returns a stored OTP challenge and true when one is present and within the TTL. A missing, corrupt, or expired row returns false, matching LoadRivianSession's recover-by-retry stance.

func LoadRivianSession

func LoadRivianSession(ctx context.Context, s *Store, userID uuid.UUID) (rivian.Session, error)

LoadRivianSession returns the persisted Rivian session for a user, or the zero value when none is stored. A corrupt / un-openable blob also returns zero rather than an error: matches the previous settings-backed behaviour where an operator could "recover" a bad row by logging in again, and means a failed KEK rotation doesn't brick every user at once. The caller gets an error log-worthy only when the DB itself misbehaves.

func SavePendingMFA added in v0.23.1

func SavePendingMFA(ctx context.Context, s *Store, userID uuid.UUID, p rivian.PendingMFA) error

SavePendingMFA persists an in-flight OTP challenge so a peer pod can finish the second leg. An empty OTPToken clears the row instead.

func SaveRivianSession

func SaveRivianSession(ctx context.Context, s *Store, userID uuid.UUID, sess rivian.Session) error

SaveRivianSession persists or clears the session. An empty UserSessionToken is the sentinel for "logged out"; we delete the row rather than store an empty sealed blob, because a missing row is cheaper to read and removes the only place where the user has stale token material on disk.

Types

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store wraps *sql.DB with Seal/Open plumbing. Safe for concurrent use — the underlying DB pool handles concurrency, and the sealer is stateless.

func New

func New(db *sql.DB, sealer crypto.Sealer) *Store

New builds a Store. Both arguments are required; passing a NoopSealer is allowed but only for tests and is logged at OpenStore callers so nobody accidentally ships with it.

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, userID uuid.UUID, name string) error

Delete removes a secret. Idempotent: no error if the row is absent. Used on explicit logout / unlink and by the future GDPR-delete pipeline (though that will usually go through ON DELETE CASCADE from users).

func (*Store) Get

func (s *Store) Get(ctx context.Context, userID uuid.UUID, name string) ([]byte, error)

Get reads + decrypts (userID, name). Returns ErrNotFound when the row doesn't exist; any other error is either a DB failure or crypto.ErrSealedBlob (from the sealer, wrapped).

func (*Store) Put

func (s *Store) Put(ctx context.Context, userID uuid.UUID, name string, plaintext []byte) error

Put writes (userID, name) = plaintext, encrypted with the configured sealer. Upserts on (user_id, name) so the row's updated_at tracks the latest write and rotation tooling can re-wrap in place with the same primary key.

The plaintext is sealed with the caller's userID bound as AAD, so a later Get with a different userID will refuse to open the row even if the attacker can read the raw blob.

Jump to

Keyboard shortcuts

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