Disable Impersonation Option (#2880)

* Disable Impersonation

* Update Description Copy

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
sean-brydon 2022-05-25 16:21:18 +01:00 committed by GitHub
parent e16eee68b0
commit 1bf009f5f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 117 additions and 48 deletions

View File

@ -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 (
<>
<div className="flex flex-col justify-between pt-9 pl-2 sm:flex-row">
<div>
<div className="flex flex-row items-center">
<h2 className="font-cal text-lg font-medium leading-6 text-gray-900">
{t("user_impersonation_heading")}
</h2>
<Badge className="ml-2 text-xs" variant={!disableImpersonation ? "success" : "gray"}>
{!disableImpersonation ? t("enabled") : t("disabled")}
</Badge>
</div>
<p className="mt-1 text-sm text-gray-500">{t("user_impersonation_description")}</p>
</div>
<div className="mt-5 sm:mt-0 sm:self-center">
<Button
type="submit"
color="secondary"
onClick={() =>
!disableImpersonation
? mutation.mutate({ disableImpersonation: true })
: mutation.mutate({ disableImpersonation: false })
}>
{!disableImpersonation ? t("disable") : t("enable")}
</Button>
</div>
</div>
</>
);
};
export default DisableUserImpersonation;

View File

@ -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: {

View File

@ -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() {
<ChangePasswordSection />
<ApiKeyListContainer />
<TwoFactorAuthSection twoFactorEnabled={user?.twoFactorEnabled || false} />
<DisableUserImpersonation disableImpersonation={user?.disableImpersonation ?? true} />
</div>
)}

View File

@ -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 stop</0>.",

View File

@ -44,6 +44,7 @@ async function getUserFromSession({
hideBranding: true,
avatar: true,
twoFactorEnabled: true,
disableImpersonation: true,
identityProvider: true,
brandColor: true,
darkBrandColor: true,

View File

@ -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;

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "disableImpersonation" BOOLEAN NOT NULL DEFAULT false;

View File

@ -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)
// </DEPRECATED>
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")