배치 수정 페이지 버그 수정 및 멀티테넌시 보안 강화

This commit is contained in:
dohyeons 2025-12-05 10:36:52 +09:00
parent b6a7b4a93b
commit 7c06b98f86
3 changed files with 88 additions and 27 deletions

View File

@ -438,12 +438,29 @@ export class BatchManagementController {
// 토큰 결정: authServiceName이 있으면 DB에서 조회, 없으면 apiKey 사용 // 토큰 결정: authServiceName이 있으면 DB에서 조회, 없으면 apiKey 사용
let finalApiKey = apiKey || ""; let finalApiKey = apiKey || "";
if (authServiceName) { if (authServiceName) {
// DB에서 토큰 조회 const companyCode = req.user?.companyCode;
// DB에서 토큰 조회 (멀티테넌시: company_code 필터링)
let tokenQuery: string;
let tokenParams: any[];
if (companyCode === "*") {
// 최고 관리자: 모든 회사 토큰 조회 가능
tokenQuery = `SELECT access_token FROM auth_tokens
WHERE service_name = $1
ORDER BY created_date DESC LIMIT 1`;
tokenParams = [authServiceName];
} else {
// 일반 회사: 자신의 회사 토큰만 조회
tokenQuery = `SELECT access_token FROM auth_tokens
WHERE service_name = $1 AND company_code = $2
ORDER BY created_date DESC LIMIT 1`;
tokenParams = [authServiceName, companyCode];
}
const tokenResult = await query<{ access_token: string }>( const tokenResult = await query<{ access_token: string }>(
`SELECT access_token FROM auth_tokens tokenQuery,
WHERE service_name = $1 tokenParams
ORDER BY created_date DESC LIMIT 1`,
[authServiceName]
); );
if (tokenResult.length > 0 && tokenResult[0].access_token) { if (tokenResult.length > 0 && tokenResult[0].access_token) {
finalApiKey = tokenResult[0].access_token; finalApiKey = tokenResult[0].access_token;
@ -708,13 +725,33 @@ export class BatchManagementController {
/** /**
* *
*/ */
static async getAuthServiceNames(req: Request, res: Response) { static async getAuthServiceNames(req: AuthenticatedRequest, res: Response) {
try { try {
const companyCode = req.user?.companyCode;
// 멀티테넌시: company_code 필터링
let queryText: string;
let queryParams: any[] = [];
if (companyCode === "*") {
// 최고 관리자: 모든 서비스 조회
queryText = `SELECT DISTINCT service_name
FROM auth_tokens
WHERE service_name IS NOT NULL
ORDER BY service_name`;
} else {
// 일반 회사: 자신의 회사 서비스만 조회
queryText = `SELECT DISTINCT service_name
FROM auth_tokens
WHERE service_name IS NOT NULL
AND company_code = $1
ORDER BY service_name`;
queryParams = [companyCode];
}
const result = await query<{ service_name: string }>( const result = await query<{ service_name: string }>(
`SELECT DISTINCT service_name queryText,
FROM auth_tokens queryParams
WHERE service_name IS NOT NULL
ORDER BY service_name`
); );
const serviceNames = result.map((row) => row.service_name); const serviceNames = result.map((row) => row.service_name);

View File

@ -260,14 +260,29 @@ export class BatchSchedulerService {
"./batchExternalDbService" "./batchExternalDbService"
); );
// auth_service_name이 설정된 경우 auth_tokens에서 토큰 조회 // auth_service_name이 설정된 경우 auth_tokens에서 토큰 조회 (멀티테넌시 적용)
let apiKey = firstMapping.from_api_key || ""; let apiKey = firstMapping.from_api_key || "";
if (config.auth_service_name) { if (config.auth_service_name) {
let tokenQuery: string;
let tokenParams: any[];
if (config.company_code === "*") {
// 최고 관리자 배치: 모든 회사 토큰 조회 가능
tokenQuery = `SELECT access_token FROM auth_tokens
WHERE service_name = $1
ORDER BY created_date DESC LIMIT 1`;
tokenParams = [config.auth_service_name];
} else {
// 일반 회사 배치: 자신의 회사 토큰만 조회
tokenQuery = `SELECT access_token FROM auth_tokens
WHERE service_name = $1 AND company_code = $2
ORDER BY created_date DESC LIMIT 1`;
tokenParams = [config.auth_service_name, config.company_code];
}
const tokenResult = await query<{ access_token: string }>( const tokenResult = await query<{ access_token: string }>(
`SELECT access_token FROM auth_tokens tokenQuery,
WHERE service_name = $1 tokenParams
ORDER BY created_date DESC LIMIT 1`,
[config.auth_service_name]
); );
if (tokenResult.length > 0 && tokenResult[0].access_token) { if (tokenResult.length > 0 && tokenResult[0].access_token) {
apiKey = tokenResult[0].access_token; apiKey = tokenResult[0].access_token;

View File

@ -796,20 +796,29 @@ export class BatchService {
const updateColumns = columns.filter( const updateColumns = columns.filter(
(col) => col !== conflictKey (col) => col !== conflictKey
); );
const updateSet = updateColumns
.map((col) => `${col} = EXCLUDED.${col}`)
.join(", ");
// updated_date 컬럼이 있으면 현재 시간으로 업데이트 // 업데이트할 컬럼이 없으면 DO NOTHING 사용
const hasUpdatedDate = columns.includes("updated_date"); if (updateColumns.length === 0) {
const finalUpdateSet = hasUpdatedDate queryStr = `INSERT INTO ${tableName} (${columns.join(", ")})
? `${updateSet}, updated_date = NOW()` VALUES (${placeholders})
: updateSet; ON CONFLICT (${conflictKey})
DO NOTHING`;
} else {
const updateSet = updateColumns
.map((col) => `${col} = EXCLUDED.${col}`)
.join(", ");
queryStr = `INSERT INTO ${tableName} (${columns.join(", ")}) // updated_date 컬럼이 있으면 현재 시간으로 업데이트
VALUES (${placeholders}) const hasUpdatedDate = columns.includes("updated_date");
ON CONFLICT (${conflictKey}) const finalUpdateSet = hasUpdatedDate
DO UPDATE SET ${finalUpdateSet}`; ? `${updateSet}, updated_date = NOW()`
: updateSet;
queryStr = `INSERT INTO ${tableName} (${columns.join(", ")})
VALUES (${placeholders})
ON CONFLICT (${conflictKey})
DO UPDATE SET ${finalUpdateSet}`;
}
} else { } else {
// INSERT 모드: 기존 방식 // INSERT 모드: 기존 방식
queryStr = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${placeholders})`; queryStr = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${placeholders})`;