258 lines
6.1 KiB
JavaScript
258 lines
6.1 KiB
JavaScript
// src/middlewares/auth.middleware.js
|
|
// 인증 미들웨어
|
|
|
|
const jwt = require('jsonwebtoken');
|
|
const { ApiKey, User } = require('../models');
|
|
const logger = require('../config/logger.config');
|
|
|
|
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
|
|
|
/**
|
|
* JWT 토큰 인증 미들웨어
|
|
* Authorization: Bearer <JWT_TOKEN>
|
|
*/
|
|
exports.authenticateJWT = async (req, res, next) => {
|
|
try {
|
|
const authHeader = req.headers.authorization;
|
|
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: {
|
|
code: 'UNAUTHORIZED',
|
|
message: '인증 토큰이 필요합니다.',
|
|
},
|
|
});
|
|
}
|
|
|
|
const token = authHeader.substring(7);
|
|
|
|
try {
|
|
const decoded = jwt.verify(token, JWT_SECRET);
|
|
req.user = decoded;
|
|
return next();
|
|
} catch (error) {
|
|
if (error.name === 'TokenExpiredError') {
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: {
|
|
code: 'TOKEN_EXPIRED',
|
|
message: '토큰이 만료되었습니다.',
|
|
},
|
|
});
|
|
}
|
|
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: {
|
|
code: 'INVALID_TOKEN',
|
|
message: '유효하지 않은 토큰입니다.',
|
|
},
|
|
});
|
|
}
|
|
} catch (error) {
|
|
return next(error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* API 키 인증 미들웨어
|
|
* Authorization: Bearer <API_KEY>
|
|
*/
|
|
exports.authenticateApiKey = async (req, res, next) => {
|
|
try {
|
|
const authHeader = req.headers.authorization;
|
|
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
return res.status(401).json({
|
|
error: {
|
|
message: 'API 키가 필요합니다.',
|
|
type: 'invalid_request_error',
|
|
code: 'missing_api_key',
|
|
},
|
|
});
|
|
}
|
|
|
|
const apiKeyValue = authHeader.substring(7);
|
|
|
|
// API 키 접두사 확인
|
|
const prefix = process.env.API_KEY_PREFIX || 'sk-';
|
|
if (!apiKeyValue.startsWith(prefix)) {
|
|
return res.status(401).json({
|
|
error: {
|
|
message: '유효하지 않은 API 키 형식입니다.',
|
|
type: 'invalid_request_error',
|
|
code: 'invalid_api_key',
|
|
},
|
|
});
|
|
}
|
|
|
|
// API 키 조회
|
|
const apiKey = await ApiKey.findByKey(apiKeyValue);
|
|
|
|
if (!apiKey) {
|
|
return res.status(401).json({
|
|
error: {
|
|
message: '유효하지 않은 API 키입니다.',
|
|
type: 'invalid_request_error',
|
|
code: 'invalid_api_key',
|
|
},
|
|
});
|
|
}
|
|
|
|
// 만료 확인
|
|
if (apiKey.isExpired()) {
|
|
return res.status(401).json({
|
|
error: {
|
|
message: 'API 키가 만료되었습니다.',
|
|
type: 'invalid_request_error',
|
|
code: 'expired_api_key',
|
|
},
|
|
});
|
|
}
|
|
|
|
// 사용자 상태 확인
|
|
if (apiKey.user.status !== 'active') {
|
|
return res.status(403).json({
|
|
error: {
|
|
message: '계정이 비활성화되었습니다.',
|
|
type: 'invalid_request_error',
|
|
code: 'account_inactive',
|
|
},
|
|
});
|
|
}
|
|
|
|
// 사용 기록 업데이트
|
|
await apiKey.recordUsage();
|
|
|
|
// 요청 객체에 사용자 및 API 키 정보 추가
|
|
req.user = {
|
|
id: apiKey.user.id,
|
|
userId: apiKey.user.id,
|
|
email: apiKey.user.email,
|
|
role: apiKey.user.role,
|
|
plan: apiKey.user.plan,
|
|
};
|
|
req.apiKey = apiKey;
|
|
|
|
return next();
|
|
} catch (error) {
|
|
logger.error('API 키 인증 오류:', error);
|
|
return next(error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 관리자 권한 확인 미들웨어
|
|
*/
|
|
exports.requireAdmin = (req, res, next) => {
|
|
if (req.user.role !== 'admin') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: {
|
|
code: 'FORBIDDEN',
|
|
message: '관리자 권한이 필요합니다.',
|
|
},
|
|
});
|
|
}
|
|
return next();
|
|
};
|
|
|
|
/**
|
|
* JWT 또는 API 키 인증 미들웨어
|
|
* JWT 토큰과 API 키 모두 허용
|
|
*/
|
|
exports.authenticateAny = async (req, res, next) => {
|
|
try {
|
|
const authHeader = req.headers.authorization;
|
|
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: {
|
|
code: 'UNAUTHORIZED',
|
|
message: '인증이 필요합니다.',
|
|
},
|
|
});
|
|
}
|
|
|
|
const token = authHeader.substring(7);
|
|
const prefix = process.env.API_KEY_PREFIX || 'sk-';
|
|
|
|
// API 키인 경우
|
|
if (token.startsWith(prefix)) {
|
|
const apiKey = await ApiKey.findByKey(token);
|
|
|
|
if (!apiKey) {
|
|
return res.status(401).json({
|
|
error: {
|
|
message: '유효하지 않은 API 키입니다.',
|
|
type: 'invalid_request_error',
|
|
code: 'invalid_api_key',
|
|
},
|
|
});
|
|
}
|
|
|
|
if (apiKey.isExpired()) {
|
|
return res.status(401).json({
|
|
error: {
|
|
message: 'API 키가 만료되었습니다.',
|
|
type: 'invalid_request_error',
|
|
code: 'expired_api_key',
|
|
},
|
|
});
|
|
}
|
|
|
|
if (apiKey.user.status !== 'active') {
|
|
return res.status(403).json({
|
|
error: {
|
|
message: '계정이 비활성화되었습니다.',
|
|
type: 'invalid_request_error',
|
|
code: 'account_inactive',
|
|
},
|
|
});
|
|
}
|
|
|
|
await apiKey.recordUsage();
|
|
|
|
req.user = {
|
|
id: apiKey.user.id,
|
|
userId: apiKey.user.id,
|
|
email: apiKey.user.email,
|
|
role: apiKey.user.role,
|
|
plan: apiKey.user.plan,
|
|
};
|
|
req.apiKey = apiKey;
|
|
|
|
return next();
|
|
}
|
|
|
|
// JWT 토큰인 경우
|
|
try {
|
|
const decoded = jwt.verify(token, JWT_SECRET);
|
|
req.user = decoded;
|
|
return next();
|
|
} catch (error) {
|
|
if (error.name === 'TokenExpiredError') {
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: {
|
|
code: 'TOKEN_EXPIRED',
|
|
message: '토큰이 만료되었습니다.',
|
|
},
|
|
});
|
|
}
|
|
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: {
|
|
code: 'INVALID_TOKEN',
|
|
message: '유효하지 않은 토큰입니다.',
|
|
},
|
|
});
|
|
}
|
|
} catch (error) {
|
|
return next(error);
|
|
}
|
|
};
|