From c0a13afe13cc990b96fade728a55c2cff2e881da Mon Sep 17 00:00:00 2001
From: Allyssa Poulin <40319407+allyssap@users.noreply.github.com>
Date: Sun, 12 Mar 2023 17:35:40 -0400
Subject: [PATCH 1/3] otpform and otp generation
-issue: how to save generated OTP to user, to check if user inputs the correct OTP
---
.idea/inspectionProfiles/Project_Default.xml | 59 ++++++++++++++++++++
.idea/workspace.xml | 35 ++++++++++++
app/api/auth.py | 1 -
app/auth/email.py | 23 ++++++++
app/auth/forms.py | 4 +-
app/auth/routes.py | 14 +++--
app/main/forms.py | 1 -
app/models.py | 7 +++
app/templates/auth/opt_login.html | 14 +++++
app/templates/email/otp.html | 9 +++
app/templates/email/otp.txt | 11 ++++
11 files changed, 171 insertions(+), 7 deletions(-)
create mode 100644 .idea/inspectionProfiles/Project_Default.xml
create mode 100644 .idea/workspace.xml
create mode 100644 app/templates/auth/opt_login.html
create mode 100644 app/templates/email/otp.html
create mode 100644 app/templates/email/otp.txt
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..a1f9af5
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..49100b4
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/api/auth.py b/app/api/auth.py
index 230f98c..e78e282 100644
--- a/app/api/auth.py
+++ b/app/api/auth.py
@@ -12,7 +12,6 @@ def verify_password(username, password):
if user and user.check_password(password):
return user
-
@basic_auth.error_handler
def basic_auth_error(status):
return error_response(status)
diff --git a/app/auth/email.py b/app/auth/email.py
index 98755ac..bdb1d7d 100644
--- a/app/auth/email.py
+++ b/app/auth/email.py
@@ -1,8 +1,31 @@
from flask import render_template, current_app
from flask_babel import _
from app.email import send_email
+import math, random
+def generate_otp(user):
+ # Declare a digits variable
+ # which stores all digits
+ digits = "0123456789"
+ otp = ""
+
+ # length of password can be changed
+ # by changing value in range
+ for i in range(4):
+ otp += digits[math.floor(random.random() * 10)]
+ return otp
+
+def send_otp_email(user):
+ otp = generate_otp(user)
+ send_email(_('[Microblog] Your one time passcode'),
+ sender=current_app.config['ADMINS'][0],
+ recipients=[user.email],
+ text_body=render_template('email/otp.txt',
+ user=user, otp=otp),
+ html_body=render_template('email/otp.html',
+ user=user, otp=otp))
+
def send_password_reset_email(user):
token = user.get_reset_password_token()
send_email(_('[Microblog] Reset Your Password'),
diff --git a/app/auth/forms.py b/app/auth/forms.py
index c1dd3eb..f851b79 100644
--- a/app/auth/forms.py
+++ b/app/auth/forms.py
@@ -4,13 +4,15 @@ from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
from flask_babel import _, lazy_gettext as _l
from app.models import User
-
class LoginForm(FlaskForm):
username = StringField(_l('Username'), validators=[DataRequired()])
password = PasswordField(_l('Password'), validators=[DataRequired()])
remember_me = BooleanField(_l('Remember Me'))
submit = SubmitField(_l('Sign In'))
+class OTPForm(FlaskForm):
+ OTP = StringField(_l('One Time Passcode'), validators=[DataRequired()]) ###EqualTo(otp)
+ submit = SubmitField(_l('Log in') )
class RegistrationForm(FlaskForm):
username = StringField(_l('Username'), validators=[DataRequired()])
diff --git a/app/auth/routes.py b/app/auth/routes.py
index b3f1d72..77702dc 100644
--- a/app/auth/routes.py
+++ b/app/auth/routes.py
@@ -9,11 +9,8 @@ from app.auth.forms import LoginForm, RegistrationForm, \
from app.models import User
from app.auth.email import send_password_reset_email
-
@bp.route('/login', methods=['GET', 'POST'])
def login():
- if current_user.is_authenticated:
- return redirect(url_for('main.index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
@@ -23,10 +20,19 @@ def login():
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
if not next_page or url_parse(next_page).netloc != '':
- next_page = url_for('main.index')
+ next_page = url_for('auth/otp_login.html')
return redirect(next_page)
return render_template('auth/login.html', title=_('Sign In'), form=form)
+def auth_opt():
+ form = OTPForm()
+ if OTP != user.curr_otp :
+ flash(_('Invalid OTP'))
+ if form.validate_on_submit():
+ return redirect(url_for('main.index'))
+
+ return render_template('auth/otp_login.html', title=_('Enter OTP'),
+ form=form)
@bp.route('/logout')
def logout():
diff --git a/app/main/forms.py b/app/main/forms.py
index a7c8d96..f06209c 100644
--- a/app/main/forms.py
+++ b/app/main/forms.py
@@ -26,7 +26,6 @@ class EditProfileForm(FlaskForm):
class EmptyForm(FlaskForm):
submit = SubmitField('Submit')
-
class PostForm(FlaskForm):
post = TextAreaField(_l('Say something'), validators=[DataRequired()])
submit = SubmitField(_l('Submit'))
diff --git a/app/models.py b/app/models.py
index 5bdab20..c7741e7 100644
--- a/app/models.py
+++ b/app/models.py
@@ -96,6 +96,7 @@ class User(UserMixin, PaginatedAPIMixin, db.Model):
password_hash = db.Column(db.String(128))
posts = db.relationship('Post', backref='author', lazy='dynamic')
about_me = db.Column(db.String(140))
+ otp = db.Column(db.String(4), index=True, unique=True)
last_seen = db.Column(db.DateTime, default=datetime.utcnow)
token = db.Column(db.String(32), index=True, unique=True)
token_expiration = db.Column(db.DateTime)
@@ -118,6 +119,12 @@ class User(UserMixin, PaginatedAPIMixin, db.Model):
def __repr__(self):
return ''.format(self.username)
+ def set_otp(self, otp):
+ self.otp = otp
+
+ def get_otp(self):
+ return self.otp
+
def set_password(self, password):
self.password_hash = generate_password_hash(password)
diff --git a/app/templates/auth/opt_login.html b/app/templates/auth/opt_login.html
new file mode 100644
index 0000000..9ab59b3
--- /dev/null
+++ b/app/templates/auth/opt_login.html
@@ -0,0 +1,14 @@
+{% extends 'base.html' %}
+{% import 'bootstrap/wtf.html' as wtf %}
+
+{% block app_content %}
+ {{ _('Validation') }}
+
+
+ {{ wtf.quick_form(form) }}
+
+
+
+ {{ _('New User?') }} {{ _('Click to Register!') }}
+ {{ _('Returning User?') }} {{ _('Click to Login!') }}
+{% endblock %}
diff --git a/app/templates/email/otp.html b/app/templates/email/otp.html
new file mode 100644
index 0000000..a2460e2
--- /dev/null
+++ b/app/templates/email/otp.html
@@ -0,0 +1,9 @@
+Dear {{ user.username }},
+
+ Here is your one time passcode
+
{{otp}}
+
+
+If you have not requested an otp, simply ignore this message.
+Sincerely,
+The Microblog Team
diff --git a/app/templates/email/otp.txt b/app/templates/email/otp.txt
new file mode 100644
index 0000000..afc9182
--- /dev/null
+++ b/app/templates/email/otp.txt
@@ -0,0 +1,11 @@
+Dear {{ user.username }},
+
+Here is your one time passcode:
+
+{{otp}}
+
+If you have not requested a otp, simply ignore this message.
+
+Sincerely,
+
+The Microblog Team
From a2857083a8a902671c018dcdffe3136c76aba2fa Mon Sep 17 00:00:00 2001
From: Allyssa Poulin <40319407+allyssap@users.noreply.github.com>
Date: Sun, 12 Mar 2023 20:22:05 -0400
Subject: [PATCH 2/3] otp password
---
app/auth/routes.py | 6 +++---
app/models.py | 11 ++++++-----
app/templates/.DS_Store | Bin 0 -> 6148 bytes
.../auth/{opt_login.html => otp_login.html} | 0
migrations/versions/e517276bb1c2_users_table.py | 1 +
5 files changed, 10 insertions(+), 8 deletions(-)
create mode 100644 app/templates/.DS_Store
rename app/templates/auth/{opt_login.html => otp_login.html} (100%)
diff --git a/app/auth/routes.py b/app/auth/routes.py
index 77702dc..6c83fb7 100644
--- a/app/auth/routes.py
+++ b/app/auth/routes.py
@@ -20,17 +20,17 @@ def login():
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
if not next_page or url_parse(next_page).netloc != '':
- next_page = url_for('auth/otp_login.html')
+ next_page = url_for('auth.otp_login')
return redirect(next_page)
return render_template('auth/login.html', title=_('Sign In'), form=form)
-def auth_opt():
+@bp.route('/otp', methods=['GET', 'POST'])
+def otp_login():
form = OTPForm()
if OTP != user.curr_otp :
flash(_('Invalid OTP'))
if form.validate_on_submit():
return redirect(url_for('main.index'))
-
return render_template('auth/otp_login.html', title=_('Enter OTP'),
form=form)
diff --git a/app/models.py b/app/models.py
index c7741e7..c725823 100644
--- a/app/models.py
+++ b/app/models.py
@@ -96,7 +96,7 @@ class User(UserMixin, PaginatedAPIMixin, db.Model):
password_hash = db.Column(db.String(128))
posts = db.relationship('Post', backref='author', lazy='dynamic')
about_me = db.Column(db.String(140))
- otp = db.Column(db.String(4), index=True, unique=True)
+ otp = db.Column(db.String(4))
last_seen = db.Column(db.DateTime, default=datetime.utcnow)
token = db.Column(db.String(32), index=True, unique=True)
token_expiration = db.Column(db.DateTime)
@@ -119,11 +119,12 @@ class User(UserMixin, PaginatedAPIMixin, db.Model):
def __repr__(self):
return ''.format(self.username)
- def set_otp(self, otp):
- self.otp = otp
+##
+ ## def set_otp(self, otp):
+ ## self.otp = otp
- def get_otp(self):
- return self.otp
+ ## def get_otp(self):
+ ## return self.otp
def set_password(self, password):
self.password_hash = generate_password_hash(password)
diff --git a/app/templates/.DS_Store b/app/templates/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..1aa90dd38745402468a211e4eb99a4992b4db050
GIT binary patch
literal 6148
zcmeH~K?=e^3`G;|qTr@Wm$UHz-e3?tK`)>n=t3%px}Kx^lL>;=wTS#c@+X-IrEk$`
zL_}A&{Zgb8krr+$3kxGt4X|{^>yQ5dhkt?1r_^63}D?Xbvq^QGsbp4;roN
zV~Ewg9h%}?4lPw{yJ!p_8c$Z6VqjX^MH3R3W)}t$Ab}BqY0W#k|2ObY^Z%%YDG89k
zpApbz-LF@8skmF;UeD^=sM@-~p?)0U_8}^eepgFWuMFqwm0mr~V0$(NY
E0L2Ru#Q*>R
literal 0
HcmV?d00001
diff --git a/app/templates/auth/opt_login.html b/app/templates/auth/otp_login.html
similarity index 100%
rename from app/templates/auth/opt_login.html
rename to app/templates/auth/otp_login.html
diff --git a/migrations/versions/e517276bb1c2_users_table.py b/migrations/versions/e517276bb1c2_users_table.py
index 4353272..5eb8181 100644
--- a/migrations/versions/e517276bb1c2_users_table.py
+++ b/migrations/versions/e517276bb1c2_users_table.py
@@ -23,6 +23,7 @@ def upgrade():
sa.Column('username', sa.String(length=64), nullable=True),
sa.Column('email', sa.String(length=120), nullable=True),
sa.Column('password_hash', sa.String(length=128), nullable=True),
+ sa.Column('otp', sa.Integer()),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
From 0098f3448af96973307dabe25cc9f3829ee9a9af Mon Sep 17 00:00:00 2001
From: Allyssa Poulin <40319407+allyssap@users.noreply.github.com>
Date: Sun, 12 Mar 2023 20:46:36 -0400
Subject: [PATCH 3/3] otp page working
needs testing:
does user receive an email
does it check that the otp matches the generated one
---
app/auth/email.py | 2 +-
app/auth/forms.py | 3 ++-
app/auth/routes.py | 13 ++++++++++---
app/models.py | 1 -
4 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/app/auth/email.py b/app/auth/email.py
index bdb1d7d..e4a765c 100644
--- a/app/auth/email.py
+++ b/app/auth/email.py
@@ -9,11 +9,11 @@ def generate_otp(user):
# which stores all digits
digits = "0123456789"
otp = ""
-
# length of password can be changed
# by changing value in range
for i in range(4):
otp += digits[math.floor(random.random() * 10)]
+ user.otp = otp
return otp
def send_otp_email(user):
diff --git a/app/auth/forms.py b/app/auth/forms.py
index f851b79..9a1af02 100644
--- a/app/auth/forms.py
+++ b/app/auth/forms.py
@@ -11,7 +11,8 @@ class LoginForm(FlaskForm):
submit = SubmitField(_l('Sign In'))
class OTPForm(FlaskForm):
- OTP = StringField(_l('One Time Passcode'), validators=[DataRequired()]) ###EqualTo(otp)
+ username = StringField(_l('Username'), validators=[DataRequired()])
+ OTP = StringField(_l('OTP'), validators=[DataRequired()]) ###EqualTo(otp)
submit = SubmitField(_l('Log in') )
class RegistrationForm(FlaskForm):
diff --git a/app/auth/routes.py b/app/auth/routes.py
index 6c83fb7..84f280f 100644
--- a/app/auth/routes.py
+++ b/app/auth/routes.py
@@ -5,7 +5,7 @@ from flask_babel import _
from app import db
from app.auth import bp
from app.auth.forms import LoginForm, RegistrationForm, \
- ResetPasswordRequestForm, ResetPasswordForm
+ ResetPasswordRequestForm, ResetPasswordForm, OTPForm
from app.models import User
from app.auth.email import send_password_reset_email
@@ -27,8 +27,15 @@ def login():
@bp.route('/otp', methods=['GET', 'POST'])
def otp_login():
form = OTPForm()
- if OTP != user.curr_otp :
- flash(_('Invalid OTP'))
+ user = User.query.filter_by(username=form.username.data).first()
+ otp = form.OTP.data
+ if user:
+ send_otp_email(user)
+ flash(_('Check your email for your OTP'))
+ return redirect(url_for('auth.otp_login'))
+ if otp != user.otp:
+ flash(_('Invalid OTP'))
+ return redirect(url_for('auth.otp_login'))
if form.validate_on_submit():
return redirect(url_for('main.index'))
return render_template('auth/otp_login.html', title=_('Enter OTP'),
diff --git a/app/models.py b/app/models.py
index c725823..3a9dcae 100644
--- a/app/models.py
+++ b/app/models.py
@@ -88,7 +88,6 @@ followers = db.Table(
db.Column('followed_id', db.Integer, db.ForeignKey('user.id'))
)
-
class User(UserMixin, PaginatedAPIMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)