Skip to content

Commit dbaa2e1

Browse files
committed
feat(backend): encrypt structure's files
1 parent d2a47a2 commit dbaa2e1

File tree

33 files changed

+363
-235
lines changed

33 files changed

+363
-235
lines changed

packages/backend/src/_migrations/1746974453999-manual-migration.ts

Lines changed: 0 additions & 103 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
import { domifaConfig } from "../config";
3+
4+
export class AutoMigration1747256357377 implements MigrationInterface {
5+
name = "AutoMigration1747256357377";
6+
7+
public async up(queryRunner: QueryRunner): Promise<void> {
8+
if (
9+
domifaConfig().envId === "prod" ||
10+
domifaConfig().envId === "preprod" ||
11+
domifaConfig().envId === "local"
12+
) {
13+
await queryRunner.query(
14+
`ALTER TABLE "structure_doc" ADD "encryptionContext" text`
15+
);
16+
await queryRunner.query(
17+
`ALTER TABLE "structure_doc" ADD "encryptionVersion" integer`
18+
);
19+
}
20+
}
21+
22+
public async down(queryRunner: QueryRunner): Promise<void> {
23+
await queryRunner.query(
24+
`ALTER TABLE "structure_doc" DROP COLUMN "encryptionVersion"`
25+
);
26+
await queryRunner.query(
27+
`ALTER TABLE "structure_doc" DROP COLUMN "encryptionContext"`
28+
);
29+
}
30+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
import { appLogger, cleanPath } from "../util";
3+
import { structureDocRepository } from "../database";
4+
import { join } from "node:path";
5+
import { FileManagerService } from "../util/file-manager/file-manager.service";
6+
import { StructureDoc } from "@domifa/common";
7+
8+
export class EncryptStructureDocsMigration1748264444997
9+
implements MigrationInterface
10+
{
11+
public fileManagerService: FileManagerService;
12+
13+
constructor() {
14+
this.fileManagerService = new FileManagerService();
15+
}
16+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
17+
public async up(_queryRunner: QueryRunner): Promise<void> {
18+
appLogger.warn(
19+
"[MIGRATION] Début du chiffrement des documents de structure"
20+
);
21+
22+
await structureDocRepository.update({}, { encryptionContext: null });
23+
type Docs = StructureDoc & {
24+
structureUuid: string;
25+
};
26+
27+
const unencryptedDocs: Docs[] = await structureDocRepository
28+
.createQueryBuilder("structure_doc")
29+
.leftJoin(
30+
"structure",
31+
"structure",
32+
`structure."id" = structure_doc."structureId"`
33+
)
34+
.where(`structure_doc."encryptionContext" IS NULL`)
35+
.select([
36+
`structure_doc."uuid" as "uuid"`,
37+
`structure_doc."structureId" as "structureId"`,
38+
`structure_doc."path" as "path"`,
39+
`structure_doc."filetype" as "filetype"`,
40+
`structure_doc."encryptionContext" as "encryptionContext"`,
41+
`structure."uuid" as "structureUuid"`,
42+
])
43+
.getRawMany();
44+
45+
appLogger.warn(
46+
`[MIGRATION] ${unencryptedDocs.length} documents à chiffrer`
47+
);
48+
49+
let processedCount = 0;
50+
let notFoundCount = 0;
51+
let errorCount = 0;
52+
53+
const errors: Array<{ filePath: string; error: string }> = [];
54+
for (const doc of unencryptedDocs) {
55+
const filePath = join(
56+
"structure-documents",
57+
cleanPath(`${doc.structureId}`),
58+
doc.path
59+
);
60+
61+
try {
62+
const fileExists = await this.fileManagerService.fileExists(filePath);
63+
if (!fileExists) {
64+
appLogger.error(`🔴 File not found: ${filePath}`);
65+
notFoundCount++;
66+
continue;
67+
}
68+
69+
appLogger.info(`⌛ Processing : ${filePath}`);
70+
71+
const encryptionContext = crypto.randomUUID();
72+
const newFilePath = join(
73+
"structure-documents-encrypted",
74+
cleanPath(`${doc.structureUuid}`),
75+
`${doc.path}.sfe`
76+
);
77+
78+
const object = await this.fileManagerService.getFileBody(filePath);
79+
await this.fileManagerService.saveEncryptedFile(
80+
newFilePath,
81+
{ ...doc, encryptionContext },
82+
object
83+
);
84+
85+
await structureDocRepository.update(
86+
{ uuid: doc.uuid },
87+
{
88+
encryptionContext,
89+
encryptionVersion: 0,
90+
}
91+
);
92+
93+
appLogger.info(`✅ encypt done : ${doc.structureUuid} ${filePath}`);
94+
processedCount++;
95+
} catch (error) {
96+
appLogger.error(
97+
`🟠 Error during encryption: ${doc.structureUuid} ${filePath} - ${error.message}`
98+
);
99+
errors.push({ filePath, error: error.message });
100+
errorCount++;
101+
}
102+
}
103+
104+
const totalDocs = unencryptedDocs.length;
105+
const successRate =
106+
totalDocs > 0 ? ((processedCount / totalDocs) * 100).toFixed(1) : "0";
107+
108+
const migrationSummary = {
109+
"Total documents": totalDocs,
110+
"✅ Success": processedCount,
111+
"🔴 Files not found": notFoundCount,
112+
"🟠 Processing errors": errorCount,
113+
"📊 Success rate": `${successRate}%`,
114+
};
115+
116+
appLogger.warn("MIGRATION SUMMARY");
117+
console.table(migrationSummary);
118+
119+
if (errors.length > 0) {
120+
appLogger.error("Error details:");
121+
console.table(errors);
122+
}
123+
124+
if (processedCount === totalDocs) {
125+
appLogger.info("🎉 Migration completed successfully!");
126+
} else {
127+
appLogger.warn("⚠️ Migration completed with errors");
128+
}
129+
}
130+
131+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
132+
public async down(_queryRunner: QueryRunner): Promise<void> {}
133+
}

packages/backend/src/database/entities/structure-doc/StructureDocTable.typeorm.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ export class StructureDocTable
5454
@Column({ type: "text", nullable: false })
5555
public path!: string;
5656

57+
@Column({ type: "text", nullable: true })
58+
public encryptionContext: string;
59+
60+
@Column({ type: "integer", nullable: true })
61+
public encryptionVersion: number;
62+
5763
public constructor(entity?: Partial<StructureDocTable>) {
5864
super(entity);
5965
Object.assign(this, entity);

packages/backend/src/modules/portail-usagers/controllers/postail-usagers-profile/portail-usagers-profile.controller.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ import {
2323
} from "../../../../database";
2424
import { InteractionsService } from "../../../interactions/services";
2525
import { PageOptionsDto } from "../../../../usagers/dto";
26-
import { appLogger } from "../../../../util";
26+
import { appLogger, cleanPath } from "../../../../util";
2727
import { FileManagerService } from "../../../../util/file-manager/file-manager.service";
2828
import { AppLogsService } from "../../../app-logs/app-logs.service";
2929
import { Response } from "express";
30+
import { join } from "node:path";
3031

3132
@Controller("portail-usagers/profile")
3233
@UseGuards(AuthGuard("jwt"), AppUserGuard)
@@ -158,10 +159,16 @@ export class PortailUsagersProfileController {
158159
});
159160

160161
try {
162+
const filePath = join(
163+
"usager-documents",
164+
cleanPath(currentUser.structure.uuid),
165+
cleanPath(doc.usagerUUID),
166+
`${doc.path}.sfe`
167+
);
168+
161169
return await this.fileManagerService.dowloadEncryptedFile(
162170
res,
163-
currentUser.structure.uuid,
164-
currentUser.usager.uuid,
171+
filePath,
165172
doc
166173
);
167174
} catch (e) {

packages/backend/src/modules/structures/controllers/structure-doc.controller.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,16 @@ export class StructureDocController {
6060
});
6161

6262
const filePath = join(
63-
"structure-documents",
64-
cleanPath(`${user.structureId}`),
65-
doc.path
63+
"structure-documents-encrypted",
64+
cleanPath(`${user.structure.uuid}`),
65+
`${doc.path}.sfe`
66+
);
67+
68+
return await this.fileManagerService.dowloadEncryptedFile(
69+
res,
70+
filePath,
71+
doc
6672
);
67-
return await this.fileManagerService.downloadObject(filePath, res);
6873
} catch (e) {
6974
return res
7075
.status(HttpStatus.BAD_REQUEST)
@@ -125,13 +130,7 @@ export class StructureDocController {
125130
}
126131

127132
const path = randomName(file);
128-
129-
const filePath = join(
130-
"structure-documents",
131-
cleanPath(`${user.structureId}`),
132-
path
133-
);
134-
await this.fileManagerService.uploadFile(filePath, file.buffer);
133+
const encryptionContext = crypto.randomUUID();
135134

136135
const newDoc = new StructureDocTable({
137136
createdAt: new Date(),
@@ -140,6 +139,7 @@ export class StructureDocController {
140139
nom: user.nom,
141140
prenom: user.prenom,
142141
},
142+
encryptionContext,
143143
displayInPortailUsager: false,
144144
filetype: file.mimetype,
145145
path,
@@ -149,6 +149,21 @@ export class StructureDocController {
149149
customDocType: structureDocDto.customDocType,
150150
});
151151

152+
const filePath = join(
153+
"structure-documents-encrypted",
154+
cleanPath(`${user.structure.uuid}`),
155+
`${path}.sfe`
156+
);
157+
158+
try {
159+
await this.fileManagerService.saveEncryptedFile(filePath, newDoc, file);
160+
} catch (e) {
161+
console.log(e);
162+
return res
163+
.status(HttpStatus.INTERNAL_SERVER_ERROR)
164+
.json({ message: "CANNOT_ENCRYPT_FILE" });
165+
}
166+
152167
try {
153168
await structureDocRepository.insert(newDoc);
154169
const docs = await structureDocRepository.findBy({

0 commit comments

Comments
 (0)