diff --git a/core/api/src/app/authentication/totp.ts b/core/api/src/app/authentication/totp.ts index b2ced3ee87..13fb8e95f7 100644 --- a/core/api/src/app/authentication/totp.ts +++ b/core/api/src/app/authentication/totp.ts @@ -7,6 +7,7 @@ import { kratosRemoveTotp, getAuthTokenFromUserId, logoutSessionByAuthToken, + refreshToken, } from "@/services/kratos" import { UsersRepository } from "@/services/mongoose" @@ -90,14 +91,17 @@ const validateTotpRegistrationWithToken = async ({ totpRegistrationId: TotpRegistrationId userId: UserId }): Promise => { + const res = await refreshToken(authToken) + if (res instanceof Error) return res + const validation = await kratosValidateTotp({ authToken, totpCode, totpRegistrationId }) if (validation instanceof Error) return validation - const res = await validateKratosToken(authToken) - if (res instanceof Error) return res - if (res.kratosUserId !== userId) return new AuthTokenUserIdMismatchError() + const res2 = await validateKratosToken(authToken) + if (res2 instanceof Error) return res2 + if (res2.kratosUserId !== userId) return new AuthTokenUserIdMismatchError() - const me = await UsersRepository().findById(res.kratosUserId) + const me = await UsersRepository().findById(res2.kratosUserId) if (me instanceof Error) return me return me diff --git a/core/api/src/domain/authentication/errors.ts b/core/api/src/domain/authentication/errors.ts index 9694593689..92e15f3fd6 100644 --- a/core/api/src/domain/authentication/errors.ts +++ b/core/api/src/domain/authentication/errors.ts @@ -3,6 +3,7 @@ import { DomainError, ValidationError, ErrorLevel } from "@/domain/shared" export class AuthenticationError extends DomainError {} export class LikelyNoUserWithThisPhoneExistError extends AuthenticationError {} export class LikelyUserAlreadyExistError extends AuthenticationError {} +export class LikelyBadCoreError extends AuthenticationError {} export class AccountHasPositiveBalanceError extends AuthenticationError {} export class PhoneAlreadyExistsError extends AuthenticationError {} diff --git a/core/api/src/graphql/error-map.ts b/core/api/src/graphql/error-map.ts index 01543e3c40..f93b8205db 100644 --- a/core/api/src/graphql/error-map.ts +++ b/core/api/src/graphql/error-map.ts @@ -33,6 +33,7 @@ import { UnauthorizedIPError, UnauthorizedIPMetadataProxyError, UnauthorizedIPMetadataCountryError, + LikelyBadCoreError, } from "@/graphql/error" import { baseLogger } from "@/services/logger" @@ -467,10 +468,15 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => { case "UnauthorizedIPMetadataCountryError": return new UnauthorizedIPMetadataCountryError({ logger: baseLogger }) + case "InvalidPaginatedQueryArgsError": message = error.message return new ValidationInternalError({ message, logger: baseLogger }) + case "LikelyBadCoreError": + message = error.message + return new LikelyBadCoreError({ message, logger: baseLogger }) + // ---------- // Unhandled below here // ---------- @@ -731,7 +737,6 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => { error.message ? ": " + error.message : "" })` return new UnknownClientError({ message, logger: baseLogger }) - default: return assertUnreachable(errorName) } diff --git a/core/api/src/graphql/error.ts b/core/api/src/graphql/error.ts index e762c7326a..e133308bc0 100644 --- a/core/api/src/graphql/error.ts +++ b/core/api/src/graphql/error.ts @@ -66,6 +66,17 @@ export class SelfPaymentError extends CustomGraphQLError { } } +export class LikelyBadCoreError extends CustomGraphQLError { + constructor(errData: CustomGraphQLErrorData) { + super({ + ...errData, + code: "BAD_ERROR_CODE", + forwardToClient: true, + message: "An error occurred. It's likely that the code you entered is not correct", + }) + } +} + export class ValidationInternalError extends CustomGraphQLError { constructor(errData: CustomGraphQLErrorData) { super({ code: "INVALID_INPUT", forwardToClient: true, ...errData }) diff --git a/core/api/src/services/kratos/index.ts b/core/api/src/services/kratos/index.ts index ba656d3782..6b12b9aa19 100644 --- a/core/api/src/services/kratos/index.ts +++ b/core/api/src/services/kratos/index.ts @@ -2,6 +2,8 @@ import { AuthWithPhonePasswordlessService } from "./auth-phone-no-password" import { AuthenticationKratosError, UnknownKratosError } from "./errors" import { kratosAdmin, kratosPublic, toDomainSession } from "./private" +import { KRATOS_MASTER_USER_PASSWORD } from "@/config" + import { InvalidFlowId, InvalidTotpCode } from "@/domain/errors" export * from "./auth-phone-no-password" @@ -97,3 +99,34 @@ export const logoutSessionByAuthToken = async (authToken: AuthToken) => { const sessionId = sessionResponse.data.id as SessionId await authService.logoutToken({ sessionId }) } + +export const refreshToken = async (authToken: AuthToken): Promise => { + const method = "password" + const password = KRATOS_MASTER_USER_PASSWORD + + const session = await kratosPublic.toSession({ xSessionToken: authToken }) + const identifier = + session.data.identity?.traits?.phone || session.data.identity?.traits?.email + + if (!identifier) { + return new UnknownKratosError("No identifier found") + } + + try { + const flow = await kratosPublic.createNativeLoginFlow({ + refresh: true, + xSessionToken: authToken, + }) + await kratosPublic.updateLoginFlow({ + flow: flow.data.id, + updateLoginFlowBody: { + identifier, + method, + password, + }, + xSessionToken: authToken, + }) + } catch (err) { + return new UnknownKratosError(err) + } +} diff --git a/core/api/src/services/kratos/totp.ts b/core/api/src/services/kratos/totp.ts index 2c7e77a4fe..ac378b83cd 100644 --- a/core/api/src/services/kratos/totp.ts +++ b/core/api/src/services/kratos/totp.ts @@ -1,3 +1,5 @@ +import { isAxiosError } from "axios" + import { UiNodeTextAttributes } from "@ory/client" import { @@ -7,8 +9,10 @@ import { } from "./errors" import { kratosAdmin, kratosPublic } from "./private" -import { KRATOS_MASTER_USER_PASSWORD } from "@/config" -import { LikelyNoUserWithThisPhoneExistError } from "@/domain/authentication/errors" +import { + LikelyBadCoreError, + LikelyNoUserWithThisPhoneExistError, +} from "@/domain/authentication/errors" export const kratosInitiateTotp = async (token: AuthToken) => { try { @@ -37,10 +41,6 @@ export const kratosValidateTotp = async ({ totpCode: string totpRegistrationId: string }) => { - // TODO: instead of refreshing, we could ask the user to re-authenticate - const res = await refreshToken(authToken) - if (res instanceof Error) return res - try { await kratosPublic.updateSettingsFlow({ flow, @@ -51,6 +51,13 @@ export const kratosValidateTotp = async ({ xSessionToken: authToken, }) } catch (err) { + if (isAxiosError(err)) { + if (err.response?.status === 400 && err.response.statusText === "Bad Request") { + return new LikelyBadCoreError() + } + } + console.log(err, "err123") + return new UnknownKratosError(err) } } @@ -94,37 +101,6 @@ export const kratosElevatingSessionWithTotp = async ({ return true } -const refreshToken = async (authToken: AuthToken): Promise => { - const method = "password" - const password = KRATOS_MASTER_USER_PASSWORD - - const session = await kratosPublic.toSession({ xSessionToken: authToken }) - const identifier = - session.data.identity?.traits?.phone || session.data.identity?.traits?.email - - if (!identifier) { - return new UnknownKratosError("No identifier found") - } - - try { - const flow = await kratosPublic.createNativeLoginFlow({ - refresh: true, - xSessionToken: authToken, - }) - await kratosPublic.updateLoginFlow({ - flow: flow.data.id, - updateLoginFlowBody: { - identifier, - method, - password, - }, - xSessionToken: authToken, - }) - } catch (err) { - return new UnknownKratosError(err) - } -} - export const kratosRemoveTotp = async (userId: UserId) => { try { const removeTotpResponse = await kratosAdmin.deleteIdentityCredentials({