package auth import ( "database/sql" "encoding/json" "errors" "net/http" "git.dustin.coffee/hobokenchicken/dumpsterChat/internal/config" "github.com/go-chi/chi/v5" "github.com/google/uuid" ) type Handler struct { db *sql.DB cfg *config.Config sessions *SessionStore } func NewHandler(db *sql.DB, cfg *config.Config) *Handler { return &Handler{ db: db, cfg: cfg, sessions: NewSessionStore(db, cfg), } } func (h *Handler) RegisterRoutes(r chi.Router) { r.Post("/register", h.Register) r.Post("/login/password", h.Login) r.Post("/logout", h.Logout) r.Get("/me", h.Me) } type registerRequest struct { Email string `json:"email"` Username string `json:"username"` Password string `json:"password"` } type loginRequest struct { Email string `json:"email"` Password string `json:"password"` } func (h *Handler) Register(w http.ResponseWriter, r *http.Request) { var req registerRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) return } if req.Email == "" || req.Username == "" || req.Password == "" { http.Error(w, `{"error":"missing fields"}`, http.StatusBadRequest) return } hash, err := HashPassword(req.Password) if err != nil { http.Error(w, `{"error":"server error"}`, http.StatusInternalServerError) return } var userID string err = h.db.QueryRowContext(r.Context(), ` INSERT INTO users (username, email, password_hash) VALUES ($1, $2, $3) RETURNING id `, req.Username, req.Email, hash).Scan(&userID) if err != nil { http.Error(w, `{"error":"user exists"}`, http.StatusConflict) return } token, err := h.sessions.Create(r.Context(), userID) if err != nil { http.Error(w, `{"error":"session error"}`, http.StatusInternalServerError) return } http.SetCookie(w, &http.Cookie{ Name: h.cfg.Session.CookieName, Value: token, Path: "/", HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode, MaxAge: int(h.cfg.Session.Duration.Seconds()), }) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"id": userID}) } func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { var req loginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) return } var userID, hash string err := h.db.QueryRowContext(r.Context(), ` SELECT id, password_hash FROM users WHERE email = $1 `, req.Email).Scan(&userID, &hash) if err != nil { http.Error(w, `{"error":"invalid credentials"}`, http.StatusUnauthorized) return } ok, err := VerifyPassword(req.Password, hash) if err != nil || !ok { http.Error(w, `{"error":"invalid credentials"}`, http.StatusUnauthorized) return } token, err := h.sessions.Create(r.Context(), userID) if err != nil { http.Error(w, `{"error":"session error"}`, http.StatusInternalServerError) return } http.SetCookie(w, &http.Cookie{ Name: h.cfg.Session.CookieName, Value: token, Path: "/", HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode, MaxAge: int(h.cfg.Session.Duration.Seconds()), }) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"id": userID}) } func (h *Handler) Logout(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(h.cfg.Session.CookieName) if err == nil { h.sessions.Delete(r.Context(), cookie.Value) } http.SetCookie(w, &http.Cookie{ Name: h.cfg.Session.CookieName, Value: "", Path: "/", MaxAge: -1, HttpOnly: true, }) w.WriteHeader(http.StatusNoContent) } func (h *Handler) Me(w http.ResponseWriter, r *http.Request) { // Placeholder; requires session middleware integration w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"id": ""}) } var _ = uuid.New() var _ = errors.New("placeholder")