Skip to content

Instantly share code, notes, and snippets.

@kirasiris
Last active January 4, 2021 01:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kirasiris/4326bf85d06b78c5e1de9f93c6e05d2a to your computer and use it in GitHub Desktop.
Save kirasiris/4326bf85d06b78c5e1de9f93c6e05d2a to your computer and use it in GitHub Desktop.
TFAuthenticationWithNodeJSandMongoDB
///
///
/// AS THE FILE TITLE SUGGEST, THIS IS WHERE YOU SEND YOUR USERNAME AND/OR PASSWORD DATA
///
///
import React, { useEffect, useState } from 'react';
import { Link, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Helmet from 'react-helmet';
// ACTIONS
import { login } from '../../actions/auth';
// HELPERS
import { URL } from '../../config';
import Alert from '../layout/Alert';
// REACTBOOTSTRAP
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import InputGroup from 'react-bootstrap/InputGroup';
import Button from 'react-bootstrap/Button';
const Login = ({ login, history }) => {
const [loginData, setLoginData] = useState({
email: '',
password: '',
rememberMe: false
});
const { email, password, rememberMe } = loginData;
// Remember me
useEffect(() => {
const email = localStorage.getItem('email');
const password = localStorage.getItem('password');
email &&
password &&
setLoginData((loginData) => ({
...loginData,
email,
password,
rememberMe: true
}));
}, []);
const handleChange = (name) => (e) => {
setLoginData({ ...loginData, [name]: e.target.value });
};
const loginAccount = async (e) => {
e.preventDefault();
if (rememberMe) {
localStorage.setItem('email', email);
localStorage.setItem('password', password);
} else {
localStorage.removeItem('email');
localStorage.removeItem('password');
}
login(loginData, history);
};
const [passwordShown, setPasswordShown] = useState(false);
const handlePasswordVisibility = () => {
setPasswordShown(passwordShown ? false : true);
};
const resetForm = () => {
setLoginData({
email: '',
password: ''
});
};
return (
<>
<Helmet>
<title>beFree | Login</title>
<meta
name="description"
content="Access to the best open minded social network"
></meta>
<meta property="og:title" content="beFree | Login" />
<meta
property="og:description"
content="Access to the best open minded social network"
/>
<meta property="og:url" content={`${URL}/login`} />
<meta property="og:site_name" content="beFree | Login" />
</Helmet>
<section>
<Container>
<Row>
<Col
xl={`6`}
lg={`6`}
md={`12`}
sm={`12`}
xs={`12`}
className="ml-auto mr-auto mt-5 mb-5"
>
<Link to={`/`} className="btn btn-dark btn-sm float-left">
Go back home
</Link>
<div className="clearfix"></div>
<h1 className="text-center">Log In</h1>
<Alert />
<div className="pb-5 pt-5 form-wrapper">
<Form className="form form-signing p-5" onSubmit={loginAccount}>
{/* Email */}
<Form.Group>
<Form.Label htmlFor="email">Email</Form.Label>
<InputGroup>
<InputGroup.Prepend>
<InputGroup.Text id="email-text">
<i className="fas fa-envelope" />
</InputGroup.Text>
</InputGroup.Prepend>
<Form.Control
type="email"
placeholder="Email Address"
aria-label="email"
aria-describedby="email-text"
autoComplete="email"
name="email"
id="email"
minLength="6"
required
onChange={handleChange('email')}
value={email}
/>
</InputGroup>
</Form.Group>
{/* Password */}
<Form.Group>
<Form.Label htmlFor="password">Password</Form.Label>
<InputGroup>
<InputGroup.Prepend>
<InputGroup.Text id="password-text">
<i className="fas fa-lock" />
</InputGroup.Text>
</InputGroup.Prepend>
<Form.Control
type={passwordShown ? 'text' : 'password'}
placeholder="Password"
aria-label="Password"
aria-describedby="password-text"
autoComplete="password"
name="password"
id="password"
minLength="6"
required
onChange={handleChange('password')}
value={password}
/>
</InputGroup>
</Form.Group>
{/* View Password */}
<Form.Group
controlId="rememberAccount"
className={`float-left`}
name={`rememberMe`}
onChange={handleChange('rememberMe')}
checked={rememberMe}
readOnly
>
<Form.Check type="checkbox" label="Remember" />
</Form.Group>
{/* View Password */}
<Form.Group
controlId="viewPassword"
className={`float-right`}
>
<Form.Check
type="checkbox"
label="View Password"
onClick={handlePasswordVisibility}
/>
</Form.Group>
<div className={`clearfix`}></div>
{/* Buttons */}
<Button
variant={`dark`}
className={`float-left`}
type={`submit`}
size={`sm`}
disabled={
email.length > 0 && password.length > 0 ? !true : !false
}
>
Login
</Button>
<Button
variant={`secondary`}
className={`float-right`}
type={`reset`}
size={`sm`}
onClick={resetForm}
>
Reset
</Button>
</Form>
</div>
<div className="links">
<Link to="/auth/register">Register</Link> |{' '}
<Link to="/auth/recover">Forgot Password</Link>
</div>
</Col>
</Row>
</Container>
</section>
</>
);
};
Login.propTypes = {
login: PropTypes.func.isRequired
};
export default connect(null, { login })(withRouter(Login));
// @desc Get current loggedIn user
// @route POST /api/v1/auth/me
// @access Private
// @status DONE
exports.getMe = asyncHandler(async (req, res, next) => {
const user = await User.findById(req.user.id)
res.status(200).json({ success: true, data: user })
})
// @desc Login user
// @route POST /api/v1/auth/login
// @access Public
// @status DONE
exports.login = asyncHandler(async (req, res, next) => {
const { email, password } = req.body
// Validate email & password
if (!email || !password) {
return next(new ErrorResponse('Please provide an email and password', 400))
}
// Check for user to login and to register activity
const user = await User.findOne({ email }).select([
'username',
'password',
'isEmailConfirmed',
'twoFactorTokenEnabled',
])
if (!user) {
return next(new ErrorResponse('Invalid credentials', 401))
}
// Check if user email is active
if (!user.isEmailConfirmed) {
return next(new ErrorResponse('Not authorized to access this route', 401))
}
// Check if password matches
const isMatch = await user.matchPassword(password)
if (!isMatch) {
return next(new ErrorResponse('Invalid credentials', 401))
}
// Create activity
await Activity.create({
resourceId: user._id,
type: `user`,
action: `logged_in`,
user: user._id,
message: `has Logged In`,
onModel: `User`,
})
// Verify if user has enabled his 2FA authentication method, otherwise let him log in as
// usual without requiring a Google Authenticar Token
user.twoFactorTokenEnabled
? res.status(200).json({ success: true, data: user })
: sendTokenResponse(user, 200, req, res)
})
// @desc Enable 2FA
// @route PUT /api/v1/auth/2fa/enable
// @access Private
// @status DONE
exports.enableFA = asyncHandler(async (req, res, next) => {
// Generate token
const temp_secret = speakeasy.generateSecret()
const user = await User.findByIdAndUpdate(
{
_id: req.user._id,
},
{
$set: {
twoFactorTokenEnabled: true,
twoFactorToken: {
ascii: temp_secret.ascii,
hex: temp_secret.hex,
base32: temp_secret.base32,
otpauth_url: temp_secret.otpauth_url,
},
},
},
{
new: true,
runValidators: true,
setDefaultsOnInsert: true,
}
)
// Create URL
const url = `${req.get('origin')}/two-fa-verify/${req.user._id}`
// Send email
await new Email(req.user, url).sendTFAEnabled()
// Create activity
await Activity.create({
resourceId: req.user._id,
type: `user`,
action: `enabled_2FA`,
user: req.user._id,
message: `has enabled 2FA`,
onModel: `User`,
})
res.status(201).json({ success: true, data: user })
})
// @desc Disable 2FA
// @route PUT /api/v1/auth/2fa/disable
// @access Private
// @status DONE
exports.disableFA = asyncHandler(async (req, res, next) => {
const user = await User.findByIdAndUpdate(
{
_id: req.user._id,
},
{
$set: {
twoFactorTokenEnabled: false,
twoFactorToken: {
ascii: null,
hex: null,
base32: null,
otpauth_url: null,
},
twoFactorRecoveryToken: null,
},
},
{
new: true,
runValidators: true,
setDefaultsOnInsert: true,
}
)
// Create URL
const url = `${req.get('origin')}/edit-two-fa`
// Send email
await new Email(req.user, url).sendTFADisabled()
// Create activity
await Activity.create({
resourceId: req.user._id,
type: `user`,
action: `disabled_2FA`,
user: req.user._id,
message: `has disabled 2FA`,
onModel: `User`,
})
res.status(201).json({
success: true,
data: user,
})
})
// @desc Verify 2FA
// @route PUT /api/v1/auth/2fa/verify/:id
// @access Private
// @status DONE
exports.verifyFA = asyncHandler(async (req, res, next) => {
const { token } = req.body
// Validate email & password
if (!req.params.id || !token) {
return next(new ErrorResponse('Please provide an userId and token', 400))
}
// Get user twoFactorToken
const user = await User.findById(req.params.id)
// User not found
if (!user) {
return next(new ErrorResponse('User not found', 404))
}
// If user has no twoFactorToken enabled...then..:
if (!user.twoFactorTokenEnabled) {
return next(new ErrorResponse('User has not requested a 2FA code'))
}
// Verify
const verified = speakeasy.totp.verify({
secret: user.twoFactorToken.base32,
encoding: 'base32',
token,
})
if (!verified) {
return next(new ErrorResponse('Token do not match', 400))
}
// Set recovery token and save it
user.twoFactorRecoveryToken = `${user._id}-${user.username}-${user.twoFactorToken.base32}`
user.save()
// Create URL
const url = `${req.get('origin')}`
// Send email
await new Email(user, url).sendTFAVerifyToken()
// Create activity
await Activity.create({
resourceId: req.params._id,
type: `user`,
action: `verified_2FA`,
user: req.params._id,
message: `has verified 2FA`,
onModel: `User`,
})
res.status(201).json({ success: true, data: user.twoFactorRecoveryToken })
})
// @desc Validate 2FA
// @route PUT /api/v1/auth/2fa/validate/:id
// @access Private
// @status DONE
exports.validateFA = asyncHandler(async (req, res, next) => {
const { token } = req.body
// Validate email & password
if (!req.params.id || !token) {
return next(new ErrorResponse('Please provide a token', 400))
}
// Get user twoFactorToken
const user = await User.findById(req.params.id)
if (!user) {
return next(new ErrorResponse('User not found', 404))
}
// Check if user email is active
if (!user.isEmailConfirmed) {
return next(new ErrorResponse('Not authorized to access this route', 401))
}
// Check if 2FA is enabled
if (!user.twoFactorTokenEnabled) {
return next(new ErrorResponse('User has not authorized to use 2FA', 401))
}
// Verify
const tokenValidates = speakeasy.totp.verify({
secret: user.twoFactorToken.base32,
encoding: 'base32',
token,
window: 0,
})
if (!tokenValidates) {
return next(new ErrorResponse('Invalid 2FA Token', 500))
}
sendTokenResponse(user, 200, req, res)
});
router.route('/2fa/enable').put(protect, enableFA);
router.route('/2fa/disable').put(protect, disableFA);
router.route('/2fa/verify/:id').put(verifyFA);
router.route('/2fa/validate/:id').post(validateFA);
///
/// HEADS UP. I USUALLY DO HOOK, TO CONVERT THEM TO A BASE CLASS COMPONENT INSTEAD OF USING USEEFFECT, USESTATE,ETC
// YOU WILL NEED THE LIFECYCLE METHODS
// I'M ALSO USING REACT-BOOTSTRAP
//
// THIS IS THE COMPONENT IN WHICH YOU CAN TOGGLE ON AND OFF YOUR TFA FUNCTIONALITY
///
///
import React, { useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
// ACTIONS
import {
getCurrentProfile,
enableTwoFactorAuth,
disableTwoFactorAuth
} from '../../actions/auth';
// HELPERS
import Alert from '../layout/Alert';
import Layout from '../layout/Layout';
import { URL } from '../../config';
import Spinner from '../layout/Spinner';
import LeftSidebar from './LeftSidebar';
import NotFound from '../layout/NotFound';
import Content from '../layout/Container';
import Sidebar from '../layout/Sidebar';
import UseImage from '../../helpers/UseImage';
// REACTBOOTSTRAP
import Row from 'react-bootstrap/Row';
import Button from 'react-bootstrap/Button';
import Card from 'react-bootstrap/Card';
// QR CODE
import QRCode from 'qrcode';
const EditTwoFactorAuthentication = ({
getCurrentProfile,
enableTwoFactorAuth,
disableTwoFactorAuth,
profile: { loading }
}) => {
const [twoFactorAuthData, setTwoFactorAuthData] = useState({
base32: null,
otpauth_url: null
});
const { base32, otpauth_url } = twoFactorAuthData;
const [profile, setProfile] = useState(null);
const [enabled, setEnabled] = useState(false);
const [enabledStr, setEnabledStr] = useState(`Enable`);
const [recoveryToken, setRecoveryToken] = useState(null);
const [qrCodeImage, setQRCodeImage] = useState(null);
const [error, setError] = useState(false);
const QRImage = (url) => {
QRCode.toDataURL(url)
.then((url) => {
setQRCodeImage(url);
})
.catch((err) => {
setError(true);
});
};
useEffect(() => {
getCurrentProfile()
.then((result) => {
setProfile(result.payload.data);
setEnabled(result.payload.data.twoFactorTokenEnabled);
setEnabledStr(
result.payload.data.twoFactorTokenEnabled ? `Enabled` : `Disabled`
);
setRecoveryToken(result.payload.data.twoFactorRecoveryToken);
setTwoFactorAuthData({
base32: result.payload.data.twoFactorToken.base32,
otpauth_url: result.payload.data.twoFactorToken.otpauth_url
});
QRImage(result.payload.data.twoFactorToken.otpauth_url);
})
.catch((err) => {
setProfile(null);
setEnabled(false);
setEnabledStr(`Enabled`);
setRecoveryToken(null);
setTwoFactorAuthData({
base32: null,
otpauth_url: null
});
setError(true);
});
}, [getCurrentProfile, loading]);
const activate = async (e) => {
enableTwoFactorAuth()
.then((result) => {
setEnabled(result.payload.data.twoFactorTokenEnabled);
setEnabledStr(`Enabled`);
setRecoveryToken(result.payload.data.twoFactorRecoveryToken);
setTwoFactorAuthData({
base32: result.payload.data.twoFactorToken.base32,
otpauth_url: result.payload.data.twoFactorToken.otpauth_url
});
QRImage(result.payload.data.twoFactorToken.otpauth_url);
})
.catch((err) => {
setEnabled(false);
setEnabledStr(`Disabled`);
setRecoveryToken(null);
setTwoFactorAuthData({
base32: null,
otpauth_url: null
});
setError(true);
});
};
const disactivate = async (e) => {
disableTwoFactorAuth()
.then((result) => {
setEnabled(result.payload.data.twoFactorTokenEnabled);
setEnabledStr(`Disabled`);
setRecoveryToken(null);
setTwoFactorAuthData({
base32: null,
otpauth_url: null
});
})
.catch((err) => {
setEnabled(true);
setEnabledStr(`Enabled`);
setRecoveryToken(profile?.twoFactorRecoveryToken);
setTwoFactorAuthData({
base32: profile?.twoFactorToken?.base32,
otpauth_url: profile?.twoFactorToken?.otpauth_url
});
setError(true);
});
};
return (
<Layout
title={`beFree | Update 2FA Settings`}
description={`Update your profile`}
author={`Kevin Fonseca`}
sectionClass={`mb-3`}
containerClass={`container`}
canonical={`${URL}`}
url={`edit-two-fa`}
posType={`page`}
>
{loading || profile === null || profile === undefined ? (
error ? (
<NotFound />
) : (
<Spinner />
)
) : (
<Row>
<Sidebar>
<LeftSidebar />
</Sidebar>
<Content>
<Alert />
<Card>
<Card.Header>
<Card.Title>Update 2FA</Card.Title>
</Card.Header>
<Card.Body>
<h6>Instructions</h6>
<ol className={`ml-3`}>
<li>On your phone, go to the app store</li>
<li>
Search for <i>Google Authenticator</i>
</li>
<li>
Download and Install the{' '}
<a
href={`https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en_US&gl=US`}
target={`_blank`}
rel={`noopener noreferrer`}
>
Google Authenticator App
</a>
</li>
</ol>
<h6>Open and Configure the Google Authenticator App</h6>
<ol className={`ml-3`}>
<li>
In Google Authenticator, touch the{' '}
<i className={`fas fa-plus mr-1`} />
and select any of the following options:
<ul className={`ml-4`}>
<li>
<i className={`fas fa-camera mr-1`} />
Scan a QR Code
</li>
<li>
<i className={`fas fa-keyboard mr-1`} />
Enter a setup key
</li>
</ul>
</li>
</ol>
{enabled && (
<>
<hr />
<UseImage
src={`${qrCodeImage}`}
alt={`QRCODE`}
idGiven={`QRCODE`}
/>
<hr />
{recoveryToken && (
<>
<p>
Please keep this code in a safe but accessible area.
This is your <b>BACKUP</b> code:
<br />
<code>{recoveryToken}</code>
<br />
You can now close this window
</p>
</>
)}
<p>
Copy this to <i>Enter a setup key</i> to your
authenticator app:
<br />
<code>{base32}</code>
</p>
<p>
This is the string used for the <i>QR Code</i>.
<b>You can ignore it!. </b>
<br />
<code>{otpauth_url}</code>
</p>
</>
)}
<Button
onClick={!enabled ? activate : disactivate}
variant={!enabled ? `secondary` : `success`}
size={`sm`}
type={`button`}
>
{enabledStr}
</Button>
</Card.Body>
</Card>
</Content>
</Row>
)}
</Layout>
);
};
EditTwoFactorAuthentication.propTypes = {
getCurrentProfile: PropTypes.func.isRequired,
enableTwoFactorAuth: PropTypes.func.isRequired,
disableTwoFactorAuth: PropTypes.func.isRequired,
profile: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
profile: state.profile
});
export default connect(mapStateToProps, {
getCurrentProfile,
enableTwoFactorAuth,
disableTwoFactorAuth
})(withRouter(EditTwoFactorAuthentication));
///
///
/// I'M USING AXIOS BUT YOU'RE FREE TO USE THE FETCH API or ANY OTHER HTTP REQUESTS API
///
///
// @route PUT api/v1/auth/2fa/enable
// @description Enable 2FA
// @access Private
// @task DONE
export const enableTwoFactorAuth = () => async (dispatch) => {
try {
const res = await api.put(`/auth/2fa/enable`);
dispatch(
setAlert(
'2FA has been enabled. Please check your email to verify',
'success'
)
);
// RETURN BECAUSE WE NEED THE DATA TO HANDLE THE STATES
return dispatch({
type: GET_PROFILE,
payload: res.data
});
} catch (err) {
const error = err.response.data.message;
const errors = err?.response?.data?.errors;
if (error) {
dispatch(setAlert(error, 'danger'));
}
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response?.statusText, status: err.response?.status }
});
}
};
// @route PUT api/v1/auth/2fa/disable
// @description Disable 2FA
// @access Private
// @task DONE
export const disableTwoFactorAuth = () => async (dispatch) => {
try {
const res = await api.put(`/auth/2fa/disable`);
dispatch(setAlert('Profile Updated. 2FA has been disabled.', 'success'));
// RETURN BECAUSE WE NEED THE DATA TO HANDLE THE STATES
return dispatch({
type: GET_PROFILE,
payload: res.data
});
} catch (err) {
const error = err.response.data.message;
const errors = err?.response?.data?.errors;
if (error) {
dispatch(setAlert(error, 'danger'));
}
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response?.statusText, status: err.response?.status }
});
}
};
// @route PUT api/v1/auth/2fa/verify/:id
// @description Verify 2FA
// @access Private
// @task DONE
export const verifyTwoFactorAuthentication = (id, formData) => async (
dispatch
) => {
try {
const res = await api.put(`/auth/2fa/verify/${id}`, formData);
dispatch(
setAlert(
'Profile Updated. You will also receive an email containing your recovery token.',
'success'
)
);
// RETURN BECAUSE WE NEED THE DATA TO HANDLE THE STATES. THE RECOVERY TOKEN WILL BE SHOWN AFTER SUCCESS
return dispatch({
type: GET_PROFILE,
payload: res.data
});
} catch (err) {
const error = err.response.data.message;
const errors = err?.response?.data?.errors;
if (error) {
dispatch(setAlert(error, 'danger'));
}
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response?.statusText, status: err.response?.status }
});
}
};
// @route PUT api/v1/auth/2fa/validate
// @description Validate 2FA
// @access Private
// @task DONE
export const validateFactorAuth = (id, formData, history) => async (
dispatch
) => {
try {
const res = await api.post(`/auth/2fa/validate/${id}`, formData);
// FUCK IT AND JUST LET THE USER LOG IN AFTER TOKEN IS FOUND TO BE VALID
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
});
dispatch(loadUser());
history.push('/timeline');
} catch (err) {
const error = err.response.data.message;
const errors = err?.response?.data?.errors;
if (error) {
dispatch(setAlert(error, 'danger'));
}
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response?.statusText, status: err.response?.status }
});
}
};
{/* REMEMBER THESE TWO ARE PUBLIC ROUTES AND DO NOT REQUIRED AUTHENTICATION */}
<Route exact path="/two-fa-verify/:id" component={VerifyTwoFactorAuth} />
<Route exact path="/two-fa-validate/:id" component={ValidateTwoFactorAuth} />
{/* REMEMBER THIS IS THE ONLY ROUTE WHICH REQUIRES USER AUTHENTICATION, HWO YOU HANDLE THE AUTHENTICATION IS UP TO YOU */}
<PrivateRoute exact path="/edit-two-fa" component={EditTwoFactorAuthentication} />
twoFactorTokenEnabled: {
type: Boolean,
required: false,
default: false
},
twoFactorToken: {
ascii: {
type: String,
required: false,
default: null
},
hex: {
type: String,
required: false,
default: null
},
base32: {
type: String,
required: false,
default: null
},
otpauth_url: {
type: String,
required: false,
default: null
}
},
twoFactorRecoveryToken: {
type: String,
required: false,
default: null
},
///
///
/// THIS COMPONENT WILL BE RETURNED DEPENDING ON THE LOGIN DATA SENT
/// IF USER IS FOUND WITH 2FA DISABLED, THIS WILL NOT BE SHOWN.
///
/// YES, THIS IS THE COMPONENT IN WHICH YOU WILL TYPE YOUR GOOGLE AUTHENTICATOR TOKEN. ONCE THE TOKEN IS FOUND TO BE VALID YOU BE
/// REDIRECTED TO YOUR APP
///
///
import React, { useState } from 'react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
// ACTIONS
import { validateFactorAuth } from '../../actions/auth';
// HELPERS
import Alert from '../layout/Alert';
import Layout from '../layout/Layout';
import { URL } from '../../config';
import Content from '../layout/Container';
// REACTBOOTSTRAP
import Row from 'react-bootstrap/Row';
import Card from 'react-bootstrap/Card';
import Form from 'react-bootstrap/Form';
import InputGroup from 'react-bootstrap/InputGroup';
import ButtonGroup from 'react-bootstrap/ButtonGroup';
import Button from 'react-bootstrap/Button';
const ValidateTwoFactorAuth = ({ validateFactorAuth, match, history }) => {
const userId = match.params.id;
const [tokenData, setTokenData] = useState({
token: ``
});
const { token } = tokenData;
const [validated, setValidated] = useState(false);
const handleChange = (name) => (e) => {
setTokenData({ ...tokenData, [name]: e.target.value });
};
const validateTFA = async (e) => {
const form = e.currentTarget;
if (form.checkValidity() === false) {
e.preventDefault();
e.stopPropagation();
}
e.preventDefault();
setValidated(true);
validateFactorAuth(userId, tokenData, history);
};
return (
<Layout
title={`beFree | Validate 2FA`}
description={`Validate your 2FA to access your account`}
author={`Kevin Fonseca`}
sectionClass={`mb-3`}
containerClass={`container`}
canonical={`${URL}`}
url={`two-fa-validate`}
posType={`page`}
>
<Row>
<Content fullWidth>
<Alert />
<Card>
<Card.Header>
<Card.Title>
Please enter the 2FA token given to you by your Authenticator
app
</Card.Title>
</Card.Header>
<Card.Body>
<Form
className={`form`}
noValidate
validated={validated}
onSubmit={validateTFA}
>
<Form.Group>
<Form.Label htmlFor={`token`}>Token</Form.Label>
<InputGroup>
<InputGroup.Prepend>
<InputGroup.Text id={`token-text`}>
<i className={`fas fa-key`} />
</InputGroup.Text>
</InputGroup.Prepend>
<Form.Control
type={`text`}
placeholder={`012 345`}
aria-label={`token`}
aria-describedby={`token-text`}
autoComplete={`token`}
name={`token`}
id={`token`}
onChange={handleChange('token')}
value={token}
/>
</InputGroup>
</Form.Group>
<ButtonGroup>
<Button
variant={`primary`}
size={`sm`}
className={`my-1`}
type={`submit`}
>
Submit
</Button>
</ButtonGroup>
</Form>
</Card.Body>
</Card>
</Content>
</Row>
</Layout>
);
};
ValidateTwoFactorAuth.propTypes = {
validateFactorAuth: PropTypes.func.isRequired
};
export default connect(null, { validateFactorAuth })(
withRouter(ValidateTwoFactorAuth)
);
///
/// HEADS UP. I USUALLY DO HOOK, TO CONVERT THEM TO A BASE CLASS COMPONENT INSTEAD OF USING USEEFFECT, USESTATE,ETC
// YOU WILL NEED THE LIFECYCLE METHODS
// I'M ALSO USING REACT-BOOTSTRAP
//
// THIS IS THE COMPONENT IN WHICH YOU SEND A FIRST TIME TOKEN TO THE DB IN ORDER TO GENERATE A RECOVERY TOKEN
// FOR CASES IN WHICH YOU FORGET YOUR PASSWORD AND NO SINGLE GOOGLE AUTHENTICATOR TOKEN IS WORKING.
// YOU CAN NOT RESET YOUR PASSWORD WHEN YOUR TFA IS ENABLED....
///
///
import React, { useState } from 'react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
// ACTIONS
import { verifyTwoFactorAuthentication } from '../../actions/auth';
// HELPERS
import Alert from '../layout/Alert';
import Layout from '../layout/Layout';
import { URL } from '../../config';
import Content from '../layout/Container';
// REACTBOOTSTRAP
import Row from 'react-bootstrap/Row';
import Card from 'react-bootstrap/Card';
import Form from 'react-bootstrap/Form';
import InputGroup from 'react-bootstrap/InputGroup';
import ButtonGroup from 'react-bootstrap/ButtonGroup';
import Button from 'react-bootstrap/Button';
const VerifyTwoFactorAuth = ({ verifyTwoFactorAuthentication, match }) => {
const userId = match.params.id;
const [tokenData, setTokenData] = useState({
token: ``
});
const { token } = tokenData;
const [verified, setVerified] = useState(false);
const [recoveryToken, setRecoveryToken] = useState(``);
const [validated, setValidated] = useState(false);
const [, setError] = useState(false);
const handleChange = (name) => (e) => {
setTokenData({ ...tokenData, [name]: e.target.value });
};
const verifyTFA = async (e) => {
const form = e.currentTarget;
if (form.checkValidity() === false) {
e.preventDefault();
e.stopPropagation();
}
e.preventDefault();
setValidated(true);
verifyTwoFactorAuthentication(userId, tokenData)
.then((result) => {
setVerified(true);
setRecoveryToken(result.payload.data);
})
.catch((err) => {
setVerified(false);
setError(true);
});
};
return (
<Layout
title={`beFree | Verify 2FA`}
description={`Verify your 2FA`}
author={`Kevin Fonseca`}
sectionClass={`mb-3`}
containerClass={`container`}
canonical={`${URL}`}
url={`two-fa-verify`}
posType={`page`}
>
<Row>
<Content fullWidth>
<Alert />
<Card>
<Card.Header>
<Card.Title>Verify your first 2FA token</Card.Title>
</Card.Header>
<Card.Body>
<Form
className={`form`}
noValidate
validated={validated}
onSubmit={verifyTFA}
>
<Form.Group>
<Form.Label htmlFor={`token`}>Token</Form.Label>
<InputGroup>
<InputGroup.Prepend>
<InputGroup.Text id={`token-text`}>
<i className={`fas fa-key`} />
</InputGroup.Text>
</InputGroup.Prepend>
<Form.Control
type={`text`}
placeholder={`012 345`}
aria-label={`token`}
aria-describedby={`token-text`}
autoComplete={`token`}
name={`token`}
id={`token`}
onChange={handleChange('token')}
value={token}
/>
</InputGroup>
</Form.Group>
{verified && (
<>
<p>
Please keep this code in a safe but accessible area. This
is your <b>BACKUP</b> code:
<br />
<code>{recoveryToken}</code>
<br />
You can now close this window
</p>
</>
)}
<ButtonGroup>
<Button
variant={`primary`}
size={`sm`}
className={`my-1`}
type={`submit`}
>
Submit
</Button>
</ButtonGroup>
</Form>
</Card.Body>
</Card>
</Content>
</Row>
</Layout>
);
};
VerifyTwoFactorAuth.propTypes = {
verifyTwoFactorAuthentication: PropTypes.func.isRequired
};
export default connect(null, { verifyTwoFactorAuthentication })(
withRouter(VerifyTwoFactorAuth)
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment