const { getConnection } = require('./connection'); const crypto = require('crypto'); const oracledb = require('oracledb'); // 사용자 생성 async function createUser(userData) { let connection; try { connection = await getConnection(); // 비밀번호 해시화 const salt = crypto.randomBytes(32).toString('hex'); const hashedPassword = crypto.pbkdf2Sync(userData.password, salt, 10000, 64, 'sha512').toString('hex'); const result = await connection.execute( `INSERT INTO USERS (USERNAME, EMAIL, PASSWORD_HASH, SALT, ROLE) VALUES (:username, :email, :passwordHash, :salt, :role)`, { username: userData.username, email: userData.email, passwordHash: hashedPassword, salt: salt, role: userData.role || 'user' }, { autoCommit: true } ); return result.rowsAffected; } catch (err) { console.error('사용자 생성 실패:', err); throw err; } finally { if (connection) { await connection.close(); } } } // 사용자 인증 async function authenticateUser(username, password) { let connection; try { connection = await getConnection(); const result = await connection.execute( `SELECT ID, USERNAME, EMAIL, PASSWORD_HASH, SALT, ROLE, IS_ACTIVE FROM USERS WHERE USERNAME = :username AND IS_ACTIVE = 1`, [username] ); if (result.rows.length === 0) { return null; } const user = { id: result.rows[0][0], username: result.rows[0][1], email: result.rows[0][2], passwordHash: result.rows[0][3], salt: result.rows[0][4], role: result.rows[0][5], isActive: result.rows[0][6] }; // 비밀번호 검증 const hashedPassword = crypto.pbkdf2Sync(password, user.salt, 10000, 64, 'sha512').toString('hex'); if (hashedPassword === user.passwordHash) { delete user.passwordHash; delete user.salt; return user; } return null; } catch (err) { console.error('사용자 인증 실패:', err); throw err; } finally { if (connection) { await connection.close(); } } } // API 키 생성 async function createApiKey(userId, keyName, permissions = []) { let connection; try { connection = await getConnection(); // API 키 생성 (32바이트 랜덤) const apiKey = 'ak_' + crypto.randomBytes(32).toString('hex'); const result = await connection.execute( `INSERT INTO API_KEYS (USER_ID, KEY_NAME, API_KEY, PERMISSIONS, IS_ACTIVE) VALUES (:userId, :keyName, :apiKey, :permissions, 1)`, { userId: userId, keyName: keyName, apiKey: apiKey, permissions: JSON.stringify(permissions) }, { autoCommit: true } ); return { apiKey, rowsAffected: result.rowsAffected }; } catch (err) { console.error('API 키 생성 실패:', err); throw err; } finally { if (connection) { await connection.close(); } } } // API 키 검증 async function validateApiKey(apiKey) { let connection; try { connection = await getConnection(); const result = await connection.execute( `SELECT ak.ID, ak.USER_ID, ak.KEY_NAME, ak.PERMISSIONS, ak.USAGE_COUNT, ak.LAST_USED, u.USERNAME, u.EMAIL, u.ROLE FROM API_KEYS ak JOIN USERS u ON ak.USER_ID = u.ID WHERE ak.API_KEY = :apiKey AND ak.IS_ACTIVE = 1 AND u.IS_ACTIVE = 1`, [apiKey] ); if (result.rows.length === 0) { return null; } const keyData = { id: result.rows[0][0], userId: result.rows[0][1], keyName: result.rows[0][2], permissions: JSON.parse(result.rows[0][3] || '[]'), usageCount: result.rows[0][4], lastUsed: result.rows[0][5], user: { username: result.rows[0][6], email: result.rows[0][7], role: result.rows[0][8] } }; // 사용 횟수 증가 및 마지막 사용 시간 업데이트 await connection.execute( `UPDATE API_KEYS SET USAGE_COUNT = USAGE_COUNT + 1, LAST_USED = CURRENT_TIMESTAMP WHERE ID = :id`, [keyData.id], { autoCommit: true } ); return keyData; } catch (err) { console.error('API 키 검증 실패:', err); throw err; } finally { if (connection) { await connection.close(); } } } // 사용자의 API 키 목록 조회 async function getUserApiKeys(userId) { let connection; try { connection = await getConnection(); const result = await connection.execute( `SELECT ID, KEY_NAME, API_KEY, PERMISSIONS, USAGE_COUNT, LAST_USED, CREATED_AT, IS_ACTIVE FROM API_KEYS WHERE USER_ID = :userId ORDER BY CREATED_AT DESC`, [userId], { fetchInfo: { "PERMISSIONS": { type: oracledb.STRING } // CLOB을 문자열로 변환 } } ); return result.rows.map(row => { let permissions = []; try { // CLOB이 문자열로 변환되어 있어야 함 if (row[3] && typeof row[3] === 'string') { permissions = JSON.parse(row[3]); } } catch (e) { console.error('권한 파싱 오류:', row[3], e); permissions = []; } return { id: row[0], keyName: row[1], apiKey: row[2], permissions: permissions, usageCount: row[4] || 0, lastUsed: row[5], createdAt: row[6], isActive: row[7] }; }); } catch (err) { console.error('API 키 목록 조회 실패:', err); throw err; } finally { if (connection) { await connection.close(); } } } // API 키 비활성화 async function deactivateApiKey(keyId, userId) { let connection; try { connection = await getConnection(); const result = await connection.execute( `UPDATE API_KEYS SET IS_ACTIVE = 0 WHERE ID = :keyId AND USER_ID = :userId`, [keyId, userId], { autoCommit: true } ); return result.rowsAffected; } catch (err) { console.error('API 키 비활성화 실패:', err); throw err; } finally { if (connection) { await connection.close(); } } } // 모든 사용자 조회 (관리자용) async function getAllUsers() { let connection; try { connection = await getConnection(); const result = await connection.execute( `SELECT ID, USERNAME, EMAIL, ROLE, IS_ACTIVE, CREATED_AT, LAST_LOGIN FROM USERS ORDER BY CREATED_AT DESC` ); return result.rows.map(row => ({ id: row[0], username: row[1], email: row[2], role: row[3], isActive: row[4], createdAt: row[5], lastLogin: row[6] })); } catch (err) { console.error('사용자 목록 조회 실패:', err); throw err; } finally { if (connection) { await connection.close(); } } } // API 사용 통계 조회 async function getApiUsageStats() { let connection; try { connection = await getConnection(); const result = await connection.execute( `SELECT COUNT(DISTINCT ak.USER_ID) as total_users, COUNT(ak.ID) as total_keys, SUM(ak.USAGE_COUNT) as total_requests, COUNT(CASE WHEN ak.LAST_USED >= SYSDATE - 1 THEN 1 END) as active_keys_today FROM API_KEYS ak WHERE ak.IS_ACTIVE = 1` ); return { totalUsers: result.rows[0][0], totalKeys: result.rows[0][1], totalRequests: result.rows[0][2], activeKeysToday: result.rows[0][3] }; } catch (err) { console.error('API 사용 통계 조회 실패:', err); throw err; } finally { if (connection) { await connection.close(); } } } // 테이블 생성 async function createAuthTables() { let connection; try { connection = await getConnection(); // 사용자 테이블 생성 const checkUsersTable = await connection.execute( `SELECT COUNT(*) as count FROM user_tables WHERE table_name = 'USERS'` ); if (checkUsersTable.rows[0][0] === 0) { await connection.execute( `CREATE TABLE USERS ( ID NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, USERNAME VARCHAR2(50) UNIQUE NOT NULL, EMAIL VARCHAR2(100) UNIQUE NOT NULL, PASSWORD_HASH VARCHAR2(128) NOT NULL, SALT VARCHAR2(64) NOT NULL, ROLE VARCHAR2(20) DEFAULT 'user' CHECK (ROLE IN ('admin', 'user')), IS_ACTIVE NUMBER(1) DEFAULT 1, CREATED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP, LAST_LOGIN TIMESTAMP )`, [], { autoCommit: true } ); console.log('USERS 테이블이 생성되었습니다.'); } // API 키 테이블 생성 const checkApiKeysTable = await connection.execute( `SELECT COUNT(*) as count FROM user_tables WHERE table_name = 'API_KEYS'` ); if (checkApiKeysTable.rows[0][0] === 0) { await connection.execute( `CREATE TABLE API_KEYS ( ID NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, USER_ID NUMBER NOT NULL, KEY_NAME VARCHAR2(100) NOT NULL, API_KEY VARCHAR2(100) UNIQUE NOT NULL, PERMISSIONS CLOB, USAGE_COUNT NUMBER DEFAULT 0, IS_ACTIVE NUMBER(1) DEFAULT 1, CREATED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP, LAST_USED TIMESTAMP, CONSTRAINT FK_API_KEYS_USER FOREIGN KEY (USER_ID) REFERENCES USERS(ID) )`, [], { autoCommit: true } ); console.log('API_KEYS 테이블이 생성되었습니다.'); } // 인덱스 생성 try { await connection.execute(`CREATE INDEX IDX_API_KEYS_KEY ON API_KEYS(API_KEY)`); await connection.execute(`CREATE INDEX IDX_API_KEYS_USER ON API_KEYS(USER_ID)`); await connection.execute(`CREATE INDEX IDX_USERS_USERNAME ON USERS(USERNAME)`); } catch (err) { // 인덱스가 이미 존재할 수 있음 } // 기본 관리자 계정 생성 const checkAdmin = await connection.execute( `SELECT COUNT(*) as count FROM USERS WHERE USERNAME = 'admin'` ); if (checkAdmin.rows[0][0] === 0) { const salt = crypto.randomBytes(32).toString('hex'); const hashedPassword = crypto.pbkdf2Sync('admin123!', salt, 10000, 64, 'sha512').toString('hex'); await connection.execute( `INSERT INTO USERS (USERNAME, EMAIL, PASSWORD_HASH, SALT, ROLE) VALUES ('admin', 'admin@restapi.com', :passwordHash, :salt, 'admin')`, { passwordHash: hashedPassword, salt: salt }, { autoCommit: true } ); console.log('기본 관리자 계정이 생성되었습니다. (admin/admin123!)'); } } catch (err) { console.error('인증 테이블 생성 실패:', err); throw err; } finally { if (connection) { await connection.close(); } } } // API 키 삭제 async function deleteApiKey(keyId, userId) { let connection; try { connection = await getConnection(); const result = await connection.execute( `DELETE FROM API_KEYS WHERE id = :keyId AND user_id = :userId`, { keyId, userId }, { autoCommit: true } ); return result.rowsAffected; } catch (err) { console.error('API 키 삭제 실패:', err); throw err; } finally { if (connection) { await connection.close(); } } } module.exports = { createUser, authenticateUser, createApiKey, validateApiKey, getUserApiKeys, deactivateApiKey, deleteApiKey, getAllUsers, getApiUsageStats, createAuthTables };