diff --git a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/landing/page.tsx b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/landing/page.tsx index 5dea2c22d6..5bc2b635e7 100644 --- a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/landing/page.tsx +++ b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/landing/page.tsx @@ -1,27 +1,25 @@ import { LandingSidebar } from "@/app/(app)/(onboarding)/organizations/[organizationId]/landing/components/landing-sidebar"; -import { authOptions } from "@/modules/auth/lib/authOptions"; import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/utils"; +import { getOrganizationAuth } from "@/modules/organization/lib/utils"; import { Header } from "@/modules/ui/components/header"; import { getTranslate } from "@/tolgee/server"; -import { getServerSession } from "next-auth"; import { notFound, redirect } from "next/navigation"; -import { getOrganization, getOrganizationsByUserId } from "@formbricks/lib/organization/service"; +import { getOrganizationsByUserId } from "@formbricks/lib/organization/service"; import { getUser } from "@formbricks/lib/user/service"; const Page = async (props) => { const params = await props.params; const t = await getTranslate(); - const session = await getServerSession(authOptions); - if (!session || !session.user) { + + const { session, organization } = await getOrganizationAuth(params.organizationId); + + if (!session?.user) { return redirect(`/auth/login`); } const user = await getUser(session.user.id); if (!user) return notFound(); - const organization = await getOrganization(params.organizationId); - if (!organization) return notFound(); - const organizations = await getOrganizationsByUserId(session.user.id); const { features } = await getEnterpriseLicense(); diff --git a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/channel/page.tsx b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/channel/page.tsx index 4ac6aa42bb..4309addd10 100644 --- a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/channel/page.tsx +++ b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/channel/page.tsx @@ -1,10 +1,9 @@ import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer"; -import { authOptions } from "@/modules/auth/lib/authOptions"; +import { getOrganizationAuth } from "@/modules/organization/lib/utils"; import { Button } from "@/modules/ui/components/button"; import { Header } from "@/modules/ui/components/header"; import { getTranslate } from "@/tolgee/server"; import { PictureInPicture2Icon, SendIcon, XIcon } from "lucide-react"; -import { getServerSession } from "next-auth"; import Link from "next/link"; import { redirect } from "next/navigation"; import { getUserProjects } from "@formbricks/lib/project/service"; @@ -17,8 +16,10 @@ interface ChannelPageProps { const Page = async (props: ChannelPageProps) => { const params = await props.params; - const session = await getServerSession(authOptions); - if (!session || !session.user) { + + const { session } = await getOrganizationAuth(params.organizationId); + + if (!session?.user) { return redirect(`/auth/login`); } diff --git a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/mode/page.tsx b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/mode/page.tsx index c3574c0a9c..a570a6ed89 100644 --- a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/mode/page.tsx +++ b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/mode/page.tsx @@ -1,10 +1,9 @@ import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer"; -import { authOptions } from "@/modules/auth/lib/authOptions"; +import { getOrganizationAuth } from "@/modules/organization/lib/utils"; import { Button } from "@/modules/ui/components/button"; import { Header } from "@/modules/ui/components/header"; import { getTranslate } from "@/tolgee/server"; import { HeartIcon, ListTodoIcon, XIcon } from "lucide-react"; -import { getServerSession } from "next-auth"; import Link from "next/link"; import { redirect } from "next/navigation"; import { getUserProjects } from "@formbricks/lib/project/service"; @@ -17,8 +16,10 @@ interface ModePageProps { const Page = async (props: ModePageProps) => { const params = await props.params; - const session = await getServerSession(authOptions); - if (!session || !session.user) { + + const { session } = await getOrganizationAuth(params.organizationId); + + if (!session?.user) { return redirect(`/auth/login`); } diff --git a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/settings/page.tsx b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/settings/page.tsx index 9c7d4f856c..5a6098b3d3 100644 --- a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/settings/page.tsx +++ b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/settings/page.tsx @@ -1,16 +1,14 @@ import { getTeamsByOrganizationId } from "@/app/(app)/(onboarding)/lib/onboarding"; import { ProjectSettings } from "@/app/(app)/(onboarding)/organizations/[organizationId]/projects/new/settings/components/ProjectSettings"; -import { authOptions } from "@/modules/auth/lib/authOptions"; import { getRoleManagementPermission } from "@/modules/ee/license-check/lib/utils"; +import { getOrganizationAuth } from "@/modules/organization/lib/utils"; import { Button } from "@/modules/ui/components/button"; import { Header } from "@/modules/ui/components/header"; import { getTranslate } from "@/tolgee/server"; import { XIcon } from "lucide-react"; -import { getServerSession } from "next-auth"; import Link from "next/link"; import { redirect } from "next/navigation"; import { DEFAULT_BRAND_COLOR } from "@formbricks/lib/constants"; -import { getOrganization } from "@formbricks/lib/organization/service"; import { getUserProjects } from "@formbricks/lib/project/service"; import { TProjectConfigChannel, TProjectConfigIndustry, TProjectMode } from "@formbricks/types/project"; @@ -29,25 +27,20 @@ const Page = async (props: ProjectSettingsPageProps) => { const searchParams = await props.searchParams; const params = await props.params; const t = await getTranslate(); - const session = await getServerSession(authOptions); - if (!session || !session.user) { + const { session, organization } = await getOrganizationAuth(params.organizationId); + + if (!session?.user) { return redirect(`/auth/login`); } - const channel = searchParams.channel || null; - const industry = searchParams.industry || null; - const mode = searchParams.mode || "surveys"; + const channel = searchParams.channel ?? null; + const industry = searchParams.industry ?? null; + const mode = searchParams.mode ?? "surveys"; const projects = await getUserProjects(session.user.id, params.organizationId); const organizationTeams = await getTeamsByOrganizationId(params.organizationId); - const organization = await getOrganization(params.organizationId); - - if (!organization) { - throw new Error(t("common.organization_not_found")); - } - const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan); if (!organizationTeams) { diff --git a/apps/web/app/(app)/environments/[environmentId]/page.tsx b/apps/web/app/(app)/environments/[environmentId]/page.tsx index 96739edc0c..062dfe781e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/page.tsx @@ -1,24 +1,11 @@ -import { authOptions } from "@/modules/auth/lib/authOptions"; -import { getTranslate } from "@/tolgee/server"; -import { getServerSession } from "next-auth"; +import { getEnvironmentAuth } from "@/modules/environments/lib/utils"; import { redirect } from "next/navigation"; import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; const EnvironmentPage = async (props) => { const params = await props.params; - const session = await getServerSession(authOptions); - const t = await getTranslate(); - const organization = await getOrganizationByEnvironmentId(params.environmentId); - - if (!session) { - return redirect(`/auth/login`); - } - - if (!organization) { - throw new Error(t("common.organization_not_found")); - } + const { session, organization } = await getEnvironmentAuth(params.environmentId); const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const { isBilling } = getAccessFlags(currentUserMembership?.role); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/page.test.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/page.test.tsx index 097a427527..b31e6432d6 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/page.test.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/page.test.tsx @@ -3,15 +3,11 @@ import { getIsOrganizationAIReady, getWhiteLabelPermission, } from "@/modules/ee/license-check/lib/utils"; +import { getEnvironmentAuth } from "@/modules/environments/lib/utils"; +import { TEnvironmentAuth } from "@/modules/environments/types/environment-auth"; import { getTranslate } from "@/tolgee/server"; -import { getServerSession } from "next-auth"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; -import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getUser } from "@formbricks/lib/user/service"; -import { TMembership } from "@formbricks/types/memberships"; -import { TOrganization } from "@formbricks/types/organizations"; import { TUser } from "@formbricks/types/user"; import Page from "./page"; @@ -45,10 +41,6 @@ vi.mock("@formbricks/lib/constants", () => ({ AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID: "mock-azure-embeddings-deployment-id", })); -vi.mock("next-auth", () => ({ - getServerSession: vi.fn(), -})); - vi.mock("@/tolgee/server", () => ({ getTranslate: vi.fn(), })); @@ -57,16 +49,8 @@ vi.mock("@formbricks/lib/user/service", () => ({ getUser: vi.fn(), })); -vi.mock("@formbricks/lib/organization/service", () => ({ - getOrganizationByEnvironmentId: vi.fn(), -})); - -vi.mock("@formbricks/lib/membership/service", () => ({ - getMembershipByUserIdOrganizationId: vi.fn(), -})); - -vi.mock("@formbricks/lib/membership/utils", () => ({ - getAccessFlags: vi.fn(), +vi.mock("@/modules/environments/lib/utils", () => ({ + getEnvironmentAuth: vi.fn(), })); vi.mock("@/modules/ee/license-check/lib/utils", () => ({ @@ -76,26 +60,21 @@ vi.mock("@/modules/ee/license-check/lib/utils", () => ({ })); describe("Page", () => { - const mockParams = { environmentId: "test-environment-id" }; - const mockSession = { user: { id: "test-user-id" } }; + let mockEnvironmentAuth = { + session: { user: { id: "test-user-id" } }, + currentUserMembership: { role: "owner" }, + organization: { id: "test-organization-id", billing: { plan: "free" } }, + isOwner: true, + isManager: false, + } as unknown as TEnvironmentAuth; + const mockUser = { id: "test-user-id" } as TUser; - const mockOrganization = { id: "test-organization-id", billing: { plan: "free" } } as TOrganization; - const mockMembership = { role: "owner" } as TMembership; const mockTranslate = vi.fn((key) => key); beforeEach(() => { - vi.clearAllMocks(); - vi.mocked(getServerSession).mockResolvedValue(mockSession); vi.mocked(getTranslate).mockResolvedValue(mockTranslate); vi.mocked(getUser).mockResolvedValue(mockUser); - vi.mocked(getOrganizationByEnvironmentId).mockResolvedValue(mockOrganization); - vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue(mockMembership); - vi.mocked(getAccessFlags).mockReturnValue({ - isOwner: true, - isManager: false, - isBilling: false, - isMember: false, - }); + vi.mocked(getEnvironmentAuth).mockResolvedValue(mockEnvironmentAuth); vi.mocked(getIsMultiOrgEnabled).mockResolvedValue(true); vi.mocked(getIsOrganizationAIReady).mockResolvedValue(true); vi.mocked(getWhiteLabelPermission).mockResolvedValue(true); @@ -111,8 +90,10 @@ describe("Page", () => { expect(result).toBeTruthy(); }); - it("renders if session user id is null", async () => { - vi.mocked(getServerSession).mockResolvedValue({ user: { id: null } }); + it("renders if session user id empty", async () => { + mockEnvironmentAuth.session.user.id = ""; + + vi.mocked(getEnvironmentAuth).mockResolvedValue(mockEnvironmentAuth); const props = { params: Promise.resolve({ environmentId: "env-123" }), @@ -123,17 +104,13 @@ describe("Page", () => { expect(result).toBeTruthy(); }); - it("throws an error if the session is not found", async () => { - vi.mocked(getServerSession).mockResolvedValue(null); + it("handles getEnvironmentAuth error", async () => { + vi.mocked(getEnvironmentAuth).mockRejectedValue(new Error("Authentication error")); - await expect(Page({ params: Promise.resolve(mockParams) })).rejects.toThrow("common.session_not_found"); - }); + const props = { + params: Promise.resolve({ environmentId: "env-123" }), + }; - it("throws an error if the organization is not found", async () => { - vi.mocked(getOrganizationByEnvironmentId).mockResolvedValue(null); - - await expect(Page({ params: Promise.resolve(mockParams) })).rejects.toThrow( - "common.organization_not_found" - ); + await expect(Page(props)).rejects.toThrow("Authentication error"); }); }); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/page.tsx index f926cd6580..d50c058260 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/page.tsx @@ -1,21 +1,17 @@ import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar"; import { AIToggle } from "@/app/(app)/environments/[environmentId]/settings/(organization)/general/components/AIToggle"; -import { authOptions } from "@/modules/auth/lib/authOptions"; import { getIsMultiOrgEnabled, getIsOrganizationAIReady, getWhiteLabelPermission, } from "@/modules/ee/license-check/lib/utils"; import { EmailCustomizationSettings } from "@/modules/ee/whitelabel/email-customization/components/email-customization-settings"; +import { getEnvironmentAuth } from "@/modules/environments/lib/utils"; import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper"; import { PageHeader } from "@/modules/ui/components/page-header"; import { SettingsId } from "@/modules/ui/components/settings-id"; import { getTranslate } from "@/tolgee/server"; -import { getServerSession } from "next-auth"; import { FB_LOGO_URL, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; -import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; -import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getUser } from "@formbricks/lib/user/service"; import { SettingsCard } from "../../components/SettingsCard"; import { DeleteOrganization } from "./components/DeleteOrganization"; @@ -24,20 +20,13 @@ import { EditOrganizationNameForm } from "./components/EditOrganizationNameForm" const Page = async (props: { params: Promise<{ environmentId: string }> }) => { const params = await props.params; const t = await getTranslate(); - const session = await getServerSession(authOptions); - if (!session) { - throw new Error(t("common.session_not_found")); - } + + const { session, currentUserMembership, organization, isOwner, isManager } = await getEnvironmentAuth( + params.environmentId + ); + const user = session?.user?.id ? await getUser(session.user.id) : null; - const organization = await getOrganizationByEnvironmentId(params.environmentId); - - if (!organization) { - throw new Error(t("common.organization_not_found")); - } - - const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); - const { isOwner, isManager } = getAccessFlags(currentUserMembership?.role); const isMultiOrgEnabled = await getIsMultiOrgEnabled(); const hasWhiteLabelPermission = await getWhiteLabelPermission(organization.billing.plan); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx index d087986bfb..eb653e4003 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx @@ -3,24 +3,16 @@ import { ResponsePage } from "@/app/(app)/environments/[environmentId]/surveys/[ import { EnableInsightsBanner } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/EnableInsightsBanner"; import { SurveyAnalysisCTA } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA"; import { needsInsightsGeneration } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils"; -import { authOptions } from "@/modules/auth/lib/authOptions"; import { getIsAIEnabled } from "@/modules/ee/license-check/lib/utils"; -import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles"; -import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams"; +import { getEnvironmentAuth } from "@/modules/environments/lib/utils"; import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper"; import { PageHeader } from "@/modules/ui/components/page-header"; import { getTranslate } from "@/tolgee/server"; -import { getServerSession } from "next-auth"; import { MAX_RESPONSES_FOR_INSIGHT_GENERATION, RESPONSES_PER_PAGE, WEBAPP_URL, } from "@formbricks/lib/constants"; -import { getEnvironment } from "@formbricks/lib/environment/service"; -import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; -import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; -import { getProjectByEnvironmentId } from "@formbricks/lib/project/service"; import { getResponseCountBySurveyId } from "@formbricks/lib/response/service"; import { getSurvey } from "@formbricks/lib/survey/service"; import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service"; @@ -30,47 +22,25 @@ import { findMatchingLocale } from "@formbricks/lib/utils/locale"; const Page = async (props) => { const params = await props.params; const t = await getTranslate(); - const session = await getServerSession(authOptions); - if (!session) { - throw new Error(t("common.session_not_found")); - } - const [survey, environment] = await Promise.all([ - getSurvey(params.surveyId), - getEnvironment(params.environmentId), - ]); - if (!environment) { - throw new Error(t("common.environment_not_found")); - } + const { session, environment, organization, isReadOnly } = await getEnvironmentAuth(params.environmentId); + + const survey = await getSurvey(params.surveyId); + if (!survey) { throw new Error(t("common.survey_not_found")); } - const project = await getProjectByEnvironmentId(environment.id); - if (!project) { - throw new Error(t("common.project_not_found")); - } const user = await getUser(session.user.id); + if (!user) { throw new Error(t("common.user_not_found")); } + const tags = await getTagsByEnvironmentId(params.environmentId); - const organization = await getOrganizationByEnvironmentId(params.environmentId); - if (!organization) { - throw new Error(t("common.organization_not_found")); - } - - const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const totalResponseCount = await getResponseCountBySurveyId(params.surveyId); - const { isMember } = getAccessFlags(currentUserMembership?.role); - - const permission = await getProjectPermissionByUserId(session.user.id, project.id); - const { hasReadAccess } = getTeamPermissionFlags(permission); - - const isReadOnly = isMember && hasReadAccess; - const isAIEnabled = await getIsAIEnabled({ isAIEnabled: organization.isAIEnabled, billing: organization.billing, diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/page.tsx index 6557fe7643..018953705f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/page.tsx @@ -3,14 +3,11 @@ import { EnableInsightsBanner } from "@/app/(app)/environments/[environmentId]/s import { SummaryPage } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage"; import { SurveyAnalysisCTA } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA"; import { needsInsightsGeneration } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils"; -import { authOptions } from "@/modules/auth/lib/authOptions"; import { getIsAIEnabled } from "@/modules/ee/license-check/lib/utils"; -import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles"; -import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams"; +import { getEnvironmentAuth } from "@/modules/environments/lib/utils"; import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper"; import { PageHeader } from "@/modules/ui/components/page-header"; import { getTranslate } from "@/tolgee/server"; -import { getServerSession } from "next-auth"; import { notFound } from "next/navigation"; import { DEFAULT_LOCALE, @@ -18,11 +15,6 @@ import { MAX_RESPONSES_FOR_INSIGHT_GENERATION, WEBAPP_URL, } from "@formbricks/lib/constants"; -import { getEnvironment } from "@formbricks/lib/environment/service"; -import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; -import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; -import { getProjectByEnvironmentId } from "@formbricks/lib/project/service"; import { getResponseCountBySurveyId } from "@formbricks/lib/response/service"; import { getSurvey } from "@formbricks/lib/survey/service"; import { getUser } from "@formbricks/lib/user/service"; @@ -30,10 +22,8 @@ import { getUser } from "@formbricks/lib/user/service"; const SurveyPage = async (props: { params: Promise<{ environmentId: string; surveyId: string }> }) => { const params = await props.params; const t = await getTranslate(); - const session = await getServerSession(authOptions); - if (!session) { - throw new Error(t("common.session_not_found")); - } + + const { session, environment, organization, isReadOnly } = await getEnvironmentAuth(params.environmentId); const surveyId = params.surveyId; @@ -41,41 +31,20 @@ const SurveyPage = async (props: { params: Promise<{ environmentId: string; surv return notFound(); } - const [survey, environment] = await Promise.all([ - getSurvey(params.surveyId), - getEnvironment(params.environmentId), - ]); - if (!environment) { - throw new Error(t("common.environment_not_found")); - } + const survey = await getSurvey(params.surveyId); + if (!survey) { throw new Error(t("common.survey_not_found")); } - const project = await getProjectByEnvironmentId(environment.id); - if (!project) { - throw new Error(t("common.project_not_found")); - } - const user = await getUser(session.user.id); + if (!user) { throw new Error(t("common.user_not_found")); } - const organization = await getOrganizationByEnvironmentId(params.environmentId); - - if (!organization) { - throw new Error(t("common.organization_not_found")); - } - const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const totalResponseCount = await getResponseCountBySurveyId(params.surveyId); - const { isMember } = getAccessFlags(currentUserMembership?.role); - const projectPermission = await getProjectPermissionByUserId(session.user.id, project.id); - const { hasReadAccess } = getTeamPermissionFlags(projectPermission); - - const isReadOnly = isMember && hasReadAccess; - // I took this out cause it's cloud only right? // const { active: isEnterpriseEdition } = await getEnterpriseLicense(); diff --git a/apps/web/modules/organization/lib/utils.ts b/apps/web/modules/organization/lib/utils.ts new file mode 100644 index 0000000000..f5041b256d --- /dev/null +++ b/apps/web/modules/organization/lib/utils.ts @@ -0,0 +1,49 @@ +import { authOptions } from "@/modules/auth/lib/authOptions"; +import { getTranslate } from "@/tolgee/server"; +import { getServerSession } from "next-auth"; +import { cache } from "react"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; +import { getAccessFlags } from "@formbricks/lib/membership/utils"; +import { getOrganization } from "@formbricks/lib/organization/service"; +import { TOrganizationAuth } from "../types/organization-auth"; + +/** + * Common utility to fetch organization data and perform authorization checks + * + * Usage: + * const { session, organization, ... } = await getOrganizationAuth(params.organizationId); + */ +export const getOrganizationAuth = cache(async (organizationId: string): Promise => { + const t = await getTranslate(); + + // Perform all fetches in parallel + const [session, organization] = await Promise.all([ + getServerSession(authOptions), + getOrganization(organizationId), + ]); + + if (!session) { + throw new Error(t("common.session_not_found")); + } + + if (!organization) { + throw new Error(t("common.organization_not_found")); + } + + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); + if (!currentUserMembership) { + throw new Error(t("common.membership_not_found")); + } + + const { isMember, isOwner, isManager, isBilling } = getAccessFlags(currentUserMembership?.role); + + return { + organization, + session, + currentUserMembership, + isMember, + isOwner, + isManager, + isBilling, + }; +}); diff --git a/apps/web/modules/organization/settings/teams/page.tsx b/apps/web/modules/organization/settings/teams/page.tsx index 7ca9c69c58..24684ea1ca 100644 --- a/apps/web/modules/organization/settings/teams/page.tsx +++ b/apps/web/modules/organization/settings/teams/page.tsx @@ -1,31 +1,20 @@ import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar"; -import { authOptions } from "@/modules/auth/lib/authOptions"; import { getRoleManagementPermission } from "@/modules/ee/license-check/lib/utils"; import { TeamsView } from "@/modules/ee/teams/team-list/components/teams-view"; +import { getEnvironmentAuth } from "@/modules/environments/lib/utils"; import { MembersView } from "@/modules/organization/settings/teams/components/members-view"; import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper"; import { PageHeader } from "@/modules/ui/components/page-header"; import { getTranslate } from "@/tolgee/server"; -import { getServerSession } from "next-auth"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; -import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; -import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; export const TeamsPage = async (props) => { const params = await props.params; const t = await getTranslate(); - const session = await getServerSession(authOptions); - if (!session) { - throw new Error(t("common.session_not_found")); - } - const organization = await getOrganizationByEnvironmentId(params.environmentId); - if (!organization) { - throw new Error(t("common.organization_not_found")); - } + const { session, currentUserMembership, organization } = await getEnvironmentAuth(params.environmentId); const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan); - const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); return ( diff --git a/apps/web/modules/organization/types/organization-auth.ts b/apps/web/modules/organization/types/organization-auth.ts new file mode 100644 index 0000000000..34a78c709a --- /dev/null +++ b/apps/web/modules/organization/types/organization-auth.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; +import { ZMembership } from "@formbricks/types/memberships"; +import { ZOrganization } from "@formbricks/types/organizations"; +import { ZUser } from "@formbricks/types/user"; + +export const ZOrganizationAuth = z.object({ + organization: ZOrganization, + session: z.object({ + user: ZUser.pick({ id: true }), + expires: z.string(), + }), + currentUserMembership: ZMembership, + isMember: z.boolean(), + isOwner: z.boolean(), + isManager: z.boolean(), + isBilling: z.boolean(), +}); + +export type TOrganizationAuth = z.infer; diff --git a/apps/web/modules/survey/editor/page.tsx b/apps/web/modules/survey/editor/page.tsx index dc3215adef..3c6bec8396 100644 --- a/apps/web/modules/survey/editor/page.tsx +++ b/apps/web/modules/survey/editor/page.tsx @@ -1,21 +1,16 @@ -import { authOptions } from "@/modules/auth/lib/authOptions"; import { getContactAttributeKeys } from "@/modules/ee/contacts/lib/contact-attribute-keys"; import { getSegments } from "@/modules/ee/contacts/segments/lib/segments"; import { getIsContactsEnabled, getMultiLanguagePermission } from "@/modules/ee/license-check/lib/utils"; -import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles"; -import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams"; +import { getEnvironmentAuth } from "@/modules/environments/lib/utils"; import { getProjectLanguages } from "@/modules/survey/editor/lib/project"; import { getUserEmail } from "@/modules/survey/editor/lib/user"; import { getSurveyFollowUpsPermission } from "@/modules/survey/follow-ups/lib/utils"; import { getActionClasses } from "@/modules/survey/lib/action-class"; -import { getEnvironment } from "@/modules/survey/lib/environment"; -import { getMembershipRoleByUserIdOrganizationId } from "@/modules/survey/lib/membership"; import { getProjectByEnvironmentId } from "@/modules/survey/lib/project"; import { getResponseCountBySurveyId } from "@/modules/survey/lib/response"; import { getOrganizationBilling, getSurvey } from "@/modules/survey/lib/survey"; import { ErrorComponent } from "@/modules/ui/components/error-component"; import { getTranslate } from "@/tolgee/server"; -import { getServerSession } from "next-auth"; import { DEFAULT_LOCALE, IS_FORMBRICKS_CLOUD, @@ -23,7 +18,6 @@ import { SURVEY_BG_COLORS, UNSPLASH_ACCESS_KEY, } from "@formbricks/lib/constants"; -import { getAccessFlags } from "@formbricks/lib/membership/utils"; import { SurveyEditor } from "./components/survey-editor"; import { getUserLocale } from "./lib/user"; @@ -38,31 +32,20 @@ export const generateMetadata = async (props) => { export const SurveyEditorPage = async (props) => { const searchParams = await props.searchParams; const params = await props.params; + + const { session, isMember, environment, hasReadAccess, currentUserMembership, projectPermission } = + await getEnvironmentAuth(params.environmentId); + const t = await getTranslate(); - const [ - survey, - project, - environment, - actionClasses, - contactAttributeKeys, - responseCount, - session, - segments, - ] = await Promise.all([ + const [survey, project, actionClasses, contactAttributeKeys, responseCount, segments] = await Promise.all([ getSurvey(params.surveyId), getProjectByEnvironmentId(params.environmentId), - getEnvironment(params.environmentId), getActionClasses(params.environmentId), getContactAttributeKeys(params.environmentId), getResponseCountBySurveyId(params.surveyId), - getServerSession(authOptions), getSegments(params.environmentId), ]); - if (!session) { - throw new Error(t("common.session_not_found")); - } - if (!project) { throw new Error(t("common.project_not_found")); } @@ -72,16 +55,6 @@ export const SurveyEditorPage = async (props) => { throw new Error(t("common.organization_not_found")); } - const membershipRole = await getMembershipRoleByUserIdOrganizationId( - session?.user.id, - project.organizationId - ); - const { isMember } = getAccessFlags(membershipRole); - - const projectPermission = await getProjectPermissionByUserId(session.user.id, project.id); - - const { hasReadAccess } = getTeamPermissionFlags(projectPermission); - const isSurveyCreationDeletionDisabled = isMember && hasReadAccess; const locale = session.user.id ? await getUserLocale(session.user.id) : undefined; @@ -115,7 +88,7 @@ export const SurveyEditorPage = async (props) => { actionClasses={actionClasses} contactAttributeKeys={contactAttributeKeys} responseCount={responseCount} - membershipRole={membershipRole} + membershipRole={currentUserMembership.role} projectPermission={projectPermission} colors={SURVEY_BG_COLORS} segments={segments} @@ -124,7 +97,7 @@ export const SurveyEditorPage = async (props) => { projectLanguages={projectLanguages} plan={organizationBilling.plan} isFormbricksCloud={IS_FORMBRICKS_CLOUD} - isUnsplashConfigured={UNSPLASH_ACCESS_KEY ? true : false} + isUnsplashConfigured={!!UNSPLASH_ACCESS_KEY} isCxMode={isCxMode} locale={locale ?? DEFAULT_LOCALE} mailFrom={MAIL_FROM ?? "hola@formbricks.com"} diff --git a/apps/web/modules/survey/list/page.tsx b/apps/web/modules/survey/list/page.tsx index c2f47a5217..e7690de1bc 100644 --- a/apps/web/modules/survey/list/page.tsx +++ b/apps/web/modules/survey/list/page.tsx @@ -1,12 +1,7 @@ -import { authOptions } from "@/modules/auth/lib/authOptions"; -import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles"; -import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams"; +import { getEnvironmentAuth } from "@/modules/environments/lib/utils"; import { TemplateList } from "@/modules/survey/components/template-list"; -import { getMembershipRoleByUserIdOrganizationId } from "@/modules/survey/lib/membership"; import { getProjectByEnvironmentId } from "@/modules/survey/lib/project"; import { SurveysList } from "@/modules/survey/list/components/survey-list"; -import { getEnvironment } from "@/modules/survey/list/lib/environment"; -import { getOrganizationIdByEnvironmentId } from "@/modules/survey/list/lib/organization"; import { getSurveyCount } from "@/modules/survey/list/lib/survey"; import { Button } from "@/modules/ui/components/button"; import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper"; @@ -14,11 +9,9 @@ import { PageHeader } from "@/modules/ui/components/page-header"; import { getTranslate } from "@/tolgee/server"; import { PlusIcon } from "lucide-react"; import { Metadata } from "next"; -import { getServerSession } from "next-auth"; import Link from "next/link"; import { redirect } from "next/navigation"; import { DEFAULT_LOCALE, SURVEYS_PER_PAGE, WEBAPP_URL } from "@formbricks/lib/constants"; -import { getAccessFlags } from "@formbricks/lib/membership/utils"; import { getUserLocale } from "@formbricks/lib/user/service"; import { TTemplateRole } from "@formbricks/types/templates"; @@ -41,42 +34,22 @@ export const SurveysPage = async ({ }: SurveyTemplateProps) => { const searchParams = await searchParamsProps; const params = await paramsProps; - - const session = await getServerSession(authOptions); - const project = await getProjectByEnvironmentId(params.environmentId); - const organizationId = await getOrganizationIdByEnvironmentId(params.environmentId); const t = await getTranslate(); - if (!session) { - throw new Error(t("common.session_not_found")); - } + + const project = await getProjectByEnvironmentId(params.environmentId); if (!project) { throw new Error(t("common.project_not_found")); } - if (!organizationId) { - throw new Error(t("common.organization_not_found")); - } + const { session, isBilling, environment, isReadOnly } = await getEnvironmentAuth(params.environmentId); const prefilledFilters = [project?.config.channel, project.config.industry, searchParams.role ?? null]; - const membershipRole = await getMembershipRoleByUserIdOrganizationId(session?.user.id, organizationId); - const { isMember, isBilling } = getAccessFlags(membershipRole); - - const projectPermission = await getProjectPermissionByUserId(session.user.id, project.id); - const { hasReadAccess } = getTeamPermissionFlags(projectPermission); - - const isReadOnly = isMember && hasReadAccess; - if (isBilling) { return redirect(`/environments/${params.environmentId}/settings/billing`); } - const environment = await getEnvironment(params.environmentId); - if (!environment) { - throw new Error(t("common.environment_not_found")); - } - const surveyCount = await getSurveyCount(params.environmentId); const currentProjectChannel = project.config.channel ?? null; @@ -92,44 +65,55 @@ export const SurveysPage = async ({ ); }; - return ( - - {surveyCount > 0 ? ( - <> - : } /> - - - ) : isReadOnly ? ( - <> -

- {t("environments.surveys.no_surveys_created_yet")} -

+ const projectWithRequiredProps = { + ...project, + brandColor: project.styling?.brandColor?.light ?? null, + highlightBorderColor: null, + }; -

- {t("environments.surveys.read_only_user_not_allowed_to_create_survey_warning")} -

- - ) : ( - <> -

- {t("environments.surveys.all_set_time_to_create_first_survey")} -

- - - )} -
- ); + let content; + if (surveyCount > 0) { + content = ( + <> + : } /> + + + ); + } else if (isReadOnly) { + content = ( + <> +

+ {t("environments.surveys.no_surveys_created_yet")} +

+ +

+ {t("environments.surveys.read_only_user_not_allowed_to_create_survey_warning")} +

+ + ); + } else { + content = ( + <> +

+ {t("environments.surveys.all_set_time_to_create_first_survey")} +

+ + + ); + } + + return {content}; }; diff --git a/apps/web/modules/survey/templates/page.tsx b/apps/web/modules/survey/templates/page.tsx index b3b9bffc4b..1e09e335d9 100644 --- a/apps/web/modules/survey/templates/page.tsx +++ b/apps/web/modules/survey/templates/page.tsx @@ -1,13 +1,7 @@ -import { authOptions } from "@/modules/auth/lib/authOptions"; -import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles"; -import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams"; -import { getEnvironment } from "@/modules/survey/lib/environment"; -import { getMembershipRoleByUserIdOrganizationId } from "@/modules/survey/lib/membership"; +import { getEnvironmentAuth } from "@/modules/environments/lib/utils"; import { getProjectByEnvironmentId } from "@/modules/survey/lib/project"; import { getTranslate } from "@/tolgee/server"; -import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; -import { getAccessFlags } from "@formbricks/lib/membership/utils"; import { TProjectConfigChannel, TProjectConfigIndustry } from "@formbricks/types/project"; import { TTemplateRole } from "@formbricks/types/templates"; import { TemplateContainerWithPreview } from "./components/template-container"; @@ -25,37 +19,18 @@ interface SurveyTemplateProps { export const SurveyTemplatesPage = async (props: SurveyTemplateProps) => { const searchParams = await props.searchParams; - const params = await props.params; const t = await getTranslate(); - const session = await getServerSession(authOptions); + const params = await props.params; const environmentId = params.environmentId; - if (!session) { - throw new Error(t("common.session_not_found")); - } + const { session, environment, isReadOnly } = await getEnvironmentAuth(environmentId); - const [environment, project] = await Promise.all([ - getEnvironment(environmentId), - getProjectByEnvironmentId(environmentId), - ]); + const project = await getProjectByEnvironmentId(environmentId); if (!project) { throw new Error(t("common.project_not_found")); } - if (!environment) { - throw new Error(t("common.environment_not_found")); - } - const membershipRole = await getMembershipRoleByUserIdOrganizationId( - session?.user.id, - project.organizationId - ); - const { isMember } = getAccessFlags(membershipRole); - - const projectPermission = await getProjectPermissionByUserId(session.user.id, project.id); - const { hasReadAccess } = getTeamPermissionFlags(projectPermission); - - const isReadOnly = isMember && hasReadAccess; if (isReadOnly) { return redirect(`/environments/${environment.id}/surveys`); }