Forgot Password Service in Badgeyay
Badgeyay is an open source badge generator service for generating badges developed by FOSSASIA community for technical events and conferences. The project is divided into two components mainly frontend and backend. After creating the user registration functionality in application, if the user forgets the credentials for the login, then there must be a way to recreate the credentials using a secure channel. This is only valid for the users signed up through email login as for the case of OAuth they must have access to their ID on respective social platform. The main challenges in resetting password for the user is to provide a secure channel. So the problem can be breakdown into following issues:
- Creating a token for reset action
- Sending that token via mail to user
- Verifying that token on the server and giving access
- Changing the credentials of the user
Procedure
- Generating token for the request to change credentials for the user. The token will be an expiry token and will be expired in the mentioned duration. So the token is valid for only a limited period of time and will prevent fraudulent requests.
def pwd_reset_token(): data = request.get_json()[‘data’][‘attributes’] if ’email’ not in data.keys(): print(‘Email not found’) email = data[’email’] user = User.getUser(email=email) if not user: return ErrorResponse(UserNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond() expire = datetime.datetime.utcnow() + datetime.timedelta(seconds=900) token = jwt.encode({ ‘id’: user.id, ‘exp’: expire }, app.config.get(‘SECRET_KEY’)) resetObj = ResetPasswordToken(user.id, token.decode(‘UTF-8’)) resetObj.save_to_db() return jsonify(TokenSchema().dump(resetObj).data) |
Model for ResetPasswordToken
class ResetPasswordToken(db.Model): __tablename__ = ‘Reset Password Token’ id = db.Column(db.String, primary_key=True) token = db.Column(db.String, nullable=False) def __init__(self, uid, token): self.id = uid self.token = token def save_to_db(self): try: db.session.add(self) db.session.commit() except Exception as e: db.session.rollback() db.session.flush() print(e) |
- Sending the password reset link via mail to the user. The link will contain the token (expiry token) that will be used to validate the request. For the case we will be using Firebase Cloud functions as an HTTP Trigger.
exports.sendResetMail = functions.https.onRequest((req, res) => { let token = req.query[‘token’]; let email = req.query[’email’]; res.setHeader(‘Content-Type’, ‘application/json’); sendResetMail(token, email) .then(() => { console.log(‘Reset mail sent to’, email); res.json({ data: { attributes: { status: 200 }, id: token, type: ‘reset-mails’ } }); return 0; }) .catch(err => { console.error(err); res.json({ data: { attributes: { status: 500 }, id: token, type: ‘reset-mails’ } }); return -1; }); }); function sendResetMail(token, email) { const mailOptions = { from: `${APP_NAME}<noreply@firebase.com>`, to: email, }; mailOptions.subject = `Password reset link`; mailOptions.html = ‘<p>Hey ‘ + email + ‘! Here is your password reset <a href=\” + PASSWORD_RESET_LINK + token + ‘\’>Link</a><p>’; return mailTransport.sendMail(mailOptions); } |
- Verifying the token on the server side to validate the user request
def validate_reset_token(): args = request.args if ‘token’ in args.keys(): token = args.get(‘token’) resp = {‘id’: token} try: jwt.decode(token, app.config[‘SECRET_KEY’]) resp[‘valid’] = True return jsonify(ValidTokenSchema().dump(resp).data) except Exception as e: resp[‘valid’] = False print(e) return jsonify(ValidTokenSchema().dump(resp).data) |
- After user has access to change the credentials, then user can send a POST request to backend through a form shown in UI to change its password.
def changePwd(): try: data = request.get_json()[‘data’][‘attributes’] except Exception as e: print(e) return ErrorResponse(PayloadNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond() token = data[‘token’] try: decoded_res = jwt.decode(token, app.config[‘SECRET_KEY’]) except Exception as e: print(e) return ErrorResponse(SignatureExpired().message, 422, {‘Content-Type’: ‘application/json’}).respond() user = User.getUser(user_id=decoded_res[‘id’]) if ‘pwd’ not in data.keys(): return ErrorResponse(PasswordNotFound().message, 422, {‘Content-Type’: ‘application/json’}).respond() pwd = data[‘pwd’] oldPwd = user.password user.password = generate_password_hash(pwd) user.save_to_db() resp = {‘id’: token} if update_firebase_password(user.id, pwd): resp[‘status’] = ‘Changed’ return jsonify(ResetPasswordOperation().dump(resp).data) else: print(‘Firebase not uploaded’) user.password = oldPwd user.save_to_db() resp[‘status’] = ‘Not Changed’ return jsonify(ResetPasswordOperation().dump(resp).data) |
- After this the password of the user will be changed and allowed to login through new credentials.
Link to PRs:
- PR for forgot password reset form – #1
- PR for implementing forgot password on firebase side – #2
- PR for password reset mail functionality – #3
Resources
You must be logged in to post a comment.