Skip to content

Commit

Permalink
feat: bypass 2FA when logging with email
Browse files Browse the repository at this point in the history
Using a react ref to store the param,
as to not disturb the existing flow.
cozy-client has been updated to use the new loginFlagship method.
Keep in mind this has not been deployed into prod stack-wise
at the time of this commit. The feature will not work with only
the front-end implementation here.
  • Loading branch information
acezard committed Feb 21, 2024
1 parent 37a4f3c commit 57397e6
Show file tree
Hide file tree
Showing 10 changed files with 61 additions and 27 deletions.
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: Record<string, unknown>, 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

0 comments on commit 57397e6

Please sign in to comment.