From bfd630ec26baf6ef12680bcb461476897d08e09b Mon Sep 17 00:00:00 2001 From: Anton Schegg Date: Sat, 26 Oct 2024 20:32:41 +0200 Subject: [PATCH] DB (encrypted) export/import --- src/controller/admin/exportController.ts | 157 ++++++++++++++++++----- src/routes/admin/database.ts | 8 +- 2 files changed, 133 insertions(+), 32 deletions(-) diff --git a/src/controller/admin/exportController.ts b/src/controller/admin/exportController.ts index 739610c..fa89fc7 100644 --- a/src/controller/admin/exportController.ts +++ b/src/controller/admin/exportController.ts @@ -5,6 +5,8 @@ import CommunicationTypeService from "../../service/communicationTypeService"; import ExecutivePositionService from "../../service/executivePositionService"; import crypto from "crypto"; import { type BinaryLike } from "node:crypto"; +import { CreateAwardCommand } from "../../command/awardCommand"; +import AwardCommandHandler from "../../command/awardCommandHandler"; /** * @description get all data stored in the database as a single json object @@ -13,52 +15,147 @@ import { type BinaryLike } from "node:crypto"; * @returns {Promise<*>} */ export async function getDatabaseExport(req: Request, res: Response): Promise { - const { secret } = req.body; + const noEncryption = req.headers["x-encrypt-no"]; + + const secret = req.headers["x-encrypt-with"]; try { - if (!secret) { - res.status(400).send('Empty encryption key'); + if (!secret && !noEncryption) { + res.status(400).send("Empty encryption key"); return; } - const iv = await new Promise((resolve, reject) => { - // create initialization vector - crypto.randomBytes(16, (err, iv) => { - if (err) { - reject(err); - return; - } - resolve(iv); - }); - }); let data: any = {}; - console.log('Exporting awards...'); + console.log("Exporting awards..."); data.awards = await AwardService.getAll(); console.log(`Exported ${data.awards.length} awards`); - console.log('Exporting communication types...'); + console.log("Exporting communication types..."); data.communicationTypes = await CommunicationTypeService.getAll(); console.log(`Exported ${data.communicationTypes.length} communicationTypes`); - console.log('Exporting executivePositions...'); + console.log("Exporting executivePositions..."); data.executivePositions = await ExecutivePositionService.getAll(); console.log(`Exported ${data.executivePositions.length} executivePositions`); - console.log('Exporting members...'); + console.log("Exporting members..."); data.members = await MemberService.getAll(); console.log(`Exported ${data.members.length} members`); - console.log(`Encrypting data...`); - // encrypt data - const dataStr: string = JSON.stringify(data); - let base64Key = crypto.createHash('sha256').update(String(secret)).digest('base64'); - const key = Buffer.from(base64Key, 'base64') - let cipher = crypto.createCipheriv("aes-256-gcm", key, iv as BinaryLike); - const encryptedData: string = cipher.update(dataStr, 'utf8', 'hex') + cipher.final('hex'); + if (noEncryption) { + console.log(`Sending data unencrypted with length ${data.length}`); + res.json({ + encrypted: !noEncryption, + data: data, + }); + console.log("finished encrypted data"); + } else { + const dataStr: string = JSON.stringify(data); + console.log(`Encrypting data...`); + const iv = await new Promise((resolve, reject) => { + // create initialization vector + crypto.randomBytes(16, (err, iv) => { + if (err) { + reject(err); + return; + } + resolve(iv); + }); + }); + // encrypt data + let base64Key = crypto.createHash("sha256").update(String(secret)).digest("base64"); + const key = Buffer.from(base64Key, "base64"); - res.json({ - data: encryptedData, - }); - } - catch(ex) { + // create initialization vector + const randomIV: Buffer = await new Promise((resolve, reject) => { + crypto.randomBytes(16, (err, iv) => { + if (err) { + reject(err); + return; + } + resolve(iv); + }); + }); + // console.log(`iv: ${randomIV.toString('hex')}`); + // console.log(`key: ${key.toString('hex')}`); + + let cipher = crypto.createCipheriv("aes-256-gcm", key, randomIV); + const encryptedData: string = cipher.update(dataStr, "utf8", "hex") + cipher.final("hex"); + const authTag = cipher.getAuthTag().toString("hex"); + + // console.log(`authTag: ${authTag}`); + console.log(`Sending encrypted data with length ${encryptedData.length}`); + res.json({ + encrypted: !noEncryption, + iv: randomIV.toString("base64"), + data: encryptedData, + authTag: authTag, + }); + console.log("finished encrypted data"); + } + } catch (ex) { console.log(ex); - res.status(500).send(ex.message ? ex.message : 'Exception!'); + res.status(500).send(ex.message ? ex.message : "Exception!"); + } +} + + +/** + * @description decrypt the import data and use it to re-fill the database + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function importDatabaseExport(req: Request, res: Response): Promise { + const { data: receivedData } = req.body; + if (!receivedData) { + res.status(400).send("Empty database import data"); + return; + } + const noEncryption = req.headers["x-encrypt-no"]; + try { + let data; + if (noEncryption) { + data = receivedData; + } else { + const secret = req.headers["x-decrypt-with"]; + const { iv, authTag } = req.body; + if (!secret) { + res.status(400).send("Empty decryption key"); + return; + } + if (!iv) { + res.status(400).send("Empty database import iv"); + return; + } + if (!authTag) { + res.status(400).send("Empty database import authTag"); + return; + } + + // prepare the secret to be in a buffer + const base64Key = crypto.createHash("sha256").update(String(secret)).digest("base64"); + const key = Buffer.from(base64Key, "base64"); + + // decrypt data + const ivString = Buffer.from(iv, "base64"); + // console.log(`iv: ${ivString.toString('hex')}`); + // console.log(`key: ${key.toString('hex')}`); + // console.log(`authTag: ${authTag}`); + const decipher = crypto.createDecipheriv("aes-256-gcm", key, ivString); + decipher.setAuthTag(Buffer.from(authTag, "hex")); + const decrypted = decipher.update(receivedData, "hex", "utf-8") + decipher.final("utf-8"); + data = JSON.parse(decrypted.toString()); + } + + for (const award of data.awards) { + let createAward: CreateAwardCommand = { + award: award, + }; + const awardId: any = await AwardCommandHandler.create(createAward); + console.log(`Award with id ${awardId} created`); + } + + res.send(200); + } catch (ex) { + console.log(ex); + res.status(500).send(ex.message ? ex.message : "Exception!"); } } diff --git a/src/routes/admin/database.ts b/src/routes/admin/database.ts index 8b90340..632797e 100644 --- a/src/routes/admin/database.ts +++ b/src/routes/admin/database.ts @@ -1,11 +1,15 @@ import express, { Request, Response } from "express"; -import { getDatabaseExport } from "../../controller/admin/exportController"; +import { getDatabaseExport, importDatabaseExport } from "../../controller/admin/exportController"; const router = express.Router({ mergeParams: true }); -router.post("/", async (req: Request, res: Response) => { +router.get("/", async (req: Request, res: Response) => { await getDatabaseExport(req, res); }); +router.post("/", async (req: Request, res: Response) => { + await importDatabaseExport(req, res); +}); + export default router;