diff --git a/backend/cmd/kubevoyage/main.go b/backend/cmd/kubevoyage/main.go index e393be9..5e770ea 100644 --- a/backend/cmd/kubevoyage/main.go +++ b/backend/cmd/kubevoyage/main.go @@ -127,6 +127,12 @@ func setupServer(handle *handlers.Handler) http.Handler { mux.Handle("/api/request", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handle.HandleRequestSite(w, r) }))) + mux.Handle("/api/logout", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handle.HandleLogout(w, r) + }))) + mux.Handle("/api/validate-session", logMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handle.HandleValidateSession(w, r) + }))) return handler } diff --git a/backend/internal/handlers/auth.go b/backend/internal/handlers/auth.go index c995386..40f7c06 100644 --- a/backend/internal/handlers/auth.go +++ b/backend/internal/handlers/auth.go @@ -32,7 +32,8 @@ type TokenInfo struct { user string } -var store = sessions.NewCookieStore([]byte("your-very-secret-key")) +var secret, _ = util.GetEnvOrDefault("JWT_SECRET_KEY", "kubevoyage") +var store = sessions.NewCookieStore([]byte(secret)) var oneTimeStore = make(map[string]TokenInfo) func NewHandler(db *gorm.DB) *Handler { @@ -105,7 +106,7 @@ func (h *Handler) HandleLogin(w http.ResponseWriter, r *http.Request) { } session.Options = &sessions.Options{ Path: "/", // Available across the entire domain - MaxAge: 3600, // Expires after 1 hour + MaxAge: 3600 * 24 * 7, // Expires after 1 week TODO: make configurable HttpOnly: true, // Not accessible via JavaScript Secure: true, // Only sent over HTTPS SameSite: http.SameSiteNoneMode, // Controls cross-site request behavior @@ -113,9 +114,11 @@ func (h *Handler) HandleLogin(w http.ResponseWriter, r *http.Request) { } session.Values["authenticated"] = true session.Values["user"] = inputUser.Email - if err := session.Save(r, w); err != nil { - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return + if session.IsNew { + if err := session.Save(r, w); err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } } var domain string @@ -306,6 +309,68 @@ func (h *Handler) HandleAuthenticate(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusOK) } +func (h *Handler) HandleLogout(w http.ResponseWriter, r *http.Request) { + session, err := store.Get(r, "session-cook") + if err != nil { + sendJSONError(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + // Clear session values + session.Values["authenticated"] = false + delete(session.Values, "user") + + // Expire the cookie + session.Options.MaxAge = -1 + + // Save the session + err = session.Save(r, w) + if err != nil { + sendJSONError(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + response := map[string]interface{}{ + "success": true, + "message": "Logout successful", + } + sendJSONResponse(w, response, http.StatusOK) +} + +func (h *Handler) HandleValidateSession(w http.ResponseWriter, r *http.Request) { + session, err := store.Get(r, "session-cook") + if err != nil { + sendJSONError(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + authenticated, ok := session.Values["authenticated"].(bool) + if !ok || !authenticated { + sendJSONError(w, "Unauthorized", http.StatusUnauthorized) + return + } + + user, ok := session.Values["user"].(string) + if !ok || user == "" { + sendJSONError(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // Optionally, you can check if the user still exists in the database + var dbUser models.User + result := h.db.Where("email = ?", user).First(&dbUser) + if result.Error != nil { + sendJSONError(w, "User not found", http.StatusUnauthorized) + return + } + + response := map[string]interface{}{ + "success": true, + "message": "Session is valid", + "user": user, + } + sendJSONResponse(w, response, http.StatusOK) +} func (h *Handler) logError(w http.ResponseWriter, message string, err error, statusCode int) { logMessage := message @@ -322,7 +387,11 @@ func (h *Handler) getUserFromSession(r *http.Request) (string, error) { slog.Debug("Error retrieving user from session", "error", err) return "", err } - user, _ := session.Values["user"].(string) + user, success := session.Values["user"].(string) + if success == false { + slog.Debug("Error retrieving user from session", "error", err) + return "", err + } return user, nil } diff --git a/backend/internal/handlers/utils.go b/backend/internal/handlers/utils.go index 12025e9..682434c 100644 --- a/backend/internal/handlers/utils.go +++ b/backend/internal/handlers/utils.go @@ -76,6 +76,9 @@ func sendJSONResponse(w http.ResponseWriter, data interface{}, statusCode int) { func IsUserAdmin(db *gorm.DB, email string) (bool, error) { var user models.User + if email == "" { + return false, errors.New("user is empty") + } // Find the user by email result := db.Where("email = ?", email).First(&user) if errors.Is(result.Error, gorm.ErrRecordNotFound) { diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index d2cfda0..99b8d64 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -1,10 +1,37 @@ @@ -26,6 +53,9 @@ {#if $isAuthenticated} {/if} diff --git a/frontend/src/authStore.js b/frontend/src/authStore.js index 0f82e16..ab9a803 100644 --- a/frontend/src/authStore.js +++ b/frontend/src/authStore.js @@ -1,3 +1,25 @@ import { writable } from 'svelte/store'; -export const isAuthenticated = writable(false); \ No newline at end of file +function createAuthStore() { + const { subscribe, set } = writable(false); + + return { + subscribe, + setAuth: (value) => { + set(value); + if (typeof window !== 'undefined') { + localStorage.setItem('isAuthenticated', JSON.stringify(value)); + } + }, + checkAuth: () => { + if (typeof window !== 'undefined') { + const storedAuth = localStorage.getItem('isAuthenticated'); + if (storedAuth) { + set(JSON.parse(storedAuth)); + } + } + } + }; +} + +export const isAuthenticated = createAuthStore();