Files
warmbly/internal/infrastructure/encryptedkeys/store.go
T
Matthew Meszaros 2430095197 feat: move infrastructure state off dynamodb
Remove DynamoDB-backed storage paths, add Postgres/HTTP repositories for mailbox state maps, wire the internal message-map API, and add provisioning runner/migration plumbing.
2026-06-02 15:54:12 +02:00

54 lines
2.3 KiB
Go

// Package encryptedkeys stores per-user envelope-encrypted DEKs.
//
// A DEK (data encryption key) is a 32-byte AES-256 key generated per user by
// the kms.Provider. The KMS encrypts it; the encrypted form ("blob") is what
// this package stores. Workers and backend services never store the plaintext
// DEK on disk; they fetch the blob, ask KMS to decrypt it, and hold the
// plaintext in a short-TTL Redis cache.
//
// Durability matters absolutely. Losing an encrypted DEK is unrecoverable —
// every encrypted mailbox credential, OAuth refresh token, and stored message
// for that user becomes permanently unreadable. Implementations must therefore
// be backed by storage with strong durability guarantees (Postgres or
// equivalent). NATS JetStream KV is intentionally not offered here.
//
// Workers MUST NOT connect to Postgres directly (per CLAUDE.md), so the worker
// process uses the HTTP implementation, which calls a backend endpoint that
// in turn talks to the chosen durable store.
package encryptedkeys
import (
"context"
"errors"
"github.com/google/uuid"
)
// ErrAlreadyExists is returned by Put when a DEK is already stored for the
// given user. Overwriting would silently invalidate every prior ciphertext;
// rotation must use a separate explicit code path that re-encrypts existing
// data under the new DEK.
var ErrAlreadyExists = errors.New("encryptedkeys: dek already exists for user")
// Store is the abstraction over encrypted-DEK durable storage.
type Store interface {
// Put inserts the encrypted DEK. Returns ErrAlreadyExists if a DEK is
// already stored for the user.
Put(ctx context.Context, userID uuid.UUID, encryptedDEKB64 string) error
// Get returns the encrypted DEK as base64, or the empty string if no DEK
// is stored for the user. (Empty string, not error, matches today's
// repository contract and lets the cipher service distinguish "never had
// one — generate now" from "lookup failed — bail out".)
Get(ctx context.Context, userID uuid.UUID) (string, error)
// Delete removes the stored DEK. Idempotent: deleting a missing key is
// not an error. Use with extreme caution — see package docs on
// unrecoverability.
Delete(ctx context.Context, userID uuid.UUID) error
// Name returns a short identifier ("postgres", "http") for admin UI
// display and audit logs.
Name() string
}