From 52e7cea92d30381655f7a7cbdb184feee1fdad2d Mon Sep 17 00:00:00 2001 From: Juan P Lopez Date: Sat, 5 Oct 2024 02:35:55 -0500 Subject: [PATCH] fix(core): totp already enabled (#4607) --- bats/core/api/auth.bats | 5 +++++ core/api/src/app/authentication/totp.ts | 6 ++++++ core/api/src/domain/kratos/errors.ts | 1 + core/api/src/graphql/error-map.ts | 4 ++++ core/api/src/graphql/error.ts | 12 ++++++++++++ 5 files changed, 28 insertions(+) diff --git a/bats/core/api/auth.bats b/bats/core/api/auth.bats index fd86a04bd7..87687f7f0b 100644 --- a/bats/core/api/auth.bats +++ b/bats/core/api/auth.bats @@ -191,6 +191,11 @@ generateTotpCode() { # Checking the response structure totpEnabled="$(graphql_output '.data.userTotpRegistrationValidate.me.totpEnabled')" [ "$totpEnabled" == "true" ] || exit 1 + + exec_graphql 'charlie' 'user-totp-registration-initiate' + error_message="$(graphql_output '.data.userTotpRegistrationInitiate.errors[0].message')" + expected_message="TOTP has already been enabled for this account. If you need to reset it, please contact support." + [[ "$error_message" == "$expected_message" ]] || exit 1 } @test "auth: log in with email with totp activated" { diff --git a/core/api/src/app/authentication/totp.ts b/core/api/src/app/authentication/totp.ts index bc409403d2..2c9b1aa435 100644 --- a/core/api/src/app/authentication/totp.ts +++ b/core/api/src/app/authentication/totp.ts @@ -6,9 +6,11 @@ import { AuthTokenUserIdMismatchError, IdentifierNotFoundError, } from "@/domain/authentication/errors" +import { TotpAlreadyExistsError } from "@/domain/kratos" import { AuthWithEmailPasswordlessService, AuthWithPhonePasswordlessService, + IdentityRepository, kratosElevatingSessionWithTotp, kratosInitiateTotp, kratosRemoveTotp, @@ -26,6 +28,10 @@ export const initiateTotpRegistration = async ({ }: { userId: UserId }): Promise => { + const identity = await IdentityRepository().getIdentity(userId) + if (identity instanceof Error) return identity + if (identity.totpEnabled) return new TotpAlreadyExistsError() + const authToken = await getAuthTokenFromUserId(userId) if (authToken instanceof Error) { return authToken diff --git a/core/api/src/domain/kratos/errors.ts b/core/api/src/domain/kratos/errors.ts index 89e85fc440..e54d129bfb 100644 --- a/core/api/src/domain/kratos/errors.ts +++ b/core/api/src/domain/kratos/errors.ts @@ -15,6 +15,7 @@ export class InvalidIdentitySessionKratosError extends KratosError {} export class SessionRefreshRequiredError extends KratosError {} export class EmailAlreadyExistsError extends KratosError {} +export class TotpAlreadyExistsError extends KratosError {} export class PhoneAccountAlreadyExistsError extends KratosError { level = ErrorLevel.Info diff --git a/core/api/src/graphql/error-map.ts b/core/api/src/graphql/error-map.ts index 4df8c0115f..f634091367 100644 --- a/core/api/src/graphql/error-map.ts +++ b/core/api/src/graphql/error-map.ts @@ -25,6 +25,7 @@ import { AccountAlreadyHasEmailError, PhoneAlreadyExistsError, EmailAlreadyExistsError, + TotpAlreadyExistsError, SessionRefreshRequiredError, CodeExpiredError, UnauthorizedIPForOnboardingError, @@ -491,6 +492,9 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => { case "EmailAlreadyExistsError": return new EmailAlreadyExistsError({ logger: baseLogger }) + case "TotpAlreadyExistsError": + return new TotpAlreadyExistsError({ logger: baseLogger }) + case "CodeExpiredKratosError": return new CodeExpiredError({ logger: baseLogger }) diff --git a/core/api/src/graphql/error.ts b/core/api/src/graphql/error.ts index dccde7ee52..5cbb39371e 100644 --- a/core/api/src/graphql/error.ts +++ b/core/api/src/graphql/error.ts @@ -362,6 +362,18 @@ export class EmailAlreadyExistsError extends CustomGraphQLError { } } +export class TotpAlreadyExistsError extends CustomGraphQLError { + constructor(errData: CustomGraphQLErrorData) { + super({ + message: + "TOTP has already been enabled for this account. If you need to reset it, please contact support.", + forwardToClient: true, + code: "TOTP_ACCOUNT_ALREADY_EXISTS_ERROR", + ...errData, + }) + } +} + export class CodeExpiredError extends CustomGraphQLError { constructor(errData: CustomGraphQLErrorData) { super({