Skip to content

Encrypt OAuth2 tokens and PATs at rest in Firestore#130

Merged
jadeddelta merged 3 commits intoosf-entry-point-integrationfrom
encrypt-tokens-at-rest
Feb 26, 2026
Merged

Encrypt OAuth2 tokens and PATs at rest in Firestore#130
jadeddelta merged 3 commits intoosf-entry-point-integrationfrom
encrypt-tokens-at-rest

Conversation

@jodeleeuw
Copy link
Member

Summary

  • Adds AES-256-GCM encryption for osfToken, authToken, and refreshToken fields stored in Firestore user documents
  • Tokens are encrypted on write and decrypted on read by Cloud Functions, so plaintext tokens never sit in the database
  • New Cloud Function endpoints (/api/saveosftoken, /api/getosftoken) replace direct Firestore reads/writes from the frontend, keeping the encryption key server-side only
  • Firestore rules tightened to remove client-side OAuth token write permissions
  • Migration script included for encrypting existing plaintext tokens
  • decrypt() has a plaintext fallback — values not starting with v1: are returned as-is, so existing tokens continue working before migration

Pre-deployment checklist

These steps must be completed before merging/deploying:

  1. Generate encryption keys (one per environment):

    node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
  2. Add GitHub Actions secrets:

    • PROD_TOKEN_ENCRYPTION_KEY — for production deployments
    • TEST_TOKEN_ENCRYPTION_KEY — for test deployments
  3. Update local functions/.env — replace the placeholder 000... key with a real 64-char hex key for local development

Deployment sequence

Order matters — deploy in this sequence:

  1. Deploy backend first — the decrypt() plaintext fallback means existing unencrypted tokens keep working. New writes will be encrypted.
  2. Deploy frontend — after the new /api/saveosftoken and /api/getosftoken endpoints are live.
  3. Run migration — encrypts all existing plaintext tokens in Firestore:
    TOKEN_ENCRYPTION_KEY=<key> node migrations/encrypt-tokens.cjs
  4. Deploy Firestore rules last — only after confirming migration success and that the frontend no longer writes tokens directly. The updated rules remove isUpdateRefreshToken() and isUpdateAuthToken() client write permissions.

Test plan

  • Local emulator: create user, set PAT via /api/saveosftoken, create experiment, submit data — full flow works
  • Local emulator: OAuth sign-up flow — tokens stored encrypted, data submission works
  • Verify decrypt() plaintext fallback — pre-existing plaintext tokens still work before migration
  • Run migration on emulator with seeded plaintext tokens, verify they start with v1:
  • After migration, verify data submission still works with encrypted tokens
  • Check Firestore console — token fields show v1:... ciphertext, not plaintext

🤖 Generated with Claude Code

Add AES-256-GCM encryption for osfToken, authToken, and refreshToken
fields stored in Firestore. Tokens are encrypted on write and decrypted
on read by Cloud Functions, protecting against database compromise.

- Add crypto-utils.ts with encrypt/decrypt using v1:<iv>:<ct>:<tag> format
- Add /api/saveosftoken endpoint (validates PAT, encrypts, writes to Firestore)
- Add /api/getosftoken endpoint (decrypts and returns token to authenticated owner)
- Encrypt tokens in oauth2-callback.ts and refresh-token.ts on write
- Decrypt tokens in api-data.ts, api-base64.ts, metadata-block.ts,
  oauth2-regenerate.ts, and scheduled-token-refresh.ts on read
- Move frontend PAT writes from direct Firestore to /api/saveosftoken
- Move frontend token reads from direct Firestore to /api/getosftoken
- Tighten Firestore rules: remove client OAuth token write permissions
- Add migration script for encrypting existing plaintext tokens
- Add TOKEN_ENCRYPTION_KEY to CI/CD workflows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jodeleeuw jodeleeuw changed the base branch from main to osf-entry-point-integration February 24, 2026 19:56
@jadeddelta jadeddelta merged commit 2f29066 into osf-entry-point-integration Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants