해당 서비스 도커화 성공, 룰 추가, 로그인 오류 수정, 소문자 룰 어느정도 해결
This commit is contained in:
@@ -69,7 +69,7 @@ exports.register = async (req, res) => {
|
||||
|
||||
// 중복 아이디 확인
|
||||
const [existing] = await db.query(
|
||||
'SELECT user_id FROM Users WHERE username = ?',
|
||||
'SELECT user_id FROM users WHERE username = ?',
|
||||
[username]
|
||||
);
|
||||
|
||||
@@ -95,7 +95,7 @@ exports.register = async (req, res) => {
|
||||
|
||||
// 사용자 등록
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO Users (username, password, name, role, access_level, worker_id)
|
||||
`INSERT INTO users (username, password, name, role, access_level, worker_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[username, hashedPassword, name, role, access_level, worker_id]
|
||||
);
|
||||
@@ -126,7 +126,7 @@ exports.deleteUser = async (req, res) => {
|
||||
|
||||
// 사용자 존재 확인
|
||||
const [user] = await db.query(
|
||||
'SELECT user_id FROM Users WHERE user_id = ?',
|
||||
'SELECT user_id FROM users WHERE user_id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
@@ -138,7 +138,7 @@ exports.deleteUser = async (req, res) => {
|
||||
}
|
||||
|
||||
// 사용자 삭제
|
||||
await db.query('DELETE FROM Users WHERE user_id = ?', [id]);
|
||||
await db.query('DELETE FROM users WHERE user_id = ?', [id]);
|
||||
|
||||
console.log('[사용자 삭제 성공] ID:', id);
|
||||
|
||||
@@ -165,7 +165,7 @@ exports.getAllUsers = async (req, res) => {
|
||||
// 비밀번호 제외하고 조회
|
||||
const [rows] = await db.query(
|
||||
`SELECT user_id, username, name, role, access_level, worker_id, created_at
|
||||
FROM Users
|
||||
FROM users
|
||||
ORDER BY created_at DESC`
|
||||
);
|
||||
|
||||
|
||||
@@ -94,8 +94,7 @@ services:
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
external: true
|
||||
name: 7a5a13668b77b18bc1efaf1811d09560aa3be0e722d782e8460cb74f37328d81 # 기존 볼륨명으로 연결
|
||||
driver: local
|
||||
# redis_data: # Redis 사용 시 주석 해제
|
||||
|
||||
networks:
|
||||
|
||||
@@ -6,15 +6,7 @@ const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const app = express();
|
||||
|
||||
// ✅ Health check (맨 처음에 등록 - 모든 미들웨어보다 우선)
|
||||
app.get('/api/health', (req, res) => {
|
||||
console.log('🟢 Health check 호출됨!');
|
||||
res.status(200).json({
|
||||
status: 'healthy',
|
||||
service: 'Hyungi API',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
// 헬스체크와 개발용 엔드포인트는 CORS 이후에 등록
|
||||
|
||||
// ✅ 보안 헤더 설정 (Helmet)
|
||||
app.use(helmet({
|
||||
@@ -39,12 +31,77 @@ app.use(helmet({
|
||||
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
|
||||
//개발용
|
||||
//개발용 CORS 설정 (수정됨)
|
||||
app.use(cors({
|
||||
origin: true, // 모든 origin 허용 (개발용)
|
||||
credentials: true
|
||||
origin: function (origin, callback) {
|
||||
// 개발 환경에서는 모든 origin 허용
|
||||
console.log('🌐 CORS Origin 요청:', origin);
|
||||
|
||||
const allowedOrigins = [
|
||||
'http://localhost:20000', // 웹 UI
|
||||
'http://localhost:3005', // API 서버
|
||||
'http://localhost:3000', // 개발 포트
|
||||
'http://127.0.0.1:20000', // 로컬호스트 대체
|
||||
];
|
||||
|
||||
// origin이 없는 경우 (직접 접근) 허용
|
||||
if (!origin) {
|
||||
console.log('✅ Origin 없음 - 허용');
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
// 허용된 origin인지 확인
|
||||
if (allowedOrigins.includes(origin)) {
|
||||
console.log('✅ 허용된 Origin:', origin);
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
// 개발 환경에서는 모든 localhost 허용
|
||||
if (origin.includes('localhost') || origin.includes('127.0.0.1')) {
|
||||
console.log('✅ 로컬호스트 허용:', origin);
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
console.log('❌ 차단된 Origin:', origin);
|
||||
callback(null, false);
|
||||
},
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
|
||||
}));
|
||||
|
||||
// ✅ Health check (CORS 이후에 등록)
|
||||
app.get('/api/health', (req, res) => {
|
||||
console.log('🟢 Health check 호출됨!');
|
||||
res.status(200).json({
|
||||
status: 'healthy',
|
||||
service: 'Hyungi API',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ 개발용 Ping 엔드포인트
|
||||
app.get('/api/ping', (req, res) => {
|
||||
console.log('🏓 Ping 요청 받음!');
|
||||
res.status(200).json({
|
||||
message: 'pong',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ 서버 상태 엔드포인트
|
||||
app.get('/api/status', (req, res) => {
|
||||
console.log('📊 Status 요청 받음!');
|
||||
res.status(200).json({
|
||||
status: 'running',
|
||||
service: 'Hyungi API',
|
||||
version: '2.1.0',
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
uptime: process.uptime(),
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ CORS 설정: 허용 origin 명시 (수정된 버전)
|
||||
//app.use(cors({
|
||||
// origin: function (origin, callback) {
|
||||
@@ -106,11 +163,11 @@ const apiLimiter = rateLimit({
|
||||
legacyHeaders: false,
|
||||
});
|
||||
|
||||
// 로그인 API 속도 제한 (더 엄격하게)
|
||||
// 로그인 API 속도 제한 (개발 환경에서 완화됨)
|
||||
const loginLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15분
|
||||
max: process.env.LOGIN_RATE_LIMIT_MAX_REQUESTS || 5,
|
||||
message: '너무 많은 로그인 시도입니다. 15분 후에 다시 시도하세요.',
|
||||
windowMs: 5 * 60 * 1000, // 5분으로 단축
|
||||
max: process.env.LOGIN_RATE_LIMIT_MAX_REQUESTS || 20, // 5 -> 20으로 증가
|
||||
message: '너무 많은 로그인 시도입니다. 5분 후에 다시 시도하세요.',
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
skipSuccessfulRequests: true, // 성공한 요청은 카운트하지 않음
|
||||
@@ -210,7 +267,9 @@ app.use('/api/*', (req, res, next) => {
|
||||
'/api/auth/login',
|
||||
'/api/auth/refresh-token',
|
||||
'/api/auth/check-password-strength',
|
||||
'/api/health'
|
||||
'/api/health',
|
||||
'/api/ping', // 개발용 핑
|
||||
'/api/status' // 서버 상태
|
||||
];
|
||||
|
||||
// 정확한 경로 매칭 확인
|
||||
|
||||
56
api.hyungi.net/migrations/003_normalize_table_names.sql
Normal file
56
api.hyungi.net/migrations/003_normalize_table_names.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
-- migrations/003_normalize_table_names.sql
|
||||
-- 모든 테이블명을 snake_case로 변경하여 룰 준수
|
||||
|
||||
-- 기존 PascalCase 테이블들을 snake_case로 변경
|
||||
|
||||
-- 1. Users -> users
|
||||
RENAME TABLE Users TO users;
|
||||
|
||||
-- 2. CuttingPlan -> cutting_plan
|
||||
RENAME TABLE CuttingPlan TO cutting_plan;
|
||||
|
||||
-- 3. DailyIssueReports -> daily_issue_reports
|
||||
RENAME TABLE DailyIssueReports TO daily_issue_reports;
|
||||
|
||||
-- 4. EquipmentList -> equipment_list
|
||||
RENAME TABLE EquipmentList TO equipment_list;
|
||||
|
||||
-- 5. FactoryInfo -> factory_info
|
||||
RENAME TABLE FactoryInfo TO factory_info;
|
||||
|
||||
-- 6. IssueTypes -> issue_types
|
||||
RENAME TABLE IssueTypes TO issue_types;
|
||||
|
||||
-- 7. PipeSpecs -> pipe_specs
|
||||
RENAME TABLE PipeSpecs TO pipe_specs;
|
||||
|
||||
-- 8. Processes -> processes (이미 소문자이지만 통일성 위해)
|
||||
-- RENAME TABLE Processes TO processes; -- 이미 소문자면 스킵
|
||||
|
||||
-- 9. Projects -> projects
|
||||
RENAME TABLE Projects TO projects;
|
||||
|
||||
-- 10. Tasks -> tasks
|
||||
RENAME TABLE Tasks TO tasks;
|
||||
|
||||
-- 11. WorkReports -> work_reports
|
||||
RENAME TABLE WorkReports TO work_reports;
|
||||
|
||||
-- 12. Workers -> workers
|
||||
RENAME TABLE Workers TO workers;
|
||||
|
||||
-- 이미 snake_case인 테이블들은 그대로 유지:
|
||||
-- activity_logs
|
||||
-- daily_work_reports
|
||||
-- daily_worker_summary
|
||||
-- error_types
|
||||
-- login_logs
|
||||
-- password_change_logs
|
||||
-- uploaded_documents
|
||||
-- work_report_audit_log
|
||||
-- work_status_types
|
||||
-- work_types
|
||||
-- worker_groups
|
||||
|
||||
-- 변경 완료 로그
|
||||
SELECT 'Table names normalized to snake_case according to project rules' as migration_status;
|
||||
@@ -1,19 +1,16 @@
|
||||
const dbPool = require('../dbPool');
|
||||
const { getDb } = require('../dbPool');
|
||||
|
||||
// 사용자 조회
|
||||
const findByUsername = async (username) => {
|
||||
let connection;
|
||||
try {
|
||||
connection = await dbPool.getConnection();
|
||||
const [rows] = await connection.query(
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
'SELECT * FROM users WHERE username = ?', [username]
|
||||
);
|
||||
return rows[0];
|
||||
} catch (err) {
|
||||
console.error('DB 오류 - 사용자 조회 실패:', err);
|
||||
throw err;
|
||||
} finally {
|
||||
if (connection) connection.release();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,18 +19,15 @@ const findByUsername = async (username) => {
|
||||
* @param {number} userId - 사용자 ID
|
||||
*/
|
||||
const incrementFailedLoginAttempts = async (userId) => {
|
||||
let connection;
|
||||
try {
|
||||
connection = await dbPool.getConnection();
|
||||
await connection.execute(
|
||||
const db = await getDb();
|
||||
await db.execute(
|
||||
'UPDATE users SET failed_login_attempts = failed_login_attempts + 1 WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('DB 오류 - 로그인 실패 횟수 증가 실패:', err);
|
||||
throw err;
|
||||
} finally {
|
||||
if (connection) connection.release();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -42,18 +36,15 @@ const incrementFailedLoginAttempts = async (userId) => {
|
||||
* @param {number} userId - 사용자 ID
|
||||
*/
|
||||
const lockUserAccount = async (userId) => {
|
||||
let connection;
|
||||
try {
|
||||
connection = await dbPool.getConnection();
|
||||
await connection.execute(
|
||||
const db = await getDb();
|
||||
await db.execute(
|
||||
'UPDATE users SET locked_until = DATE_ADD(NOW(), INTERVAL 15 MINUTE) WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('DB 오류 - 계정 잠금 실패:', err);
|
||||
throw err;
|
||||
} finally {
|
||||
if (connection) connection.release();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -62,18 +53,15 @@ const lockUserAccount = async (userId) => {
|
||||
* @param {number} userId - 사용자 ID
|
||||
*/
|
||||
const resetLoginAttempts = async (userId) => {
|
||||
let connection;
|
||||
try {
|
||||
connection = await dbPool.getConnection();
|
||||
await connection.execute(
|
||||
const db = await getDb();
|
||||
await db.execute(
|
||||
'UPDATE users SET last_login_at = NOW(), failed_login_attempts = 0, locked_until = NULL WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('DB 오류 - 로그인 상태 초기화 실패:', err);
|
||||
throw err;
|
||||
} finally {
|
||||
if (connection) connection.release();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ router.post('/refresh-token', async (req, res) => {
|
||||
|
||||
// 사용자 정보 조회
|
||||
const [users] = await connection.execute(
|
||||
'SELECT * FROM Users WHERE user_id = ? AND is_active = TRUE',
|
||||
'SELECT * FROM users WHERE user_id = ? AND is_active = TRUE',
|
||||
[decoded.user_id]
|
||||
);
|
||||
|
||||
@@ -176,7 +176,7 @@ router.post('/change-password', verifyToken, async (req, res) => {
|
||||
|
||||
// 현재 사용자의 비밀번호 조회
|
||||
const [users] = await connection.execute(
|
||||
'SELECT password FROM Users WHERE user_id = ?',
|
||||
'SELECT password FROM users WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
@@ -283,7 +283,7 @@ router.post('/admin/change-password', verifyToken, async (req, res) => {
|
||||
|
||||
// 대상 사용자 확인
|
||||
const [users] = await connection.execute(
|
||||
'SELECT username, name FROM Users WHERE user_id = ?',
|
||||
'SELECT username, name FROM users WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
@@ -400,7 +400,7 @@ router.get('/me', verifyToken, async (req, res) => {
|
||||
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
const [rows] = await connection.execute(
|
||||
'SELECT user_id, username, name, email, access_level, worker_id, last_login_at, created_at FROM Users WHERE user_id = ?',
|
||||
'SELECT user_id, username, name, email, access_level, worker_id, last_login_at, created_at FROM users WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
@@ -466,7 +466,7 @@ router.post('/register', verifyToken, async (req, res) => {
|
||||
|
||||
// 사용자명 중복 체크
|
||||
const [existing] = await connection.execute(
|
||||
'SELECT user_id FROM Users WHERE username = ?',
|
||||
'SELECT user_id FROM users WHERE username = ?',
|
||||
[username]
|
||||
);
|
||||
|
||||
@@ -480,7 +480,7 @@ router.post('/register', verifyToken, async (req, res) => {
|
||||
// 이메일 중복 체크 (이메일이 제공된 경우)
|
||||
if (email) {
|
||||
const [existingEmail] = await connection.execute(
|
||||
'SELECT user_id FROM Users WHERE email = ?',
|
||||
'SELECT user_id FROM users WHERE email = ?',
|
||||
[email]
|
||||
);
|
||||
|
||||
@@ -561,7 +561,7 @@ router.get('/users', verifyToken, async (req, res) => {
|
||||
is_active,
|
||||
last_login_at,
|
||||
created_at
|
||||
FROM Users
|
||||
FROM users
|
||||
WHERE 1=1
|
||||
`;
|
||||
|
||||
@@ -638,7 +638,7 @@ router.put('/users/:id', verifyToken, async (req, res) => {
|
||||
|
||||
// 사용자 존재 확인
|
||||
const [existing] = await connection.execute(
|
||||
'SELECT user_id, username FROM Users WHERE user_id = ?',
|
||||
'SELECT user_id, username FROM users WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
@@ -662,7 +662,7 @@ router.put('/users/:id', verifyToken, async (req, res) => {
|
||||
// 이메일 중복 체크
|
||||
if (email) {
|
||||
const [emailCheck] = await connection.execute(
|
||||
'SELECT user_id FROM Users WHERE email = ? AND user_id != ?',
|
||||
'SELECT user_id FROM users WHERE email = ? AND user_id != ?',
|
||||
[email, userId]
|
||||
);
|
||||
|
||||
@@ -732,7 +732,7 @@ router.put('/users/:id', verifyToken, async (req, res) => {
|
||||
|
||||
// 업데이트된 사용자 정보 조회
|
||||
const [updated] = await connection.execute(
|
||||
'SELECT user_id, username, name, email, access_level, worker_id, is_active FROM Users WHERE user_id = ?',
|
||||
'SELECT user_id, username, name, email, access_level, worker_id, is_active FROM users WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
@@ -786,7 +786,7 @@ router.delete('/users/:id', verifyToken, async (req, res) => {
|
||||
|
||||
// 사용자 존재 확인
|
||||
const [existing] = await connection.execute(
|
||||
'SELECT username FROM Users WHERE user_id = ?',
|
||||
'SELECT username FROM users WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const userModel = require('../models/userModel');
|
||||
const dbPool = require('../dbPool');
|
||||
const { getDb } = require('../dbPool');
|
||||
|
||||
// 로그인 이력 기록 (서비스 내부 헬퍼 함수)
|
||||
const recordLoginHistory = async (userId, success, ipAddress, userAgent, failureReason = null) => {
|
||||
let connection;
|
||||
try {
|
||||
connection = await dbPool.getConnection();
|
||||
await connection.execute(
|
||||
const db = await getDb();
|
||||
await db.execute(
|
||||
`INSERT INTO login_logs (user_id, login_time, ip_address, user_agent, login_status, failure_reason)
|
||||
VALUES (?, NOW(), ?, ?, ?, ?)`,
|
||||
[userId, ipAddress || 'unknown', userAgent || 'unknown', success ? 'success' : 'failed', failureReason]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('로그인 이력 기록 실패:', error);
|
||||
} finally {
|
||||
if (connection) connection.release();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user