feat: show dialog when changing email after using Google Login (#9611)
Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Alex van Andel <me@alexvanandel.com> Co-authored-by: alannnc <alannnc@gmail.com> Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
parent
710bfc0d7e
commit
123ecf3700
|
@ -1,13 +1,9 @@
|
|||
import type { ResetPasswordRequest } from "@prisma/client";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { sendPasswordResetEmail } from "@calcom/emails";
|
||||
import { PASSWORD_RESET_EXPIRY_HOURS } from "@calcom/emails/templates/forgot-password-email";
|
||||
import { passwordResetRequest } from "@calcom/features/auth/lib/passwordResetRequest";
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { defaultHandler } from "@calcom/lib/server";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
@ -37,63 +33,16 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
});
|
||||
|
||||
try {
|
||||
const maybeUser = await prisma.user.findUnique({
|
||||
where: {
|
||||
email: email.data,
|
||||
},
|
||||
select: {
|
||||
name: true,
|
||||
identityProvider: true,
|
||||
email: true,
|
||||
locale: true,
|
||||
},
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: email.data },
|
||||
select: { name: true, email: true, locale: true },
|
||||
});
|
||||
|
||||
if (!maybeUser) {
|
||||
// Don't leak information about whether an email is registered or not
|
||||
return res
|
||||
.status(200)
|
||||
.json({ message: "If this email exists in our system, you should receive a Reset email." });
|
||||
}
|
||||
|
||||
const t = await getTranslation(maybeUser.locale ?? "en", "common");
|
||||
|
||||
const maybePreviousRequest = await prisma.resetPasswordRequest.findMany({
|
||||
where: {
|
||||
email: maybeUser.email,
|
||||
expires: {
|
||||
gt: new Date(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let passwordRequest: ResetPasswordRequest;
|
||||
|
||||
if (maybePreviousRequest && maybePreviousRequest?.length >= 1) {
|
||||
passwordRequest = maybePreviousRequest[0];
|
||||
} else {
|
||||
const expiry = dayjs().add(PASSWORD_RESET_EXPIRY_HOURS, "hours").toDate();
|
||||
const createdResetPasswordRequest = await prisma.resetPasswordRequest.create({
|
||||
data: {
|
||||
email: maybeUser.email,
|
||||
expires: expiry,
|
||||
},
|
||||
});
|
||||
passwordRequest = createdResetPasswordRequest;
|
||||
}
|
||||
|
||||
const resetLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/auth/forgot-password/${passwordRequest.id}`;
|
||||
await sendPasswordResetEmail({
|
||||
language: t,
|
||||
user: maybeUser,
|
||||
resetLink,
|
||||
});
|
||||
|
||||
return res
|
||||
.status(201)
|
||||
.json({ message: "If this email exists in our system, you should receive a Reset email." });
|
||||
// Don't leak info about whether the user exists
|
||||
if (!user) return res.status(201).json({ message: "password_reset_email_sent" });
|
||||
await passwordResetRequest(user);
|
||||
return res.status(201).json({ message: "password_reset_email_sent" });
|
||||
} catch (reason) {
|
||||
// console.error(reason);
|
||||
console.error(reason);
|
||||
return res.status(500).json({ message: "Unable to create password reset request" });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import type { GetServerSidePropsContext } from "next";
|
|||
import { getCsrfToken } from "next-auth/react";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { CSSProperties, SyntheticEvent } from "react";
|
||||
import React from "react";
|
||||
|
||||
|
@ -20,7 +19,6 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
|||
const [error, setError] = React.useState<{ message: string } | null>(null);
|
||||
const [success, setSuccess] = React.useState(false);
|
||||
const [email, setEmail] = React.useState("");
|
||||
const router = useRouter();
|
||||
|
||||
const handleChange = (e: SyntheticEvent) => {
|
||||
const target = e.target as typeof e.target & { value: string };
|
||||
|
@ -40,8 +38,6 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
|||
const json = await res.json();
|
||||
if (!res.ok) {
|
||||
setError(json);
|
||||
} else if ("resetLink" in json) {
|
||||
router.push(json.resetLink);
|
||||
} else {
|
||||
setSuccess(true);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,12 @@ export function Logout(props: Props) {
|
|||
}, [props.query?.survey]);
|
||||
const { t } = useLocale();
|
||||
|
||||
const message = () => {
|
||||
if (props.query?.passReset === "true") return "reset_your_password";
|
||||
if (props.query?.emailChange === "true") return "email_change";
|
||||
return "hope_to_see_you_soon";
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContainer title={t("logged_out")} description={t("youve_been_logged_out")} showLogo>
|
||||
<div className="mb-4">
|
||||
|
@ -40,7 +46,7 @@ export function Logout(props: Props) {
|
|||
{t("youve_been_logged_out")}
|
||||
</h3>
|
||||
<div className="mt-2">
|
||||
<p className="text-subtle text-sm">{t("hope_to_see_you_soon")}</p>
|
||||
<p className="text-subtle text-sm">{t(message())}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { signOut } from "next-auth/react";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import type { BaseSyntheticEvent } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { APP_NAME, FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { md } from "@calcom/lib/markdownIt";
|
||||
import turndown from "@calcom/lib/turndownService";
|
||||
|
@ -26,6 +24,7 @@ import {
|
|||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogTrigger,
|
||||
Editor,
|
||||
Form,
|
||||
ImageUploader,
|
||||
Label,
|
||||
|
@ -37,7 +36,6 @@ import {
|
|||
SkeletonContainer,
|
||||
SkeletonText,
|
||||
TextField,
|
||||
Editor,
|
||||
} from "@calcom/ui";
|
||||
import { AlertTriangle, Trash2 } from "@calcom/ui/components/icon";
|
||||
|
||||
|
@ -83,12 +81,23 @@ const ProfileView = () => {
|
|||
|
||||
const { data: user, isLoading } = trpc.viewer.me.useQuery();
|
||||
const { data: avatar, isLoading: isLoadingAvatar } = trpc.viewer.avatar.useQuery();
|
||||
const mutation = trpc.viewer.updateProfile.useMutation({
|
||||
onSuccess: (values) => {
|
||||
const updateProfileMutation = trpc.viewer.updateProfile.useMutation({
|
||||
onSuccess: async (res) => {
|
||||
showToast(t("settings_updated_successfully"), "success");
|
||||
if (res.signOutUser && tempFormValues) {
|
||||
if (res.passwordReset) {
|
||||
showToast(t("password_reset_email", { email: tempFormValues.email }), "success");
|
||||
// sign out the user to avoid unauthorized access error
|
||||
await signOut({ callbackUrl: "/auth/logout?passReset=true" });
|
||||
} else {
|
||||
// sign out the user to avoid unauthorized access error
|
||||
await signOut({ callbackUrl: "/auth/logout?emailChange=true" });
|
||||
}
|
||||
}
|
||||
utils.viewer.me.invalidate();
|
||||
utils.viewer.avatar.invalidate();
|
||||
update(values);
|
||||
setConfirmAuthEmailChangeWarningDialogOpen(false);
|
||||
update(res);
|
||||
setTempFormValues(null);
|
||||
},
|
||||
onError: () => {
|
||||
|
@ -99,6 +108,8 @@ const ProfileView = () => {
|
|||
const [confirmPasswordOpen, setConfirmPasswordOpen] = useState(false);
|
||||
const [tempFormValues, setTempFormValues] = useState<FormValues | null>(null);
|
||||
const [confirmPasswordErrorMessage, setConfirmPasswordDeleteErrorMessage] = useState("");
|
||||
const [confirmAuthEmailChangeWarningDialogOpen, setConfirmAuthEmailChangeWarningDialogOpen] =
|
||||
useState(false);
|
||||
|
||||
const [deleteAccountOpen, setDeleteAccountOpen] = useState(false);
|
||||
const [hasDeleteErrors, setHasDeleteErrors] = useState(false);
|
||||
|
@ -119,9 +130,7 @@ const ProfileView = () => {
|
|||
|
||||
const confirmPasswordMutation = trpc.viewer.auth.verifyPassword.useMutation({
|
||||
onSuccess() {
|
||||
if (tempFormValues) {
|
||||
mutation.mutate(tempFormValues);
|
||||
}
|
||||
if (tempFormValues) updateProfileMutation.mutate(tempFormValues);
|
||||
setConfirmPasswordOpen(false);
|
||||
},
|
||||
onError() {
|
||||
|
@ -148,7 +157,7 @@ const ProfileView = () => {
|
|||
},
|
||||
});
|
||||
|
||||
const isCALIdentityProviver = user?.identityProvider === IdentityProvider.CAL;
|
||||
const isCALIdentityProvider = user?.identityProvider === IdentityProvider.CAL;
|
||||
|
||||
const onConfirmPassword = (e: Event | React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
|
@ -157,9 +166,15 @@ const ProfileView = () => {
|
|||
confirmPasswordMutation.mutate({ passwordInput: password });
|
||||
};
|
||||
|
||||
const onConfirmAuthEmailChange = (e: Event | React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (tempFormValues) updateProfileMutation.mutate(tempFormValues);
|
||||
};
|
||||
|
||||
const onConfirmButton = (e: Event | React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
if (isCALIdentityProviver) {
|
||||
if (isCALIdentityProvider) {
|
||||
const totpCode = form.getValues("totpCode");
|
||||
const password = passwordRef.current.value;
|
||||
deleteMeMutation.mutate({ password, totpCode });
|
||||
|
@ -170,7 +185,7 @@ const ProfileView = () => {
|
|||
|
||||
const onConfirm = ({ totpCode }: DeleteAccountValues, e: BaseSyntheticEvent | undefined) => {
|
||||
e?.preventDefault();
|
||||
if (isCALIdentityProviver) {
|
||||
if (isCALIdentityProvider) {
|
||||
const password = passwordRef.current.value;
|
||||
deleteMeMutation.mutate({ password, totpCode });
|
||||
} else {
|
||||
|
@ -209,13 +224,17 @@ const ProfileView = () => {
|
|||
<ProfileForm
|
||||
key={JSON.stringify(defaultValues)}
|
||||
defaultValues={defaultValues}
|
||||
isLoading={mutation.isLoading}
|
||||
isLoading={updateProfileMutation.isLoading}
|
||||
onSubmit={(values) => {
|
||||
if (values.email !== user.email && isCALIdentityProviver) {
|
||||
if (values.email !== user.email && isCALIdentityProvider) {
|
||||
setTempFormValues(values);
|
||||
setConfirmPasswordOpen(true);
|
||||
} else if (values.email !== user.email && !isCALIdentityProvider) {
|
||||
setTempFormValues(values);
|
||||
// Opens a dialog warning the change
|
||||
setConfirmAuthEmailChangeWarningDialogOpen(true);
|
||||
} else {
|
||||
mutation.mutate(values);
|
||||
updateProfileMutation.mutate(values);
|
||||
}
|
||||
}}
|
||||
extraField={
|
||||
|
@ -253,7 +272,7 @@ const ProfileView = () => {
|
|||
<p className="text-default mb-4">
|
||||
{t("delete_account_confirmation_message", { appName: APP_NAME })}
|
||||
</p>
|
||||
{isCALIdentityProviver && (
|
||||
{isCALIdentityProvider && (
|
||||
<PasswordField
|
||||
data-testid="password"
|
||||
name="password"
|
||||
|
@ -265,7 +284,7 @@ const ProfileView = () => {
|
|||
/>
|
||||
)}
|
||||
|
||||
{user?.twoFactorEnabled && isCALIdentityProviver && (
|
||||
{user?.twoFactorEnabled && isCALIdentityProvider && (
|
||||
<Form handleSubmit={onConfirm} className="pb-4" form={form}>
|
||||
<TwoFactor center={false} />
|
||||
</Form>
|
||||
|
@ -314,6 +333,27 @@ const ProfileView = () => {
|
|||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* If changing email from !CAL Login */}
|
||||
<Dialog
|
||||
open={confirmAuthEmailChangeWarningDialogOpen}
|
||||
onOpenChange={setConfirmAuthEmailChangeWarningDialogOpen}>
|
||||
<DialogContent
|
||||
title={t("confirm_auth_change")}
|
||||
description={t("confirm_auth_email_change")}
|
||||
type="creation"
|
||||
Icon={AlertTriangle}>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={updateProfileMutation.isLoading}
|
||||
onClick={(e) => onConfirmAuthEmailChange(e)}>
|
||||
{t("confirm")}
|
||||
</Button>
|
||||
<DialogClose />
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
"event_awaiting_approval_recurring": "A recurring event is waiting for your approval",
|
||||
"someone_requested_an_event": "Someone has requested to schedule an event on your calendar.",
|
||||
"someone_requested_password_reset": "Someone has requested a link to change your password.",
|
||||
"password_reset_email_sent": "If this email exists in our system, you should receive a reset email.",
|
||||
"password_reset_instructions": "If you didn't request this, you can safely ignore this email and your password will not be changed.",
|
||||
"event_awaiting_approval_subject": "Awaiting Approval: {{title}} at {{date}}",
|
||||
"event_still_awaiting_approval": "An event is still waiting for your approval",
|
||||
|
@ -223,6 +224,10 @@
|
|||
"already_have_an_account": "Already have an account?",
|
||||
"create_account": "Create Account",
|
||||
"confirm_password": "Confirm password",
|
||||
"confirm_auth_change": "This will change the way you log in",
|
||||
"confirm_auth_email_change": "Changing the email address will disconnect your current authentication method to log in to Cal.com. We will ask you to verify your new email address. Moving forward, you will be logged out and use your new email address to log in instead of your current authentication method after setting your password by following the instructions that will be sent to your mail.",
|
||||
"reset_your_password": "Set your new password with the instructions sent to your email address.",
|
||||
"email_change": "Log back in with your new email address and password.",
|
||||
"create_your_account": "Create your account",
|
||||
"sign_up": "Sign up",
|
||||
"youve_been_logged_out": "You've been logged out",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TFunction } from "next-i18next";
|
||||
import type { TFunction } from "next-i18next";
|
||||
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
|
||||
|
@ -14,8 +14,6 @@ export type PasswordReset = {
|
|||
resetLink: string;
|
||||
};
|
||||
|
||||
export const PASSWORD_RESET_EXPIRY_HOURS = 6;
|
||||
|
||||
export default class ForgotPasswordEmail extends BaseEmail {
|
||||
passwordEvent: PasswordReset;
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import type { User } from "@prisma/client";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { sendPasswordResetEmail } from "@calcom/emails";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export const PASSWORD_RESET_EXPIRY_HOURS = 6;
|
||||
|
||||
const RECENT_MAX_ATTEMPTS = 3;
|
||||
const RECENT_PERIOD_IN_MINUTES = 5;
|
||||
|
||||
const createPasswordReset = async (email: string): Promise<string> => {
|
||||
const expiry = dayjs().add(PASSWORD_RESET_EXPIRY_HOURS, "hours").toDate();
|
||||
const createdResetPasswordRequest = await prisma.resetPasswordRequest.create({
|
||||
data: {
|
||||
email,
|
||||
expires: expiry,
|
||||
},
|
||||
});
|
||||
|
||||
return `${process.env.NEXT_PUBLIC_WEBAPP_URL}/auth/forgot-password/${createdResetPasswordRequest.id}`;
|
||||
};
|
||||
|
||||
const guardAgainstTooManyPasswordResets = async (email: string) => {
|
||||
const recentPasswordRequestsCount = await prisma.resetPasswordRequest.count({
|
||||
where: {
|
||||
email,
|
||||
createdAt: {
|
||||
gt: dayjs().subtract(RECENT_PERIOD_IN_MINUTES, "minutes").toDate(),
|
||||
},
|
||||
},
|
||||
});
|
||||
if (recentPasswordRequestsCount >= RECENT_MAX_ATTEMPTS) {
|
||||
throw new Error("Too many password reset attempts. Please try again later.");
|
||||
}
|
||||
};
|
||||
const passwordResetRequest = async (user: Pick<User, "email" | "name" | "locale">) => {
|
||||
const { email } = user;
|
||||
const t = await getTranslation(user.locale ?? "en", "common");
|
||||
await guardAgainstTooManyPasswordResets(email);
|
||||
const resetLink = await createPasswordReset(email);
|
||||
// send email in user language
|
||||
await sendPasswordResetEmail({
|
||||
language: t,
|
||||
user,
|
||||
resetLink,
|
||||
});
|
||||
};
|
||||
|
||||
export { passwordResetRequest };
|
|
@ -19,7 +19,7 @@ function detectTransport(): SendmailTransport.Options | SMTPConnection.Options |
|
|||
},
|
||||
secure: port === 465,
|
||||
tls: {
|
||||
rejectUnauthorized: isENVDev ? false : true,
|
||||
rejectUnauthorized: !isENVDev,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import type { Prisma } from "@prisma/client";
|
||||
import type { NextApiResponse, GetServerSidePropsContext } from "next";
|
||||
import type { GetServerSidePropsContext, NextApiResponse } from "next";
|
||||
|
||||
import stripe from "@calcom/app-store/stripepayment/lib/server";
|
||||
import { getPremiumPlanProductId } from "@calcom/app-store/stripepayment/lib/utils";
|
||||
import { passwordResetRequest } from "@calcom/features/auth/lib/passwordResetRequest";
|
||||
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
|
||||
import { getTranslation } from "@calcom/lib/server";
|
||||
import { checkUsername } from "@calcom/lib/server/checkUsername";
|
||||
|
@ -11,6 +12,7 @@ import slugify from "@calcom/lib/slugify";
|
|||
import { updateWebUser as syncServicesUpdateWebUser } from "@calcom/lib/sync/SyncServiceManager";
|
||||
import { validateBookerLayouts } from "@calcom/lib/validateBookerLayouts";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
import { userMetadata } from "@calcom/prisma/zod-utils";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
|
@ -33,6 +35,9 @@ export const updateProfileHandler = async ({ ctx, input }: UpdateProfileOptions)
|
|||
metadata: input.metadata as Prisma.InputJsonValue,
|
||||
};
|
||||
|
||||
// some actions can invalidate a user session.
|
||||
let signOutUser = false;
|
||||
let passwordReset = false;
|
||||
let isPremiumUsername = false;
|
||||
|
||||
const layoutError = validateBookerLayouts(input?.metadata?.defaultBookerLayouts || null);
|
||||
|
@ -56,16 +61,7 @@ export const updateProfileHandler = async ({ ctx, input }: UpdateProfileOptions)
|
|||
if (input.avatar) {
|
||||
data.avatar = await resizeBase64Image(input.avatar);
|
||||
}
|
||||
const userToUpdate = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!userToUpdate) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "User not found" });
|
||||
}
|
||||
const metadata = userMetadata.parse(userToUpdate.metadata);
|
||||
const metadata = userMetadata.parse(user.metadata);
|
||||
|
||||
const isPremium = metadata?.isPremium;
|
||||
if (isPremiumUsername) {
|
||||
|
@ -98,12 +94,26 @@ export const updateProfileHandler = async ({ ctx, input }: UpdateProfileOptions)
|
|||
});
|
||||
}
|
||||
}
|
||||
const hasEmailBeenChanged = userToUpdate.email !== data.email;
|
||||
const hasEmailBeenChanged = data.email && user.email !== data.email;
|
||||
|
||||
if (hasEmailBeenChanged) {
|
||||
data.emailVerified = null;
|
||||
}
|
||||
|
||||
// check if we are changing email and identity provider is not CAL
|
||||
const hasEmailChangedOnNonCalProvider =
|
||||
hasEmailBeenChanged && user.identityProvider !== IdentityProvider.CAL;
|
||||
const hasEmailChangedOnCalProvider = hasEmailBeenChanged && user.identityProvider === IdentityProvider.CAL;
|
||||
|
||||
if (hasEmailChangedOnNonCalProvider) {
|
||||
// Only validate if we're changing email
|
||||
data.identityProvider = IdentityProvider.CAL;
|
||||
data.identityProviderId = null;
|
||||
} else if (hasEmailChangedOnCalProvider) {
|
||||
// when the email changes, the user needs to sign in again.
|
||||
signOutUser = true;
|
||||
}
|
||||
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
|
@ -113,12 +123,23 @@ export const updateProfileHandler = async ({ ctx, input }: UpdateProfileOptions)
|
|||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
identityProvider: true,
|
||||
identityProviderId: true,
|
||||
metadata: true,
|
||||
name: true,
|
||||
createdDate: true,
|
||||
locale: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (hasEmailChangedOnNonCalProvider) {
|
||||
// Because the email has changed, we are now attempting to use the CAL provider-
|
||||
// which has no password yet. We have to send the reset password email.
|
||||
await passwordResetRequest(updatedUser);
|
||||
signOutUser = true;
|
||||
passwordReset = true;
|
||||
}
|
||||
|
||||
// Sync Services
|
||||
await syncServicesUpdateWebUser(updatedUser);
|
||||
|
||||
|
@ -154,5 +175,5 @@ export const updateProfileHandler = async ({ ctx, input }: UpdateProfileOptions)
|
|||
.then(() => console.info("Booking pages revalidated"))
|
||||
.catch((e) => console.error(e));
|
||||
}*/
|
||||
return input;
|
||||
return { ...input, signOutUser, passwordReset };
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import dayjs from "@calcom/dayjs";
|
||||
import { sendPasswordResetEmail } from "@calcom/emails";
|
||||
import { PASSWORD_RESET_EXPIRY_HOURS } from "@calcom/emails/templates/forgot-password-email";
|
||||
import { PASSWORD_RESET_EXPIRY_HOURS } from "@calcom/features/auth/lib/passwordResetRequest";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user