131 lines
3.2 KiB
JavaScript
131 lines
3.2 KiB
JavaScript
// src/models/api-key.model.js
|
|
// API 키 모델
|
|
|
|
const { DataTypes } = require('sequelize');
|
|
const crypto = require('crypto');
|
|
|
|
module.exports = (sequelize) => {
|
|
const ApiKey = sequelize.define('ApiKey', {
|
|
id: {
|
|
type: DataTypes.UUID,
|
|
defaultValue: DataTypes.UUIDV4,
|
|
primaryKey: true,
|
|
},
|
|
userId: {
|
|
type: DataTypes.UUID,
|
|
allowNull: false,
|
|
references: {
|
|
model: 'users',
|
|
key: 'id',
|
|
},
|
|
comment: '소유자 사용자 ID',
|
|
},
|
|
name: {
|
|
type: DataTypes.STRING(100),
|
|
allowNull: false,
|
|
comment: 'API 키 이름 (사용자 지정)',
|
|
},
|
|
keyPrefix: {
|
|
type: DataTypes.STRING(12),
|
|
allowNull: false,
|
|
comment: 'API 키 접두사 (표시용)',
|
|
},
|
|
keyHash: {
|
|
type: DataTypes.STRING(64),
|
|
allowNull: false,
|
|
unique: true,
|
|
comment: 'API 키 해시 (SHA-256)',
|
|
},
|
|
permissions: {
|
|
type: DataTypes.JSONB,
|
|
defaultValue: ['chat:read', 'chat:write'],
|
|
comment: '권한 목록',
|
|
},
|
|
rateLimit: {
|
|
type: DataTypes.INTEGER,
|
|
defaultValue: 60, // 분당 60회
|
|
comment: '분당 요청 제한',
|
|
},
|
|
status: {
|
|
type: DataTypes.ENUM('active', 'revoked', 'expired'),
|
|
defaultValue: 'active',
|
|
comment: 'API 키 상태',
|
|
},
|
|
expiresAt: {
|
|
type: DataTypes.DATE,
|
|
allowNull: true,
|
|
comment: '만료 일시 (null이면 무기한)',
|
|
},
|
|
lastUsedAt: {
|
|
type: DataTypes.DATE,
|
|
allowNull: true,
|
|
comment: '마지막 사용 시간',
|
|
},
|
|
totalRequests: {
|
|
type: DataTypes.INTEGER,
|
|
defaultValue: 0,
|
|
comment: '총 요청 수',
|
|
},
|
|
}, {
|
|
tableName: 'api_keys',
|
|
timestamps: true,
|
|
underscored: true,
|
|
indexes: [
|
|
{
|
|
fields: ['key_hash'],
|
|
unique: true,
|
|
},
|
|
{
|
|
fields: ['user_id'],
|
|
},
|
|
{
|
|
fields: ['status'],
|
|
},
|
|
],
|
|
});
|
|
|
|
// 클래스 메서드: API 키 생성
|
|
ApiKey.generateKey = function() {
|
|
const prefix = process.env.API_KEY_PREFIX || 'sk-';
|
|
const length = parseInt(process.env.API_KEY_LENGTH, 10) || 48;
|
|
const randomPart = crypto.randomBytes(length).toString('base64url').slice(0, length);
|
|
return `${prefix}${randomPart}`;
|
|
};
|
|
|
|
// 클래스 메서드: API 키 해시 생성
|
|
ApiKey.hashKey = function(key) {
|
|
return crypto.createHash('sha256').update(key).digest('hex');
|
|
};
|
|
|
|
// 클래스 메서드: API 키로 조회
|
|
ApiKey.findByKey = async function(key) {
|
|
const keyHash = this.hashKey(key);
|
|
const apiKey = await this.findOne({
|
|
where: { keyHash, status: 'active' },
|
|
});
|
|
|
|
if (apiKey) {
|
|
// 사용자 정보 별도 조회
|
|
const { User } = require('./index');
|
|
apiKey.user = await User.findByPk(apiKey.userId);
|
|
}
|
|
|
|
return apiKey;
|
|
};
|
|
|
|
// 인스턴스 메서드: 사용 기록 업데이트
|
|
ApiKey.prototype.recordUsage = async function() {
|
|
this.lastUsedAt = new Date();
|
|
this.totalRequests += 1;
|
|
await this.save();
|
|
};
|
|
|
|
// 인스턴스 메서드: 만료 여부 확인
|
|
ApiKey.prototype.isExpired = function() {
|
|
if (!this.expiresAt) return false;
|
|
return new Date() > this.expiresAt;
|
|
};
|
|
|
|
return ApiKey;
|
|
};
|