secrets

package
v0.40.7 Latest Latest
Warning

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

Go to latest
Published: Oct 28, 2026 License: MIT Imports: 10 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 RivianAccountSource added in v0.40.7

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

RivianAccountSource reads a user's shared Rivian account state for rivian.AccountSyncer: the version of the stored session (its updated_at, so any pod's sign-in or sign-out changes it) with the needs_reauth flag. It reads the session with LoadRivianSession.

func NewRivianAccountSource added in v0.40.7

func NewRivianAccountSource(pool *sql.DB, store *Store) *RivianAccountSource

NewRivianAccountSource returns the source over pool (the system pool: it reads the users and user_secrets tables of every user) and store.

func (*RivianAccountSource) Session added in v0.40.7

func (s *RivianAccountSource) Session(ctx context.Context, uid uuid.UUID) (rivian.Session, error)

Session implements rivian.AccountStateSource.

func (*RivianAccountSource) States added in v0.40.7

States implements rivian.AccountStateSource.

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