From dbbd78fb26b83c7c3ab045ac3b971ba91ab9b1ec Mon Sep 17 00:00:00 2001 From: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:32:04 +0100 Subject: [PATCH] feat: include org role in org session data (#14652) * feat: include org role in org session data * update contents * remove: files --- .../features/auth/lib/next-auth-options.ts | 14 ++++ .../ee/organizations/context/provider.ts | 2 + .../settings/layouts/SettingsLayout.tsx | 72 ++++++++++++------- packages/types/next-auth.d.ts | 4 ++ 4 files changed, 65 insertions(+), 27 deletions(-) diff --git a/packages/features/auth/lib/next-auth-options.ts b/packages/features/auth/lib/next-auth-options.ts index 192eb9ceec..5f391a66df 100644 --- a/packages/features/auth/lib/next-auth-options.ts +++ b/packages/features/auth/lib/next-auth-options.ts @@ -511,6 +511,19 @@ export const AUTH_OPTIONS: AuthOptions = { } const profileOrg = profile?.organization; + let orgRole: MembershipRole | undefined; + // Get users role of org + if (profileOrg) { + const membership = await prisma.membership.findUnique({ + where: { + userId_teamId: { + teamId: profileOrg.id, + userId: existingUser.id, + }, + }, + }); + orgRole = membership?.role; + } return { ...existingUserWithoutTeamsField, @@ -528,6 +541,7 @@ export const AUTH_OPTIONS: AuthOptions = { logoUrl: profileOrg.logoUrl, fullDomain: getOrgFullOrigin(profileOrg.slug ?? profileOrg.requestedSlug ?? ""), domainSuffix: subdomainSuffix(), + role: orgRole as MembershipRole, // It can't be undefined if we have a profileOrg } : null, } as JWT; diff --git a/packages/features/ee/organizations/context/provider.ts b/packages/features/ee/organizations/context/provider.ts index 6419bc2603..28e8049564 100644 --- a/packages/features/ee/organizations/context/provider.ts +++ b/packages/features/ee/organizations/context/provider.ts @@ -1,6 +1,7 @@ import { createContext, useContext, createElement } from "react"; import type z from "zod"; +import type { MembershipRole } from "@calcom/prisma/enums"; import type { teamMetadataSchema } from "@calcom/prisma/zod-utils"; /** @@ -22,6 +23,7 @@ export type OrganizationBranding = fullDomain: string; /** cal.com */ domainSuffix: string; + role: MembershipRole; } & z.infer) | null | undefined; diff --git a/packages/features/settings/layouts/SettingsLayout.tsx b/packages/features/settings/layouts/SettingsLayout.tsx index 8babd6ceff..329a4efda4 100644 --- a/packages/features/settings/layouts/SettingsLayout.tsx +++ b/packages/features/settings/layouts/SettingsLayout.tsx @@ -3,7 +3,7 @@ import { useSession } from "next-auth/react"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; import type { ComponentProps } from "react"; -import React, { Suspense, useEffect, useState } from "react"; +import React, { Suspense, useEffect, useState, useMemo } from "react"; import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; import Shell from "@calcom/features/shell/Shell"; @@ -135,41 +135,59 @@ tabs.find((tab) => { // The following keys are assigned to admin only const adminRequiredKeys = ["admin"]; const organizationRequiredKeys = ["organization"]; +const organizationAdminKeys = ["privacy", "appearance", "billing", "OAuth Clients", "SSO", "directory_sync"]; const useTabs = () => { const session = useSession(); const { data: user } = trpc.viewer.me.useQuery({ includePasswordAdded: true }); const orgBranding = useOrgBranding(); const isAdmin = session.data?.user.role === UserPermissionRole.ADMIN; + const isOrgAdminOrOwner = + orgBranding?.role === MembershipRole.ADMIN || orgBranding?.role === MembershipRole.OWNER; - tabs.map((tab) => { - if (tab.href === "/settings/my-account") { - tab.name = user?.name || "my_account"; - tab.icon = undefined; - tab.avatar = getUserAvatarUrl(user); - } else if (tab.href === "/settings/organizations") { - tab.name = orgBranding?.name || "organization"; - tab.avatar = getPlaceholderAvatar(orgBranding?.logoUrl, orgBranding?.name); - } else if ( - tab.href === "/settings/security" && - user?.identityProvider === IdentityProvider.GOOGLE && - !user?.twoFactorEnabled && - !user?.passwordAdded - ) { - tab.children = tab?.children?.filter( - (childTab) => childTab.href !== "/settings/security/two-factor-auth" - ); - } - return tab; - }); + const processTabsMemod = useMemo(() => { + const processedTabs = tabs.map((tab) => { + if (tab.href === "/settings/my-account") { + return { + ...tab, + name: user?.name || "my_account", + icon: undefined, + avatar: getUserAvatarUrl(user), + }; + } else if (tab.href === "/settings/organizations") { + const newArray = (tab?.children ?? []).filter( + (child) => isOrgAdminOrOwner || !organizationAdminKeys.includes(child.name) + ); + return { + ...tab, + children: newArray, + name: orgBranding?.name || "organization", + avatar: getPlaceholderAvatar(orgBranding?.logoUrl, orgBranding?.name), + }; + } else if ( + tab.href === "/settings/security" && + user?.identityProvider === IdentityProvider.GOOGLE && + !user?.twoFactorEnabled && + !user?.passwordAdded + ) { + const filtered = tab?.children?.filter( + (childTab) => childTab.href !== "/settings/security/two-factor-auth" + ); + return { ...tab, children: filtered }; + } + return tab; + }); - // check if name is in adminRequiredKeys - return tabs.filter((tab) => { - if (organizationRequiredKeys.includes(tab.name)) return !!session.data?.user?.org; + // check if name is in adminRequiredKeys + return processedTabs.filter((tab) => { + if (organizationRequiredKeys.includes(tab.name)) return !!orgBranding; - if (isAdmin) return true; - return !adminRequiredKeys.includes(tab.name); - }); + if (isAdmin) return true; + return !adminRequiredKeys.includes(tab.name); + }); + }, [isAdmin, orgBranding, isOrgAdminOrOwner, user]); + + return processTabsMemod; }; const BackButtonInSidebar = ({ name }: { name: string }) => { diff --git a/packages/types/next-auth.d.ts b/packages/types/next-auth.d.ts index d6f3138498..438c2fa7da 100644 --- a/packages/types/next-auth.d.ts +++ b/packages/types/next-auth.d.ts @@ -1,6 +1,8 @@ import type { User as PrismaUser, UserPermissionRole } from "@prisma/client"; import type { DefaultUser } from "next-auth"; +import type { MembershipRole } from "@calcom/prisma/enums"; + import type { UserProfile } from "./UserProfile"; declare module "next-auth" { @@ -30,6 +32,7 @@ declare module "next-auth" { logoUrl?: string | null; fullDomain: string; domainSuffix: string; + role: MembershipRole; }; username?: PrismaUser["username"]; avatarUrl?: PrismaUser["avatarUrl"]; @@ -61,6 +64,7 @@ declare module "next-auth/jwt" { logoUrl?: string | null; fullDomain: string; domainSuffix: string; + role: MembershipRole; }; organizationId?: number | null; locale?: string;