feat: 권한 탭 분리 + 부서 인원 표시 + 다수 시스템 개선
- tkuser: 권한 관리를 별도 탭으로 분리, 부서 클릭 시 소속 인원 목록 표시 - system1: 모바일 UI 개선, nginx 권한 보정, 신고 카테고리 타입 마이그레이션 - system2: 신고 상세/보고서 개선, 내 보고서 페이지 추가 - system3: 이슈 뷰/수신함/관리함 개선 - gateway: 포털 라우팅 수정 - user-management API: 부서별 권한 벌크 설정 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -149,11 +149,77 @@ async function deletePermission(req, res, next) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/permissions/departments/:deptId/permissions - 부서 권한 조회
|
||||
*/
|
||||
async function getDepartmentPermissions(req, res, next) {
|
||||
try {
|
||||
const deptId = parseInt(req.params.deptId);
|
||||
const permissions = await permissionModel.getDepartmentPermissions(deptId);
|
||||
res.json({ success: true, data: permissions });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/permissions/departments/:deptId/bulk-set - 부서 권한 일괄 설정
|
||||
*/
|
||||
async function bulkSetDepartmentPermissions(req, res, next) {
|
||||
try {
|
||||
const deptId = parseInt(req.params.deptId);
|
||||
const { permissions } = req.body;
|
||||
const grantedById = req.user.user_id || req.user.id;
|
||||
|
||||
const result = await permissionModel.bulkSetDepartmentPermissions({
|
||||
department_id: deptId,
|
||||
permissions,
|
||||
granted_by_id: grantedById
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `${result.updated_count}개의 부서 권한이 설정되었습니다`,
|
||||
updated_count: result.updated_count
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/permissions/users/:userId/effective-permissions - 출처 포함 권한 조회
|
||||
*/
|
||||
async function getUserPermissionsWithSource(req, res, next) {
|
||||
try {
|
||||
const userId = parseInt(req.params.userId);
|
||||
const requesterId = req.user.user_id || req.user.id;
|
||||
|
||||
// 관리자이거나 본인만 조회 가능
|
||||
if (req.user.role !== 'admin' && requesterId !== userId) {
|
||||
return res.status(403).json({ success: false, error: '권한이 없습니다' });
|
||||
}
|
||||
|
||||
const user = await userModel.findById(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({ success: false, error: '사용자를 찾을 수 없습니다' });
|
||||
}
|
||||
|
||||
const result = await permissionModel.getUserPermissionsWithSource(userId);
|
||||
res.json({ success: true, ...result });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getUserPermissions,
|
||||
grantPermission,
|
||||
bulkGrant,
|
||||
checkAccess,
|
||||
getAvailablePages,
|
||||
deletePermission
|
||||
deletePermission,
|
||||
getDepartmentPermissions,
|
||||
bulkSetDepartmentPermissions,
|
||||
getUserPermissionsWithSource
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ async function getUsers(req, res, next) {
|
||||
*/
|
||||
async function createUser(req, res, next) {
|
||||
try {
|
||||
const { username, password, name, full_name, department, role } = req.body;
|
||||
const { username, password, name, full_name, department, department_id, role } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ success: false, error: '사용자명과 비밀번호는 필수입니다' });
|
||||
@@ -39,6 +39,7 @@ async function createUser(req, res, next) {
|
||||
password,
|
||||
name: name || full_name,
|
||||
department,
|
||||
department_id: department_id || null,
|
||||
role
|
||||
});
|
||||
res.status(201).json({ success: true, data: user });
|
||||
|
||||
@@ -104,20 +104,36 @@ async function bulkGrant({ user_id, permissions, granted_by_id }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 접근 권한 확인
|
||||
* 접근 권한 확인 (우선순위: 개인 > 부서 > 기본값)
|
||||
*/
|
||||
async function checkAccess(userId, pageName) {
|
||||
const db = getPool();
|
||||
|
||||
// 1. 명시적 개인 권한
|
||||
const [rows] = await db.query(
|
||||
'SELECT can_access FROM user_page_permissions WHERE user_id = ? AND page_name = ?',
|
||||
[userId, pageName]
|
||||
);
|
||||
|
||||
if (rows.length > 0) {
|
||||
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' };
|
||||
@@ -125,6 +141,110 @@ async function checkAccess(userId, pageName) {
|
||||
return { can_access: pageConfig.default_access, reason: 'default_permission' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서별 페이지 권한 조회
|
||||
*/
|
||||
async function getDepartmentPermissions(departmentId) {
|
||||
const db = getPool();
|
||||
const [rows] = await db.query(
|
||||
'SELECT * FROM department_page_permissions WHERE department_id = ? ORDER BY page_name',
|
||||
[departmentId]
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서 권한 단건 UPSERT
|
||||
*/
|
||||
async function setDepartmentPermission({ department_id, page_name, can_access, granted_by_id }) {
|
||||
const db = getPool();
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO department_page_permissions (department_id, page_name, can_access, granted_by_id)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE can_access = VALUES(can_access), granted_by_id = VALUES(granted_by_id), granted_at = CURRENT_TIMESTAMP`,
|
||||
[department_id, page_name, can_access, granted_by_id]
|
||||
);
|
||||
return { id: result.insertId, department_id, page_name, can_access };
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서 권한 일괄 설정
|
||||
*/
|
||||
async function bulkSetDepartmentPermissions({ department_id, permissions, granted_by_id }) {
|
||||
const db = getPool();
|
||||
let count = 0;
|
||||
|
||||
for (const perm of permissions) {
|
||||
if (!DEFAULT_PAGES[perm.page_name]) continue;
|
||||
await db.query(
|
||||
`INSERT INTO department_page_permissions (department_id, page_name, can_access, granted_by_id)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE can_access = VALUES(can_access), granted_by_id = VALUES(granted_by_id), granted_at = CURRENT_TIMESTAMP`,
|
||||
[department_id, perm.page_name, perm.can_access, granted_by_id]
|
||||
);
|
||||
count++;
|
||||
}
|
||||
|
||||
return { updated_count: count };
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서 권한 삭제 (기본값으로 복귀)
|
||||
*/
|
||||
async function deleteDepartmentPermission(departmentId, pageName) {
|
||||
const db = getPool();
|
||||
const [result] = await db.query(
|
||||
'DELETE FROM department_page_permissions WHERE department_id = ? AND page_name = ?',
|
||||
[departmentId, pageName]
|
||||
);
|
||||
return result.affectedRows > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 권한 + 출처 조회 (각 페이지별 현재 적용 권한과 출처 반환)
|
||||
*/
|
||||
async function getUserPermissionsWithSource(userId) {
|
||||
const db = getPool();
|
||||
|
||||
// 개인 권한 조회
|
||||
const [userPerms] = await db.query(
|
||||
'SELECT page_name, can_access FROM user_page_permissions WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
const userPermMap = {};
|
||||
userPerms.forEach(p => { userPermMap[p.page_name] = !!p.can_access; });
|
||||
|
||||
// 부서 권한 조회
|
||||
const [userRows] = await db.query(
|
||||
'SELECT department_id FROM sso_users WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
const deptId = userRows.length > 0 ? userRows[0].department_id : null;
|
||||
|
||||
const deptPermMap = {};
|
||||
if (deptId) {
|
||||
const [deptPerms] = await db.query(
|
||||
'SELECT page_name, can_access FROM department_page_permissions WHERE department_id = ?',
|
||||
[deptId]
|
||||
);
|
||||
deptPerms.forEach(p => { deptPermMap[p.page_name] = !!p.can_access; });
|
||||
}
|
||||
|
||||
// 모든 페이지에 대해 결과 조합
|
||||
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' };
|
||||
} else {
|
||||
result[pageName] = { can_access: config.default_access, source: 'default' };
|
||||
}
|
||||
}
|
||||
|
||||
return { permissions: result, department_id: deptId };
|
||||
}
|
||||
|
||||
/**
|
||||
* 권한 삭제 (기본값으로 되돌림)
|
||||
*/
|
||||
@@ -146,5 +266,10 @@ module.exports = {
|
||||
grantPermission,
|
||||
bulkGrant,
|
||||
checkAccess,
|
||||
deletePermission
|
||||
deletePermission,
|
||||
getDepartmentPermissions,
|
||||
setDepartmentPermission,
|
||||
bulkSetDepartmentPermissions,
|
||||
deleteDepartmentPermission,
|
||||
getUserPermissionsWithSource
|
||||
};
|
||||
|
||||
@@ -97,18 +97,18 @@ async function findById(userId) {
|
||||
async function findAll() {
|
||||
const db = getPool();
|
||||
const [rows] = await db.query(
|
||||
'SELECT user_id, username, name, department, role, system1_access, system2_access, system3_access, is_active, last_login, created_at FROM sso_users ORDER BY user_id'
|
||||
'SELECT user_id, username, name, department, department_id, role, system1_access, system2_access, system3_access, is_active, last_login, created_at FROM sso_users ORDER BY user_id'
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function create({ username, password, name, department, role }) {
|
||||
async function create({ username, password, name, department, department_id, role }) {
|
||||
const db = getPool();
|
||||
const password_hash = await hashPassword(password);
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO sso_users (username, password_hash, name, department, role)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
[username, password_hash, name || null, department || null, role || 'user']
|
||||
`INSERT INTO sso_users (username, password_hash, name, department, department_id, role)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[username, password_hash, name || null, department || null, department_id || null, role || 'user']
|
||||
);
|
||||
return findById(result.insertId);
|
||||
}
|
||||
@@ -120,6 +120,7 @@ async function update(userId, data) {
|
||||
|
||||
if (data.name !== undefined) { fields.push('name = ?'); values.push(data.name); }
|
||||
if (data.department !== undefined) { fields.push('department = ?'); values.push(data.department); }
|
||||
if (data.department_id !== undefined) { fields.push('department_id = ?'); values.push(data.department_id); }
|
||||
if (data.role !== undefined) { fields.push('role = ?'); values.push(data.role); }
|
||||
if (data.system1_access !== undefined) { fields.push('system1_access = ?'); values.push(data.system1_access); }
|
||||
if (data.system2_access !== undefined) { fields.push('system2_access = ?'); values.push(data.system2_access); }
|
||||
|
||||
@@ -17,6 +17,13 @@ router.get('/check/:uid/:page', requireAuth, permissionController.checkAccess);
|
||||
// 설정 가능 페이지 목록 (auth)
|
||||
router.get('/available-pages', requireAuth, permissionController.getAvailablePages);
|
||||
|
||||
// 부서별 권한 (admin)
|
||||
router.get('/departments/:deptId/permissions', requireAdmin, permissionController.getDepartmentPermissions);
|
||||
router.post('/departments/:deptId/bulk-set', requireAdmin, permissionController.bulkSetDepartmentPermissions);
|
||||
|
||||
// 출처 포함 사용자 권한 조회 (admin or self)
|
||||
router.get('/users/:userId/effective-permissions', requireAuth, permissionController.getUserPermissionsWithSource);
|
||||
|
||||
// 권한 삭제 (admin)
|
||||
router.delete('/:id', requireAdmin, permissionController.deletePermission);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user