KI-AGENT: Verschlüssele Bankverbindungen beim Import neu
This commit is contained in:
@@ -324,16 +324,28 @@ function maskIban(iban: string) {
|
||||
}
|
||||
|
||||
function decryptEntityBankAccount(row: Record<string, any>) {
|
||||
const iban = row.ibanEncrypted ? decrypt(row.ibanEncrypted as any) : null
|
||||
const bic = row.bicEncrypted ? decrypt(row.bicEncrypted as any) : null
|
||||
const bankName = row.bankNameEncrypted ? decrypt(row.bankNameEncrypted as any) : null
|
||||
let iban = null
|
||||
let bic = null
|
||||
let bankName = null
|
||||
let decryptError = null
|
||||
|
||||
try {
|
||||
iban = row.ibanEncrypted ? decrypt(row.ibanEncrypted as any) : null
|
||||
bic = row.bicEncrypted ? decrypt(row.bicEncrypted as any) : null
|
||||
bankName = row.bankNameEncrypted ? decrypt(row.bankNameEncrypted as any) : null
|
||||
} catch (err: any) {
|
||||
decryptError = err?.message || "Bankverbindung konnte nicht entschlüsselt werden."
|
||||
}
|
||||
|
||||
return {
|
||||
...row,
|
||||
iban,
|
||||
bic,
|
||||
bankName,
|
||||
displayLabel: `${maskIban(iban || "")}${bankName ? ` | ${bankName}` : ""}${row.description ? ` (${row.description})` : ""}`.trim(),
|
||||
decryptError,
|
||||
displayLabel: decryptError
|
||||
? `Bankverbindung nicht lesbar${row.description ? ` (${row.description})` : ""}`
|
||||
: `${maskIban(iban || "")}${bankName ? ` | ${bankName}` : ""}${row.description ? ` (${row.description})` : ""}`.trim(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,18 @@ import crypto from "crypto";
|
||||
import {secrets} from "./secrets"
|
||||
const ALGORITHM = "aes-256-gcm";
|
||||
|
||||
function getEncryptionKey() {
|
||||
const key = secrets.ENCRYPTION_KEY || ""
|
||||
|
||||
if (!/^[a-f0-9]{64}$/i.test(key)) {
|
||||
throw new Error("ENCRYPTION_KEY muss ein 64 Zeichen langer Hex-String sein. Beispiel: openssl rand -hex 32")
|
||||
}
|
||||
|
||||
return Buffer.from(key, "hex")
|
||||
}
|
||||
|
||||
export function encrypt(text) {
|
||||
const ENCRYPTION_KEY = Buffer.from(secrets.ENCRYPTION_KEY, "hex");
|
||||
const ENCRYPTION_KEY = getEncryptionKey();
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv(ALGORITHM, ENCRYPTION_KEY, iv);
|
||||
|
||||
@@ -21,7 +28,7 @@ export function encrypt(text) {
|
||||
}
|
||||
|
||||
export function decrypt({ iv, content, tag }) {
|
||||
const ENCRYPTION_KEY = Buffer.from(secrets.ENCRYPTION_KEY, "hex");
|
||||
const ENCRYPTION_KEY = getEncryptionKey();
|
||||
const decipher = crypto.createDecipheriv(
|
||||
ALGORITHM,
|
||||
ENCRYPTION_KEY,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"
|
||||
import { pool } from "../../db"
|
||||
import { s3 } from "./s3"
|
||||
import { secrets } from "./secrets"
|
||||
import { decrypt, encrypt } from "./crypt"
|
||||
|
||||
type TableRows = Record<string, Record<string, any>[]>
|
||||
type TableMetadata = {
|
||||
@@ -38,6 +39,12 @@ type ImportOptions = {
|
||||
targetTenantId?: number | null
|
||||
}
|
||||
|
||||
const ENTITY_BANKACCOUNT_PLAIN_FIELDS = {
|
||||
iban: "__plainIban",
|
||||
bic: "__plainBic",
|
||||
bankName: "__plainBankName",
|
||||
}
|
||||
|
||||
const quoteIdent = (value: string) => `"${value.replace(/"/g, '""')}"`
|
||||
|
||||
const tableColumns = async (client: any) => {
|
||||
@@ -98,6 +105,22 @@ const addRows = (tables: TableRows, table: string, rows: Record<string, any>[])
|
||||
tables[table] = existingRows
|
||||
}
|
||||
|
||||
const decryptEntityBankAccountsForExport = (rows: Record<string, any>[] = []) => {
|
||||
return rows.map((row) => {
|
||||
const nextRow = { ...row }
|
||||
|
||||
try {
|
||||
nextRow[ENTITY_BANKACCOUNT_PLAIN_FIELDS.iban] = row.iban_encrypted ? decrypt(row.iban_encrypted) : null
|
||||
nextRow[ENTITY_BANKACCOUNT_PLAIN_FIELDS.bic] = row.bic_encrypted ? decrypt(row.bic_encrypted) : null
|
||||
nextRow[ENTITY_BANKACCOUNT_PLAIN_FIELDS.bankName] = row.bank_name_encrypted ? decrypt(row.bank_name_encrypted) : null
|
||||
} catch (err: any) {
|
||||
throw new Error(`Bankverbindung ${row.id || ""} konnte für den Export nicht entschlüsselt werden: ${err?.message || err}`)
|
||||
}
|
||||
|
||||
return nextRow
|
||||
})
|
||||
}
|
||||
|
||||
const loadObjectAsBase64 = async (path: string) => {
|
||||
const { Body } = await s3.send(new GetObjectCommand({
|
||||
Bucket: secrets.S3_BUCKET,
|
||||
@@ -165,6 +188,10 @@ export const buildTenantFullExport = async (server: FastifyInstance, tenantId: n
|
||||
addRows(tables, "auth_profile_teams", await loadRows(client, "auth_profile_teams", "profile_id = any($1::uuid[])", [profileIds]))
|
||||
}
|
||||
|
||||
if (tables.entitybankaccounts?.length) {
|
||||
tables.entitybankaccounts = decryptEntityBankAccountsForExport(tables.entitybankaccounts)
|
||||
}
|
||||
|
||||
const fileRows = tables.files || []
|
||||
const files = []
|
||||
|
||||
@@ -266,6 +293,26 @@ const remapTenantScopedExport = (
|
||||
}
|
||||
}
|
||||
|
||||
const encryptEntityBankAccountRowsForImport = (exportData: TenantFullExport) => {
|
||||
const rows = exportData.tables.entitybankaccounts || []
|
||||
|
||||
for (const row of rows) {
|
||||
const plainIban = row[ENTITY_BANKACCOUNT_PLAIN_FIELDS.iban]
|
||||
const plainBic = row[ENTITY_BANKACCOUNT_PLAIN_FIELDS.bic]
|
||||
const plainBankName = row[ENTITY_BANKACCOUNT_PLAIN_FIELDS.bankName]
|
||||
|
||||
if (typeof plainIban === "string" && typeof plainBic === "string" && typeof plainBankName === "string") {
|
||||
row.iban_encrypted = encrypt(plainIban)
|
||||
row.bic_encrypted = encrypt(plainBic)
|
||||
row.bank_name_encrypted = encrypt(plainBankName)
|
||||
}
|
||||
|
||||
delete row[ENTITY_BANKACCOUNT_PLAIN_FIELDS.iban]
|
||||
delete row[ENTITY_BANKACCOUNT_PLAIN_FIELDS.bic]
|
||||
delete row[ENTITY_BANKACCOUNT_PLAIN_FIELDS.bankName]
|
||||
}
|
||||
}
|
||||
|
||||
const prepareColumnValue = (value: any, isJsonColumn: boolean) => {
|
||||
if (!isJsonColumn || value === null || typeof value === "undefined") return value
|
||||
if (typeof value === "string") return value
|
||||
@@ -384,6 +431,7 @@ export const importTenantFullExport = async (
|
||||
}
|
||||
|
||||
const exportData = remapTenantScopedExport(rawExportData, options.targetTenantId)
|
||||
encryptEntityBankAccountRowsForImport(exportData)
|
||||
const client = await pool.connect()
|
||||
const importOrder = [
|
||||
"tenants",
|
||||
|
||||
Reference in New Issue
Block a user