Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
f0cbc37
feat: Font Validation
SiwonHae May 16, 2025
0bb4eec
feat: Member Validation
SiwonHae May 16, 2025
9bbdc55
Merge pull request #45 from team-fontory/feature/validation
SiwonHae May 16, 2025
42c464e
feat: 폰트 영어 이름 추가
SiwonHae May 16, 2025
9ef074d
feat: 폰트 수정 기능 삭제
SiwonHae May 16, 2025
ab558e5
feat: 폰트 이름 정규 표현식 검증 추가
SiwonHae May 16, 2025
4e74a78
feat: Validation Exception Handler
SiwonHae May 16, 2025
9b7f610
chore: Spring Validation
SiwonHae May 16, 2025
823d8b2
feat: 폰트 영어 이름도 비속어 검증하도록 추가
SiwonHae May 16, 2025
541f970
refactor: 폰트 이름 정규표현식에 숫자 허용
SiwonHae May 16, 2025
560fff1
test: 영어 폰트 이름 추가 테스트 반영
SiwonHae May 16, 2025
e444c37
fix bug empty files sent when onboarding & update memebr
tape4 May 16, 2025
8af6375
Merge remote-tracking branch 'origin/develop' into fix/regiseter
tape4 May 16, 2025
7421c46
Merge pull request #47 from team-fontory/fix/regiseter
tape4 May 16, 2025
11da0c6
fix: /member/me with onboarding user return 401 Unauthorized
tape4 May 16, 2025
b536126
Merge pull request #48 from team-fontory/fix/auth
tape4 May 16, 2025
484cb4c
Merge branch 'develop' of https://github.com/font-king/FONTory_BE int…
SiwonHae May 19, 2025
26b86e4
feat: 메세지 큐에 폰트 영어 이름 전달
SiwonHae May 19, 2025
1369941
Merge pull request #46 from team-fontory/feature/font-eng-name
tape4 May 19, 2025
522b692
refactor: remove terms properties in member entity
tape4 May 19, 2025
0271631
Merge pull request #50 from team-fontory/refactor/member
tape4 May 19, 2025
c30f862
hotfix: fix error when /member/me for test token
tape4 May 19, 2025
ca93f48
change signp url
tape4 May 25, 2025
c552c60
/member/me return 404 when miss auth
tape4 May 25, 2025
c078e50
Merge pull request #51 from team-fontory/hotfix
tape4 May 25, 2025
4dacd9e
refactor: 폰트 이름 중복 검사 HTTP METHOD 변경
SiwonHae May 26, 2025
f672de3
Merge pull request #52 from team-fontory/refactor/font-verify-method
SiwonHae May 26, 2025
58201bf
feat: 이벤트 기반 알림 로직 도입 및 통합 테스트 추가
tape4 Jun 3, 2025
965c382
Merge pull request #53 from team-fontory/feat/smsEventProducer
tape4 Jun 3, 2025
0becd8a
chore: Rename S3 event listener phase from BEFORE_COMMIT to AFTER_COMMIT
tape4 Jul 6, 2025
57b713f
fix: Add try–catch around S3 copy/delete in afterCommit listener
tape4 Jul 6, 2025
3989d23
fix: Wrap sendToDiscord in try–catch to log notification failures
tape4 Jul 6, 2025
3979c13
Merge pull request #54 from team-fontory/feature/error-handling-s3-di…
tape4 Jul 6, 2025
e5c5b33
feat: add asynchronous retryable S3 promotion with DLQ support
tape4 Jul 7, 2025
a924282
chore: Remove profile image upload feature and related artifacts
tape4 Jul 8, 2025
e0c1e42
Merge pull request #55 from team-fontory/chore/Remove-profile-image-u…
tape4 Jul 8, 2025
c3f6c48
chore: Add cors url
SiwonHae Jul 12, 2025
91f10fc
Merge pull request #56 from team-fontory/chore/cors
SiwonHae Jul 12, 2025
8e23de6
fix: remove profile image logics when login
tape4 Sep 14, 2025
99ad46a
Merge pull request #57 from team-fontory/fix/login
tape4 Sep 14, 2025
17633d5
Add logs on oAuth2 login logics
tape4 Oct 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
// Spring Boot Starters:
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'

