From ee3de663b5bb2f25996a894249e0477b7ea95078 Mon Sep 17 00:00:00 2001 From: Simon Larsen Date: Sun, 28 Jul 2024 12:29:55 -0600 Subject: [PATCH] feat: Add two-factor authentication support to login page 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 --- Accounts/src/Pages/Login.tsx | 34 +++++++++++++++---- App/FeatureSet/Identity/API/Authentication.ts | 26 +++++++++----- CommonServer/Utils/TwoFactorAuth.ts | 2 +- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/Accounts/src/Pages/Login.tsx b/Accounts/src/Pages/Login.tsx index bd02c9e62d4..2a942547e64 100644 --- a/Accounts/src/Pages/Login.tsx +++ b/Accounts/src/Pages/Login.tsx @@ -79,13 +79,20 @@ const LoginPage: () => JSX.Element = () => { src={OneUptimeLogo} alt="OneUptime" /> -

+ {!showTwoFactorAuth && <>

Sign in to your account

Join thousands of business that use OneUptime to help them stay online all the time. -

+

} + + {showTwoFactorAuth && <>

+ Two Factor Authentication +

+

+ Select two factor authentication method. You will be asked to enter a code from the selected method. +

}
@@ -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.fromJSONArray( - (value as JSONObject)["twoFactorAuthList"] as JSONArray, + (miscData as JSONObject)["twoFactorAuthList"] as JSONArray, UserTwoFactorAuth, ); setTwoFactorAuthList(twoFactorAuthList); @@ -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 = await API.post(VERIFY_TWO_FACTOR_AUTH_API_URL, { data: initialValues, code: code, + twoFactorAuthId: twoFactorAuthId, }); if (result instanceof HTTPErrorResponse) { @@ -231,7 +242,7 @@ const LoginPage: () => JSX.Element = () => { )}
-
+ {!selectedTwoFactorAuth &&
Don't have an account?{" "} JSX.Element = () => { > Register. -
+
} + {selectedTwoFactorAuth ?
+ { + setSelectedTwoFactorAuth(undefined); + }} + className="text-indigo-500 hover:text-indigo-900 cursor-pointer" + > + Select a different two factor authentication method +
: <>}
diff --git a/App/FeatureSet/Identity/API/Authentication.ts b/App/FeatureSet/Identity/API/Authentication.ts index 6941bc9dac9..a58c49b527c 100644 --- a/App/FeatureSet/Identity/API/Authentication.ts +++ b/App/FeatureSet/Identity/API/Authentication.ts @@ -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. @@ -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 @@ -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, }, @@ -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."), ); } } diff --git a/CommonServer/Utils/TwoFactorAuth.ts b/CommonServer/Utils/TwoFactorAuth.ts index ed29d3cb42b..47460731ef7 100644 --- a/CommonServer/Utils/TwoFactorAuth.ts +++ b/CommonServer/Utils/TwoFactorAuth.ts @@ -28,7 +28,7 @@ export default class TwoFactorAuth { window: 3, }); - return isVerified || true; + return isVerified; } /**