-
Notifications
You must be signed in to change notification settings - Fork 0
๐ Redis Email Send #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
7496060
74386e1
72969d8
ecc236b
4391b23
d165832
fa72e44
81dbc82
f307fc3
5e12d71
4121cf6
c3ea46e
f7ec6d2
371a66b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.mycom.socket.auth.config; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| @Component | ||
| @ConfigurationProperties(prefix = "spring.mail") | ||
| public class MailProperties { | ||
| private String host; | ||
| private int port; | ||
| private String protocol; | ||
| private String username; | ||
| private String password; | ||
| private String senderEmail; | ||
| private String senderName; | ||
| private String subject; | ||
| private String bodyTemplate; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,43 +1,37 @@ | ||
| package com.mycom.socket.auth.service; | ||
|
|
||
| import com.mycom.socket.auth.config.MailProperties; | ||
| import com.mycom.socket.auth.dto.response.EmailVerificationResponse; | ||
| import com.mycom.socket.auth.service.data.VerificationData; | ||
| import com.mycom.socket.global.exception.BaseException; | ||
| import com.mycom.socket.global.service.RedisService; | ||
| import jakarta.mail.MessagingException; | ||
| import jakarta.mail.internet.MimeMessage; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.mail.javamail.JavaMailSender; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.util.StringUtils; | ||
|
|
||
| import java.security.SecureRandom; | ||
| import java.util.Map; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class MailService { | ||
|
|
||
| private final JavaMailSender javaMailSender; | ||
| private final RateLimiter rateLimiter; // ์ธ์ฆ ๋ฒํธ ์์ฒญ ์ ํ | ||
|
|
||
| private final Map<String, VerificationData> verificationDataMap = new ConcurrentHashMap<>(); | ||
|
|
||
| @Value("${spring.mail.username}") | ||
| private String senderEmail; | ||
| private final RedisService redisService; | ||
| private final MailProperties mailProperties; | ||
|
|
||
| /** | ||
| * 6์๋ฆฌ ๋์ ์ธ์ฆ๋ฒํธ ์์ฑ | ||
| * SecureRandom ์ฌ์ฉํ์ฌ ๋ณด์์ฑ ํฅ์ | ||
| * @return 100000~999999 ๋ฒ์์ ์ธ์ฆ๋ฒํธ | ||
| * 6์๋ฆฌ ์ธ์ฆ๋ฒํธ ์์ฑ (100000-999999) | ||
| */ | ||
| private String createVerificationCode() { | ||
| // Math.random()์ ์์ธก ๊ฐ๋ฅํ ๋์๋ฅผ ์์ฑํ ์ ์์ด ๋ณด์์ ์ทจ์ฝ | ||
| // SecureRandom์ ์ํธํ์ ์ผ๋ก ์์ ํ ๋์๋ฅผ ์์ฑํ๋ฏ๋ก ์ธ์ฆ๋ฒํธ ์์ฑ์ ๋ ์ ํฉ | ||
| SecureRandom secureRandom = new SecureRandom(); | ||
| return String.format("%06d", secureRandom.nextInt(1000000)); | ||
| return String.format("%06d", new SecureRandom().nextInt(1000000)); | ||
| } | ||
|
|
||
| public boolean isEmailVerified(String email) { | ||
| return redisService.isEmailVerified(email); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -48,14 +42,10 @@ private String createVerificationCode() { | |
| public MimeMessage createMail(String email, String verificationCode) { | ||
| MimeMessage message = javaMailSender.createMimeMessage(); | ||
| try { | ||
| message.setFrom(senderEmail); | ||
| message.setFrom(mailProperties.getUsername()); | ||
| message.setRecipients(MimeMessage.RecipientType.TO, email); | ||
| message.setSubject("์ด๋ฉ์ผ ์ธ์ฆ"); | ||
| String body = String.format(""" | ||
| <h3>์์ฒญํ์ ์ธ์ฆ ๋ฒํธ์ ๋๋ค.</h3> | ||
| <h1>%s</h1> | ||
| <h3>๊ฐ์ฌํฉ๋๋ค.</h3> | ||
| """, verificationCode); | ||
| String body = String.format(mailProperties.getBodyTemplate(), verificationCode); | ||
|
||
| message.setText(body, "UTF-8", "html"); | ||
| } catch (MessagingException e) { | ||
| throw new BaseException("์ด๋ฉ์ผ ์์ฑ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: " + e.getMessage(), | ||
|
|
@@ -70,15 +60,18 @@ public MimeMessage createMail(String email, String verificationCode) { | |
| * @return ์์ฑ๋ ์ธ์ฆ๋ฒํธ | ||
| */ | ||
| public EmailVerificationResponse sendMail(String email) { | ||
| rateLimiter.checkRateLimit(email); | ||
| if (redisService.incrementCount(email) > 3) { | ||
| throw new BaseException("๋๋ฌด ๋ง์ ์์ฒญ์ ๋๋ค. 1๋ถ ํ์ ๋ค์ ์๋ํด์ฃผ์ธ์.", | ||
| HttpStatus.TOO_MANY_REQUESTS); | ||
| } | ||
|
Comment on lines
+63
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์์ฒญ ํ์ ์ ํ์ ๋ํ ๋ง๋ฃ ์๊ฐ ์ค์ ํ์
|
||
|
|
||
| String verificationCode = createVerificationCode(); | ||
| verificationDataMap.put(email, new VerificationData(verificationCode)); | ||
| redisService.saveCode(verificationCode); | ||
|
|
||
| MimeMessage message = createMail(email, verificationCode); | ||
| try { | ||
| javaMailSender.send(message); | ||
| return EmailVerificationResponse.of("์ด๋ฉ์ผ ์ ์ก ์ฑ๊ณต"); | ||
| return EmailVerificationResponse.of("์ด๋ฉ์ผ ์ ์ก ์ฑ๊ณต"); // ๋ฉ์์ง ์์ | ||
| } catch (Exception e) { | ||
| throw new BaseException("์ด๋ฉ์ผ ๋ฐ์ก ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: " + e.getMessage(), | ||
| HttpStatus.INTERNAL_SERVER_ERROR); | ||
|
|
@@ -93,30 +86,17 @@ public EmailVerificationResponse sendMail(String email) { | |
| * @return ์ธ์ฆ๋ฒํธ ์ผ์น ์ฌ๋ถ | ||
| */ | ||
| public EmailVerificationResponse verifyCode(String email, String code) { | ||
| validateVerificationCode(code); | ||
|
|
||
| VerificationData data = verificationDataMap.get(email); | ||
| if (data == null || data.isExpired()) { | ||
| throw new BaseException("์ธ์ฆ ์ฝ๋๊ฐ ๋ง๋ฃ๋์๊ฑฐ๋ ์กด์ฌํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST); | ||
| if (!code.matches("\\d{6}")) { | ||
| throw new BaseException("์ ํจํ์ง ์์ ์ธ์ฆ ์ฝ๋ ํ์์ ๋๋ค.", HttpStatus.BAD_REQUEST); | ||
| } | ||
|
|
||
| if (!data.code().equals(code)) { | ||
| try { | ||
| redisService.getCode(code); // ์ธ์ฆ์ฝ๋ ๊ฒ์ฆ | ||
| redisService.saveVerifiedEmail(email); // ์ธ์ฆ๋ ์ด๋ฉ์ผ ์ ์ฅ | ||
| return EmailVerificationResponse.of("์ด๋ฉ์ผ ์ธ์ฆ์ด ์๋ฃ๋์์ต๋๋ค."); | ||
| } catch (Exception e) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ธ์ฆ ์ฝ๋ ๊ฒ์ฆ ๋ก์ง ๋ณด์ ํ์ ์ธ์ฆ ์ฝ๋ ๊ฒ์ฆ ์ -public EmailVerificationResponse verifyCode(String email, String code) {
if (!code.matches("\\d{6}")) {
throw new BaseException("์ ํจํ์ง ์์ ์ธ์ฆ ์ฝ๋ ํ์์
๋๋ค.", HttpStatus.BAD_REQUEST);
}
try {
- redisService.getCode(code); // ์ธ์ฆ์ฝ๋ ๊ฒ์ฆ
+ String savedCode = redisService.getCode(email); // ์ด๋ฉ์ผ๋ก๋ถํฐ ์ฝ๋ ์กฐํ
+ if (!code.equals(savedCode)) {
+ throw new BaseException("์ธ์ฆ ์ฝ๋๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST);
+ }
redisService.saveVerifiedEmail(email); // ์ธ์ฆ๋ ์ด๋ฉ์ผ ์ ์ฅ
return EmailVerificationResponse.of("์ด๋ฉ์ผ ์ธ์ฆ์ด ์๋ฃ๋์์ต๋๋ค.");
} catch (Exception e) {
throw new BaseException("์ธ์ฆ ์ฝ๋๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST);
}
}
|
||
| throw new BaseException("์ธ์ฆ ์ฝ๋๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST); | ||
| } | ||
|
|
||
| verificationDataMap.put(email, data.withVerified()); | ||
| return EmailVerificationResponse.of("์ด๋ฉ์ผ ์ธ์ฆ์ด ์๋ฃ๋์์ต๋๋ค."); | ||
| } | ||
|
|
||
| private void validateVerificationCode(String code) { | ||
| if (!StringUtils.hasText(code) || !code.matches("\\d{6}")) { | ||
| throw new BaseException("์ ํจํ์ง ์์ ์ธ์ฆ ์ฝ๋ ํ์์ ๋๋ค.", HttpStatus.BAD_REQUEST); | ||
| } | ||
| } | ||
|
|
||
| public boolean isEmailVerified(String email) { | ||
| VerificationData data = verificationDataMap.get(email); | ||
| return data != null && !data.isExpired() && data.verified(); | ||
| } | ||
|
|
||
| } | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||||||||||||||||
| package com.mycom.socket.global.config; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||
| import org.springframework.context.annotation.Bean; | ||||||||||||||||||||||||||||
| import org.springframework.context.annotation.Configuration; | ||||||||||||||||||||||||||||
| import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; | ||||||||||||||||||||||||||||
| import org.springframework.data.redis.core.RedisTemplate; | ||||||||||||||||||||||||||||
| import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; | ||||||||||||||||||||||||||||
| import org.springframework.data.redis.serializer.StringRedisSerializer; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @Configuration | ||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||
| @EnableRedisRepositories | ||||||||||||||||||||||||||||
| public class RedisConfig { | ||||||||||||||||||||||||||||
| private final RedisProperties redisProperties; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @Bean | ||||||||||||||||||||||||||||
| public LettuceConnectionFactory redisConnectionFactory() { | ||||||||||||||||||||||||||||
| return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort()); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion Redis ์ฐ๊ฒฐ ์ค์ ๊ฐ์ ํ์ ์ฐ๊ฒฐ ์๊ฐ ์ด๊ณผ ๋ฐ ํ ์ค์ ์ด ๋๋ฝ๋์ด ์์ต๋๋ค. ํ๋ก๋์ ํ๊ฒฝ์์๋ ๋ค์ ์ค์ ๋ค์ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค:
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
- return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
+ LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
+ .commandTimeout(Duration.ofSeconds(1))
+ .build();
+ RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration(
+ redisProperties.getHost(),
+ redisProperties.getPort()
+ );
+ return new LettuceConnectionFactory(serverConfig, clientConfig);
}๐ Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @Bean | ||||||||||||||||||||||||||||
| public RedisTemplate<?, ?> redisTemplate() { | ||||||||||||||||||||||||||||
| RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); | ||||||||||||||||||||||||||||
| redisTemplate.setConnectionFactory(redisConnectionFactory()); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| redisTemplate.setKeySerializer(new StringRedisSerializer()); | ||||||||||||||||||||||||||||
| redisTemplate.setValueSerializer(new StringRedisSerializer()); | ||||||||||||||||||||||||||||
| return redisTemplate; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.mycom.socket.global.config; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| @Component | ||
| @ConfigurationProperties(prefix = "spring.data.redis") | ||
| public class RedisProperties { | ||
|
|
||
| private String host; | ||
| private int port; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,87 @@ | ||||||||||||||||||||||||||||||
| package com.mycom.socket.global.service; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import com.mycom.socket.global.exception.BaseException; | ||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||
| import org.springframework.data.redis.core.RedisTemplate; | ||||||||||||||||||||||||||||||
| import org.springframework.http.HttpStatus; | ||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import java.time.Duration; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| @Service | ||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||
| public class RedisService { | ||||||||||||||||||||||||||||||
| private final RedisTemplate<String, Object> redisTemplate; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||
| * Redis Key Prefix ์์ | ||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||
| private static final String VERIFIED_EMAIL_PREFIX = "verified:email:"; | ||||||||||||||||||||||||||||||
| private static final String RATE_LIMIT_PREFIX = "rate-limit:"; | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion Redis ํค ์ ๋์ฌ ์ฒด๊ณ ๊ฐ์ ํ์ ์ธ์ฆ ์ฝ๋์ ๋ํ ํค ์ ๋์ฌ๊ฐ ๋๋ฝ๋์ด ์์ต๋๋ค. ํค ์ถฉ๋์ ๋ฐฉ์งํ๊ธฐ ์ํด ๋ชจ๋ ํค์ ์ ๋์ฌ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค. private static final String VERIFIED_EMAIL_PREFIX = "verified:email:";
private static final String RATE_LIMIT_PREFIX = "rate-limit:";
+private static final String VERIFICATION_CODE_PREFIX = "verification:code:";๐ Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||
| * Redis TTL ์์ | ||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||
| private static final Duration VERIFICATION_TTL = Duration.ofMinutes(3); // ์ธ์ฆ๋ฒํธ ์ ํจ์๊ฐ | ||||||||||||||||||||||||||||||
| private static final Duration VERIFIED_EMAIL_TTL = Duration.ofMinutes(30); // ์ธ์ฆ๋ ์ด๋ฉ์ผ ์ ํจ์๊ฐ | ||||||||||||||||||||||||||||||
| private static final Duration RATE_LIMIT_TTL = Duration.ofMinutes(1); // ์์ฒญ ์ ํ ์๊ฐ | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||
| * ์ธ์ฆ๋ฒํธ๋ฅผ Redis์ ์ ์ฅ | ||||||||||||||||||||||||||||||
| * Key์ Value๋ก ๋์ผํ ์ธ์ฆ๋ฒํธ๋ฅผ ์ฌ์ฉ | ||||||||||||||||||||||||||||||
| * 3๋ถ ํ ์๋ ์ญ์ | ||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||
| public void saveCode(String code) { | ||||||||||||||||||||||||||||||
| redisTemplate.opsForValue().set(code, code, VERIFICATION_TTL); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||
| * Redis์์ ์ธ์ฆ๋ฒํธ ์กฐํ | ||||||||||||||||||||||||||||||
| * ์ธ์ฆ๋ฒํธ๊ฐ ์กด์ฌํ์ง ์๊ฑฐ๋ ๋ง๋ฃ๋ ๊ฒฝ์ฐ ์์ธ ๋ฐ์ | ||||||||||||||||||||||||||||||
| * @throws BaseException ์ธ์ฆ๋ฒํธ๊ฐ ๋ง๋ฃ๋์๊ฑฐ๋ ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ | ||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||
| public String getCode(String code) { | ||||||||||||||||||||||||||||||
| Object savedCode = redisTemplate.opsForValue().get(code); | ||||||||||||||||||||||||||||||
| if (savedCode == null) { | ||||||||||||||||||||||||||||||
| throw new BaseException("์ธ์ฆ ์ฝ๋๊ฐ ๋ง๋ฃ๋์๊ฑฐ๋ ์กด์ฌํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| return savedCode.toString(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| public String getCode(String code) { | |
| Object savedCode = redisTemplate.opsForValue().get(code); | |
| if (savedCode == null) { | |
| throw new BaseException("์ธ์ฆ ์ฝ๋๊ฐ ๋ง๋ฃ๋์๊ฑฐ๋ ์กด์ฌํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST); | |
| } | |
| return savedCode.toString(); | |
| } | |
| public String getCode(String email) { | |
| Object savedCode = redisTemplate.opsForValue().get(VERIFICATION_CODE_PREFIX + email); | |
| if (savedCode == null) { | |
| throw new BaseException("์ธ์ฆ ์ฝ๋๊ฐ ๋ง๋ฃ๋์๊ฑฐ๋ ์กด์ฌํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST); | |
| } | |
| return savedCode.toString(); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ ๏ธ Refactor suggestion
๋์์ฑ ๋ฌธ์ ํด๊ฒฐ์ ์ํ ์์์ ์ฐ์ฐ ์ฌ์ฉ ๊ถ์ฅ
incrementCount ๋ฉ์๋์์ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ์ ๊ทผํ ๊ฒฝ์ฐ, race condition์ด ๋ฐ์ํ์ฌ ์์ฒญ ํ์ ์ ํ ๋ก์ง์ด ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ์ง ์์ ์ ์์ต๋๋ค. Redis์ ์์์ ๋ช
๋ น์ด ๋๋ Lua ์คํฌ๋ฆฝํธ๋ฅผ ํ์ฉํ์ฌ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ก Codebase verification
API ๋ฌธ์ํ๊ฐ ํ์ํฉ๋๋ค
์๋ํฌ์ธํธ ๋ณ๊ฒฝ์ฌํญ(
/verify-email)์ด ๋ฌธ์ํ๋์ด ์์ง ์์ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค. ๋ค์ ์ฌํญ๋ค์ ์ถ๊ฐํด์ฃผ์๊ธฐ ๋ฐ๋๋๋ค:๐ Analysis chain
์๋ํฌ์ธํธ ์ด๋ฆ์ด ๊ฐ์ ๋์์ต๋๋ค!
์๋ก์ด ์๋ํฌ์ธํธ ์ด๋ฆ
/verify-email์ด ๊ธฐ๋ฅ์ ๋ ๋ช ํํ๊ฒ ์ค๋ช ํฉ๋๋ค.API ๋ฌธ์๊ฐ ์ ๋ฐ์ดํธ๋์๋์ง ํ์ธํ๊ธฐ ์ํด ๋ค์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ์ธ์:
๐ Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 63
Script:
Length of output: 407