mirror of
https://github.com/veops/oneterm.git
synced 2025-10-15 20:00:38 +08:00
197 lines
5.9 KiB
Go
197 lines
5.9 KiB
Go
package web_proxy
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/veops/oneterm/internal/model"
|
|
"github.com/veops/oneterm/internal/repository"
|
|
gsession "github.com/veops/oneterm/internal/session"
|
|
"github.com/veops/oneterm/pkg/logger"
|
|
)
|
|
|
|
// Global session storage
|
|
var webProxySessions = make(map[string]*WebProxySession)
|
|
|
|
// WebProxySession represents an active web proxy session
|
|
type WebProxySession struct {
|
|
SessionId string
|
|
AssetId int
|
|
AccountId int
|
|
Asset *model.Asset
|
|
CreatedAt time.Time
|
|
LastActivity time.Time
|
|
LastHeartbeat time.Time // Track heartbeat separately
|
|
IsActive bool // Active for concurrent control (heartbeat-based)
|
|
CurrentHost string
|
|
Permissions *model.AuthPermissions // User permissions for this asset
|
|
WebConfig *model.WebConfig // Web-specific configuration
|
|
}
|
|
|
|
// cleanupExpiredSessions implements layered timeout mechanism
|
|
func cleanupExpiredSessions(maxInactiveTime time.Duration) {
|
|
now := time.Now()
|
|
deactivatedCount := 0
|
|
cleanedCount := 0
|
|
|
|
// Layer 1: Concurrent control timeout (fast)
|
|
heartbeatTimeout := 45 * time.Second
|
|
|
|
// Layer 2: Session expiry timeout (slow, system config)
|
|
|
|
for sessionID, session := range webProxySessions {
|
|
// Layer 1: Check heartbeat for concurrent control
|
|
if session.IsActive && !session.LastHeartbeat.IsZero() &&
|
|
now.Sub(session.LastHeartbeat) > heartbeatTimeout {
|
|
// Deactivate session (release concurrent slot AND mark as offline)
|
|
session.IsActive = false
|
|
UpdateWebSessionStatus(sessionID, model.SESSIONSTATUS_OFFLINE)
|
|
deactivatedCount++
|
|
}
|
|
|
|
// Layer 2: Check session expiry for final cleanup
|
|
shouldDelete := false
|
|
if now.Sub(session.LastActivity) > maxInactiveTime {
|
|
shouldDelete = true
|
|
}
|
|
|
|
if shouldDelete {
|
|
// No need to update status again - already done in Layer 1
|
|
delete(webProxySessions, sessionID)
|
|
cleanedCount++
|
|
}
|
|
}
|
|
|
|
if deactivatedCount > 0 || cleanedCount > 0 {
|
|
logger.L().Debug("Session cleanup completed",
|
|
zap.Int("deactivated", deactivatedCount),
|
|
zap.Int("deleted", cleanedCount))
|
|
}
|
|
}
|
|
|
|
// StartSessionCleanupRoutine starts background cleanup routine for web sessions
|
|
func StartSessionCleanupRoutine() {
|
|
// More frequent cleanup - every 30 seconds to catch closed browser tabs quickly
|
|
ticker := time.NewTicker(30 * time.Second)
|
|
go func() {
|
|
for range ticker.C {
|
|
// Use system configured timeout (same as other protocols)
|
|
systemTimeout := time.Duration(model.GlobalConfig.Load().Timeout) * time.Second
|
|
cleanupExpiredSessions(systemTimeout)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// GetSession retrieves a session by ID
|
|
func GetSession(sessionID string) (*WebProxySession, bool) {
|
|
session, exists := webProxySessions[sessionID]
|
|
return session, exists
|
|
}
|
|
|
|
// StoreSession stores a session in the session map
|
|
func StoreSession(sessionID string, session *WebProxySession) {
|
|
webProxySessions[sessionID] = session
|
|
}
|
|
|
|
// DeleteSession removes a session from the session map
|
|
func DeleteSession(sessionID string) {
|
|
delete(webProxySessions, sessionID)
|
|
}
|
|
|
|
// UpdateSessionActivity updates the last activity time for a session
|
|
func UpdateSessionActivity(sessionID string) {
|
|
if session, exists := webProxySessions[sessionID]; exists {
|
|
session.LastActivity = time.Now()
|
|
}
|
|
}
|
|
|
|
// UpdateSessionHeartbeat updates the last heartbeat time for a session
|
|
func UpdateSessionHeartbeat(sessionID string) {
|
|
if session, exists := webProxySessions[sessionID]; exists {
|
|
now := time.Now()
|
|
wasInactive := !session.IsActive
|
|
|
|
session.LastHeartbeat = now
|
|
session.IsActive = true // Re-activate session on heartbeat
|
|
// Heartbeat also counts as activity (user is still viewing the page)
|
|
session.LastActivity = now
|
|
|
|
// If session was previously inactive, mark it as online again
|
|
if wasInactive {
|
|
UpdateWebSessionStatus(sessionID, model.SESSIONSTATUS_ONLINE)
|
|
}
|
|
}
|
|
}
|
|
|
|
// UpdateSessionHost updates the current host for a session
|
|
func UpdateSessionHost(sessionID string, host string) {
|
|
if session, exists := webProxySessions[sessionID]; exists {
|
|
session.CurrentHost = host
|
|
}
|
|
}
|
|
|
|
// GetActiveSessionsForAsset returns the number of active sessions for an asset
|
|
func GetActiveSessionsForAsset(assetID int) int {
|
|
// First cleanup expired sessions to get accurate count
|
|
systemTimeout := time.Duration(model.GlobalConfig.Load().Timeout) * time.Second
|
|
cleanupExpiredSessions(systemTimeout)
|
|
|
|
count := 0
|
|
for _, session := range webProxySessions {
|
|
if session.AssetId == assetID && session.IsActive {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
// GetAllSessions returns all active sessions
|
|
func GetAllSessions() map[string]*WebProxySession {
|
|
return webProxySessions
|
|
}
|
|
|
|
// CountActiveSessions returns the total number of active sessions
|
|
func CountActiveSessions() int {
|
|
return len(webProxySessions)
|
|
}
|
|
|
|
// CloseWebSession closes and removes a session
|
|
func CloseWebSession(sessionID string) {
|
|
if session, exists := webProxySessions[sessionID]; exists {
|
|
logger.L().Info("Closing web session",
|
|
zap.String("sessionID", sessionID),
|
|
zap.Int("assetID", session.AssetId),
|
|
zap.Int("accountID", session.AccountId))
|
|
|
|
// Update database session record to offline status
|
|
UpdateWebSessionStatus(sessionID, model.SESSIONSTATUS_OFFLINE)
|
|
|
|
delete(webProxySessions, sessionID)
|
|
}
|
|
}
|
|
|
|
// UpdateWebSessionStatus updates the session status in database
|
|
func UpdateWebSessionStatus(sessionID string, status int) {
|
|
// Use repository to get and update database session status
|
|
repo := repository.NewSessionRepository()
|
|
if dbSession, err := repo.GetSession(context.Background(), sessionID); err == nil && dbSession != nil {
|
|
now := time.Now()
|
|
dbSession.Status = status
|
|
if status == model.SESSIONSTATUS_OFFLINE {
|
|
dbSession.ClosedAt = &now
|
|
}
|
|
dbSession.UpdatedAt = now
|
|
|
|
// Save updated session to database
|
|
fullSession := &gsession.Session{Session: dbSession}
|
|
if err := gsession.UpsertSession(fullSession); err != nil {
|
|
logger.L().Error("Failed to update web session status in database",
|
|
zap.String("sessionID", sessionID),
|
|
zap.Int("status", status),
|
|
zap.Error(err))
|
|
}
|
|
}
|
|
}
|