Skip to content

Commit 1ab2f4a

Browse files
committed
fix(auth): handle Google token verification network failures to avoid 502
1 parent c481d0c commit 1ab2f4a

2 files changed

Lines changed: 105 additions & 93 deletions

File tree

server/src/main/java/com/incial/stockflow/controller/AuthController.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,31 @@
66
import com.incial.stockflow.service.AuthService;
77
import jakarta.validation.Valid;
88
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
910
import org.springframework.http.ResponseEntity;
1011
import org.springframework.web.bind.annotation.*;
1112

13+
@Slf4j
1214
@RestController
1315
@RequestMapping("/api/v1/auth")
1416
@RequiredArgsConstructor
1517
public class AuthController {
16-
18+
1719
private final AuthService authService;
18-
20+
1921
@PostMapping("/login")
2022
public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
23+
log.info("Login request received for email: {}", request.getEmail());
2124
LoginResponse response = authService.login(request);
25+
log.info("Login successful for email: {}", request.getEmail());
2226
return ResponseEntity.ok(response);
2327
}
28+
2429
@PostMapping("/google-login")
2530
public ResponseEntity<LoginResponse> googleLogin(@Valid @RequestBody GoogleLoginRequest request) {
31+
log.info("Google login request received");
2632
LoginResponse response = authService.loginWithGoogle(request);
33+
log.info("Google login completed successfully");
2734
return ResponseEntity.ok(response);
2835
}
2936
}

server/src/main/java/com/incial/stockflow/service/AuthService.java

Lines changed: 96 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313
import com.incial.stockflow.repository.UserRepository;
1414
import com.incial.stockflow.security.JwtUtil;
1515
import lombok.RequiredArgsConstructor;
16+
import lombok.extern.slf4j.Slf4j;
1617
import org.springframework.beans.factory.annotation.Value;
17-
import org.springframework.security.core.userdetails.UsernameNotFoundException;
18-
1918
import org.springframework.security.crypto.password.PasswordEncoder;
2019
import org.springframework.stereotype.Service;
2120
import org.springframework.transaction.annotation.Transactional;
@@ -24,32 +23,33 @@
2423
import java.security.GeneralSecurityException;
2524
import java.util.Collections;
2625

