Skip to content

Commit

Permalink
feat(captcha): consolidate captcha token fetch into saga
Browse files Browse the repository at this point in the history
  • Loading branch information
schnogz committed May 6, 2022
1 parent 00be745 commit 3c22abe
Show file tree
Hide file tree
Showing 19 changed files with 105 additions and 259 deletions.
1 change: 0 additions & 1 deletion config/mocks/wallet-options-v4.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
"mergeAndUpgrade": false,
"nftExplorer": true,
"recurringBuys": true,
"refreshCaptchaWithSignupError": true,
"showTermsAndConditions": true,
"stxSelfCustodyEnableAll": true,
"stxSelfCustodyEnableAirdrop": true,
Expand Down
39 changes: 14 additions & 25 deletions packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { fetchBalances } from 'data/balance/sagas'
import goalSagas from 'data/goals/sagas'
import miscSagas from 'data/misc/sagas'
import profileSagas from 'data/modules/profile/sagas'
import { Analytics, ExchangeAuthOriginType } from 'data/types'
import { Analytics, CaptchaActionName, ExchangeAuthOriginType } from 'data/types'
import walletSagas from 'data/wallet/sagas'
import * as C from 'services/alerts'
import { isGuid } from 'services/forms'
Expand Down Expand Up @@ -49,7 +49,7 @@ export default ({ api, coreSagas, networks }) => {
coreSagas
})
const { saveGoals } = goalSagas({ api, coreSagas, networks })
const { startCoinWebsockets } = miscSagas()
const { generateCaptchaToken, startCoinWebsockets } = miscSagas()

const LOGIN_FORM = 'login'

Expand Down Expand Up @@ -77,11 +77,10 @@ export default ({ api, coreSagas, networks }) => {
}

