Files
tk-factory-services/tkpurchase/api/middleware/auth.js
Hyungi Ahn 281f5d35d1 feat: tkpurchase 시스템 Phase 1 - 협력업체 마스터 + 당일 방문 관리
신규 독립 시스템 tkpurchase (구매/방문 관리) 구축:
- 협력업체 CRUD + 소속 작업자 관리 (마스터 데이터 소유)
- 당일 방문 등록/체크인/체크아웃 + 일괄 마감
- 업체 자동완성, CSV 내보내기, 집계 통계
- 자정 자동 체크아웃 (node-cron)
- tkuser 협력업체 읽기 전용 탭 + 권한 그리드(tkpurchase-perms) 추가
- docker-compose에 tkpurchase-api/web 서비스 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:45:37 +09:00

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 = { extractToken, requireAuth, requireAdmin, requirePage };