diff --git a/apps/web/components/security/DisableUserImpersonation.tsx b/apps/web/components/security/DisableUserImpersonation.tsx
new file mode 100644
index 0000000000..66c978398d
--- /dev/null
+++ b/apps/web/components/security/DisableUserImpersonation.tsx
@@ -0,0 +1,55 @@
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import showToast from "@calcom/lib/notification";
+import Button from "@calcom/ui/Button";
+
+import { trpc } from "@lib/trpc";
+
+import Badge from "@components/ui/Badge";
+
+const DisableUserImpersonation = ({ disableImpersonation }: { disableImpersonation: boolean }) => {
+ const utils = trpc.useContext();
+
+ const { t } = useLocale();
+
+ const mutation = trpc.useMutation("viewer.updateProfile", {
+ onSuccess: async () => {
+ showToast(t("your_user_profile_updated_successfully"), "success");
+ await utils.invalidateQueries(["viewer.me"]);
+ },
+ async onSettled() {
+ await utils.invalidateQueries(["viewer.i18n"]);
+ },
+ });
+
+ return (
+ <>
+
+
+
+
+ {t("user_impersonation_heading")}
+
+
+ {!disableImpersonation ? t("enabled") : t("disabled")}
+
+
+
{t("user_impersonation_description")}
+
+
+
+
+
+ >
+ );
+};
+
+export default DisableUserImpersonation;
diff --git a/apps/web/ee/lib/impersonation/ImpersonationProvider.ts b/apps/web/ee/lib/impersonation/ImpersonationProvider.ts
index 331d5ba3c3..6d53b683f1 100644
--- a/apps/web/ee/lib/impersonation/ImpersonationProvider.ts
+++ b/apps/web/ee/lib/impersonation/ImpersonationProvider.ts
@@ -32,6 +32,10 @@ const ImpersonationProvider = CredentialsProvider({
throw new Error("This user does not exist");
}
+ if (user.disableImpersonation) {
+ throw new Error("This user has disabled Impersonation.");
+ }
+
// Log impersonations for audit purposes
await prisma.impersonations.create({
data: {
diff --git a/apps/web/pages/settings/security.tsx b/apps/web/pages/settings/security.tsx
index 58e8e57ea3..1fda179798 100644
--- a/apps/web/pages/settings/security.tsx
+++ b/apps/web/pages/settings/security.tsx
@@ -11,6 +11,7 @@ import { trpc } from "@lib/trpc";
import SettingsShell from "@components/SettingsShell";
import Shell from "@components/Shell";
import ChangePasswordSection from "@components/security/ChangePasswordSection";
+import DisableUserImpersonation from "@components/security/DisableUserImpersonation";
import TwoFactorAuthSection from "@components/security/TwoFactorAuthSection";
export default function Security() {
@@ -39,6 +40,7 @@ export default function Security() {
+
)}
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index 56f74193ae..d436894040 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -816,6 +816,8 @@
"confirmation_page_gif": "Gif for confirmation page",
"search": "Search",
"impersonate": "Impersonate",
+ "user_impersonation_heading":"User Impersonation",
+ "user_impersonation_description":"Allows our support team to temporarily sign in as you to help us quickly resolve any issues you report to us.",
"impersonate_user_tip": "All uses of this feature is audited.",
"impersonating_user_warning": "Impersonating username \"{{user}}\".",
"impersonating_stop_instructions": "<0>Click Here to stop0>.",
diff --git a/apps/web/server/createContext.ts b/apps/web/server/createContext.ts
index 6b4c6cfdc7..c57f1bbe97 100644
--- a/apps/web/server/createContext.ts
+++ b/apps/web/server/createContext.ts
@@ -44,6 +44,7 @@ async function getUserFromSession({
hideBranding: true,
avatar: true,
twoFactorEnabled: true,
+ disableImpersonation: true,
identityProvider: true,
brandColor: true,
darkBrandColor: true,
diff --git a/apps/web/server/routers/viewer.tsx b/apps/web/server/routers/viewer.tsx
index c3cdf010ac..daaccbd8c4 100644
--- a/apps/web/server/routers/viewer.tsx
+++ b/apps/web/server/routers/viewer.tsx
@@ -86,6 +86,7 @@ const loggedInViewerRouter = createProtectedRouter()
trialEndsAt: user.trialEndsAt,
completedOnboarding: user.completedOnboarding,
twoFactorEnabled: user.twoFactorEnabled,
+ disableImpersonation: user.disableImpersonation,
identityProvider: user.identityProvider,
brandColor: user.brandColor,
darkBrandColor: user.darkBrandColor,
@@ -681,6 +682,7 @@ const loggedInViewerRouter = createProtectedRouter()
completedOnboarding: z.boolean().optional(),
locale: z.string().optional(),
timeFormat: z.number().optional(),
+ disableImpersonation: z.boolean().optional(),
}),
async resolve({ input, ctx }) {
const { user, prisma } = ctx;
diff --git a/packages/prisma/migrations/20220525110759_add_user_impersonation_toggle/migration.sql b/packages/prisma/migrations/20220525110759_add_user_impersonation_toggle/migration.sql
new file mode 100644
index 0000000000..8872876bfa
--- /dev/null
+++ b/packages/prisma/migrations/20220525110759_add_user_impersonation_toggle/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "users" ADD COLUMN "disableImpersonation" BOOLEAN NOT NULL DEFAULT false;
diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma
index 8716d18912..e6023a097c 100644
--- a/packages/prisma/schema.prisma
+++ b/packages/prisma/schema.prisma
@@ -119,59 +119,60 @@ enum UserPermissionRole {
}
model User {
- id Int @id @default(autoincrement())
- username String? @unique
- name String?
+ id Int @id @default(autoincrement())
+ username String? @unique
+ name String?
/// @zod.email()
- email String @unique
- emailVerified DateTime?
- password String?
- bio String?
- avatar String?
- timeZone String @default("Europe/London")
- weekStart String @default("Sunday")
+ email String @unique
+ emailVerified DateTime?
+ password String?
+ bio String?
+ avatar String?
+ timeZone String @default("Europe/London")
+ weekStart String @default("Sunday")
// DEPRECATED - TO BE REMOVED
- startTime Int @default(0)
- endTime Int @default(1440)
+ startTime Int @default(0)
+ endTime Int @default(1440)
//
- bufferTime Int @default(0)
- hideBranding Boolean @default(false)
- theme String?
- createdDate DateTime @default(now()) @map(name: "created")
- trialEndsAt DateTime?
- eventTypes EventType[] @relation("user_eventtype")
- credentials Credential[]
- teams Membership[]
- bookings Booking[]
- schedules Schedule[]
- defaultScheduleId Int?
- selectedCalendars SelectedCalendar[]
- completedOnboarding Boolean @default(false)
- locale String?
- timeFormat Int? @default(12)
- twoFactorSecret String?
- twoFactorEnabled Boolean @default(false)
- identityProvider IdentityProvider @default(CAL)
- identityProviderId String?
- availability Availability[]
- invitedTo Int?
- plan UserPlan @default(TRIAL)
- webhooks Webhook[]
- brandColor String @default("#292929")
- darkBrandColor String @default("#fafafa")
+ bufferTime Int @default(0)
+ hideBranding Boolean @default(false)
+ theme String?
+ createdDate DateTime @default(now()) @map(name: "created")
+ trialEndsAt DateTime?
+ eventTypes EventType[] @relation("user_eventtype")
+ credentials Credential[]
+ teams Membership[]
+ bookings Booking[]
+ schedules Schedule[]
+ defaultScheduleId Int?
+ selectedCalendars SelectedCalendar[]
+ completedOnboarding Boolean @default(false)
+ locale String?
+ timeFormat Int? @default(12)
+ twoFactorSecret String?
+ twoFactorEnabled Boolean @default(false)
+ identityProvider IdentityProvider @default(CAL)
+ identityProviderId String?
+ availability Availability[]
+ invitedTo Int?
+ plan UserPlan @default(TRIAL)
+ webhooks Webhook[]
+ brandColor String @default("#292929")
+ darkBrandColor String @default("#fafafa")
// the location where the events will end up
- destinationCalendar DestinationCalendar?
- away Boolean @default(false)
+ destinationCalendar DestinationCalendar?
+ away Boolean @default(false)
// participate in dynamic group booking or not
- allowDynamicBooking Boolean? @default(true)
- metadata Json?
- verified Boolean? @default(false)
- role UserPermissionRole @default(USER)
- impersonatedUsers Impersonations[] @relation("impersonated_user")
- impersonatedBy Impersonations[] @relation("impersonated_by_user")
- apiKeys ApiKey[]
- accounts Account[]
- sessions Session[]
+ allowDynamicBooking Boolean? @default(true)
+ metadata Json?
+ verified Boolean? @default(false)
+ role UserPermissionRole @default(USER)
+ disableImpersonation Boolean @default(false)
+ impersonatedUsers Impersonations[] @relation("impersonated_user")
+ impersonatedBy Impersonations[] @relation("impersonated_by_user")
+ apiKeys ApiKey[]
+ accounts Account[]
+ sessions Session[]
Feedback Feedback[]
@@map(name: "users")