Repository: CTFd/CTFd

Issue:
Advice on E-mail Mechanism
It seems that when E-mail server and E-mail confirmation are enabled, CTFd will send a new E-mail to the E-mail address input by the new user every time he or she logged in without confirmation yet (At least my SMTP server does like that, I will be apologize if it is my own wrong configuration with my own server). I think it will be better that only one E-mail will be sent at the first time new user signs up, and provide a button allowing the unconfirmed user to receive a new confirmation E-mail if needed.

Anyway, Thanks for your excellent project!


Discussion:
Hi @brant-ruan I apologize, I let this slip a bit. 

So the way email confirmations work is every time a user is directed to `/confirm`, CTFd will send out the confirmation email. 

I think you have a good point that it should probably be behind a button.

I think it should probably be put under a `POST` to `/confirm` and then the register route will send the confirmation email on its own and the redirect to `/confirm` will be purely cosmetic. 
@ColdHeat 

Agree with you. Let the register route encapsulate the confirmation logic is a good idea.

Resolving patch:
diff --git a/CTFd/auth.py b/CTFd/auth.py
--- a/CTFd/auth.py
+++ b/CTFd/auth.py
@@ -18,8 +18,11 @@
 @auth.route('/confirm/<data>', methods=['GET'])
 def confirm_user(data=None):
     if not utils.get_config('verify_emails'):
+        # If the CTF doesn't care about confirming email addresses then redierct to challenges
         return redirect(url_for('challenges.challenges_view'))
-    if data and request.method == "GET":  # User is confirming email account
+
+    # User is confirming email account
+    if data and request.method == "GET":
         try:
             s = Signer(app.config['SECRET_KEY'])
             email = s.unsign(urllib.unquote_plus(data.decode('base64')))
@@ -36,15 +39,28 @@ def confirm_user(data=None):
         if utils.authed():
             return redirect(url_for('challenges.challenges_view'))
         return redirect(url_for('auth.login'))
-    if not data and request.method == "GET":  # User has been directed to the confirm page because his account is not verified
-        if not utils.authed():
-            return redirect(url_for('auth.login'))
-        team = Teams.query.filter_by(id=session['id']).first_or_404()
-        if team.verified:
-            return redirect(url_for('views.profile'))
-        else:
-            utils.verify_email(team.email)
-        return render_template('confirm.html', team=team)
+
+    # User is trying to start or restart the confirmation flow
+    if not utils.authed():
+        return redirect(url_for('auth.login'))
+
+    team = Teams.query.filter_by(id=session['id']).first_or_404()
+
+    if data is None:
+        if request.method == "POST":
+            # User wants to resend their confirmation email
+            if team.verified:
+                return redirect(url_for('views.profile'))
+            else:
+                utils.verify_email(team.email)
+            return render_template('confirm.html', team=team, infos=['Your confirmation email has been resent!'])
+        elif request.method == "GET":
+            # User has been directed to the confirm page
+            team = Teams.query.filter_by(id=session['id']).first_or_404()
+            if team.verified:
+                # If user is already verified, redirect to their profile
+                return redirect(url_for('views.profile'))
+            return render_template('confirm.html', team=team)
 
 
 @auth.route('/reset_password', methods=['POST', 'GET'])
@@ -136,6 +152,9 @@ def register():
                     logger.warn("[{0}] {1} registered (UNCONFIRMED) with {2}".format(time.strftime("%m/%d/%Y %X"),
                                                                                      request.form['name'].encode('utf-8'),
                                                                                      request.form['email'].encode('utf-8')))
+
+                    utils.verify_email(team.email)
+
                     return redirect(url_for('auth.confirm_user'))
                 else:  # Don't care about confirming users
                     if utils.can_send_mail():  # We want to notify the user that they have registered.


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -3,10 +3,20 @@
 
 from tests.helpers import *
 from CTFd.models import ip2long, long2ip
-from CTFd.utils import override_template
+from CTFd.utils import get_config, set_config, override_template
 import json
 
 
+def test_get_config_and_set_config():
+    """Does get_config and set_config work properly"""
+    app = create_ctfd()
+    with app.app_context():
+        assert get_config('setup') == True
+        config = set_config('TEST_CONFIG_ENTRY', 'test_config_entry')
+        assert config.value == 'test_config_entry'
+        assert get_config('TEST_CONFIG_ENTRY') == 'test_config_entry'
+
+
 def test_ip2long_ipv4():
     """Does ip2long work properly for ipv4 addresses"""
     assert ip2long('127.0.0.1') == 2130706433