Skip to content

Commit

Permalink
Merge pull request #1004 from nickgros/SWC-6872
Browse files Browse the repository at this point in the history
  • Loading branch information
nickgros authored Jun 12, 2024
2 parents 3f759e0 + 29cbf9e commit 9ba2c99
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 12 deletions.
2 changes: 1 addition & 1 deletion packages/synapse-react-client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "synapse-react-client",
"version": "3.2.21",
"version": "3.2.22",
"private": false,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
101 changes: 94 additions & 7 deletions packages/synapse-react-client/src/utils/hooks/useLogin.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ function renderUseLogin(
})
}

async function verify2FAResetStarted(
result: ReturnType<typeof renderUseLogin>['result'],
resetEndpoint: string,
) {
await waitFor(() => {
expect(mockReset2FA).toHaveBeenCalledWith({
userId: twoFactorAuthErrorResponse.userId,
twoFaToken: twoFactorAuthErrorResponse.twoFaToken,
twoFaResetEndpoint: resetEndpoint,
})
expect(mockSetAccessTokenCookie).not.toHaveBeenCalled()
expect(onSuccessfulLoginFn).not.toHaveBeenCalled()
expect(result.current.step).toBe('DISABLE_2FA_PROMPT')
})
}

describe('useLogin tests', () => {
beforeEach(() => {
jest.clearAllMocks()
Expand Down Expand Up @@ -403,21 +419,92 @@ describe('useLogin tests', () => {
expect(result.current.step).toBe('VERIFICATION_CODE')
})

// Change the step so the UI shows the correct prompt
act(() => {
result.current.onStepChange('DISABLE_2FA_PROMPT')
})
await waitFor(() => {
expect(result.current.step).toBe('DISABLE_2FA_PROMPT')
})

// Trigger sending the email
act(() => {
result.current.beginTwoFactorAuthReset(resetEndpoint)
})

await verify2FAResetStarted(result, resetEndpoint)
})

it('Allows the user to begin the 2fa reset process when a 2FA token is passed via props', async () => {
const resetEndpoint = 'https://test.synapse.org/reset-2fa'

mockReset2FA.mockResolvedValue()

const { result } = renderUseLogin(
onSuccessfulLoginFn,
twoFactorAuthErrorResponse,
)

await waitFor(() => {
expect(result.current.step).toBe('VERIFICATION_CODE')
})

// Change the step so the UI shows the correct prompt
act(() => {
result.current.onStepChange('DISABLE_2FA_PROMPT')
})
await waitFor(() => {
expect(mockReset2FA).toHaveBeenCalledWith({
userId: twoFactorAuthErrorResponse.userId,
twoFaToken: twoFactorAuthErrorResponse.twoFaToken,
twoFaResetEndpoint: resetEndpoint,
})
expect(mockSetAccessTokenCookie).not.toHaveBeenCalled()
expect(onSuccessfulLoginFn).not.toHaveBeenCalled()
expect(result.current.step).toBe('DISABLE_2FA_PROMPT')
})

// Trigger sending the email
act(() => {
result.current.beginTwoFactorAuthReset(resetEndpoint)
})

await verify2FAResetStarted(result, resetEndpoint)
})

test.each([
['Regular path', '/'],
['SWC-style path', '/LoginPlace:0'],
])(
'Allows resetting 2FA when token is provided via search params, %s',
async (name, path) => {
history.replaceState(
{},
'',
`${path}?` +
`twoFaToken=${twoFactorAuthErrorResponse.twoFaToken}` +
`&userId=${twoFactorAuthErrorResponse.userId}`,
)

const resetEndpoint = 'https://test.synapse.org/reset-2fa'
mockLogInWith2FA.mockResolvedValue(successfulLoginResponse)

const { result } = renderUseLogin(onSuccessfulLoginFn)

await waitFor(() => {
expect(result.current.step).toBe('VERIFICATION_CODE')
})

// Change the step so the UI shows the correct prompt
act(() => {
result.current.onStepChange('DISABLE_2FA_PROMPT')
})
await waitFor(() => {
expect(result.current.step).toBe('DISABLE_2FA_PROMPT')
})

// Trigger sending the email
act(() => {
result.current.beginTwoFactorAuthReset(resetEndpoint)
})

await verify2FAResetStarted(result, resetEndpoint)
},
)

it('Handles reset 2fa error where there is no token or userId', async () => {
const resetEndpoint = 'https://test.synapse.org/reset-2fa'
const { result } = renderUseLogin(onSuccessfulLoginFn)
Expand Down
14 changes: 10 additions & 4 deletions packages/synapse-react-client/src/utils/hooks/useLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ export type UseLoginReturn = {
loginIsPending: boolean
}

// When prompting the user for a 2FA code, allow the UI to show only these steps
const VALID_STEPS_DURING_2FA_PROMPT: Array<UseLoginReturn['step']> = [
'VERIFICATION_CODE',
'RECOVERY_CODE',
'LOGGED_IN',
'DISABLE_2FA_PROMPT',
]

/**
* Stateful hook that manages logging into Synapse
*/
Expand Down Expand Up @@ -94,9 +102,7 @@ export default function useLogin(opts: UseLoginOptions): UseLoginReturn {
concreteType:
'org.sagebionetworks.repo.model.auth.TwoFactorAuthErrorResponse',
})
if (
!['VERIFICATION_CODE', 'RECOVERY_CODE', 'LOGGED_IN'].includes(step)
) {
if (!VALID_STEPS_DURING_2FA_PROMPT.includes(step)) {
setStep('VERIFICATION_CODE')
}
}
Expand All @@ -113,7 +119,7 @@ export default function useLogin(opts: UseLoginOptions): UseLoginReturn {
useEffect(() => {
if (twoFaErrorResponse) {
setTwoFaErrorResponse(twoFaErrorResponse)
if (!['VERIFICATION_CODE', 'RECOVERY_CODE', 'LOGGED_IN'].includes(step)) {
if (!VALID_STEPS_DURING_2FA_PROMPT.includes(step)) {
setStep('VERIFICATION_CODE')
}
}
Expand Down

0 comments on commit 9ba2c99

Please sign in to comment.