Phase 1: tkuser 협력업체 CRUD 이관 (읽기전용 → 전체 CRUD) Phase 2: tkpurchase 개편 — 일용공 신청/확정, 작업일정, 업무현황, 계정관리, 협력업체 포털 Phase 3: tksafety 신규 시스템 — 방문관리 + 안전교육 신고 Phase 4: SSO 인증 보강 (partner_company_id JWT, 만료일 체크), 권한 테이블 기반 접근 제어 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
102 lines
3.3 KiB
JavaScript
102 lines
3.3 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const mysql = require('mysql2/promise');
|
|
|
|
const JWT_SECRET = process.env.SSO_JWT_SECRET;
|
|
|
|
let pool;
|
|
function getPool() {
|
|
if (!pool) {
|
|
pool = mysql.createPool({
|
|
host: process.env.DB_HOST || 'mariadb',
|
|
port: parseInt(process.env.DB_PORT) || 3306,
|
|
user: process.env.DB_USER || 'hyungi_user',
|
|
password: process.env.DB_PASSWORD,
|
|
database: process.env.DB_NAME || 'hyungi',
|
|
waitForConnections: true,
|
|
connectionLimit: 5,
|
|
queueLimit: 0
|
|
});
|
|
}
|
|
return pool;
|
|
}
|
|
|
|
function extractToken(req) {
|
|
const authHeader = req.headers['authorization'];
|
|
if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
return authHeader.split(' ')[1];
|
|
}
|
|
if (req.cookies && req.cookies.sso_token) {
|
|
return req.cookies.sso_token;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function requireAuth(req, res, next) {
|
|
const token = extractToken(req);
|
|
if (!token) {
|
|
return res.status(401).json({ success: false, error: '인증이 필요합니다' });
|
|
}
|
|
try {
|
|
const decoded = jwt.verify(token, JWT_SECRET);
|
|
req.user = decoded;
|
|
next();
|
|
} catch {
|
|
return res.status(401).json({ success: false, error: '유효하지 않은 토큰입니다' });
|
|
}
|
|
}
|
|
|
|
function requireAdmin(req, res, next) {
|
|
const token = extractToken(req);
|
|
if (!token) {
|
|
return res.status(401).json({ success: false, error: '인증이 필요합니다' });
|
|
}
|
|
try {
|
|
const decoded = jwt.verify(token, JWT_SECRET);
|
|
if (!['admin', 'system'].includes((decoded.role || '').toLowerCase())) {
|
|
return res.status(403).json({ success: false, error: '관리자 권한이 필요합니다' });
|
|
}
|
|
req.user = decoded;
|
|
next();
|
|
} catch {
|
|
return res.status(401).json({ success: false, error: '유효하지 않은 토큰입니다' });
|
|
}
|
|
}
|
|
|
|
function requirePage(pageName) {
|
|
return async (req, res, next) => {
|
|
const userId = req.user.user_id || req.user.id;
|
|
const role = (req.user.role || '').toLowerCase();
|
|
if (role === 'admin' || role === 'system') return next();
|
|
|
|
try {
|
|
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 rows[0].can_access ? next() : res.status(403).json({ success: false, error: '접근 권한이 없습니다' });
|
|
}
|
|
// 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 deptRows[0].can_access ? next() : res.status(403).json({ success: false, error: '접근 권한이 없습니다' });
|
|
}
|
|
}
|
|
// 3. 기본 거부
|
|
return res.status(403).json({ success: false, error: '접근 권한이 없습니다' });
|
|
} catch (err) {
|
|
console.error('Permission check error:', err);
|
|
return res.status(500).json({ success: false, error: '권한 확인 실패' });
|
|
}
|
|
};
|
|
}
|
|
|
|
module.exports = { getPool, extractToken, requireAuth, requireAdmin, requirePage };
|