const exchangeLogin = function* (action) {
const { captchaToken, code, password, username } = action.payload
const { code, password, username } = action.payload
const { platform, product, redirect, userType } = yield select(
selectors.auth.getProductAuthMetadata
)
const unificationFlowType = yield select(selectors.auth.getAccountUnificationFlowType)
const magicLinkData: AuthMagicLink = yield select(S.getMagicLinkData)
const exchangeAuthUrl = magicLinkData?.exchange_auth_url
const { exchange: exchangeDomain } = selectors.core.walletOptions
Expand Down Expand Up @@ -114,6 +113,7 @@ export default ({ api, coreSagas, networks }) => {
}
// start signin flow
try {
const captchaToken = yield call(generateCaptchaToken, CaptchaActionName.LOGIN)
const response = yield call(api.exchangeSignIn, captchaToken, code, password, username)
const { csrfToken, sessionExpirationTime, token: jwtToken } = response
yield put(actions.auth.setJwtToken(jwtToken))
Expand Down Expand Up @@ -743,8 +743,7 @@ export default ({ api, coreSagas, networks }) => {
}

// this is the function we run when submitting the login form
const continueLoginProcess = function* (action) {
const { captchaToken, initCaptcha } = action.payload
const continueLoginProcess = function* () {
const {
code,
email,
Expand Down Expand Up @@ -775,23 +774,11 @@ export default ({ api, coreSagas, networks }) => {
} else if (product === ProductAuthOptions.EXCHANGE) {
// trigger email for exchange form
yield put(actions.form.change(LOGIN_FORM, 'exchangeEmail', exchangeEmail))
yield put(
actions.auth.triggerWalletMagicLink({
captchaToken,
email: exchangeEmail
})
)
initCaptcha()
yield put(actions.auth.triggerWalletMagicLink({ email: exchangeEmail }))
} else {
// trigger email from wallet form
yield put(actions.form.change(LOGIN_FORM, 'email', email || guidOrEmail))
yield put(
actions.auth.triggerWalletMagicLink({
captchaToken,
email: email || guidOrEmail
})
)
initCaptcha()
yield put(actions.auth.triggerWalletMagicLink({ email: email || guidOrEmail }))
}
yield put(
actions.analytics.trackEvent({
Expand Down Expand Up @@ -831,7 +818,6 @@ export default ({ api, coreSagas, networks }) => {
// i.e. creating a new wallet and merging it to their exchange account
yield put(
actions.auth.exchangeLogin({
captchaToken,
code: exchangeTwoFA,
password: exchangePassword,
username: exchangeEmail
Expand All @@ -845,13 +831,15 @@ export default ({ api, coreSagas, networks }) => {

// triggers verification email for login
const triggerWalletMagicLink = function* (action) {
const formValues = yield select(selectors.form.getFormValues(LOGIN_FORM))
const { email } = action.payload
const { product } = yield select(selectors.auth.getProductAuthMetadata)
const { step } = formValues
const { captchaToken, email } = action.payload
yield put(startSubmit(LOGIN_FORM))

try {
let sessionToken
yield put(startSubmit(LOGIN_FORM))
const formValues = yield select(selectors.form.getFormValues(LOGIN_FORM))
const { step } = formValues

if (step === LoginSteps.CHECK_EMAIL && product === ProductAuthOptions.EXCHANGE) {
sessionToken = yield select(selectors.session.getSession, null, email)
if (!sessionToken) {
Expand All @@ -866,6 +854,7 @@ export default ({ api, coreSagas, networks }) => {
yield put(actions.session.saveWalletSession({ email, id: sessionToken }))
}
}
const captchaToken = yield call(generateCaptchaToken, CaptchaActionName.LOGIN)
yield call(api.triggerWalletMagicLink, sessionToken, email, captchaToken, product)
if (step === LoginSteps.CHECK_EMAIL) {
yield put(actions.alerts.displayInfo(C.VERIFY_EMAIL_SENT))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
AccountUnificationFlows,
AuthStateType,
AuthUserType,
ContinueLoginProcessPayloadType,
ExchangeLoginFailureType,
ExchangeLoginSuccessType,
ExchangeLoginType,
Expand Down Expand Up @@ -62,7 +61,7 @@ const authSlice = createSlice({
state.login = Remote.NotAsked
state.exchangeAuth.exchangeLogin = Remote.NotAsked
},
continueLoginProcess: (state, action: PayloadAction<ContinueLoginProcessPayloadType>) => {},
continueLoginProcess: () => {},
exchangeLogin: (state, action: PayloadAction<ExchangeLoginType>) => {},
exchangeLoginFailure: (state, action: PayloadAction<ExchangeLoginFailureType>) => {
state.exchangeAuth.exchangeLogin = Remote.Failure(action.payload)
Expand Down
7 changes: 0 additions & 7 deletions packages/blockchain-wallet-v4-frontend/src/data/auth/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export type LoginRoutinePayloadType = {
}

export type ExchangeLoginType = {
captchaToken: string
code?: string
password?: string
username: string
Expand Down Expand Up @@ -175,15 +174,9 @@ export type AuthStateType = {
}

export type MagicLinkRequestPayloadType = {
captchaToken: string
email: string
}

export type ContinueLoginProcessPayloadType = {
captchaToken?: string
initCaptcha: () => void
}

export type LoginPayloadType = {
code?: string
guid: string
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as AT from './actionTypes'

export const resetWallet2fa = (captchaToken, formValues) => ({
payload: { captchaToken, formValues },
export const resetWallet2fa = (formValues) => ({
payload: { formValues },
type: AT.RESET_WALLET_2FA
})
export const resetLoading = () => ({ type: AT.RESET_WALLET_2FA_LOADING })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ import { call, put } from 'redux-saga/effects'

import { APIType } from '@core/network/api'
import { actions } from 'data'
import miscSagas from 'data/misc/sagas'
import { CaptchaActionName } from 'data/types'

export default ({ api }: { api: APIType }) => {
const { generateCaptchaToken } = miscSagas()

const resetWallet2fa = function* (action) {
try {
const { captchaToken, formValues } = action.payload
const { formValues } = action.payload
const { email, guid, newEmail } = formValues
yield put(actions.components.resetWallet2fa.resetLoading())
const sessionToken = yield call(api.obtainSessionToken)
const captchaToken = yield call(generateCaptchaToken, CaptchaActionName.RESET_2FA)
yield call(api.reset2fa, guid, email, newEmail, captchaToken, sessionToken)
yield put(actions.components.resetWallet2fa.resetSuccess())
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default () => {
const miscSagas = sagas()

return function* authSaga() {
yield takeLatest(actions.generateCaptchaToken.type, miscSagas.generateCaptchaToken)
yield takeLatest(actions.pingManifestFile.type, miscSagas.pingManifestFile)
}
}
33 changes: 32 additions & 1 deletion packages/blockchain-wallet-v4-frontend/src/data/misc/sagas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { delay, put, select } from 'redux-saga/effects'
import { call, delay, put, select } from 'redux-saga/effects'

import { actions, selectors } from 'data'
import { ModalName } from 'data/modals/types'
Expand Down Expand Up @@ -78,7 +78,38 @@ export default () => {
}
}

const generateCaptchaToken = function* (actionName) {
let pollCount = 0

// wait up to 10 seconds for captcha library to load
while (true) {
pollCount += 1

if (pollCount >= 50) {
console.error('Captcha: window.grecaptcha not found')
break
}
if (window.grecaptcha && window.grecaptcha.enterprise) {
break
}

yield delay(200)
}

const getToken = () =>
window.grecaptcha.enterprise
.execute(window.CAPTCHA_KEY, { action: actionName })
.then((token) => token)
.catch((e) => {
console.error('Captcha: ', e)
})

const captchaToken = yield call(getToken)
return captchaToken
}

return {
generateCaptchaToken,
initAppLanguage,
logAppConsoleWarning,
pingManifestFile,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { createSlice } from '@reduxjs/toolkit'
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

import { CaptchaActionType } from './types'

const initialState = {
manifestFile: null
Expand All @@ -8,6 +10,7 @@ const miscSlice = createSlice({
initialState,
name: 'misc',
reducers: {
generateCaptchaToken: (state, action: PayloadAction<CaptchaActionType>) => {},
pingManifestFile: () => {},
setManifestFile: (state, action) => {
state.manifestFile = action.payload
Expand Down
7 changes: 7 additions & 0 deletions packages/blockchain-wallet-v4-frontend/src/data/misc/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export enum CaptchaActionName {
LOGIN = 'LOGIN',
RECOVER = 'RECOVER',
RESET_2FA = 'RESET_2FA',
SIGNUP = 'SIGNUP'
}
export type CaptchaActionType = keyof CaptchaActionName
25 changes: 10 additions & 15 deletions packages/blockchain-wallet-v4-frontend/src/data/signup/sagas.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { call, fork, put, select } from 'redux-saga/effects'
import { call, put, select } from 'redux-saga/effects'

import { errorHandler } from '@core/utils'
import { actions, selectors } from 'data'
import authSagas from 'data/auth/sagas'
import miscSagas from 'data/misc/sagas'
import profileSagas from 'data/modules/profile/sagas'
import {
Analytics,
AuthMagicLink,
CaptchaActionName,
ExchangeAuthOriginType,
PlatformTypes,
ProductAuthOptions
Expand All @@ -27,15 +29,14 @@ export default ({ api, coreSagas, networks }) => {
networks
})

const { generateCaptchaToken } = miscSagas()

const exchangeMobileAppSignup = function* ({
country = undefined,
email = undefined,
state = undefined
}) {
try {
const createExchangeUserFlag = (yield select(
selectors.core.walletOptions.getCreateExchangeUserOnSignupOrLogin
)).getOrElse(false)
yield call(coreSagas.settings.fetchSettings)
yield put(actions.auth.authenticate())
// root and wallet are necessary to auth into the exchange
Expand Down Expand Up @@ -64,18 +65,15 @@ export default ({ api, coreSagas, networks }) => {
}
}
const register = function* (action) {
const { country, email, initCaptcha, state } = action.payload
const { country, email, language, password, state } = action.payload
const isAccountReset: boolean = yield select(selectors.signup.getAccountReset)
// Want this behind a feature flag to monitor if this thing could be abused or not
const refreshToken = (yield select(
selectors.core.walletOptions.getRefreshCaptchaOnSignupError
)).getOrElse(false)
const { platform, product } = yield select(selectors.signup.getProductSignupMetadata)
try {
yield put(actions.signup.registerLoading())
yield put(actions.auth.loginLoading())
yield put(actions.signup.setRegisterEmail(email))
yield call(coreSagas.wallet.createWalletSaga, action.payload)
const captchaToken = yield call(generateCaptchaToken, CaptchaActionName.SIGNUP)
yield call(coreSagas.wallet.createWalletSaga, { captchaToken, email, language, password })
// We don't want to show the account success message if user is resetting their account
if (!isAccountReset) {
yield put(actions.alerts.displaySuccess(C.REGISTER_SUCCESS))
Expand Down Expand Up @@ -113,9 +111,6 @@ export default ({ api, coreSagas, networks }) => {
yield put(actions.auth.loginFailure(e))
yield put(actions.logs.logErrorMessage(logLocation, 'register', e))
yield put(actions.alerts.displayError(C.REGISTER_ERROR))
if (refreshToken) {
initCaptcha()
}
}
}

Expand Down Expand Up @@ -166,8 +161,9 @@ export default ({ api, coreSagas, networks }) => {

const restore = function* (action) {
try {
const { captchaToken, email, language, mnemonic, password } = action.payload
const { email, language, mnemonic, password } = action.payload
const kvCredentials = (yield select(selectors.signup.getMetadataRestore)).getOrElse({})
const captchaToken = yield call(generateCaptchaToken, CaptchaActionName.RECOVER)

yield put(actions.signup.restoreLoading())
yield put(actions.signup.setRegisterEmail(email))
Expand All @@ -180,7 +176,6 @@ export default ({ api, coreSagas, networks }) => {
mnemonic,
password
})

yield call(loginRoutineSaga, {
email,
firstLogin: true,
Expand Down
1 change: 1 addition & 0 deletions packages/blockchain-wallet-v4-frontend/src/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export * from './components/swap/types'
export * from './components/withdraw/types'
export * from './custodial/types'
export * from './goals/types'
export * from './misc/types'
export * from './modals/types'
export * from './modules/profile/types'
export * from './modules/transferEth/types'
Expand Down

0 comments on commit 3c22abe

Please sign in to comment.