Skip to content

Commit

Permalink
feat: Ignore flagship certification failure to allow 2FA certification
Browse files Browse the repository at this point in the history
Flagship certification heavily depends on the Google and Apple official
stores API

Not every devices are compatible with those API like for iOS old
devices or F-Droid based devices

We don't want to block those devices' users so we allow them to certify
the app using alternative 2FA verification

The 2FA verification is handled by the cozy-stack so we only need to
continue the OAuth flow instead of throwing an error

This requires cozy/cozy-stack#3287
  • Loading branch information
Ldoppea committed Feb 23, 2022
1 parent ba1221f commit 3ee7c2f
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 6 deletions.
@@ -1,4 +1,5 @@
import CozyClient from '../CozyClient'
import logger from '../logger'

import { getAppAttestationFromStore } from './store-attestation'

Expand Down Expand Up @@ -82,12 +83,19 @@ export const certifyFlagship = async (certificationConfig, client) => {
)
}

const stackChallengeNonce = await getStackChallenge(client)
try {
const stackChallengeNonce = await getStackChallenge(client)

const appAttestation = await getAppAttestationFromStore(
stackChallengeNonce,
certificationConfig
)
const appAttestation = await getAppAttestationFromStore(
stackChallengeNonce,
certificationConfig
)

await giveAppAttestationToStack(appAttestation, stackChallengeNonce, client)
await giveAppAttestationToStack(appAttestation, stackChallengeNonce, client)
} catch (e) {
logger.warn(
'[FLAGSHIP_CERTIFICATION] Certification failed but the cozy-stack will continue with 2FA certification'
)
logger.warn(e.message)
}
}
Expand Up @@ -2,11 +2,16 @@ import { certifyFlagship } from './flagship-certification'
import { createMockClient } from '../mock'

import { getAppAttestationFromStore } from './store-attestation'
import logger from '../logger'

jest.mock('./store-attestation', () => ({
getAppAttestationFromStore: jest.fn()
}))

jest.mock('../logger', () => ({
warn: jest.fn()
}))

const getClientMock = () => {
const client = createMockClient({
clientOptions: {
Expand Down Expand Up @@ -112,4 +117,99 @@ describe('certifyFlagship', () => {
expect(client.stackClient.fetchJSON).not.toHaveBeenCalled()
expect(getAppAttestationFromStore).not.toHaveBeenCalled()
})

it('should ask challenge to cozy-stack and then handle challenge query failure', async () => {
const client = getClientMock()

const certificationConfig = mockCorrectCertificationConfig()

// Mock errored stack challenge request
client.stackClient.fetchJSON.mockImplementation(() => {
throw Error('SOME_STACK_CHALLENGE_ERROR')
})

mockCorrectStoreApiRequest()

await certifyFlagship(certificationConfig, client)

expect(client.stackClient.fetchJSON).toHaveBeenCalledWith(
...getFetchJsonPostParams(`/auth/clients/SOME_CLIENT_ID/challenge`)
)

expect(logger.warn).toHaveBeenCalledWith(
'[FLAGSHIP_CERTIFICATION] Certification failed but the cozy-stack will continue with 2FA certification'
)
expect(logger.warn).toHaveBeenCalledWith(
'[FLAGSHIP_CERTIFICATION] Something went wrong while requesting a challenge from CozyStack:\nSOME_STACK_CHALLENGE_ERROR'
)
})

it('should ask challenge to cozy-stack, call the store API and then handle store API failure', async () => {
const client = getClientMock()

const certificationConfig = mockCorrectCertificationConfig()

mockCorrectChallengeRequest(client)

// Mock errored store API
getAppAttestationFromStore.mockImplementationOnce(() => {
throw Error('SOME_STORE_API_ERROR')
})

await certifyFlagship(certificationConfig, client)

expect(client.stackClient.fetchJSON).toHaveBeenCalledWith(
...getFetchJsonPostParams(`/auth/clients/SOME_CLIENT_ID/challenge`)
)

expect(getAppAttestationFromStore).toHaveBeenCalledWith('SOME_NONCE', {
androidSafetyNetApiKey: 'SOME_ANDROID_SAFETY_NET_API_KEY'
})

expect(logger.warn).toHaveBeenCalledWith(
'[FLAGSHIP_CERTIFICATION] Certification failed but the cozy-stack will continue with 2FA certification'
)
expect(logger.warn).toHaveBeenCalledWith('SOME_STORE_API_ERROR')
})

it('should ask challenge to cozy-stack, call the store API, send the result to the stack and then handle cozy-stack certification failure', async () => {
const client = getClientMock()

const certificationConfig = mockCorrectCertificationConfig()

mockCorrectChallengeRequest(client)

// Mock errored stack certification request
client.stackClient.fetchJSON.mockImplementationOnce(() => {
throw Error('SOME_STACK_CERTIFICATION_ERROR')
})

mockCorrectStoreApiRequest()

await certifyFlagship(certificationConfig, client)

expect(client.stackClient.fetchJSON).toHaveBeenCalledWith(
...getFetchJsonPostParams(`/auth/clients/SOME_CLIENT_ID/challenge`)
)

expect(getAppAttestationFromStore).toHaveBeenCalledWith('SOME_NONCE', {
androidSafetyNetApiKey: 'SOME_ANDROID_SAFETY_NET_API_KEY'
})

expect(client.stackClient.fetchJSON).toHaveBeenCalledWith(
...getFetchJsonPostParams(`/auth/clients/SOME_CLIENT_ID/attestation`, {
platform: 'android',
attestation: 'SOME_STORE_ATTESTATION',
challenge: 'SOME_NONCE',
keyId: 'SOME_KEY_ID'
})
)

expect(logger.warn).toHaveBeenCalledWith(
'[FLAGSHIP_CERTIFICATION] Certification failed but the cozy-stack will continue with 2FA certification'
)
expect(logger.warn).toHaveBeenCalledWith(
'[FLAGSHIP_CERTIFICATION] Something went wrong while giving attestation to CozyStack:\nSOME_STACK_CERTIFICATION_ERROR'
)
})
})

0 comments on commit 3ee7c2f

Please sign in to comment.