fix: 2FA from CAL to GOOGLE (#10288)
* Fixing 2FA from CAL to GOOGLE * Missing change * Missing logic for converted providers * Fixing edge case and applying feedback * Leftovers
This commit is contained in:
parent
b49952b8d8
commit
d1778fdce8
|
@ -12,7 +12,7 @@ import TwoFactorAuthAPI from "./TwoFactorAuthAPI";
|
|||
interface DisableTwoFactorAuthModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: () => void;
|
||||
|
||||
disablePassword?: boolean;
|
||||
/** Called when the user closes the modal without disabling two-factor auth */
|
||||
onCancel: () => void;
|
||||
/** Called when the user disables two-factor auth */
|
||||
|
@ -27,6 +27,7 @@ interface DisableTwoFactorValues {
|
|||
const DisableTwoFactorAuthModal = ({
|
||||
onDisable,
|
||||
onCancel,
|
||||
disablePassword,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: DisableTwoFactorAuthModalProps) => {
|
||||
|
@ -75,19 +76,21 @@ const DisableTwoFactorAuthModal = ({
|
|||
<DialogContent title={t("disable_2fa")} description={t("disable_2fa_recommendation")} type="creation">
|
||||
<Form form={form} handleSubmit={handleDisable}>
|
||||
<div className="mb-8">
|
||||
<PasswordField
|
||||
labelProps={{
|
||||
className: "block text-sm font-medium text-default",
|
||||
}}
|
||||
{...form.register("password")}
|
||||
className="border-default mt-1 block w-full rounded-md border px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
|
||||
/>
|
||||
{!disablePassword && (
|
||||
<PasswordField
|
||||
labelProps={{
|
||||
className: "block text-sm font-medium text-default",
|
||||
}}
|
||||
{...form.register("password")}
|
||||
className="border-default mt-1 block w-full rounded-md border px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
|
||||
/>
|
||||
)}
|
||||
<TwoFactor center={false} />
|
||||
|
||||
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
|
||||
</div>
|
||||
|
||||
<DialogFooter showDivider className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<DialogFooter showDivider className="relative mt-5">
|
||||
<Button color="secondary" onClick={onCancel}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
|
|
|
@ -185,7 +185,7 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
|
|||
)}
|
||||
</div>
|
||||
</WithStep>
|
||||
<DialogFooter className="mt-8 sm:flex sm:flex-row-reverse" showDivider>
|
||||
<DialogFooter className="mt-8" showDivider>
|
||||
<Button color="secondary" onClick={onCancel}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|||
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
|
||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { IdentityProvider } from "@calcom/prisma/client";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== "POST") {
|
||||
|
@ -28,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
if (!user.password) {
|
||||
if (!user.password && user.identityProvider === IdentityProvider.CAL) {
|
||||
return res.status(400).json({ error: ErrorCode.UserMissingPassword });
|
||||
}
|
||||
|
||||
|
@ -36,9 +37,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
return res.json({ message: "Two factor disabled" });
|
||||
}
|
||||
|
||||
const isCorrectPassword = await verifyPassword(req.body.password, user.password);
|
||||
if (!isCorrectPassword) {
|
||||
return res.status(400).json({ error: ErrorCode.IncorrectPassword });
|
||||
if (user.password && user.identityProvider === IdentityProvider.CAL) {
|
||||
const isCorrectPassword = await verifyPassword(req.body.password, user.password);
|
||||
if (!isCorrectPassword) {
|
||||
return res.status(400).json({ error: ErrorCode.IncorrectPassword });
|
||||
}
|
||||
}
|
||||
// if user has 2fa
|
||||
if (user.twoFactorEnabled) {
|
||||
|
|
|
@ -57,7 +57,7 @@ export default function Login({
|
|||
.string()
|
||||
.min(1, `${t("error_required_field")}`)
|
||||
.email(`${t("enter_valid_email")}`),
|
||||
password: z.string().min(1, `${t("error_required_field")}`),
|
||||
password: !!totpEmail ? z.literal("") : z.string().min(1, `${t("error_required_field")}`),
|
||||
})
|
||||
// Passthrough other fields like totpCode
|
||||
.passthrough();
|
||||
|
|
|
@ -34,14 +34,15 @@ const TwoFactorAuthView = () => {
|
|||
if (isLoading) return <SkeletonLoader />;
|
||||
|
||||
const isCalProvider = user?.identityProvider === "CAL";
|
||||
const canSetupTwoFactor = !isCalProvider && !user?.twoFactorEnabled;
|
||||
return (
|
||||
<>
|
||||
<Meta title={t("2fa")} description={t("set_up_two_factor_authentication")} />
|
||||
{!isCalProvider && <Alert severity="neutral" message={t("2fa_disabled")} />}
|
||||
{canSetupTwoFactor && <Alert severity="neutral" message={t("2fa_disabled")} />}
|
||||
<div className="mt-6 flex items-start space-x-4">
|
||||
<Switch
|
||||
data-testid="two-factor-switch"
|
||||
disabled={!isCalProvider}
|
||||
disabled={canSetupTwoFactor}
|
||||
checked={user?.twoFactorEnabled}
|
||||
onCheckedChange={() =>
|
||||
user?.twoFactorEnabled ? setDisableModalOpen(true) : setEnableModalOpen(true)
|
||||
|
@ -72,6 +73,7 @@ const TwoFactorAuthView = () => {
|
|||
|
||||
<DisableTwoFactorModal
|
||||
open={disableModalOpen}
|
||||
disablePassword={!isCalProvider}
|
||||
onOpenChange={() => setDisableModalOpen(!disableModalOpen)}
|
||||
onDisable={() => {
|
||||
setDisableModalOpen(false);
|
||||
|
|
|
@ -115,7 +115,7 @@ const providers: Provider[] = [
|
|||
throw new Error(ErrorCode.IncorrectEmailPassword);
|
||||
}
|
||||
|
||||
if (user.password || !credentials.totpCode) {
|
||||
if (user.password && !credentials.totpCode) {
|
||||
if (!user.password) {
|
||||
throw new Error(ErrorCode.IncorrectEmailPassword);
|
||||
}
|
||||
|
@ -560,7 +560,7 @@ export const AUTH_OPTIONS: AuthOptions = {
|
|||
console.error("Error while linking account of already existing user");
|
||||
}
|
||||
}
|
||||
if (existingUser.twoFactorEnabled) {
|
||||
if (existingUser.twoFactorEnabled && existingUser.identityProvider === idP) {
|
||||
return loginWithTotp(existingUser);
|
||||
} else {
|
||||
return true;
|
||||
|
@ -600,7 +600,11 @@ export const AUTH_OPTIONS: AuthOptions = {
|
|||
|
||||
if (existingUserWithEmail) {
|
||||
// if self-hosted then we can allow auto-merge of identity providers if email is verified
|
||||
if (!hostedCal && existingUserWithEmail.emailVerified) {
|
||||
if (
|
||||
!hostedCal &&
|
||||
existingUserWithEmail.emailVerified &&
|
||||
existingUserWithEmail.identityProvider !== IdentityProvider.CAL
|
||||
) {
|
||||
if (existingUserWithEmail.twoFactorEnabled) {
|
||||
return loginWithTotp(existingUserWithEmail);
|
||||
} else {
|
||||
|
|
|
@ -147,7 +147,11 @@ const useTabs = () => {
|
|||
tab.name = user?.name || "my_account";
|
||||
tab.icon = undefined;
|
||||
tab.avatar = avatar?.avatar || WEBAPP_URL + "/" + session?.data?.user?.username + "/avatar.png";
|
||||
} else if (tab.href === "/settings/security" && user?.identityProvider === IdentityProvider.GOOGLE) {
|
||||
} else if (
|
||||
tab.href === "/settings/security" &&
|
||||
user?.identityProvider === IdentityProvider.GOOGLE &&
|
||||
!user?.twoFactorEnabled
|
||||
) {
|
||||
tab.children = tab?.children?.filter(
|
||||
(childTab) => childTab.href !== "/settings/security/two-factor-auth"
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue
Block a user