Repository: CTFd/CTFd

Issue:
500 when calling `api/teams/me` under user mode
<!--
If this is a bug report please fill out the template below.

If this is a feature request please describe the behavior that you'd like to see.
-->

**Environment**:

- CTFd Version/Commit: 3.5.0
- Operating System: irrelevant
- Web Browser and Version: irrelevant

**What happened?**

`curl /api/v1/teams/me --with-cookie -i` > 500 Internal Server Error

**What did you expect to happen?**

`{"success": False, "message": "proper reason"}`

**Any associated stack traces or error logs**

https://github.com/CTFd/CTFd/blob/6d74c1c4267b4dc395949a65331c369e0737d16f/CTFd/api/v1/teams.py#L262-L270

```log
ctfd_1     |   File "/opt/CTFd/CTFd/utils/decorators/__init__.py", line 87, in authed_only_wrapper
ctfd_1     |     return f(*args, **kwargs)
ctfd_1     |   File "/opt/CTFd/CTFd/utils/decorators/__init__.py", line 153, in require_team_wrapper
ctfd_1     |     return f(*args, **kwargs)
ctfd_1     |   File "/opt/CTFd/CTFd/api/v1/teams.py", line 269, in get
ctfd_1     |     response.data["place"] = team.place
ctfd_1     | AttributeError: 'NoneType' object has no attribute 'place'
```


Discussion:
Interesting... I see that this route does have the `require_team()` decorator so I feel like this shouldn't happen. 

https://github.com/CTFd/CTFd/blob/6d74c1c4267b4dc395949a65331c369e0737d16f/CTFd/api/v1/teams.py#L251

I would accept a PR for this if you can track it down. 
> Interesting... I see that this route does have the `require_team()` decorator so I feel like this shouldn't happen.
> 
> https://github.com/CTFd/CTFd/blob/6d74c1c4267b4dc395949a65331c369e0737d16f/CTFd/api/v1/teams.py#L251
> 
> I would accept a PR for this if you can track it down.

`require_team` doesn't assert team mode, `require_team_mode` does.
yeah it should though, simple fix

Resolving patch:
diff --git a/CTFd/api/v1/teams.py b/CTFd/api/v1/teams.py
--- a/CTFd/api/v1/teams.py
+++ b/CTFd/api/v1/teams.py
@@ -18,6 +18,7 @@
 from CTFd.schemas.teams import TeamSchema
 from CTFd.utils import get_config
 from CTFd.utils.decorators import admins_only, authed_only, require_team
+from CTFd.utils.decorators.modes import require_team_mode
 from CTFd.utils.decorators.visibility import (
     check_account_visibility,
     check_score_visibility,
@@ -50,6 +51,8 @@ class TeamListSuccessResponse(PaginatedAPIListSuccessResponse):
 
 @teams_namespace.route("")
 class TeamList(Resource):
+    method_decorators = [require_team_mode]
+
     @check_account_visibility
     @teams_namespace.doc(
         description="Endpoint to get Team objects in bulk",
@@ -159,6 +162,8 @@ def post(self):
 @teams_namespace.route("/<int:team_id>")
 @teams_namespace.param("team_id", "Team ID")
 class TeamPublic(Resource):
+    method_decorators = [require_team_mode]
+
     @check_account_visibility
     @teams_namespace.doc(
         description="Endpoint to get a specific Team object",
@@ -247,6 +252,8 @@ def delete(self, team_id):
 @teams_namespace.route("/me")
 @teams_namespace.param("team_id", "Current Team")
 class TeamPrivate(Resource):
+    method_decorators = [require_team_mode]
+
     @authed_only
     @require_team
     @teams_namespace.doc(
@@ -376,6 +383,8 @@ def delete(self):
 
 @teams_namespace.route("/me/members")
 class TeamPrivateMembers(Resource):
+    method_decorators = [require_team_mode]
+
     @authed_only
     @require_team
     def post(self):
@@ -397,6 +406,8 @@ def post(self):
 @teams_namespace.route("/<team_id>/members")
 @teams_namespace.param("team_id", "Team ID")
 class TeamMembers(Resource):
+    method_decorators = [require_team_mode]
+
     @admins_only
     def get(self, team_id):
         team = Teams.query.filter_by(id=team_id).first_or_404()
@@ -485,6 +496,8 @@ def delete(self, team_id):
 
 @teams_namespace.route("/me/solves")
 class TeamPrivateSolves(Resource):
+    method_decorators = [require_team_mode]
+
     @authed_only
     @require_team
     def get(self):
@@ -504,6 +517,8 @@ def get(self):
 
 @teams_namespace.route("/me/fails")
 class TeamPrivateFails(Resource):
+    method_decorators = [require_team_mode]
+
     @authed_only
     @require_team
     def get(self):
@@ -532,6 +547,8 @@ def get(self):
 
 @teams_namespace.route("/me/awards")
 class TeamPrivateAwards(Resource):
+    method_decorators = [require_team_mode]
+
     @authed_only
     @require_team
     def get(self):
@@ -551,6 +568,8 @@ def get(self):
 @teams_namespace.route("/<team_id>/solves")
 @teams_namespace.param("team_id", "Team ID")
 class TeamPublicSolves(Resource):
+    method_decorators = [require_team_mode]
+
     @check_account_visibility
     @check_score_visibility
     def get(self, team_id):
@@ -574,6 +593,8 @@ def get(self, team_id):
 @teams_namespace.route("/<team_id>/fails")
 @teams_namespace.param("team_id", "Team ID")
 class TeamPublicFails(Resource):
+    method_decorators = [require_team_mode]
+
     @check_account_visibility
     @check_score_visibility
     def get(self, team_id):
@@ -606,6 +627,8 @@ def get(self, team_id):
 @teams_namespace.route("/<team_id>/awards")
 @teams_namespace.param("team_id", "Team ID")
 class TeamPublicAwards(Resource):
+    method_decorators = [require_team_mode]
+
     @check_account_visibility
     @check_score_visibility
     def get(self, team_id):
diff --git a/CTFd/utils/decorators/__init__.py b/CTFd/utils/decorators/__init__.py
--- a/CTFd/utils/decorators/__init__.py
+++ b/CTFd/utils/decorators/__init__.py
@@ -150,7 +150,9 @@ def require_team_wrapper(*args, **kwargs):
                     abort(403)
                 else:
                     return redirect(url_for("teams.private", next=request.full_path))
-        return f(*args, **kwargs)
+            return f(*args, **kwargs)
+        else:
+            abort(404)
 
     return require_team_wrapper
 


Test patch: