package web import ( "fmt" "log" "net/http" "nfeeder/internal/db" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) const ISSUER = "nfeeder-app" var jwtKey = []byte("very_super_duper_secret") func (s *Server) handleRegister() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { email := r.FormValue("email") password := r.FormValue("password") // hash password hashpw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { http.Error(w, "Internal Error", http.StatusInternalServerError) return } // create the user user, err := s.store.CreateUser(r.Context(), db.CreateUserParams{ Email: email, Password: string(hashpw), }) if err != nil { // Log the actual error for yourself, send a generic one to the user log.Printf("failed to create user: %v", err) http.Error(w, "Could not create user", http.StatusBadRequest) return } // Auto-login after reg s.issueTokenAndRedirect(w, r, fmt.Sprintf("%d", user.ID)) } } func (s *Server) handleLogin() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { email := r.FormValue("email") password := r.FormValue("password") user, err := s.store.GetUserByEmail(r.Context(), email) if err != nil { http.Error(w, "Invalid credentials", http.StatusUnauthorized) return } // compare passwords err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) if err != nil { http.Error(w, "Invalid credentials", http.StatusUnauthorized) return } s.issueTokenAndRedirect(w, r, fmt.Sprintf("%d", user.ID)) } } func (s *Server) handleLogout() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // To logout, we simply instruct the browser to delete the cookie http.SetCookie(w, &http.Cookie{ Name: "nfeeder_token", Value: "", Path: "/", Expires: time.Unix(0, 0), // Expire immediately HttpOnly: true, }) http.Redirect(w, r, "/login", http.StatusSeeOther) } } func (s *Server) issueTokenAndRedirect(w http.ResponseWriter, r *http.Request, userID string) { claims := jwt.RegisteredClaims{ Issuer: ISSUER, Subject: userID, ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(jwtKey) if err != nil { http.Error(w, "Internal Error", http.StatusInternalServerError) return } http.SetCookie(w, &http.Cookie{ Name: "nfeeder_token", Value: tokenString, Path: "/", HttpOnly: true, Secure: false, // Set to true in prod for HTTPS SameSite: http.SameSiteLaxMode, Expires: time.Now().Add(24 * time.Hour), }) http.Redirect(w, r, "/", http.StatusSeeOther) }