fixed clearing of csrf and refresh tokens
This commit is contained in:
+82
-28
@@ -603,48 +603,47 @@ func checkEmailInDB(email string) (bool, error) {
|
||||
|
||||
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if !isValidAuthHeader(authHeader) {
|
||||
helper.RespondWithError(w, http.StatusUnauthorized, "Authorization header missing or invalid")
|
||||
return
|
||||
}
|
||||
clearRefreshTokenCookie(w)
|
||||
clearCSRFCookie(w)
|
||||
|
||||
tokenString := strings.TrimSpace(strings.TrimPrefix(authHeader, bearerPrefix))
|
||||
if tokenString == "" {
|
||||
helper.RespondWithError(w, http.StatusUnauthorized, "Token is missing or empty")
|
||||
return
|
||||
}
|
||||
if isValidAuthHeader(authHeader) {
|
||||
tokenString := strings.TrimSpace(strings.TrimPrefix(authHeader, bearerPrefix))
|
||||
if tokenString != "" {
|
||||
token, err := jwt.ParseWithClaims(tokenString, &models.AccessToken{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
if rsaPrivateKey == nil {
|
||||
return nil, errors.New("RSA private key is not initialized")
|
||||
}
|
||||
return &rsaPrivateKey.PublicKey, nil
|
||||
})
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, &models.AccessToken{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
if rsaPrivateKey == nil {
|
||||
return nil, errors.New("RSA private key is not initialized")
|
||||
}
|
||||
return &rsaPrivateKey.PublicKey, nil
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
if claims, ok := token.Claims.(*models.AccessToken); ok {
|
||||
userID, err := services.GetUserIDFromEmail(claims.Email)
|
||||
if err == nil {
|
||||
if err := RevokeAllUserSessions(userID); err != nil {
|
||||
helper.LogError(err, "Failed to revoke user sessions during logout")
|
||||
if claims, ok := token.Claims.(*models.AccessToken); ok {
|
||||
userID, err := services.GetUserIDFromEmail(claims.Email)
|
||||
if err == nil {
|
||||
if err := RevokeAllUserSessions(userID); err != nil {
|
||||
helper.LogError(err, "Failed to revoke user sessions during logout")
|
||||
}
|
||||
} else {
|
||||
helper.LogError(err, "Failed to get user ID during logout")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
helper.LogError(err, "Failed to get user ID during logout")
|
||||
helper.LogError(err, "Failed to parse JWT token during logout")
|
||||
}
|
||||
} else {
|
||||
helper.LogWarn("Authorization header contains empty bearer token during logout")
|
||||
}
|
||||
} else {
|
||||
helper.LogError(err, "Failed to parse JWT token during logout")
|
||||
helper.LogWarn("Authorization header missing or invalid during logout; proceeding with cookie clear only")
|
||||
}
|
||||
|
||||
if err := accessLog(r, nil, 18, nil); err != nil {
|
||||
helper.LogError(err, "Failed to write access log during logout")
|
||||
}
|
||||
|
||||
clearRefreshTokenCookie(w)
|
||||
|
||||
response := map[string]interface{}{
|
||||
"message": "Successfully logged out",
|
||||
"action": "clear_session_storage",
|
||||
@@ -709,3 +708,58 @@ func clearRefreshTokenCookie(w http.ResponseWriter) {
|
||||
|
||||
helper.LogInfo("Refresh token cookie clearing commands sent to browser")
|
||||
}
|
||||
|
||||
func clearCSRFCookie(w http.ResponseWriter) {
|
||||
helper.LogInfo("Clearing csrf_token cookie...")
|
||||
|
||||
isSecure := strings.HasPrefix(os.Getenv("BACKEND_URL"), HTTPS)
|
||||
|
||||
// Match middleware cookie characteristics first (host-only, SameSiteStrict)
|
||||
primaryCookie := &http.Cookie{
|
||||
Name: "csrf_token",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
Expires: time.Unix(0, 0),
|
||||
MaxAge: -1,
|
||||
}
|
||||
http.SetCookie(w, primaryCookie)
|
||||
helper.LogInfo(fmt.Sprintf("CSRF cookie clear #1 sent: Name=%s, Domain=%s, Secure=%v, SameSite=%v",
|
||||
primaryCookie.Name, primaryCookie.Domain, primaryCookie.Secure, primaryCookie.SameSite))
|
||||
|
||||
// Fallback for local/dev browser behavior where secure or samesite attributes differ
|
||||
fallbackCookie := &http.Cookie{
|
||||
Name: "csrf_token",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: isSecure,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Expires: time.Unix(0, 0),
|
||||
MaxAge: -1,
|
||||
}
|
||||
http.SetCookie(w, fallbackCookie)
|
||||
helper.LogInfo(fmt.Sprintf("CSRF cookie clear #2 sent: Name=%s, Domain=%s, Secure=%v, SameSite=%v",
|
||||
fallbackCookie.Name, fallbackCookie.Domain, fallbackCookie.Secure, fallbackCookie.SameSite))
|
||||
|
||||
if !isSecure {
|
||||
localhostCookie := &http.Cookie{
|
||||
Name: "csrf_token",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
Domain: "localhost",
|
||||
HttpOnly: true,
|
||||
Secure: false,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Expires: time.Unix(0, 0),
|
||||
MaxAge: -1,
|
||||
}
|
||||
http.SetCookie(w, localhostCookie)
|
||||
helper.LogInfo(fmt.Sprintf("CSRF cookie clear #3 sent: Name=%s, Domain=%s, Secure=%v, SameSite=%v",
|
||||
localhostCookie.Name, localhostCookie.Domain, localhostCookie.Secure, localhostCookie.SameSite))
|
||||
}
|
||||
|
||||
helper.LogInfo("CSRF token cookie clearing commands sent to browser")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user