Skip to content

Commit

Permalink
SMTP를 통한 이메일 인증 구현 (#133)
Browse files Browse the repository at this point in the history
Co-authored-by: jagaldol <[email protected]>
  • Loading branch information
sososo0 and jagaldol authored Nov 9, 2023
1 parent b7b2f02 commit 37faf87
Show file tree
Hide file tree
Showing 21 changed files with 154 additions and 41 deletions.
13 changes: 13 additions & 0 deletions flask/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3.8-alpine

COPY . /app

WORKDIR /app

RUN pip install Flask-Mail

RUN pip install flask

RUN chmod +x /app/app.py

CMD ["python3", "app.py"]
42 changes: 42 additions & 0 deletions flask/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from flask import Flask, request, Response
import smtplib
from email.mime.text import MIMEText

app = Flask(__name__)

# HTTP POST 요청을 처리하는 엔드포인트
@app.route('/email', methods=['POST'])
def sendEmailEndpoint():

try:
jsonRequest = request.get_json()

subject = str(jsonRequest.get('subject')[0])
text = str(jsonRequest.get('text')[0])
email = str(jsonRequest.get('email')[0])
username = str(jsonRequest.get('username')[0])
password = str(jsonRequest.get('password')[0])

smtp = smtplib.SMTP('smtp.gmail.com', 587)
smtp.ehlo()
smtp.starttls()
smtp.login(username, password)

msg = MIMEText(text, "html")
msg['Subject'] = subject

smtp.sendmail(username, email, msg.as_string())
smtp.quit()

response = Response("Email sent successfully", status=200)

return response

except Exception as e:
error_message = str(e)
response = Response("Failed to send email: " + error_message, status=500)

return response

if __name__ == '__main__':
app.run('0.0.0.0', port=5000, debug=True)
7 changes: 7 additions & 0 deletions flask/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

# Docker 이미지 빌드
docker build -t flask-app:v1 .

# Docker 컨테이너 실행
docker run -d -p 80:5000 flask-app:v1
9 changes: 7 additions & 2 deletions k8s/backend.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ spec:
containers:
- name: backend
# 여러분의 backend image 주소를 입력해주세요. -> 빌드 후 빌드 이미지 경로 새로 넣기
image: krmp-d2hub-idock.9rum.cc/dev-test/repo_1414cba4227e
image: krmp-d2hub-idock.9rum.cc/dev-test/repo_64cf4065f88f
env:
- name: TOKEN_SECRET
valueFrom:
Expand Down Expand Up @@ -58,7 +58,7 @@ spec:
name: secrets
key: MYSQL_ROOT_PASSWORD
- name: MYSQL_USERNAME
valueFrom:
valueFrom:
secretKeyRef:
name: secrets
key: MYSQL_USERNAME
Expand All @@ -77,6 +77,11 @@ spec:
secretKeyRef:
name: secrets
key: API_SERVER_URL
- name: FLASK_MAIL_SERVER
valueFrom:
secretKeyRef:
name: secrets
key: FLASK_MAIL_SERVER
ports:
- containerPort: 8080
resources:
Expand Down
11 changes: 7 additions & 4 deletions k8s/configs/default.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
server {
listen 80;
server_tokens off; #nginx 버전 정보 숨기기
listen 80;
server_tokens off; #nginx 버전 정보 숨기기

error_log /tmp/error.log;
access_log /tmp/access.log main;

location / {
proxy_pass http://frontend.default.svc.cluster.local:3000;
Expand All @@ -12,5 +15,5 @@ server {
proxy_connect_timeout 60s; # 연결 타임아웃 설정
proxy_send_timeout 60s; # 소켓 타임아웃 설정
proxy_read_timeout 300s; # 프록시 서버로부터 응답을 읽어들이는 데 허용되는 시간
}
}
}
}
4 changes: 3 additions & 1 deletion k8s/create-k8s-secret.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ kubectl create secret generic $SECRET_NAME \
--from-literal=MYSQL_PASSWORD="$MYSQL_PASSWORD" \
--from-literal=DOMAIN="$DOMAIN" \
--from-literal=GOOGLE_MAP_API_KEY="$GOOGLE_MAP_API_KEY" \
--from-literal=API_SERVER_URL="$API_SERVER_URL"
--from-literal=API_SERVER_URL="$API_SERVER_URL" \
--from-literal=NEXT_PUBLIC_KAKAOMAP_APPKEY="$NEXT_PUBLIC_KAKAOMAP_APPKEY" \
--from-literal=FLASK_MAIL_SERVER="$FLASK_MAIL_SERVER"


