feat: Implement password masking and encryption in data services

- Added a new function `maskPasswordColumns` to mask password fields in data responses, ensuring sensitive information is not exposed.
- Integrated password handling in `DynamicFormService` to encrypt new passwords and maintain existing ones when empty values are provided.
- Enhanced logging for better tracking of password field updates and masking failures, improving overall security and debugging capabilities.
This commit is contained in:
DDD1542
2026-02-12 16:32:23 +09:00
parent df04afa5de
commit b1ec674fa9
3 changed files with 104 additions and 5 deletions

View File

@@ -596,10 +596,22 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
const additionalFields = componentConfig.additionalFields || [];
const mainTable = componentConfig.targetTable!;
// 수정 모드 감지: URL에 mode=edit가 있으면 수정 모드
// 수정 모드 감지 (2가지 방법으로 확인)
// 1. URL에 mode=edit 파라미터 확인
// 2. 로드된 데이터에 DB id(PK)가 존재하는지 확인
// 수정 모드에서는 항상 deleteOrphans=true (기존 레코드 교체, 복제 방지)
const urlParams = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
const isEditMode = urlParams?.get("mode") === "edit";
const urlEditMode = urlParams?.get("mode") === "edit";
const dataHasDbId = items.some(item => !!item.originalData?.id);
const isEditMode = urlEditMode || dataHasDbId;
console.log("[SelectedItemsDetailInput] 수정 모드 감지:", {
urlEditMode,
dataHasDbId,
isEditMode,
itemCount: items.length,
firstItemId: items[0]?.originalData?.id,
});
// fieldGroup별 sourceTable 분류
const groupsByTable = new Map<string, typeof groups>();
@@ -695,6 +707,16 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
// 신규 등록이고 id 없으면 → 기존 레코드 건드리지 않음
const mappingHasDbIds = mappingRecords.some((r) => !!r.id);
const shouldDeleteOrphans = isEditMode || mappingHasDbIds;
console.log(`[SelectedItemsDetailInput] ${mainTable} 저장:`, {
isEditMode,
mappingHasDbIds,
shouldDeleteOrphans,
recordCount: mappingRecords.length,
recordIds: mappingRecords.map(r => r.id || "NEW"),
parentKeys: itemParentKeys,
});
// 저장된 매핑 ID를 추적 (디테일 테이블에 mapping_id 주입용)
let savedMappingIds: string[] = [];
try {
@@ -782,6 +804,16 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
const priceHasDbIds = priceRecords.some((r) => !!r.id);
const shouldDeleteDetailOrphans = isEditMode || priceHasDbIds;
console.log(`[SelectedItemsDetailInput] ${detailTable} 저장:`, {
isEditMode,
priceHasDbIds,
shouldDeleteDetailOrphans,
recordCount: priceRecords.length,
recordIds: priceRecords.map(r => r.id || "NEW"),
parentKeys: itemParentKeys,
});
try {
const detailResult = await dataApi.upsertGroupedRecords(
detailTable,