// OAuth2
implementation 'org.springframework.session:spring-session-data-redis'
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ services:
container_name: fontory-mariadb
environment:
MYSQL_ROOT_PASSWORD: rootPW
MYSQL_DATRABASE: FONTORY
MYSQL_DATABASE: FONTORY
MYSQL_USER: fontory
MYSQL_PASSWORD: fontoryPW
ports:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableAsync;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@EnableAsync
@EnableRetry
@EnableJpaAuditing
@SpringBootApplication
@EnableConfigurationProperties({JwtProperties.class, MemberDefaults.class})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
Expand All @@ -13,6 +14,7 @@
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
@RequiredArgsConstructor
public class CustomOauth2FailureHandler implements AuthenticationFailureHandler {
Expand All @@ -22,8 +24,18 @@ public class CustomOauth2FailureHandler implements AuthenticationFailureHandler
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
log.warn("OAuth2 authentication failed: errorMessage={}, exceptionType={}",
exception.getMessage(), exception.getClass().getSimpleName());

String requestUrl = request.getRequestURL().toString();
String queryString = request.getQueryString();
log.debug("Failed OAuth2 request details: url={}, queryString={}", requestUrl, queryString);

Map<String, Object> attributes = new HashMap<>();
attributes.put("message", "error occurred during authentication");

log.info("Sending OAuth2 authentication failure response: status={}", HttpServletResponse.SC_UNAUTHORIZED);

response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(objectMapper.writeValueAsString(attributes));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.fontory.fontorybe.authentication.application.dto.ResponseCookies;
import org.fontory.fontorybe.authentication.application.AuthService;
import org.fontory.fontorybe.authentication.application.port.CookieUtils;
Expand All @@ -21,6 +22,7 @@
import java.io.IOException;
import java.util.Objects;

@Slf4j
@Component
@RequiredArgsConstructor
public class CustomOauth2SuccessHandler implements AuthenticationSuccessHandler {
Expand All @@ -37,19 +39,39 @@ public class CustomOauth2SuccessHandler implements AuthenticationSuccessHandler
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException {
log.info("OAuth2 authentication success handler triggered");

OAuth2User authUser = (OAuth2User) authentication.getPrincipal();
Provide provide = authUser.getAttribute("provide");
Objects.requireNonNull(provide, "OAuth2User must have 'provide' attribute");

log.info("Processing successful OAuth2 login: provideId={}, provider={}, email={}",
provide.getId(), provide.getProvider(), provide.getEmail());

Member member = memberOnboardService.fetchOrCreateMember(provide);
log.info("Member fetched/created: memberId={}, status={}",
member.getId(), member.getStatus());

ResponseCookies cookies = authService.issueAuthCookies(member);
log.debug("Auth cookies issued for member: memberId={}", member.getId());

cookieUtils.addCookies(response, cookies);
log.debug("Auth cookies added to response: memberId={}", member.getId());

redirectStrategy.sendRedirect(request, response, buildRedirectUrl(member));
String redirectUrl = buildRedirectUrl(member);
log.info("Redirecting user after successful OAuth2 login: memberId={}, status={}, redirectUrl={}",
member.getId(), member.getStatus(), redirectUrl);

redirectStrategy.sendRedirect(request, response, redirectUrl);
}

private String buildRedirectUrl(Member member) {
String path = (member.getStatus() == MemberStatus.ONBOARDING) ? signUpPath : authPath;
return baseUrl + path;
String redirectUrl = baseUrl + path;

log.debug("Building redirect URL: memberStatus={}, path={}, fullUrl={}",
member.getStatus(), path, redirectUrl);

return redirectUrl;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.fontory.fontorybe.authentication.adapter.inbound;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.fontory.fontorybe.authentication.domain.Auth2UserInfo;
import org.fontory.fontorybe.provide.domain.Provide;
import org.fontory.fontorybe.provide.infrastructure.entity.Provider;
Expand All @@ -22,6 +23,7 @@

import static org.fontory.fontorybe.authentication.domain.Auth2UserInfo.getOAuth2UserInfo;

@Slf4j
@Service
@RequiredArgsConstructor
public class CustomOauth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
Expand All @@ -30,16 +32,26 @@ public class CustomOauth2UserService implements OAuth2UserService<OAuth2UserRequ
@Override
@Transactional
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
String registrationId = userRequest.getClientRegistration().getRegistrationId();
log.info("OAuth2 login attempt: provider={}", registrationId);

OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
log.debug("OAuth2 user loaded from provider: provider={}", registrationId);

String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
Provider provider = Provider.from(userRequest.getClientRegistration().getRegistrationId());
Map<String, Object> attributes = new HashMap<>(oAuth2User.getAttributes());
Auth2UserInfo oAuth2UserInfo = getOAuth2UserInfo(provider, attributes, userNameAttributeName);

log.info("OAuth2 user info extracted: provider={}, userIdentifier={}, email={}",
provider, oAuth2UserInfo.getUserIdentifier(), oAuth2UserInfo.getEmail());

Provide provide = getProvide(oAuth2UserInfo);
attributes.put("provide", provide);

log.info("OAuth2 authentication successful: provider={}, provideId={}",
provider, provide.getId());

return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")),
Expand All @@ -51,17 +63,28 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic
private Provide getProvide(Auth2UserInfo oAuth2UserInfo) {
String userIdentifier = oAuth2UserInfo.getUserIdentifier();
Provider provider = oAuth2UserInfo.getProvider();

log.debug("Looking up provide info: userIdentifier={}, provider={}", userIdentifier, provider);

Optional<Provide> oAuthInfo = provideRepository.findByOAuthInfo(userIdentifier, provider);

if (oAuthInfo.isEmpty() || oAuthInfo.get().getId() == null) {
log.info("Creating new provide entry: userIdentifier={}, provider={}, email={}",
userIdentifier, provider, oAuth2UserInfo.getEmail());

Provide provide = Provide.builder()
.providedId(userIdentifier)
.email(oAuth2UserInfo.getEmail())
.provider(provider)
.build();
return provideRepository.save(provide);
Provide savedProvide = provideRepository.save(provide);

log.info("New provide created: provideId={}, userIdentifier={}, provider={}",
savedProvide.getId(), userIdentifier, provider);
return savedProvide;
} else {
log.info("Existing provide found: provideId={}, userIdentifier={}, provider={}",
oAuthInfo.get().getId(), userIdentifier, provider);
return oAuthInfo.get();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.SecretKey;
import java.util.Date;

@Slf4j
@Component
public class JwtTokenProviderImpl implements JwtTokenProvider {

Expand All @@ -36,6 +38,8 @@ private SecretKey getSigningKey(String key) {

public JwtTokenProviderImpl(
JwtProperties props) {
log.info("Initializing JWT token provider");

this.accessSecretKey = getSigningKey(props.getAccessSecretKey());
this.refreshSecretKey = getSigningKey(props.getRefreshSecretKey());
this.provideSecretKey = getSigningKey(props.getProvideSecretKey());
Expand All @@ -46,75 +50,119 @@ public JwtTokenProviderImpl(
this.provideJwtParser = Jwts.parserBuilder().setSigningKey(provideSecretKey).build();
this.fontCreateJwtParser = Jwts.parserBuilder().setSigningKey(fontCreateSecretKey).build();
this.props = props;

log.debug("JWT token provider initialized with token validities - access: {}ms, refresh: {}ms",
props.getAccessTokenValidityMs(), props.getRefreshTokenValidityMs());
}

public String generateTemporalProvideToken(String id) {
log.debug("Generating temporal provide token for id: {}", id);

Date now = new Date();
Date expiryDate = new Date(now.getTime() + props.getTempTokenValidityMs());
return Jwts.builder()
String token = Jwts.builder()
.setSubject(String.valueOf(id))
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(this.provideSecretKey)
.compact();

log.info("Temporal provide token generated: id={}, expiresAt={}", id, expiryDate);
return token;
}

public Long getProvideId(String token) {
log.debug("Extracting provide ID from token");

Claims claims = provideJwtParser
.parseClaimsJws(token)
.getBody();
return Long.valueOf(claims.getSubject());
Long provideId = Long.valueOf(claims.getSubject());

log.debug("Provide ID extracted: {}", provideId);
return provideId;
}

public String generateAccessToken(UserPrincipal user) {
log.debug("Generating access token for user: {}", user.getId());

Date now = new Date();
Date expiryDate = new Date(now.getTime() + props.getAccessTokenValidityMs());
return Jwts.builder()
String token = Jwts.builder()
.setSubject(String.valueOf(user.getId()))
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(this.accessSecretKey)
.compact();

log.info("Access token generated: userId={}, expiresAt={}", user.getId(), expiryDate);
return token;
}

public String generateRefreshToken(UserPrincipal user) {
log.debug("Generating refresh token for user: {}", user.getId());

Date now = new Date();
Date expiryDate = new Date(now.getTime() + props.getRefreshTokenValidityMs());
return Jwts.builder()
String token = Jwts.builder()
.setSubject(String.valueOf(user.getId()))
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(refreshSecretKey)
.compact();

log.info("Refresh token generated: userId={}, expiresAt={}", user.getId(), expiryDate);
return token;
}

public Long getMemberIdFromAccessToken(String token) {
log.debug("Extracting member ID from access token");

Claims claims = accessJwtParser
.parseClaimsJws(token)
.getBody();
return Long.valueOf(claims.getSubject());
Long memberId = Long.valueOf(claims.getSubject());

log.debug("Member ID extracted from access token: {}", memberId);
return memberId;
}

public Long getMemberIdFromRefreshToken(String token) {
log.debug("Extracting member ID from refresh token");

Claims claims = refreshJwtParser
.parseClaimsJws(token)
.getBody();
return Long.valueOf(claims.getSubject());
Long memberId = Long.valueOf(claims.getSubject());

log.debug("Member ID extracted from refresh token: {}", memberId);
return memberId;
}

public Authentication getAuthenticationFromAccessToken(String token) {
log.debug("Creating authentication from access token");

Claims claims = accessJwtParser
.parseClaimsJws(token)
.getBody();
Long id = Long.valueOf(claims.getSubject());
UserPrincipal principal = new UserPrincipal(id);
return new UsernamePasswordAuthenticationToken(principal, token, principal.getAuthorities());
Authentication auth = new UsernamePasswordAuthenticationToken(principal, token, principal.getAuthorities());

log.info("Authentication created from access token: userId={}, authorities={}",
id, principal.getAuthorities());
return auth;
}

public String getFontCreateServer(String token) {
log.debug("Validating font create server token");

Claims claims = fontCreateJwtParser
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
String subject = claims.getSubject();

log.debug("Font create server token validated: subject={}", subject);
return subject;
}
}
Loading
Loading