Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: bypass 2FA when logging with email (VO-380) #1174

Merged
merged 1 commit into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@sentry/integrations": "7.81.1",
"@sentry/react-native": "5.16.0",
"base-64": "^1.0.0",
"cozy-client": "^45.12.0",
"cozy-client": "^45.13.0",
"cozy-clisk": "^0.34.0",
"cozy-device-helper": "^2.7.0",
"cozy-flags": "^3.2.0",
Expand Down
1 change: 1 addition & 0 deletions src/app/domain/deeplinks/models/OnboardingParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export interface OnboardingParams {
onboardUrl: string | null
onboardedRedirection: string | null
fqdn: string | null
emailVerifiedCode: string | null
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe('DeeplinksParserService', () => {
expect(result).toStrictEqual({
route: routes.authenticate,
params: {
emailVerifiedCode: null,
fqdn: 'SOME_FQDN'
},
onboardedRedirection: null
Expand Down
14 changes: 10 additions & 4 deletions src/app/domain/deeplinks/services/DeeplinksParserService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,19 @@ export const parseOnboardingURL = (
'onboarded_redirection'
)
const fqdn = onboardingUrl.searchParams.get('fqdn')
const emailVerifiedCode = onboardingUrl.searchParams.get(
'email_verified_code'
)

if (!onboardUrl && !fqdn) {
return undefined
}

return {
emailVerifiedCode,
fqdn,
onboardUrl,
onboardedRedirection,
fqdn
onboardedRedirection
}
} catch (error) {
const errorMessage = getErrorMessage(error)
Expand Down Expand Up @@ -183,7 +187,8 @@ export const parseOnboardLink = (
const {
onboardUrl,
onboardedRedirection: onboardedRedirectionParam,
fqdn
fqdn,
emailVerifiedCode
} = onboardingParams

if (onboardUrl) {
Expand All @@ -200,7 +205,8 @@ export const parseOnboardLink = (
return {
route: routes.authenticate,
params: {
fqdn
fqdn,
emailVerifiedCode
},
onboardedRedirection: onboardedRedirectionParam
}
Expand Down
1 change: 1 addition & 0 deletions src/hooks/useAppBootstrap.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ it('should set routes.stack and authenticate - when onboard_url not provided', a
initialRoute: {
route: routes.authenticate,
params: {
emailVerifiedCode: null,
fqdn: paramFqdn
}
},
Expand Down
8 changes: 6 additions & 2 deletions src/libs/clientHelpers/connectClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface ConnectClientParams {
loginData: LoginData
client: CozyClient
twoFactorAuthenticationData?: TwoFactorAuthenticationData
emailVerifiedCode?: string
}

/**
Expand All @@ -31,17 +32,20 @@ interface ConnectClientParams {
* @param {LoginData} param.loginData - login data containing hashed password and encryption keys
* @param {CozyClient} param.client - the CozyClient instance that will be authenticated through OAuth
* @param {TwoFactorAuthenticationData} param.twoFactorAuthenticationData - the 2FA data containing a token and a passcode
* @param {string} param.emailVerifiedCode - the emailVerifiedCode that should be used to log in the stack
* @returns {CozyClientCreationContext} The CozyClient with its corresponding state (i.e: connected, waiting for 2FA, invalid password etc)
*/
export const connectClient = async ({
loginData,
client,
twoFactorAuthenticationData = undefined
twoFactorAuthenticationData = undefined,
emailVerifiedCode = undefined
}: ConnectClientParams): Promise<CozyClientCreationContext> => {
const result = await loginFlagship({
client,
loginData,
twoFactorAuthenticationData
twoFactorAuthenticationData,
emailVerifiedCode
})

if (isInvalidPasswordResult(result)) {
Expand Down
8 changes: 6 additions & 2 deletions src/libs/clientHelpers/initClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface CallInitClientParams {
client?: CozyClient
instance: string
loginData: LoginData
emailVerifiedCode?: string
}

/**
Expand All @@ -26,18 +27,21 @@ interface CallInitClientParams {
* @param {LoginData} param.loginData - login data containing hashed password and encryption keys
* @param {string} param.instance - the Cozy instance used to create the client
* @param {CozyClient} [param.client] - an optional CozyClient instance that can be used for the authentication. If not provided a new CozyClient will be created
* @param {string} [param.emailVerifiedCode] - the emailVerifiedCode that should be used to log in the stack
* @returns {CozyClientCreationContext} The CozyClient for the Cozy instance with its corresponding state (i.e: connected, waiting for 2FA, invalid password etc)
*/
export const callInitClient = async ({
loginData,
instance,
client: clientParam
client: clientParam,
emailVerifiedCode
}: CallInitClientParams): Promise<CozyClientCreationContext> => {
const client = clientParam ?? (await createClient(instance))

return await connectClient({
loginData,
client
client,
emailVerifiedCode
})
}

Expand Down
9 changes: 6 additions & 3 deletions src/libs/clientHelpers/loginFlagship.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface LoginFlagshipParams {
client: CozyClient
loginData: LoginData
twoFactorAuthenticationData?: TwoFactorAuthenticationData
emailVerifiedCode?: string
}

/**
Expand All @@ -25,16 +26,17 @@ interface LoginFlagshipParams {
* @param {object} param.client - the CozyClient instance that will be authenticated through OAuth
* @param {object} param.loginData - login data containing hashed password
* @param {object} [param.twoFactorAuthenticationData] - the 2FA data containing a token and a passcode
* @param {string} [param.emailVerifiedCode] - the emailVerifiedCode that should be used to log in the stack
* @returns {LoginFlagshipResult} The query result with session_code, or 2FA token, or invalid password error
* @throws
*/
export const loginFlagship = async ({
client,
loginData,
twoFactorAuthenticationData = undefined
twoFactorAuthenticationData = undefined,
emailVerifiedCode
}: LoginFlagshipParams): Promise<LoginFlagshipResult> => {
const stackClient = client.getStackClient()

try {
const loginResult = await stackClient.loginFlagship({
passwordHash: loginData.passwordHash,
Expand All @@ -43,7 +45,8 @@ export const loginFlagship = async ({
: undefined,
twoFactorPasscode: twoFactorAuthenticationData
? twoFactorAuthenticationData.passcode
: undefined
: undefined,
...(emailVerifiedCode ? { emailVerifiedCode } : {})
})

return loginResult
Expand Down
26 changes: 20 additions & 6 deletions src/screens/login/LoginScreen.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { changeLanguage } from 'i18next'
import React, { useCallback, useEffect, useState } from 'react'
import { BackHandler, StyleSheet, View } from 'react-native'

Expand Down Expand Up @@ -36,7 +37,6 @@ import { routes } from '/constants/routes'
import { setStatusBarColorToMatchBackground } from '/screens/login/components/functions/clouderyBackgroundFetcher'
import { getInstanceFromFqdn } from '/screens/login/components/functions/getInstanceFromFqdn'
import { getInstanceDataFromFqdn } from '/screens/login/components/functions/getInstanceDataFromRequest'
import { changeLanguage } from 'i18next'

const log = Minilog('LoginScreen')

Expand Down Expand Up @@ -107,6 +107,12 @@ const LoginSteps = ({
const magicCode = consumeRouteParameter('magicCode', route, navigation)
const oauthCode = consumeRouteParameter('code', route, navigation)
const onboardUrl = consumeRouteParameter('onboardUrl', route, navigation)
const emailVerifiedCode = consumeRouteParameter(
'emailVerifiedCode',
route,
navigation
)

if (fqdn) {
const instanceData = getInstanceDataFromFqdn(fqdn)

Expand All @@ -126,7 +132,7 @@ const LoginSteps = ({
} else if (oauthCode) {
startOidcOAuth(instanceData.fqdn, oauthCode)
} else {
setInstanceData(instanceData)
setInstanceData(instanceData, { emailVerifiedCode })
}
} else if (onboardUrl && oauthCode) {
// when receiving fqdn from route parameter, we don't have access to partner context
Expand Down Expand Up @@ -158,7 +164,13 @@ const LoginSteps = ({
}

const setInstanceData = useCallback(
async ({ instance, fqdn }) => {
/**
* Sets the instance data.
* @param {{ instance: string, fqdn: string }} instanceData - The first parameter object with `instance` as a record of string keys to unknown values, and `fqdn` as a string.
* @param {{ emailVerifiedCode?: string }} [options] - The optional second parameter object with an optional `emailVerifiedCode` property.
* @returns {Promise<void>} A promise that resolves to void.
*/
async ({ instance, fqdn }, { emailVerifiedCode }) => {
if (await NetService.isOffline())
NetService.handleOffline(routes.authenticate)

Expand All @@ -183,7 +195,8 @@ const LoginSteps = ({
instance: instance,
name: name,
kdfIterations: kdfIterations,
client: client
client: client,
emailVerifiedCode
})
} catch (error) {
setError(error.message, error)
Expand Down Expand Up @@ -332,12 +345,13 @@ const LoginSteps = ({
NetService.handleOffline(routes.authenticate)

try {
const { loginData, instance, client } = state
const { loginData, instance, client, emailVerifiedCode } = state

const result = await callInitClient({
loginData,
instance,
client
client,
emailVerifiedCode
})

if (result.state === STATE_INVALID_PASSWORD) {
Expand Down
18 changes: 9 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7943,16 +7943,16 @@ cosmiconfig@^7.0.1:
path-type "^4.0.0"
yaml "^1.10.0"

cozy-client@^45.12.0:
version "45.12.0"
resolved "https://registry.yarnpkg.com/cozy-client/-/cozy-client-45.12.0.tgz#e550c86a5b5fc430fec71ebba43c7ba1cd58c0db"
integrity sha512-Vic8k6f6LKY1AJt0rMLe1AHX51kB3ROYsXnpj7JduUa3Tise1Y/9iCQfWmWMVdSicFtmEeZXqt0Z/ot3gt0W4A==
cozy-client@^45.13.0:
version "45.13.0"
resolved "https://registry.yarnpkg.com/cozy-client/-/cozy-client-45.13.0.tgz#4e9eda7ac30312ccc7de99c9525e62d61e0a08d8"
integrity sha512-bftRIKkjfdjGtWGguvhuuohWElUSN9q/x+u6ksCx6iiM7VWpPBIJEMqfj+tA+RkghJnN80Phdguk3HMkENxqEA==
dependencies:
"@cozy/minilog" "1.0.0"
"@types/jest" "^26.0.20"
"@types/lodash" "^4.14.170"
btoa "^1.2.1"
cozy-stack-client "^45.2.0"
cozy-stack-client "^45.13.0"
date-fns "2.29.3"
json-stable-stringify "^1.0.1"
lodash "^4.17.13"
Expand Down Expand Up @@ -8017,10 +8017,10 @@ cozy-minilog@3.3.1:
dependencies:
microee "0.0.6"

cozy-stack-client@^45.2.0:
version "45.2.0"
resolved "https://registry.yarnpkg.com/cozy-stack-client/-/cozy-stack-client-45.2.0.tgz#c0d20d6e11a57477752cd38f1ca8732c668f5d14"
integrity sha512-XpbD568C+gG2ZSkWcdlBQIfXb2kefpQpwjgMISZ0raHk6cA1c626pptfbadXzmNaj9IiDG/gTCztzbnxZ3xBUA==
cozy-stack-client@^45.13.0:
version "45.13.0"
resolved "https://registry.yarnpkg.com/cozy-stack-client/-/cozy-stack-client-45.13.0.tgz#394b21a153b752be99ac6c79d073159fd024a4f4"
integrity sha512-ruigDMH6XB1Pxa+e3doAMjXihgKSdNZpNPlEcpd7l8MfHazUaHVs/D8Kyop1n+VGM/4va2DU1c1nE7ainT7+jw==
dependencies:
detect-node "^2.0.4"
mime "^2.4.0"
Expand Down
Loading