echo "Kubernetes secret $SECRET_NAME has been created or updated with the environment variables."
2 changes: 1 addition & 1 deletion k8s/frontend.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ spec:
containers:
- name: frontend
# 여러분의 image 주소를 입력해주세요.
image: krmp-d2hub-idock.9rum.cc/dev-test/repo_eb6339562f9c
image: krmp-d2hub-idock.9rum.cc/dev-test/repo_23d8d3138910
---
apiVersion: v1
kind: Service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ public AmazonS3 amazonS3ClientForDeploy() {
clientConfiguration.setProxyPort(proxyPort);
clientConfiguration.setProxyProtocol(Protocol.HTTP);


AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(endpoint, null);

return AmazonS3ClientBuilder
Expand All @@ -70,4 +69,4 @@ public AmazonS3 amazonS3ClientForDeploy() {
.withRegion(region)
.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;

Expand All @@ -20,6 +21,7 @@ public class MailConfig {
private String password;

@Bean
@Profile({"local", "product", "test", "deploy"})
public JavaMailSender javaMailService() {
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bungaebowling.server._core.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
Expand All @@ -12,11 +13,17 @@
@Configuration
public class RestTemplateConfig {

@Value("krmp-proxy.9rum.cc")
private String proxyHost;

@Value("3128")
private int proxyPort;

@Bean
@Profile("deploy")
public RestTemplate restTemplateForDeploy() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("krmp-proxy.9rum.cc", 3128));
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
requestFactory.setProxy(proxy);

return new RestTemplate(requestFactory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
import com.bungaebowling.server._core.errors.exception.CustomException;
import com.bungaebowling.server._core.errors.exception.ErrorCode;
import com.bungaebowling.server._core.utils.ApiUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CustomException.class)
Expand All @@ -19,7 +17,6 @@ public ResponseEntity<?> customError(CustomException e) {

@ExceptionHandler(Exception.class)
public ResponseEntity<?> unknownServerError(Exception e) {
log.error("unknown 에러 발생", e);
var status = HttpStatus.INTERNAL_SERVER_ERROR;
var response = ApiUtils.error(e.getMessage(), ErrorCode.UNKNOWN_SERVER_ERROR);
return ResponseEntity.status(status).body(response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,4 @@ private String fileWhiteList(String fileName) {

return caseInSensitiveFileName;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,4 @@ public ResponseEntity<?> deleteScore(

return ResponseEntity.ok(ApiUtils.success());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,4 @@ public int findMinScore(List<Score> scores) {
.min()
.orElse(0);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
@RequestMapping("/api")
public class UserController {

final private UserService userService;
private final UserService userService;

@PostMapping("/join")
public ResponseEntity<?> join(@RequestBody @Valid UserRequest.JoinDto requestDto, Errors errors) throws URISyntaxException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public User createUser(District district, String encodedPassword) {
return User.builder()
.name(name)
.email(email)
.role(Role.ROLE_USER)
.password(encodedPassword)
.district(district)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,26 @@
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
Expand All @@ -57,12 +65,21 @@ public class UserService {
private final PasswordEncoder passwordEncoder;

private final JavaMailSender javaMailSender;
private final RestTemplate restTemplate;

private final AwsS3Service awsS3Service;
private final ScoreService scoreService;

private final Environment environment;

@Value("${bungaebowling.domain}")
private String domain;
@Value("${mail.server}")
private String mailServer;
@Value("${mail.username}")
private String username;
@Value("${mail.password}")
private String password;

@Transactional
public UserResponse.JoinDto join(UserRequest.JoinDto requestDto) {
Expand Down Expand Up @@ -147,6 +164,35 @@ public void sendVerificationMail(Long userId) {
String subject = "[번개볼링] 이메일 인증을 완료해주세요.";
String text = "<a href='" + domain + "/email-verification?token=" + token + "'>링크</a>를 클릭하여 인증을 완료해주세요!";

if (Arrays.asList(environment.getActiveProfiles()).contains("deploy")) {
sendMailToMailServer(user, subject, text);
} else {
sendMail(user, subject, text);
}
}

private void sendMailToMailServer(User user, String subject, String text) {
try {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);

MultiValueMap<String, String> requests = new LinkedMultiValueMap<>();
requests.add("subject", subject);
requests.add("text", text);
requests.add("email", user.getEmail());
requests.add("username", username);
requests.add("password", password);

HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requests, httpHeaders);
String requestURL = "http://" + mailServer + "/email";

restTemplate.postForEntity(requestURL, request, String.class);
} catch (Exception e) {
throw new CustomException(ErrorCode.EMAIL_SEND_LIMIT_EXCEEDED);
}
}

private void sendMail(User user, String subject, String text) {
try {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "utf-8");
Expand Down Expand Up @@ -271,15 +317,10 @@ public void sendVerificationMailForPasswordReset(UserRequest.SendVerificationMai
String subject = "[번개볼링] 비밀번호 초기화 및 임시 비밀번호 발급을 위한 이메일 인증을 완료해주세요.";
String text = "<a href='" + domain + "/password/email-verification?token=" + token + "'>링크</a>를 클릭하여 인증을 완료해주세요!";

try {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "utf-8");
helper.setTo(user.getEmail());
helper.setSubject(subject);
helper.setText(text, true);
javaMailSender.send(mimeMessage);
} catch (Exception e) {
throw new CustomException(ErrorCode.EMAIL_SEND_LIMIT_EXCEEDED);
if (Arrays.asList(environment.getActiveProfiles()).contains("deploy")) {
sendMailToMailServer(user, subject, text);
} else {
sendMail(user, subject, text);
}
}

Expand All @@ -293,17 +334,11 @@ public void confirmEmailAndSendTempPassword(UserRequest.ConfirmEmailAndSendTempP
String subject = "[번개볼링] 임시 비밀번호";
String text = "임시 비밀번호는 " + tempPassword + " 입니다. <br>*비밀번호를 변경해주세요." + "<br>*기존의 비밀번호는 사용할 수 없습니다.";

try {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "utf-8");
helper.setTo(user.getEmail());
helper.setSubject(subject);
helper.setText(text, true);
javaMailSender.send(mimeMessage);
} catch (Exception e) {
throw new CustomException(ErrorCode.EMAIL_SEND_LIMIT_EXCEEDED);
if (Arrays.asList(environment.getActiveProfiles()).contains("deploy")) {
sendMailToMailServer(user, subject, text);
} else {
sendMail(user, subject, text);
}

}

public UserResponse.GetRecordDto getRecords(Long userId) {
Expand Down Expand Up @@ -356,6 +391,4 @@ public String getRamdomPassword(int length) {
return stringBuilder.toString();

}


}
Loading

0 comments on commit 37faf87

Please sign in to comment.