Repository: CTFd/CTFd

Issue:
Reduce usage of the user-side session object
The user side session is very difficult to modify. We should only store the user's ID in it. 

1. Next major version, cut out all session data besides the user's ID
2. Make a function that takes user ID and returns the user's properties (name, email, role)
3. Cache that function with arguments
4. Invalidate that function's cache key when the user's properties change


Discussion:
All user properties should be relegated to cache items. Banned, verified, etc. 
With this approach it might be possible to push sessions back to the user's cookie. Not sure if there would be benefits or not. 

Resolving patch:
diff --git a/CTFd/constants/sessions.py b/CTFd/constants/sessions.py
new file mode 100644
--- /dev/null
+++ b/CTFd/constants/sessions.py
@@ -0,0 +1,18 @@
+from flask import session
+
+
+class _SessionWrapper:
+    @property
+    def id(self):
+        return session.get("id", 0)
+
+    @property
+    def nonce(self):
+        return session.get("nonce")
+
+    @property
+    def hash(self):
+        return session.get("hash")
+
+
+Session = _SessionWrapper()
diff --git a/CTFd/utils/initialization/__init__.py b/CTFd/utils/initialization/__init__.py
--- a/CTFd/utils/initialization/__init__.py
+++ b/CTFd/utils/initialization/__init__.py
@@ -10,6 +10,7 @@
 from CTFd.cache import clear_user_recent_ips
 from CTFd.constants.config import Configs
 from CTFd.constants.plugins import Plugins
+from CTFd.constants.sessions import Session
 from CTFd.exceptions import UserNotFoundException, UserTokenExpiredException
 from CTFd.models import Tracking, db
 from CTFd.utils import config, get_config, markdown
@@ -91,6 +92,7 @@ def init_template_globals(app):
     app.jinja_env.globals.update(get_ip=get_ip)
     app.jinja_env.globals.update(Configs=Configs)
     app.jinja_env.globals.update(Plugins=Plugins)
+    app.jinja_env.globals.update(Session=Session)
 
 
 def init_logs(app):
@@ -154,12 +156,6 @@ def init_events(app):
 
 
 def init_request_processors(app):
-    @app.context_processor
-    def inject_user():
-        if session:
-            return dict(session)
-        return dict()
-
     @app.url_defaults
     def inject_theme(endpoint, values):
         if "theme" not in values and app.url_map.is_endpoint_expecting(
diff --git a/CTFd/utils/security/auth.py b/CTFd/utils/security/auth.py
--- a/CTFd/utils/security/auth.py
+++ b/CTFd/utils/security/auth.py
@@ -13,8 +13,6 @@
 
 def login_user(user):
     session["id"] = user.id
-    session["name"] = user.name
-    session["email"] = user.email
     session["nonce"] = generate_nonce()
     session["hash"] = hmac(user.password)
 
@@ -24,7 +22,6 @@ def login_user(user):
 
 def update_user(user):
     session["id"] = user.id
-    session["name"] = user.name
     session["email"] = user.email
     session["hash"] = hmac(user.password)
 


Test patch:
diff --git a/tests/challenges/test_dynamic.py b/tests/challenges/test_dynamic.py
--- a/tests/challenges/test_dynamic.py
+++ b/tests/challenges/test_dynamic.py
@@ -212,9 +212,8 @@ def test_dynamic_challenge_loses_value_properly():
                 # We need to bypass rate-limiting so creating a fake user instead of logging in
                 with client.session_transaction() as sess:
                     sess["id"] = team_id
-                    sess["name"] = name
-                    sess["email"] = email
                     sess["nonce"] = "fake-nonce"
+                    sess["hash"] = "fake-hash"
 
                 data = {"submission": "flag", "challenge_id": 1}
 
@@ -304,9 +303,8 @@ def test_dynamic_challenge_value_isnt_affected_by_hidden_users():
                 # We need to bypass rate-limiting so creating a fake user instead of logging in
                 with client.session_transaction() as sess:
                     sess["id"] = team_id
-                    sess["name"] = name
-                    sess["email"] = email
                     sess["nonce"] = "fake-nonce"
+                    sess["hash"] = "fake-hash"
 
                 data = {"submission": "flag", "challenge_id": 1}
 
diff --git a/tests/helpers.py b/tests/helpers.py
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -144,9 +144,8 @@ def register_user(
             if raise_for_error:
                 with client.session_transaction() as sess:
                     assert sess["id"]
-                    assert sess["name"] == name
-                    assert sess["email"]
                     assert sess["nonce"]
+                    assert sess["hash"]
 
 
 def register_team(app, name="team", password="password", raise_for_error=True):
@@ -171,9 +170,8 @@ def login_as_user(app, name="user", password="password", raise_for_error=True):
             if raise_for_error:
                 with client.session_transaction() as sess:
                     assert sess["id"]
-                    assert sess["name"]
-                    assert sess["email"]
                     assert sess["nonce"]
+                    assert sess["hash"]
             return client
 
 
@@ -229,9 +227,8 @@ def login_with_mlc(
         if raise_for_error:
             with client.session_transaction() as sess:
                 assert sess["id"]
-                assert sess["name"]
-                assert sess["email"]
                 assert sess["nonce"]
+                assert sess["hash"]
         return client
 
 
diff --git a/tests/oauth/test_redirect.py b/tests/oauth/test_redirect.py
--- a/tests/oauth/test_redirect.py
+++ b/tests/oauth/test_redirect.py
@@ -71,9 +71,8 @@ def test_oauth_configured_flow():
         client = login_with_mlc(app)
         with client.session_transaction() as sess:
             assert sess["id"]
-            assert sess["name"]
-            assert sess["email"]
             assert sess["nonce"]
+            assert sess["hash"]
     destroy_ctfd(app)