Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9538aeb
fix members page crash with pbac feature flag
sean-brydon Jul 11, 2025
36ea724
invite member to org backend
sean-brydon Jul 11, 2025
403f7a7
update org permissions
sean-brydon Jul 11, 2025
97cc7b8
feat: org profile update settings
sean-brydon Jul 11, 2025
cb5c51e
org general page
sean-brydon Jul 11, 2025
8d3917e
remove redudant me call
sean-brydon Jul 14, 2025
98573af
privacy page
sean-brydon Jul 14, 2025
a4e6769
add attributes to pbac
sean-brydon Jul 14, 2025
021fb0c
dync + sso on organization permissions
sean-brydon Jul 14, 2025
8373205
add tests for resource-permission util
sean-brydon Jul 14, 2025
0cb398d
pass permissions to attributes\
sean-brydon Jul 14, 2025
f638bdd
restore invite members
sean-brydon Jul 14, 2025
3185e44
update org update and attribute backends
sean-brydon Jul 14, 2025
799743e
fix type errors
sean-brydon Jul 15, 2025
7593758
fix orgId
sean-brydon Jul 15, 2025
d84119b
fix types attempt two
sean-brydon Jul 15, 2025
bbd4901
fix types attempt two
sean-brydon Jul 15, 2025
74f34d9
fix merge coflict
sean-brydon Jul 15, 2025
ec7c5a4
fix type error
sean-brydon Jul 16, 2025
3b03b59
Merge branch 'main' into feat/org-settings-pbac
sean-brydon Jul 17, 2025
18ad08e
Merge branch 'main' into feat/org-settings-pbac
sean-brydon Jul 28, 2025
365a320
Merge branch 'main' into feat/org-settings-pbac
sean-brydon Jul 28, 2025
28b789f
fix dupe string in i18n
sean-brydon Jul 28, 2025
85f140a
Merge branch 'main' into feat/org-settings-pbac
sean-brydon Jul 28, 2025
4bedc60
Merge branch 'main' into feat/org-settings-pbac
sean-brydon Jul 28, 2025
9f80c57
fix tests
sean-brydon Jul 30, 2025
667ed6f
fix team-dsync
sean-brydon Jul 30, 2025
194cc30
update session to use profile
sean-brydon Jul 30, 2025
51c0a47
use profile metadata to get orgRolew
sean-brydon Jul 30, 2025
603f9a1
fix dsync
sean-brydon Jul 30, 2025
8e2df35
fix typing
sean-brydon Jul 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
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";

