store data encoded
This commit is contained in:
parent
0220f468ac
commit
297f1d8bda
8 changed files with 124 additions and 14 deletions
|
@ -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
8
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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");
|
||||
|
|
86
src/helpers/codingHelper.ts
Normal file
86
src/helpers/codingHelper.ts
Normal 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 "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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({
|
||||
|
|
Loading…
Add table
Reference in a new issue