Skip to content

Commit

Permalink
feat: Add two-factor authentication support to login page
Browse files Browse the repository at this point in the history
This commit adds support for two-factor authentication on the login page. It includes the necessary code changes to display the two-factor authentication UI elements when the user has enabled it. The login form now includes a field for entering the two-factor authentication code. Additionally, the API endpoint for verifying the two-factor authentication code has been implemented. This feature enhances the security of the login process by adding an extra layer of authentication.

Files modified:
- Accounts/src/Pages/Login.tsx
- App/FeatureSet/Identity/API/Authentication.ts
- CommonServer/Utils/TwoFactorAuth.ts
  • Loading branch information
simlarsen committed Jul 28, 2024
1 parent a4d1ed7 commit ee3de66
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 16 deletions.
34 changes: 27 additions & 7 deletions Accounts/src/Pages/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,20 @@ const LoginPage: () => JSX.Element = () => {
src={OneUptimeLogo}
alt="OneUptime"
/>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
{!showTwoFactorAuth && <><h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Sign in to your account
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Join thousands of business that use OneUptime to help them stay online
all the time.
</p>
</p></>}

{showTwoFactorAuth && <><h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Two Factor Authentication
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Select two factor authentication method. You will be asked to enter a code from the selected method.
</p></>}
</div>

<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
Expand Down Expand Up @@ -136,10 +143,10 @@ const LoginPage: () => JSX.Element = () => {
value: User | JSONObject,
miscData: JSONObject | undefined,
) => {
if ((value as JSONObject)["twoFactorAuth"] === true) {
if ((miscData as JSONObject)["twoFactorAuth"] === true) {
const twoFactorAuthList: Array<UserTwoFactorAuth> =
UserTwoFactorAuth.fromJSONArray(
(value as JSONObject)["twoFactorAuthList"] as JSONArray,
(miscData as JSONObject)["twoFactorAuthList"] as JSONArray,
UserTwoFactorAuth,
);
setTwoFactorAuthList(twoFactorAuthList);
Expand Down Expand Up @@ -189,21 +196,25 @@ const LoginPage: () => JSX.Element = () => {
description: "Enter the code from your authenticator app",
required: true,
dataTestId: "code",
fieldType: FormFieldSchemaType.Text
},
]}
submitButtonText={"Login"}
maxPrimaryButtonWidth={true}
isLoading={isTwoFactorAuthLoading}
error={twofactorAuthError}
submitButtonText={"Submit"}
onSubmit={async (data: JSONObject) => {
setIsTwoFactorAuthLoading(true);

try {
const code: string = data["code"] as string;
const twoFactorAuthId: string = selectedTwoFactorAuth.id?.toString() as string;

const result: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.post(VERIFY_TWO_FACTOR_AUTH_API_URL, {
data: initialValues,
code: code,
twoFactorAuthId: twoFactorAuthId,
});

if (result instanceof HTTPErrorResponse) {
Expand Down Expand Up @@ -231,15 +242,24 @@ const LoginPage: () => JSX.Element = () => {
)}
</div>
<div className="mt-10 text-center">
<div className="text-muted mb-0 text-gray-500">
{!selectedTwoFactorAuth && <div className="text-muted mb-0 text-gray-500">
Don&apos;t have an account?{" "}
<Link
to={new Route("/accounts/register")}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Register.
</Link>
</div>
</div>}
{selectedTwoFactorAuth ? <div className="text-muted mb-0 text-gray-500">
<Link
onClick={() => {
setSelectedTwoFactorAuth(undefined);
}}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Select a different two factor authentication method
</Link></div> : <></>}
</div>
</div>
</div>
Expand Down
26 changes: 18 additions & 8 deletions App/FeatureSet/Identity/API/Authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,14 @@ const login: LoginFunction = async (options: {
);
}

if (alreadySavedUser.password.toString() !== user.password!.toString()) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid login: Email or password does not match."),
);
}

if (alreadySavedUser.enableTwoFactorAuth && !verifyTwoFactorAuth) {
// If two factor auth is enabled then we will send the user to the two factor auth page.

Expand All @@ -646,12 +654,14 @@ const login: LoginFunction = async (options: {
);
}

return Response.sendJsonObjectResponse(req, res, {
twoFactorAuth: true,
twoFactorAuthList: twoFactorAuthList,
userId: alreadySavedUser.id?.toString(),
});
}
return Response.sendEntityResponse(
req, res, user, User, {
miscData: {
twoFactorAuthList: UserTwoFactorAuth.toJSONArray(twoFactorAuthList, UserTwoFactorAuth),
twoFactorAuth: true
},
});
}

if (verifyTwoFactorAuth) {
// code from req
Expand All @@ -661,7 +671,7 @@ const login: LoginFunction = async (options: {
const twoFactorAuth: UserTwoFactorAuth | null =
await UserTwoFactorAuthService.findOneBy({
query: {
id: new ObjectID(twoFactorAuthId),
_id: twoFactorAuthId,
userId: alreadySavedUser.id!,
isVerified: true,
},
Expand Down Expand Up @@ -691,7 +701,7 @@ const login: LoginFunction = async (options: {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid Two Factor authentication code."),
new BadDataException("Invalid code."),
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion CommonServer/Utils/TwoFactorAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default class TwoFactorAuth {
window: 3,
});

return isVerified || true;
return isVerified;
}

/**
Expand Down

0 comments on commit ee3de66

Please sign in to comment.