package api import ( "encoding/json" "net/http" "time" "git.freecumextremist.com/grumbulon/pomme/internal" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) // These are used for documentation, might be removed later // nolint: unused type httpError struct { Message string `json:"message" example:"Status bad request"` Code int `json:"code" example:"401"` } // nolint: unused type httpSuccess struct { Message string `json:"message" example:"Success"` Code int `json:"code" example:"200"` } // nolint: unused type httpInternalServerError struct { Message string `json:"message" example:"Internal Server Error"` Code int `json:"code" example:"500"` } // Auth godoc // // @Summary authenticate as a regular user // @Description login to Pomme // // @Description Rate limited: 5 requests every 5 second // // @Tags accounts // @Accept json // @Produce json // @Param username query string true "Username" // @Param password query string true "Password" // @Success 200 {object} httpSuccess // @Failure 401 {object} httpError // @Router /api/login [post] func Login(w http.ResponseWriter, r *http.Request) { var result internal.User if _, err := r.Cookie("jwt"); err == nil { http.Error(w, "Logged in", http.StatusCreated) return } err := r.ParseForm() if err != nil { internalServerError(w, "unable to parse request") return } username := r.Form.Get("username") password := r.Form.Get("password") if username == "" { internalServerError(w, "no username provided") // this should prob be handled by the frontend return } if password == "" { internalServerError(w, "no password provided") // this should prob be handled by the frontend return } db, ok := r.Context().Value(keyPrincipalContextID).(*gorm.DB) if !ok { internalServerError(w, "DB connection failed") return } db.Where("username = ?", username).First(&result) if result.Username == "" { authFailed(w, "login") return } err = bcrypt.CompareHashAndPassword([]byte(result.HashedPassword), []byte(password)) if err != nil { authFailed(w, "login") return } token, err := makeToken(username) if err != nil { internalServerError(w, err.Error()) return } http.SetCookie(w, &http.Cookie{ HttpOnly: true, Expires: time.Now().Add(1 * time.Hour), MaxAge: 3600, SameSite: http.SameSiteLaxMode, // Uncomment below for HTTPS: // Secure: true, Name: "jwt", // Must be named "jwt" or else the token cannot be searched for by jwtauth.Verifier. Value: token, }) w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode( internal.Response{ Message: "Successfully logged in", HTTPResponse: 200, }) if err != nil { internalServerError(w, "internal server error") return } http.Redirect(w, r, "/", http.StatusSeeOther) } // Logout destroys a users JWT cookie. func Logout(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, &http.Cookie{ HttpOnly: true, MaxAge: -1, // Delete the cookie. SameSite: http.SameSiteLaxMode, // Secure: true, Name: "jwt", Value: "", }) w.Header().Set("Content-Type", "application/json") err := json.NewEncoder(w).Encode( internal.Response{ Message: "Successfully logged out", HTTPResponse: 200, }) if err != nil { internalServerError(w, "internal server error") return } http.Redirect(w, r, "/", http.StatusSeeOther) }