Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import ita.tinybite.domain.user.constant.PlatformType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record GoogleAndAppleSignupRequest(
@NotBlank(message = "idToken์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค")
Expand All @@ -12,7 +13,7 @@ public record GoogleAndAppleSignupRequest(
String nickname,
@NotBlank(message = "์œ„์น˜ ์ •๋ณด ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค")
String location,
@NotBlank(message = "ํ”Œ๋žซํผ์ •๋ณด๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค")
@NotNull(message = "ํ”Œ๋žซํผ์ •๋ณด๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค")
PlatformType platform
) {
}
33 changes: 30 additions & 3 deletions src/main/java/ita/tinybite/domain/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -47,8 +49,8 @@ public class AuthService {
private final JwtDecoder appleJwtDecoder;
private final NicknameGenerator nicknameGenerator;

// @Value("${apple.client-id}")
// private String appleClientId;
@Value("${apple.client-id}")
private String appleClientId;

@Value("${google.android-id}")
private String googleAndroidId;
Expand Down Expand Up @@ -224,10 +226,35 @@ private String getEmailFromIdToken(String idToken, PlatformType platformType, Lo

} catch (GeneralSecurityException | IOException e) {
throw BusinessException.of(AuthErrorCode.GOOGLE_LOGIN_ERROR);
} catch (Exception e) {
throw BusinessException.of(AuthErrorCode.INVALID_TOKEN);
}
}
case APPLE -> {
//TODO Apple ๊ตฌํ˜„ ์˜ˆ์ •
String clientId = appleClientId;

try {
Jwt jwt = appleJwtDecoder.decode(idToken);

if(!"https://appleid.apple.com".equals(jwt.getIssuer().toString())) {
throw BusinessException.of(AuthErrorCode.INVALID_TOKEN);
}

String aud = jwt.getAudience().get(0);
if (!aud.equals(clientId)) {
throw BusinessException.of(AuthErrorCode.INVALID_TOKEN);
}
Comment on lines +243 to +246

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸก Minor

Audience ๋ฆฌ์ŠคํŠธ ๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ ๋ฐฉ์–ด ์ฝ”๋“œ ์ถ”๊ฐ€

jwt.getAudience()๊ฐ€ ๋นˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด IndexOutOfBoundsException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

-String aud = jwt.getAudience().get(0);
-if (!aud.equals(clientId)) {
+if (jwt.getAudience().isEmpty() || !jwt.getAudience().get(0).equals(clientId)) {
     throw BusinessException.of(AuthErrorCode.INVALID_TOKEN);
 }
๐Ÿ“ 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
String aud = jwt.getAudience().get(0);
if (!aud.equals(clientId)) {
throw BusinessException.of(AuthErrorCode.INVALID_TOKEN);
}
if (jwt.getAudience().isEmpty() || !jwt.getAudience().get(0).equals(clientId)) {
throw BusinessException.of(AuthErrorCode.INVALID_TOKEN);
}
๐Ÿค– Prompt for AI Agents
In src/main/java/ita/tinybite/domain/auth/service/AuthService.java around lines
243-246, the code assumes jwt.getAudience() has at least one element and
directly accesses index 0 which can throw IndexOutOfBoundsException; add a
defensive check that jwt.getAudience() is not null and not empty before reading
index 0, and if it is null/empty throw
BusinessException.of(AuthErrorCode.INVALID_TOKEN) (or use the same error path as
other invalid audience cases) so the failure is handled predictably.


Object emailObject = jwt.getClaims().get("email");
if(emailObject == null) {
throw BusinessException.of(AuthErrorCode.NOT_EXISTS_EMAIL);
}
return emailObject.toString();
} catch (JwtException e) {
throw BusinessException.of(AuthErrorCode.INVALID_TOKEN);
} catch (Exception e) {
throw BusinessException.of(AuthErrorCode.APPLE_LOGIN_ERROR);
}
Comment on lines +253 to +257

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

BusinessException์„ ๋‹ค์‹œ ์žก์•„์„œ ์—๋Ÿฌ ์ •๋ณด๊ฐ€ ์†์‹ค๋ฉ๋‹ˆ๋‹ค.

try ๋ธ”๋ก ๋‚ด์—์„œ ๋˜์ง„ BusinessException (์˜ˆ: NOT_EXISTS_EMAIL, INVALID_TOKEN)์ด catch (BusinessException e)์—์„œ ์žกํ˜€์„œ APPLE_LOGIN_ERROR๋กœ ๋ฎ์–ด์”Œ์›Œ์ง‘๋‹ˆ๋‹ค. ์›๋ž˜์˜ ๊ตฌ์ฒด์ ์ธ ์—๋Ÿฌ ์ฝ”๋“œ๊ฐ€ ์†์‹ค๋ฉ๋‹ˆ๋‹ค.

BusinessException์€ ๊ทธ๋Œ€๋กœ ๋‹ค์‹œ ๋˜์ง€์„ธ์š”:

 } catch (JwtException e) {
+    log.warn("Apple JWT decoding failed: {}", e.getMessage());
     throw BusinessException.of(AuthErrorCode.INVALID_TOKEN);
 } catch (BusinessException e) {
-    throw BusinessException.of(AuthErrorCode.APPLE_LOGIN_ERROR);
+    throw e;
+} catch (Exception e) {
+    log.error("Apple login error: {}", e.getMessage());
+    throw BusinessException.of(AuthErrorCode.APPLE_LOGIN_ERROR);
 }
๐Ÿค– Prompt for AI Agents
In src/main/java/ita/tinybite/domain/auth/service/AuthService.java around lines
253 to 257, a catch (BusinessException e) block is currently intercepting
BusinessExceptions thrown in the try and replacing them with APPLE_LOGIN_ERROR,
which loses the original error code; remove that catch or change it to rethrow
the original BusinessException (throw e) so specific error codes (e.g.,
NOT_EXISTS_EMAIL, INVALID_TOKEN) are preserved, leaving only the JwtException
mapped to INVALID_TOKEN and any other unexpected exceptions handled separately.

}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ public enum AuthErrorCode implements ErrorCode {
GOOGLE_LOGIN_ERROR(HttpStatus.BAD_REQUEST, "GOOGLE_LOGIN_ERROR", "๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."),
APPLE_LOGIN_ERROR(HttpStatus.BAD_REQUEST, "APPLE_LOGIN_ERROR", "์• ํ”Œ ๋กœ๊ทธ์ธ ์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."),

INVALID_PLATFORM(HttpStatus.BAD_REQUEST, "INVALID_PLATFORM", "์˜ฌ๋ฐ”๋ฅธ ํ”Œ๋žซํผ์ด ์•„๋‹™๋‹ˆ๋‹ค. (Android, iOS)");
INVALID_PLATFORM(HttpStatus.BAD_REQUEST, "INVALID_PLATFORM", "์˜ฌ๋ฐ”๋ฅธ ํ”Œ๋žซํผ์ด ์•„๋‹™๋‹ˆ๋‹ค. (Android, iOS)"),
NOT_EXISTS_EMAIL(HttpStatus.BAD_REQUEST, "NOT_EXISTS_EMAIL", "์• ํ”Œ ์ด๋ฉ”์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
;

private final HttpStatus httpStatus;
private final String code;
Expand Down
6 changes: 1 addition & 5 deletions src/main/resources/application-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ spring:
jpa:
show-sql: true
hibernate:
ddl-auto: create-drop # ๊ฐœ๋ฐœ: update, ์šด์˜: validate ๋˜๋Š” none
ddl-auto: update # ๊ฐœ๋ฐœ: update, ์šด์˜: validate ๋˜๋Š” none

data:
redis:
Expand All @@ -22,7 +22,3 @@ spring:
kakao:
client-id: ${KAKAO_CLIENT_ID}
redirect-uri: ${KAKAO_REDIRECT_URI}

naver:
client-id: ${NAVER_CLIENT_ID}
secret: ${NAVER_CLIENT_SECRET}
8 changes: 4 additions & 4 deletions src/main/resources/application-local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ spring:
kakao:
client-id: ${KAKAO_CLIENT_ID}
redirect-uri: ${KAKAO_REDIRECT_URI}

naver:
client-id: ${NAVER_CLIENT_ID}
secret: ${NAVER_CLIENT_SECRET}
google:
android-id: ${GOOGLE_ANDROID_CLIENT_ID}
ios-id: ${GOOGLE_IOS_CLIENT_ID}

google:
android-id: ${GOOGLE_ANDROID_CLIENT_ID}
Expand Down
7 changes: 7 additions & 0 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ sms:
api-key: ${SMS_API_KEY}
api-secret: ${SMS_API_SECRET}

apple:
client-id: ${APPLE_CLIENT_ID}

naver:
client-id: ${NAVER_CLIENT_ID}
secret: ${NAVER_CLIENT_SECRET}

fcm:
file_path: firebase/tinybite_fcm.json
url: https://fcm.googleapis.com/v1/projects/${FCM_PROJECT_ID}/messages:send
Expand Down