package api import ( "net/http" "strings" "time" "github.com/snip/backend/internal/domain" ) // /api/v1/auth/... // GET providers // GET {provider}/login // GET {provider}/callback // GET me // POST logout func (a *API) authRoutes(w http.ResponseWriter, r *http.Request) { rest := pathTail(r.URL.Path, "/api/v1/auth/") parts := strings.Split(strings.Trim(rest, "/"), "/") switch { case len(parts) == 1 && parts[0] == "providers": writeJSON(w, http.StatusOK, map[string][]string{"providers": a.auth.Providers()}) case len(parts) == 1 && parts[0] == "me": a.me(w, r) case len(parts) == 1 && parts[0] == "logout": a.clearCookie(w, sessionCookie) w.WriteHeader(http.StatusNoContent) case len(parts) == 2 && parts[1] == "login": a.beginLogin(w, r, parts[0]) case len(parts) == 2 && parts[1] == "callback": a.callback(w, r, parts[0]) default: w.WriteHeader(http.StatusNotFound) } } func (a *API) me(w http.ResponseWriter, r *http.Request) { c, err := r.Cookie(sessionCookie) if err != nil { writeError(w, domain.ErrUnauthorized) return } user, err := a.auth.Me(r.Context(), c.Value) if err != nil { writeError(w, domain.ErrUnauthorized) return } writeJSON(w, http.StatusOK, userDTO{ID: user.ID, Email: user.Email, Name: user.Name}) } func (a *API) beginLogin(w http.ResponseWriter, r *http.Request, provider string) { state := randomState() url, err := a.auth.AuthURL(provider, state) if err != nil { writeError(w, err) return } // Double-submit: stash state in a short-lived cookie, compare on callback. http.SetCookie(w, &http.Cookie{ Name: stateCookie, Value: state, Path: "/", HttpOnly: true, Secure: a.cfg.CookieSecure, SameSite: http.SameSiteLaxMode, Expires: time.Now().Add(10 * time.Minute), }) http.Redirect(w, r, url, http.StatusFound) } func (a *API) callback(w http.ResponseWriter, r *http.Request, provider string) { cookie, err := r.Cookie(stateCookie) if err != nil || cookie.Value == "" || cookie.Value != r.URL.Query().Get("state") { writeError(w, domain.ErrUnauthorized) return } a.clearCookie(w, stateCookie) code := r.URL.Query().Get("code") if code == "" { writeError(w, domain.ErrUnauthorized) return } _, token, err := a.auth.Complete(r.Context(), provider, code) if err != nil { writeError(w, err) return } a.setSession(w, token) http.Redirect(w, r, a.cfg.PostLoginRedirect, http.StatusFound) }