로그로그
This commit is contained in:
@@ -1,55 +1,88 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as nodemailer from 'nodemailer';
|
||||
|
||||
/**
|
||||
* 이메일 발송 서비스
|
||||
*
|
||||
* @export
|
||||
* @class EmailService
|
||||
*/
|
||||
@Injectable()
|
||||
export class EmailService {
|
||||
private transporter; // nodemailer 전송 객체
|
||||
private readonly logger = new Logger(EmailService.name);
|
||||
private transporter;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
// SMTP 서버 설정 (AWS SES, Gmail 등)
|
||||
this.transporter = nodemailer.createTransport({ // .env 파일 EMAIL CONFIGURATION
|
||||
host: this.configService.get('SMTP_HOST'),
|
||||
port: parseInt(this.configService.get('SMTP_PORT')),
|
||||
secure: this.configService.get('SMTP_PORT') === '465',
|
||||
const smtpHost = this.configService.get('SMTP_HOST');
|
||||
const smtpPort = this.configService.get('SMTP_PORT');
|
||||
const smtpUser = this.configService.get('SMTP_USER');
|
||||
const smtpPass = this.configService.get('SMTP_PASS');
|
||||
|
||||
this.logger.log(`[EMAIL] SMTP 설정 초기화`);
|
||||
this.logger.log(`[EMAIL] Host: ${smtpHost}`);
|
||||
this.logger.log(`[EMAIL] Port: ${smtpPort}`);
|
||||
this.logger.log(`[EMAIL] User: ${smtpUser}`);
|
||||
this.logger.log(`[EMAIL] Pass: ${smtpPass ? '****설정됨' : '미설정!!'}`);
|
||||
process.stdout.write(`[EMAIL] SMTP Config - Host: ${smtpHost}, Port: ${smtpPort}, User: ${smtpUser}\n`);
|
||||
|
||||
if (!smtpHost || !smtpPort || !smtpUser || !smtpPass) {
|
||||
this.logger.error(`[EMAIL] SMTP 설정 누락! 환경변수를 확인하세요.`);
|
||||
process.stdout.write(`[EMAIL] ERROR: SMTP 설정 누락!\n`);
|
||||
}
|
||||
|
||||
this.transporter = nodemailer.createTransport({
|
||||
host: smtpHost,
|
||||
port: parseInt(smtpPort || '587'),
|
||||
secure: smtpPort === '465',
|
||||
auth: {
|
||||
user: this.configService.get('SMTP_USER'),
|
||||
pass: this.configService.get('SMTP_PASS'),
|
||||
user: smtpUser,
|
||||
pass: smtpPass,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 인증번호 이메일 발송
|
||||
*
|
||||
* @async
|
||||
* @param {string} email - 수신자 이메일
|
||||
* @param {string} code - 6자리 인증번호
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async sendVerificationCode(email: string, code: string): Promise<void> {
|
||||
await this.transporter.sendMail({
|
||||
from: this.configService.get('FROM_EMAIL'),
|
||||
to: email,
|
||||
subject: '[한우 유전능력 시스템] 인증번호 안내',
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2 style="color: #333;">인증번호 안내</h2>
|
||||
<p>아래 인증번호를 입력해주세요.</p>
|
||||
<div style="background-color: #f5f5f5; padding: 20px; text-align: center; margin: 20px 0;">
|
||||
<h1 style="color: #4CAF50; font-size: 32px; margin: 0;">${code}</h1>
|
||||
const fromEmail = this.configService.get('FROM_EMAIL');
|
||||
|
||||
this.logger.log(`[EMAIL] ========== 이메일 발송 시작 ==========`);
|
||||
this.logger.log(`[EMAIL] From: ${fromEmail}`);
|
||||
this.logger.log(`[EMAIL] To: ${email}`);
|
||||
this.logger.log(`[EMAIL] Code: ${code}`);
|
||||
process.stdout.write(`[EMAIL] Sending to: ${email}, code: ${code}\n`);
|
||||
|
||||
try {
|
||||
const result = await this.transporter.sendMail({
|
||||
from: fromEmail,
|
||||
to: email,
|
||||
subject: '[한우 유전능력 시스템] 인증번호 안내',
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2 style="color: #333;">인증번호 안내</h2>
|
||||
<p>아래 인증번호를 입력해주세요.</p>
|
||||
<div style="background-color: #f5f5f5; padding: 20px; text-align: center; margin: 20px 0;">
|
||||
<h1 style="color: #4CAF50; font-size: 32px; margin: 0;">${code}</h1>
|
||||
</div>
|
||||
<p style="color: #666;">인증번호는 3분간 유효합니다.</p>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="color: #999; font-size: 12px;">본 메일은 발신 전용입니다.</p>
|
||||
</div>
|
||||
<p style="color: #666;">인증번호는 3분간 유효합니다.</p>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="color: #999; font-size: 12px;">본 메일은 발신 전용입니다.</p>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
this.logger.log(`[EMAIL] 발송 성공 - MessageId: ${result.messageId}`);
|
||||
this.logger.log(`[EMAIL] Response: ${JSON.stringify(result)}`);
|
||||
process.stdout.write(`[EMAIL] SUCCESS - MessageId: ${result.messageId}\n`);
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`[EMAIL] ========== 발송 실패 ==========`);
|
||||
this.logger.error(`[EMAIL] Error Name: ${error.name}`);
|
||||
this.logger.error(`[EMAIL] Error Message: ${error.message}`);
|
||||
this.logger.error(`[EMAIL] Error Code: ${error.code}`);
|
||||
this.logger.error(`[EMAIL] Stack: ${error.stack}`);
|
||||
process.stdout.write(`[EMAIL] ERROR: ${error.message}\n`);
|
||||
process.stdout.write(`[EMAIL] STACK: ${error.stack}\n`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRedis } from '@nestjs-modules/ioredis';
|
||||
import Redis from 'ioredis';
|
||||
import * as crypto from 'crypto';
|
||||
@@ -6,92 +6,138 @@ import { VERIFICATION_CONFIG } from 'src/common/config/VerificationConfig';
|
||||
|
||||
/**
|
||||
* 인증번호 생성 및 검증 서비스 (Redis 기반)
|
||||
*
|
||||
* @export
|
||||
* @class VerificationService
|
||||
*/
|
||||
@Injectable()
|
||||
export class VerificationService {
|
||||
constructor(@InjectRedis() private readonly redis: Redis) {}
|
||||
private readonly logger = new Logger(VerificationService.name);
|
||||
|
||||
/**
|
||||
* 6자리 인증번호 생성
|
||||
*
|
||||
* @returns {string} 6자리 숫자
|
||||
*/
|
||||
generateCode(): string {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
}
|
||||
constructor(@InjectRedis() private readonly redis: Redis) {
|
||||
this.logger.log(`[REDIS] VerificationService 초기화`);
|
||||
process.stdout.write(`[REDIS] VerificationService initialized\n`);
|
||||
|
||||
// Redis 연결 상태 로깅
|
||||
this.checkRedisConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* 인증번호 저장 (Redis 3분 후 자동 삭제)
|
||||
*
|
||||
* @async
|
||||
* @param {string} key - Redis 키 (예: find-id:test@example.com)
|
||||
* @param {string} code - 인증번호
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async saveCode(key: string, code: string): Promise<void> {
|
||||
await this.redis.set(key, code, 'EX', VERIFICATION_CONFIG.CODE_EXPIRY_SECONDS);
|
||||
}
|
||||
private async checkRedisConnection(): Promise<void> {
|
||||
try {
|
||||
const pong = await this.redis.ping();
|
||||
this.logger.log(`[REDIS] 연결 상태: ${pong}`);
|
||||
process.stdout.write(`[REDIS] Connection status: ${pong}\n`);
|
||||
} catch (error) {
|
||||
this.logger.error(`[REDIS] 연결 실패: ${error.message}`);
|
||||
process.stdout.write(`[REDIS] Connection FAILED: ${error.message}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 인증번호 검증
|
||||
*
|
||||
* @async
|
||||
* @param {string} key - Redis 키
|
||||
* @param {string} code - 사용자가 입력한 인증번호
|
||||
* @returns {Promise<boolean>} 검증 성공 여부
|
||||
*/
|
||||
async verifyCode(key: string, code: string): Promise<boolean> {
|
||||
const savedCode = await this.redis.get(key);
|
||||
console.log(`[DEBUG VerificationService] Key: ${key}, Input code: ${code}, Saved code: ${savedCode}`);
|
||||
/**
|
||||
* 6자리 인증번호 생성
|
||||
*/
|
||||
generateCode(): string {
|
||||
const code = Math.floor(100000 + Math.random() * 900000).toString();
|
||||
this.logger.log(`[REDIS] 인증번호 생성: ${code}`);
|
||||
return code;
|
||||
}
|
||||
|
||||
if (!savedCode) {
|
||||
console.log(`[DEBUG VerificationService] No saved code found for key: ${key}`);
|
||||
return false; // 인증번호 없음 (만료 또는 미발급)
|
||||
}
|
||||
/**
|
||||
* 인증번호 저장 (Redis)
|
||||
*/
|
||||
async saveCode(key: string, code: string): Promise<void> {
|
||||
this.logger.log(`[REDIS] ========== 코드 저장 시작 ==========`);
|
||||
this.logger.log(`[REDIS] Key: ${key}`);
|
||||
this.logger.log(`[REDIS] Code: ${code}`);
|
||||
this.logger.log(`[REDIS] TTL: ${VERIFICATION_CONFIG.CODE_EXPIRY_SECONDS}초`);
|
||||
process.stdout.write(`[REDIS] Saving - Key: ${key}, Code: ${code}\n`);
|
||||
|
||||
if (savedCode !== code) {
|
||||
console.log(`[DEBUG VerificationService] Code mismatch - Saved: ${savedCode}, Input: ${code}`);
|
||||
return false; // 인증번호 불일치
|
||||
}
|
||||
try {
|
||||
const result = await this.redis.set(key, code, 'EX', VERIFICATION_CONFIG.CODE_EXPIRY_SECONDS);
|
||||
this.logger.log(`[REDIS] 저장 결과: ${result}`);
|
||||
process.stdout.write(`[REDIS] Save result: ${result}\n`);
|
||||
} catch (error) {
|
||||
this.logger.error(`[REDIS] ========== 저장 실패 ==========`);
|
||||
this.logger.error(`[REDIS] Error: ${error.message}`);
|
||||
this.logger.error(`[REDIS] Stack: ${error.stack}`);
|
||||
process.stdout.write(`[REDIS] SAVE ERROR: ${error.message}\n`);
|
||||
process.stdout.write(`[REDIS] STACK: ${error.stack}\n`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 검증 성공 시 Redis에서 삭제 (1회용)
|
||||
await this.redis.del(key);
|
||||
console.log(`[DEBUG VerificationService] Code verified successfully!`);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 인증번호 검증
|
||||
*/
|
||||
async verifyCode(key: string, code: string): Promise<boolean> {
|
||||
this.logger.log(`[REDIS] ========== 코드 검증 시작 ==========`);
|
||||
this.logger.log(`[REDIS] Key: ${key}`);
|
||||
this.logger.log(`[REDIS] Input Code: ${code}`);
|
||||
process.stdout.write(`[REDIS] Verifying - Key: ${key}, Input: ${code}\n`);
|
||||
|
||||
/**
|
||||
* 비밀번호 재설정 토큰 생성 및 저장
|
||||
*
|
||||
* @async
|
||||
* @param {string} userId - 사용자 ID
|
||||
* @returns {Promise<string>} 재설정 토큰
|
||||
*/
|
||||
async generateResetToken(userId: string): Promise<string> {
|
||||
const token = crypto.randomBytes(VERIFICATION_CONFIG.TOKEN_BYTES_LENGTH).toString('hex');
|
||||
await this.redis.set(`reset:${token}`, userId, 'EX', VERIFICATION_CONFIG.RESET_TOKEN_EXPIRY_SECONDS);
|
||||
return token;
|
||||
}
|
||||
try {
|
||||
const savedCode = await this.redis.get(key);
|
||||
this.logger.log(`[REDIS] Saved Code: ${savedCode}`);
|
||||
process.stdout.write(`[REDIS] Saved code: ${savedCode}\n`);
|
||||
|
||||
/**
|
||||
* 비밀번호 재설정 토큰 검증
|
||||
*
|
||||
* @async
|
||||
* @param {string} token - 재설정 토큰
|
||||
* @returns {Promise<string | null>} 사용자 ID 또는 null
|
||||
*/
|
||||
async verifyResetToken(token: string): Promise<string | null> {
|
||||
const userId = await this.redis.get(`reset:${token}`);
|
||||
if (!savedCode) {
|
||||
this.logger.warn(`[REDIS] 저장된 코드 없음 (만료 또는 미발급)`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return null; // 토큰 없음 (만료 또는 미발급)
|
||||
}
|
||||
if (savedCode !== code) {
|
||||
this.logger.warn(`[REDIS] 코드 불일치 - Saved: ${savedCode}, Input: ${code}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 검증 성공 시 토큰 삭제 (1회용)
|
||||
await this.redis.del(`reset:${token}`);
|
||||
return userId;
|
||||
}
|
||||
await this.redis.del(key);
|
||||
this.logger.log(`[REDIS] 검증 성공, 코드 삭제됨`);
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`[REDIS] ========== 검증 실패 ==========`);
|
||||
this.logger.error(`[REDIS] Error: ${error.message}`);
|
||||
this.logger.error(`[REDIS] Stack: ${error.stack}`);
|
||||
process.stdout.write(`[REDIS] VERIFY ERROR: ${error.message}\n`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 비밀번호 재설정 토큰 생성 및 저장
|
||||
*/
|
||||
async generateResetToken(userId: string): Promise<string> {
|
||||
this.logger.log(`[REDIS] 리셋 토큰 생성 - userId: ${userId}`);
|
||||
|
||||
try {
|
||||
const token = crypto.randomBytes(VERIFICATION_CONFIG.TOKEN_BYTES_LENGTH).toString('hex');
|
||||
await this.redis.set(`reset:${token}`, userId, 'EX', VERIFICATION_CONFIG.RESET_TOKEN_EXPIRY_SECONDS);
|
||||
this.logger.log(`[REDIS] 리셋 토큰 저장 완료`);
|
||||
return token;
|
||||
} catch (error) {
|
||||
this.logger.error(`[REDIS] 리셋 토큰 생성 실패: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 비밀번호 재설정 토큰 검증
|
||||
*/
|
||||
async verifyResetToken(token: string): Promise<string | null> {
|
||||
this.logger.log(`[REDIS] 리셋 토큰 검증`);
|
||||
|
||||
try {
|
||||
const userId = await this.redis.get(`reset:${token}`);
|
||||
|
||||
if (!userId) {
|
||||
this.logger.warn(`[REDIS] 리셋 토큰 없음 또는 만료`);
|
||||
return null;
|
||||
}
|
||||
|
||||
await this.redis.del(`reset:${token}`);
|
||||
this.logger.log(`[REDIS] 리셋 토큰 검증 성공 - userId: ${userId}`);
|
||||
return userId;
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`[REDIS] 리셋 토큰 검증 실패: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user