diff --git a/backend-node/src/services/amaranthApprovalClient.ts b/backend-node/src/services/amaranthApprovalClient.ts index 7628a3fb..750a6364 100644 --- a/backend-node/src/services/amaranthApprovalClient.ts +++ b/backend-node/src/services/amaranthApprovalClient.ts @@ -22,6 +22,33 @@ import { logger } from "../utils/logger"; const CONNECTION_NAME = "Amaranth - 결재"; +// external_rest_api_connections.auth_config 의 accessToken/hashKey 는 +// ExternalRestApiConnectionService 에서 AES-256-GCM 으로 암호화되어 저장됨 +// (`iv:authTag:ciphertext` 3-part hex). 같은 키/알고리즘으로 복호화한다. +const ENC_ALGORITHM = "aes-256-gcm"; +const ENC_KEY = crypto.scryptSync( + process.env.DB_PASSWORD_SECRET || "default-secret-key-change-in-production", + "salt", + 32, +); + +function tryDecrypt(value: string): string { + if (!value || typeof value !== "string") return value; + const parts = value.split(":"); + if (parts.length !== 3) return value; // 평문 그대로 (마이그레이션 호환) + try { + const iv = Buffer.from(parts[0], "hex"); + const authTag = Buffer.from(parts[1], "hex"); + const decipher = crypto.createDecipheriv(ENC_ALGORITHM, ENC_KEY, iv) as crypto.DecipherGCM; + decipher.setAuthTag(authTag); + let out = decipher.update(parts[2], "hex", "utf8"); + out += decipher.final("utf8"); + return out; + } catch { + return value; // 복호화 실패 시 원본 (평문 가정) + } +} + interface AmaranthAuth { baseUrl: string; callerName: string; @@ -31,7 +58,7 @@ interface AmaranthAuth { aesKey: string; } -/** DB 의 외부 커넥션 'Amaranth - 결재' 에서 인증 정보를 로드 */ +/** DB 의 외부 커넥션 'Amaranth - 결재' 에서 인증 정보를 로드 (민감 필드 자동 복호화) */ export async function loadApprovalConnection(): Promise { const row = await queryOne( `SELECT base_url, auth_config FROM external_rest_api_connections @@ -48,10 +75,10 @@ export async function loadApprovalConnection(): Promise { } return { baseUrl: String(row.base_url || "").replace(/\/+$/, ""), - callerName: cfg.callerName, - accessToken: cfg.accessToken, - hashKey: cfg.hashKey, - groupSeq: cfg.groupSeq, + callerName: cfg.callerName, // 평문 (암호화 대상 아님) + accessToken: tryDecrypt(cfg.accessToken), + hashKey: tryDecrypt(cfg.hashKey), + groupSeq: cfg.groupSeq, // 평문 aesKey: cfg.aesKey || "8441e27489d402cd", }; }