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}
Requests
+
+
+
{/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();