From b50ac0a68082a6d1ac2d53ac8fad254d7844625c Mon Sep 17 00:00:00 2001 From: Iason Paraskevopoulos Date: Mon, 9 Feb 2026 22:52:17 +0000 Subject: [PATCH 1/3] fix: block entering a room for not active accounts --- .pre-commit-config.yaml | 2 +- backend/internal/handlers/handlers.go | 17 +++++++++++++++++ backend/internal/handlers/slackHandlers.go | 16 ++++++++++++++++ tauri/src/lib/deepLinkUtils.ts | 7 ++++++- tauri/src/windows/main-window/tabs/Rooms.tsx | 8 ++++++-- 5 files changed, 46 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 23a317db..58a1e711 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: entry: bash -c "yarn pretty-quick --staged" - repo: https://github.com/golangci/golangci-lint - rev: v2.7.2 + rev: v2.8.0 hooks: - id: golangci-lint entry: bash -c 'cd backend && golangci-lint run --timeout=5m --config ../.golangcli.yml' diff --git a/backend/internal/handlers/handlers.go b/backend/internal/handlers/handlers.go index 1465012e..0a225d3a 100644 --- a/backend/internal/handlers/handlers.go +++ b/backend/internal/handlers/handlers.go @@ -1024,6 +1024,23 @@ func (h *AuthHandler) GetRoom(c echo.Context) error { return c.String(http.StatusUnauthorized, "Unauthorized request") } + // Check if caller has access (paid or active trial) + userWithSub, err := models.GetUserWithSubscription(h.DB, user) + if err != nil { + c.Logger().Error("Error getting user subscription: ", err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to check subscription status") + } + + hasAccess := userWithSub.IsPro + if !hasAccess && userWithSub.IsTrial && userWithSub.TrialEndsAt != nil { + hasAccess = userWithSub.TrialEndsAt.After(time.Now()) + } + + if !hasAccess { + _ = notifications.SendTelegramNotification(fmt.Sprintf("Unsubscribed user %s tried to join room %s", user.ID, room.Name), h.Config) + return c.JSON(http.StatusForbidden, map[string]string{"error": "trial-ended"}) + } + tokens, err := generateLiveKitTokens(&h.ServerState, room.ID, user) if err != nil { c.Logger().Error("Failed to generate room tokens:", err) diff --git a/backend/internal/handlers/slackHandlers.go b/backend/internal/handlers/slackHandlers.go index 158d3431..fa1decde 100644 --- a/backend/internal/handlers/slackHandlers.go +++ b/backend/internal/handlers/slackHandlers.go @@ -558,6 +558,22 @@ func (h *SlackHandler) GetSessionTokens(c echo.Context) error { return echo.NewHTTPError(http.StatusForbidden, "You don't have access to this session") } + // Check if user has access (paid or active trial) + userWithSub, err2 := models.GetUserWithSubscription(h.DB, user) + if err2 != nil { + c.Logger().Error("Error getting user subscription: ", err2) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to check subscription status") + } + + hasAccess := userWithSub.IsPro + if !hasAccess && userWithSub.IsTrial && userWithSub.TrialEndsAt != nil { + hasAccess = userWithSub.TrialEndsAt.After(time.Now()) + } + + if !hasAccess { + return c.JSON(http.StatusForbidden, map[string]string{"error": "trial-ended"}) + } + // Generate LiveKit tokens for this room tokens, err := generateLiveKitTokens(&h.ServerState, room.ID, user) if err != nil { diff --git a/tauri/src/lib/deepLinkUtils.ts b/tauri/src/lib/deepLinkUtils.ts index c9fc7c55..c859003d 100644 --- a/tauri/src/lib/deepLinkUtils.ts +++ b/tauri/src/lib/deepLinkUtils.ts @@ -92,7 +92,12 @@ export const handleJoinSessionDeepLink = async (sessionId: string): Promise null); + if (body?.error === "trial-ended") { + toast.error("Trial has expired, contact us if you want to extend it"); + } else { + toast.error("You don't have access to this session. It belongs to a different team."); + } } else { toast.error("Failed to join session"); } diff --git a/tauri/src/windows/main-window/tabs/Rooms.tsx b/tauri/src/windows/main-window/tabs/Rooms.tsx index 8878d839..4de90d88 100644 --- a/tauri/src/windows/main-window/tabs/Rooms.tsx +++ b/tauri/src/windows/main-window/tabs/Rooms.tsx @@ -294,8 +294,12 @@ export const Rooms = () => { cameraWindowOpen: false, krispToggle: true, }); - } catch (error) { - toast.error("Error joining room"); + } catch (error: any) { + if (error?.error === "trial-ended") { + toast.error("Trial has expired, contact us if you want to extend it"); + } else { + toast.error("Error joining room"); + } } }, [getRoomTokens, callTokens, setCallTokens, endCall], From 2980e3dc0314a1873304b5b35984d64d7f567313 Mon Sep 17 00:00:00 2001 From: Iason Paraskevopoulos Date: Tue, 10 Feb 2026 23:09:10 +0000 Subject: [PATCH 2/3] chore: review --- backend/internal/handlers/handlers.go | 7 +------ backend/internal/handlers/slackHandlers.go | 13 ++++--------- backend/internal/handlers/utils.go | 17 +++++++++++++++++ backend/internal/handlers/websocketHandlers.go | 11 ++--------- tauri/src/lib/deepLinkUtils.ts | 6 ++++-- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/backend/internal/handlers/handlers.go b/backend/internal/handlers/handlers.go index 0a225d3a..b39a07f5 100644 --- a/backend/internal/handlers/handlers.go +++ b/backend/internal/handlers/handlers.go @@ -1025,17 +1025,12 @@ func (h *AuthHandler) GetRoom(c echo.Context) error { } // Check if caller has access (paid or active trial) - userWithSub, err := models.GetUserWithSubscription(h.DB, user) + hasAccess, err := checkUserHasAccess(h.DB, user) if err != nil { c.Logger().Error("Error getting user subscription: ", err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to check subscription status") } - hasAccess := userWithSub.IsPro - if !hasAccess && userWithSub.IsTrial && userWithSub.TrialEndsAt != nil { - hasAccess = userWithSub.TrialEndsAt.After(time.Now()) - } - if !hasAccess { _ = notifications.SendTelegramNotification(fmt.Sprintf("Unsubscribed user %s tried to join room %s", user.ID, room.Name), h.Config) return c.JSON(http.StatusForbidden, map[string]string{"error": "trial-ended"}) diff --git a/backend/internal/handlers/slackHandlers.go b/backend/internal/handlers/slackHandlers.go index fa1decde..e865df6d 100644 --- a/backend/internal/handlers/slackHandlers.go +++ b/backend/internal/handlers/slackHandlers.go @@ -559,19 +559,14 @@ func (h *SlackHandler) GetSessionTokens(c echo.Context) error { } // Check if user has access (paid or active trial) - userWithSub, err2 := models.GetUserWithSubscription(h.DB, user) - if err2 != nil { - c.Logger().Error("Error getting user subscription: ", err2) + hasAccess, err := checkUserHasAccess(h.DB, user) + if err != nil { + c.Logger().Error("Error getting user subscription: ", err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to check subscription status") } - hasAccess := userWithSub.IsPro - if !hasAccess && userWithSub.IsTrial && userWithSub.TrialEndsAt != nil { - hasAccess = userWithSub.TrialEndsAt.After(time.Now()) - } - if !hasAccess { - return c.JSON(http.StatusForbidden, map[string]string{"error": "trial-ended"}) + return c.JSON(http.StatusPaymentRequired, map[string]string{"error": "trial-ended"}) } // Generate LiveKit tokens for this room diff --git a/backend/internal/handlers/utils.go b/backend/internal/handlers/utils.go index 3152d582..5a38e44f 100644 --- a/backend/internal/handlers/utils.go +++ b/backend/internal/handlers/utils.go @@ -197,3 +197,20 @@ func extractUserIDFromIdentity(identity string) (string, error) { } return "", fmt.Errorf("invalid identity format: %s", identity) } + +// checkUserHasAccess checks if a user has an active subscription or trial. +// Returns true if the user is a Pro subscriber or has an active trial, false otherwise. +// Returns an error if the subscription check fails. +func checkUserHasAccess(db *gorm.DB, user *models.User) (bool, error) { + userWithSub, err := models.GetUserWithSubscription(db, user) + if err != nil { + return false, fmt.Errorf("failed to get user subscription: %w", err) + } + + hasAccess := userWithSub.IsPro + if !hasAccess && userWithSub.IsTrial && userWithSub.TrialEndsAt != nil { + hasAccess = userWithSub.TrialEndsAt.After(time.Now()) + } + + return hasAccess, nil +} diff --git a/backend/internal/handlers/websocketHandlers.go b/backend/internal/handlers/websocketHandlers.go index bf295e31..d59107a9 100644 --- a/backend/internal/handlers/websocketHandlers.go +++ b/backend/internal/handlers/websocketHandlers.go @@ -9,7 +9,6 @@ import ( "hopp-backend/internal/models" "hopp-backend/internal/notifications" "net/http" - "time" "github.com/google/uuid" "github.com/gorilla/websocket" @@ -272,20 +271,14 @@ func initiateCall(ctx echo.Context, s *common.ServerState, ws *websocket.Conn, r return } - callerWithSub, err := models.GetUserWithSubscription(s.DB, caller) + // Check if caller has access (paid or active trial) + hasAccess, err := checkUserHasAccess(s.DB, caller) if err != nil { ctx.Logger().Error("Error getting caller subscription: ", err) sendWSErrorMessage(ws, "Failed to check subscription status") return } - // Check if caller has access (paid or active trial) - hasAccess := callerWithSub.IsPro - if !hasAccess && callerWithSub.IsTrial && callerWithSub.TrialEndsAt != nil { - // Check if trial is still active - hasAccess = callerWithSub.TrialEndsAt.After(time.Now()) - } - if !hasAccess { ctx.Logger().Warn("Caller does not have active subscription or trial: ", callerId) msg := messages.NewRejectCallMessage(calleeID, "trial-ended") diff --git a/tauri/src/lib/deepLinkUtils.ts b/tauri/src/lib/deepLinkUtils.ts index c859003d..69a40535 100644 --- a/tauri/src/lib/deepLinkUtils.ts +++ b/tauri/src/lib/deepLinkUtils.ts @@ -91,13 +91,15 @@ export const handleJoinSessionDeepLink = async (sessionId: string): Promise null); if (body?.error === "trial-ended") { toast.error("Trial has expired, contact us if you want to extend it"); } else { - toast.error("You don't have access to this session. It belongs to a different team."); + toast.error("Payment required to access this session"); } + } else if (response.status === 403) { + toast.error("You don't have access to this session. It belongs to a different team."); } else { toast.error("Failed to join session"); } From fdd7e41cd6eaddb8291218d55298420ab04f98bb Mon Sep 17 00:00:00 2001 From: Iason Paraskevopoulos Date: Wed, 11 Feb 2026 08:18:07 +0000 Subject: [PATCH 3/3] fixes --- backend/internal/handlers/handlers.go | 2 +- backend/internal/handlers/utils.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/internal/handlers/handlers.go b/backend/internal/handlers/handlers.go index b39a07f5..49d825eb 100644 --- a/backend/internal/handlers/handlers.go +++ b/backend/internal/handlers/handlers.go @@ -1033,7 +1033,7 @@ func (h *AuthHandler) GetRoom(c echo.Context) error { if !hasAccess { _ = notifications.SendTelegramNotification(fmt.Sprintf("Unsubscribed user %s tried to join room %s", user.ID, room.Name), h.Config) - return c.JSON(http.StatusForbidden, map[string]string{"error": "trial-ended"}) + return c.JSON(http.StatusPaymentRequired, map[string]string{"error": "trial-ended"}) } tokens, err := generateLiveKitTokens(&h.ServerState, room.ID, user) diff --git a/backend/internal/handlers/utils.go b/backend/internal/handlers/utils.go index 5a38e44f..bfe53690 100644 --- a/backend/internal/handlers/utils.go +++ b/backend/internal/handlers/utils.go @@ -202,6 +202,11 @@ func extractUserIDFromIdentity(identity string) (string, error) { // Returns true if the user is a Pro subscriber or has an active trial, false otherwise. // Returns an error if the subscription check fails. func checkUserHasAccess(db *gorm.DB, user *models.User) (bool, error) { + // If user has no team, they have no subscription or trial access + if user.TeamID == nil { + return false, nil + } + userWithSub, err := models.GetUserWithSubscription(db, user) if err != nil { return false, fmt.Errorf("failed to get user subscription: %w", err)