feat(#352): Encrypt existing plaintext Account tokens
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Implements transparent encryption/decryption of OAuth tokens via Prisma middleware with progressive migration strategy. Core Implementation: - Prisma middleware transparently encrypts tokens on write, decrypts on read - Auto-detects ciphertext format: aes:iv:authTag:encrypted, vault:v1:..., or plaintext - Uses existing CryptoService (AES-256-GCM) for encryption - Progressive encryption: tokens encrypted as they're accessed/refreshed - Zero-downtime migration (schema change only, no bulk data migration) Security Features: - Startup key validation prevents silent data loss if ENCRYPTION_KEY changes - Secure error logging (no stack traces that could leak sensitive data) - Graceful handling of corrupted encrypted data - Idempotent encryption prevents double-encryption - Future-proofed for OpenBao Transit encryption (Phase 2) Token Fields Encrypted: - accessToken (OAuth access tokens) - refreshToken (OAuth refresh tokens) - idToken (OpenID Connect ID tokens) Backward Compatibility: - Existing plaintext tokens readable (encryptionVersion = NULL) - Progressive encryption on next write - BetterAuth integration transparent (middleware layer) Test Coverage: - 20 comprehensive unit tests (89.06% coverage) - Encryption/decryption scenarios - Null/undefined handling - Corrupted data handling - Legacy plaintext compatibility - Future vault format support - All CRUD operations (create, update, updateMany, upsert) Files Created: - apps/api/src/prisma/account-encryption.middleware.ts - apps/api/src/prisma/account-encryption.middleware.spec.ts - apps/api/prisma/migrations/20260207_encrypt_account_tokens/migration.sql Files Modified: - apps/api/src/prisma/prisma.service.ts (register middleware) - apps/api/src/prisma/prisma.module.ts (add CryptoService) - apps/api/src/federation/crypto.service.ts (add key validation) - apps/api/prisma/schema.prisma (add encryptionVersion) - .env.example (document ENCRYPTION_KEY) Fixes #352 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,34 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { PrismaService } from "./prisma.service";
|
||||
import { CryptoService } from "../federation/crypto.service";
|
||||
|
||||
describe("PrismaService", () => {
|
||||
let service: PrismaService;
|
||||
let mockConfigService: Partial<ConfigService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Mock ConfigService with a valid test encryption key
|
||||
mockConfigService = {
|
||||
get: vi.fn((key: string) => {
|
||||
if (key === "ENCRYPTION_KEY") {
|
||||
// Valid 64-character hex string (32 bytes)
|
||||
return "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [PrismaService],
|
||||
providers: [
|
||||
PrismaService,
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: mockConfigService,
|
||||
},
|
||||
CryptoService,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<PrismaService>(PrismaService);
|
||||
@@ -25,6 +46,8 @@ describe("PrismaService", () => {
|
||||
describe("onModuleInit", () => {
|
||||
it("should connect to the database", async () => {
|
||||
const connectSpy = vi.spyOn(service, "$connect").mockResolvedValue(undefined);
|
||||
// Mock $use to prevent middleware registration errors in tests
|
||||
(service as any).$use = vi.fn();
|
||||
|
||||
await service.onModuleInit();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user