diff --git a/accounts/src/pages/VerifyBackupCode.js b/accounts/src/pages/VerifyBackupCode.js index 1eae6f5808..2077485af8 100644 --- a/accounts/src/pages/VerifyBackupCode.js +++ b/accounts/src/pages/VerifyBackupCode.js @@ -107,6 +107,15 @@ export class VerifyBackupCode extends Component { +
+

+ Have a google app authenticator?{' '} + + Enter auth token + + . +

+
@@ -156,7 +216,10 @@ export class UserSetting extends Component { UserSetting.displayName = 'UserSetting'; const mapDispatchToProps = dispatch => { - return bindActionCreators({}, dispatch); + return bindActionCreators( + { updateTwoFactorAuthToken, openModal, setTwoFactorAuth }, + dispatch + ); }; function mapStateToProps(state) { @@ -169,6 +232,9 @@ function mapStateToProps(state) { UserSetting.propTypes = { user: PropTypes.object.isRequired, + updateTwoFactorAuthToken: PropTypes.func, + openModal: PropTypes.func, + setTwoFactorAuth: PropTypes.func, }; UserSetting.contextTypes = { diff --git a/admin-dashboard/src/constants/user.js b/admin-dashboard/src/constants/user.js index e7e71cc069..ed835a129e 100644 --- a/admin-dashboard/src/constants/user.js +++ b/admin-dashboard/src/constants/user.js @@ -61,3 +61,14 @@ export const SEARCH_USERS_REQUEST = 'SEARCH_USERS_REQUEST'; export const SEARCH_USERS_RESET = 'SEARCH_USERS_RESET'; export const SEARCH_USERS_SUCCESS = 'SEARCH_USERS_SUCCESS'; export const SEARCH_USERS_FAILURE = 'SEARCH_USERS_FAILURE'; + +//update user's two factor auth settings +export const UPDATE_TWO_FACTOR_AUTH_REQUEST = 'UPDATE_TWO_FACTOR_AUTH_REQUEST'; +export const UPDATE_TWO_FACTOR_AUTH_SUCCESS = 'UPDATE_TWO_FACTOR_AUTH_SUCCESS'; +export const UPDATE_TWO_FACTOR_AUTH_FAILURE = 'UPDATE_TWO_FACTOR_AUTH_FAILURE'; +export const SET_TWO_FACTOR_AUTH = 'SET_TWO_FACTOR_AUTH'; + +// Generate QR code +export const GENERATE_TWO_FACTOR_QR_REQUEST = 'GENERATE_TWO_FACTOR_QR_REQUEST'; +export const GENERATE_TWO_FACTOR_QR_SUCCESS = 'GENERATE_TWO_FACTOR_QR_SUCCESS'; +export const GENERATE_TWO_FACTOR_QR_FAILURE = 'GENERATE_TWO_FACTOR_QR_FAILURE'; diff --git a/admin-dashboard/src/reducers/user.js b/admin-dashboard/src/reducers/user.js index 7240ae4c4e..715c368330 100644 --- a/admin-dashboard/src/reducers/user.js +++ b/admin-dashboard/src/reducers/user.js @@ -39,6 +39,13 @@ import { SEARCH_USERS_RESET, SEARCH_USERS_SUCCESS, SEARCH_USERS_FAILURE, + UPDATE_TWO_FACTOR_AUTH_REQUEST, + UPDATE_TWO_FACTOR_AUTH_SUCCESS, + UPDATE_TWO_FACTOR_AUTH_FAILURE, + SET_TWO_FACTOR_AUTH, + GENERATE_TWO_FACTOR_QR_REQUEST, + GENERATE_TWO_FACTOR_QR_SUCCESS, + GENERATE_TWO_FACTOR_QR_FAILURE, } from '../constants/user'; const INITIAL_STATE = { @@ -98,6 +105,18 @@ const INITIAL_STATE = { error: null, success: false, }, + twoFactorAuthSetting: { + error: null, + requesting: false, + success: false, + data: {}, + }, + qrCode: { + error: null, + requesting: false, + success: false, + data: {}, + }, }; export default function user(state = INITIAL_STATE, action) { @@ -524,6 +543,80 @@ export default function user(state = INITIAL_STATE, action) { ...INITIAL_STATE, }); + //update user's two factor auth settings + case UPDATE_TWO_FACTOR_AUTH_REQUEST: + return Object.assign({}, state, { + twoFactorAuthSetting: { + requesting: true, + error: null, + success: false, + data: state.twoFactorAuthSetting.data, + }, + }); + + case UPDATE_TWO_FACTOR_AUTH_SUCCESS: + return Object.assign({}, state, { + user: { + ...INITIAL_STATE.user, + user: action.payload, + }, + twoFactorAuthSetting: { + requesting: false, + error: null, + success: false, + data: state.twoFactorAuthSetting.data, + }, + }); + + case UPDATE_TWO_FACTOR_AUTH_FAILURE: + return Object.assign({}, state, { + twoFactorAuthSetting: { + requesting: false, + error: action.payload, + success: false, + data: state.twoFactorAuthSetting.data, + }, + }); + + case SET_TWO_FACTOR_AUTH: + return Object.assign({}, state, { + user: { + ...state.user, + twoFactorAuthEnabled: action.payload, + }, + }); + + //generate user's QR code + case GENERATE_TWO_FACTOR_QR_REQUEST: + return Object.assign({}, state, { + qrCode: { + requesting: true, + error: null, + success: false, + data: state.qrCode.data, + }, + }); + + case GENERATE_TWO_FACTOR_QR_SUCCESS: + return Object.assign({}, state, { + qrCode: { + requesting: false, + error: null, + success: false, + data: action.payload, + }, + }); + + case GENERATE_TWO_FACTOR_QR_FAILURE: + return Object.assign({}, state, { + qrCode: { + requesting: false, + error: action.payload, + success: false, + data: state.qrCode.data, + }, + }); + default: return state; } diff --git a/admin-dashboard/src/test/Users.test.enterprise.js b/admin-dashboard/src/test/Users.test.enterprise.js index 8d30368efc..9e05d8404f 100644 --- a/admin-dashboard/src/test/Users.test.enterprise.js +++ b/admin-dashboard/src/test/Users.test.enterprise.js @@ -91,4 +91,82 @@ describe('Users Component (IS_SAAS_SERVICE=false)', () => { }, operationTimeOut ); + + test( + 'Should not activate google authenticator if the verification code field is empty', + async () => { + return await cluster.execute(null, async ({ page }) => { + // visit the dashboard + await page.goto(utils.ADMIN_DASHBOARD_URL, { + waitUntil: 'networkidle0', + }); + await page.waitForSelector( + '.bs-ObjectList-rows > a:nth-child(2)' + ); + await page.click('.bs-ObjectList-rows > a:nth-child(2)'); + await page.waitFor(5000); + + // toggle the google authenticator + await page.$eval('input[name=twoFactorAuthEnabled]', e => + e.click() + ); + + // click on the next button + await page.waitForSelector('#nextFormButton'); + await page.click('#nextFormButton'); + + // click the verification button + await page.waitForSelector('#enableTwoFactorAuthButton'); + await page.click('#enableTwoFactorAuthButton'); + + // verify there is an error message + let spanElement = await page.waitForSelector('.field-error'); + spanElement = await spanElement.getProperty('innerText'); + spanElement = await spanElement.jsonValue(); + spanElement.should.be.exactly('Auth token is required.'); + }); + }, + operationTimeOut + ); + + test( + 'Should not activate google authenticator if the verification code is invalid', + async () => { + return await cluster.execute(null, async ({ page }) => { + // visit the dashboard + await page.goto(utils.ADMIN_DASHBOARD_URL, { + waitUntil: 'networkidle0', + }); + await page.waitForSelector( + '.bs-ObjectList-rows > a:nth-child(2)' + ); + await page.click('.bs-ObjectList-rows > a:nth-child(2)'); + await page.waitFor(5000); + + // toggle the google authenticator + await page.$eval('input[name=twoFactorAuthEnabled]', e => + e.click() + ); + + // click on the next button + await page.waitForSelector('#nextFormButton'); + await page.click('#nextFormButton'); + + // enter a random verification code + await page.waitForSelector('input[name=token]'); + await page.type('input[name=token]', '021196'); + + // click the verification button + await page.waitForSelector('#enableTwoFactorAuthButton'); + await page.click('#enableTwoFactorAuthButton'); + + // verify there is an error message + let spanElement = await page.waitForSelector('#modal-message'); + spanElement = await spanElement.getProperty('innerText'); + spanElement = await spanElement.jsonValue(); + spanElement.should.be.exactly('Invalid token.'); + }); + }, + operationTimeOut + ); });