export const generateMetadata = async () =>
await _generateMetadata(
Expand All @@ -14,10 +22,40 @@ 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 { canRead, canEdit, canDelete, canCreate } = await getResourcePermissions({
userId: session.user.id,
teamId: session.user.profile.organizationId,
resource: Resource.Attributes,
userRole: session.user.org.role,
fallbackRoles: {
read: {
roles: [MembershipRole.MEMBER, MembershipRole.ADMIN, MembershipRole.OWNER],
},
update: {
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
},
delete: {
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
},
create: {
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
},
},
});

if (!canRead) {
return redirect("/settings/profile");
}

return (
<SettingsHeader title={t("attributes")} description={t("attribute_meta_description")}>
<OrgSettingsAttributesPage />
<OrgSettingsAttributesPage permissions={{ canEdit, canDelete, canCreate }} />
</SettingsHeader>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
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";

export const generateMetadata = async () =>
await _generateMetadata(
Expand All @@ -14,10 +22,27 @@ 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 { canEdit } = await getResourcePermissions({
userId: session.user.id,
teamId: session.user.profile.organizationId,
resource: Resource.Organization,
userRole: session.user.org.role,
fallbackRoles: {
update: {
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
},
},
});

return (
<SettingsHeader title={t("directory_sync")} description={t("directory_sync_description")}>
<DirectorySyncTeamView />
<DirectorySyncTeamView permissions={{ canEdit }} />
</SettingsHeader>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
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";

export const generateMetadata = async () =>
await _generateMetadata(
Expand All @@ -15,9 +23,34 @@ 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 { canRead, canEdit } = await getResourcePermissions({
userId: session.user.id,
teamId: session.user.profile.organizationId,
resource: Resource.Organization,
userRole: session.user.org.role,
fallbackRoles: {
read: {
roles: [MembershipRole.MEMBER, MembershipRole.ADMIN, MembershipRole.OWNER],
},
update: {
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
},
},
});

if (!canRead) {
return redirect("/settings/profile");
}

return (
<SettingsHeader title={t("privacy")} description={t("privacy_organization_description")}>
<PrivacyView />
<PrivacyView permissions={{ canRead, canEdit }} />
</SettingsHeader>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
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";

export const generateMetadata = async () =>
await _generateMetadata(
Expand All @@ -14,10 +22,27 @@ 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 { canEdit } = await getResourcePermissions({
userId: session.user.id,
teamId: session.user.profile.organizationId,
resource: Resource.Organization,
userRole: session.user.org.role,
fallbackRoles: {
update: {
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
},
},
});

return (
<SettingsHeader title={t("sso_configuration")} description={t("sso_configuration_description_orgs")}>
<OrgSSOView />
<OrgSSOView permissions={{ canEdit }} />
</SettingsHeader>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
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";

export const generateMetadata = async () =>
await _generateMetadata(
Expand All @@ -15,9 +23,30 @@ 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 { canRead, canEdit } = await getResourcePermissions({
userId: session.user.id,
teamId: session.user.profile.organizationId,
resource: Resource.Organization,
userRole: session.user.org.role,
fallbackRoles: {
read: {
roles: [MembershipRole.MEMBER, MembershipRole.ADMIN, MembershipRole.OWNER],
},
update: {
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
},
},
});

return (
<SettingsHeader title={t("general")} description={t("general_description")} borderInShellHeader={true}>
<LegacyPage />
<LegacyPage permissions={{ canRead, canEdit }} />
</SettingsHeader>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
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";
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";

export const generateMetadata = async () =>
await _generateMetadata(
Expand All @@ -13,14 +22,47 @@ export const generateMetadata = async () =>
);

const Page = async () => {
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
const t = await getTranslate();

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) {
return redirect("/settings/profile");
}

const { canRead, canEdit, canDelete } = await getResourcePermissions({
userId: session.user.id,
teamId: session?.user.profile?.organizationId,
resource: Resource.Organization,
userRole: orgRole,
fallbackRoles: {
read: {
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
},
update: {
roles: [MembershipRole.ADMIN, MembershipRole.OWNER],
},
delete: {
roles: [MembershipRole.OWNER],
},
},
});

return (
<SettingsHeader
title={t("profile")}
description={t("profile_org_description")}
borderInShellHeader={true}>
<LegacyPage />
<LegacyPage
permissions={{
canEdit,
canRead,
canDelete,
}}
/>
</SettingsHeader>
);
};
Expand Down
11 changes: 11 additions & 0 deletions apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,10 @@
"create_team": "Create Team",
"name": "Name",
"nameless_team": "Nameless Team",
"oauth_clients": "OAuth Clients",
"oauth_clients_description": "Manage OAuth clients for your organization",
"create_oauth_client": "Create OAuth Client",
"create_oauth_client_description": "Create a new OAuth client for third-party integrations",
"oauth_client_deletion_message": "OAuth client deleted successfully",
"create_new_team_description": "Create a new team to collaborate with users.",
"create_new_team": "Create a new team",
Expand Down Expand Up @@ -1836,6 +1840,8 @@
"edit_event_type": "Edit event type",
"only_admin_can_see_members_of_org": "This Organization is private, and only the organization's admin or owner can view its members.",
"only_admin_can_manage_sso_org": "Only the organization's admin or owner can manage SSO settings",
"only_admin_can_manage_directory_sync": "Only the organization's admin or owner can manage directory sync settings",
"only_admin_can_manage_oauth_clients": "Only the organization's admin or owner can manage OAuth clients",
"collective_scheduling": "Collective Scheduling",
"make_it_easy_to_book": "Make it easy to book your team when everyone is available.",
"find_the_best_person": "Find the best person available and cycle through your team.",
Expand Down Expand Up @@ -3302,6 +3308,11 @@
"error_creating_role": "Error creating role",
"error_updating_role": "Error updating role",
"pbac_desc_create_roles": "Create roles",
"pbac_resource_attributes": "Attributes",
"pbac_desc_view_organization_attributes": "View organization attributes",
"pbac_desc_update_organization_attributes": "Update organization attributes",
"pbac_desc_delete_organization_attributes": "Delete organization attributes",
"pbac_desc_create_organization_attributes": "Create organization attributes",
"pbac_desc_view_roles": "View roles",
"pbac_desc_update_roles": "Update roles",
"pbac_desc_delete_roles": "Delete roles",
Expand Down
6 changes: 5 additions & 1 deletion packages/features/ee/dsync/page/team-dsync-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { showToast } from "@calcom/ui/components/toast";
import ConfigureDirectorySync from "../components/ConfigureDirectorySync";

// For Hosted Cal - Team view
const DirectorySync = () => {
const DirectorySync = ({ permissions }: { permissions?: { canEdit: boolean } }) => {
const { t } = useLocale();
const router = useRouter();

Expand All @@ -36,6 +36,10 @@ const DirectorySync = () => {
showToast(error.message, "error");
}

if (!permissions?.canEdit) {
router.push("/404");
}

return (
<div className="bg-default w-full sm:mx-0 xl:mt-0">
{HOSTED_CAL_FEATURES && <ConfigureDirectorySync organizationId={currentOrg?.id || null} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import { showToast } from "@calcom/ui/components/toast";

interface GeneralViewProps {
currentOrg: RouterOutputs["viewer"]["organizations"]["listCurrent"];
isAdminOrOwner: boolean;
}

export const DisablePhoneOnlySMSNotificationsSwitch = ({ currentOrg, isAdminOrOwner }: GeneralViewProps) => {
export const DisablePhoneOnlySMSNotificationsSwitch = ({ currentOrg }: GeneralViewProps) => {
const { t } = useLocale();
const utils = trpc.useUtils();
const [disablePhoneOnlySMSNotificationsActive, setDisablePhoneOnlySMSNotificationsActive] = useState(
Expand All @@ -32,8 +31,6 @@ export const DisablePhoneOnlySMSNotificationsSwitch = ({ currentOrg, isAdminOrOw
},
});

if (!isAdminOrOwner) return null;

return (
<>
<SettingsToggle
Expand Down
Loading
Loading