Skip to content

Commit

Permalink
fix: add session validation and logout mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
Björn Urban committed Jul 28, 2024
1 parent 8c1a437 commit d39b33a
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 7 deletions.
6 changes: 6 additions & 0 deletions backend/cmd/kubevoyage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
81 changes: 75 additions & 6 deletions backend/internal/handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -105,17 +106,19 @@ 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
Domain: tld,
}
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
Expand Down Expand Up @@ -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
Expand All @@ -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
}

Expand Down
3 changes: 3 additions & 0 deletions backend/internal/handlers/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
30 changes: 30 additions & 0 deletions frontend/src/App.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
<script>
import { Router, Route, Link } from 'svelte-routing';
import {onMount} from 'svelte';
import { isAuthenticated } from './authStore.js';
import routes from './routes.js';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap-icons/font/bootstrap-icons.css';
onMount(async () => {
isAuthenticated.checkAuth();
await validateSession();
});
async function validateSession() {
try {
const response = await fetch('/api/validate-session', {
credentials: 'include' // This is important for sending cookies
});
if (!response.ok) {
throw new Error('Session invalid');
}
isAuthenticated.setAuth(true);
} catch (error) {
console.error('Session validation failed:', error);
isAuthenticated.setAuth(false);
}
}
async function logout() {
await fetch('/api/logout', { method: 'POST', credentials: 'include' });
isAuthenticated.setAuth(false);
// Handle logout (e.g., redirect to login page)
}
</script>

<Router>
Expand All @@ -26,6 +53,9 @@
{#if $isAuthenticated}
<li class="nav-item">
<Link class="nav-link" to="/requests">Requests</Link>
<button class="nav-link" on:click={logout()}>Requests</button>


</li>
{/if}
<!-- Add more links as needed -->
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/authStore.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
import { writable } from 'svelte/store';

export const isAuthenticated = writable(false);
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();

0 comments on commit d39b33a

Please sign in to comment.