Merge branch 'rb-verification-challenge-refactor' into 'main'
Verification challenge code refactor See merge request engineering/remarkbox/remarkbox!1
This commit is contained in:
commit
cf89a62a66
15 changed files with 246 additions and 100 deletions
1
Makefile
1
Makefile
|
|
@ -20,6 +20,7 @@ clean:
|
|||
|
||||
test: dev
|
||||
env/bin/py.test
|
||||
#env/bin/py.test --lf
|
||||
|
||||
serve: dev
|
||||
# In the first shell, run a copy of remarkbox using:
|
||||
|
|
|
|||
16
README.rst
16
README.rst
|
|
@ -94,17 +94,27 @@ Now browse to http://127.0.0.1:8000 and index.html will load and will have an em
|
|||
If you attempt to log in, your third shell will capture the email and you can copy / paste the verification link to log in!
|
||||
|
||||
|
||||
|
||||
SQL Migrations
|
||||
===============
|
||||
New Environments
|
||||
================
|
||||
|
||||
If your deployment is brand new, you don't need to run any migrations.
|
||||
|
||||
To create all the schemas & tables in your database, run:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
env/bin/remarkbox_init_db development.ini
|
||||
|
||||
You should however run this to stamp the database as ready:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
alembic -c development.ini stamp head
|
||||
|
||||
|
||||
SQL Migrations
|
||||
===============
|
||||
|
||||
Otherwise, it should be safe to run this at anytime to catch your database up:
|
||||
|
||||
.. code-block:: bash
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
|||
</div>
|
||||
<script src="http://127.0.0.1:6543/static/js/iframe-resizer/iframeResizer.min.js"></script>
|
||||
<script>
|
||||
var rb_owner_key = "f7ea7fb0-cd90-11e7-a69d-9c4e369c7158";
|
||||
var rb_owner_key = "3484c7d4-f012-11ec-b5f9-843a4b34def8";
|
||||
var thread_uri = window.location.href;
|
||||
var thread_title = window.document.title;
|
||||
var thread_fragment = window.location.hash;
|
||||
|
|
|
|||
|
|
@ -25,41 +25,30 @@ jinja2_env = Environment(
|
|||
)
|
||||
|
||||
|
||||
def send_otp_email(request, to_email, raw_otp, return_to):
|
||||
def send_verification_digits_to_email(request, to_email, raw_digits):
|
||||
"""
|
||||
Send email with OTP (one time password) link.
|
||||
Send email with raw_digits a user may pass to verify & authenticate.
|
||||
|
||||
request
|
||||
the request (of the successful log in attempt)
|
||||
|
||||
to_email
|
||||
the email address to send the OTP link
|
||||
the email address to send the raw_digits
|
||||
|
||||
raw_otp
|
||||
the raw (unencrypted) one time password
|
||||
|
||||
return_to
|
||||
the URI to return the user on successful authentication
|
||||
raw_digits:
|
||||
the raw (unencrypted) digits the user may use to verify & authenticate.
|
||||
"""
|
||||
subject = "Verification Code - {}".format(raw_digits)
|
||||
|
||||
query_params = [
|
||||
"email={}".format(quote_plus(to_email)),
|
||||
"raw-otp={}".format(raw_otp),
|
||||
]
|
||||
|
||||
if return_to:
|
||||
query_params.append("return-to={}".format(return_to))
|
||||
|
||||
link = "{0}/join-or-log-in?{1}".format(request.host_url, "&".join(query_params))
|
||||
|
||||
subject = "Magic sign-in link for comments"
|
||||
message_text = WELCOME_1_TEXT.format(raw_digits)
|
||||
message_html = WELCOME_1_HTML.format(subject, raw_digits)
|
||||
|
||||
if not request.user.verified:
|
||||
message_text = WELCOME_1_TEXT.format(link)
|
||||
message_html = WELCOME_1_HTML.format(subject, link)
|
||||
message_text = WELCOME_1_TEXT.format(raw_digits)
|
||||
message_html = WELCOME_1_HTML.format(subject, raw_digits)
|
||||
else:
|
||||
message_text = WELCOME_2_TEXT.format(link)
|
||||
message_html = WELCOME_2_HTML.format(subject, link)
|
||||
message_text = WELCOME_2_TEXT.format(raw_digits)
|
||||
message_html = WELCOME_2_HTML.format(subject, raw_digits)
|
||||
|
||||
send_pyramid_email(request, to_email, subject, message_text, message_html)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ Hello!
|
|||
|
||||
Thanks for joining the discussion.
|
||||
|
||||
To get reply notifications, you should paste this link into your browser:
|
||||
Hey there!
|
||||
|
||||
Here is the verification code you requested:
|
||||
|
||||
\n{0}\n
|
||||
|
||||
This will verify your email and log you in.
|
||||
Type code into the challenge input field.
|
||||
|
||||
What's next?
|
||||
|
||||
|
|
@ -33,23 +35,10 @@ WELCOME_1_HTML = """
|
|||
</p>
|
||||
|
||||
<p>
|
||||
To get reply notifcations, you should click this link:
|
||||
Here is the verification code you requested:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="{1}" style="font-weight: bold;" target="_blank">Click here to verify and log in!</a>
|
||||
</p>
|
||||
|
||||
<p style="font-size: .8em;">
|
||||
<span style="color: #aaaaaa;">
|
||||
You may paste this link into your browser:
|
||||
</span>
|
||||
<br>
|
||||
<br>
|
||||
<a href="{1}" style="color: #439fe0; font-weight: normal; text-decoration: none; word-break: break-word;" target="_blank">
|
||||
{1}
|
||||
</a>
|
||||
</p>
|
||||
<p><b>{1}</b></p>
|
||||
|
||||
<p>
|
||||
This will verify your email and log you in.
|
||||
|
|
@ -76,10 +65,12 @@ WELCOME_1_HTML = """
|
|||
WELCOME_2_TEXT = """
|
||||
Hello again!
|
||||
|
||||
To log in please paste this link into your browser:
|
||||
Here is the verification code you requested:
|
||||
|
||||
\n{0}\n
|
||||
|
||||
Type code into the challenge input field.
|
||||
|
||||
Don't forget to check out your notification settings.
|
||||
|
||||
Talk to you soon!
|
||||
|
|
@ -95,23 +86,10 @@ WELCOME_2_HTML = """
|
|||
<h2>Hello again!</h2>
|
||||
|
||||
<p>
|
||||
To log in please click the link below.
|
||||
Here is the verification code you requested:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="{1}" style="font-weight: bold;" target="_blank">Click here to log in!</a>
|
||||
</p>
|
||||
|
||||
<p style="font-size: .8em;">
|
||||
<span style="color: #aaaaaa;">
|
||||
You may paste this link into your browser:
|
||||
</span>
|
||||
<br>
|
||||
<br>
|
||||
<a href="{1}" style="color: #439fe0; font-weight: normal; text-decoration: none; word-break: break-word;" target="_blank">
|
||||
{1}
|
||||
</a>
|
||||
</p>
|
||||
<p><b>{1}</b></p>
|
||||
|
||||
<h3>What's next?</h3>
|
||||
|
||||
|
|
|
|||
|
|
@ -150,3 +150,7 @@ class RBase(object):
|
|||
@property
|
||||
def dbsession(self):
|
||||
return object_session(self)
|
||||
|
||||
@property
|
||||
def id_without_dashes(self):
|
||||
return self.id.__str__().replace("-","")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from sqlalchemy import BigInteger, Boolean, Column, Unicode, Enum, func, or_
|
||||
from sqlalchemy import BigInteger, Boolean, Integer, Column, Unicode, Enum, func, or_
|
||||
|
||||
from sqlalchemy.orm import relationship, backref
|
||||
|
||||
|
|
@ -91,6 +91,7 @@ class User(RBase, Base):
|
|||
# we only share this string with the owner of an email box. not-in-use...
|
||||
email_id = Column(Unicode(8))
|
||||
password = Column(Unicode(64))
|
||||
password_attempts = Column(Integer, default=0)
|
||||
password_timestamp = Column(BigInteger)
|
||||
# TODO: someday this should be renamed to created_timestamp
|
||||
created = Column(BigInteger, nullable=False)
|
||||
|
|
@ -261,15 +262,20 @@ class User(RBase, Base):
|
|||
self.created = now_timestamp()
|
||||
self.id = uuid.uuid1()
|
||||
self.email = unicode(email)
|
||||
self.email_id = unicode(self._generate_raw_password(8))
|
||||
|
||||
# TODO: this field was never used & should be sunset.
|
||||
self.email_id = unicode(generate_password(size=8))
|
||||
|
||||
self.new_password()
|
||||
# don't password throttle new User objects.
|
||||
self.password_timestamp = 0
|
||||
|
||||
def _generate_raw_password(self, size=32):
|
||||
def _generate_raw_password(self):
|
||||
"""Return a system generated password"""
|
||||
return generate_password(size)
|
||||
#return generate_password(size)
|
||||
from random import choice
|
||||
numbers = '0123456789'
|
||||
return "".join([choice(numbers) for i in range(0,6)])
|
||||
|
||||
def new_password(self):
|
||||
"""Generate and return raw password, store password hash into DB."""
|
||||
|
|
@ -281,19 +287,32 @@ class User(RBase, Base):
|
|||
bcrypt.gensalt()
|
||||
).decode("utf-8")
|
||||
self.password_timestamp = now_timestamp()
|
||||
self.password_attempts = 0
|
||||
return raw_password
|
||||
|
||||
def check_password(self, password):
|
||||
"""Accept plain-text raw password, create hash, compare with DB."""
|
||||
stored_hash = self.password
|
||||
|
||||
# increment password attempts.
|
||||
self.password_attempts += 1
|
||||
|
||||
# expire password after 15 minutes.
|
||||
# 900000 milliseconds == 15 minutes
|
||||
if self.password_timestamp_delta >= 900000:
|
||||
return False
|
||||
|
||||
# prevent brute force, allow 10 invalid verification code attempts.
|
||||
if self.password_attempts >= 10:
|
||||
return False
|
||||
|
||||
# bcrypt works with bytes so we encode to utf-8.
|
||||
new_hash = bcrypt.hashpw(
|
||||
password.encode("utf-8"),
|
||||
stored_hash.encode("utf-8"),
|
||||
).decode("utf-8")
|
||||
|
||||
log.info("new_hash={} stored_hash={}".format(new_hash, stored_hash))
|
||||
#log.info("new_hash={} stored_hash={}".format(new_hash, stored_hash))
|
||||
|
||||
if new_hash == stored_hash:
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -96,6 +96,8 @@ def includeme(config):
|
|||
# basic routes:
|
||||
config.add_route("basic-join-or-log-in", "/join-or-log-in")
|
||||
|
||||
config.add_route("verification-challenge", "/verification-challenge")
|
||||
|
||||
config.add_route("basic-namespace-nodes", "/ns/{namespace}/nodes")
|
||||
config.add_route("basic-namespace-settings", "/ns/{namespace}/settings")
|
||||
config.add_route("basic-namespace-stats-json", "/ns/{namespace}/stats.json")
|
||||
|
|
|
|||
|
|
@ -22,9 +22,6 @@ input.rb-submit {
|
|||
{% if return_to %}
|
||||
<input type="hidden" name="return-to" value="{{ return_to }}" >
|
||||
{% endif %}
|
||||
{% if thread_uri %}
|
||||
<input type="hidden" name="thread_uri" value="{{ thread_uri }}" >
|
||||
{% endif %}
|
||||
</form>
|
||||
<span class="whats-next">
|
||||
<b>No account?</b>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
{{ snippets.user_avatar_link(request.user, 35, class="avatar-nav") }}
|
||||
</span>
|
||||
{% else %}
|
||||
<a href="{{ domain_app }}{% if request.mode == "embed" %}{{ request.link_prefix }}{% endif %}/join-or-log-in{% if request.mode == "basic" %}?return-to={{ request.path_url }}{% elif request.node and request.mode == "embed" %}?thread_uri={{ request.node.root.uri.data }}{% endif %}" style="display: none;">join-or-log-in</a>
|
||||
<a href="{{ domain_app }}{% if request.mode == "embed" %}{{ request.link_prefix }}{% endif %}/join-or-log-in{% if request.mode == "basic" %}?return-to={{ request.path_url }}{% endif %}" style="display: none;">join-or-log-in</a>
|
||||
{%- endif %}
|
||||
|
||||
|
||||
|
|
|
|||
39
remarkbox/templates/verification-challenge.j2
Normal file
39
remarkbox/templates/verification-challenge.j2
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{% extends request.base_funnel_template -%}
|
||||
{% block title %}verify code | {{ request.domain }}{% endblock -%}
|
||||
{% block content %}
|
||||
<section class="one-column">
|
||||
<section class="log-in-form well">
|
||||
<h2>Verification Code</h2>
|
||||
<b>Please enter code to log in.<b>
|
||||
<br>
|
||||
<br>
|
||||
<form method="post" action="/verification-challenge">
|
||||
<input
|
||||
name = "email"
|
||||
type = "hidden"
|
||||
id = "email_input"
|
||||
class = "common-text-input"
|
||||
value = "{{ email }}"
|
||||
placeholder = "Your Email Address" />
|
||||
|
||||
<input
|
||||
name = "raw-otp"
|
||||
type = "number"
|
||||
id = "raw-otp"
|
||||
class = "common-text-input"
|
||||
tabindex = "1"
|
||||
value = "{% if raw_otp %}{{ raw_otp }}{% endif %}"
|
||||
placeholder = "6 digit challenge verification code"
|
||||
required
|
||||
autofocus />
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<input type="submit" name="submit" id="submit" value="verify code" required />
|
||||
|
||||
</form>
|
||||
<br/>
|
||||
</section>
|
||||
</section>
|
||||
{%- endblock -%}
|
||||
|
|
@ -38,7 +38,7 @@ class TestUser(unittest.TestCase):
|
|||
|
||||
def test_new_password(self):
|
||||
raw_password = self.user.new_password()
|
||||
self.assertGreater(len(raw_password), 30)
|
||||
self.assertEqual(len(raw_password), 6)
|
||||
|
||||
def test_check_password_success(self):
|
||||
raw_password = self.user.new_password()
|
||||
|
|
|
|||
|
|
@ -102,8 +102,8 @@ class UnauthenticatedFunctionalTests(FunctionalTests):
|
|||
self.assertIn(b"The resource was found at", redirect_res2.body)
|
||||
|
||||
res = redirect_res2.follow()
|
||||
self.assertIn(b"test title", res.body)
|
||||
self.assertIn(b"test data", res.body)
|
||||
self.assertIn(b"Your post was successful!", res.body)
|
||||
self.assertIn(b"We just sent a link to test@example.com. Check email to log in.", res.body)
|
||||
|
||||
def test_new_thread_without_email(self):
|
||||
redirect_res = self.testapp.post(
|
||||
|
|
@ -210,8 +210,8 @@ class AuthenticatedFunctionalTests(FunctionalTests):
|
|||
|
||||
def _log_in_test_user(self, test_creds):
|
||||
# log in user.
|
||||
res_login = self.testapp.get(
|
||||
"/join-or-log-in?email={}&raw-otp={}".format(*test_creds)
|
||||
res_login = self.testapp.post(
|
||||
"/verification-challenge?email={}&raw-otp={}&submit".format(*test_creds)
|
||||
)
|
||||
# attach csrf to class.
|
||||
res_csrf = self.testapp.get("/")
|
||||
|
|
@ -219,10 +219,8 @@ class AuthenticatedFunctionalTests(FunctionalTests):
|
|||
return res_login
|
||||
|
||||
def test_log_in(self):
|
||||
redirect_res1 = self._log_in_test_user(self.test_creds1)
|
||||
redirect_res2 = redirect_res1.follow()
|
||||
redirect_res3 = redirect_res2.follow()
|
||||
self.assertIn(self.test_user1.name.encode("utf-8"), redirect_res3.body)
|
||||
res = self._log_in_test_user(self.test_creds1)
|
||||
self.assertIn(self.test_user1.name.encode("utf-8"), res.body)
|
||||
|
||||
def test_log_in_or_join_creates_default_watcher(self):
|
||||
"""This tests makes sure a default watcher is created for new users."""
|
||||
|
|
|
|||
|
|
@ -105,12 +105,15 @@ def get_join_or_log_in_route_uri(request, return_to=""):
|
|||
)
|
||||
|
||||
|
||||
nodes_pending_verify = lambda request: request.session.get("nodes_pending_verify", [])
|
||||
def nodes_pending_verify(request):
|
||||
if "nodes_pending_verify" not in request.session:
|
||||
request.session["nodes_pending_verify"] = []
|
||||
return request.session["nodes_pending_verify"]
|
||||
|
||||
|
||||
def set_node_to_pending_in_session(request, node):
|
||||
"""Update session to make node go into pending_verify state."""
|
||||
nodes_pending_verify(request).append(str(node.id))
|
||||
nodes_pending_verify(request).append(str(node.id_without_dashes))
|
||||
|
||||
|
||||
def verify_pending_nodes_in_session(request, user):
|
||||
|
|
@ -123,6 +126,6 @@ def verify_pending_nodes_in_session(request, user):
|
|||
if user == node.user:
|
||||
node.verified = True
|
||||
request.dbsession.add(node)
|
||||
still_pending_verify.remove(str(node.id))
|
||||
request.dbsession.flush()
|
||||
still_pending_verify.remove(str(node.id_without_dashes))
|
||||
request.session["nodes_pending_verify"] = still_pending_verify
|
||||
request.dbsession.flush()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
import re
|
||||
|
||||
from pyramid.view import view_config
|
||||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
|
||||
from remarkbox.models import get_node_by_uri, get_node_by_id
|
||||
|
||||
from remarkbox.lib.mail import send_otp_email
|
||||
from remarkbox.models.user import get_or_create_user_by_email
|
||||
|
||||
from remarkbox.lib.mail import send_verification_digits_to_email
|
||||
|
||||
from . import get_referer_or_home, get_embed_route_uri, verify_pending_nodes_in_session
|
||||
|
||||
|
||||
|
||||
@view_config(route_name="log-out")
|
||||
@view_config(route_name="embed-log-out")
|
||||
def log_out(request):
|
||||
|
|
@ -17,6 +22,7 @@ def log_out(request):
|
|||
request.session["authenticated_user_id"] = None
|
||||
return HTTPFound(uri)
|
||||
|
||||
'''
|
||||
|
||||
# disable CSRF checking for iframe embedded version of this view.
|
||||
# If a client has 3rd party cookies disabled this security feature causes
|
||||
|
|
@ -31,11 +37,7 @@ def join_or_log_in(request):
|
|||
It uses "password-less" authentication by sending OTP (one-time-password)
|
||||
links to the user's email address.
|
||||
"""
|
||||
# get the return_to uri from posted parameters.
|
||||
return_to = request.params.get("return-to", "")
|
||||
|
||||
# get the return_to uri from posted parameters.
|
||||
thread_uri = request.params.get("thread_uri", "")
|
||||
_email_regex = re.compile("^[^@]+@[^@]+\.[^.@]+$")
|
||||
|
||||
# get the raw OTP (one-time-password) from posted parameters.
|
||||
raw_otp = request.params.get("raw-otp", "")
|
||||
|
|
@ -43,6 +45,11 @@ def join_or_log_in(request):
|
|||
# get the email_id from posted parameters.
|
||||
email_id = request.params.get("email-id", "")
|
||||
|
||||
if email_id and _email_regex.match(email_id) is None:
|
||||
# posted email does not pass regex, set it to None.
|
||||
email_id = ""
|
||||
request.session.flash(("That email address is invalid.", "error"))
|
||||
|
||||
if request.spam:
|
||||
return request.spam
|
||||
|
||||
|
|
@ -50,8 +57,6 @@ def join_or_log_in(request):
|
|||
|
||||
if request.user.authenticated:
|
||||
# user already authenticated, return early.
|
||||
if return_to:
|
||||
return HTTPFound(return_to)
|
||||
return HTTPFound(get_referer_or_home(request))
|
||||
|
||||
user = request.user
|
||||
|
|
@ -72,7 +77,7 @@ def join_or_log_in(request):
|
|||
request.dbsession.add(user)
|
||||
request.dbsession.flush()
|
||||
|
||||
return HTTPFound(return_to)
|
||||
return HTTPFound("/")
|
||||
|
||||
if user.throttle_password():
|
||||
msg = (
|
||||
|
|
@ -86,15 +91,8 @@ def join_or_log_in(request):
|
|||
request.dbsession.add(user)
|
||||
request.dbsession.flush()
|
||||
|
||||
email_return_to = return_to
|
||||
|
||||
if thread_uri:
|
||||
if "#" not in thread_uri:
|
||||
thread_uri = thread_uri + "#remarkbox-div"
|
||||
email_return_to = thread_uri
|
||||
|
||||
# email user the one-time-password and flash message.
|
||||
send_otp_email(request, user.email, raw_otp, email_return_to)
|
||||
send_otp_email(request, user.email, raw_otp)
|
||||
|
||||
msg = (
|
||||
"We just sent a link to {}. Click it to log in.".format(user.email),
|
||||
|
|
@ -126,5 +124,113 @@ def join_or_log_in(request):
|
|||
# 'the_title' : 'join or log in',
|
||||
"title": "join or log in",
|
||||
"return_to": return_to,
|
||||
"thread_uri": thread_uri,
|
||||
}
|
||||
|
||||
'''
|
||||
|
||||
|
||||
#@view_config(route_name="join-or-log-in", renderer="join-or-log-in.j2")
|
||||
@view_config(route_name="basic-join-or-log-in", renderer="join-or-log-in.j2", require_csrf=False)
|
||||
@view_config(route_name="embed-join-or-log-in", renderer="join-or-log-in.j2", require_csrf=False)
|
||||
def join_or_log_in(request):
|
||||
"""
|
||||
This view handles user registration, verification, and log in.
|
||||
It uses "password-less" authentication by sending 6 digit
|
||||
OTP (one-time-password) tokens to email addresses to verify both the email
|
||||
address & to authenticate the device displaying the challenge input field.
|
||||
"""
|
||||
_email_regex = re.compile("^[^@]+@[^@]+\.[^.@]+$")
|
||||
|
||||
# get the raw OTP (one-time-password) from posted parameters.
|
||||
raw_otp = request.params.get("raw-otp", "")
|
||||
|
||||
# get the email from posted parameters.
|
||||
email = request.params.get("email", "")
|
||||
|
||||
if email and _email_regex.match(email) is None:
|
||||
# posted email does not pass regex, set it to None.
|
||||
email = ""
|
||||
request.session.flash(("That email address is invalid.", "error"))
|
||||
|
||||
if request.spam:
|
||||
return request.spam
|
||||
|
||||
if request.user and request.user.authenticated:
|
||||
return HTTPFound(get_referer_or_home(request))
|
||||
|
||||
elif email:
|
||||
# get or create a User object from the posted email.
|
||||
user = get_or_create_user_by_email(request.dbsession, email)
|
||||
|
||||
if user.throttle_password():
|
||||
msg = (
|
||||
"We already sent a link to {}. Check email to log in.".format(user.email),
|
||||
"info",
|
||||
)
|
||||
|
||||
else:
|
||||
# generate a new one-time-password and save to database
|
||||
raw_otp = user.new_password()
|
||||
request.dbsession.add(user)
|
||||
request.dbsession.flush()
|
||||
|
||||
# email user the one-time-password and flash message.
|
||||
send_verification_digits_to_email(request, user.email, raw_otp)
|
||||
|
||||
msg = (
|
||||
"We just sent a link to {}. Check email to log in.".format(user.email),
|
||||
"info",
|
||||
)
|
||||
|
||||
request.session.flash(msg)
|
||||
|
||||
return HTTPFound("/verification-challenge?email={}".format(email))
|
||||
|
||||
return {
|
||||
"title": "join or log in",
|
||||
}
|
||||
|
||||
|
||||
@view_config(route_name="verification-challenge", renderer="verification-challenge.j2", require_csrf=False)
|
||||
def verification_challenge(request):
|
||||
|
||||
# get the raw OTP (one-time-password) from posted parameters.
|
||||
raw_otp = request.params.get("raw-otp", "")
|
||||
|
||||
# get the email from posted parameters.
|
||||
email = request.params.get("email", "")
|
||||
|
||||
user = None
|
||||
if email:
|
||||
# get or create a User object from the posted email.
|
||||
user = get_or_create_user_by_email(request.dbsession, email)
|
||||
|
||||
if "submit" in request.params:
|
||||
if raw_otp and user.check_password(raw_otp):
|
||||
# success: the user was verified.
|
||||
user.verified = True
|
||||
msg = ("Welcome {}".format(user.name), "success")
|
||||
request.session["authenticated_user_id"] = str(user.id)
|
||||
request.session.flash(msg)
|
||||
|
||||
# attempt to verify all nodes_pending_verify in user's session.
|
||||
verify_pending_nodes_in_session(request, user)
|
||||
|
||||
# Idempotent operation. Make certain a user has at least one reply_watcher.
|
||||
user.create_default_reply_watcher()
|
||||
|
||||
request.dbsession.add(user)
|
||||
request.dbsession.flush()
|
||||
|
||||
#return HTTPFound("/")
|
||||
|
||||
else:
|
||||
msg = ("Invalid Verification Code", "error")
|
||||
request.session.flash(msg)
|
||||
|
||||
return {
|
||||
"title": "Please Enter Verification Code",
|
||||
"email": email,
|
||||
"raw_otp": raw_otp,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue