This commit is contained in:
2026-06-08 15:27:06 +02:00
parent 8d2be395b9
commit 0f8c7f57ac
4 changed files with 61 additions and 28 deletions

View File

@@ -10,9 +10,18 @@ type contextKey string
const UserContextKey contextKey = contextKey("user") const UserContextKey contextKey = contextKey("user")
// middleware.go
func AuthMiddleware(secret []byte) func(http.Handler) http.Handler { func AuthMiddleware(secret []byte) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// WICHTIG: Wenn der User auf einer öffentlichen Seite ist,
// darf die Middleware KEINEN Auth-Zwang ausüben und nicht redirecten!
if r.URL.Path == "/login" || r.URL.Path == "/register" || r.URL.Path == "/" {
next.ServeHTTP(w, r)
return
}
tokenStr := "" tokenStr := ""
authHeader := r.Header.Get("Authorization") authHeader := r.Header.Get("Authorization")
@@ -37,22 +46,25 @@ func AuthMiddleware(secret []byte) func(http.Handler) http.Handler {
} }
claims, err := ValidateJWT(tokenStr, secret) claims, err := ValidateJWT(tokenStr, secret)
if err != nil { if err != nil {
if strings.HasPrefix(r.URL.Path, "/api/") { if strings.HasPrefix(r.URL.Path, "/api/") {
http.Error(w, "Invalid token", http.StatusUnauthorized) http.Error(w, "Invalid token", http.StatusUnauthorized)
} else { } else {
// Falls das Cookie korrupt oder abgelaufen ist, löschen wir es direkt,
// damit das Frontend sauber merkt, dass es weg ist.
http.SetCookie(w, &http.Cookie{
Name: "access_token",
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: false, // Erlaubt JS das Auslesen
})
http.Redirect(w, r, "/login", http.StatusSeeOther) http.Redirect(w, r, "/login", http.StatusSeeOther)
} }
return return
} }
ctx := context.WithValue( ctx := context.WithValue(r.Context(), UserContextKey, claims)
r.Context(),
UserContextKey,
claims,
)
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })
} }

View File

