From 5a25f0073d14c53ba8ff9c18087080374c09f9d5 Mon Sep 17 00:00:00 2001
From: the catalyst <adityabhatnagar14@gmail.com>
Date: Thu, 16 Jan 2020 02:38:41 +0530
Subject: [PATCH 01/11] add a command to create admin users and removed
 admin.json file also authenticated the stories/all route using jwt

---
 README.md                                     |   5 ++
 fact-bounty-flask/api/app.py                  |  34 ++++-----
 fact-bounty-flask/api/commands.py             |  56 ++++++++++++++-
 fact-bounty-flask/api/data-dev.sqlite         | Bin 0 -> 32768 bytes
 fact-bounty-flask/api/helpers.py              |   7 +-
 fact-bounty-flask/api/stories/controller.py   |  52 ++++++++------
 fact-bounty-flask/api/user/model.py           |  15 +++-
 .../migrations/versions/02db7a3aaa95_.py      |  66 ++++++++++++++++++
 .../migrations/versions/78d18dc2789d_.py      |  58 +++++++++++++++
 fact-bounty-flask/requirements.txt            |   2 +-
 10 files changed, 248 insertions(+), 47 deletions(-)
 create mode 100644 fact-bounty-flask/api/data-dev.sqlite
 mode change 100644 => 100755 fact-bounty-flask/api/user/model.py
 create mode 100644 fact-bounty-flask/migrations/versions/02db7a3aaa95_.py
 create mode 100644 fact-bounty-flask/migrations/versions/78d18dc2789d_.py

diff --git a/README.md b/README.md
index 3a265574..63596016 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,11 @@ Run npm install in fact-bounty-client folder.
         export ELASTIC_SEARCH_USERNAME=""
         export ELASTIC_SEARCH_PASSWORD=""
 
