Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.pinback.api.auth.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.pinback.application.auth.dto.SignUpResponseV3;
import com.pinback.application.auth.dto.TokenResponse;
import com.pinback.application.auth.usecase.AuthUsecase;
import com.pinback.shared.dto.ResponseDto;

import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/v3/auth")
@RequiredArgsConstructor
@Tag(name = "Authentication V3", description = "인증 관리 API V3")
public class AuthControllerV3 {
private final AuthUsecase authUsecase;

@Value("${jwt.refreshExpirationPeriod}")
private long refreshTokenExpirationPeriod;

@PostMapping("/reissue")
public ResponseEntity<ResponseDto<TokenResponse>> reissueAccessToken(
@CookieValue(name = "refreshToken") String refreshToken
) {
SignUpResponseV3 response = authUsecase.getNewToken(refreshToken);

ResponseCookie cookie = ResponseCookie.from("refreshToken", response.refreshToken())
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(refreshTokenExpirationPeriod / 1000)
.sameSite("None")
.build();

return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.body(ResponseDto.ok(TokenResponse.of(response.accessToken())));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public CorsConfigurationSource corsConfigurationSource() {
""
));

configuration.setAllowCredentials(false);
configuration.setAllowCredentials(true);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In Spring Framework CORS configuration, what are the security implications of using setAllowCredentials(true)together withsetAllowedOriginPatterns(List.of("chrome-extension://*"))? Does this allow any Chrome extension origin to make credentialed cross-origin requests?

💡 Result:

Using setAllowCredentials(true) means the server is telling the browser it’s OK for the JS caller to make credentialed cross-origin requests (cookies / HTTP auth / client certs, depending on the client) and read the response, as long as the response includes an explicit Access-Control-Allow-Origin (not *) plus Access-Control-Allow-Credentials: true. Spring notes that the CORS spec forbids * with credentials and provides allowedOriginPatterns as the “dynamic origins” alternative. [2]

setAllowedOriginPatterns(List.of("chrome-extension://*")) matches any Chrome extension origin of the form chrome-extension://<extension-id> (the extension ID acts like the “host” part of the origin). [3] When an allowedOriginPattern matches, Spring sets Access-Control-Allow-Origin to the request’s Origin value (the matched origin), not to * and not to the pattern. [1] That makes it fully effective for credentialed requests.

So yes: for requests that are subject to normal browser CORS enforcement, this configuration effectively allows any Chrome extension ID to make credentialed cross-origin requests to your Spring app and read the responses, as long as the extension uses credentials: "include" (or equivalent) and the browser includes the user’s cookies for your site.

Security implication: you are extending “trusted frontend” status to every installed extension (including potentially malicious ones), which can then exfiltrate user-specific data from your API using the user’s ambient authentication (cookies/session). Prefer whitelisting specific extension IDs (e.g., chrome-extension://<your-id>) rather than chrome-extension://*. [1]


🏁 Script executed:

# First, locate and read the CorsConfig.java file
find . -name "CorsConfig.java" -type f | head -20

Repository: Pinback-Team/pinback-server

Length of output: 134


🏁 Script executed:

# Read the CorsConfig.java file with line numbers
cat -n ./api/src/main/java/com/pinback/api/config/cors/CorsConfig.java

Repository: Pinback-Team/pinback-server

Length of output: 2183


Do not enable credentialed CORS for the wildcard extension origin.

Line 49 enables credentials for the chrome-extension://* pattern defined at line 22, allowing any installed Chrome extension to make authenticated cross-origin requests and read responses with the user's cookies. Split extension traffic into a separate non-credentialed CORS policy, or whitelist only the specific extension IDs that require authenticated access.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/main/java/com/pinback/api/config/cors/CorsConfig.java` at line 49,
The CorsConfig currently calls configuration.setAllowCredentials(true) while
also registering the chrome-extension://* origin pattern; change this so
credentialed CORS is never enabled for a wildcard extension origin: create two
distinct CorsConfiguration entries in CorsConfig — one for chrome-extension://*
(or extension traffic) with setAllowCredentials(false) and the minimal allowed
methods/headers, and a separate credentialed configuration that only lists
explicit allowedOrigins (no wildcards) and keeps setAllowCredentials(true);
update the registerCorsConfiguration calls so chrome-extension://* uses the
non-credentialed CorsConfiguration and only specific extension IDs or trusted
origins are placed in the credentialed CorsConfiguration.


configuration.setExposedHeaders(Arrays.asList(
"Access-Control-Allow-Origin",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletExce
path.startsWith("/api/v2/auth/signup") ||
path.startsWith("/api/v3/auth/signup") ||
path.startsWith("/api/v3/auth/google") ||
path.startsWith("/api/v3/enums/jobs")
path.startsWith("/api/v3/enums/jobs") ||
path.startsWith("/api/v3/auth/reissue")
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
"/api/v2/auth/google",
"/api/v2/auth/signup",
"/api/v3/auth/signup",
"/api/v3/auth/google"
"/api/v3/auth/google",
"/api/v3/auth/reissue"
).permitAll()

.requestMatchers(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.pinback.api.google.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -8,7 +12,7 @@

import com.pinback.api.auth.dto.request.SignUpRequestV3;
import com.pinback.api.google.dto.request.GoogleLoginRequestV3;
import com.pinback.application.auth.dto.SignUpResponse;
import com.pinback.application.auth.dto.SignUpResponseV3;
import com.pinback.application.auth.usecase.AuthUsecase;
import com.pinback.application.google.dto.response.GoogleLoginResponseV3;
import com.pinback.application.google.usecase.GoogleUsecase;
Expand All @@ -27,28 +31,51 @@
public class GoogleLoginControllerV3 {
private final GoogleUsecase googleUsecase;
private final AuthUsecase authUsecase;
@Value("${jwt.refreshExpirationPeriod}")
private long refreshTokenExpirationPeriod;

@Operation(summary = "구글 소셜 로그인 V3", description = "구글 소셜 로그인을 진행하며, 응답에 직무 선택 여부를 포함합니다.")
@PostMapping("/google")
public Mono<ResponseDto<GoogleLoginResponseV3>> googleLogin(
public Mono<ResponseEntity<ResponseDto<GoogleLoginResponseV3>>> googleLogin(
@Valid @RequestBody GoogleLoginRequestV3 request
) {
return googleUsecase.getUserInfoV3(request.toCommand())
.flatMap(googleResponse -> {
return authUsecase.getInfoAndTokenV3(googleResponse.email(), googleResponse.pictureUrl(),
googleResponse.name())
.map(loginResponse -> {
return ResponseDto.ok(loginResponse);
ResponseCookie cookie = ResponseCookie.from("refreshToken", loginResponse.refreshToken())
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(refreshTokenExpirationPeriod / 1000)
.sameSite("None")
.build();

return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.body(ResponseDto.ok(loginResponse));
Comment on lines +47 to +57
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clear the cookie on temp-login responses instead of issuing one.

AuthUsecase.getInfoAndTokenV3() returns GoogleLoginResponseV3.tempLogin(...) for onboarding-incomplete and new users in application/src/main/java/com/pinback/application/auth/usecase/AuthUsecase.java, Line 177-179 and Line 198-199, and tempLogin() sets refreshToken to null in application/src/main/java/com/pinback/application/google/dto/response/GoogleLoginResponseV3.java, Line 18-19. Line 47-57 still writes a refreshToken cookie for that branch, so the non-user path can leave a stale cookie behind or emit a meaningless one.

🔧 Proposed fix
 					.map(loginResponse -> {
-						ResponseCookie cookie = ResponseCookie.from("refreshToken", loginResponse.refreshToken())
-							.httpOnly(true)
-							.secure(true)
-							.path("/")
-							.maxAge(refreshTokenExpirationPeriod / 1000)
-							.sameSite("None")
-							.build();
+						ResponseCookie cookie = loginResponse.refreshToken() == null
+							? ResponseCookie.from("refreshToken", "")
+								.httpOnly(true)
+								.secure(true)
+								.path("/")
+								.maxAge(0)
+								.sameSite("None")
+								.build()
+							: ResponseCookie.from("refreshToken", loginResponse.refreshToken())
+								.httpOnly(true)
+								.secure(true)
+								.path("/")
+								.maxAge(refreshTokenExpirationPeriod / 1000)
+								.sameSite("None")
+								.build();
 
 						return ResponseEntity.ok()
 							.header(HttpHeaders.SET_COOKIE, cookie.toString())
 							.body(ResponseDto.ok(loginResponse));
 					});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ResponseCookie cookie = ResponseCookie.from("refreshToken", loginResponse.refreshToken())
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(refreshTokenExpirationPeriod / 1000)
.sameSite("None")
.build();
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.body(ResponseDto.ok(loginResponse));
.map(loginResponse -> {
ResponseCookie cookie = loginResponse.refreshToken() == null
? ResponseCookie.from("refreshToken", "")
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(0)
.sameSite("None")
.build()
: ResponseCookie.from("refreshToken", loginResponse.refreshToken())
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(refreshTokenExpirationPeriod / 1000)
.sameSite("None")
.build();
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.body(ResponseDto.ok(loginResponse));
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@api/src/main/java/com/pinback/api/google/controller/GoogleLoginControllerV3.java`
around lines 47 - 57, The response currently always sets a refreshToken cookie
in GoogleLoginControllerV3 even when AuthUsecase.getInfoAndTokenV3() returns a
tempLogin (GoogleLoginResponseV3.tempLogin() sets refreshToken to null); change
the controller to check loginResponse.refreshToken() and if null send a clearing
Set-Cookie (e.g., cookie with empty value and maxAge=0/expired) instead of
issuing a normal refresh cookie so onboarding-incomplete/new-user flows do not
leave a stale or meaningless cookie.

});
});
}

@Operation(summary = "신규 회원 온보딩 V3", description = "신규 회원의 기본 정보(직무 포함)를 등록합니다.")
@PatchMapping("/signup")
public ResponseDto<SignUpResponse> signUpV3(
public ResponseEntity<ResponseDto<SignUpResponseV3>> signUpV3(
@Valid @RequestBody SignUpRequestV3 request
) {
SignUpResponse response = authUsecase.signUpV3(request.toCommand());
return ResponseDto.ok(response);
SignUpResponseV3 response = authUsecase.signUpV3(request.toCommand());

ResponseCookie cookie = ResponseCookie.from("refreshToken", response.refreshToken())
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(refreshTokenExpirationPeriod / 1000)
.sameSite("None")
.build();

return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.body(ResponseDto.ok(response));
Comment on lines +77 to +79
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't return SignUpResponseV3 verbatim after setting the cookie.

SignUpResponseV3 includes refreshToken in application/src/main/java/com/pinback/application/auth/dto/SignUpResponseV3.java, Line 1-10, so Line 77-79 still exposes the secret in JSON. AuthControllerV3 already shows the safer contract on Line 44-46 by returning only TokenResponse.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@api/src/main/java/com/pinback/api/google/controller/GoogleLoginControllerV3.java`
around lines 77 - 79, The controller currently returns SignUpResponseV3 (which
contains refreshToken) in the response body after setting the refresh token
cookie; change GoogleLoginControllerV3 to not expose SignUpResponseV3 directly
but instead construct and return a TokenResponse (or a DTO that excludes
refreshToken) in the body while keeping the refresh token only in the HttpOnly
cookie (use the existing cookie variable and map the accessToken and other
non-secret fields from SignUpResponseV3 into TokenResponse); ensure the response
body type and returned object match TokenResponse to mirror AuthControllerV3
behavior.

}
}
1 change: 1 addition & 0 deletions api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ springdoc:
jwt:
secret-key: ${SECRET_KEY}
accessExpirationPeriod: ${EXPIRATION_PERIOD}
refreshExpirationPeriod: ${REFRESH_EXPIRATION_PERIOD}
issuer: ${ISSUER}

fcm: ${FCM_JSON}
Expand Down
2 changes: 2 additions & 0 deletions application/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'

annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private void validateArticleCreation(User user, ArticleCreateCommand command) {
throw new ArticleAlreadyExistException();
}

if (TextUtil.countGraphemeClusters(command.memo()) >= MEMO_LIMIT_LENGTH) {
if (TextUtil.countGraphemeClusters(command.memo()) > MEMO_LIMIT_LENGTH) {
throw new MemoLengthLimitException();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void updateArticle(User user, long articleId, ArticleUpdateCommand comman
}

private void validateMemoLength(String memo) {
if (TextUtil.countGraphemeClusters(memo) >= MEMO_LIMIT_LENGTH) {
if (TextUtil.countGraphemeClusters(memo) > MEMO_LIMIT_LENGTH) {
throw new MemoLengthLimitException();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.pinback.application.auth.dto;

public record SignUpResponseV3(
String accessToken,
String refreshToken
) {
public static SignUpResponseV3 from(String accessToken, String refreshToken) {
return new SignUpResponseV3(accessToken, refreshToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
public record TokenResponse(
String token
) {
public static TokenResponse of(String token) {
return new TokenResponse(token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ public interface JwtProvider {
boolean validateToken(String token);

UUID getUserIdFromToken(String token);

String createRefreshToken(UUID userId);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package com.pinback.application.auth.usecase;

import java.time.LocalTime;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.pinback.application.auth.dto.SignUpCommand;
import com.pinback.application.auth.dto.SignUpCommandV3;
import com.pinback.application.auth.dto.SignUpResponse;
import com.pinback.application.auth.dto.SignUpResponseV3;
import com.pinback.application.auth.dto.TokenResponse;
import com.pinback.application.auth.service.JwtProvider;
import com.pinback.application.common.exception.InvalidTokenException;
import com.pinback.application.config.ProfileImageConfig;
import com.pinback.application.google.dto.response.GoogleLoginResponse;
import com.pinback.application.google.dto.response.GoogleLoginResponseV3;
Expand All @@ -30,6 +35,7 @@
@Service
@RequiredArgsConstructor
public class AuthUsecase {
private static final String REDIS_REFRESH_TOKEN_PREFIX = "RT:";

private final UserValidateServicePort userValidateServicePort;
private final UserSaveServicePort userSaveServicePort;
Expand All @@ -40,6 +46,10 @@ public class AuthUsecase {
private final UserUpdateServicePort userUpdateServicePort;
private final UserOAuthUsecase userOAuthUsecase;
private final ProfileImageConfig profileImageConfig;
private final StringRedisTemplate stringRedisTemplate;

@Value("${jwt.refreshExpirationPeriod}")
private long refreshTokenExpirationPeriod;

@Transactional
public SignUpResponse signUp(SignUpCommand signUpCommand) {
Expand Down Expand Up @@ -123,9 +133,11 @@ public SignUpResponse signUpV2(SignUpCommand signUpCommand) {
}

@Transactional
public SignUpResponse signUpV3(SignUpCommandV3 signUpCommand) {
public SignUpResponseV3 signUpV3(SignUpCommandV3 signUpCommand) {
User user = userGetServicePort.findByEmail(signUpCommand.email());
String accessToken = jwtProvider.createAccessToken(user.getId());
String refreshToken = jwtProvider.createRefreshToken(user.getId());
saveRefreshTokenToRedis(user.getId(), refreshToken);
userUpdateServicePort.updateRemindDefault(user.getId(), signUpCommand.remindDefault());

savePushSubscriptionPort.savePushSubscription(user, signUpCommand.fcmToken());
Expand All @@ -135,7 +147,7 @@ public SignUpResponse signUpV3(SignUpCommandV3 signUpCommand) {
Job job = Job.from(signUpCommand.job());
userUpdateServicePort.updateJob(user.getId(), job);

return SignUpResponse.from(accessToken);
return SignUpResponseV3.from(accessToken, refreshToken);
}

@Transactional
Expand All @@ -149,11 +161,15 @@ public Mono<GoogleLoginResponseV3> getInfoAndTokenV3(String email, String pictur
if (updatedUser.getRemindDefault() != null && updatedUser.getProfileImage() != null) {
log.info("기존 사용자 로그인 성공: User ID {}", updatedUser.getId());

//Access Token 발급
//Access Token & Refresh Token 발급
String accessToken = jwtProvider.createAccessToken(updatedUser.getId());
String refreshToken = jwtProvider.createRefreshToken(updatedUser.getId());

saveRefreshTokenToRedis(updatedUser.getId(), refreshToken);

return Mono.just(GoogleLoginResponseV3.loggedIn(
updatedUser.hasJob(), updatedUser.getId(), updatedUser.getEmail(), accessToken
updatedUser.hasJob(), updatedUser.getId(), updatedUser.getEmail(), accessToken,
refreshToken
));
} else {
log.info("기존 사용자 - 온보딩 미완료 유저 처리: User ID {}", updatedUser.getId());
Expand Down Expand Up @@ -185,6 +201,25 @@ public Mono<GoogleLoginResponseV3> getInfoAndTokenV3(String email, String pictur
}));
}

@Transactional
public SignUpResponseV3 getNewToken(String refreshToken) {
UUID userId = jwtProvider.getUserIdFromToken(refreshToken);

String redisKey = REDIS_REFRESH_TOKEN_PREFIX + userId.toString();
String savedToken = stringRedisTemplate.opsForValue().get(redisKey);
if (savedToken == null || !savedToken.equals(refreshToken)) {
stringRedisTemplate.delete(redisKey);
throw new InvalidTokenException();
}
Comment on lines +204 to +213
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate the JWT type before deleting the Redis token.

jwtProvider.getUserIdFromToken(...) in infrastructure/src/main/java/com/pinback/infrastructure/jwt/JwtProviderImpl.java, Line 60-64 extracts the user ID from any valid JWT. If an access token is sent here, Line 210-212 will reject it and delete the real refresh token, turning a bad request into a forced logout. Only revoke on mismatch after you've confirmed the presented JWT is actually a refresh token.


String newAccessToken = jwtProvider.createAccessToken(userId);
String newRefreshToken = jwtProvider.createRefreshToken(userId);

saveRefreshTokenToRedis(userId, newRefreshToken);

return SignUpResponseV3.from(newAccessToken, newRefreshToken);
}

private Mono<User> applyMissingUserInfo(User existingUser, String pictureUrl, String name) {
// 1. 이름 업데이트
boolean nameUpdated = false;
Expand Down Expand Up @@ -224,4 +259,13 @@ private String matchingProfileImage(LocalTime remindDefault) {
return "IMAGE3";
}
}

private void saveRefreshTokenToRedis(UUID userId, String refreshToken) {
stringRedisTemplate.opsForValue().set(
"RT:" + userId.toString(),
refreshToken,
refreshTokenExpirationPeriod,
java.util.concurrent.TimeUnit.MILLISECONDS
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.pinback.application.common.exception;

import com.pinback.shared.constant.ExceptionCode;
import com.pinback.shared.exception.ApplicationException;

public class InvalidTokenException extends ApplicationException {
public InvalidTokenException() {
super(ExceptionCode.INVALID_TOKEN);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ public record GoogleLoginResponseV3(
boolean hasJob,
UUID userId,
String email,
String accessToken
String accessToken,
String refreshToken
) {
public static GoogleLoginResponseV3 loggedIn(boolean hasJob, UUID userId, String email, String accessToken) {
return new GoogleLoginResponseV3(true, hasJob, userId, email, accessToken);
public static GoogleLoginResponseV3 loggedIn(boolean hasJob, UUID userId, String email, String accessToken,
String refreshToken) {
return new GoogleLoginResponseV3(true, hasJob, userId, email, accessToken, refreshToken);
Comment on lines +10 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep refreshToken off this response record.

GoogleLoginControllerV3.googleLogin() serializes GoogleLoginResponseV3 directly in api/src/main/java/com/pinback/api/google/controller/GoogleLoginControllerV3.java, Line 55-57. Adding refreshToken here makes it readable in the JSON body even though the controller also sends it as an HttpOnly cookie.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application/src/main/java/com/pinback/application/google/dto/response/GoogleLoginResponseV3.java`
around lines 10 - 15, GoogleLoginResponseV3 currently includes a refreshToken
field and the loggedIn(...) factory returns it, causing the refresh token to be
serialized into the JSON response; remove the refreshToken from the record
declaration and update the loggedIn method signature and body to omit the
refreshToken parameter so JSON responses no longer include it (keep sending the
refresh token only via the HttpOnly cookie in GoogleLoginControllerV3). Ensure
all references to GoogleLoginResponseV3.refreshToken and the loggedIn overload
are updated or removed accordingly to avoid compilation errors.

}

public static GoogleLoginResponseV3 tempLogin(UUID userId, String email) {
return new GoogleLoginResponseV3(false, false, userId, email, null);
return new GoogleLoginResponseV3(false, false, userId, email, null, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,12 @@ public ArticleCountInfoV3 findAllCountByCategoryV3(UUID userId, long categoryId)
@Override
public SharedArticles findTopListByJob(Job job) {
List<Article> articles = queryFactory.selectFrom(article)
.join(article.user, user)
.where(user.job.eq(job))
.join(article.user, user).fetchJoin()
.join(article.category, category)
.where(
user.job.eq(job),
category.isPublic.isTrue()
)
.orderBy(article.createdAt.desc())
.limit(10)
.fetch();
Expand Down
Loading