Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented Forgot Password Feature #133

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions React-frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,29 @@ import { Route, Switch } from 'react-router-dom';
import LoginPage from './pages/LoginPage';
import HomePage from './pages/HomePage';
import AboutPage from './pages/AboutPage';
import ContactPage from './pages/ContactPage'
import Alert from './components/core/Alert'
import ContactPage from './pages/ContactPage';
import ForgotPasswordPage from './pages/ForgotPasswordPage';
import ResetPasswordPage from './pages/ResetPasswordPage';
import Alert from './components/core/Alert';

import store from './store/store';

function App() {
return (
<Provider store={store}>
<div className="containerBI">
<div className='containerBI'>
<Alert />
<Switch>
<Route path="/login" exact component={LoginPage} />
<Route path='/login' exact component={LoginPage} />
<Route path='/' exact component={HomePage} />
<Route path='/about' exact component={AboutPage} />
<Route path='/contact' exact component={ContactPage} />
<Route path='/forgot-password' exact component={ForgotPasswordPage} />
<Route
path='/reset-password/:token'
exact
component={ResetPasswordPage}
/>
</Switch>
</div>
</Provider>
Expand Down
60 changes: 27 additions & 33 deletions React-frontend/src/components/LoginForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { MDBContainer, MDBInput, MDBBtn, MDBCard, MDBCardBody } from 'mdbreact';

import { login } from '../store/actions/auth';
import formReducer from '../utils/formReducer';
import { Link } from 'react-router-dom';

const FormPage = () => {
const dispatch = useDispatch();
Expand All @@ -14,17 +15,17 @@ const FormPage = () => {
};
const { isLoading } = useSelector(state => state.auth);
const [formData, setFormData] = useReducer(formReducer, initialFormData);
const [passwordShown, setPasswordShown] = useState(false)
const [passwordShown, setPasswordShown] = useState(false);

// password toggle handler
const togglePasswordVisibilty = () => {
setPasswordShown(passwordShown ? false : true)
}
setPasswordShown(passwordShown ? false : true);
};

const loginHandler = async e => {
e.preventDefault();
e.target.className += " was-validated"
if (formData.email && formData.password){
e.target.className += ' was-validated';
if (formData.email && formData.password) {
dispatch(login(formData));
}
};
Expand All @@ -34,11 +35,7 @@ const FormPage = () => {
<br />
<MDBCard>
<MDBCardBody className='align-center'>
<form
className='needs-validation'
onSubmit={loginHandler}
noValidate
>
<form className='needs-validation' onSubmit={loginHandler} noValidate>
<p className='h4 text-center py-4'>Sign In</p>
<p className='h7 text-center'>
{isLoading
Expand All @@ -54,11 +51,10 @@ const FormPage = () => {
value={formData.email}
type='email'
error='wrong'
required
required
success='right'
name='email'
onChange={event => setFormData(event.target)}
>
onChange={event => setFormData(event.target)}>
<div className='invalid-feedback'>
Please provide a valid email.
</div>
Expand All @@ -71,27 +67,26 @@ const FormPage = () => {
group
required
value={formData.password}
type={passwordShown ? 'text': 'password'}
type={passwordShown ? 'text' : 'password'}
name='password'
onChange={event => setFormData(event.target)}
>
<div className="invalid-feedback mb-4">
onChange={event => setFormData(event.target)}>
<div className='invalid-feedback mb-4'>
Please provide a password.
</div>
<div className="valid-feedback">Looks good!</div>
<div className='valid-feedback'>Looks good!</div>
</MDBInput>
<div class='form-check m-0'>
<input
class='form-check-input'
type='checkbox'
name='remember'
value={passwordShown}
onChange={togglePasswordVisibilty}
/>
<label class='form-check-label' htmlFor='flexCheckDefault'>
Show Password
</label>
</div>
<input
class='form-check-input'
type='checkbox'
name='remember'
value={passwordShown}
onChange={togglePasswordVisibilty}
/>
<label class='form-check-label' htmlFor='flexCheckDefault'>
Show Password
</label>
</div>
<div className='d-flex justify-content-center'>
<div class='form-check m-0'>
<input
Expand All @@ -108,10 +103,9 @@ const FormPage = () => {
</div>
</div>
<p className='mt-2 font-small blue-text d-flex justify-content-center pb-3'>
Forgot
<a href='#!' className='blue-text ml-1'>
Password?
</a>
<Link to='/forgot-password' className='blue-text ml-1'>
Forgot Password?
</Link>
</p>
<div className='text-center py-4'>
<MDBBtn
Expand Down
14 changes: 8 additions & 6 deletions React-frontend/src/components/core/Layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import Navbar from './Navbar';
import Footer from './Footer';
import { MDBContainer, MDBRow, MDBCol } from 'mdbreact';
import Sidebar from '../Sidebar';
import '../../App.css'
import '../../App.css';

const Layout = ({ children, sidebarBool = true }) => {
const Layout = ({ children, sidebarBool = true, background = true }) => {
return (
<div className="containerBI">
<div className='containerBI'>
<Navbar />
<MDBRow>
<MDBCol md="2">{sidebarBool && <Sidebar />}</MDBCol>
<MDBCol md="10"><MDBContainer>{children}</MDBContainer></MDBCol>
<MDBRow className={!background ? 'bg-white' : null}>
<MDBCol md='2'>{sidebarBool && <Sidebar />}</MDBCol>
<MDBCol md={sidebarBool ? '10' : '12'}>
<MDBContainer>{children}</MDBContainer>
</MDBCol>
</MDBRow>
<Footer />
</div>
Expand Down
59 changes: 59 additions & 0 deletions React-frontend/src/pages/ForgotPasswordPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { MDBBtn, MDBInput } from 'mdbreact';
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';

import Layout from '../components/core/Layout';
import { setAlert } from '../store/actions/alerts';
import { sendResetPasswordMail } from '../utils/resetPassword';

const ForgotPasswordPage = () => {
const dispatch = useDispatch();
const [email, setEmail] = useState('');
const [isLoading, setIsLoading] = useState(false);

const sendResetMailHandler = () => {
if (!email) {
dispatch(setAlert('Please enter an email'));
return;
}
setIsLoading(true);
sendResetPasswordMail(email)
.then(res => {
dispatch(setAlert('Reset link sent. Check your email', 'success'));
setIsLoading(false);
})
.catch(err => {
console.log(err);
dispatch(setAlert(err.message, 'danger'));
setIsLoading(false);
});
};

return (
<Layout sidebarBool={false} background={false}>
<div className='col mt-4' align='center'>
<h1 className='display-4'>Forgot Password</h1>
<div className='col-6'>
<MDBInput
disabled={isLoading}
label='Enter email'
icon='email'
required
value={email}
name='email'
onChange={event => setEmail(event.target.value)}
/>
<MDBBtn onClick={sendResetMailHandler} disabled={isLoading}>
Send Reset Password Mail
</MDBBtn>
<Link to='/'>
<MDBBtn disabled={isLoading}>Go to Home</MDBBtn>
</Link>
</div>
</div>
</Layout>
);
};

export default ForgotPasswordPage;
75 changes: 75 additions & 0 deletions React-frontend/src/pages/ResetPasswordPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useState } from 'react';
import { MDBBtn, MDBInput } from 'mdbreact';
import { Link, useParams } from 'react-router-dom';

import Layout from '../components/core/Layout';
import { changePassword } from '../utils/resetPassword';
import { useDispatch } from 'react-redux';
import { setAlert } from '../store/actions/alerts';

const ResetPasswordPage = () => {
const params = useParams();
const dispatch = useDispatch();
const [password, setPassword] = useState('');
const [password2, setPassword2] = useState('');
const [isLoading, setIsLoading] = useState(false);

const resetPasswordHandler = () => {
const token = params.token;
if (!password || !password2) {
dispatch(setAlert('Please enter password', 'danger'));
return;
}
if (password !== password2) {
dispatch(setAlert('Passwords do not match', 'danger'));
return;
}
setIsLoading(true);
changePassword(token, password)
.then(res => {
dispatch(setAlert('Password reset. Please log in', 'success'));
setIsLoading(false);
})
.catch(err => {
console.log(err);
dispatch(setAlert(err.message, 'danger'));
setIsLoading(false);
});
};

return (
<Layout sidebarBool={false} background={false}>
<div className='col mt-4' align='center'>
<h1 className='display-4'>Reset Password</h1>
<div className='col-6'>
<MDBInput
disabled={isLoading}
label='Enter password'
required
type='password'
value={password}
name='password'
onChange={event => setPassword(event.target.value)}
/>
<MDBInput
disabled={isLoading}
label='Confirm password'
required
type='password'
value={password2}
name='password2'
onChange={event => setPassword2(event.target.value)}
/>
<MDBBtn onClick={resetPasswordHandler} disabled={isLoading}>
Reset Password
</MDBBtn>
<Link to='/'>
<MDBBtn disabled={isLoading}>Go to Home</MDBBtn>
</Link>
</div>
</div>
</Layout>
);
};

export default ResetPasswordPage;
33 changes: 33 additions & 0 deletions React-frontend/src/utils/resetPassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import axios from '../axios';

export const sendResetPasswordMail = async email => {
try {
await axios.post(
'/forgot-password',
{ email },
{
headers: {
'Content-Type': 'application/json',
},
}
);
} catch (error) {
throw Error(error.response.data);
}
};

export const changePassword = async (token, password) => {
try {
await axios.post(
`/reset-password/${token}`,
{ password },
{
headers: {
'Content-Type': 'application/json',
},
}
);
} catch (error) {
throw Error(error.response.data);
}
};
4 changes: 4 additions & 0 deletions flask-backend/.envsample
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
MAIL_USERNAME=<email>
MAIL_PASSWORD=<password>
MAIL_SERVER=<smtpserver>
MAIL_PORT=<portnumber>
7 changes: 7 additions & 0 deletions flask-backend/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ To run the server use the following command:

Eithr from a terminal window or from postman you can send requests.

**Setting up .env file for Forgot Password Feature**

To enable the forgot password feature, you need to configure the .env file for sending emails through the Flask server.

1. Go to flask-backend directory
2. Create a .env file and configure it by following the .envsample file.

API Documentation
-----------------
[click here](https://github.com/shivanshu1333/My-GSoC-Proposals/blob/master/GSoC'20-SCoReLab-OpenMF.pdf)
Expand Down
10 changes: 8 additions & 2 deletions flask-backend/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from flask_login import LoginManager
from flask_marshmallow import Marshmallow
from flask_cors import CORS, cross_origin
from dotenv import load_dotenv
from flask_jwt_extended import JWTManager

db = SQLAlchemy()
ma = Marshmallow()
Expand All @@ -14,16 +16,20 @@ def create_app():
app.config['SECRET_KEY'] = 'thisismysecretkeydonotstealit'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False


app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
jwt = JWTManager(app)
db.init_app(app)

# Load environment variables
load_dotenv()

login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)

from .models.models import User


@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
Expand Down
Loading