PD-16 — Plan d'implémentation¶
📚 Navigation User Story
| Document | | | ---------- | -- | | 📋 [Spécification](PD-16-specification.md) | | | 🛠️ **Plan d'implémentation** | *(ce document)* | | ✅ [Critères d'acceptation](PD-16-acceptability.md) | | | 📝 [Retour d'expérience](PD-16-rex.md) | | [← Retour à backend-core](../PD-186-epic.md) · [↑ Index User Story](index.md)Objectif¶
Créer le schéma PostgreSQL vault_secure.documents avec Row-Level Security (RLS) et cycle de vie probatoire.
Choix techniques retenus¶
- Schéma :
vault_secure(isolation) - RLS : Policies par user_id
- Cycle : PENDING → SEALED → EXPIRED
- Context : AsyncLocalStorage pour user_id
Architecture ciblée¶
src/modules/documents/entities/
└── document-secure.entity.ts
src/modules/auth/services/
└── rls-context.service.ts
src/modules/auth/middleware/
└── rls.middleware.ts
src/database/migrations/
├── 1731888050000-CreateVaultSecureSchema.ts
└── 1731888100000-CreateDocumentsTable.ts
Découpage technique¶
Phase 1 : Schéma et ENUM¶
- Créer migration schema :
CREATE SCHEMA IF NOT EXISTS vault_secure;
DO $$ BEGIN
CREATE TYPE vault_secure.document_status AS ENUM (
'PENDING', 'SEALED', 'EXPIRED'
);
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
Phase 2 : Table documents¶
- Migration table :
CREATE TABLE vault_secure.documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
encrypted_metadata BYTEA NOT NULL,
keyword_deterministic TEXT[] NULL,
file_hash BYTEA NOT NULL CHECK (octet_length(file_hash) = 32),
ovh_path TEXT NOT NULL,
status vault_secure.document_status DEFAULT 'PENDING',
sealed_at TIMESTAMPTZ,
retention_until TIMESTAMPTZ,
expired_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
Phase 3 : Index¶
- Créer index :
idx_documents_user_id(BTREE)idx_documents_user_file_hash(UNIQUE)idx_documents_created_at(DESC)idx_documents_keywords_gin(GIN)idx_documents_statusidx_documents_retention_until
Phase 4 : RLS Policies¶
- Activer RLS :
- Créer policies :
-- SELECT : propriétaire uniquement
CREATE POLICY documents_select ON vault_secure.documents
FOR SELECT USING (
user_id = current_setting('app.current_user_id', true)::uuid
);
-- INSERT : forcer user_id
CREATE POLICY documents_insert ON vault_secure.documents
FOR INSERT WITH CHECK (
user_id = current_setting('app.current_user_id', true)::uuid
);
-- UPDATE : propriétaire + PENDING
CREATE POLICY documents_update ON vault_secure.documents
FOR UPDATE USING (
user_id = current_setting('app.current_user_id', true)::uuid
AND status = 'PENDING'
);
-- DELETE : propriétaire + PENDING
CREATE POLICY documents_delete ON vault_secure.documents
FOR DELETE USING (
user_id = current_setting('app.current_user_id', true)::uuid
AND status = 'PENDING'
);
Phase 5 : Injection contexte¶
- Créer
RlsContextService: - Utiliser AsyncLocalStorage
-
Stocker user_id par requête
-
Créer
RlsMiddleware: - Extraire user_id du JWT
- Injecter dans AsyncLocalStorage
- SET LOCAL app.current_user_id
Phase 6 : Entité TypeORM¶
- Créer
DocumentSecureentity : - Mapper colonnes
- Relations user
- Enum status
Phase 7 : Tests¶
- Tests RLS isolation (user A ≠ user B)
- Tests policies UPDATE/DELETE
- Tests contraintes (file_hash 32 bytes)
- Tests transitions status
Points de vigilance¶
- RLS bypass : Attention aux rôles superuser
- AsyncLocalStorage : Bien gérer le cycle de vie
- Transactions : SET LOCAL valide pour transaction courante
- SEALED : Immutable, prévoir role technique pour transitions
Hors périmètre¶
- Upload fichiers OVH (→ PD-5)
- Calcul hash SHA3 (→ PD-38)
- API CRUD documents (→ autre US)