2026-07-17 18:33:06 +02:00
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
import (
|
2026-07-26 12:23:39 +02:00
|
|
|
"context"
|
2026-07-17 18:33:06 +02:00
|
|
|
"encoding/json"
|
2026-07-26 12:23:39 +02:00
|
|
|
"log"
|
2026-07-17 18:33:06 +02:00
|
|
|
"net/http"
|
|
|
|
|
|
2026-07-24 21:08:07 +02:00
|
|
|
"geniusrun/backend/internal/garmin"
|
2026-07-17 18:33:06 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type authResponse struct {
|
|
|
|
|
Status string `json:"status"` // "authenticated" | "mfa_required" | "failed"
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func authStatusString(s garmin.AuthStatus) string {
|
|
|
|
|
switch s {
|
|
|
|
|
case garmin.AuthSuccess:
|
|
|
|
|
return "authenticated"
|
|
|
|
|
case garmin.AuthMFARequired:
|
|
|
|
|
return "mfa_required"
|
|
|
|
|
case garmin.AuthFailed:
|
|
|
|
|
return "failed"
|
|
|
|
|
default:
|
|
|
|
|
return "unknown"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-26 12:23:39 +02:00
|
|
|
// recordAuthResult updates the in-memory auth status/message for userID,
|
|
|
|
|
// and -- on a successful authentication -- persists that this account has
|
|
|
|
|
// connected to Garmin at least once (store.MarkGarminConnected), which is
|
|
|
|
|
// what the login gate actually checks (the in-memory auth status resets on
|
|
|
|
|
// every backend restart; this doesn't).
|
|
|
|
|
func (s *Server) recordAuthResult(ctx context.Context, userID int64, res garmin.AuthResult) {
|
2026-07-17 18:33:06 +02:00
|
|
|
s.mu.Lock()
|
2026-07-25 18:16:06 +02:00
|
|
|
s.userAuthStatus[userID] = res.Status
|
|
|
|
|
s.userAuthMessage[userID] = res.Message
|
2026-07-17 18:33:06 +02:00
|
|
|
s.mu.Unlock()
|
2026-07-26 12:23:39 +02:00
|
|
|
|
|
|
|
|
if res.Status == garmin.AuthSuccess {
|
|
|
|
|
if err := s.DB.MarkGarminConnected(ctx, userID); err != nil {
|
|
|
|
|
log.Printf("api: mark garmin connected for user %d: %v", userID, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-17 18:33:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) handleAuthLogin(w http.ResponseWriter, r *http.Request) {
|
2026-07-25 18:16:06 +02:00
|
|
|
userID := userIDFromContext(r.Context())
|
|
|
|
|
client, err := s.garminFor(r.Context(), userID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
res, err := client.Authenticate(r.Context())
|
2026-07-17 18:33:06 +02:00
|
|
|
if err != nil {
|
|
|
|
|
writeError(w, http.StatusBadGateway, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-07-26 12:23:39 +02:00
|
|
|
s.recordAuthResult(r.Context(), userID, res)
|
2026-07-17 18:33:06 +02:00
|
|
|
writeJSON(w, http.StatusOK, authResponse{Status: authStatusString(res.Status), Message: res.Message})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) handleAuthMFA(w http.ResponseWriter, r *http.Request) {
|
2026-07-25 18:16:06 +02:00
|
|
|
userID := userIDFromContext(r.Context())
|
2026-07-17 18:33:06 +02:00
|
|
|
var body struct {
|
|
|
|
|
Code string `json:"code"`
|
|
|
|
|
}
|
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
|
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if body.Code == "" {
|
|
|
|
|
writeError(w, http.StatusBadRequest, "code is required")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-25 18:16:06 +02:00
|
|
|
client, err := s.garminFor(r.Context(), userID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
res, err := client.CompleteMFA(r.Context(), body.Code)
|
2026-07-17 18:33:06 +02:00
|
|
|
if err != nil {
|
|
|
|
|
writeError(w, http.StatusBadGateway, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-07-26 12:23:39 +02:00
|
|
|
s.recordAuthResult(r.Context(), userID, res)
|
2026-07-17 18:33:06 +02:00
|
|
|
writeJSON(w, http.StatusOK, authResponse{Status: authStatusString(res.Status), Message: res.Message})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) handleAuthStatus(w http.ResponseWriter, r *http.Request) {
|
2026-07-25 18:16:06 +02:00
|
|
|
userID := userIDFromContext(r.Context())
|
2026-07-17 18:33:06 +02:00
|
|
|
s.mu.Lock()
|
2026-07-25 18:16:06 +02:00
|
|
|
status, msg := s.userAuthStatus[userID], s.userAuthMessage[userID]
|
2026-07-17 18:33:06 +02:00
|
|
|
s.mu.Unlock()
|
|
|
|
|
writeJSON(w, http.StatusOK, authResponse{Status: authStatusString(status), Message: msg})
|
|
|
|
|
}
|