issue # 이메일 인증 코드 발송 로직 구현
This commit is contained in:
2
.env
2
.env
@@ -19,7 +19,7 @@ GMAIL_USER=bkd.scheduler@gmail.com
|
|||||||
GMAIL_PASS= # 앱 비밀번호 또는 OAuth2 토큰
|
GMAIL_PASS= # 앱 비밀번호 또는 OAuth2 토큰
|
||||||
GMAIL_CLIENT_ID=688417162908-iqvnj4ceb8t1dkbjr70dtcafo27m8kqe.apps.googleusercontent.com
|
GMAIL_CLIENT_ID=688417162908-iqvnj4ceb8t1dkbjr70dtcafo27m8kqe.apps.googleusercontent.com
|
||||||
GMAIL_CLIENT_SECRET=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
|
GMAIL_CLIENT_SECRET=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
|
||||||
GMAIL_REFRESH_TOKEN=1//04_pSivNoGpPUCgYIARAAGAQSNwF-L9IrO0Kx6jSzq_eQNjdl65f0O2iqKSNpFeZ3gtIGMhOk0oiZsnKrPfWs8jvuEic1NhUoZ0g
|
GMAIL_REFRESH_TOKEN=1//04P8ekVQmkdtnCgYIARAAGAQSNwF-L9IrqPOyH8oYB-mdjUqw9jGHienVLBTWFdiZgpRnPgFmYnAdbjnstd9RkRVeJErB0NRAwg4
|
||||||
|
|
||||||
# SMTP 추가 옵션
|
# SMTP 추가 옵션
|
||||||
SMTP_AUTH=true
|
SMTP_AUTH=true
|
||||||
|
|||||||
@@ -28,7 +28,9 @@
|
|||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"drizzle-kit": "^0.31.7",
|
"drizzle-kit": "^0.31.7",
|
||||||
"drizzle-orm": "^0.44.7",
|
"drizzle-orm": "^0.44.7",
|
||||||
|
"googleapis": "^166.0.0",
|
||||||
"ioredis": "^5.8.2",
|
"ioredis": "^5.8.2",
|
||||||
|
"nodemailer": "^7.0.10",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
@@ -43,6 +45,7 @@
|
|||||||
"@types/ioredis": "^5.0.0",
|
"@types/ioredis": "^5.0.0",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
|
"@types/nodemailer": "^7.0.4",
|
||||||
"@types/pg": "^8.15.6",
|
"@types/pg": "^8.15.6",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import { AppController } from './app.controller';
|
|||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { DbModule } from './db/db.module';
|
import { DbModule } from './db/db.module';
|
||||||
import { RedisModule } from './redis/redis.module';
|
import { RedisModule } from './redis/redis.module';
|
||||||
|
import { AccountModule } from './modules/account/account.module';
|
||||||
|
import { MailerModule } from './util/mailer/mailer.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [DbModule, RedisModule],
|
imports: [DbModule, RedisModule, MailerModule, AccountModule],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
import { Controller, Get, Post, Query } from "@nestjs/common";
|
import { Body, Controller, Get, Post, Query } from "@nestjs/common";
|
||||||
import { CheckDuplicationRequestDto } from "./dto/checkDuplication/check-duplication-request.dto";
|
import { CheckDuplicationRequestDto } from "./dto/checkDuplication/check-duplication-request.dto";
|
||||||
import { CheckDuplicationResponseDto } from "./dto/checkDuplication/check-duplication-response.dto";
|
import { CheckDuplicationResponseDto } from "./dto/checkDuplication/check-duplication-response.dto";
|
||||||
import { AccountService } from "./account.service";
|
import { AccountService } from "./account.service";
|
||||||
|
import { SendVerificationCodeRequestDto } from "./dto/checkDuplication/send-verification-code-request.dto";
|
||||||
|
import { SendVerificationCodeResponseDto } from "./dto/checkDuplication/send-verification-code-response.dto";
|
||||||
|
|
||||||
@Controller('account')
|
@Controller('account')
|
||||||
export class AccountController {
|
export class AccountController {
|
||||||
constructor(private readonly accountService: AccountService) {}
|
constructor(private readonly accountService: AccountService) {}
|
||||||
|
|
||||||
|
@Get('/')
|
||||||
|
async test() {
|
||||||
|
return "Test"
|
||||||
|
}
|
||||||
|
|
||||||
@Get('check-duplication')
|
@Get('check-duplication')
|
||||||
async checkDuplication(@Query() query: CheckDuplicationRequestDto): Promise<CheckDuplicationResponseDto> {
|
async checkDuplication(@Query() query: CheckDuplicationRequestDto): Promise<CheckDuplicationResponseDto> {
|
||||||
return this.accountService.checkDuplication(query);
|
return await this.accountService.checkDuplication(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('send-verification-code')
|
||||||
|
async sendVerificationCode(@Body() body: SendVerificationCodeRequestDto): Promise<SendVerificationCodeResponseDto> {
|
||||||
|
const result = await this.accountService.sendVerificationCode(body);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import { AccountService } from "./account.service";
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [AccountController],
|
controllers: [AccountController],
|
||||||
providers: [AccountService, AccountRepo]
|
providers: [AccountService, AccountRepo],
|
||||||
|
exports: [AccountService, AccountRepo]
|
||||||
})
|
})
|
||||||
export class AccountModule {}
|
export class AccountModule {}
|
||||||
@@ -2,14 +2,33 @@ import { Injectable } from "@nestjs/common";
|
|||||||
import { AccountRepo } from "./account.repo";
|
import { AccountRepo } from "./account.repo";
|
||||||
import { CheckDuplicationRequestDto } from "./dto/checkDuplication/check-duplication-request.dto";
|
import { CheckDuplicationRequestDto } from "./dto/checkDuplication/check-duplication-request.dto";
|
||||||
import { CheckDuplicationResponseDto } from "./dto/checkDuplication/check-duplication-response.dto";
|
import { CheckDuplicationResponseDto } from "./dto/checkDuplication/check-duplication-response.dto";
|
||||||
|
import { SendVerificationCodeRequestDto } from "./dto/checkDuplication/send-verification-code-request.dto";
|
||||||
|
import { MailerService } from "src/util/mailer/mailer.service";
|
||||||
|
import { Generator } from "src/util/generator";
|
||||||
|
import { SendVerificationCodeResponseDto } from "./dto/checkDuplication/send-verification-code-response.dto";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountService {
|
export class AccountService {
|
||||||
constructor(private readonly accountRepo: AccountRepo) {}
|
constructor(
|
||||||
|
private readonly accountRepo: AccountRepo
|
||||||
|
, private readonly mailerService: MailerService
|
||||||
|
) {}
|
||||||
|
|
||||||
async checkDuplication(data: CheckDuplicationRequestDto): Promise<CheckDuplicationResponseDto> {
|
async checkDuplication(data: CheckDuplicationRequestDto): Promise<CheckDuplicationResponseDto> {
|
||||||
const count = await this.accountRepo.checkDuplication(data.type, data.value);
|
const count = await this.accountRepo.checkDuplication(data.type, data.value);
|
||||||
|
|
||||||
return { isDuplicated: count > 0 };
|
return { isDuplicated: count > 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendVerificationCode(data: SendVerificationCodeRequestDto): Promise<SendVerificationCodeResponseDto> {
|
||||||
|
const code = Generator.getVerificationCode();
|
||||||
|
const html = `<p>Your verification code is: <strong style="font-size:16px;">${code}</strong></p>`;
|
||||||
|
const result = await this.mailerService.sendMail(data.email, "<Scheduler> 이메일 인증 코드", html);
|
||||||
|
|
||||||
|
if (result.rejected.length > 0) {
|
||||||
|
return { success: false, error: result.response }
|
||||||
|
} else {
|
||||||
|
return { success: true, message: "이메일 발송 완료" };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { IsEmail } from "@nestjs/class-validator";
|
||||||
|
|
||||||
|
export class SendVerificationCodeRequestDto {
|
||||||
|
@IsEmail()
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export class SendVerificationCodeResponseDto {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
0
src/util/converter.ts
Normal file
0
src/util/converter.ts
Normal file
5
src/util/generator.ts
Normal file
5
src/util/generator.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export class Generator {
|
||||||
|
static getVerificationCode() {
|
||||||
|
return Math.random().toString().slice(2, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/util/mailer/mailer.module.ts
Normal file
9
src/util/mailer/mailer.module.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Module, Global } from '@nestjs/common';
|
||||||
|
import { MailerService } from './mailer.service';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
providers: [MailerService],
|
||||||
|
exports: [MailerService]
|
||||||
|
})
|
||||||
|
export class MailerModule {}
|
||||||
46
src/util/mailer/mailer.service.ts
Normal file
46
src/util/mailer/mailer.service.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
import { google } from 'googleapis';
|
||||||
|
import SMTPTransport from 'nodemailer/lib/smtp-transport';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MailerService {
|
||||||
|
private oauth2Client = new google.auth.OAuth2(
|
||||||
|
process.env.GMAIL_CLIENT_ID,
|
||||||
|
process.env.GMAIL_CLIENT_SECRET,
|
||||||
|
'https://developers.google.com/oauthplayground'
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.oauth2Client.setCredentials({
|
||||||
|
refresh_token: process.env.GMAIL_REFRESH_TOKEN
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendMail(to: string, subject: string, html: string) {
|
||||||
|
const accessToken = await this.oauth2Client.getAccessToken();
|
||||||
|
console.log(accessToken);
|
||||||
|
const options: SMTPTransport.Options = {
|
||||||
|
host: "smtp.gmail.com",
|
||||||
|
port: 465,
|
||||||
|
secure: true,
|
||||||
|
auth: {
|
||||||
|
type: "OAuth2",
|
||||||
|
user: process.env.GMAIL_USER,
|
||||||
|
clientId: process.env.GMAIL_CLIENT_ID,
|
||||||
|
clientSecret: process.env.GMAIL_CLIENT_SECRET,
|
||||||
|
refreshToken: process.env.GMAIL_REFRESH_TOKEN,
|
||||||
|
accessToken: accessToken?.token || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport(options);
|
||||||
|
|
||||||
|
return transporter.sendMail({
|
||||||
|
from: `Scheduler ${process.env.GMAIL_USER}>`,
|
||||||
|
to,
|
||||||
|
subject,
|
||||||
|
html
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user