feat(security): field encryption AES-256-GCM + random IV (at-rest)

- common/crypto/crypto.util.ts: encrypt/decrypt (GCM, random 96-bit IV), format v1:base64(iv|tag|ct)
- EncryptedTransformer applied to users.name + drivers.vehicle_plate (auto, no service change)
- blindIndex (HMAC) helper for future searchable-field encryption (phone)
- tolerant decrypt for legacy plaintext → no migration needed (varchar holds base64)
- ENCRYPTION_KEY env + boot warning if unset; docs/16-encryption.md
- fixes Siro's documented CBC+fixed-IV flaw

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 19:24:27 +03:00
co-authored by Claude Opus 4.8
parent bb6a7bc7ec
commit f3383c3cf8
6 changed files with 114 additions and 2 deletions
@@ -6,6 +6,7 @@ import {
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { EncryptedTransformer } from '../../../common/crypto/crypto.util';
export type VerificationStatus = 'pending' | 'approved' | 'rejected';
@@ -34,7 +35,8 @@ export class Driver {
@Column({ nullable: true })
vehicle_model: string;
@Column({ nullable: true })
// مشفّر at-rest (AES-256-GCM)
@Column({ type: 'varchar', nullable: true, transformer: EncryptedTransformer })
vehicle_plate: string;
@Column({ nullable: true })
@@ -1,4 +1,5 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { EncryptedTransformer } from '../../../common/crypto/crypto.util';
export enum UserRole {
RIDER = 'rider',
@@ -18,7 +19,8 @@ export class User {
@Column()
phone: string;
@Column({ nullable: true })
// مشفّر at-rest (AES-256-GCM) — يُفكّ تلقائياً عند القراءة
@Column({ type: 'varchar', nullable: true, transformer: EncryptedTransformer })
name: string;
// varchar (لا enum) لمطابقة الهجرة وتفادي إنشاء نوع enum في القاعدة.