Help us shape the future of Go! Take the 2026 Go Developer Survey

secrets

package
v0.36.21 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 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