GoogleOAuthController.java

package com.soen390.backend.controller;

import com.soen390.backend.service.GoogleOAuthService;
import com.soen390.backend.service.GoogleSessionService;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

import java.time.Duration;
import java.util.Map;


@RestController
@RequestMapping("/api/google/oauth")
public class GoogleOAuthController {

  private static final String GOOGLE_SESSION_COOKIE = "google_session_id";

  private final GoogleOAuthService googleOAuthService;
  private final GoogleSessionService googleSessionService;

  @Value("${app.google.session-cookie.secure:false}")
  private boolean secureSessionCookie;

  @Value("${app.google.session-cookie.same-site:Lax}")
  private String sessionCookieSameSite;

  @Value("${app.google.session-cookie.max-age-seconds:2592000}")
  private long sessionCookieMaxAgeSeconds;

  public GoogleOAuthController(
      GoogleOAuthService googleOAuthService,
      GoogleSessionService googleSessionService
  ) {
    this.googleOAuthService = googleOAuthService;
    this.googleSessionService = googleSessionService;
  }


  @PostMapping("/exchange")
  public Map<String, Object> exchange(
      @RequestBody Map<String, String> body,
      HttpServletResponse response
  ) {
    String code = body.get("serverAuthCode");
    if (code == null || code.isBlank()) {
      throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "serverAuthCode is required.");
    }

    try {
      String sessionId = googleOAuthService.exchangeServerAuthCode(code);
      response.addHeader(HttpHeaders.SET_COOKIE, buildSessionCookie(sessionId).toString());
      return Map.of("connected", true);
    } catch (RuntimeException e) {
      throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, e.getMessage(), e);
    }
  }

  @PostMapping("/logout")
  public Map<String, Object> logout(
      @CookieValue(value = GOOGLE_SESSION_COOKIE, required = false) String sessionId,
      HttpServletResponse response
  ) {
    googleSessionService.remove(sessionId);
    response.addHeader(HttpHeaders.SET_COOKIE, clearSessionCookie().toString());
    return Map.of("loggedOut", true);
  }

  private ResponseCookie buildSessionCookie(String sessionId) {
    return ResponseCookie.from(GOOGLE_SESSION_COOKIE, sessionId)
        .httpOnly(true)
        .secure(secureSessionCookie)
        .path("/api/google")
        .sameSite(sessionCookieSameSite)
        .maxAge(Duration.ofSeconds(Math.max(1, sessionCookieMaxAgeSeconds)))
        .build();
  }

  private ResponseCookie clearSessionCookie() {
    return ResponseCookie.from(GOOGLE_SESSION_COOKIE, "")
        .httpOnly(true)
        .secure(secureSessionCookie)
        .path("/api/google")
        .sameSite(sessionCookieSameSite)
        .maxAge(Duration.ZERO)
        .build();
  }

}