diff --git a/backend/src/ee/services/pam-account/pam-account-service.ts b/backend/src/ee/services/pam-account/pam-account-service.ts index aafdfece30..ce715cb98e 100644 --- a/backend/src/ee/services/pam-account/pam-account-service.ts +++ b/backend/src/ee/services/pam-account/pam-account-service.ts @@ -661,12 +661,8 @@ export const pamAccountServiceFactory = ({ // Determine which MFA method to use // Priority: org-enforced > user-selected > email as fallback - const orgMfaMethod = org.enforceMfa - ? ((org.selectedMfaMethod as MfaMethod | null) ?? MfaMethod.EMAIL) - : undefined; - const userMfaMethod = actorUser.isMfaEnabled - ? ((actorUser.selectedMfaMethod as MfaMethod | null) ?? MfaMethod.EMAIL) - : undefined; + const orgMfaMethod = org.enforceMfa ? (org.selectedMfaMethod as MfaMethod | null) : undefined; + const userMfaMethod = actorUser.isMfaEnabled ? (actorUser.selectedMfaMethod as MfaMethod | null) : undefined; const mfaMethod = (orgMfaMethod ?? userMfaMethod ?? MfaMethod.EMAIL) as MfaMethod; // Create MFA session @@ -706,7 +702,7 @@ export const pamAccountServiceFactory = ({ // Verify the session is for the same account if (mfaSession.resourceId !== account.id) { throw new BadRequestError({ - message: "MFA session is for a different resource" + message: "MFA session is for a different account" }); } diff --git a/backend/src/server/routes/v1/user-router.ts b/backend/src/server/routes/v1/user-router.ts index 28344c0e22..112c7187a3 100644 --- a/backend/src/server/routes/v1/user-router.ts +++ b/backend/src/server/routes/v1/user-router.ts @@ -357,7 +357,7 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { attestationObject: z.string() }) .passthrough(), - clientExtensionResults: z.record(z.unknown()).optional(), + clientExtensionResults: z.record(z.unknown()).default({}), type: z.literal("public-key") }) .passthrough(), @@ -376,7 +376,7 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { handler: async (req) => { return server.services.webAuthn.verifyRegistrationResponse({ userId: req.permission.id, - registrationResponse: req.body.registrationResponse as unknown as RegistrationResponseJSON, + registrationResponse: req.body.registrationResponse as RegistrationResponseJSON, name: req.body.name }); } @@ -437,7 +437,7 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { handler: async (req) => { return server.services.webAuthn.verifyAuthenticationResponse({ userId: req.permission.id, - authenticationResponse: req.body.authenticationResponse as unknown as AuthenticationResponseJSON + authenticationResponse: req.body.authenticationResponse as AuthenticationResponseJSON }); } }); diff --git a/backend/src/services/mfa-session/mfa-session-service.ts b/backend/src/services/mfa-session/mfa-session-service.ts index 7b58426aee..fbe6f72ca4 100644 --- a/backend/src/services/mfa-session/mfa-session-service.ts +++ b/backend/src/services/mfa-session/mfa-session-service.ts @@ -68,6 +68,12 @@ export const mfaSessionServiceFactory = ({ }); } + if (mfaSession.mfaMethod !== mfaMethod) { + throw new BadRequestError({ + message: "MFA method does not match the session" + }); + } + // Verify the session belongs to the current user if (mfaSession.userId !== userId) { throw new ForbiddenRequestError({ diff --git a/backend/src/services/webauthn/webauthn-fns.ts b/backend/src/services/webauthn/webauthn-fns.ts deleted file mode 100644 index b563b79131..0000000000 --- a/backend/src/services/webauthn/webauthn-fns.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Verify that a credential ID belongs to a user - */ -export const verifyCredentialOwnership = (userId: string, credentialUserId: string): boolean => { - return userId === credentialUserId; -}; diff --git a/backend/src/services/webauthn/webauthn-service.ts b/backend/src/services/webauthn/webauthn-service.ts index 49dd99ad2c..8648d17862 100644 --- a/backend/src/services/webauthn/webauthn-service.ts +++ b/backend/src/services/webauthn/webauthn-service.ts @@ -16,7 +16,6 @@ import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service"; import { TokenType } from "../auth-token/auth-token-types"; import { TUserDALFactory } from "../user/user-dal"; import { TWebAuthnCredentialDALFactory } from "./webauthn-credential-dal"; -import { verifyCredentialOwnership } from "./webauthn-fns"; import { TDeleteWebAuthnCredentialDTO, TGenerateAuthenticationOptionsDTO, @@ -91,7 +90,6 @@ export const webAuthnServiceFactory = ({ transports: cred.transports as AuthenticatorTransportFuture[] })), authenticatorSelection: { - authenticatorAttachment: "platform", requireResidentKey: true, residentKey: "required", userVerification: "required" @@ -240,7 +238,7 @@ export const webAuthnServiceFactory = ({ } // Verify the credential belongs to the user - if (!verifyCredentialOwnership(userId, credential.userId)) { + if (userId !== credential.userId) { throw new ForbiddenRequestError({ message: "Credential does not belong to this user" }); @@ -333,7 +331,7 @@ export const webAuthnServiceFactory = ({ }); } - if (!verifyCredentialOwnership(userId, credential.userId)) { + if (userId !== credential.userId) { throw new ForbiddenRequestError({ message: "Credential does not belong to this user" }); @@ -358,7 +356,7 @@ export const webAuthnServiceFactory = ({ }); } - if (!verifyCredentialOwnership(userId, credential.userId)) { + if (userId !== credential.userId) { throw new ForbiddenRequestError({ message: "Credential does not belong to this user" }); diff --git a/frontend/src/hooks/api/mfaSession/index.tsx b/frontend/src/hooks/api/mfaSession/index.tsx index 4980a7e5af..14bc2e344a 100644 --- a/frontend/src/hooks/api/mfaSession/index.tsx +++ b/frontend/src/hooks/api/mfaSession/index.tsx @@ -1 +1,7 @@ -export { MfaSessionStatus, useMfaSessionStatus, useVerifyMfaSession } from "./queries"; +export { useMfaSessionStatus, useVerifyMfaSession } from "./queries"; +export type { + TMfaSessionStatusResponse, + TVerifyMfaSessionRequest, + TVerifyMfaSessionResponse +} from "./types"; +export { MfaSessionStatus } from "./types"; diff --git a/frontend/src/hooks/api/mfaSession/queries.tsx b/frontend/src/hooks/api/mfaSession/queries.tsx index b074b545b8..1578cc6c22 100644 --- a/frontend/src/hooks/api/mfaSession/queries.tsx +++ b/frontend/src/hooks/api/mfaSession/queries.tsx @@ -2,28 +2,12 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { MfaMethod } from "../auth/types"; - -export enum MfaSessionStatus { - PENDING = "PENDING", - ACTIVE = "ACTIVE" -} - -export type TMfaSessionStatusResponse = { - status: MfaSessionStatus; - mfaMethod: MfaMethod; -}; - -export type TVerifyMfaSessionRequest = { - mfaSessionId: string; - mfaToken: string; - mfaMethod: MfaMethod; -}; - -export type TVerifyMfaSessionResponse = { - success: boolean; - message: string; -}; +import { + MfaSessionStatus, + TMfaSessionStatusResponse, + TVerifyMfaSessionRequest, + TVerifyMfaSessionResponse +} from "./types"; export const useMfaSessionStatus = (mfaSessionId: string, enabled = true) => { return useQuery({ diff --git a/frontend/src/hooks/api/mfaSession/types.ts b/frontend/src/hooks/api/mfaSession/types.ts new file mode 100644 index 0000000000..3802a2f41b --- /dev/null +++ b/frontend/src/hooks/api/mfaSession/types.ts @@ -0,0 +1,22 @@ +import { MfaMethod } from "../auth/types"; + +export enum MfaSessionStatus { + PENDING = "PENDING", + ACTIVE = "ACTIVE" +} + +export type TMfaSessionStatusResponse = { + status: MfaSessionStatus; + mfaMethod: MfaMethod; +}; + +export type TVerifyMfaSessionRequest = { + mfaSessionId: string; + mfaToken: string; + mfaMethod: MfaMethod; +}; + +export type TVerifyMfaSessionResponse = { + success: boolean; + message: string; +};