-
Notifications
You must be signed in to change notification settings - Fork 7.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Create change password screen * Add two factor auth screen * Add two factor auth screen * Remove header file * Updates middleware and rewrites * Adds Meta component to handle layout headings/metadata (#4021) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: zomars <zomars@me.com>
- Loading branch information
1 parent
e3d0347
commit 2174dc0
Showing
24 changed files
with
844 additions
and
122 deletions.
There are no files selected for viewing
107 changes: 107 additions & 0 deletions
107
apps/web/components/v2/settings/DisableTwoFactorModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { SyntheticEvent, useState } from "react"; | ||
|
||
import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||
import Button from "@calcom/ui/v2/core/Button"; | ||
import { Dialog, DialogContent } from "@calcom/ui/v2/core/Dialog"; | ||
|
||
import { ErrorCode } from "@lib/auth"; | ||
|
||
import TwoFactorAuthAPI from "./TwoFactorAuthAPI"; | ||
|
||
interface DisableTwoFactorAuthModalProps { | ||
open: boolean; | ||
onOpenChange: () => void; | ||
|
||
/** Called when the user closes the modal without disabling two-factor auth */ | ||
onCancel: () => void; | ||
/** Called when the user disables two-factor auth */ | ||
onDisable: () => void; | ||
} | ||
|
||
const DisableTwoFactorAuthModal = ({ | ||
onDisable, | ||
onCancel, | ||
open, | ||
onOpenChange, | ||
}: DisableTwoFactorAuthModalProps) => { | ||
const [password, setPassword] = useState(""); | ||
const [isDisabling, setIsDisabling] = useState(false); | ||
const [errorMessage, setErrorMessage] = useState<string | null>(null); | ||
const { t } = useLocale(); | ||
|
||
async function handleDisable(e: SyntheticEvent) { | ||
e.preventDefault(); | ||
|
||
if (isDisabling) { | ||
return; | ||
} | ||
setIsDisabling(true); | ||
setErrorMessage(null); | ||
|
||
try { | ||
const response = await TwoFactorAuthAPI.disable(password); | ||
if (response.status === 200) { | ||
onDisable(); | ||
return; | ||
} | ||
|
||
const body = await response.json(); | ||
if (body.error === ErrorCode.IncorrectPassword) { | ||
setErrorMessage(t("incorrect_password")); | ||
} else { | ||
setErrorMessage(t("something_went_wrong")); | ||
} | ||
} catch (e) { | ||
setErrorMessage(t("something_went_wrong")); | ||
console.error(t("error_disabling_2fa"), e); | ||
} finally { | ||
setIsDisabling(false); | ||
} | ||
} | ||
|
||
return ( | ||
<Dialog open={open} onOpenChange={onOpenChange}> | ||
<DialogContent | ||
title={t("disable_2fa")} | ||
description={t("disable_2fa_recommendation")} | ||
type="creation" | ||
useOwnActionButtons> | ||
<form onSubmit={handleDisable}> | ||
<div className="mb-4"> | ||
<label htmlFor="password" className="mt-4 block text-sm font-medium text-gray-700"> | ||
{t("password")} | ||
</label> | ||
<div className="mt-1"> | ||
<input | ||
type="password" | ||
name="password" | ||
id="password" | ||
required | ||
value={password} | ||
onInput={(e) => setPassword(e.currentTarget.value)} | ||
className="block w-full rounded-sm border-gray-300 text-sm" | ||
/> | ||
</div> | ||
|
||
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>} | ||
</div> | ||
</form> | ||
|
||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse"> | ||
<Button | ||
type="submit" | ||
className="ltr:ml-2 rtl:mr-2" | ||
onClick={handleDisable} | ||
disabled={password.length === 0 || isDisabling}> | ||
{t("disable")} | ||
</Button> | ||
<Button color="secondary" onClick={onCancel}> | ||
{t("cancel")} | ||
</Button> | ||
</div> | ||
</DialogContent> | ||
</Dialog> | ||
); | ||
}; | ||
|
||
export default DisableTwoFactorAuthModal; |
232 changes: 232 additions & 0 deletions
232
apps/web/components/v2/settings/EnableTwoFactorModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
import React, { SyntheticEvent, useState } from "react"; | ||
|
||
import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||
import Button from "@calcom/ui/v2/core/Button"; | ||
import { Dialog, DialogContent } from "@calcom/ui/v2/core/Dialog"; | ||
|
||
import { ErrorCode } from "@lib/auth"; | ||
|
||
import TwoFactorAuthAPI from "./TwoFactorAuthAPI"; | ||
|
||
interface EnableTwoFactorModalProps { | ||
open: boolean; | ||
onOpenChange: () => void; | ||
|
||
/** | ||
* Called when the user closes the modal without disabling two-factor auth | ||
*/ | ||
onCancel: () => void; | ||
|
||
/** | ||
* Called when the user enables two-factor auth | ||
*/ | ||
onEnable: () => void; | ||
} | ||
|
||
enum SetupStep { | ||
ConfirmPassword, | ||
DisplayQrCode, | ||
EnterTotpCode, | ||
} | ||
|
||
const WithStep = ({ | ||
step, | ||
current, | ||
children, | ||
}: { | ||
step: SetupStep; | ||
current: SetupStep; | ||
children: JSX.Element; | ||
}) => { | ||
return step === current ? children : null; | ||
}; | ||
|
||
const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: EnableTwoFactorModalProps) => { | ||
const { t } = useLocale(); | ||
const setupDescriptions = { | ||
[SetupStep.ConfirmPassword]: t("2fa_confirm_current_password"), | ||
[SetupStep.DisplayQrCode]: t("2fa_scan_image_or_use_code"), | ||
[SetupStep.EnterTotpCode]: t("2fa_enter_six_digit_code"), | ||
}; | ||
const [step, setStep] = useState(SetupStep.ConfirmPassword); | ||
const [password, setPassword] = useState(""); | ||
const [totpCode, setTotpCode] = useState(""); | ||
const [dataUri, setDataUri] = useState(""); | ||
const [secret, setSecret] = useState(""); | ||
const [isSubmitting, setIsSubmitting] = useState(false); | ||
const [errorMessage, setErrorMessage] = useState<string | null>(null); | ||
|
||
async function handleSetup(e: SyntheticEvent) { | ||
e.preventDefault(); | ||
|
||
if (isSubmitting) { | ||
return; | ||
} | ||
|
||
setIsSubmitting(true); | ||
setErrorMessage(null); | ||
|
||
try { | ||
const response = await TwoFactorAuthAPI.setup(password); | ||
const body = await response.json(); | ||
|
||
if (response.status === 200) { | ||
setDataUri(body.dataUri); | ||
setSecret(body.secret); | ||
setStep(SetupStep.DisplayQrCode); | ||
return; | ||
} | ||
|
||
if (body.error === ErrorCode.IncorrectPassword) { | ||
setErrorMessage(t("incorrect_password")); | ||
} else { | ||
setErrorMessage(t("something_went_wrong")); | ||
} | ||
} catch (e) { | ||
setErrorMessage(t("something_went_wrong")); | ||
console.error(t("error_enabling_2fa"), e); | ||
} finally { | ||
setIsSubmitting(false); | ||
} | ||
} | ||
|
||
async function handleEnable(e: SyntheticEvent) { | ||
e.preventDefault(); | ||
|
||
if (isSubmitting || totpCode.length !== 6) { | ||
return; | ||
} | ||
|
||
setIsSubmitting(true); | ||
setErrorMessage(null); | ||
|
||
try { | ||
const response = await TwoFactorAuthAPI.enable(totpCode); | ||
const body = await response.json(); | ||
|
||
if (response.status === 200) { | ||
onEnable(); | ||
return; | ||
} | ||
|
||
if (body.error === ErrorCode.IncorrectTwoFactorCode) { | ||
setErrorMessage(`${t("code_is_incorrect")} ${t("please_try_again")}`); | ||
} else { | ||
setErrorMessage(t("something_went_wrong")); | ||
} | ||
} catch (e) { | ||
setErrorMessage(t("something_went_wrong")); | ||
console.error(t("error_enabling_2fa"), e); | ||
} finally { | ||
setIsSubmitting(false); | ||
} | ||
} | ||
|
||
return ( | ||
<Dialog open={open} onOpenChange={onOpenChange}> | ||
<DialogContent | ||
title={t("enable_2fa")} | ||
description={setupDescriptions[step]} | ||
type="creation" | ||
useOwnActionButtons | ||
// Icon={Icon.FiAlertTriangle}> | ||
> | ||
{/* <TwoFactorModalHeader title={t("enable_2fa")} description={setupDescriptions[step]} /> */} | ||
|
||
<WithStep step={SetupStep.ConfirmPassword} current={step}> | ||
<form onSubmit={handleSetup}> | ||
<div className="mb-4"> | ||
<label htmlFor="password" className="mt-4 block text-sm font-medium text-gray-700"> | ||
{t("password")} | ||
</label> | ||
<div className="mt-1"> | ||
<input | ||
type="password" | ||
name="password" | ||
id="password" | ||
required | ||
value={password} | ||
onInput={(e) => setPassword(e.currentTarget.value)} | ||
className="block w-full rounded-sm border-gray-300 text-sm" | ||
/> | ||
</div> | ||
|
||
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>} | ||
</div> | ||
</form> | ||
</WithStep> | ||
<WithStep step={SetupStep.DisplayQrCode} current={step}> | ||
<> | ||
<div className="flex justify-center"> | ||
{ | ||
// eslint-disable-next-line @next/next/no-img-element | ||
<img src={dataUri} alt="" /> | ||
} | ||
</div> | ||
<p className="text-center font-mono text-xs">{secret}</p> | ||
</> | ||
</WithStep> | ||
<WithStep step={SetupStep.EnterTotpCode} current={step}> | ||
<form onSubmit={handleEnable}> | ||
<div className="mb-4"> | ||
<label htmlFor="code" className="mt-4 block text-sm font-medium text-gray-700"> | ||
{t("code")} | ||
</label> | ||
<div className="mt-1"> | ||
<input | ||
type="text" | ||
name="code" | ||
id="code" | ||
required | ||
value={totpCode} | ||
maxLength={6} | ||
minLength={6} | ||
inputMode="numeric" | ||
onInput={(e) => setTotpCode(e.currentTarget.value)} | ||
className="block w-full rounded-sm border-gray-300 text-sm" | ||
autoComplete="one-time-code" | ||
/> | ||
</div> | ||
|
||
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>} | ||
</div> | ||
</form> | ||
</WithStep> | ||
|
||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse"> | ||
<WithStep step={SetupStep.ConfirmPassword} current={step}> | ||
<Button | ||
type="submit" | ||
className="ltr:ml-2 rtl:mr-2" | ||
onClick={handleSetup} | ||
disabled={password.length === 0 || isSubmitting}> | ||
{t("continue")} | ||
</Button> | ||
</WithStep> | ||
<WithStep step={SetupStep.DisplayQrCode} current={step}> | ||
<Button | ||
type="submit" | ||
className="ltr:ml-2 rtl:mr-2" | ||
onClick={() => setStep(SetupStep.EnterTotpCode)}> | ||
{t("continue")} | ||
</Button> | ||
</WithStep> | ||
<WithStep step={SetupStep.EnterTotpCode} current={step}> | ||
<Button | ||
type="submit" | ||
className="ltr:ml-2 rtl:mr-2" | ||
onClick={handleEnable} | ||
disabled={totpCode.length !== 6 || isSubmitting}> | ||
{t("enable")} | ||
</Button> | ||
</WithStep> | ||
<Button color="secondary" onClick={onCancel}> | ||
{t("cancel")} | ||
</Button> | ||
</div> | ||
</DialogContent> | ||
</Dialog> | ||
); | ||
}; | ||
|
||
export default EnableTwoFactorModal; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
const TwoFactorAuthAPI = { | ||
async setup(password: string) { | ||
return fetch("/api/auth/two-factor/totp/setup", { | ||
method: "POST", | ||
body: JSON.stringify({ password }), | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
}, | ||
|
||
async enable(code: string) { | ||
return fetch("/api/auth/two-factor/totp/enable", { | ||
method: "POST", | ||
body: JSON.stringify({ code }), | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
}, | ||
|
||
async disable(password: string) { | ||
return fetch("/api/auth/two-factor/totp/disable", { | ||
method: "POST", | ||
body: JSON.stringify({ password }), | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
}, | ||
}; | ||
|
||
export default TwoFactorAuthAPI; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.