@@ -14,17 +14,19 @@
} }
function clearAllAuth() { function clearAllAuth() {
console.log("Clearing all auth remnants from cookies and localStorage..."); console.log("Clearing all auth remnants from everywhere...");
localStorage.removeItem("access_token"); localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token"); localStorage.removeItem("refresh_token");
document.cookie = "access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;"; sessionStorage.removeItem("is_refreshing");
document.cookie = "refresh_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;"; document.cookie = "access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; Secure; SameSite=Lax;";
document.cookie = "refresh_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; Secure; SameSite=Lax;";
} }
async function tryTokenRefresh(refreshToken) { async function tryTokenRefresh(refreshToken) {
if (!refreshToken) return false; if (!refreshToken) return false;
try { try {
console.log("Sending refresh token to /api/refresh...");
const response = await fetch("/api/refresh", { const response = await fetch("/api/refresh", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -33,13 +35,10 @@
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
localStorage.setItem("access_token", data.access_token); localStorage.setItem("access_token", data.access_token);
localStorage.setItem("refresh_token", data.refresh_token); localStorage.setItem("refresh_token", data.refresh_token);
document.cookie = `access_token=${data.access_token}; path=/; max-age=900; SameSite=Lax; Secure`; document.cookie = `access_token=${data.access_token}; path=/; max-age=900; SameSite=Lax; Secure`;
document.cookie = `refresh_token=${data.refresh_token}; path=/; max-age=604800; SameSite=Lax; Secure`; document.cookie = `refresh_token=${data.refresh_token}; path=/; max-age=604800; SameSite=Lax; Secure`;
return true; return true;
} }
} catch (err) { } catch (err) {
@@ -59,52 +58,71 @@
const refreshToken = cookieRefreshToken || localRefreshToken; const refreshToken = cookieRefreshToken || localRefreshToken;
console.log("Auth check started..."); console.log("Auth check started...");
console.log("AccessToken present:", !!accessToken); console.log("AccessToken available (Cookie/Local):", !!cookieAccessToken, "/", !!localAccessToken);
console.log("RefreshToken present:", !!refreshToken); console.log("RefreshToken available (Cookie/Local):", !!cookieRefreshToken, "/", !!localRefreshToken);
if (!accessToken && !refreshToken) { if (!accessToken && !refreshToken) {
console.log("No tokens found. User is guest."); console.log("No tokens found in cookies or localStorage. User is a guest.");
return; return;
} }
if (accessToken) { if (accessToken) {
try { try {
console.log("Attempting ping with access token..."); console.log("Validating token against /api/userinfo...");
const response = await fetch("/api/ping", {
const response = await fetch("/api/userinfo", {
method: "GET", method: "GET",
headers: { "Authorization": `Bearer ${accessToken}` } headers: { "Authorization": `Bearer ${accessToken}` }
}); });
if (response.ok) { if (response.ok) {
console.log("Ping successful! Redirecting to dashboard..."); console.log("Token is perfectly valid!");
sessionStorage.removeItem("is_refreshing");
if (!cookieAccessToken) {
document.cookie = `access_token=${accessToken}; path=/; max-age=900; SameSite=Lax; Secure`;
}
if (!cookieRefreshToken && localRefreshToken) {
document.cookie = `refresh_token=${localRefreshToken}; path=/; max-age=604800; SameSite=Lax; Secure`;
}
console.log("Redirecting to dashboard...");
window.location.href = "/dashboard"; window.location.href = "/dashboard";
return; return;
} else { } else {
console.log("Ping failed. Token might be expired. Status:", response.status); console.log("Token rejected by /api/userinfo. Status:", response.status);
} }
} catch (err) { } catch (err) {
console.error("Network error during ping:", err); console.error("Network error during token verification:", err);
} }
} }
if (sessionStorage.getItem("is_refreshing") === "true") {
console.warn("Loop protection triggered! Tokens appear to be corrupt. Clearing storage.");
clearAllAuth();
return;
}
if (refreshToken) { if (refreshToken) {
console.log("Starting token refresh to rebuild cookies..."); console.log("Access token has expired. Starting refresh process...");
sessionStorage.setItem("is_refreshing", "true");
const refreshSuccessful = await tryTokenRefresh(refreshToken); const refreshSuccessful = await tryTokenRefresh(refreshToken);
if (refreshSuccessful) { if (refreshSuccessful) {
console.log("Refresh successful! Redirecting to dashboard..."); console.log("Refresh successful! Reloading page to apply cookies...");
window.location.href = "/dashboard"; window.location.reload();
return; return;
} else { } else {
console.log("Refresh failed. Refresh token is invalid/expired."); console.log("Refresh failed. Refresh token has also expired.");
} }
} else { } else {
console.log("No refresh token present."); console.log("No refresh token available for recovery.");
} }
clearAllAuth(); clearAllAuth();
console.log("Authentication completely failed. Staying on current guest page."); console.log("Authentication completely failed. User remains on login page.");
} }
checkAuth(); setTimeout(checkAuth, 50);
})(); })();

View File

@@ -268,6 +268,8 @@ func UserInfo(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
log.Println("GET [api/userinfo] " + r.RemoteAddr + ": " + err.Error()) log.Println("GET [api/userinfo] " + r.RemoteAddr + ": " + err.Error())
http.Error(w, "Unauthorized: Invalid or expired token", http.StatusUnauthorized)
return
} }
idParam = claims.UserID idParam = claims.UserID

View File

@@ -85,6 +85,7 @@ func (this *Server) Run() {
mux.HandleFunc("/api/refresh", handlers.RefreshToken) mux.HandleFunc("/api/refresh", handlers.RefreshToken)
mux.Handle("/api/logout", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(handlers.Logout))) mux.Handle("/api/logout", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(handlers.Logout)))
mux.Handle("/api/profile", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(handlers.UserInfo))) mux.Handle("/api/profile", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(handlers.UserInfo)))
mux.HandleFunc("/api/userinfo", handlers.UserInfo)
if this.AllowRegistration { if this.AllowRegistration {
mux.HandleFunc("/api/register", handlers.APIRegister) mux.HandleFunc("/api/register", handlers.APIRegister)
} }