Files
Matthew Meszaros c800081987 feat: store envelope-encryption DEKs per organization
Mailboxes, integration tokens, and message content are organization
assets. Keying their DEK by the connecting user meant offboarding that
user made every ciphertext they created unreadable.

Migration 000039 drops user_encrypted_keys and creates
organization_encrypted_keys keyed by organization_id. The new table has
no FK on purpose: platform secrets live under the zero UUID (no
organizations row), and DEK rows must never cascade-delete because a
lost DEK is unrecoverable.

Pre-production, so there is no data migration; ciphertexts sealed under
the old per-user DEKs are abandoned with the table.
2026-06-10 17:15:47 +02:00

64 lines
1.7 KiB
Go

package encryptedkeys
import (
"context"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/warmbly/warmbly/internal/infrastructure/db"
)
// PostgresStore stores encrypted DEKs in the organization_encrypted_keys
// table. Only the backend uses this impl directly; workers reach DEKs via the
// HTTP store (which proxies through the backend).
type PostgresStore struct {
db *db.DB
}
func NewPostgres(d *db.DB) *PostgresStore {
return &PostgresStore{db: d}
}
func (s *PostgresStore) Name() string { return "postgres" }
func (s *PostgresStore) Put(ctx context.Context, orgID uuid.UUID, encryptedDEKB64 string) error {
const q = `
INSERT INTO organization_encrypted_keys (organization_id, encrypted_data_key)
VALUES ($1, $2)
`
_, err := s.db.Exec(ctx, q, orgID, encryptedDEKB64)
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == "23505" { // unique_violation
return ErrAlreadyExists
}
return fmt.Errorf("encryptedkeys.postgres: put: %w", err)
}
return nil
}
func (s *PostgresStore) Get(ctx context.Context, orgID uuid.UUID) (string, error) {
const q = `SELECT encrypted_data_key FROM organization_encrypted_keys WHERE organization_id = $1`
var out string
err := s.db.QueryRow(ctx, q, orgID).Scan(&out)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return "", nil
}
return "", fmt.Errorf("encryptedkeys.postgres: get: %w", err)
}
return out, nil
}
func (s *PostgresStore) Delete(ctx context.Context, orgID uuid.UUID) error {
const q = `DELETE FROM organization_encrypted_keys WHERE organization_id = $1`
_, err := s.db.Exec(ctx, q, orgID)
if err != nil {
return fmt.Errorf("encryptedkeys.postgres: delete: %w", err)
}
return nil
}