feat(tkuser): 부서 마스터 + 개인 추가 부여 권한 시스템 구현
부서 권한을 바닥(마스터)으로 설정하고 개인은 추가 부여만 가능하도록 변경. 부서 허용 항목은 개인 페이지에서 잠금(해제 불가) 표시되며, 부서 이동 시 기존 개인 권한이 자동 초기화됨. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -164,6 +164,7 @@ async function getDepartmentPermissions(req, res, next) {
|
||||
|
||||
/**
|
||||
* POST /api/permissions/departments/:deptId/bulk-set - 부서 권한 일괄 설정
|
||||
* 저장 후 소속 사용자의 중복 개인 레코드 정리
|
||||
*/
|
||||
async function bulkSetDepartmentPermissions(req, res, next) {
|
||||
try {
|
||||
@@ -177,10 +178,34 @@ async function bulkSetDepartmentPermissions(req, res, next) {
|
||||
granted_by_id: grantedById
|
||||
});
|
||||
|
||||
// 소속 사용자의 중복 개인 레코드 정리
|
||||
const { getPool } = require('../models/userModel');
|
||||
const db = getPool();
|
||||
const [deptUsers] = await db.query(
|
||||
'SELECT user_id FROM sso_users WHERE department_id = ?', [deptId]
|
||||
);
|
||||
|
||||
// 부서가 허용한 페이지 목록
|
||||
const grantedPages = (permissions || [])
|
||||
.filter(p => p.can_access)
|
||||
.map(p => p.page_name);
|
||||
|
||||
let syncedUsers = 0;
|
||||
if (grantedPages.length > 0 && deptUsers.length > 0) {
|
||||
for (const u of deptUsers) {
|
||||
const [delResult] = await db.query(
|
||||
`DELETE FROM user_page_permissions WHERE user_id = ? AND page_name IN (${grantedPages.map(() => '?').join(',')})`,
|
||||
[u.user_id, ...grantedPages]
|
||||
);
|
||||
if (delResult.affectedRows > 0) syncedUsers++;
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `${result.updated_count}개의 부서 권한이 설정되었습니다`,
|
||||
updated_count: result.updated_count
|
||||
message: `${result.updated_count}개 부서 권한 설정 (${deptUsers.length}명 적용)`,
|
||||
updated_count: result.updated_count,
|
||||
synced_users: deptUsers.length
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
const userModel = require('../models/userModel');
|
||||
const permissionModel = require('../models/permissionModel');
|
||||
|
||||
/**
|
||||
* GET /api/users - 전체 사용자 목록
|
||||
@@ -62,6 +63,14 @@ async function updateUser(req, res, next) {
|
||||
delete data.full_name;
|
||||
}
|
||||
|
||||
// 부서 변경 감지 → 개인 권한 초기화
|
||||
if (data.department_id !== undefined) {
|
||||
const existingUser = await userModel.findById(userId);
|
||||
if (existingUser && existingUser.department_id !== data.department_id) {
|
||||
await permissionModel.clearUserPermissionsForDepartmentChange(userId);
|
||||
}
|
||||
}
|
||||
|
||||
const user = await userModel.update(userId, data);
|
||||
if (!user) {
|
||||
return res.status(404).json({ success: false, error: '사용자를 찾을 수 없습니다' });
|
||||
|
||||
@@ -105,14 +105,39 @@ async function grantPermission({ user_id, page_name, can_access, granted_by_id,
|
||||
}
|
||||
|
||||
/**
|
||||
* 일괄 권한 부여
|
||||
* 일괄 권한 부여 (부서 허용 페이지는 스킵 + 기존 중복 레코드 정리)
|
||||
*/
|
||||
async function bulkGrant({ user_id, permissions, granted_by_id }) {
|
||||
const db = getPool();
|
||||
let count = 0;
|
||||
|
||||
// 부서 권한 조회
|
||||
const [userRows] = await db.query(
|
||||
'SELECT department_id FROM sso_users WHERE user_id = ?', [user_id]
|
||||
);
|
||||
const deptId = userRows.length > 0 ? userRows[0].department_id : null;
|
||||
|
||||
const deptGranted = {};
|
||||
if (deptId) {
|
||||
const [deptPerms] = await db.query(
|
||||
'SELECT page_name, can_access FROM department_page_permissions WHERE department_id = ?',
|
||||
[deptId]
|
||||
);
|
||||
deptPerms.forEach(p => { if (p.can_access) deptGranted[p.page_name] = true; });
|
||||
}
|
||||
|
||||
for (const perm of permissions) {
|
||||
if (!DEFAULT_PAGES[perm.page_name]) continue;
|
||||
|
||||
// 부서가 허용한 페이지는 개인 레코드 삭제 (스킵)
|
||||
if (deptGranted[perm.page_name]) {
|
||||
await db.query(
|
||||
'DELETE FROM user_page_permissions WHERE user_id = ? AND page_name = ?',
|
||||
[user_id, perm.page_name]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
await db.query(
|
||||
`INSERT INTO user_page_permissions (user_id, page_name, can_access, granted_by_id)
|
||||
VALUES (?, ?, ?, ?)
|
||||
@@ -126,12 +151,27 @@ async function bulkGrant({ user_id, permissions, granted_by_id }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 접근 권한 확인 (우선순위: 개인 > 부서 > 기본값)
|
||||
* 접근 권한 확인 (우선순위: 부서 OR 개인 OR 기본값)
|
||||
* 부서가 허용하면 무조건 허용 (개인이 해제 불가)
|
||||
*/
|
||||
async function checkAccess(userId, pageName) {
|
||||
const db = getPool();
|
||||
|
||||
// 1. 명시적 개인 권한
|
||||
// 1. 부서 권한 (마스터) — 부서가 허용하면 무조건 허용
|
||||
const [userRows] = await db.query(
|
||||
'SELECT department_id FROM sso_users WHERE user_id = ?', [userId]
|
||||
);
|
||||
if (userRows.length > 0 && userRows[0].department_id) {
|
||||
const [deptRows] = await db.query(
|
||||
'SELECT can_access FROM department_page_permissions WHERE department_id = ? AND page_name = ?',
|
||||
[userRows[0].department_id, pageName]
|
||||
);
|
||||
if (deptRows.length > 0 && deptRows[0].can_access) {
|
||||
return { can_access: true, reason: 'department_permission' };
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 개인 권한 (추가 부여)
|
||||
const [rows] = await db.query(
|
||||
'SELECT can_access FROM user_page_permissions WHERE user_id = ? AND page_name = ?',
|
||||
[userId, pageName]
|
||||
@@ -140,26 +180,9 @@ async function checkAccess(userId, pageName) {
|
||||
return { can_access: rows[0].can_access, reason: 'explicit_permission' };
|
||||
}
|
||||
|
||||
// 2. 부서 권한
|
||||
const [userRows] = await db.query(
|
||||
'SELECT department_id FROM sso_users WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
if (userRows.length > 0 && userRows[0].department_id) {
|
||||
const [deptRows] = await db.query(
|
||||
'SELECT can_access FROM department_page_permissions WHERE department_id = ? AND page_name = ?',
|
||||
[userRows[0].department_id, pageName]
|
||||
);
|
||||
if (deptRows.length > 0) {
|
||||
return { can_access: deptRows[0].can_access, reason: 'department_permission' };
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 기본 권한
|
||||
const pageConfig = DEFAULT_PAGES[pageName];
|
||||
if (!pageConfig) {
|
||||
return { can_access: false, reason: 'invalid_page' };
|
||||
}
|
||||
if (!pageConfig) return { can_access: false, reason: 'invalid_page' };
|
||||
return { can_access: pageConfig.default_access, reason: 'default_permission' };
|
||||
}
|
||||
|
||||
@@ -252,21 +275,36 @@ async function getUserPermissionsWithSource(userId) {
|
||||
deptPerms.forEach(p => { deptPermMap[p.page_name] = !!p.can_access; });
|
||||
}
|
||||
|
||||
// 모든 페이지에 대해 결과 조합
|
||||
// 모든 페이지에 대해 결과 조합 (부서 OR 개인 OR 기본)
|
||||
const result = {};
|
||||
for (const [pageName, config] of Object.entries(DEFAULT_PAGES)) {
|
||||
if (pageName in userPermMap) {
|
||||
result[pageName] = { can_access: userPermMap[pageName], source: 'explicit' };
|
||||
} else if (pageName in deptPermMap) {
|
||||
result[pageName] = { can_access: deptPermMap[pageName], source: 'department' };
|
||||
const deptGranted = deptPermMap[pageName] === true;
|
||||
|
||||
if (deptGranted) {
|
||||
// 부서가 허용 → 무조건 허용, 잠금
|
||||
result[pageName] = { can_access: true, source: 'department', dept_granted: true };
|
||||
} else if (pageName in userPermMap) {
|
||||
result[pageName] = { can_access: userPermMap[pageName], source: 'explicit', dept_granted: false };
|
||||
} else {
|
||||
result[pageName] = { can_access: config.default_access, source: 'default' };
|
||||
result[pageName] = { can_access: config.default_access, source: 'default', dept_granted: false };
|
||||
}
|
||||
}
|
||||
|
||||
return { permissions: result, department_id: deptId };
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서 이동 시 개인 권한 초기화 — user_page_permissions 전체 삭제
|
||||
*/
|
||||
async function clearUserPermissionsForDepartmentChange(userId) {
|
||||
const db = getPool();
|
||||
const [result] = await db.query(
|
||||
'DELETE FROM user_page_permissions WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
return { deleted_count: result.affectedRows };
|
||||
}
|
||||
|
||||
/**
|
||||
* 권한 삭제 (기본값으로 되돌림)
|
||||
*/
|
||||
@@ -293,5 +331,6 @@ module.exports = {
|
||||
setDepartmentPermission,
|
||||
bulkSetDepartmentPermissions,
|
||||
deleteDepartmentPermission,
|
||||
getUserPermissionsWithSource
|
||||
getUserPermissionsWithSource,
|
||||
clearUserPermissionsForDepartmentChange
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user