mirror of
https://github.com/OneUptime/oneuptime
synced 2024-11-22 23:30:10 +00:00
Merge branch 'fix-sort-users-by-last-active' into 'master'
adds a link back to enter auth token form See merge request fyipe-project/app!826
This commit is contained in:
commit
8eedf4c67f
@ -107,6 +107,15 @@ export class VerifyBackupCode extends Component {
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div className="below-box">
|
||||
<p>
|
||||
Have a google app authenticator?{' '}
|
||||
<Link to="/accounts/user-auth/token">
|
||||
Enter auth token
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
<div id="footer_spacer" />
|
||||
<div id="bottom">
|
||||
<ul>
|
||||
|
15
admin-dashboard/package-lock.json
generated
15
admin-dashboard/package-lock.json
generated
@ -15501,6 +15501,21 @@
|
||||
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
||||
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
|
||||
},
|
||||
"qr.js": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz",
|
||||
"integrity": "sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8="
|
||||
},
|
||||
"qrcode.react": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-1.0.0.tgz",
|
||||
"integrity": "sha512-jBXleohRTwvGBe1ngV+62QvEZ/9IZqQivdwzo9pJM4LQMoCM2VnvNBnKdjvGnKyDZ/l0nCDgsPod19RzlPvm/Q==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"qr.js": "0.0.0"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
|
@ -22,6 +22,7 @@
|
||||
"prop-types": "^15.6.1",
|
||||
"puppeteer": "^2.1.1",
|
||||
"puppeteer-cluster": "^0.19.0",
|
||||
"qrcode.react": "^1.0.0",
|
||||
"react": "^16.5.2",
|
||||
"react-click-outside": "github:tj/react-click-outside",
|
||||
"react-dom": "^16.5.2",
|
||||
|
@ -548,3 +548,139 @@ export const searchUsers = (filter, skip, limit) => async dispatch => {
|
||||
dispatch(searchUsersError(errors(errorMsg)));
|
||||
}
|
||||
};
|
||||
|
||||
// Update user twoFactorAuthToken
|
||||
export function twoFactorAuthTokenRequest() {
|
||||
return {
|
||||
type: types.UPDATE_TWO_FACTOR_AUTH_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
export function twoFactorAuthTokenSuccess(payload) {
|
||||
return {
|
||||
type: types.UPDATE_TWO_FACTOR_AUTH_SUCCESS,
|
||||
payload: payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function twoFactorAuthTokenError(error) {
|
||||
return {
|
||||
type: types.UPDATE_TWO_FACTOR_AUTH_FAILURE,
|
||||
payload: error,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateTwoFactorAuthToken(data) {
|
||||
return function(dispatch) {
|
||||
const promise = putApi('user/profile', data);
|
||||
dispatch(twoFactorAuthTokenRequest());
|
||||
promise.then(
|
||||
function(response) {
|
||||
const payload = response.data;
|
||||
dispatch(twoFactorAuthTokenSuccess(payload));
|
||||
return payload;
|
||||
},
|
||||
function(error) {
|
||||
if (error && error.response && error.response.data)
|
||||
error = error.response.data;
|
||||
if (error && error.data) {
|
||||
error = error.data;
|
||||
}
|
||||
if (error && error.message) {
|
||||
error = error.message;
|
||||
} else {
|
||||
error = 'Network Error';
|
||||
}
|
||||
dispatch(twoFactorAuthTokenError(errors(error)));
|
||||
}
|
||||
);
|
||||
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
|
||||
export function setTwoFactorAuth(enabled) {
|
||||
return {
|
||||
type: types.SET_TWO_FACTOR_AUTH,
|
||||
payload: enabled,
|
||||
};
|
||||
}
|
||||
|
||||
export function verifyTwoFactorAuthToken(values) {
|
||||
return function(dispatch) {
|
||||
const promise = postApi('user/totp/verifyToken', values);
|
||||
dispatch(twoFactorAuthTokenRequest());
|
||||
promise.then(
|
||||
function(response) {
|
||||
const payload = response.data;
|
||||
dispatch(twoFactorAuthTokenSuccess(payload));
|
||||
return payload;
|
||||
},
|
||||
function(error) {
|
||||
if (error && error.response && error.response.data)
|
||||
error = error.response.data;
|
||||
if (error && error.data) {
|
||||
error = error.data;
|
||||
}
|
||||
if (error && error.message) {
|
||||
error = error.message;
|
||||
} else {
|
||||
error = 'Network Error';
|
||||
}
|
||||
dispatch(twoFactorAuthTokenError(errors(error)));
|
||||
}
|
||||
);
|
||||
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
|
||||
// Generate user's QR code
|
||||
export function generateTwoFactorQRCodeRequest() {
|
||||
return {
|
||||
type: types.GENERATE_TWO_FACTOR_QR_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
export function generateTwoFactorQRCodeSuccess(payload) {
|
||||
return {
|
||||
type: types.GENERATE_TWO_FACTOR_QR_SUCCESS,
|
||||
payload: payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function generateTwoFactorQRCodeError(error) {
|
||||
return {
|
||||
type: types.GENERATE_TWO_FACTOR_QR_FAILURE,
|
||||
payload: error,
|
||||
};
|
||||
}
|
||||
|
||||
export function generateTwoFactorQRCode(userId) {
|
||||
return function(dispatch) {
|
||||
const promise = postApi(`user/totp/token/${userId}`);
|
||||
dispatch(generateTwoFactorQRCodeRequest());
|
||||
promise.then(
|
||||
function(response) {
|
||||
const payload = response.data;
|
||||
dispatch(generateTwoFactorQRCodeSuccess(payload));
|
||||
return payload;
|
||||
},
|
||||
function(error) {
|
||||
if (error && error.response && error.response.data)
|
||||
error = error.response.data;
|
||||
if (error && error.data) {
|
||||
error = error.data;
|
||||
}
|
||||
if (error && error.message) {
|
||||
error = error.message;
|
||||
} else {
|
||||
error = 'Network Error';
|
||||
}
|
||||
dispatch(generateTwoFactorQRCodeError(errors(error)));
|
||||
}
|
||||
);
|
||||
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
|
337
admin-dashboard/src/components/user/TwoFactorAuth.js
Normal file
337
admin-dashboard/src/components/user/TwoFactorAuth.js
Normal file
@ -0,0 +1,337 @@
|
||||
import React, { Component } from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { closeModal } from '../../actions/modal';
|
||||
import ShouldRender from '../basic/ShouldRender';
|
||||
import { reduxForm, Field, SubmissionError } from 'redux-form';
|
||||
import { Spinner } from '../basic/Loader';
|
||||
import QRCode from 'qrcode.react';
|
||||
import { RenderField } from '../basic/RenderField';
|
||||
import { ListLoader } from '../basic/Loader.js';
|
||||
import {
|
||||
setTwoFactorAuth,
|
||||
verifyTwoFactorAuthToken,
|
||||
generateTwoFactorQRCode,
|
||||
twoFactorAuthTokenError,
|
||||
} from '../../actions/user';
|
||||
|
||||
function validate() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
class TwoFactorAuthModal extends Component {
|
||||
state = { next: false };
|
||||
|
||||
async componentDidMount() {
|
||||
const {
|
||||
user: { user },
|
||||
generateTwoFactorQRCode,
|
||||
} = this.props;
|
||||
generateTwoFactorQRCode(user._id);
|
||||
}
|
||||
|
||||
handleKeyBoard = e => {
|
||||
const { twoFactorAuthId, closeModal } = this.props;
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
return closeModal({
|
||||
id: twoFactorAuthId,
|
||||
});
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
nextHandler = event => {
|
||||
event.preventDefault();
|
||||
this.setState({ next: true });
|
||||
};
|
||||
|
||||
submitForm = values => {
|
||||
if (!values.token) {
|
||||
throw new SubmissionError({ token: 'Auth token is required.' });
|
||||
}
|
||||
|
||||
const { twoFactorAuthId, closeModal } = this.props;
|
||||
if (values.token) {
|
||||
const {
|
||||
setTwoFactorAuth,
|
||||
verifyTwoFactorAuthToken,
|
||||
user: { user },
|
||||
} = this.props;
|
||||
values.userId = user._id;
|
||||
verifyTwoFactorAuthToken(values).then(response => {
|
||||
setTwoFactorAuth(response.data.twoFactorAuthEnabled);
|
||||
return closeModal({
|
||||
id: twoFactorAuthId,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { handleSubmit, qrCode, twoFactorAuthSetting } = this.props;
|
||||
const { next } = this.state;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(this.submitForm)}>
|
||||
<div
|
||||
onKeyDown={this.handleKeyBoard}
|
||||
className="ModalLayer-wash Box-root Flex-flex Flex-alignItems--flexStart Flex-justifyContent--center"
|
||||
>
|
||||
<div
|
||||
className="ModalLayer-contents"
|
||||
tabIndex={-1}
|
||||
style={{ marginTop: 40 }}
|
||||
>
|
||||
<div className="bs-BIM">
|
||||
<div className="bs-Modal bs-Modal--medium">
|
||||
<div className="bs-Modal-header">
|
||||
<div className="bs-Modal-header-copy">
|
||||
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
|
||||
<span>
|
||||
Two Factor Authentication
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="bs-Modal-messages">
|
||||
<ShouldRender
|
||||
if={twoFactorAuthSetting.error}
|
||||
>
|
||||
<p
|
||||
className="bs-Modal-message"
|
||||
id="modal-message"
|
||||
>
|
||||
{twoFactorAuthSetting.error}
|
||||
</p>
|
||||
</ShouldRender>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bs-Fieldset-wrapper Box-root Margin-bottom--2">
|
||||
<div className="bs-u-paddingless">
|
||||
<div className="bs-Modal-block">
|
||||
<div>
|
||||
{next ? (
|
||||
<div>
|
||||
<div
|
||||
className="bs-Fieldset-wrapper Box-root"
|
||||
style={{
|
||||
width:
|
||||
'90%',
|
||||
margin:
|
||||
'1px 0 6px 2%',
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
Input a
|
||||
token from
|
||||
your mobile
|
||||
device to
|
||||
complete
|
||||
setup
|
||||
</p>
|
||||
</div>
|
||||
<div className="bs-Modal-body">
|
||||
<Field
|
||||
className="bs-TextInput"
|
||||
component={
|
||||
RenderField
|
||||
}
|
||||
name="token"
|
||||
id="token"
|
||||
placeholder="Verification token"
|
||||
disabled={
|
||||
twoFactorAuthSetting.requesting
|
||||
}
|
||||
style={{
|
||||
width:
|
||||
'90%',
|
||||
margin:
|
||||
'5px 0 10px 2%',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bs-Fieldset-wrapper Box-root">
|
||||
<div
|
||||
className="bs-Fieldset-wrapper Box-root"
|
||||
style={{
|
||||
marginBottom:
|
||||
'10px',
|
||||
marginTop:
|
||||
'-5px',
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
Download the
|
||||
Google
|
||||
Authenticator
|
||||
Mobile app
|
||||
on your
|
||||
mobile
|
||||
device
|
||||
<span>
|
||||
{' '}
|
||||
(
|
||||
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en">
|
||||
Android
|
||||
</a>
|
||||
,
|
||||
<a href="https://apps.apple.com/us/app/google-authenticator/id388497605">
|
||||
{' '}
|
||||
IOS
|
||||
</a>
|
||||
)
|
||||
</span>{' '}
|
||||
and then
|
||||
scan the QR
|
||||
code below
|
||||
to set up
|
||||
Two-factor
|
||||
Authentication
|
||||
with an
|
||||
Authenticator
|
||||
app.
|
||||
</p>
|
||||
</div>
|
||||
{qrCode.data
|
||||
.otpauth_url ? (
|
||||
<QRCode
|
||||
size={230}
|
||||
value={`${qrCode.data.otpauth_url}`}
|
||||
style={{
|
||||
display:
|
||||
'block',
|
||||
margin:
|
||||
'0 auto',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ListLoader />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bs-Modal-footer">
|
||||
<div className="bs-Modal-footer-actions">
|
||||
<button
|
||||
className={`bs-Button bs-DeprecatedButton ${twoFactorAuthSetting.requesting &&
|
||||
'bs-is-disabled'}`}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
this.props.twoFactorAuthTokenError(
|
||||
''
|
||||
);
|
||||
this.props.closeModal({
|
||||
id: this.props
|
||||
.twoFactorAuthId,
|
||||
});
|
||||
}}
|
||||
disabled={
|
||||
twoFactorAuthSetting.requesting
|
||||
}
|
||||
>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
{!next ? (
|
||||
<button
|
||||
id="nextFormButton"
|
||||
className={`bs-Button bs-DeprecatedButton bs-Button--blue ${twoFactorAuthSetting.requesting &&
|
||||
'bs-is-disabled'}`}
|
||||
disabled={
|
||||
twoFactorAuthSetting.requesting
|
||||
}
|
||||
onClick={event =>
|
||||
this.nextHandler(event)
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<ShouldRender
|
||||
if={
|
||||
twoFactorAuthSetting.requesting
|
||||
}
|
||||
>
|
||||
<Spinner />
|
||||
</ShouldRender>
|
||||
<span>Next</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
id="enableTwoFactorAuthButton"
|
||||
className={`bs-Button bs-DeprecatedButton bs-Button--blue ${twoFactorAuthSetting.requesting &&
|
||||
'bs-is-disabled'}`}
|
||||
type="submit"
|
||||
disabled={
|
||||
twoFactorAuthSetting.requesting
|
||||
}
|
||||
>
|
||||
<ShouldRender
|
||||
if={
|
||||
twoFactorAuthSetting.requesting
|
||||
}
|
||||
>
|
||||
<Spinner />
|
||||
</ShouldRender>
|
||||
<span>Verify</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TwoFactorAuthModal.displayName = 'TwoFactorAuthModal';
|
||||
|
||||
const TwoFactorAuthForm = reduxForm({
|
||||
form: 'TwoFactorAuthForm',
|
||||
enableReinitialize: true,
|
||||
validate,
|
||||
})(TwoFactorAuthModal);
|
||||
|
||||
TwoFactorAuthModal.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
generateTwoFactorQRCode: PropTypes.func,
|
||||
setTwoFactorAuth: PropTypes.func,
|
||||
user: PropTypes.object,
|
||||
qrCode: PropTypes.object,
|
||||
twoFactorAuthId: PropTypes.string,
|
||||
twoFactorAuthSetting: PropTypes.object,
|
||||
verifyTwoFactorAuthToken: PropTypes.func,
|
||||
twoFactorAuthTokenError: PropTypes.func,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
user: state.user.user,
|
||||
qrCode: state.user.qrCode,
|
||||
twoFactorAuthSetting: state.user.twoFactorAuthSetting,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
closeModal,
|
||||
setTwoFactorAuth,
|
||||
verifyTwoFactorAuthToken,
|
||||
generateTwoFactorQRCode,
|
||||
twoFactorAuthTokenError,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TwoFactorAuthForm);
|
@ -112,7 +112,7 @@ UserBlockBox.propTypes = {
|
||||
blockUser: PropTypes.func.isRequired,
|
||||
closeModal: PropTypes.func,
|
||||
openModal: PropTypes.func.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
userId: PropTypes.string,
|
||||
};
|
||||
|
||||
UserBlockBox.contextTypes = {
|
||||
|
@ -69,7 +69,7 @@ const mapStateToProps = state => {
|
||||
UserProject.propTypes = {
|
||||
fetchUserProjects: PropTypes.func.isRequired,
|
||||
userId: PropTypes.string,
|
||||
projects: PropTypes.array,
|
||||
projects: PropTypes.object,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserProject);
|
||||
|
@ -3,6 +3,10 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import ShouldRender from '../basic/ShouldRender';
|
||||
import PropTypes from 'prop-types';
|
||||
import DataPathHoC from '../DataPathHoC';
|
||||
import TwoFactorAuthModal from './TwoFactorAuth';
|
||||
import { updateTwoFactorAuthToken, setTwoFactorAuth } from '../../actions/user';
|
||||
import { openModal } from '../../actions/modal';
|
||||
|
||||
export class UserSetting extends Component {
|
||||
constructor(props) {
|
||||
@ -16,7 +20,35 @@ export class UserSetting extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
const {
|
||||
user,
|
||||
updateTwoFactorAuthToken,
|
||||
openModal,
|
||||
setTwoFactorAuth,
|
||||
} = this.props;
|
||||
if (user.twoFactorAuthEnabled) {
|
||||
updateTwoFactorAuthToken({
|
||||
twoFactorAuthEnabled: false,
|
||||
email: user.email,
|
||||
}).then(() => {
|
||||
setTwoFactorAuth(!user.twoFactorAuthEnabled);
|
||||
});
|
||||
} else {
|
||||
openModal({
|
||||
twoFactorAuthId: user.id,
|
||||
onClose: () => '',
|
||||
content: DataPathHoC(TwoFactorAuthModal, {}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let { twoFactorAuthEnabled } = this.props.user;
|
||||
if (twoFactorAuthEnabled === undefined) {
|
||||
twoFactorAuthEnabled = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bs-ContentSection Card-root Card-shadow--medium">
|
||||
<div className="Box-root">
|
||||
@ -121,6 +153,34 @@ export class UserSetting extends Component {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bs-Fieldset-row">
|
||||
<label className="bs-Fieldset-label">
|
||||
Two Factor Authentication <br />{' '}
|
||||
by Google Authenticator
|
||||
</label>
|
||||
<div className="bs-Fieldset-fields">
|
||||
<label
|
||||
className="Toggler-wrap"
|
||||
style={{
|
||||
marginTop: '10px',
|
||||
}}
|
||||
>
|
||||
<input
|
||||
className="btn-toggler"
|
||||
type="checkbox"
|
||||
onChange={
|
||||
this.handleChange
|
||||
}
|
||||
name="twoFactorAuthEnabled"
|
||||
id="twoFactorAuthEnabled"
|
||||
checked={
|
||||
twoFactorAuthEnabled
|
||||
}
|
||||
/>
|
||||
<span className="TogglerBtn-slider round"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
@ -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 = {
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user