fix: add membership checks in remaining routes (#5026)

This commit is contained in:
victorvhs017
2025-03-24 08:49:04 -03:00
committed by GitHub
parent 60d0563487
commit 306f654617
15 changed files with 203 additions and 329 deletions
@@ -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();
@@ -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`);
}
@@ -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`);
}
@@ -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) {
@@ -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);
@@ -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");
});
});
@@ -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);
@@ -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,
@@ -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();
@@ -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<TOrganizationAuth> => {
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,
};
});
@@ -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 (
<PageContentWrapper>
@@ -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<typeof ZOrganizationAuth>;
+8 -35
View File
@@ -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"}
+54 -70
View File
@@ -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 (
<PageContentWrapper>
{surveyCount > 0 ? (
<>
<PageHeader pageTitle={t("common.surveys")} cta={isReadOnly ? <></> : <CreateSurveyButton />} />
<SurveysList
environmentId={environment.id}
isReadOnly={isReadOnly}
WEBAPP_URL={WEBAPP_URL}
userId={session.user.id}
surveysPerPage={SURVEYS_PER_PAGE}
currentProjectChannel={currentProjectChannel}
locale={locale}
/>
</>
) : isReadOnly ? (
<>
<h1 className="px-6 text-3xl font-extrabold text-slate-700">
{t("environments.surveys.no_surveys_created_yet")}
</h1>
const projectWithRequiredProps = {
...project,
brandColor: project.styling?.brandColor?.light ?? null,
highlightBorderColor: null,
};
<h2 className="px-6 text-lg font-medium text-slate-500">
{t("environments.surveys.read_only_user_not_allowed_to_create_survey_warning")}
</h2>
</>
) : (
<>
<h1 className="px-6 text-3xl font-extrabold text-slate-700">
{t("environments.surveys.all_set_time_to_create_first_survey")}
</h1>
<TemplateList
environmentId={environment.id}
project={project}
userId={session.user.id}
prefilledFilters={prefilledFilters}
/>
</>
)}
</PageContentWrapper>
);
let content;
if (surveyCount > 0) {
content = (
<>
<PageHeader pageTitle={t("common.surveys")} cta={isReadOnly ? <></> : <CreateSurveyButton />} />
<SurveysList
environmentId={environment.id}
isReadOnly={isReadOnly}
WEBAPP_URL={WEBAPP_URL}
userId={session.user.id}
surveysPerPage={SURVEYS_PER_PAGE}
currentProjectChannel={currentProjectChannel}
locale={locale}
/>
</>
);
} else if (isReadOnly) {
content = (
<>
<h1 className="px-6 text-3xl font-extrabold text-slate-700">
{t("environments.surveys.no_surveys_created_yet")}
</h1>
<h2 className="px-6 text-lg font-medium text-slate-500">
{t("environments.surveys.read_only_user_not_allowed_to_create_survey_warning")}
</h2>
</>
);
} else {
content = (
<>
<h1 className="px-6 text-3xl font-extrabold text-slate-700">
{t("environments.surveys.all_set_time_to_create_first_survey")}
</h1>
<TemplateList
environmentId={environment.id}
project={projectWithRequiredProps}
userId={session.user.id}
prefilledFilters={prefilledFilters}
/>
</>
);
}
return <PageContentWrapper>{content}</PageContentWrapper>;
};
+4 -29
View File
@@ -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`);
}