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..49d825eb 100644 --- a/backend/internal/handlers/handlers.go +++ b/backend/internal/handlers/handlers.go @@ -1024,6 +1024,18 @@ func (h *AuthHandler) GetRoom(c echo.Context) error { return c.String(http.StatusUnauthorized, "Unauthorized request") } + // Check if caller has access (paid or active trial) + 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") + } + + if !hasAccess { + _ = notifications.SendTelegramNotification(fmt.Sprintf("Unsubscribed user %s tried to join room %s", user.ID, room.Name), h.Config) + return c.JSON(http.StatusPaymentRequired, 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..e865df6d 100644 --- a/backend/internal/handlers/slackHandlers.go +++ b/backend/internal/handlers/slackHandlers.go @@ -558,6 +558,17 @@ 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) + 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") + } + + if !hasAccess { + return c.JSON(http.StatusPaymentRequired, 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/backend/internal/handlers/utils.go b/backend/internal/handlers/utils.go index 3152d582..bfe53690 100644 --- a/backend/internal/handlers/utils.go +++ b/backend/internal/handlers/utils.go @@ -197,3 +197,25 @@ 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) { + // 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) + } + + 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 c9fc7c55..69a40535 100644 --- a/tauri/src/lib/deepLinkUtils.ts +++ b/tauri/src/lib/deepLinkUtils.ts @@ -91,6 +91,13 @@ 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("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 { 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],