store data encoded

This commit is contained in:
Julian Krauser 2025-04-08 11:14:46 +02:00
parent 0220f468ac
commit 297f1d8bda
8 changed files with 124 additions and 14 deletions

View file

@ -24,6 +24,8 @@ JWT_EXPIRATION = [0-9]*(y|d|h|m|s) # default ist 15m
REFRESH_EXPIRATION = [0-9]*(y|d|h|m|s) # default ist 1d
PWA_REFRESH_EXPIRATION = [0-9]*(y|d|h|m|s) # default ist 5d
CODING_SECRET = ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 # besitzt default
MAIL_USERNAME = mail_username
MAIL_PASSWORD = mail_password
MAIL_HOST = mail_hoststring

8
package-lock.json generated
View file

@ -12,6 +12,7 @@
"@ff-admin/webapi-client": "^1.1.1",
"@socket.io/admin-ui": "^0.5.1",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"dotenv": "^16.4.5",
"express": "^5.0.0-beta.3",
"express-rate-limit": "^7.5.0",
@ -1407,6 +1408,13 @@
"node": ">= 8"
}
},
"node_modules/crypto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.",
"license": "ISC"
},
"node_modules/dayjs": {
"version": "1.11.12",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz",

View file

@ -27,6 +27,7 @@
"@ff-admin/webapi-client": "^1.1.1",
"@socket.io/admin-ui": "^0.5.1",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"dotenv": "^16.4.5",
"express": "^5.0.0-beta.3",
"express-rate-limit": "^7.5.0",

View file

@ -5,6 +5,8 @@ import { mission_vehicle } from "./mission_vehicle";
import { mission_equipment } from "./mission_equipment";
import { mission_contact } from "./mission_contact";
import { getTypeByORM } from "../../migrations/ormHelper";
import { CodingHelper } from "../../helpers/codingHelper";
import { CODING_SECRET } from "../../env.defaults";
@Entity()
export class mission {
@ -29,9 +31,12 @@ export class mission {
@Column({ type: "varchar", length: 255, default: "" })
keyword: string;
@Column({ type: "varchar", length: 255, default: "" })
@Column({ type: "text", default: "", transformer: CodingHelper.entityBaseCoding(CODING_SECRET) })
location: string;
@Column({ type: "varchar", length: 255, default: "" })
city: string;
@Column({ type: "varchar", length: 255, default: "" })
others: string;
@ -41,7 +46,7 @@ export class mission {
@Column({ type: "int", default: 0 })
recovered: number;
@Column({ type: "text", default: "[]" })
@Column({ type: "text", default: "", transformer: CodingHelper.entityBaseCoding(CODING_SECRET, "[]") })
description: string;
@Column({ type: "bigint", default: 0 })

View file

@ -1,5 +1,7 @@
import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
import { mission } from "./mission";
import { CodingHelper } from "../../helpers/codingHelper";
import { CODING_SECRET } from "../../env.defaults";
@Entity()
export class mission_contact {
@ -9,19 +11,19 @@ export class mission_contact {
@PrimaryColumn({ type: "varchar", length: 36 })
contactId: string;
@Column({ type: "varchar", length: 255, default: "" })
@Column({ type: "text", default: "", transformer: CodingHelper.entityBaseCoding(CODING_SECRET) })
firstname: string;
@Column({ type: "varchar", length: 255, default: "" })
@Column({ type: "text", default: "", transformer: CodingHelper.entityBaseCoding(CODING_SECRET) })
lastname: string;
@Column({ type: "varchar", length: 255, default: "" })
@Column({ type: "text", default: "", transformer: CodingHelper.entityBaseCoding(CODING_SECRET) })
phone: string;
@Column({ type: "varchar", length: 255, default: "" })
@Column({ type: "text", default: "", transformer: CodingHelper.entityBaseCoding(CODING_SECRET) })
address: string;
@Column({ type: "varchar", length: 255, default: "" })
@Column({ type: "text", default: "", transformer: CodingHelper.entityBaseCoding(CODING_SECRET) })
note: string;
@ManyToOne(() => mission, {

View file

@ -16,6 +16,9 @@ export const JWT_EXPIRATION = process.env.JWT_EXPIRATION ?? "15m";
export const REFRESH_EXPIRATION = process.env.REFRESH_EXPIRATION ?? "1d";
export const PWA_REFRESH_EXPIRATION = process.env.PWA_REFRESH_EXPIRATION ?? "5d";
export const CODING_SECRET =
process.env.CODING_SECRET ?? "my_coding_secret_string_41YzO6JiE6iGNUZsZXX8EQa6L2DpqtdZiPK6VYRS";
export const MAIL_USERNAME = process.env.MAIL_USERNAME ?? "";
export const MAIL_PASSWORD = process.env.MAIL_PASSWORD ?? "";
export const MAIL_HOST = process.env.MAIL_HOST ?? "";
@ -75,6 +78,8 @@ export function configCheck() {
checkMS(REFRESH_EXPIRATION, "REFRESH_EXPIRATION");
checkMS(PWA_REFRESH_EXPIRATION, "PWA_REFRESH_EXPIRATION");
if (CODING_SECRET == "" || typeof CODING_SECRET != "string") throw new Error("set valid value to JWT_SECRET");
if (MAIL_USERNAME == "" || typeof MAIL_USERNAME != "string") throw new Error("set valid value to MAIL_USERNAME");
if (MAIL_PASSWORD == "" || typeof MAIL_PASSWORD != "string") throw new Error("set valid value to MAIL_PASSWORD");
if (MAIL_HOST == "" || typeof MAIL_HOST != "string") throw new Error("set valid value to MAIL_HOST");

View file

@ -0,0 +1,86 @@
import { createCipheriv, createDecipheriv, scryptSync, randomBytes } from "crypto";
import { ValueTransformer } from "typeorm";
export abstract class CodingHelper {
private static readonly algorithm = "aes-256-gcm";
private static readonly ivLength = 16;
private static readonly authTagLength = 16;
static entityBaseCoding(key: string = "", fallback: string = ""): ValueTransformer {
return {
from(val: string | null | undefined): string {
if (!val) return fallback;
try {
return CodingHelper.decrypt(key, val) || fallback;
} catch (error) {
console.error("Decryption error:", error);
return fallback;
}
},
to(val: string | null | undefined): string {
const valueToEncrypt = val || fallback;
if (valueToEncrypt === "") return "";
try {
return CodingHelper.encrypt(key, valueToEncrypt);
} catch (error) {
console.error("Encryption error:", error);
return "";
}
},
};
}
public static encrypt(phrase: string, content: string): string {
if (!content) return "";
// Generiere zufälligen IV für jede Verschlüsselung (sicherer als statischer IV)
const iv = randomBytes(this.ivLength);
const key = scryptSync(phrase, "salt", 32);
const cipher = createCipheriv(this.algorithm, Uint8Array.from(key), Uint8Array.from(iv));
// Verschlüssele den Inhalt
let encrypted = cipher.update(content, "utf8", "hex");
encrypted += cipher.final("hex");
// Speichere das Auth-Tag für GCM (wichtig für die Entschlüsselung)
const authTag = cipher.getAuthTag();
// Gib das Format: iv:verschlüsselter_text:authTag zurück
return Buffer.concat([
Uint8Array.from(iv),
Uint8Array.from(Buffer.from(encrypted, "hex")),
Uint8Array.from(authTag),
]).toString("base64");
}
public static decrypt(phrase: string, content: string): string {
if (!content) return "";
try {
// Dekodiere den Base64-String
const buffer = Buffer.from(content, "base64");
// Extrahiere IV, verschlüsselten Text und Auth-Tag
const iv = buffer.subarray(0, this.ivLength);
const authTag = buffer.subarray(buffer.length - this.authTagLength);
const encryptedText = buffer.subarray(this.ivLength, buffer.length - this.authTagLength).toString("hex");
const key = scryptSync(phrase, "salt", 32);
// Erstelle Decipher und setze Auth-Tag
const decipher = createDecipheriv(this.algorithm, Uint8Array.from(key), Uint8Array.from(iv));
decipher.setAuthTag(Uint8Array.from(authTag));
// Entschlüssele den Text
let decrypted = decipher.update(encryptedText, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
} catch (error) {
console.error("Decryption failed:", error);
return "";
}
}
}

View file

@ -11,11 +11,12 @@ export const mission_table = new Table({
{ name: "mission_start", ...getTypeByORM("datetime", true, 6), default: getDefaultByORM("null") },
{ name: "mission_end", ...getTypeByORM("datetime", true, 6), default: getDefaultByORM("null") },
{ name: "keyword", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
{ name: "location", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
{ name: "location", ...getTypeByORM("text"), default: getDefaultByORM("string") },
{ name: "city", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
{ name: "others", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
{ name: "rescued", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
{ name: "recovered", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
{ name: "description", ...getTypeByORM("text"), default: getDefaultByORM("string", "[]") },
{ name: "description", ...getTypeByORM("text"), default: getDefaultByORM("string") },
{ name: "last_update", ...getTypeByORM("bigint"), default: getDefaultByORM("number", 0) },
{ name: "createdAt", ...getTypeByORM("datetime", false, 6), default: getDefaultByORM("currentTimestamp") },
],
@ -117,11 +118,11 @@ export const mission_contact_table = new Table({
columns: [
{ name: "missionId", ...getTypeByORM("uuid"), isPrimary: true },
{ name: "contactId", ...getTypeByORM("uuid"), isPrimary: true },
{ name: "firstname", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
{ name: "lastname", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
{ name: "phone", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
{ name: "address", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
{ name: "note", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
{ name: "firstname", ...getTypeByORM("text"), default: getDefaultByORM("string") },
{ name: "lastname", ...getTypeByORM("text"), default: getDefaultByORM("string") },
{ name: "phone", ...getTypeByORM("text"), default: getDefaultByORM("string") },
{ name: "address", ...getTypeByORM("text"), default: getDefaultByORM("string") },
{ name: "note", ...getTypeByORM("text"), default: getDefaultByORM("string") },
],
foreignKeys: [
new TableForeignKey({