fix: org layout authentication (#23707)
* refactor layout to not check session * add actions for orgs + org admins * update types on actions to be correctly non nullable * Add tests for utils * Apply suggestion from @coderabbitai[bot] Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * restore lock file * rename _actions to actions * update tests to be mocked * restore lock --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
parent
1137047606
commit
a379bd2b52
+4
@@ -2,6 +2,8 @@ import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import OrgAttributesEditPage from "@calcom/ee/organizations/pages/settings/attributes/attributes-edit-view";
|
||||
|
||||
import { validateUserHasOrgAdmin } from "../../../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
|
||||
await _generateMetadata(
|
||||
(t) => t("attribute"),
|
||||
@@ -12,6 +14,8 @@ export const generateMetadata = async ({ params }: { params: Promise<{ id: strin
|
||||
);
|
||||
|
||||
const OrgAttributesEditPageWrapper = async () => {
|
||||
await validateUserHasOrgAdmin();
|
||||
|
||||
return <OrgAttributesEditPage />;
|
||||
};
|
||||
|
||||
|
||||
+4
@@ -2,6 +2,8 @@ import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import OrgAttributesCreatePage from "@calcom/ee/organizations/pages/settings/attributes/attributes-create-view";
|
||||
|
||||
import { validateUserHasOrgAdmin } from "../../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("attribute"),
|
||||
@@ -12,6 +14,8 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const OrgAttributesCreatePageWrapper = async () => {
|
||||
await validateUserHasOrgAdmin();
|
||||
|
||||
return <OrgAttributesCreatePage />;
|
||||
};
|
||||
|
||||
|
||||
+2
-8
@@ -1,15 +1,13 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import OrgSettingsAttributesPage from "@calcom/ee/organizations/pages/settings/attributes/attributes-list-view";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -22,11 +20,7 @@ export const generateMetadata = async () =>
|
||||
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
|
||||
return redirect("/settings/profile");
|
||||
}
|
||||
const session = await validateUserHasOrgAdmin();
|
||||
|
||||
const { canRead, canEdit, canDelete, canCreate } = await getResourcePermissions({
|
||||
userId: session.user.id,
|
||||
|
||||
+33
-3
@@ -1,5 +1,35 @@
|
||||
import BillingPage, { generateMetadata } from "../../../billing/page";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getTranslate } from "app/_utils";
|
||||
|
||||
export { generateMetadata };
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
export default BillingPage;
|
||||
import BillingView from "~/settings/billing/billing-view";
|
||||
|
||||
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("billing"),
|
||||
(t) => t("manage_billing_description"),
|
||||
undefined,
|
||||
undefined,
|
||||
"/settings/organizations/billing"
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
await validateUserHasOrgAdmin();
|
||||
|
||||
// TODO(SEAN): Add PBAC to this page in the next PR
|
||||
|
||||
return (
|
||||
<SettingsHeader
|
||||
title={t("billing")}
|
||||
description={t("manage_billing_description")}
|
||||
borderInShellHeader={true}>
|
||||
<BillingView />
|
||||
</SettingsHeader>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
+4
@@ -3,6 +3,8 @@ import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import DelegationCredentialList from "@calcom/features/ee/organizations/pages/settings/delegationCredential";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("delegation_credential"),
|
||||
@@ -15,6 +17,8 @@ export const generateMetadata = async () =>
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
await validateUserHasOrgAdmin();
|
||||
|
||||
return (
|
||||
<SettingsHeader
|
||||
borderInShellHeader
|
||||
|
||||
+2
-9
@@ -1,15 +1,12 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import DirectorySyncTeamView from "@calcom/features/ee/dsync/page/team-dsync-view";
|
||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -22,11 +19,7 @@ export const generateMetadata = async () =>
|
||||
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
|
||||
return redirect("/settings/organizations/general");
|
||||
}
|
||||
const session = await validateUserHasOrgAdmin();
|
||||
|
||||
const { canEdit } = await getResourcePermissions({
|
||||
userId: session.user.id,
|
||||
|
||||
+3
-5
@@ -1,15 +1,13 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import PrivacyView from "@calcom/features/ee/organizations/pages/settings/privacy";
|
||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -21,9 +19,9 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const [t, _headers, _cookies] = await Promise.all([getTranslate(), headers(), cookies()]);
|
||||
const t = await getTranslate();
|
||||
|
||||
const session = await getServerSession({ req: buildLegacyRequest(_headers, _cookies) });
|
||||
const session = await validateUserHasOrgAdmin();
|
||||
|
||||
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
|
||||
return redirect("/settings/profile");
|
||||
|
||||
+2
-4
@@ -1,15 +1,13 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import OrgSSOView from "@calcom/features/ee/sso/page/orgs-sso-view";
|
||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrgAdmin } from "../../actions/validateUserHasOrgAdmin";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -22,7 +20,7 @@ export const generateMetadata = async () =>
|
||||
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
const session = await validateUserHasOrgAdmin();
|
||||
|
||||
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
|
||||
return redirect("/settings/organizations/general");
|
||||
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
import type { Session } from "next-auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import { describe, it, vi, expect, beforeEach, type MockedFunction } from "vitest";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { validateUserHasOrg, type ValidatedOrgSession } from "./validateUserHasOrg";
|
||||
|
||||
// Mock the dependencies
|
||||
vi.mock("next/navigation", () => ({
|
||||
redirect: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("next/headers", () => ({
|
||||
cookies: vi.fn(() => Promise.resolve({})),
|
||||
headers: vi.fn(() => Promise.resolve({})),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/auth/lib/getServerSession", () => ({
|
||||
getServerSession: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@lib/buildLegacyCtx", () => ({
|
||||
buildLegacyRequest: vi.fn(() => ({})),
|
||||
}));
|
||||
|
||||
const mockedGetServerSession = getServerSession as MockedFunction<typeof getServerSession>;
|
||||
const mockedRedirect = vi.mocked(redirect);
|
||||
|
||||
describe("validateUserHasOrg", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const createMockSession = (overrides: Partial<Session> = {}): Session => ({
|
||||
expires: "2025-01-01",
|
||||
hasValidLicense: true,
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
logoUrl: null,
|
||||
fullDomain: "test-org.cal.com",
|
||||
domainSuffix: "cal.com",
|
||||
role: MembershipRole.ADMIN,
|
||||
},
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
profileId: 789,
|
||||
upId: "usr-123",
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe("when user has valid organization", () => {
|
||||
it("should return session when user has org in user.org", async () => {
|
||||
const mockSession = createMockSession();
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const result = await validateUserHasOrg();
|
||||
|
||||
expect(result).toEqual(mockSession);
|
||||
expect(mockedRedirect).not.toHaveBeenCalled();
|
||||
expect(result.user.id).toBe(123);
|
||||
expect(result.user.org?.id).toBe(456);
|
||||
expect(result.user.profile?.organizationId).toBe(456);
|
||||
});
|
||||
|
||||
it("should return session when user has organizationId but no org", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: undefined,
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const result = await validateUserHasOrg();
|
||||
|
||||
expect(result).toEqual(mockSession);
|
||||
expect(mockedRedirect).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when user does not have valid organization", () => {
|
||||
it("should redirect when session is null", async () => {
|
||||
mockedGetServerSession.mockResolvedValue(null);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrg()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/my-account/profile");
|
||||
});
|
||||
|
||||
it("should redirect when user has no organization data", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: undefined,
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: null,
|
||||
organization: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrg()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/my-account/profile");
|
||||
});
|
||||
|
||||
it("should redirect when user has no user id", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: undefined as unknown as number,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
logoUrl: null,
|
||||
fullDomain: "test-org.cal.com",
|
||||
domainSuffix: "cal.com",
|
||||
role: MembershipRole.ADMIN,
|
||||
},
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrg()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/my-account/profile");
|
||||
});
|
||||
});
|
||||
|
||||
describe("return type validation", () => {
|
||||
it("should return ValidatedOrgSession type with non-nullable properties", async () => {
|
||||
const mockSession = createMockSession();
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const result: ValidatedOrgSession = await validateUserHasOrg();
|
||||
|
||||
// Type assertions to ensure proper typing
|
||||
expect(typeof result.user.id).toBe("number");
|
||||
expect(result.user.org).toBeTruthy();
|
||||
expect(result.user.profile).toBeTruthy();
|
||||
expect(typeof result.user.profile.organizationId).toBe("number");
|
||||
|
||||
// Runtime checks that the types guarantee these properties exist
|
||||
expect(result.user.id).toBe(123);
|
||||
expect(result.user.org.id).toBe(456);
|
||||
expect(result.user.profile.organizationId).toBe(456);
|
||||
});
|
||||
});
|
||||
|
||||
describe("error handling", () => {
|
||||
it("should propagate errors from getServerSession", async () => {
|
||||
const error = new Error("Session error");
|
||||
mockedGetServerSession.mockRejectedValue(error);
|
||||
|
||||
await expect(validateUserHasOrg()).rejects.toThrow("Session error");
|
||||
});
|
||||
});
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import type { Session } from "next-auth";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
|
||||
export type ValidatedOrgSession = NonNullable<Session> & {
|
||||
user: NonNullable<Session["user"]> & {
|
||||
id: number;
|
||||
org: NonNullable<NonNullable<Session["user"]>["org"]>;
|
||||
profile: NonNullable<NonNullable<Session["user"]>["profile"]> & {
|
||||
organizationId: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export async function validateUserHasOrg(): Promise<ValidatedOrgSession> {
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
const orgExists =
|
||||
session?.user?.org || session?.user?.profile?.organizationId || session?.user?.profile?.organization;
|
||||
|
||||
if (!orgExists || !session?.user?.id || !session?.user?.profile?.organizationId) {
|
||||
redirect("/settings/my-account/profile");
|
||||
}
|
||||
|
||||
return session as ValidatedOrgSession;
|
||||
}
|
||||
+418
@@ -0,0 +1,418 @@
|
||||
import type { Session } from "next-auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import { describe, it, vi, expect, beforeEach, type MockedFunction } from "vitest";
|
||||
|
||||
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { validateUserHasOrgAdmin, type ValidatedOrgAdminSession } from "./validateUserHasOrgAdmin";
|
||||
|
||||
// Mock the dependencies
|
||||
vi.mock("next/navigation", () => ({
|
||||
redirect: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("next/headers", () => ({
|
||||
cookies: vi.fn(() => Promise.resolve({})),
|
||||
headers: vi.fn(() => Promise.resolve({})),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/auth/lib/getServerSession", () => ({
|
||||
getServerSession: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/auth/lib/checkAdminOrOwner", () => ({
|
||||
checkAdminOrOwner: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@lib/buildLegacyCtx", () => ({
|
||||
buildLegacyRequest: vi.fn(() => ({})),
|
||||
}));
|
||||
|
||||
const mockedGetServerSession = getServerSession as MockedFunction<typeof getServerSession>;
|
||||
const mockedCheckAdminOrOwner = vi.mocked(checkAdminOrOwner);
|
||||
const mockedRedirect = vi.mocked(redirect);
|
||||
|
||||
describe("validateUserHasOrgAdmin", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const createMockSession = (overrides: Partial<Session> = {}): Session => ({
|
||||
expires: "2025-01-01",
|
||||
hasValidLicense: true,
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
logoUrl: null,
|
||||
fullDomain: "test-org.cal.com",
|
||||
domainSuffix: "cal.com",
|
||||
role: MembershipRole.ADMIN,
|
||||
},
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
profileId: 789,
|
||||
upId: "usr-123",
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe("when user has valid organization and admin role", () => {
|
||||
it("should return session when user is admin via user.org.role", async () => {
|
||||
const mockSession = createMockSession();
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(true);
|
||||
|
||||
const result = await validateUserHasOrgAdmin();
|
||||
|
||||
expect(result).toEqual(mockSession);
|
||||
expect(mockedRedirect).not.toHaveBeenCalled();
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(MembershipRole.ADMIN);
|
||||
expect(result.user.id).toBe(123);
|
||||
expect(result.user.org?.id).toBe(456);
|
||||
expect(result.user.profile?.organizationId).toBe(456);
|
||||
});
|
||||
|
||||
it("should return session when user is owner", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
logoUrl: null,
|
||||
fullDomain: "test-org.cal.com",
|
||||
domainSuffix: "cal.com",
|
||||
role: MembershipRole.OWNER,
|
||||
},
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(true);
|
||||
|
||||
const result = await validateUserHasOrgAdmin();
|
||||
|
||||
expect(result).toEqual(mockSession);
|
||||
expect(mockedRedirect).not.toHaveBeenCalled();
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(MembershipRole.OWNER);
|
||||
});
|
||||
|
||||
it("should return session when role comes from organization members", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: undefined,
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
members: [
|
||||
{ userId: 123, role: MembershipRole.ADMIN },
|
||||
{ userId: 124, role: MembershipRole.MEMBER },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(true);
|
||||
|
||||
const result = await validateUserHasOrgAdmin();
|
||||
|
||||
expect(result).toEqual(mockSession);
|
||||
expect(mockedRedirect).not.toHaveBeenCalled();
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(MembershipRole.ADMIN);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when user does not have valid organization", () => {
|
||||
it("should redirect when session is null", async () => {
|
||||
mockedGetServerSession.mockResolvedValue(null);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrgAdmin()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/my-account/profile");
|
||||
});
|
||||
|
||||
it("should redirect when user has no organization data", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: undefined,
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: null,
|
||||
organization: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrgAdmin()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/my-account/profile");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when user has organization but is not admin", () => {
|
||||
it("should redirect when user is only a member", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
logoUrl: null,
|
||||
fullDomain: "test-org.cal.com",
|
||||
domainSuffix: "cal.com",
|
||||
role: MembershipRole.MEMBER,
|
||||
},
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(false);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrgAdmin()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/organizations/profile");
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(MembershipRole.MEMBER);
|
||||
});
|
||||
|
||||
it("should redirect when role is undefined", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: undefined,
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
members: [
|
||||
{ userId: 999, role: MembershipRole.ADMIN }, // Different user
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(false);
|
||||
|
||||
const redirectError = new Error("NEXT_REDIRECT");
|
||||
mockedRedirect.mockImplementation(() => {
|
||||
throw redirectError;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrgAdmin()).rejects.toThrow("NEXT_REDIRECT");
|
||||
expect(mockedRedirect).toHaveBeenCalledWith("/settings/organizations/profile");
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("return type validation", () => {
|
||||
it("should return ValidatedOrgAdminSession type with non-nullable properties", async () => {
|
||||
const mockSession = createMockSession();
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(true);
|
||||
|
||||
const result: ValidatedOrgAdminSession = await validateUserHasOrgAdmin();
|
||||
|
||||
// Type assertions to ensure proper typing
|
||||
expect(typeof result.user.id).toBe("number");
|
||||
expect(result.user.org).toBeTruthy();
|
||||
expect(result.user.profile).toBeTruthy();
|
||||
expect(typeof result.user.profile.organizationId).toBe("number");
|
||||
expect(result.user.org.role).toBeTruthy();
|
||||
|
||||
// Runtime checks that the types guarantee these properties exist
|
||||
expect(result.user.id).toBe(123);
|
||||
expect(result.user.org.id).toBe(456);
|
||||
expect(result.user.org.role).toBe(MembershipRole.ADMIN);
|
||||
expect(result.user.profile.organizationId).toBe(456);
|
||||
});
|
||||
});
|
||||
|
||||
describe("role resolution logic", () => {
|
||||
it("should prefer org role over organization members role", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
logoUrl: null,
|
||||
fullDomain: "test-org.cal.com",
|
||||
domainSuffix: "cal.com",
|
||||
role: MembershipRole.OWNER, // This should be preferred
|
||||
},
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
members: [
|
||||
{ userId: 123, role: MembershipRole.MEMBER }, // Different role
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(true);
|
||||
|
||||
await validateUserHasOrgAdmin();
|
||||
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(MembershipRole.OWNER);
|
||||
});
|
||||
|
||||
it("should fall back to organization members role when org role is not available", async () => {
|
||||
const mockSession = createMockSession({
|
||||
user: {
|
||||
id: 123,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
org: undefined,
|
||||
profile: {
|
||||
id: 789,
|
||||
upId: "usr-123",
|
||||
username: "testuser",
|
||||
organizationId: 456,
|
||||
organization: {
|
||||
id: 456,
|
||||
name: "Test Org",
|
||||
slug: "test-org",
|
||||
calVideoLogo: null,
|
||||
bannerUrl: null,
|
||||
requestedSlug: null,
|
||||
members: [{ userId: 123, role: MembershipRole.ADMIN }],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
mockedCheckAdminOrOwner.mockReturnValue(true);
|
||||
|
||||
await validateUserHasOrgAdmin();
|
||||
|
||||
expect(mockedCheckAdminOrOwner).toHaveBeenCalledWith(MembershipRole.ADMIN);
|
||||
});
|
||||
});
|
||||
|
||||
describe("error handling", () => {
|
||||
it("should propagate errors from getServerSession", async () => {
|
||||
const error = new Error("Session error");
|
||||
mockedGetServerSession.mockRejectedValue(error);
|
||||
|
||||
await expect(validateUserHasOrgAdmin()).rejects.toThrow("Session error");
|
||||
});
|
||||
|
||||
it("should propagate errors from checkAdminOrOwner", async () => {
|
||||
const mockSession = createMockSession();
|
||||
mockedGetServerSession.mockResolvedValue(mockSession);
|
||||
|
||||
const error = new Error("Permission check error");
|
||||
mockedCheckAdminOrOwner.mockImplementation(() => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
await expect(validateUserHasOrgAdmin()).rejects.toThrow("Permission check error");
|
||||
});
|
||||
});
|
||||
});
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import type { Session } from "next-auth";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import type { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
|
||||
export type ValidatedOrgAdminSession = NonNullable<Session> & {
|
||||
user: NonNullable<Session["user"]> & {
|
||||
id: number;
|
||||
org: NonNullable<NonNullable<Session["user"]>["org"]> & {
|
||||
role: MembershipRole;
|
||||
};
|
||||
profile: NonNullable<NonNullable<Session["user"]>["profile"]> & {
|
||||
organizationId: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export async function validateUserHasOrgAdmin(): Promise<ValidatedOrgAdminSession> {
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
const orgExists =
|
||||
session?.user?.org || session?.user?.profile?.organizationId || session?.user?.profile?.organization;
|
||||
|
||||
if (!orgExists || !session?.user?.id || !session?.user?.profile?.organizationId) {
|
||||
redirect("/settings/my-account/profile");
|
||||
}
|
||||
|
||||
const userProfile = session?.user?.profile;
|
||||
const userId = session?.user?.id;
|
||||
const orgRole =
|
||||
session?.user?.org?.role ??
|
||||
userProfile?.organization?.members.find((m: { userId: number }) => m.userId === userId)?.role;
|
||||
const isOrgAdminOrOwner = checkAdminOrOwner(orgRole);
|
||||
|
||||
if (!isOrgAdminOrOwner) {
|
||||
redirect("/settings/organizations/profile");
|
||||
}
|
||||
|
||||
return session as ValidatedOrgAdminSession;
|
||||
}
|
||||
+4
@@ -3,6 +3,8 @@ import { getTranslate, _generateMetadata } from "app/_utils";
|
||||
import { AdminAPIView } from "@calcom/features/ee/organizations/pages/settings/admin-api";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import { validateUserHasOrg } from "../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => `${t("admin")} ${t("api_reference")}`,
|
||||
@@ -15,6 +17,8 @@ export const generateMetadata = async () =>
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
await validateUserHasOrg();
|
||||
|
||||
return (
|
||||
<SettingsHeader
|
||||
title={`${t("admin")} ${t("api_reference")}`}
|
||||
|
||||
+2
-9
@@ -1,15 +1,12 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import LegacyPage from "@calcom/features/ee/organizations/pages/settings/general";
|
||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrg } from "../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -23,11 +20,7 @@ export const generateMetadata = async () =>
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
if (!session?.user.id || !session?.user.profile?.organizationId || !session?.user.org) {
|
||||
return redirect("/settings/profile");
|
||||
}
|
||||
const session = await validateUserHasOrg();
|
||||
|
||||
const { canRead, canEdit } = await getResourcePermissions({
|
||||
userId: session.user.id,
|
||||
|
||||
@@ -1,19 +1,4 @@
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
|
||||
const SettingsOrganizationsLayout = async ({ children }: { children: React.ReactNode }) => {
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
|
||||
const orgExists =
|
||||
session?.user?.org || session?.user?.profile?.organizationId || session?.user?.profile?.organization;
|
||||
if (!orgExists) {
|
||||
return redirect("/settings/my-account/profile");
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
|
||||
+6
-8
@@ -1,8 +1,6 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import LegacyPage from "@calcom/features/ee/organizations/pages/settings/profile";
|
||||
import { Resource } from "@calcom/features/pbac/domain/types/permission-registry";
|
||||
import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions";
|
||||
@@ -10,7 +8,7 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import type { Membership } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrg } from "../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -22,20 +20,20 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
const session = await validateUserHasOrg();
|
||||
const t = await getTranslate();
|
||||
|
||||
const orgRole = session?.user.profile?.organization.members?.find(
|
||||
(member: Membership) => member.userId === session?.user.id
|
||||
const orgRole = session.user.profile.organization.members?.find(
|
||||
(member: Membership) => member.userId === session.user.id
|
||||
)?.role;
|
||||
|
||||
if (!session?.user.id || !session?.user.profile?.organizationId || !orgRole) {
|
||||
if (!orgRole) {
|
||||
return redirect("/settings/profile");
|
||||
}
|
||||
|
||||
const { canRead, canEdit, canDelete } = await getResourcePermissions({
|
||||
userId: session.user.id,
|
||||
teamId: session?.user.profile?.organizationId,
|
||||
teamId: session.user.profile.organizationId,
|
||||
resource: Resource.Organization,
|
||||
userRole: orgRole,
|
||||
fallbackRoles: {
|
||||
|
||||
+2
-5
@@ -1,9 +1,7 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import type { AppFlags } from "@calcom/features/flags/config";
|
||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
import { PermissionMapper } from "@calcom/features/pbac/domain/mappers/PermissionMapper";
|
||||
@@ -13,8 +11,7 @@ import { RoleService } from "@calcom/features/pbac/services/role.service";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { validateUserHasOrg } from "../actions/validateUserHasOrg";
|
||||
import { CreateRoleCTA } from "./_components/CreateRoleCta";
|
||||
import { RolesList } from "./_components/RolesList";
|
||||
import { roleSearchParamsCache } from "./_components/searchParams";
|
||||
@@ -58,7 +55,7 @@ export const generateMetadata = async () =>
|
||||
|
||||
const Page = async ({ searchParams }: { searchParams: Record<string, string | string[] | undefined> }) => {
|
||||
const t = await getTranslate();
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
const session = await validateUserHasOrg();
|
||||
|
||||
if (!session?.user?.org?.id || !session.user.id) {
|
||||
return notFound();
|
||||
|
||||
+2
-4
@@ -1,13 +1,11 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { OtherTeamsListing } from "@calcom/features/ee/organizations/pages/components/OtherTeamsListing";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { OrganizationRepository } from "@calcom/lib/server/repository/organization";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
import { validateUserHasOrg } from "../../../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -20,7 +18,7 @@ export const generateMetadata = async () =>
|
||||
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
|
||||
const session = await validateUserHasOrg();
|
||||
|
||||
if (!session?.user?.id) {
|
||||
redirect("/auth/login");
|
||||
|
||||
+4
@@ -3,6 +3,8 @@ import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import LegacyPage from "@calcom/features/ee/teams/pages/team-appearance-view";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import { validateUserHasOrg } from "../../../../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
|
||||
await _generateMetadata(
|
||||
(t) => t("booking_appearance"),
|
||||
@@ -15,6 +17,8 @@ export const generateMetadata = async ({ params }: { params: Promise<{ id: strin
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
await validateUserHasOrg();
|
||||
|
||||
return (
|
||||
<SettingsHeader
|
||||
title={t("booking_appearance")}
|
||||
|
||||
+4
@@ -5,6 +5,8 @@ import LegacyPage, {
|
||||
} from "@calcom/features/ee/organizations/pages/settings/other-team-members-view";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import { validateUserHasOrg } from "../../../../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
|
||||
await _generateMetadata(
|
||||
(t) => t("team_members"),
|
||||
@@ -17,6 +19,8 @@ export const generateMetadata = async ({ params }: { params: Promise<{ id: strin
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
await validateUserHasOrg();
|
||||
|
||||
return (
|
||||
<SettingsHeader
|
||||
title={t("team_members")}
|
||||
|
||||
+4
@@ -3,6 +3,8 @@ import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import LegacyPage from "@calcom/features/ee/organizations/pages/settings/other-team-profile-view";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import { validateUserHasOrg } from "../../../../actions/validateUserHasOrg";
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
|
||||
await _generateMetadata(
|
||||
(t) => t("profile"),
|
||||
@@ -15,6 +17,8 @@ export const generateMetadata = async ({ params }: { params: Promise<{ id: strin
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
await validateUserHasOrg();
|
||||
|
||||
return (
|
||||
<SettingsHeader title={t("profile")} description={t("profile_team_description")}>
|
||||
<LegacyPage />
|
||||
|
||||
Reference in New Issue
Block a user