Add Google OAuth

This commit is contained in:
2025-08-08 16:47:02 +00:00
parent abed5d75c1
commit cd60dec5b8
18 changed files with 484 additions and 322 deletions

View File

@@ -3,6 +3,8 @@ package handlers
import (
"encoding/json"
"net/http"
"time"
"strings"
"go.mongodb.org/mongo-driver/bson"
@@ -16,9 +18,7 @@ type AuthHandler struct {
}
func NewAuthHandler(authService *services.AuthService) *AuthHandler {
return &AuthHandler{
authService: authService,
}
return &AuthHandler{authService: authService}
}
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
@@ -36,11 +36,7 @@ func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(models.APIResponse{
Success: true,
Data: response,
Message: "User registered successfully",
})
json.NewEncoder(w).Encode(models.APIResponse{Success: true, Data: response, Message: "User registered successfully"})
}
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
@@ -52,21 +48,82 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
response, err := h.authService.Login(req)
if err != nil {
// Определяем правильный статус код в зависимости от ошибки
statusCode := http.StatusBadRequest
if err.Error() == "Account not activated. Please verify your email." {
statusCode = http.StatusForbidden // 403 для неверифицированного email
statusCode = http.StatusForbidden
}
http.Error(w, err.Error(), statusCode)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(models.APIResponse{
Success: true,
Data: response,
Message: "Login successful",
})
json.NewEncoder(w).Encode(models.APIResponse{Success: true, Data: response, Message: "Login successful"})
}
func (h *AuthHandler) GoogleLogin(w http.ResponseWriter, r *http.Request) {
state := generateState()
http.SetCookie(w, &http.Cookie{Name: "oauth_state", Value: state, HttpOnly: true, Path: "/", Expires: time.Now().Add(10 * time.Minute)})
url, err := h.authService.GetGoogleLoginURL(state)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
http.Redirect(w, r, url, http.StatusFound)
}
func (h *AuthHandler) GoogleCallback(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
state := q.Get("state")
code := q.Get("code")
preferJSON := q.Get("response") == "json" || strings.Contains(r.Header.Get("Accept"), "application/json")
cookie, _ := r.Cookie("oauth_state")
if cookie == nil || cookie.Value != state || code == "" {
if preferJSON {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(models.APIResponse{Success: false, Message: "invalid oauth state"})
return
}
redirectURL, ok := h.authService.BuildFrontendRedirect("", "invalid_state")
if ok {
http.Redirect(w, r, redirectURL, http.StatusFound)
return
}
http.Error(w, "invalid oauth state", http.StatusBadRequest)
return
}
resp, err := h.authService.HandleGoogleCallback(r.Context(), code)
if err != nil {
if preferJSON {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(models.APIResponse{Success: false, Message: err.Error()})
return
}
redirectURL, ok := h.authService.BuildFrontendRedirect("", "auth_failed")
if ok {
http.Redirect(w, r, redirectURL, http.StatusFound)
return
}
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if preferJSON {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(models.APIResponse{Success: true, Data: resp, Message: "Login successful"})
return
}
redirectURL, ok := h.authService.BuildFrontendRedirect(resp.Token, "")
if ok {
http.Redirect(w, r, redirectURL, http.StatusFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(models.APIResponse{Success: true, Data: resp, Message: "Login successful"})
}
func (h *AuthHandler) GetProfile(w http.ResponseWriter, r *http.Request) {
@@ -83,10 +140,7 @@ func (h *AuthHandler) GetProfile(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(models.APIResponse{
Success: true,
Data: user,
})
json.NewEncoder(w).Encode(models.APIResponse{Success: true, Data: user})
}
func (h *AuthHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
@@ -102,7 +156,6 @@ func (h *AuthHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
return
}
// Удаляем поля, которые нельзя обновлять через этот эндпоинт
delete(updates, "password")
delete(updates, "email")
delete(updates, "_id")
@@ -115,14 +168,9 @@ func (h *AuthHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(models.APIResponse{
Success: true,
Data: user,
Message: "Profile updated successfully",
})
json.NewEncoder(w).Encode(models.APIResponse{Success: true, Data: user, Message: "Profile updated successfully"})
}
// Удаление аккаунта
func (h *AuthHandler) DeleteAccount(w http.ResponseWriter, r *http.Request) {
userID, ok := middleware.GetUserIDFromContext(r.Context())
if !ok {
@@ -136,12 +184,9 @@ func (h *AuthHandler) DeleteAccount(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(models.APIResponse{
Success: true,
Message: "Account deleted successfully",
})
json.NewEncoder(w).Encode(models.APIResponse{Success: true, Message: "Account deleted successfully"})
}
// Подтверждение email
func (h *AuthHandler) VerifyEmail(w http.ResponseWriter, r *http.Request) {
var req models.VerifyEmailRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -159,7 +204,6 @@ func (h *AuthHandler) VerifyEmail(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(response)
}
// Повторная отправка кода верификации
func (h *AuthHandler) ResendVerificationCode(w http.ResponseWriter, r *http.Request) {
var req models.ResendCodeRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -175,4 +219,7 @@ func (h *AuthHandler) ResendVerificationCode(w http.ResponseWriter, r *http.Requ
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
}
// helpers
func generateState() string { return uuidNew() }