Skip to content

Commit

Permalink
Fix tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
Joerger committed Dec 3, 2024
1 parent 14722b6 commit e53c97c
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 51 deletions.
4 changes: 2 additions & 2 deletions web/packages/teleport/src/Account/Account.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ test('adding an MFA device', async () => {
const user = userEvent.setup();
const ctx = createTeleportContext();
jest.spyOn(ctx.mfaService, 'fetchDevices').mockResolvedValue([testPasskey]);
jest.spyOn(auth, 'getChallenge').mockResolvedValue({
jest.spyOn(auth, 'getMfaChallenge').mockResolvedValue({
webauthnPublicKey: null,
totpChallenge: true,
ssoChallenge: null,
Expand Down Expand Up @@ -327,7 +327,7 @@ test('removing an MFA method', async () => {
const user = userEvent.setup();
const ctx = createTeleportContext();
jest.spyOn(ctx.mfaService, 'fetchDevices').mockResolvedValue([testMfaMethod]);
jest.spyOn(auth, 'getChallenge').mockResolvedValue({
jest.spyOn(auth, 'getMfaChallenge').mockResolvedValue({
webauthnPublicKey: null,
totpChallenge: false,
ssoChallenge: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ beforeEach(() => {
user = userEvent.setup();
onSuccess = jest.fn();

jest.spyOn(auth, 'getMfaChallenge').mockResolvedValueOnce(undefined);
jest
.spyOn(auth, 'getMfaChallengeResponse')
.mockResolvedValueOnce(dummyChallengeResponse);
Expand All @@ -107,6 +108,7 @@ describe('with passwordless reauthentication', () => {
scope: MfaChallengeScope.CHANGE_PASSWORD,
userVerificationRequirement: 'required',
});
expect(auth.getMfaChallengeResponse).toHaveBeenCalled();
}

it('changes password', async () => {
Expand All @@ -127,7 +129,7 @@ describe('with passwordless reauthentication', () => {
oldPassword: '',
newPassword: 'new-pass1234',
secondFactorToken: '',
credential: dummyChallengeResponse.webauthn_response,
webauthnResponse: dummyChallengeResponse.webauthn_response,
});
expect(onSuccess).toHaveBeenCalled();
});
Expand Down Expand Up @@ -194,7 +196,7 @@ describe('with WebAuthn MFA reauthentication', () => {
);
await user.click(reauthenticateStep.getByText('MFA Device'));
await user.click(reauthenticateStep.getByText('Next'));
expect(auth.getMfaChallengeResponse).toHaveBeenCalledWith({
expect(auth.getMfaChallenge).toHaveBeenCalledWith({
scope: MfaChallengeScope.CHANGE_PASSWORD,
userVerificationRequirement: 'discouraged',
});
Expand Down Expand Up @@ -222,7 +224,7 @@ describe('with WebAuthn MFA reauthentication', () => {
oldPassword: 'current-pass',
newPassword: 'new-pass1234',
secondFactorToken: '',
credential: dummyChallengeResponse.webauthn_response,
webauthnResponse: dummyChallengeResponse.webauthn_response,
});
expect(onSuccess).toHaveBeenCalled();
});
Expand Down Expand Up @@ -296,7 +298,7 @@ describe('with OTP MFA reauthentication', () => {
);
await user.click(reauthenticateStep.getByText('Authenticator App'));
await user.click(reauthenticateStep.getByText('Next'));
expect(auth.getMfaChallengeResponse).not.toHaveBeenCalled();
expect(auth.getMfaChallenge).not.toHaveBeenCalled();
}

it('changes password', async () => {
Expand Down Expand Up @@ -420,11 +422,11 @@ describe('without reauthentication', () => {
'new-pass1234'
);
await user.click(changePasswordStep.getByText('Save Changes'));
expect(auth.getMfaChallengeResponse).not.toHaveBeenCalled();
expect(auth.getMfaChallenge).not.toHaveBeenCalled();
expect(auth.changePassword).toHaveBeenCalledWith({
oldPassword: 'current-pass',
newPassword: 'new-pass1234',
credential: undefined,
webauthnResponse: undefined,
secondFactorToken: '',
});
expect(onSuccess).toHaveBeenCalled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import Box from 'design/Box';

import { ChangePasswordReq } from 'teleport/services/auth';
import auth, { MfaChallengeScope } from 'teleport/services/auth/auth';
import { MfaDevice } from 'teleport/services/mfa';
import { MfaDevice, WebauthnAssertionResponse } from 'teleport/services/mfa';

export interface ChangePasswordWizardProps {
/** MFA type setting, as configured in the cluster's configuration. */
Expand Down Expand Up @@ -66,7 +66,8 @@ export function ChangePasswordWizard({
const [reauthMethod, setReauthMethod] = useState<ReauthenticationMethod>(
reauthOptions[0]?.value
);
const [credential, setCredential] = useState<Credential | undefined>();
const [webauthnResponse, setWebauthnResponse] =
useState<WebauthnAssertionResponse>();
const reauthRequired = reauthOptions.length > 0;

return (
Expand All @@ -84,9 +85,9 @@ export function ChangePasswordWizard({
// Step properties
reauthOptions={reauthOptions}
reauthMethod={reauthMethod}
credential={credential}
onReauthMethodChange={setReauthMethod}
onAuthenticated={setCredential}
webauthnResponse={webauthnResponse}
onWebauthnResponse={setWebauthnResponse}
onClose={onClose}
onSuccess={onSuccess}
/>
Expand Down Expand Up @@ -154,7 +155,7 @@ interface ReauthenticateStepProps {
reauthOptions: ReauthenticationOption[];
reauthMethod: ReauthenticationMethod;
onReauthMethodChange(method: ReauthenticationMethod): void;
onAuthenticated(res: Credential): void;
onWebauthnResponse(res: WebauthnAssertionResponse): void;
onClose(): void;
}

Expand All @@ -166,7 +167,7 @@ export function ReauthenticateStep({
reauthOptions,
reauthMethod,
onReauthMethodChange,
onAuthenticated,
onWebauthnResponse,
onClose,
}: ChangePasswordWizardStepProps) {
const [reauthenticateAttempt, reauthenticate] = useAsync(
Expand All @@ -181,7 +182,7 @@ export function ReauthenticateStep({
const response = await auth.getMfaChallengeResponse(challenge);

// TODO(Joerger): handle non-webauthn response.
onAuthenticated(response.webauthn_response);
onWebauthnResponse(response.webauthn_response);
}
next();
}
Expand Down Expand Up @@ -229,7 +230,7 @@ export function ReauthenticateStep({
}

interface ChangePasswordStepProps {
credential: Credential;
webauthnResponse: WebauthnAssertionResponse;
reauthMethod: ReauthenticationMethod;
onClose(): void;
onSuccess(): void;
Expand All @@ -240,7 +241,7 @@ export function ChangePasswordStep({
prev,
stepIndex,
flowLength,
credential,
webauthnResponse,
reauthMethod,
onClose,
onSuccess,
Expand Down Expand Up @@ -279,7 +280,7 @@ export function ChangePasswordStep({
oldPassword,
newPassword,
secondFactorToken: authCode,
credential,
webauthnResponse,
});
}

Expand Down
15 changes: 7 additions & 8 deletions web/packages/teleport/src/lib/useMfa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@ import { useState, useEffect, useCallback } from 'react';

import { EventEmitterMfaSender } from 'teleport/lib/EventEmitterMfaSender';
import { TermEvent } from 'teleport/lib/term/enums';
import {
parseMfaChallengeJson as parseMfaChallenge,
makeWebauthnAssertionResponse,
} from 'teleport/services/mfa/makeMfa';
import { parseMfaChallengeJson as parseMfaChallenge } from 'teleport/services/mfa/makeMfa';
import {
MfaAuthenticateChallengeJson,
SSOChallenge,
} from 'teleport/services/mfa';
import auth from 'teleport/services/auth/auth';

export function useMfa(emitterSender: EventEmitterMfaSender): MfaState {
const [state, setState] = useState<{
Expand Down Expand Up @@ -86,16 +84,17 @@ export function useMfa(emitterSender: EventEmitterMfaSender): MfaState {
return;
}

navigator.credentials
.get({ publicKey: state.webauthnPublicKey })
auth
.getMfaChallengeResponse({
webauthnPublicKey: state.webauthnPublicKey,
})
.then(res => {
setState(prevState => ({
...prevState,
errorText: '',
webauthnPublicKey: null,
}));
const credential = makeWebauthnAssertionResponse(res);
emitterSender.sendWebAuthn(credential);
emitterSender.sendWebAuthn(res.webauthn_response);
})
.catch((err: Error) => {
setErrorText(err.message);
Expand Down
36 changes: 20 additions & 16 deletions web/packages/teleport/src/services/api/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { MfaChallengeResponse } from '../mfa';

import api, {
MFA_HEADER,
defaultRequestOptions,
Expand All @@ -26,18 +28,20 @@ import api, {
describe('api.fetch', () => {
const mockedFetch = jest.spyOn(global, 'fetch').mockResolvedValue({} as any); // we don't care about response

const webauthnResp = {
id: 'some-id',
type: 'some-type',
extensions: {
appid: false,
},
rawId: 'some-raw-id',
response: {
authenticatorData: 'authen-data',
clientDataJSON: 'client-data-json',
signature: 'signature',
userHandle: 'user-handle',
const mfaResp: MfaChallengeResponse = {
webauthn_response: {
id: 'some-id',
type: 'some-type',
extensions: {
appid: false,
},
rawId: 'some-raw-id',
response: {
authenticatorData: 'authen-data',
clientDataJSON: 'client-data-json',
signature: 'signature',
userHandle: 'user-handle',
},
},
};

Expand Down Expand Up @@ -88,7 +92,7 @@ describe('api.fetch', () => {
});

test('with webauthnResponse', async () => {
await api.fetch('/something', undefined, webauthnResp);
await api.fetch('/something', undefined, mfaResp);
expect(mockedFetch).toHaveBeenCalledTimes(1);

const firstCall = mockedFetch.mock.calls[0];
Expand All @@ -100,14 +104,14 @@ describe('api.fetch', () => {
...defaultRequestOptions.headers,
...getAuthHeaders(),
[MFA_HEADER]: JSON.stringify({
webauthnAssertionResponse: webauthnResp,
webauthnAssertionResponse: mfaResp.webauthn_response,
}),
},
});
});

test('with customOptions and webauthnResponse', async () => {
await api.fetch('/something', customOpts, webauthnResp);
await api.fetch('/something', customOpts, mfaResp);
expect(mockedFetch).toHaveBeenCalledTimes(1);

const firstCall = mockedFetch.mock.calls[0];
Expand All @@ -120,7 +124,7 @@ describe('api.fetch', () => {
...customOpts.headers,
...getAuthHeaders(),
[MFA_HEADER]: JSON.stringify({
webauthnAssertionResponse: webauthnResp,
webauthnAssertionResponse: mfaResp.webauthn_response,
}),
},
});
Expand Down
11 changes: 4 additions & 7 deletions web/packages/teleport/src/services/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,14 +212,13 @@ const auth = {
oldPassword,
newPassword,
secondFactorToken,
credential,
webauthnResponse,
}: ChangePasswordReq) {
const data = {
old_password: base64EncodeUnicode(oldPassword),
new_password: base64EncodeUnicode(newPassword),
second_factor_token: secondFactorToken,
webauthnAssertionResponse:
credential && makeWebauthnAssertionResponse(credential),
webauthnAssertionResponse: webauthnResponse,
};

return api.put(cfg.api.changeUserPasswordPath, data);
Expand Down Expand Up @@ -342,9 +341,7 @@ const auth = {
.then(res =>
api.post(cfg.api.createPrivilegeTokenPath, {
// TODO(Joerger): Handle non-webauthn challenges.
webauthnAssertionResponse: makeWebauthnAssertionResponse(
res.webauthn_response
),
webauthnAssertionResponse: res.webauthn_response,
})
);
},
Expand Down Expand Up @@ -390,7 +387,7 @@ const auth = {
return auth
.getMfaChallenge({ scope, allowReuse, isMfaRequiredRequest }, abortSignal)
.then(challenge => auth.getMfaChallengeResponse(challenge))
.then(res => makeWebauthnAssertionResponse(res.webauthn_response));
.then(res => res.webauthn_response);
},

getMfaChallengeResponseForAdminAction(allowReuse?: boolean) {
Expand Down
4 changes: 2 additions & 2 deletions web/packages/teleport/src/services/auth/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import { EventMeta } from 'teleport/services/userEvent';

import { DeviceUsage } from '../mfa';
import { DeviceUsage, WebauthnAssertionResponse } from '../mfa';

import { IsMfaRequiredRequest, MfaChallengeScope } from './auth';

Expand Down Expand Up @@ -74,7 +74,7 @@ export type ChangePasswordReq = {
oldPassword: string;
newPassword: string;
secondFactorToken: string;
credential?: Credential;
webauthnResponse?: WebauthnAssertionResponse;
};

export type CreateNewHardwareDeviceRequest = {
Expand Down

0 comments on commit e53c97c

Please sign in to comment.