Encrypt existing plaintext Account tokens #352

Closed
opened 2026-02-07 17:11:39 +00:00 by jason.woltje · 0 comments
Owner

Phase 1c - Security Foundations

Problem

OAuth tokens (accessToken, refreshToken, idToken) in the Account table are stored as plaintext in PostgreSQL. If the database is compromised, all OAuth credentials are exposed. The Account model is managed by BetterAuth, which reads and writes these fields directly.

Additionally, ENCRYPTION_KEY is not documented in the root .env.example, so developers may not configure it.

Requirements

  1. Create Prisma middleware that transparently encrypts Account tokens on write and decrypts on read
  2. Auto-detect ciphertext format: vault:... (Transit), aes:... (AES fallback), or plaintext (legacy/unencrypted)
  3. Write a one-time data migration script to encrypt existing plaintext tokens in-place
  4. Add ENCRYPTION_KEY to root .env.example with generation instructions
  5. Verify BetterAuth login/refresh flows work with encrypted tokens

Implementation Notes

  • Use existing CryptoService at apps/api/src/federation/crypto.service.ts for AES-256-GCM
  • Prisma middleware intercepts Account create/update to encrypt, and findMany/findUnique to decrypt
  • Token fields to encrypt: accessToken, refreshToken, idToken
  • Prefix encrypted values with aes: to distinguish from plaintext and future Transit ciphertext
  • Data migration: run in transaction, process in batches, add encryption_version tracking column
  • BetterAuth compatibility: test that token refresh still works when tokens are encrypted at rest

Files

  • apps/api/src/prisma/account-encryption.middleware.ts (new)
  • apps/api/src/prisma/account-encryption.middleware.spec.ts (new)
  • apps/api/src/prisma/prisma.service.ts (modify - register middleware)
  • apps/api/prisma/migrations/[timestamp]_encrypt_account_tokens/ (new - data migration)
  • .env.example (modify - add ENCRYPTION_KEY)

Acceptance Criteria

  • New Account tokens are encrypted before storage
  • Existing plaintext tokens are decrypted correctly (backward compatible)
  • Data migration encrypts all existing plaintext tokens
  • ENCRYPTION_KEY documented in .env.example with generation command
  • BetterAuth login, session refresh, and token rotation work correctly
  • Unit tests for middleware encrypt/decrypt logic

Dependencies

  • Depends on: RLS context interceptor (should be in place first)
  • Blocks: VaultService migration (Phase 2c will upgrade to Transit encryption)

Refs #346

## Phase 1c - Security Foundations ### Problem OAuth tokens (accessToken, refreshToken, idToken) in the Account table are stored as plaintext in PostgreSQL. If the database is compromised, all OAuth credentials are exposed. The Account model is managed by BetterAuth, which reads and writes these fields directly. Additionally, ENCRYPTION_KEY is not documented in the root .env.example, so developers may not configure it. ### Requirements 1. Create Prisma middleware that transparently encrypts Account tokens on write and decrypts on read 2. Auto-detect ciphertext format: vault:... (Transit), aes:... (AES fallback), or plaintext (legacy/unencrypted) 3. Write a one-time data migration script to encrypt existing plaintext tokens in-place 4. Add ENCRYPTION_KEY to root .env.example with generation instructions 5. Verify BetterAuth login/refresh flows work with encrypted tokens ### Implementation Notes - Use existing CryptoService at apps/api/src/federation/crypto.service.ts for AES-256-GCM - Prisma middleware intercepts Account create/update to encrypt, and findMany/findUnique to decrypt - Token fields to encrypt: accessToken, refreshToken, idToken - Prefix encrypted values with aes: to distinguish from plaintext and future Transit ciphertext - Data migration: run in transaction, process in batches, add encryption_version tracking column - BetterAuth compatibility: test that token refresh still works when tokens are encrypted at rest ### Files - apps/api/src/prisma/account-encryption.middleware.ts (new) - apps/api/src/prisma/account-encryption.middleware.spec.ts (new) - apps/api/src/prisma/prisma.service.ts (modify - register middleware) - apps/api/prisma/migrations/[timestamp]_encrypt_account_tokens/ (new - data migration) - .env.example (modify - add ENCRYPTION_KEY) ### Acceptance Criteria - [ ] New Account tokens are encrypted before storage - [ ] Existing plaintext tokens are decrypted correctly (backward compatible) - [ ] Data migration encrypts all existing plaintext tokens - [ ] ENCRYPTION_KEY documented in .env.example with generation command - [ ] BetterAuth login, session refresh, and token rotation work correctly - [ ] Unit tests for middleware encrypt/decrypt logic ### Dependencies - Depends on: RLS context interceptor (should be in place first) - Blocks: VaultService migration (Phase 2c will upgrade to Transit encryption) Refs #346
jason.woltje added this to the M9-CredentialSecurity (0.0.9) milestone 2026-02-07 17:11:39 +00:00
jason.woltje added the securityp0apiapi labels 2026-02-07 17:11:39 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaic/stack#352