+        export ADMIN_USERNAME = "admin"
+        export ADMIN_PASSWORD = "password"
+        export ADMIN_EMAIL = "email@email.com"
+
+
         export TZ=“Asia/Colombo”
     ```
 
diff --git a/fact-bounty-flask/api/app.py b/fact-bounty-flask/api/app.py
index f8c360d2..4bfc9022 100644
--- a/fact-bounty-flask/api/app.py
+++ b/fact-bounty-flask/api/app.py
@@ -15,28 +15,23 @@
 
 
 def create_app(config_name):
-    try:
-        # create and configure the app
-        app = Flask(
-            __name__,
-            static_folder="../build/static",
-            template_folder="../build",
-        )
-        app.config.from_object(config[config_name])
-        config[config_name].init_app(app)
+    # create and configure the app
+    app = Flask(
+        __name__, static_folder="../build/static", template_folder="../build"
+    )
+    app.config.from_object(config[config_name])
+    config[config_name].init_app(app)
 
-        register_extensions(app)
-        register_blueprint(app)
-        register_shellcontext(app)
-        register_commands(app)
+    register_extensions(app)
+    register_blueprint(app)
+    register_shellcontext(app)
+    register_commands(app)
 
-        @app.before_first_request
-        def create_tables():
-            db.create_all()
+    @app.before_first_request
+    def create_tables():
+        db.create_all()
 
-        return app
-    except Exception as err:
-        print("Error occured:", err)
+    return app
 
 
 def register_extensions(app):
@@ -82,6 +77,7 @@ def register_commands(app):
     app.cli.add_command(commands.clean)
     app.cli.add_command(commands.urls)
     app.cli.add_command(commands.deploy)
+    app.cli.add_command(commands.create_admin)
 
 
 def register_shellcontext(app):
diff --git a/fact-bounty-flask/api/commands.py b/fact-bounty-flask/api/commands.py
index e99267ad..53dcd1c8 100644
--- a/fact-bounty-flask/api/commands.py
+++ b/fact-bounty-flask/api/commands.py
@@ -6,16 +6,39 @@
 import coverage
 import sys
 import click
-from flask import current_app
+from flask import current_app, make_response, jsonify
 from flask.cli import with_appcontext
 from flask_migrate import upgrade
 from werkzeug.exceptions import MethodNotAllowed, NotFound
+from .user import model
+import getpass
+import re
 
 
 COV = None
 if os.environ.get("FLASK_COVERAGE"):
     COV = coverage.coverage(branch=True, include="./*")
     COV.start()
+"""
+regex to check valid email is entered
+"""
+regex = r"^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$"
+
+"""
+function for
+for validating an Email
+"""
+
+
+def check(email):
+    """
+    pass the regualar expression
+    and the string in search() method
+    """
+    if re.search(regex, email):
+        return True
+    else:
+        return False
 
 
 @click.command()
@@ -168,3 +191,34 @@ def deploy():
     migrate database to latest revision
     """
     upgrade()
+
+
+@click.command(name="create_admin")
+@with_appcontext
+def create_admin():
+    """
+    create an admin user
+    """
+    admin_username = input("Enter the admin username: ")
+    admin_password = getpass.getpass(prompt="Enter the admin password: ")
+    admin_role = "admin"
+    valid = False
+    while not valid:
+        admin_email = input("Enter the admin email: ")
+        valid = check(admin_email)
+        if valid:
+            break
+        else:
+            pass
+    try:
+        user = model.User(
+            name=admin_username,
+            email=admin_email,
+            password=admin_password,
+            role=admin_role,
+        )
+        user.save()
+    except Exception as e:
+        print(str(e))
+        response = {"message": "Something went wrong!!"}
+        return make_response(jsonify(response)), 500
diff --git a/fact-bounty-flask/api/data-dev.sqlite b/fact-bounty-flask/api/data-dev.sqlite
new file mode 100644
index 0000000000000000000000000000000000000000..08de9227599eb6c8295e880198c571ad3a758069
GIT binary patch
literal 32768
zcmeI4O-v)n701mm48x~4yE|bg`{k8(<w27&_1Rrbw9*<dU<??8fq~r!Rd-c^0b?*W
z46G7`P4=8~lw;0`a!Yc}Ik_f>T#`sRtyan<Qf|rX#xuZVCelh9DH>I4{8jz$dhh+-
zdnLNSvr?I_wM@n~nswF6u+%H5uCCNanM^8`V(_>Nk0(bDyt)141H5)!d;g-hER}of
zmqY1)rg{hdlu8e!e>42~kRASH@Tc(69gct_;0QPZj({WJ2si?cfFtnr5xDrUw|C^;
zy{^x`+fw1aIcc<#$KA>0LcUtaRPz(_g-kq}dC=ci)0yI8wJ=jy&McOznZ?!l`EO_X
z`;J?U=GhjUT+1&{&gPdN5GqIG6YXQujK@0{#X~1*ziqA$EG-uo^2-~UQeh+Wpr(`S
zrpwEP;>=<^_MkJzXlA)EU05zGP8L=&@mM16AHA5p(>wC!n_Zu6C;mr`dfgnfIxoF9
zqUa2MO@Ty>gBDo&Izu_WouuN2#UzwC$oSE7BZ$k;UzmymRX0!5Ao1vpJRPdz<I_g-
zKl5bjs<!|1GJ>DEPAf?N6qG}?xKN0b*g8ABvLbMjvu0!eYRtPFdtPo=7mJnELgqnY
zbF_an+jD2+JNLR$wF7M)A0O?*jpmkWw;IXktvK8*5<lO$=;`hq86EAqC?t5AP^WuF
zZ?)k0;P~dJ+c%KiJojJHzuT%^L7>kM0V-~^t9Rt#!>-Tz5<k?wsfV>_>%=sVYmI|v
zKHj?F$ulQoS2~w%9klCDZ8Lf^Z<FQ4mFjXHx+wF^3|ohLQRW8aYx4oz4{zNWsXTlU
z+-D~v&kS_KGLzif55b$R^xsqP&mE3{Bj5-)0*-(q;0QPZj({WJ2si?cfFtmd5E$;d
z^#P_j^c7P|1)?9~a`*o)Npf8nj({WJ2si?cfFs}tI0BA<Bj5-)0**k3K)U;bzU%k@
z@$diN>;F|M{m1k#(x0b4NwakS@Lz_1J^bTgJN)7B>qB1*{dVXlLx)2%L+=d!WAJx_
zKN~zBtPDOF_}9Q627W&9qk)eH=s;ippJ57jI0BA<Bj5-)0*-(q;0QPZ|0@F7_qtQ7
zCe+0DghY{rCmsd@1(8pXls?gzN{XW}n*pJchG@?tND0q?P%<PVFGNCejFs?l$XWJX
z5QZM}nes!VjEE4op+o@_8X4=ez+fquC$jIvGYTUZ*Bpf`3=k(FN6Hg$ohLzJC1ZY+
zeH(;6<6eLnMO5Jsajg|nffdL^Hj;*OjeVSb3xw7ZER})Nkw+2do`*tBHS)Ojh!TNP
zCd$4E!oasya4<-Hk0CBRFs-;jBD8`;q0*e&?6*KDRj7mvEMi^=!YC9dlmeb2pHVH9
z^)$=gkA;FWBeX<XMLyuM7zL67Ov|k{+=RYQvu}XVGcxd~43G^CMx2ol$xwkOoCxDf
zpBf%!M?gq0ga-bg(9<A{{0PY)@(}n*bzn5+MrOYWLK|=iAS^OISmlC6C{U3=QZp>6
z^$GQ8_8tf|(Di(!ku;1WuBd`Ih(;`mm<cQnOk}dJgODpLg=G+e21jA`07)i1q$3?*
z-wQZE%Dx6dFX99%Ymj9cio%cxDH&;`rJx4F3?s&}uYwR88e&U41dq5Vz*~R^*J);m
zu%6+8&3*%f(i*Np!I2K7hN7TQX4d*B3{|AGU=%Z(eFcPp^dc5AI4vXa5r<HjmH~PS
zm#YZN5PVF7kiZ;(!=i{vsGhib74<Fh7^lqlXrMfj9R^_-8bC!PGFT9Z+Q(p80t8|L
zW58R9Rdxu3%HY8I3Zu{(Fb(rSg+nn($X1+e9Z;Gb1fhZAqeL@ALkqwu2Mxne6kg={
z42b%~W(PnRL9v?%3WumTg>iWV5hloT=o*EIG&aokgHT4;V^#rc5&=hTC<=vCC?YJ7
zk+PA18T&wpEd-$hXf7WElput{ky0WSP$r2V31MyaE(irtp%KI&rj<b44}i5F5$Ku7
zSAs#~7@F;kX9WL*0*wlwuAw6dRF9>IzzQI_!ORa?_6`UGfsHi~u#gzK(1ZY73WK6R
z8{<<B9^h;b2&2dl=|esuifz0A_z21l%1lelEu>9YoV^V~1<R1sKo~j`fLjICW0RhN
zSUgW)Pta_4JR>JY`d}JbBB>q^1%|^a5Ge}n5Wy*(y_NU~q{4_J8^jf^p~FJ!2S|m?
zaA+nb1C#9nAy#1!(Rek3hJ)&fodEZs&2gXwPzbDz@Bat-w^HdJ41YPShyFISGx){e
z!N3;-JN<u!VRtwJj({WJ2si?cfFs}tI09dVz(s1HFLnQ-2j%aNC|$1|i1%suKB4a~
zl;$RPr_a_O^XYQVOw63m(Z^@y%IwnSVQUSS7J|ym#2J>wnFh_#v$2Dvlb}+%oZ+M7
zB?sU6rkG*`w;I&X5IWBMaT4T$fWS}bO;d+2wkN`i?*7CDk-vXW>21Be-EpB@o5kB?
z_cZ5|?d@r+>$_B~<#tNNHeXt;dbLGWZj{a&Vkz?dU_L*$A|J0EDP3!wsc=WNKH84I
zTKyb1__zpiKHQy4@6wI<JI)uWzP{9(2?+3_)`CeoZp=Nl+l^UTAsZ*Vx#gomgOL1M
zxxHIB^mliT=GyBM?c({yBcbPKttm|`G*^#~S6~XjP|JNa7w+E71@CyUZkkOm^dIGR
z&(@}j>$9|YaJbhFqOg6ur0b_Ui#vz43Y$EgZ(%Vxzo=DMsh=fm`3rLa>5w^rh3Bzr
zx%jmAi3h3Vde-s4ukxsPI92s_9`Sa&*j$*~m?@X*k9Uuo^Gl0fqjA7QsdRLBI;Bre
zA5By?m-D6a<qR+M0FM)r^8$~Fjt8IKxoMnsj2}%*>m7aAI<bXft-KV{-7yiAC)C8$
zUVbmwTdl9?=2Wp+U9;P#D`au4um`9T<Mc(wsT}7dCpd=fpv&+-?Mc%AFn|9tazZ-R
zNv$}&iBC2*_{{9);lW;IZ*y)vw|Y9gzke)bA)MUYQ}re9bmq+8*_`CnM>e<T#n#F5
zjgxT>#{HZS0cY~k`1fx2rQS|z?}_mz)=kxx%SC^8a;H|T&**BN?Z`u;7pjq|oS(Gz
z!qw&4+J?%_uC(%4RZ8vM@+1@=H1dnhd*htuFo%tzj`@o_-AV9y2tJ?mPo@)mbQU!D
z+QR0Mm~Hdsd@*Oj^)chK2~`x!`TeLgL-KomZnLo+E##M)=Y>tyR&o1Ww%~!<R!zd=
zPQ2NOB16bH!{eOiB=!Y<;>atv`cmkkXY$IC3FzcxnvO@0T8+i|YW;l8luwRxb-h0|
zH?~mr+nWvVa158$_D)S{eW_CNWOE-E%bS(;wd%D+;hIO!tPP1vl#f%E<3du8U#8~!
zU45yMiyo+p%k{D2#>PQ;r92jHpX8?=kqvLmw0Fr-nQm7@zA#oTFYZjXxL=u^+bf-K
z&MmCT#f^#D<#P9&sz^)|KF(+^5FTudUhByC@Bas2t0_&=Z=`>p{0+cQ(ue7f)6?nh
z^p{^{sdrjOz!7i+905nb5pV<?0Y|_Qa0DCyN8taO!0X+pE7{TzSNF~XZ*&V7e6rDS
zwb$2wslK`U*7tOZYa3#BpANFCd3&D@UfVmm^IG@A#Di;lDm_ny*9LFD)1ANO)wRur
zWc#E0-R{ZfpT69Sxb<rH!@eZaD~ICW|HFU!?+!=65pV<?0Y|_Qa0DCyN5Bzq1RMcJ
qz!7+f3Ap?JmpHjDG)KS@a0DCyN5Bzq1RMcJz!7i+905n5L*U<j8Mf&F

literal 0
HcmV?d00001

diff --git a/fact-bounty-flask/api/helpers.py b/fact-bounty-flask/api/helpers.py
index 96c0d0d3..9778d726 100644
--- a/fact-bounty-flask/api/helpers.py
+++ b/fact-bounty-flask/api/helpers.py
@@ -11,8 +11,11 @@ def send_async_email(app, msg):
 
 def send_email(to, subject, body):
     app = current_app._get_current_object()
-    msg = Message(app.config['FACTBOUNTY_MAIL_SUBJECT_PREFIX'] + subject,
-                  sender=app.config['FACTBOUNTY_MAIL_SENDER'], recipients=[to])
+    msg = Message(
+        app.config["FACTBOUNTY_MAIL_SUBJECT_PREFIX"] + subject,
+        sender=app.config["FACTBOUNTY_MAIL_SENDER"],
+        recipients=[to],
+    )
     msg.body = body
     thr = Thread(target=send_async_email, args=[app, msg])
     thr.start()
diff --git a/fact-bounty-flask/api/stories/controller.py b/fact-bounty-flask/api/stories/controller.py
index 5855eb05..05bbd45f 100644
--- a/fact-bounty-flask/api/stories/controller.py
+++ b/fact-bounty-flask/api/stories/controller.py
@@ -4,41 +4,49 @@
 from elasticsearch.helpers import scan
 from .model import Vote, Comment
 from flasgger import swag_from
+from ..user import model
 
 
 class AllStories(MethodView):
     """
-    Retrieve stories
+    Retrieve stories only for admin
 
     :return: JSON object with all stories and HTTP status code 200.
     """
 
+    @jwt_required
     @swag_from("../../docs/stories/get_all.yml")
     def get(self):
+        user_id = get_jwt_identity()
+        user = model.User.find_by_user_id(user_id)
 
-        es_index = current_app.config["ES_INDEX"]
-        es = current_app.elasticsearch
+        if user.role == "admin":
+            es_index = current_app.config["ES_INDEX"]
+            es = current_app.elasticsearch
 
-        doc = {
-            "sort": [{"date": {"order": "desc"}}],
-            "query": {"match_all": {}},
-        }
-        stories = {}
-        try:
-            for story in scan(es, doc, index=es_index, doc_type="story"):
-                PID = story["_id"]
-                source = story["_source"]
-                stories[PID] = source
-        except Exception as e:
-            # An error occured, so return a string message containing error
-            response = {"message": str(e)}
-            return make_response(jsonify(response)), 500
+            doc = {
+                "sort": [{"date": {"order": "desc"}}],
+                "query": {"match_all": {}},
+            }
+            stories = {}
+            try:
+                for story in scan(es, doc, index=es_index, doc_type="story"):
+                    PID = story["_id"]
+                    source = story["_source"]
+                    stories[PID] = source
+            except Exception as e:
+                # An error occured, so return a string message containing error
+                response = {"message": str(e)}
+                return make_response(jsonify(response)), 500
 
-        response = {
-            "message": "Stories successfully fetched",
-            "stories": stories,
-        }
-        return make_response(jsonify(response)), 200
+            response = {
+                "message": "Stories successfully fetched",
+                "stories": stories,
+            }
+            return make_response(jsonify(response)), 200
+        else:
+            response = {"message": "only for admins"}
+            return make_response(jsonify(response)), 400
 
 
 class GetRange(MethodView):
diff --git a/fact-bounty-flask/api/user/model.py b/fact-bounty-flask/api/user/model.py
old mode 100644
new mode 100755
index 8fb28dc7..623a9315
--- a/fact-bounty-flask/api/user/model.py
+++ b/fact-bounty-flask/api/user/model.py
@@ -22,8 +22,9 @@ class User(Model):
     date = Column(db.DateTime, default=datetime.now())
     votes = db.relationship("Vote", backref=db.backref("user"))
     type = Column(db.String(50), default="remote")
+    role = Column(db.String(10), default="user")
 
-    def __init__(self, name, email, password, _type="remote"):
+    def __init__(self, id, name, email, password, role="user", _type="remote"):
         """
         Initializes the user instance
         """
@@ -32,6 +33,7 @@ def __init__(self, name, email, password, _type="remote"):
         if password:
             self.password = Bcrypt().generate_password_hash(password).decode()
         self.type = _type
+        self.role = role
 
     def __repr__(self):
         """
@@ -45,13 +47,22 @@ def to_json(self):
 
         :return: user JSON object
         """
-        user_json = {"name": self.name, "email": self.email, "date": self.date}
+        user_json = {
+            "name": self.name,
+            "email": self.email,
+            "date": self.date,
+            "role": self.role,
+        }
         return user_json
 
     @classmethod
     def find_by_email(cls, email):
         return cls.query.filter_by(email=email).first()
 
+    @classmethod
+    def find_by_user_id(cls, id):
+        return cls.query.filter_by(id=id).first()
+
     def verify_password(self, password):
         """
         Verify the password
diff --git a/fact-bounty-flask/migrations/versions/02db7a3aaa95_.py b/fact-bounty-flask/migrations/versions/02db7a3aaa95_.py
new file mode 100644
index 00000000..a10bf7ff
--- /dev/null
+++ b/fact-bounty-flask/migrations/versions/02db7a3aaa95_.py
@@ -0,0 +1,66 @@
+"""empty message
+
+Revision ID: 02db7a3aaa95
+Revises: 78d18dc2789d
+Create Date: 2020-01-25 11:13:21.447023
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = "02db7a3aaa95"
+down_revision = "78d18dc2789d"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.create_table(
+        "revoked_tokens",
+        sa.Column("id", sa.Integer(), nullable=False),
+        sa.Column("jti", sa.String(length=120), nullable=True),
+        sa.PrimaryKeyConstraint("id"),
+    )
+    op.create_table(
+        "user",
+        sa.Column("id", sa.Integer(), nullable=False),
+        sa.Column("name", sa.String(length=80), nullable=False),
+        sa.Column("password", sa.String(length=128), nullable=True),
+        sa.Column("email", sa.String(length=100), nullable=False),
+        sa.Column("date", sa.DateTime(), nullable=True),
+        sa.Column("type", sa.String(length=50), nullable=True),
+        sa.Column("role", sa.String(length=60), nullable=True),
+        sa.PrimaryKeyConstraint("id"),
+        sa.UniqueConstraint("email"),
+    )
+    op.create_table(
+        "comment",
+        sa.Column("id", sa.Integer(), nullable=False),
+        sa.Column("story_id", sa.String(length=128), nullable=True),
+        sa.Column("user_id", sa.Integer(), nullable=True),
+        sa.Column("content", sa.Integer(), nullable=True),
+        sa.ForeignKeyConstraint(["user_id"], ["user.id"],),
+        sa.PrimaryKeyConstraint("id"),
+    )
+    op.create_table(
+        "vote",
+        sa.Column("id", sa.Integer(), nullable=False),
+        sa.Column("story_id", sa.String(length=128), nullable=True),
+        sa.Column("user_id", sa.Integer(), nullable=True),
+        sa.Column("value", sa.Integer(), nullable=True),
+        sa.ForeignKeyConstraint(["user_id"], ["user.id"],),
+        sa.PrimaryKeyConstraint("id"),
+    )
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_table("vote")
+    op.drop_table("comment")
+    op.drop_table("user")
+    op.drop_table("revoked_tokens")
+    # ### end Alembic commands ###
diff --git a/fact-bounty-flask/migrations/versions/78d18dc2789d_.py b/fact-bounty-flask/migrations/versions/78d18dc2789d_.py
new file mode 100644
index 00000000..1d7f5203
--- /dev/null
+++ b/fact-bounty-flask/migrations/versions/78d18dc2789d_.py
@@ -0,0 +1,58 @@
+"""empty message
+
+Revision ID: 78d18dc2789d
+Revises: d2fc5d7ff47e
+Create Date: 2020-01-25 10:50:19.720464
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = "78d18dc2789d"
+down_revision = "d2fc5d7ff47e"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.create_table(
+        "revoked_tokens",
+        sa.Column("id", sa.Integer(), nullable=False),
+        sa.Column("jti", sa.String(length=120), nullable=True),
+        sa.PrimaryKeyConstraint("id"),
+    )
+    op.create_table(
+        "comment",
+        sa.Column("id", sa.Integer(), nullable=False),
+        sa.Column("story_id", sa.String(length=128), nullable=True),
+        sa.Column("user_id", sa.Integer(), nullable=True),
+        sa.Column("content", sa.Integer(), nullable=True),
+        sa.ForeignKeyConstraint(["user_id"], ["user.id"],),
+        sa.PrimaryKeyConstraint("id"),
+    )
+    op.add_column(
+        "user", sa.Column("role", sa.String(length=60), nullable=True)
+    )
+    op.alter_column(
+        "user", "password", existing_type=sa.VARCHAR(length=128), nullable=True
+    )
+    op.create_unique_constraint(None, "user", ["email"])
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_constraint(None, "user", type_="unique")
+    op.alter_column(
+        "user",
+        "password",
+        existing_type=sa.VARCHAR(length=128),
+        nullable=False,
+    )
+    op.drop_column("user", "role")
+    op.drop_table("comment")
+    op.drop_table("revoked_tokens")
+    # ### end Alembic commands ###
diff --git a/fact-bounty-flask/requirements.txt b/fact-bounty-flask/requirements.txt
index 08ad1896..734b3fff 100644
--- a/fact-bounty-flask/requirements.txt
+++ b/fact-bounty-flask/requirements.txt
@@ -43,7 +43,7 @@ SQLAlchemy==1.2.18
 tornado==6.0
 tweepy==3.8.0
 typed-ast==1.2.0
-urllib3==1.24.1
+urllib3==1.23.0
 Werkzeug==0.14.1
 wrapt==1.11.1
 WTForms==2.2.1

From f1e4d5436b43fdb482383c79bbc4853af17eb245 Mon Sep 17 00:00:00 2001
From: hv7214 <hverma1@cs.iitr.ac.in>
Date: Fri, 24 Jan 2020 11:41:26 +0530
Subject: [PATCH 02/11] feat(server/user): added forgot password functionality

---
 README.md                                |  10 ++
 fact-bounty-flask/api/user/controller.py | 139 ++++++++++++++++++++++-
 fact-bounty-flask/api/user/model.py      |  27 ++++-
 fact-bounty-flask/api/user/views.py      |  17 +++
 4 files changed, 188 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 63596016..d92bfd24 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,15 @@ Run npm install in fact-bounty-client folder.
         export ELASTIC_SEARCH_URL=""
         export ELASTIC_SEARCH_USERNAME=""
         export ELASTIC_SEARCH_PASSWORD=""
+	
+        export TZ="Asia/Colombo"
+        
+        export MAIL_USERNAME=""
+        export MAIL_PASSWORD=""
+        export FACTBOUNTY_ADMIN=""
+        export MAIL_PORT="587"
+        export MAIL_USE_TLS="true"
+        export MAIL_SERVER="smtp.gmail.com"
 
         export ADMIN_USERNAME = "admin"
         export ADMIN_PASSWORD = "password"
@@ -216,3 +225,4 @@ And use [localhost:3000](https://) to browse.
 
 ## License
 [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fscorelab%2Ffact-Bounty.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fscorelab%2Ffact-Bounty?ref=badge_large)
+
diff --git a/fact-bounty-flask/api/user/controller.py b/fact-bounty-flask/api/user/controller.py
index 0f1485e6..421e2185 100644
--- a/fact-bounty-flask/api/user/controller.py
+++ b/fact-bounty-flask/api/user/controller.py
@@ -1,5 +1,5 @@
 from flask.views import MethodView
-from flask import make_response, request, jsonify
+from flask import make_response, request, jsonify, current_app
 from flask_jwt_extended import (
     create_access_token,
     create_refresh_token,
@@ -10,6 +10,9 @@
 )
 from .model import User, RevokedToken
 from flasgger import swag_from
+import smtplib
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
 
 
 class Register(MethodView):
@@ -50,7 +53,8 @@ def post(self):
         try:
             user = User(email=email, password=password, name=name)
             user.save()
-        except Exception:
+        except Exception as err:
+            print("Error occured: ", err)
             response = {"message": "Something went wrong!!"}
             return make_response(jsonify(response)), 500
 
@@ -72,7 +76,8 @@ def post(self):
         try:
             email = data["email"]
             password = data["password"]
-        except Exception:
+        except Exception as err:
+            print("Error occured: ", err)
             response = {"message": "Please provide all the required fields."}
             return make_response(jsonify(response)), 404
 
@@ -109,6 +114,129 @@ def post(self):
         return make_response(jsonify(response)), 200
 
 
+class ForgotPassword(MethodView):
+    """This class sends a reset password mail."""
+
+    def post(self):
+        """Handle POST request for this view. Url --> /api/users/forgot_password"""
+        # getting JSON data from request
+        post_data = request.get_json(silent=True)
+
+        try:
+            email = post_data["email"]
+        except Exception:
+            response = {"message": "Please provide email."}
+            return make_response(jsonify(response)), 404
+
+        # Querying the database with requested email
+        user = User.find_by_email(email)
+        if user:
+            verification_token = user.verification_token
+            # Body of the email
+            mail_content = (
+                """Hi,
+
+            You are receiving this because you have requested to reset password for your account.
+
+            Here is you verification token: """
+                + verification_token
+            )
+            # The email addresses and password
+            sender_address = current_app.config["MAIL_USERNAME"]
+            sender_pass = current_app.config["MAIL_PASSWORD"]
+            receiver_address = user.email
+            # Setup the MIME
+            message = MIMEMultipart()
+            message["From"] = sender_address
+            message["To"] = receiver_address
+            message["Subject"] = "Password reset"
+            # The body and the attachments for the mail
+            message.attach(MIMEText(mail_content, "plain"))
+            # Create SMTP session for sending the mail
+            session = smtplib.SMTP(
+                current_app.config["MAIL_SERVER"],
+                current_app.config["MAIL_PORT"],
+            )  # use gmail with port
+            session.starttls()  # enable security
+            session.login(
+                sender_address, sender_pass
+            )  # login with mail_id and password
+            text = message.as_string()
+            session.sendmail(sender_address, receiver_address, text)
+            session.quit()
+            print("Password reset email sent successfully")
+
+            response = {
+                "message": "Verification token has been sent to you on your registered email"
+            }
+
+            # return a response notifying the user that a password reset mail has
+            # been sent to registered email
+            return make_response(jsonify(response)), 200
+        else:
+            response = {"message": "Email not registered"}
+            return make_response(jsonify(response)), 202
+
+
+class AuthVerificationToken(MethodView):
+    """This class verifies token which is being used to reset the password"""
+
+    def post(self):
+        data = request.get_json(silent=True)
+        try:
+            verification_token = data["verification_token"]
+        except Exception:
+            response = {"message": "Please provide verification token."}
+            return make_response(jsonify(response)), 404
+
+        # since token is unique, find user by its verification token
+        # if it exist it means input token is correct
+        user = User.find_by_verification_token(verification_token)
+
+        if user:
+            response = {"message": "Auth success"}
+            return make_response(jsonify(response)), 200
+        else:
+            response = {"message": "Incorrect, please check again"}
+            return make_response(jsonify(response)), 202
+
+
+class ResetPassword(MethodView):
+    """This class is used to change the password"""
+
+    def post(self):
+
+        data = request.get_json(silent=True)
+        try:
+            verification_token = data["verificationToken"]
+            new_password = data["password"]
+            new_password_confirm = data["password2"]
+        except Exception:
+            response = {"message": "Please provide all required fields."}
+            return make_response(jsonify(response)), 202
+
+        # find user by token
+        user = User.find_by_verification_token(verification_token)
+
+        # if token is correct
+        if user:
+            # new password and confirm has to be equal
+            if new_password != new_password_confirm:
+                response = {
+                    "message": "New password and confirm password does not match"
+                }
+                return make_response(jsonify(response)), 202
+            # overwrite old password with new password
+            else:
+                user.password = user.generate_password_hash(new_password)
+                user.save()
+                response = {"message": "Password successfully changed"}
+                return make_response(jsonify(response)), 200
+        else:
+            response = {"message": "Unauthorized"}
+            return make_response(jsonify(response)), 401
+
+
 class Auth(MethodView):
     """This class-based view handles user register and access token generation \
     via 3rd sources like facebook, google"""
@@ -218,6 +346,11 @@ def post(self):
 userController = {
     "register": Register.as_view("register"),
     "login": Login.as_view("login"),
+    "forgot_password": ForgotPassword.as_view("forgot_password"),
+    "auth_verification_token": AuthVerificationToken.as_view(
+        "auth_verification_token"
+    ),
+    "reset_password": ResetPassword.as_view("reset_password"),
     "auth": Auth.as_view("auth"),
     "logout_access": LogoutAccess.as_view("logout_access"),
     "logout_refresh": LogoutRefresh.as_view("logout_refresh"),
diff --git a/fact-bounty-flask/api/user/model.py b/fact-bounty-flask/api/user/model.py
index 623a9315..76c0a19c 100755
--- a/fact-bounty-flask/api/user/model.py
+++ b/fact-bounty-flask/api/user/model.py
@@ -1,6 +1,7 @@
 from datetime import datetime
 from flask import current_app
 from flask_bcrypt import Bcrypt
+from uuid import uuid4
 
 # from itsdangerous import (
 #     TimedJSONWebSignatureSerializer as Serializer,
@@ -18,6 +19,7 @@ class User(Model):
     id = Column(db.Integer, primary_key=True)
     name = Column(db.String(80), nullable=False)
     password = Column(db.String(128))
+    verification_token = Column(db.String(128), nullable=False, unique=True)
     email = Column(db.String(100), nullable=False, unique=True)
     date = Column(db.DateTime, default=datetime.now())
     votes = db.relationship("Vote", backref=db.backref("user"))
@@ -30,8 +32,9 @@ def __init__(self, id, name, email, password, role="user", _type="remote"):
         """
         self.name = name
         self.email = email
+        self.verification_token = User.generate_token()
         if password:
-            self.password = Bcrypt().generate_password_hash(password).decode()
+            self.password = User.generate_password_hash(password)
         self.type = _type
         self.role = role
 
@@ -63,6 +66,26 @@ def find_by_email(cls, email):
     def find_by_user_id(cls, id):
         return cls.query.filter_by(id=id).first()
 
+    @classmethod
+    def find_by_verification_token(cls, verification_token):
+        return cls.query.filter_by(
+            verification_token=verification_token
+        ).first()
+
+    @staticmethod
+    def generate_token():
+        """
+        Returns a random token
+        """
+        return uuid4().hex
+
+    @staticmethod
+    def generate_password_hash(password):
+        """
+        Returns hash of password
+        """
+        return Bcrypt().generate_password_hash(password).decode()
+
     def verify_password(self, password):
         """
         Verify the password
@@ -82,7 +105,7 @@ def save(self):
 
 class RevokedToken(Model):
     """
-    This model holds information about revoked tokens, users who have looged out
+    This model holds information about revoked tokens, users who have logged out
     """
 
     __tablename__ = "revoked_tokens"
diff --git a/fact-bounty-flask/api/user/views.py b/fact-bounty-flask/api/user/views.py
index ea9213da..82a45048 100644
--- a/fact-bounty-flask/api/user/views.py
+++ b/fact-bounty-flask/api/user/views.py
@@ -10,6 +10,23 @@
 userprint.add_url_rule(
     "/register", view_func=userController["register"], methods=["POST"]
 )
+userprint.add_url_rule(
+    "/forgot_password",
+    view_func=userController["forgot_password"],
+    methods=["POST"],
+)
+
+userprint.add_url_rule(
+    "/auth_verification_token",
+    view_func=userController["auth_verification_token"],
+    methods=["POST"],
+)
+
+userprint.add_url_rule(
+    "/reset_password",
+    view_func=userController["reset_password"],
+    methods=["POST"],
+)
 
 userprint.add_url_rule(
     "/oauth", view_func=userController["auth"], methods=["POST"]

From 1329797b9949802d79f8fffa206fe506621f8cd1 Mon Sep 17 00:00:00 2001
From: lukasz-zbrzeski <lukasz.zbrzeski1@gmail.com>
Date: Sat, 18 Jan 2020 15:54:25 +0100
Subject: [PATCH 03/11] Update README.md

---
 README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)

diff --git a/README.md b/README.md
index d92bfd24..479cab1e 100644
--- a/README.md
+++ b/README.md
@@ -216,6 +216,66 @@ And use [localhost:3000](https://) to browse.
 
 *   #### Once build completes, run `docker-compose up`
 
+## Setting up an OAuth Daemon
+
+## Step 1: Pre-requisite
+
+### Technologies required
+
+*   **[Node.js](http://nodejs.org/)** 
+*   **[Redis](http://redis.io/download)**
+
+You should also install the `grunt-cli` npm package with the following command: 
+```sh
+$ (sudo) npm install -g grunt-cli
+```
+
+## Step 2: Install OAuthD
+You should install OAuthD from npm, by executing the following command:
+```sh
+$ (sudo) npm install -g oauthd
+```
+
+Then check the correctness of the OAuthD installation with the following command:
+```sh
+$ oauthd -v
+```
+
+## Step 3: Create an instance
+
+Go into a folder where you want to create your oauthd instance and run the following command:
+```sh
+$ oauthd init
+```
+
+Follow the prompt instructions. It will guide you in the instance creation. You need to answer 'Y' or just press enter when the prompt ask you:
+```sh
+oauthd> Do you want to install default plugins?  (Y|n)>  (Y)
+```
+
+## Step 4: Run instance
+To run the instance, just run the following command:
+```sh
+$ cd myinstance && oauthd start
+```
+
+You should see something like this in your shell:
+```sh
+Initializing plugins engine
+Loading auth
+Loading request
+Loading slashme
+Loading statistics
+Loading front
+oauthd start server
+oauthd listening at http://0.0.0.0:6284 for http://localhost:6284
+Server is ready (load time: 0.9s)
+```
+
+Then go to browser and type http://localhost:6284/
+
+Learn more about the OAuthD [configuration](https://github.com/oauth-io/oauthd/wiki/Configuration) and the [command line features](https://github.com/oauth-io/oauthd/wiki/Command-Line-Interface).
+
 # How to Contribute
 
 - First fork the repository and clone it.

From fb8713285b273ea77677311be824b142c55a6460 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz=20Zbrzeski?= <lukasz.zbrzeski1@gmail.com>
Date: Thu, 23 Jan 2020 17:24:01 +0100
Subject: [PATCH 04/11] Update README.md

---
 README.md | 58 +------------------------------------------------------
 1 file changed, 1 insertion(+), 57 deletions(-)

diff --git a/README.md b/README.md
index 479cab1e..d69415bb 100644
--- a/README.md
+++ b/README.md
@@ -218,63 +218,7 @@ And use [localhost:3000](https://) to browse.
 
 ## Setting up an OAuth Daemon
 
-## Step 1: Pre-requisite
-
-### Technologies required
-
-*   **[Node.js](http://nodejs.org/)** 
-*   **[Redis](http://redis.io/download)**
-
-You should also install the `grunt-cli` npm package with the following command: 
-```sh
-$ (sudo) npm install -g grunt-cli
-```
-
-## Step 2: Install OAuthD
-You should install OAuthD from npm, by executing the following command:
-```sh
-$ (sudo) npm install -g oauthd
-```
-
-Then check the correctness of the OAuthD installation with the following command:
-```sh
-$ oauthd -v
-```
-
-## Step 3: Create an instance
-
-Go into a folder where you want to create your oauthd instance and run the following command:
-```sh
-$ oauthd init
-```
-
-Follow the prompt instructions. It will guide you in the instance creation. You need to answer 'Y' or just press enter when the prompt ask you:
-```sh
-oauthd> Do you want to install default plugins?  (Y|n)>  (Y)
-```
-
-## Step 4: Run instance
-To run the instance, just run the following command:
-```sh
-$ cd myinstance && oauthd start
-```
-
-You should see something like this in your shell:
-```sh
-Initializing plugins engine
-Loading auth
-Loading request
-Loading slashme
-Loading statistics
-Loading front
-oauthd start server
-oauthd listening at http://0.0.0.0:6284 for http://localhost:6284
-Server is ready (load time: 0.9s)
-```
-
-Then go to browser and type http://localhost:6284/
-
-Learn more about the OAuthD [configuration](https://github.com/oauth-io/oauthd/wiki/Configuration) and the [command line features](https://github.com/oauth-io/oauthd/wiki/Command-Line-Interface).
+[How to setup OAuth Daemon](OAuthdSetup.md)
 
 # How to Contribute
 

From 8fe02647231cb6ddb67c34d9c015dcb67d641212 Mon Sep 17 00:00:00 2001
From: RAJPRAKASH <42652941+rajprakash00@users.noreply.github.com>
Date: Wed, 12 Feb 2020 22:34:51 +0530
Subject: [PATCH 05/11] Update README.md

---
 README.md | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index d69415bb..a8f62c54 100644
--- a/README.md
+++ b/README.md
@@ -121,12 +121,12 @@ Run npm install in fact-bounty-client folder.
 
 ### How to install Elasticsearch and start elasticsearch server
 
-*   #### Elasticsearch v6.7.0 can be installed as follows:
+*   #### Elasticsearch v7.6.0 can be installed as follows:
     ```
-        (venv)$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.7.0.deb
-        (venv)$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.7.0.deb.sha512
-        (venv)$ shasum -a 512 -c elasticsearch-6.7.0.deb.sha512
-        (venv)$ sudo dpkg -i elasticsearch-6.7.0.deb
+        (venv)$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.6.0.deb
+        (venv)$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.6.0.deb.sha512
+        (venv)$ shasum -a 512 -c elasticsearch-7.6.0.deb.sha512
+        (venv)$ sudo dpkg -i elasticsearch-7.6.0.deb
 
     ```
 

From d7045d1b15f4d6b9f03e0576b4ad14773742fdea Mon Sep 17 00:00:00 2001
From: hv7214 <hverma1@cs.iitr.ac.in>
Date: Mon, 20 Jan 2020 15:13:00 +0530
Subject: [PATCH 06/11] feat(client/ForgotPassword): Made different components
 and routes

---
 fact-bounty-client/src/AppRouter.js           |   9 +-
 .../pages/ForgotPassword/ForgotPassword.jsx   | 255 ++++++++++++++++
 .../ForgotPassword/ForgotPassword.style.js    |  32 ++
 .../src/pages/ForgotPassword/index.js         |   2 +
 fact-bounty-client/src/pages/Login/Login.jsx  |   8 +-
 .../src/pages/ResetPassword/ResetPassword.jsx | 279 ++++++++++++++++++
 .../ResetPassword/ResetPassword.style.js      |  32 ++
 .../src/pages/ResetPassword/index.js          |   2 +
 .../src/redux/actions/actionTypes.js          |   2 +
 .../src/redux/actions/authActions.js          |  95 +++++-
 .../src/redux/actions/successActions.js       |   4 +
 .../src/redux/reducers/index.js               |   2 +
 .../src/redux/reducers/successReducers.js     |  16 +
 .../src/services/AuthService.js               |  28 +-
 14 files changed, 762 insertions(+), 4 deletions(-)
 create mode 100644 fact-bounty-client/src/pages/ForgotPassword/ForgotPassword.jsx
 create mode 100644 fact-bounty-client/src/pages/ForgotPassword/ForgotPassword.style.js
 create mode 100644 fact-bounty-client/src/pages/ForgotPassword/index.js
 create mode 100644 fact-bounty-client/src/pages/ResetPassword/ResetPassword.jsx
 create mode 100644 fact-bounty-client/src/pages/ResetPassword/ResetPassword.style.js
 create mode 100644 fact-bounty-client/src/pages/ResetPassword/index.js
 create mode 100644 fact-bounty-client/src/redux/actions/successActions.js
 create mode 100644 fact-bounty-client/src/redux/reducers/successReducers.js

diff --git a/fact-bounty-client/src/AppRouter.js b/fact-bounty-client/src/AppRouter.js
index dbbff8c5..f2fc5fab 100755
--- a/fact-bounty-client/src/AppRouter.js
+++ b/fact-bounty-client/src/AppRouter.js
@@ -13,7 +13,8 @@ import Dashboard from './pages/Dashboard'
 import Posts from './pages/Posts'
 import PostDetailView from './pages/PostDetailView'
 import Tweets from './pages/Tweets'
-
+import ForgotPassword from './pages/ForgotPassword'
+import ResetPassword from './pages/ResetPassword'
 class AppRouter extends Component {
   render() {
     return (
@@ -23,6 +24,12 @@ class AppRouter extends Component {
           <Route exact path="/" component={Landing} />
           <Route exact path="/register" component={Register} />
           <Route exact path="/login" component={Login} />
+          <Route exact path="/forgotpassword" component={ForgotPassword} />
+          <Route
+            exact
+            path="/resetpassword/:verificationToken"
+            component={ResetPassword}
+          />
           <Route path="/dashboard" component={Dashboard} />
           <Route exact path="/posts" component={Posts} />
           <Route exact path="/tweets" component={Tweets} />
diff --git a/fact-bounty-client/src/pages/ForgotPassword/ForgotPassword.jsx b/fact-bounty-client/src/pages/ForgotPassword/ForgotPassword.jsx
new file mode 100644
index 00000000..52c89977
--- /dev/null
+++ b/fact-bounty-client/src/pages/ForgotPassword/ForgotPassword.jsx
@@ -0,0 +1,255 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import classnames from 'classnames'
+import compose from 'recompose/compose'
+import Avatar from '@material-ui/core/Avatar'
+import Button from '@material-ui/core/Button'
+import CssBaseline from '@material-ui/core/CssBaseline'
+import FormControl from '@material-ui/core/FormControl'
+import Input from '@material-ui/core/Input'
+import InputLabel from '@material-ui/core/InputLabel'
+import LockOutlinedIcon from '@material-ui/icons/LockOutlined'
+import Paper from '@material-ui/core/Paper'
+import Typography from '@material-ui/core/Typography'
+import withStyles from '@material-ui/core/styles/withStyles'
+import Toast from '../../components/Toast'
+import { updateError } from '../../redux/actions/errorActions'
+import { updateSuccess } from '../../redux/actions/successActions'
+import {
+  forgotPassword,
+  authVerificationToken
+} from '../../redux/actions/authActions'
+import styles from './ForgotPassword.style'
+
+class ForgotPassword extends Component {
+  constructor() {
+    super()
+    this.state = {
+      email: '',
+      verificationToken: '',
+      errors: {},
+      success: {},
+      emailValid: false,
+      verificationTokenValid: false,
+      formValid: false,
+      openToast: false
+    }
+  }
+
+  componentDidMount() {
+    // If logged in and user navigates to Register page, should redirect them to dashboard
+    this.props.updateError({})
+    this.props.updateSuccess({})
+    if (this.props.auth.isAuthenticated) {
+      this.props.history.push('/dashboard')
+    }
+  }
+
+  static getDerivedStateFromProps(props, state) {
+    if (props.errors) {
+      const errors = props.errors
+      let openToast = false
+      if (errors.fetch) {
+        openToast = true
+      }
+      return { errors, openToast }
+    }
+    if (props.success) {
+      const success = props.success
+      let openToast = false
+      if (success.fetch) {
+        openToast = true
+      }
+      return { success, openToast }
+    }
+    return null
+  }
+
+  onChange = e => {
+    let { id, value } = e.target
+    this.setState({ [id]: value }, () => {
+      this.validateField(id, value)
+    })
+  }
+
+  validateField = (fieldname, value) => {
+    let { emailValid, verificationTokenValid, errors } = this.state
+
+    emailValid = value.match(/^([\w.%+-]+)@([\w-]+\.)+([\w]{2,})$/i)
+    verificationTokenValid = !!this.state.verificationToken
+    switch (fieldname) {
+      case 'email':
+        errors.email = emailValid ? '' : 'Invalid E-mail'
+        break
+      default:
+        break
+    }
+    this.setState(
+      {
+        errors,
+        emailValid,
+        verificationTokenValid
+      },
+      this.validateForm
+    )
+  }
+
+  validateForm = () => {
+    this.setState({
+      formValid: this.state.emailValid || this.state.verificationToken !== ''
+    })
+  }
+  closeToast = () => {
+    // Remove error from store
+    this.props.updateError({})
+    this.props.updateSuccess({})
+    this.setState({ openToast: false })
+  }
+
+  onSubmit = e => {
+    e.preventDefault()
+    // Remove error from store
+    this.props.updateSuccess({})
+    this.props.updateError({})
+
+    const { email } = this.state
+    if (!this.props.success.message) {
+      this.props.forgotPassword({ email: email })
+    } else {
+      this.props.authVerificationToken(
+        {
+          verification_token: this.state.verificationToken
+        },
+        this.props.history
+      )
+    }
+  }
+
+  render() {
+    const { errors, openToast } = this.state
+    var formInput, buttonName
+    if (!this.props.success.message) {
+      formInput = (
+        <FormControl margin="normal" required fullWidth>
+          <InputLabel htmlFor="email">Email Address</InputLabel>
+          <Input
+            autoComplete="on"
+            onChange={this.onChange}
+            validate
+            value={this.state.email}
+            error={!!errors.email}
+            id="email"
+            type="email"
+            className={classnames('', {
+              invalid: errors.email
+            })}
+          />
+          <Typography component="span" variant="caption" color="error">
+            {errors.email}
+          </Typography>
+        </FormControl>
+      )
+      buttonName = 'Send verification code'
+    } else {
+      formInput = (
+        <FormControl margin="normal" required fullWidth>
+          <InputLabel htmlFor="verificationToken">
+            Verification token
+          </InputLabel>
+          <Input
+            autoComplete="on"
+            onChange={this.onChange}
+            value={this.state.verificationToken}
+            id="verificationToken"
+          />
+        </FormControl>
+      )
+      buttonName = 'Verify token'
+    }
+    return (
+      <main className={this.props.classes.main}>
+        <CssBaseline />
+        <Paper className={this.props.classes.paper}>
+          {errors.fetch ? (
+            <Toast
+              open={openToast}
+              onClose={this.closeToast}
+              message="Something went wrong, Try again later"
+              variant="error"
+            />
+          ) : null}
+          <Avatar className={this.props.classes.avatar}>
+            <LockOutlinedIcon />
+          </Avatar>
+          <Typography component="h1" variant="h5">
+            Forgot Password
+          </Typography>
+
+          <form
+            noValidate
+            onSubmit={this.onSubmit}
+            className={this.props.classes.form}
+          >
+            <Typography component="span" variant="caption" color="error">
+              {typeof this.props.errors === 'object'
+                ? this.props.errors.message
+                : null}
+            </Typography>
+            <Typography
+              component="span"
+              variant="caption"
+              color="primary"
+              align="center"
+            >
+              {typeof this.props.success === 'object'
+                ? this.props.success.message
+                : null}
+            </Typography>
+            {formInput}
+            <Button
+              type="submit"
+              fullWidth
+              variant="contained"
+              color="primary"
+              className={this.props.classes.submit}
+              disabled={!this.state.formValid}
+            >
+              {buttonName}
+            </Button>
+          </form>
+        </Paper>
+      </main>
+    )
+  }
+}
+
+ForgotPassword.propTypes = {
+  forgotPassword: PropTypes.func.isRequired,
+  authVerificationToken: PropTypes.func.isRequired,
+  auth: PropTypes.object.isRequired,
+  errors: PropTypes.object.isRequired,
+  success: PropTypes.object.isRequired,
+  history: PropTypes.object,
+  classes: PropTypes.object,
+  updateError: PropTypes.func.isRequired,
+  updateSuccess: PropTypes.func.isRequired
+}
+
+const mapStateToProps = state => ({
+  auth: state.auth,
+  errors: state.errors,
+  success: state.success
+})
+
+export default compose(
+  withStyles(styles, {
+    name: 'ForgotPassword'
+  }),
+  connect(mapStateToProps, {
+    forgotPassword,
+    authVerificationToken,
+    updateError,
+    updateSuccess
+  })
+)(ForgotPassword)
diff --git a/fact-bounty-client/src/pages/ForgotPassword/ForgotPassword.style.js b/fact-bounty-client/src/pages/ForgotPassword/ForgotPassword.style.js
new file mode 100644
index 00000000..4af949a6
--- /dev/null
+++ b/fact-bounty-client/src/pages/ForgotPassword/ForgotPassword.style.js
@@ -0,0 +1,32 @@
+export default theme => ({
+  main: {
+    width: 'auto',
+    display: 'block', // Fix IE 11 issue.
+    marginLeft: theme.spacing.unit * 3,
+    marginRight: theme.spacing.unit * 3,
+    [theme.breakpoints.up(400 + theme.spacing.unit * 3 * 2)]: {
+      width: 400,
+      marginLeft: 'auto',
+      marginRight: 'auto'
+    }
+  },
+  paper: {
+    marginTop: theme.spacing.unit * 12,
+    display: 'flex',
+    flexDirection: 'column',
+    alignItems: 'center',
+    padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${theme
+      .spacing.unit * 3}px`
+  },
+  avatar: {
+    margin: theme.spacing.unit,
+    backgroundColor: theme.palette.secondary.main
+  },
+  form: {
+    width: '100%', // Fix IE 11 issue.
+    marginTop: theme.spacing.unit
+  },
+  submit: {
+    marginTop: theme.spacing.unit * 3
+  }
+})
diff --git a/fact-bounty-client/src/pages/ForgotPassword/index.js b/fact-bounty-client/src/pages/ForgotPassword/index.js
new file mode 100644
index 00000000..7a92c0ce
--- /dev/null
+++ b/fact-bounty-client/src/pages/ForgotPassword/index.js
@@ -0,0 +1,2 @@
+import ForgotPassword from './ForgotPassword'
+export default ForgotPassword
diff --git a/fact-bounty-client/src/pages/Login/Login.jsx b/fact-bounty-client/src/pages/Login/Login.jsx
index b067d9e6..1efed1b4 100644
--- a/fact-bounty-client/src/pages/Login/Login.jsx
+++ b/fact-bounty-client/src/pages/Login/Login.jsx
@@ -228,7 +228,13 @@ class Login extends Component {
             >
               Login
             </Button>
-
+            <p style={{ textAlign: 'center' }}>
+              {' '}
+              <br />
+              <Link component={RouterLink} to="/forgotpassword">
+                Forgot password ?
+              </Link>
+            </p>
             <div
               style={{
                 width: '100%',
diff --git a/fact-bounty-client/src/pages/ResetPassword/ResetPassword.jsx b/fact-bounty-client/src/pages/ResetPassword/ResetPassword.jsx
new file mode 100644
index 00000000..8c25b4f9
--- /dev/null
+++ b/fact-bounty-client/src/pages/ResetPassword/ResetPassword.jsx
@@ -0,0 +1,279 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import classnames from 'classnames'
+import IconButton from '@material-ui/core/IconButton'
+import InputAdornment from '@material-ui/core/InputAdornment'
+import Visibility from '@material-ui/icons/Visibility'
+import VisibilityOff from '@material-ui/icons/VisibilityOff'
+import compose from 'recompose/compose'
+import Avatar from '@material-ui/core/Avatar'
+import Button from '@material-ui/core/Button'
+import CssBaseline from '@material-ui/core/CssBaseline'
+import FormControl from '@material-ui/core/FormControl'
+import Input from '@material-ui/core/Input'
+import InputLabel from '@material-ui/core/InputLabel'
+import LockOutlinedIcon from '@material-ui/icons/LockOutlined'
+import Paper from '@material-ui/core/Paper'
+import Typography from '@material-ui/core/Typography'
+import withStyles from '@material-ui/core/styles/withStyles'
+import Toast from '../../components/Toast'
+import { updateError } from '../../redux/actions/errorActions'
+import { resetPassword } from '../../redux/actions/authActions'
+import styles from './ResetPassword.style'
+
+class Register extends Component {
+  constructor() {
+    super()
+    this.state = {
+      verificationToken: '',
+      password: '',
+      password2: '',
+      errors: {},
+      showPassword: false,
+      showPassword2: false,
+      passwordValid: false,
+      password2Valid: false,
+      formValid: false,
+      openToast: false
+    }
+  }
+
+  componentDidMount() {
+    this.props.updateError({})
+    // set verification token
+    const {
+      match: { params }
+    } = this.props
+    this.setState({
+      verificationToken: params.verificationToken
+    })
+
+    // If logged in and user navigates to Register page, should redirect them to dashboard
+    if (this.props.auth.isAuthenticated) {
+      this.props.history.push('/dashboard')
+    }
+  }
+
+  static getDerivedStateFromProps(props) {
+    if (props.errors) {
+      const errors = props.errors
+      let openToast = false
+      if (errors.fetch) {
+        openToast = true
+      }
+      return { errors, openToast }
+    }
+    return null
+  }
+
+  onChange = e => {
+    let { id, value } = e.target
+    this.setState({ [id]: value }, () => {
+      this.validateField(id, value)
+    })
+  }
+
+  handleClickShowPassword = () => {
+    this.setState(state => ({ showPassword: !state.showPassword }))
+  }
+
+  handleClickShowPassword2 = () => {
+    this.setState(state => ({ showPassword2: !state.showPassword2 }))
+  }
+
+  validateField = (fieldName, value) => {
+    let { passwordValid, password2Valid, errors } = this.state
+
+    switch (fieldName) {
+      case 'password':
+        passwordValid = value.length >= 8
+        errors.password = passwordValid ? '' : 'Too short!'
+        password2Valid = value === this.state.password2
+        if (password2Valid && passwordValid) {
+          errors.password2 = null
+        }
+        break
+      case 'password2':
+        password2Valid = value === this.state.password
+        errors.password2 = password2Valid ? '' : "Password don't match"
+        break
+      default:
+        break
+    }
+    this.setState(
+      {
+        errors,
+        passwordValid,
+        password2Valid
+      },
+      this.validateForm
+    )
+  }
+
+  validateForm = () => {
+    this.setState({
+      formValid: this.state.passwordValid && this.state.password2Valid
+    })
+  }
+  closeToast = () => {
+    // Remove error from store
+    this.props.updateError({})
+    this.setState({ openToast: false })
+  }
+
+  onSubmit = e => {
+    e.preventDefault()
+    // Remove error from store
+    const { password, password2, verificationToken } = this.state
+
+    if (password === password2) {
+      const data = {
+        password,
+        password2,
+        verificationToken
+      }
+      this.props.resetPassword(data, this.props.history)
+    } else {
+      const passwordError = 'Password dont match'
+      const errors = {
+        password2: password !== password2 ? passwordError : ''
+      }
+      this.props.updateError(errors)
+    }
+  }
+
+  render() {
+    const { errors, openToast } = this.state
+    return (
+      <main className={this.props.classes.main}>
+        <CssBaseline />
+        <Paper className={this.props.classes.paper}>
+          {errors.fetch ? (
+            <Toast
+              open={openToast}
+              onClose={this.closeToast}
+              message="Something went wrong, Try again later"
+              variant="error"
+            />
+          ) : null}
+          <Avatar className={this.props.classes.avatar}>
+            <LockOutlinedIcon />
+          </Avatar>
+          <Typography component="h1" variant="h5">
+            Reset Password
+          </Typography>
+
+          <form
+            noValidate
+            onSubmit={this.onSubmit}
+            className={this.props.classes.form}
+          >
+            <Typography component="span" variant="caption" color="error">
+              {typeof this.props.errors === 'object'
+                ? this.props.errors.message
+                : null}
+            </Typography>
+
+            <FormControl margin="normal" required fullWidth>
+              <InputLabel htmlFor="password">New Password</InputLabel>
+              <Input
+                autoComplete="on"
+                onChange={this.onChange}
+                value={this.state.password}
+                error={!!errors.password}
+                id="password"
+                type={this.state.showPassword ? 'text' : 'password'}
+                className={classnames('', {
+                  invalid: errors.password
+                })}
+                endAdornment={
+                  <InputAdornment position="end">
+                    <IconButton
+                      aria-label="Toggle password visibility"
+                      onClick={this.handleClickShowPassword}
+                    >
+                      {this.state.showPassword ? (
+                        <Visibility />
+                      ) : (
+                        <VisibilityOff />
+                      )}
+                    </IconButton>
+                  </InputAdornment>
+                }
+              />
+              <Typography component="span" variant="caption" color="error">
+                {errors.password}
+              </Typography>
+            </FormControl>
+
+            <FormControl margin="normal" required fullWidth>
+              <InputLabel htmlFor="password2">Confirm New Password</InputLabel>
+              <Input
+                autoComplete="on"
+                onChange={this.onChange}
+                value={this.state.password2}
+                error={!!errors.password2}
+                id="password2"
+                type={this.state.showPassword2 ? 'text' : 'password'}
+                className={classnames('', {
+                  invalid: errors.password2
+                })}
+                endAdornment={
+                  <InputAdornment position="end">
+                    <IconButton
+                      aria-label="Toggle password visibility"
+                      onClick={this.handleClickShowPassword2}
+                    >
+                      {this.state.showPassword2 ? (
+                        <Visibility />
+                      ) : (
+                        <VisibilityOff />
+                      )}
+                    </IconButton>
+                  </InputAdornment>
+                }
+              />
+              <Typography component="span" variant="caption" color="error">
+                {errors.password2}
+              </Typography>
+            </FormControl>
+
+            <Button
+              type="submit"
+              fullWidth
+              variant="contained"
+              color="primary"
+              className={this.props.classes.submit}
+              disabled={!this.state.formValid}
+            >
+              Change password
+            </Button>
+          </form>
+        </Paper>
+      </main>
+    )
+  }
+}
+
+Register.propTypes = {
+  resetPassword: PropTypes.func.isRequired,
+  auth: PropTypes.object.isRequired,
+  errors: PropTypes.object.isRequired,
+  history: PropTypes.object,
+  classes: PropTypes.object,
+  updateError: PropTypes.func.isRequired,
+  match: PropTypes.object.isRequired
+}
+
+const mapStateToProps = state => ({
+  auth: state.auth,
+  errors: state.errors
+})
+
+export default compose(
+  withStyles(styles, {
+    name: 'Register'
+  }),
+  connect(mapStateToProps, { resetPassword, updateError })
+)(Register)
diff --git a/fact-bounty-client/src/pages/ResetPassword/ResetPassword.style.js b/fact-bounty-client/src/pages/ResetPassword/ResetPassword.style.js
new file mode 100644
index 00000000..4af949a6
--- /dev/null
+++ b/fact-bounty-client/src/pages/ResetPassword/ResetPassword.style.js
@@ -0,0 +1,32 @@
+export default theme => ({
+  main: {
+    width: 'auto',
+    display: 'block', // Fix IE 11 issue.
+    marginLeft: theme.spacing.unit * 3,
+    marginRight: theme.spacing.unit * 3,
+    [theme.breakpoints.up(400 + theme.spacing.unit * 3 * 2)]: {
+      width: 400,
+      marginLeft: 'auto',
+      marginRight: 'auto'
+    }
+  },
+  paper: {
+    marginTop: theme.spacing.unit * 12,
+    display: 'flex',
+    flexDirection: 'column',
+    alignItems: 'center',
+    padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${theme
+      .spacing.unit * 3}px`
+  },
+  avatar: {
+    margin: theme.spacing.unit,
+    backgroundColor: theme.palette.secondary.main
+  },
+  form: {
+    width: '100%', // Fix IE 11 issue.
+    marginTop: theme.spacing.unit
+  },
+  submit: {
+    marginTop: theme.spacing.unit * 3
+  }
+})
diff --git a/fact-bounty-client/src/pages/ResetPassword/index.js b/fact-bounty-client/src/pages/ResetPassword/index.js
new file mode 100644
index 00000000..6b2df239
--- /dev/null
+++ b/fact-bounty-client/src/pages/ResetPassword/index.js
@@ -0,0 +1,2 @@
+import ResetPassword from './ResetPassword'
+export default ResetPassword
diff --git a/fact-bounty-client/src/redux/actions/actionTypes.js b/fact-bounty-client/src/redux/actions/actionTypes.js
index 5364673b..ea705304 100644
--- a/fact-bounty-client/src/redux/actions/actionTypes.js
+++ b/fact-bounty-client/src/redux/actions/actionTypes.js
@@ -2,6 +2,8 @@ export const GET_ERRORS = 'GET_ERRORS'
 export const UPDATE_ERRORS = 'UPDATE_ERRORS'
 export const USER_LOADING = 'USER_LOADING'
 export const SET_CURRENT_USER = 'SET_CURRENT_USER'
+export const GET_SUCCESS = 'GET_SUCCESS'
+export const UPDATE_SUCCESS = 'UPDATE_SUCCESS'
 
 export const LOADING_POSTS = 'LOADING_POSTS'
 export const FETCH_POSTS = 'FETCH_POSTS'
diff --git a/fact-bounty-client/src/redux/actions/authActions.js b/fact-bounty-client/src/redux/actions/authActions.js
index f6adc61c..462b61cb 100644
--- a/fact-bounty-client/src/redux/actions/authActions.js
+++ b/fact-bounty-client/src/redux/actions/authActions.js
@@ -1,6 +1,11 @@
 import jwt_decode from 'jwt-decode'
 import { setAuthToken, saveAllTokens } from '../../helpers/AuthTokenHelper'
-import { SET_CURRENT_USER, USER_LOADING, GET_ERRORS } from './actionTypes'
+import {
+  SET_CURRENT_USER,
+  USER_LOADING,
+  GET_ERRORS,
+  GET_SUCCESS
+} from './actionTypes'
 import AuthService from '../../services/AuthService'
 
 // Set logged in user
@@ -134,3 +139,91 @@ export const logoutUser = () => dispatch => {
       console.error(err)
     })
 }
+
+export const forgotPassword = userData => dispatch => {
+  AuthService.forgotPassword(userData)
+    .then(res => {
+      if (res.status === 202) {
+        dispatch({
+          type: GET_ERRORS,
+          payload: { message: res.data.message }
+        })
+      }
+      if (res.status === 200) {
+        dispatch({
+          type: GET_SUCCESS,
+          payload: { message: res.data.message }
+        })
+      }
+    })
+    .catch(err => {
+      if (err && err.response) {
+        let payload = err.response.data
+        if (typeof payload === 'string') {
+          payload = { fetch: err.response.data }
+        }
+        dispatch({
+          type: GET_ERRORS,
+          payload
+        })
+      }
+    })
+}
+
+export const authVerificationToken = (userData, history) => dispatch => {
+  AuthService.authVerificationToken(userData)
+    .then(res => {
+      if (res.status === 202) {
+        dispatch({
+          type: GET_ERRORS,
+          payload: { message: res.data.message }
+        })
+      }
+      if (res.status === 200) {
+        history.push('/resetpassword/' + userData.verification_token)
+      }
+    })
+    .catch(err => {
+      if (err && err.response) {
+        let payload = err.response.data
+        if (typeof payload === 'string') {
+          payload = { fetch: err.response.data }
+        }
+        dispatch({
+          type: GET_ERRORS,
+          payload
+        })
+      }
+    })
+}
+
+export const resetPassword = (userData, history) => dispatch => {
+  AuthService.resetPassword(userData)
+    .then(res => {
+      if (res.status === 202) {
+        dispatch({
+          type: GET_ERRORS,
+          payload: { message: res.data.message }
+        })
+      }
+      if (res.status === 200) {
+        dispatch({
+          type: GET_SUCCESS,
+          payload: { message: res.data.message }
+        })
+        history.push('/login')
+      }
+    })
+    .catch(err => {
+      if (err && err.response) {
+        let payload = err.response.data
+        if (typeof payload === 'string') {
+          payload = { fetch: err.response.data }
+        }
+        dispatch({
+          type: GET_ERRORS,
+          payload
+        })
+      }
+    })
+}
diff --git a/fact-bounty-client/src/redux/actions/successActions.js b/fact-bounty-client/src/redux/actions/successActions.js
new file mode 100644
index 00000000..8f40ed74
--- /dev/null
+++ b/fact-bounty-client/src/redux/actions/successActions.js
@@ -0,0 +1,4 @@
+import { UPDATE_SUCCESS } from './actionTypes'
+
+export const updateSuccess = payload => dispatch =>
+  dispatch({ type: UPDATE_SUCCESS, payload })
diff --git a/fact-bounty-client/src/redux/reducers/index.js b/fact-bounty-client/src/redux/reducers/index.js
index ef358a13..ea3e30d4 100644
--- a/fact-bounty-client/src/redux/reducers/index.js
+++ b/fact-bounty-client/src/redux/reducers/index.js
@@ -4,10 +4,12 @@ import errorReducer from './errorReducers'
 import postReducer from './postReducers'
 import contactUsReducer from './contactUsReducers'
 import twitterReducer from './twitterReducers'
+import successReducers from './successReducers'
 
 export default combineReducers({
   auth: authReducer,
   errors: errorReducer,
+  success: successReducers,
   posts: postReducer,
   contactUs: contactUsReducer,
   tweets: twitterReducer
diff --git a/fact-bounty-client/src/redux/reducers/successReducers.js b/fact-bounty-client/src/redux/reducers/successReducers.js
new file mode 100644
index 00000000..b3fda27e
--- /dev/null
+++ b/fact-bounty-client/src/redux/reducers/successReducers.js
@@ -0,0 +1,16 @@
+import { GET_SUCCESS, UPDATE_SUCCESS } from '../actions/actionTypes'
+
+const initialState = {}
+
+export default function(state = initialState, action) {
+  switch (action.type) {
+    case GET_SUCCESS:
+      return action.payload
+
+    case UPDATE_SUCCESS:
+      return action.payload
+
+    default:
+      return state
+  }
+}
diff --git a/fact-bounty-client/src/services/AuthService.js b/fact-bounty-client/src/services/AuthService.js
index 460b2912..3ecd4f4d 100644
--- a/fact-bounty-client/src/services/AuthService.js
+++ b/fact-bounty-client/src/services/AuthService.js
@@ -17,7 +17,30 @@ const loginUser = userData => {
 const registerUser = userData => {
   return ApiBuilder.API.post(`/api/users/register`, userData)
 }
-
+/**
+ *
+ * POST : forgotPassword
+ *
+ */
+const forgotPassword = userData => {
+  return ApiBuilder.API.post(`/api/users/forgot_password`, userData)
+}
+/**
+ *
+ * POST : authVerificationToken
+ *
+ */
+const authVerificationToken = userData => {
+  return ApiBuilder.API.post(`/api/users/auth_verification_token`, userData)
+}
+/**
+ *
+ * POST : resetPassword
+ *
+ */
+const resetPassword = userData => {
+  return ApiBuilder.API.post(`/api/users/reset_password`, userData)
+}
 /**
  *
  * POST : OauthUser
@@ -56,6 +79,9 @@ const revokeRefreshToken = () => {
 export default {
   loginUser,
   registerUser,
+  forgotPassword,
+  authVerificationToken,
+  resetPassword,
   OauthUser,
   tokenRefresh,
   revokeAccessToken,

From b9f3b7fd82a207cc55532e81ed09b6551d479337 Mon Sep 17 00:00:00 2001
From: hv7214 <hverma1@cs.iitr.ac.in>
Date: Wed, 12 Feb 2020 15:07:22 +0530
Subject: [PATCH 07/11] feat(server/docs/users): swagger user documentation

---
 fact-bounty-flask/api/user/controller.py      |  5 ++
 fact-bounty-flask/docs/users/login.yml        |  2 +-
 .../docs/users/logout_access.yml              | 17 +++++
 .../docs/users/logout_refresh.yml             | 17 +++++
 fact-bounty-flask/docs/users/oauth.yml        | 69 +++++++++++++++++++
 fact-bounty-flask/docs/users/register.yml     | 67 ++++++++++++++++++
 .../docs/users/token_refresh.yml              | 15 ++++
 7 files changed, 191 insertions(+), 1 deletion(-)
 create mode 100644 fact-bounty-flask/docs/users/logout_access.yml
 create mode 100644 fact-bounty-flask/docs/users/logout_refresh.yml
 create mode 100644 fact-bounty-flask/docs/users/oauth.yml
 create mode 100644 fact-bounty-flask/docs/users/register.yml
 create mode 100644 fact-bounty-flask/docs/users/token_refresh.yml

diff --git a/fact-bounty-flask/api/user/controller.py b/fact-bounty-flask/api/user/controller.py
index 421e2185..667ab8af 100644
--- a/fact-bounty-flask/api/user/controller.py
+++ b/fact-bounty-flask/api/user/controller.py
@@ -18,6 +18,7 @@
 class Register(MethodView):
     """This class registers a new user."""
 
+    @swag_from("../../docs/users/register.yml")
     def post(self):
         """Handle POST request for this view. Url --> /api/users/register"""
         # getting JSON data from request
@@ -241,6 +242,7 @@ class Auth(MethodView):
     """This class-based view handles user register and access token generation \
     via 3rd sources like facebook, google"""
 
+    @swag_from("../../docs/users/oauth.yml")
     def post(self):
         # Querying the database with requested email
         data = request.get_json(silent=True)
@@ -300,6 +302,7 @@ def post(self):
 
 class LogoutAccess(MethodView):
     @jwt_required
+    @swag_from("../../docs/users/logout_access.yml")
     def post(self):
         jti = get_raw_jwt()["jti"]
         try:
@@ -316,6 +319,7 @@ def post(self):
 
 class LogoutRefresh(MethodView):
     @jwt_refresh_token_required
+    @swag_from("../../docs/users/logout_refresh.yml")
     def post(self):
         jti = get_raw_jwt()["jti"]
         try:
@@ -332,6 +336,7 @@ def post(self):
 
 class TokenRefresh(MethodView):
     @jwt_refresh_token_required
+    @swag_from("../../docs/users/token_refresh.yml")
     def post(self):
         current_user = get_jwt_identity()
         access_token = create_access_token(identity=current_user, fresh=False)
diff --git a/fact-bounty-flask/docs/users/login.yml b/fact-bounty-flask/docs/users/login.yml
index ba76f27b..f3faca2b 100644
--- a/fact-bounty-flask/docs/users/login.yml
+++ b/fact-bounty-flask/docs/users/login.yml
@@ -1,7 +1,7 @@
 User login
 ---
 tags:
-- Auth
+- User
 consumes:
 - "application/json"
 parameters:
diff --git a/fact-bounty-flask/docs/users/logout_access.yml b/fact-bounty-flask/docs/users/logout_access.yml
new file mode 100644
index 00000000..47d991c3
--- /dev/null
+++ b/fact-bounty-flask/docs/users/logout_access.yml
@@ -0,0 +1,17 @@
+User logout
+---
+tags:
+- User
+produces:
+  - application/json
+parameters:
+- in: header
+  name: Authorization
+  description: an authorization header
+  required: true
+  type: string
+responses:
+    200:
+        description: Access token has been revoked.
+    500:
+        description: Something went wrong!
diff --git a/fact-bounty-flask/docs/users/logout_refresh.yml b/fact-bounty-flask/docs/users/logout_refresh.yml
new file mode 100644
index 00000000..e5c4e3b5
--- /dev/null
+++ b/fact-bounty-flask/docs/users/logout_refresh.yml
@@ -0,0 +1,17 @@
+User logout
+---
+tags:
+- User
+produces:
+  - application/json
+parameters:
+- in: header
+  name: Authorization
+  description: an authorization header
+  required: true
+  type: string
+responses:
+    200:
+        description: Refresh token has been revoked.
+    500:
+        description: Something went wrong!
diff --git a/fact-bounty-flask/docs/users/oauth.yml b/fact-bounty-flask/docs/users/oauth.yml
new file mode 100644
index 00000000..b88f2bee
--- /dev/null
+++ b/fact-bounty-flask/docs/users/oauth.yml
@@ -0,0 +1,69 @@
+User registration via OAuth like facebook, google, etc.
+---
+tags:
+  - User
+consumes:
+  - "application/json"
+produces:
+  - "application/json"
+parameters:
+- in: body
+  name: body
+  schema:
+    type: object
+    properties:
+        name:
+            type: string
+            example: xyz
+        email:
+            type: string
+            example: xyz@gmail.com
+        type:
+            type: string
+            example: admin
+responses:
+  201:
+    description: (for new user) JSON object containing success message, access token and refresh token.
+    schema:
+        type: object
+        properties:
+            message:
+                type: string
+                example: You logged in successfully.
+            access_token:
+                type: string
+                example: hash-generated-by-jwt
+            refresh_token:
+                type: string
+                example: hash-generated-by-jwt
+  202:
+    description: (for existing user) JSON object containing success message, access token and refresh token.
+    schema:
+        type: object
+        properties:
+            message:
+                type: string
+                example: You logged in successfully.
+            access_token:
+                type: string
+                example: hash-generated-by-jwt
+            refresh_token:
+                type: string
+                example: hash-generated-by-jwt
+
+  404:
+    description: Fields are missing
+    schema:
+        type: object
+        properties:
+            message:
+                type: string
+                example: Please provide all the required fields.
+  500:
+    description: Something went wrong
+    schema:
+        type: object
+        properties:
+            message:
+                type: string
+                example: Something went wrong
diff --git a/fact-bounty-flask/docs/users/register.yml b/fact-bounty-flask/docs/users/register.yml
new file mode 100644
index 00000000..6c042f36
--- /dev/null
+++ b/fact-bounty-flask/docs/users/register.yml
@@ -0,0 +1,67 @@
+User registration via form
+---
+tags:
+  - User
+consumes:
+  - "application/json"
+produces:
+  - "application/json"
+parameters:
+- in: body
+  name: body
+  schema:
+    type: object
+    properties:
+        name:
+            type: string
+            example: xyz
+        email:
+            type: string
+            example: xyz@gmail.com
+        password:
+            type: string
+            example: password123
+        password2:
+            type: string
+            example: password123
+responses:
+  201:
+    description:  JSON object containing success message
+    schema:
+        type: object
+        properties:
+            message:
+                type: string
+                example: You registered successfully. Please log in.
+  202:
+    description: JSON object containing success message
+    schema:
+        type: object
+        properties:
+            message:
+                type: string
+                example: User already exists. Please login.
+  401:
+    description: Passwords do not match
+    schema:
+        type: object
+        properties:
+            message:
+                type: string
+                example: Both passwords does not match
+  404:
+    description: Fields are missing
+    schema:
+        type: object
+        properties:
+            message:
+                type: string
+                example: Please provide all the required fields.
+  500:
+    description: Something went wrong
+    schema:
+        type: object
+        properties:
+            message:
+                type: string
+                example: Something went wrong
diff --git a/fact-bounty-flask/docs/users/token_refresh.yml b/fact-bounty-flask/docs/users/token_refresh.yml
new file mode 100644
index 00000000..1176c67b
--- /dev/null
+++ b/fact-bounty-flask/docs/users/token_refresh.yml
@@ -0,0 +1,15 @@
+Token refresh
+---
+tags:
+- User
+produces:
+  - application/json
+parameters:
+- in: header
+  name: Authorization
+  description: an authorization header
+  required: true
+  type: string
+responses:
+    200:
+        description: Token refreshed successfully.

From e29c3cd06d5690278483891fd40f2d91761a5c4b Mon Sep 17 00:00:00 2001
From: Joseph Semrai <josephsemrai@gmail.com>
Date: Sat, 28 Dec 2019 13:43:14 -0500
Subject: [PATCH 08/11] Added profile controllers and routes

Decorator fix

Codacy fix
---
 fact-bounty-flask/api/user/controller.py | 37 ++++++++++++++++++++++++
 fact-bounty-flask/api/user/views.py      |  6 ++++
 2 files changed, 43 insertions(+)

diff --git a/fact-bounty-flask/api/user/controller.py b/fact-bounty-flask/api/user/controller.py
index 667ab8af..2bd75087 100644
--- a/fact-bounty-flask/api/user/controller.py
+++ b/fact-bounty-flask/api/user/controller.py
@@ -348,6 +348,42 @@ def post(self):
         return make_response(jsonify(response)), 200
 
 
+class Profile(MethodView):
+    """This class-based view handles retrieving and updating the current \
+    user's information"""
+
+    @staticmethod
+    @jwt_required
+    def get(self):
+        current_user = get_jwt_identity()
+
+        response = jsonify(current_user)
+        return make_response(response), 200
+
+    @jwt_required
+    def post(self):
+        try:
+            post_data = request.get_json(silent=True)
+            name = post_data["name"]
+            email = post_data["email"]
+        except Exception:
+            response = {"message": "Some user details are missing."}
+            return make_response(jsonify(response)), 404
+
+        # Querying the database to get the user to update
+        user = User.find_by_email(email)
+
+        if user:
+            user = user.update(email=email, name=name)
+            user.save()
+            response = {"message": "User has been updated."}
+            return make_response(jsonify(response)), 204
+
+        else:
+            response = {"message": "User not found. Please check your request."}
+            return make_response(jsonify(response)), 404
+
+
 userController = {
     "register": Register.as_view("register"),
     "login": Login.as_view("login"),
@@ -360,4 +396,5 @@ def post(self):
     "logout_access": LogoutAccess.as_view("logout_access"),
     "logout_refresh": LogoutRefresh.as_view("logout_refresh"),
     "token_refresh": TokenRefresh.as_view("token_refresh"),
+    "profile": Profile.as_view("profile")
 }
diff --git a/fact-bounty-flask/api/user/views.py b/fact-bounty-flask/api/user/views.py
index 82a45048..20ddefc3 100644
--- a/fact-bounty-flask/api/user/views.py
+++ b/fact-bounty-flask/api/user/views.py
@@ -49,3 +49,9 @@
     view_func=userController["token_refresh"],
     methods=["POST"],
 )
+
+userprint.add_url_rule(
+    "/profile",
+    view_func=userController["profile"],
+    methods=["GET", "POST"]
+)

From 9c365694038796dc63aef3a59e799a181248f1f5 Mon Sep 17 00:00:00 2001
From: hv7214 <hverma1@cs.iitr.ac.in>
Date: Fri, 24 Jan 2020 11:41:26 +0530
Subject: [PATCH 09/11] feat(server/user): added forgot password functionality

---
 fact-bounty-flask/api/user/model.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fact-bounty-flask/api/user/model.py b/fact-bounty-flask/api/user/model.py
index 76c0a19c..4d1023dc 100755
--- a/fact-bounty-flask/api/user/model.py
+++ b/fact-bounty-flask/api/user/model.py
@@ -66,7 +66,7 @@ def find_by_email(cls, email):
     def find_by_user_id(cls, id):
         return cls.query.filter_by(id=id).first()
 
-    @classmethod
+    @classmethod   
     def find_by_verification_token(cls, verification_token):
         return cls.query.filter_by(
             verification_token=verification_token

From ec9e54dae8df7860fee5749ebcbfec982a83d502 Mon Sep 17 00:00:00 2001
From: Aditya Bhatnagar <adityabhatnagar14@gmail.com>
Date: Sat, 28 Dec 2019 13:43:14 -0500
Subject: [PATCH 10/11] Add admin controller

---
 fact-bounty-flask/api/app.py        | 4 +++-
 fact-bounty-flask/api/commands.py   | 4 +---
 fact-bounty-flask/api/user/model.py | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/fact-bounty-flask/api/app.py b/fact-bounty-flask/api/app.py
index 4bfc9022..bc0660cf 100644
--- a/fact-bounty-flask/api/app.py
+++ b/fact-bounty-flask/api/app.py
@@ -30,8 +30,10 @@ def create_app(config_name):
     @app.before_first_request
     def create_tables():
         db.create_all()
-
     return app
+    
+    except Exception as err:
+        print("Error occured:", err)
 
 
 def register_extensions(app):
diff --git a/fact-bounty-flask/api/commands.py b/fact-bounty-flask/api/commands.py
index 53dcd1c8..1f9d12e5 100644
--- a/fact-bounty-flask/api/commands.py
+++ b/fact-bounty-flask/api/commands.py
@@ -219,6 +219,4 @@ def create_admin():
         )
         user.save()
     except Exception as e:
-        print(str(e))
-        response = {"message": "Something went wrong!!"}
-        return make_response(jsonify(response)), 500
+        return {"Error occured": str(e)}
diff --git a/fact-bounty-flask/api/user/model.py b/fact-bounty-flask/api/user/model.py
index 4d1023dc..8361befb 100755
--- a/fact-bounty-flask/api/user/model.py
+++ b/fact-bounty-flask/api/user/model.py
@@ -26,7 +26,7 @@ class User(Model):
     type = Column(db.String(50), default="remote")
     role = Column(db.String(10), default="user")
 
-    def __init__(self, id, name, email, password, role="user", _type="remote"):
+    def __init__(self, name, email, password, role="user", _type="remote"):
         """
         Initializes the user instance
         """

From 7d9d4c8be90d49617b26da1f0288c0b4b8f5bbe8 Mon Sep 17 00:00:00 2001
From: the catalyst <adityabhatnagar14@gmail.com>
Date: Mon, 9 Mar 2020 17:33:20 +0530
Subject: [PATCH 11/11] Fix indentation errors

---
 fact-bounty-flask/api/app.py | 35 +++++++++++++++++++----------------
 1 file changed, 19 insertions(+), 16 deletions(-)

diff --git a/fact-bounty-flask/api/app.py b/fact-bounty-flask/api/app.py
index bc0660cf..ffd4f2d6 100644
--- a/fact-bounty-flask/api/app.py
+++ b/fact-bounty-flask/api/app.py
@@ -16,22 +16,25 @@
 
 def create_app(config_name):
     # create and configure the app
-    app = Flask(
-        __name__, static_folder="../build/static", template_folder="../build"
-    )
-    app.config.from_object(config[config_name])
-    config[config_name].init_app(app)
-
-    register_extensions(app)
-    register_blueprint(app)
-    register_shellcontext(app)
-    register_commands(app)
-
-    @app.before_first_request
-    def create_tables():
-        db.create_all()
-    return app
-    
+    try:
+        app = Flask(
+            __name__,
+            static_folder="../build/static",
+            template_folder="../build",
+        )
+        app.config.from_object(config[config_name])
+        config[config_name].init_app(app)
+
+        register_extensions(app)
+        register_blueprint(app)
+        register_shellcontext(app)
+        register_commands(app)
+
+        @app.before_first_request
+        def create_tables():
+            db.create_all()
+
+        return app
     except Exception as err:
         print("Error occured:", err)