From dff38f92053dc4f0c592518dbc08629fd28aeabc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Urban?= Date: Mon, 30 Oct 2023 19:21:03 +0100 Subject: [PATCH] feat(backend): update request and redirect logic --- backend/cmd/kubevoyage/main.go | 11 ++-- backend/internal/handlers/auth.go | 15 +++-- backend/internal/handlers/requests.go | 94 ++++++++++++++++++++++----- backend/internal/handlers/utils.go | 19 ++++++ backend/internal/models/models.go | 18 ++++- 5 files changed, 131 insertions(+), 26 deletions(-) diff --git a/backend/cmd/kubevoyage/main.go b/backend/cmd/kubevoyage/main.go index f45e97f..d536192 100644 --- a/backend/cmd/kubevoyage/main.go +++ b/backend/cmd/kubevoyage/main.go @@ -69,12 +69,12 @@ func main() { log.Fatalf(err.Error()) } - mux := setupServer(handler) + mux := setupServer(handler, app.DB) log.Println("Starting server on :8080") log.Fatal(http.ListenAndServe(":8080", mux)) } -func setupServer(handle *handlers.Handler) http.Handler { +func setupServer(handle *handlers.Handler, db *gorm.DB) http.Handler { mux := http.NewServeMux() handler := cors.Default().Handler(mux) @@ -109,7 +109,10 @@ func setupServer(handle *handlers.Handler) http.Handler { }))) mux.Handle("/api/requests", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - handlers.HandleRequests(w, r, db) + handle.HandleRequests(w, r) + }))) + mux.Handle("/api/requests/update", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handle.HandleUpdateSiteState(w, r) }))) mux.Handle("/api/register", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handle.HandleRegister(w, r) @@ -124,7 +127,7 @@ func setupServer(handle *handlers.Handler) http.Handler { handle.HandleRedirect(w, r) }))) mux.Handle("/api/request", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - handle.HandleRequestSite(w, r, db) + handle.HandleRequestSite(w, r) }))) return handler diff --git a/backend/internal/handlers/auth.go b/backend/internal/handlers/auth.go index 25c34f0..6e3a4ef 100644 --- a/backend/internal/handlers/auth.go +++ b/backend/internal/handlers/auth.go @@ -210,18 +210,21 @@ func (h *Handler) HandleAuthenticate(w http.ResponseWriter, r *http.Request) { var userSite models.UserSite err = h.db.Joins("JOIN users ON users.id = user_sites.user_id"). Joins("JOIN sites ON sites.id = user_sites.site_id"). - Where("users.email = ? AND sites.url = ? AND user_sites.state = ?", userEmail, siteURL, "authorized"). + Where("users.email = ? AND sites.url = ?", userEmail, siteURL). First(&userSite).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { // Return 401 if the user is not authorized for the requested siteURL - w.WriteHeader(http.StatusUnauthorized) + http.Redirect(w, r, "api/request", http.StatusSeeOther) return } h.logError(w, "Database error while checking user authorization", err, http.StatusInternalServerError) return } + if userSite.State == models.Requested || userSite.State == models.Declined { + w.WriteHeader(http.StatusUnauthorized) + } w.WriteHeader(http.StatusOK) } @@ -310,13 +313,17 @@ func (h *Handler) getRedirectUrl(r *http.Request, w http.ResponseWriter) (string if siteURL == "" { surl, err := h.getRedirectFromCookie(r, w, false) if err != nil { - fmt.Errorf("Redirect URL missing from both header and URL parameter") + return "", fmt.Errorf("Redirect URL missing from both header and URL parameter") } siteURL = surl } } } - return siteURL, nil + if siteURL == "" { + return "", fmt.Errorf("Redirect URL missing from both header and URL parameter") + } else { + return siteURL, nil + } } func printHeaders(r *http.Request) { for name, values := range r.Header { diff --git a/backend/internal/handlers/requests.go b/backend/internal/handlers/requests.go index 00ea18d..470c76c 100644 --- a/backend/internal/handlers/requests.go +++ b/backend/internal/handlers/requests.go @@ -1,7 +1,9 @@ package handlers import ( + "encoding/json" "errors" + "fmt" "github.com/B-Urb/KubeVoyage/internal/models" "gorm.io/gorm" "log" @@ -9,20 +11,23 @@ import ( "time" ) -type UserSiteResponse struct { - User string `json:"user"` - Site string `json:"site"` - State string `json:"state"` -} - -func HandleRequests(w http.ResponseWriter, r *http.Request, db *gorm.DB) { +func (h *Handler) HandleRequests(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { sendJSONError(w, "Method not allowed", http.StatusMethodNotAllowed) return } - - var results []UserSiteResponse - err := db.Table("user_sites"). + userEmail, err := h.getUserEmailFromToken(r) + if err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + isAdmin, err := IsUserAdmin(h.db, userEmail) + if !isAdmin { + http.Error(w, "Only Admins can view this", http.StatusUnauthorized) + return + } + var results []models.UserSiteResponse + err = h.db.Table("user_sites"). Select("users.email as user, sites.url as site, user_sites.state as state"). Joins("JOIN users ON users.id = user_sites.user_id"). Joins("JOIN sites ON sites.id = user_sites.site_id"). @@ -37,39 +42,94 @@ func HandleRequests(w http.ResponseWriter, r *http.Request, db *gorm.DB) { sendJSONResponse(w, results, http.StatusOK) } -func (h *Handler) HandleRequestSite(w http.ResponseWriter, r *http.Request, db *gorm.DB) { +func (h *Handler) HandleRequestSite(w http.ResponseWriter, r *http.Request) { userEmail, err := h.getUserEmailFromToken(r) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } + // Query the User table to get the unique ID associated with the email var user models.User - if err := db.Where("email = ?", userEmail).First(&user).Error; err != nil { + if err := h.db.Where("email = ?", userEmail).First(&user).Error; err != nil { http.Error(w, "User not found", http.StatusNotFound) return } - + if user.Role == "admin" { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } siteURL := r.URL.Query().Get("redirect") // Check if site already exists var site models.Site - if err := db.Where("url = ?", siteURL).First(&site).Error; errors.Is(err, gorm.ErrRecordNotFound) { + if err := h.db.Where("url = ?", siteURL).First(&site).Error; errors.Is(err, gorm.ErrRecordNotFound) { // If not, create a new site entry site = models.Site{URL: siteURL} - db.Create(&site) + h.db.Create(&site) } // Create a new UserSite entry with state "requested" userSite := models.UserSite{ UserID: user.ID, // Use the ID from the user query SiteID: site.ID, - State: "requested", + State: models.Requested, } - db.Create(&userSite) + h.db.Create(&userSite) w.Write([]byte("Request submitted")) } +func (h *Handler) HandleUpdateSiteState(w http.ResponseWriter, r *http.Request) { + type RequestBody struct { + UserEmail string `json:"userEmail"` + SiteURL string `json:"siteURL"` + NewState string `json:"newState"` + } + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + var body RequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + state := models.State(body.NewState) + if !state.IsValid() { + http.Error(w, "Invalid state value", http.StatusBadRequest) + return + } + userEmail, err := h.getUserEmailFromToken(r) + if err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + isAdmin, err := IsUserAdmin(h.db, userEmail) + if !isAdmin { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + var userID uint + if err := h.db.Model(&models.User{}).Where("email = ?", body.UserEmail).Select("id").First(&userID).Error; err != nil { + http.Error(w, fmt.Errorf("failed to find user: %w", err).Error(), http.StatusBadRequest) + return + } + + // 2. Find the site ID + var siteID uint + if err := h.db.Model(&models.Site{}).Where("url = ?", body.SiteURL).Select("id").First(&siteID).Error; err != nil { + http.Error(w, fmt.Errorf("failed to find site: %w", err).Error(), http.StatusBadRequest) + return + } + + // 3. Update the UserSite record + if err := h.db.Model(&models.UserSite{}).Where("user_id = ? AND site_id = ?", userID, siteID).Update("state", state).Error; err != nil { + http.Error(w, fmt.Errorf("failed to find and update request: %w", err).Error(), http.StatusBadRequest) + return + } + + w.Write([]byte("State updated successfully")) +} func HandleLogout(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, &http.Cookie{ diff --git a/backend/internal/handlers/utils.go b/backend/internal/handlers/utils.go index f384257..12025e9 100644 --- a/backend/internal/handlers/utils.go +++ b/backend/internal/handlers/utils.go @@ -2,7 +2,10 @@ package handlers import ( "encoding/json" + "errors" "fmt" + "github.com/B-Urb/KubeVoyage/internal/models" + "gorm.io/gorm" "log" "net/http" "net/url" @@ -69,3 +72,19 @@ func sendJSONResponse(w http.ResponseWriter, data interface{}, statusCode int) { w.WriteHeader(statusCode) json.NewEncoder(w).Encode(data) } + +func IsUserAdmin(db *gorm.DB, email string) (bool, error) { + var user models.User + + // Find the user by email + result := db.Where("email = ?", email).First(&user) + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return false, errors.New("user not found") + } + if result.Error != nil { + return false, result.Error + } + + // Check if the user's role is "admin" + return user.Role == "admin", nil +} diff --git a/backend/internal/models/models.go b/backend/internal/models/models.go index 3f25264..11fe063 100644 --- a/backend/internal/models/models.go +++ b/backend/internal/models/models.go @@ -15,7 +15,7 @@ type Site struct { type UserSite struct { UserID uint `gorm:"primaryKey"` SiteID uint `gorm:"primaryKey"` - State string + State State } type UserSiteResponse struct { @@ -23,3 +23,19 @@ type UserSiteResponse struct { Site string `json:"site"` State string `json:"state"` } + +type State string + +const ( + Requested State = "requested" + Authorized State = "authorized" + Declined State = "declined" +) + +func (s State) IsValid() bool { + switch s { + case Requested, Authorized, Declined: + return true + } + return false +}