RESTAPI_SERVER/database/auth-queries.js

441 lines
12 KiB
JavaScript
Raw Normal View History

const { getConnection } = require('./connection');
const crypto = require('crypto');
2025-09-25 16:23:32 +09:00
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`,
2025-09-25 16:23:32 +09:00
[userId],
{
fetchInfo: {
"PERMISSIONS": { type: oracledb.STRING } // CLOB을 문자열로 변환
}
}
);
2025-09-25 16:23:32 +09:00
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
};