diff options
-rw-r--r-- | gn_auth/auth/authorisation/users/views.py | 56 | ||||
-rw-r--r-- | gn_auth/templates/emails/verify-email.html | 49 | ||||
-rw-r--r-- | gn_auth/templates/emails/verify-email.txt | 12 |
3 files changed, 115 insertions, 2 deletions
diff --git a/gn_auth/auth/authorisation/users/views.py b/gn_auth/auth/authorisation/users/views.py index feacae3..8919a6d 100644 --- a/gn_auth/auth/authorisation/users/views.py +++ b/gn_auth/auth/authorisation/users/views.py @@ -1,11 +1,22 @@ """User authorisation endpoints.""" +import sqlite3 +import secrets +import datetime import traceback from typing import Any from functools import partial from dataclasses import asdict -import sqlite3 +from email.headerregistry import Address from email_validator import validate_email, EmailNotValidError -from flask import request, jsonify, Response, Blueprint, current_app +from flask import ( + request, + jsonify, + Response, + Blueprint, + current_app, + render_template) + +from gn_auth.smtp import send_message, build_email_message from gn_auth.auth.db import sqlite3 as db from gn_auth.auth.db.sqlite3 import with_db_connection @@ -86,6 +97,46 @@ def __assert_not_logged_in__(conn: db.DbConnection): raise UserRegistrationError( "Cannot register user while authenticated") +def user_address(user: User) -> Address: + """Compute the `email.headerregistry.Address` from a `User`""" + return Address(display_name=user.name, addr_spec=user.email) + +def send_verification_email(conn, user: User) -> None: + """Send an email verification message.""" + subject="GeneNetwork: Please Verify Your Email" + verification_code = secrets.token_urlsafe(64) + generated = datetime.datetime.now() + expiration_minutes = 15 + def __render__(template): + return render_template(template, + subject=subject, + verification_code=verification_code, + verification_uri="https://please/change/this/", + expiration_minutes=expiration_minutes) + with db.cursor(conn) as cursor: + cursor.execute( + ("INSERT INTO " + "user_verification_codes(user_id, code, generated, expires) " + "VALUES (:user_id, :code, :generated, :expires)"), + { + "user_id": str(user.user_id), + "code": verification_code, + "generated": int(generated.timestamp()), + "expires": int( + (generated + + datetime.timedelta( + minutes=expiration_minutes)).timestamp()) + }) + send_message(smtp_user=current_app.config["SMTP_USER"], + smtp_passwd=current_app.config["SMTP_PASSWORD"], + message=build_email_message( + to_addresses=(user_address(user),), + subject=subject, + txtmessage=__render__("emails/verify-email.txt"), + htmlmessage=__render__("emails/verify-email.html")), + host=current_app.config["SMTP_HOST"], + port=current_app.config["SMTP_PORT"]) + @users.route("/register", methods=["POST"]) def register_user() -> Response: """Register a user.""" @@ -105,6 +156,7 @@ def register_user() -> Response: cursor, save_user( cursor, email["email"], user_name), password) assign_default_roles(cursor, user) + send_verification_email(conn, user) return jsonify(asdict(user)) except sqlite3.IntegrityError as sq3ie: current_app.logger.debug(traceback.format_exc()) diff --git a/gn_auth/templates/emails/verify-email.html b/gn_auth/templates/emails/verify-email.html new file mode 100644 index 0000000..08f1dfe --- /dev/null +++ b/gn_auth/templates/emails/verify-email.html @@ -0,0 +1,49 @@ +<html> + <head> + <meta charset="UTF-8" /> + <title>{{subject}}</title> + <style type="text/css"> + .btn { + display: inline-block; + text-align: center; + vertical-align: center; + cursor: pointer; + border-radius: 4px; + background-color: #336699; + border-color: #357ebd; + } + .verification-code{ + color: #3A3AFF; + font-weight: bolder; + } + .note { + font-weight: bold; + color: #ee55ee; + } + </style> + </head> + <body> + <p>Thank you for registering an account with GeneNetwork.</p> + + <p> + To avoid bots, we need to verify that you are an actual human. To do so, + please + <a href="{{verification_uri}}/{{verification_code}}" + class="btn"> + Click here + </a>. + </p> + + <p> + If that does not work, please log in to GeneNetwork and copy the + verification code below when requested:<br /><br /> + + <span class="verification-code">{{verification_code}}</span> + </p> + + <p class="note"> + Please note that the verification code will expire in + <strong>{{expiration_minutes}}</strong> minutes after it was generated. + </p> + </body> +</html> diff --git a/gn_auth/templates/emails/verify-email.txt b/gn_auth/templates/emails/verify-email.txt new file mode 100644 index 0000000..92a6435 --- /dev/null +++ b/gn_auth/templates/emails/verify-email.txt @@ -0,0 +1,12 @@ +{{subject}} +=============== + +Thank you for registering an account with GeneNetwork. + +To avoid bots, we need to verify that you are an actual human. To do so, please go to "{{verification_uri}}/{{verification_code}}". + +If that does not work, please log in to GeneNetwork and copy the verification code below when requested: + +{{verification_code}} + +Please note that the verification code will expire {{expiration_minutes}} minutes after it was generated. |