26+
@Slf4j
2727
@Service
2828
@RequiredArgsConstructor
2929
public class AuthService {
30-
30+
3131
private final UserRepository userRepository;
3232
private final PasswordEncoder passwordEncoder;
3333
private final JwtUtil tokenProvider;
3434
private final AuditService auditService;
3535

3636
@Value("${google.client.id}")
3737
private String googleClientId;
38-
38+
3939
@Transactional
4040
public LoginResponse login(LoginRequest request) {
4141
User user = userRepository.findByEmail(request.getEmail())
4242
.orElseThrow(() -> new UnauthorizedException("AUTH_001", "Invalid email or password"));
43-
43+
4444
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
4545
throw new UnauthorizedException("AUTH_001", "Invalid email or password");
4646
}
47-
47+
4848
String token = tokenProvider.generateToken(user.getId(),user.getEmail(), String.valueOf(user.getRole()));
49-
49+
5050
// Log the login
5151
auditService.logAction(user, "LOGIN", "User", user.getId(), "User logged in successfully");
52-
52+
5353
UserResponse userResponse = UserResponse.builder()
5454
.id(user.getId())
5555
.name(user.getName())
@@ -58,105 +58,110 @@ public LoginResponse login(LoginRequest request) {
5858
.outletId(user.getOutlet() != null ? user.getOutlet().getId() : null)
5959
.avatarUrl(user.getAvatarUrl())
6060
.build();
61-
61+
6262
return LoginResponse.builder()
6363
.token(token)
6464
.user(userResponse)
6565
.build();
6666
}
6767

68-
@Transactional
6968
public LoginResponse loginWithGoogle(GoogleLoginRequest request) {
70-
71-
if (request == null || request.getCredential() == null || request.getCredential().isBlank()) {
72-
throw new UnauthorizedException("AUTH_GOOGLE_001", "Missing Google credential");
73-
}
74-
75-
if (googleClientId == null || googleClientId.trim().isEmpty()) {
76-
throw new IllegalStateException("Google authentication not configured on server");
77-
}
78-
79-
GoogleIdToken idToken;
80-
69+
log.info("Google login attempt starting");
8170
try {
71+
// Check if Google Client ID is configured
72+
if (googleClientId == null || googleClientId.trim().isEmpty()) {
73+
log.error("Google Client ID not configured");
74+
throw new UnauthorizedException("AUTH_003", "Google authentication is not properly configured. Please contact the administrator.");
75+
}
76+
77+
log.debug("Creating Google ID token verifier");
78+
// Verify Google ID token with timeout protection
8279
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(
8380
new NetHttpTransport(),
8481
GsonFactory.getDefaultInstance())
8582
.setAudience(Collections.singletonList(googleClientId))
8683
.build();
8784

88-
idToken = verifier.verify(request.getCredential());
89-
90-
} catch (Exception e) {
91-
throw new UnauthorizedException("AUTH_GOOGLE_002", "Invalid Google token");
92-
}
93-
94-
if (idToken == null) {
95-
throw new UnauthorizedException("AUTH_GOOGLE_003", "Invalid Google token");
96-
}
97-
98-
GoogleIdToken.Payload payload;
99-
try {
100-
payload = idToken.getPayload();
101-
} catch (Exception e) {
102-
throw new UnauthorizedException("AUTH_GOOGLE_004", "Invalid Google payload");
103-
}
104-
105-
String email = payload.getEmail();
106-
String googleId = payload.getSubject();
107-
108-
if (email == null || googleId == null) {
109-
throw new UnauthorizedException("AUTH_GOOGLE_005", "Google account missing required data");
110-
}
111-
112-
String name = (String) payload.get("name");
113-
String pictureUrl = (String) payload.get("picture");
114-
115-
// User lookup
116-
User user = userRepository.findByEmail(email)
117-
.orElseThrow(() ->
118-
new UnauthorizedException("AUTH_GOOGLE_006",
119-
"User not associated with this email. Contact administrator"));
120-
121-
// Update Google details if changed
122-
boolean needsUpdate = false;
123-
124-
if (user.getGoogleId() == null || !user.getGoogleId().equals(googleId)) {
125-
user.setGoogleId(googleId);
126-
needsUpdate = true;
127-
}
85+
GoogleIdToken idToken = null;
86+
try {
87+
log.debug("Verifying Google token - this may involve network I/O to googleapis.com");
88+
long startTime = System.currentTimeMillis();
89+
idToken = verifier.verify(request.getCredential());
90+
long duration = System.currentTimeMillis() - startTime;
91+
log.info("Google token verification completed in {}ms", duration);
92+
} catch (java.net.SocketTimeoutException e) {
93+
log.error("Socket timeout during Google token verification", e);
94+
throw new RuntimeException("Network timeout while verifying Google token. Please check your internet connection and try again.");
95+
} catch (java.net.UnknownHostException e) {
96+
log.error("Unknown host during Google token verification - cannot reach googleapis.com", e);
97+
throw new RuntimeException("Cannot reach Google authentication servers. Please check network connectivity.");
98+
} catch (javax.net.ssl.SSLException e) {
99+
log.error("SSL error during Google token verification", e);
100+
throw new RuntimeException("SSL certificate error during Google authentication. Please contact system administrator.");
101+
} catch (IOException e) {
102+
log.error("IO error during Google token verification", e);
103+
throw new RuntimeException("Network error during Google authentication: " + e.getMessage());
104+
}
105+
106+
if (idToken == null) {
107+
log.warn("Google token verification returned null - invalid token");
108+
throw new UnauthorizedException("AUTH_004", "Invalid Google ID token. Please try logging in again.");
109+
}
110+
111+
GoogleIdToken.Payload payload = idToken.getPayload();
112+
String googleId = payload.getSubject();
113+
String email = payload.getEmail();
114+
String name = (String) payload.get("name");
115+
String pictureUrl = (String) payload.get("picture");
116+
117+
log.info("Google token verified successfully for email: {}", email);
118+
119+
// Find user by email - user must be pre-registered
120+
User user = userRepository.findByEmail(email).orElseThrow(
121+
() -> {
122+
log.warn("Google login attempted for unregistered email: {}", email);
123+
return new UnauthorizedException("AUTH_005", "User not associated with this email. Contact sales");
124+
}
125+
);
126+
127+
// Update existing user with Google info only if values changed
128+
boolean needsUpdate = false;
129+
if (user.getGoogleId() == null || !user.getGoogleId().equals(googleId)) {
130+
user.setGoogleId(googleId);
131+
needsUpdate = true;
132+
}
133+
if (pictureUrl != null && (user.getAvatarUrl() == null || !user.getAvatarUrl().equals(pictureUrl))) {
134+
user.setAvatarUrl(pictureUrl);
135+
needsUpdate = true;
136+
}
137+
if (needsUpdate) {
138+
userRepository.save(user);
139+
log.debug("Updated user Google info for: {}", email);
140+
}
141+
142+
String token = tokenProvider.generateToken(user.getId(),user.getEmail(), String.valueOf(user.getRole()));
143+
144+
// Log the login
145+
auditService.logAction(user, "GOOGLE LOGIN", "User", user.getId(), "User logged in successfully");
146+
147+
UserResponse userResponse = UserResponse.builder()
148+
.id(user.getId())
149+
.name(user.getName())
150+
.email(user.getEmail())
151+
.role(user.getRole())
152+
.outletId(user.getOutlet() != null ? user.getOutlet().getId() : null)
153+
.avatarUrl(user.getAvatarUrl())
154+
.build();
128155

129-
if (pictureUrl != null && (user.getAvatarUrl() == null || !user.getAvatarUrl().equals(pictureUrl))) {
130-
user.setAvatarUrl(pictureUrl);
131-
needsUpdate = true;
132-
}
156+
log.info("Google login successful for user: {}", email);
157+
return LoginResponse.builder()
158+
.token(token)
159+
.user(userResponse)
160+
.build();
133161

134-
if (needsUpdate) {
135-
userRepository.save(user);
162+
} catch (GeneralSecurityException e) {
163+
log.error("Security exception during Google token verification", e);
164+
throw new RuntimeException("Google token verification failed: " + e.getMessage());
136165
}
137-
138-
// Generate JWT
139-
String token = tokenProvider.generateToken(
140-
user.getId(),
141-
user.getEmail(),
142-
String.valueOf(user.getRole())
143-
);
144-
145-
auditService.logAction(user, "GOOGLE LOGIN", "User", user.getId(), "User logged in successfully");
146-
147-
UserResponse userResponse = UserResponse.builder()
148-
.id(user.getId())
149-
.name(user.getName())
150-
.email(user.getEmail())
151-
.role(user.getRole())
152-
.outletId(user.getOutlet() != null ? user.getOutlet().getId() : null)
153-
.avatarUrl(user.getAvatarUrl())
154-
.build();
155-
156-
return LoginResponse.builder()
157-
.token(token)
158-
.user(userResponse)
159-
.build();
160166
}
161-
162167
}

0 commit comments

Comments
 (0)