diff --git a/server/exceptions.py b/server/exceptions.py
index 5c5a103d6..48095d2a4 100644
--- a/server/exceptions.py
+++ b/server/exceptions.py
@@ -34,7 +34,9 @@ def __init__(self, ban_expiry, ban_reason, *args, **kwargs):
def message(self):
return (
f"You are banned from FAF {self._ban_duration_text()}.
"
- f"Reason:
{self.ban_reason}"
+ f"Reason:
{self.ban_reason}
"
+ "If you would like to appeal this ban, please send an email to: "
+ "moderation@faforever.com"
)
def _ban_duration_text(self):
diff --git a/server/lobbyconnection.py b/server/lobbyconnection.py
index c536c758d..e69613c89 100644
--- a/server/lobbyconnection.py
+++ b/server/lobbyconnection.py
@@ -516,16 +516,37 @@ async def command_auth(self, message):
async with self._db.acquire() as conn:
result = await conn.execute(
- select(t_login.c.login)
+ select(
+ t_login.c.login,
+ lobby_ban.c.reason,
+ lobby_ban.c.expires_at
+ )
+ .select_from(t_login.outerjoin(lobby_ban))
.where(t_login.c.id == player_id)
+ .order_by(lobby_ban.c.expires_at.desc())
)
row = result.fetchone()
if not row:
- self._logger.warning("User id not found in database possible fraudulent token: %s", player_id)
+ self._logger.warning(
+ "User id %s not found in database! Possible fraudulent "
+ "token: %s",
+ player_id,
+ token
+ )
raise AuthenticationError("Cannot find user id", auth_method)
username = row.login
+ ban_reason = row.reason
+ ban_expiry = row.expires_at
+
+ now = datetime.utcnow()
+ if ban_reason is not None and now < ban_expiry:
+ self._logger.debug(
+ "Rejected login from banned user: %s, %s, %s",
+ player_id, username, self.session
+ )
+ raise BanError(ban_expiry, ban_reason)
# DEPRECATED: IRC passwords are handled outside of the lobby server.
# This message remains here for backwards compatibility, but the data
diff --git a/tests/integration_tests/test_login.py b/tests/integration_tests/test_login.py
index ee1e7c77b..179d71214 100644
--- a/tests/integration_tests/test_login.py
+++ b/tests/integration_tests/test_login.py
@@ -11,7 +11,7 @@
)
-async def test_server_invalid_login(lobby_server):
+async def test_server_login_invalid(lobby_server):
proto = await connect_client(lobby_server)
# Try a user that doesn't exist
await perform_login(proto, ("Cat", "epic"))
@@ -39,7 +39,46 @@ async def test_server_ban(lobby_server, user):
assert msg == {
"command": "notice",
"style": "error",
- "text": "You are banned from FAF forever.
Reason:
Test permanent ban"
+ "text": (
+ "You are banned from FAF forever.
Reason:
Test permanent ban"
+ "
If you would like to appeal this ban, please send an "
+ "email to: moderation@faforever.com"
+ )
+ }
+
+
+@pytest.mark.parametrize("user", [
+ ("Dostya", 2),
+ ("ban_long_time", 203)
+])
+async def test_server_ban_token(lobby_server, user, jwk_priv_key, jwk_kid):
+ user_name, user_id = user
+ proto = await connect_client(lobby_server)
+ await proto.send_message({
+ "command": "auth",
+ "version": "1.0.0-dev",
+ "user_agent": "faf-client",
+ "token": jwt.encode({
+ "sub": user_id,
+ "user_name": user_name,
+ "scp": ["lobby"],
+ "exp": int(time() + 1000),
+ "authorities": [],
+ "non_locked": True,
+ "jti": "",
+ "client_id": ""
+ }, jwk_priv_key, algorithm="RS256", headers={"kid": jwk_kid}),
+ "unique_id": "some_id"
+ })
+ msg = await proto.read_message()
+ assert msg == {
+ "command": "notice",
+ "style": "error",
+ "text": (
+ "You are banned from FAF forever.
Reason:
Test permanent ban"
+ "
If you would like to appeal this ban, please send an "
+ "email to: moderation@faforever.com"
+ )
}
@@ -53,7 +92,7 @@ async def test_server_ban_revoked_or_expired(lobby_server, user):
assert msg["login"] == user
-async def test_server_valid_login(lobby_server):
+async def test_server_login_valid(lobby_server):
proto = await connect_client(lobby_server)
await perform_login(proto, ("Rhiza", "puff_the_magic_dragon"))
msg = await proto.read_message()
@@ -98,7 +137,7 @@ async def test_server_valid_login(lobby_server):
}
-async def test_server_valid_login_admin(lobby_server):
+async def test_server_login_valid_admin(lobby_server):
proto = await connect_client(lobby_server)
await perform_login(proto, ("test", "test_password"))
msg = await proto.read_message()
@@ -143,7 +182,7 @@ async def test_server_valid_login_admin(lobby_server):
}
-async def test_server_valid_login_moderator(lobby_server):
+async def test_server_login_valid_moderator(lobby_server):
proto = await connect_client(lobby_server)
await perform_login(proto, ("moderator", "moderator"))
msg = await proto.read_message()
@@ -202,7 +241,7 @@ async def test_policy_server_contacted(lobby_server, policy_server, player_servi
policy_server.verify.assert_called_once()
-async def test_server_double_login(lobby_server):
+async def test_server_login_double(lobby_server):
proto = await connect_client(lobby_server)
await perform_login(proto, ("test", "test_password"))
msg = await proto.read_message()
@@ -222,7 +261,7 @@ async def test_server_double_login(lobby_server):
}
-async def test_server_valid_login_with_token(lobby_server, jwk_priv_key, jwk_kid):
+async def test_server_login_token_valid(lobby_server, jwk_priv_key, jwk_kid):
proto = await connect_client(lobby_server)
await proto.send_message({
"command": "auth",
@@ -285,7 +324,7 @@ async def test_server_valid_login_with_token(lobby_server, jwk_priv_key, jwk_kid
}
-async def test_server_login_bad_id_in_token(lobby_server, jwk_priv_key, jwk_kid):
+async def test_server_login_token_bad_id(lobby_server, jwk_priv_key, jwk_kid):
proto = await connect_client(lobby_server)
await proto.send_message({
"command": "auth",
@@ -311,7 +350,7 @@ async def test_server_login_bad_id_in_token(lobby_server, jwk_priv_key, jwk_kid)
}
-async def test_server_login_expired_token(lobby_server, jwk_priv_key, jwk_kid):
+async def test_server_login_token_expired(lobby_server, jwk_priv_key, jwk_kid):
proto = await connect_client(lobby_server)
await proto.send_message({
"command": "auth",
@@ -333,7 +372,7 @@ async def test_server_login_expired_token(lobby_server, jwk_priv_key, jwk_kid):
}
-async def test_server_login_malformed_token(lobby_server, jwk_priv_key, jwk_kid):
+async def test_server_login_token_malformed(lobby_server, jwk_priv_key, jwk_kid):
"""This scenario could only happen if the hydra signed a token that
was missing critical data"""
proto = await connect_client(lobby_server)
@@ -355,7 +394,11 @@ async def test_server_login_malformed_token(lobby_server, jwk_priv_key, jwk_kid)
}
-async def test_server_login_lobby_scope_missing(lobby_server, jwk_priv_key, jwk_kid):
+async def test_server_login_token_lobby_scope_missing(
+ lobby_server,
+ jwk_priv_key,
+ jwk_kid,
+):
"""This scenario could only happen if the hydra signed a token that
was missing critical data"""
proto = await connect_client(lobby_server)
diff --git a/tests/integration_tests/test_server.py b/tests/integration_tests/test_server.py
index a1199e2d9..3f2fe7a7b 100644
--- a/tests/integration_tests/test_server.py
+++ b/tests/integration_tests/test_server.py
@@ -793,7 +793,11 @@ async def test_server_ban_prevents_hosting(lobby_server, database, command):
assert msg == {
"command": "notice",
"style": "error",
- "text": "You are banned from FAF forever.
Reason:
Test live ban"
+ "text": (
+ "You are banned from FAF forever.
Reason:
Test live ban
"
+ "
If you would like to appeal this ban, please send an email "
+ "to: moderation@faforever.com"
+ )
}
diff --git a/tests/unit_tests/test_lobbyconnection.py b/tests/unit_tests/test_lobbyconnection.py
index 61af68d68..48e812ecc 100644
--- a/tests/unit_tests/test_lobbyconnection.py
+++ b/tests/unit_tests/test_lobbyconnection.py
@@ -1076,8 +1076,11 @@ async def test_abort_connection_if_banned(
lobbyconnection.player.id = 203
with pytest.raises(BanError) as banned_error:
await lobbyconnection.abort_connection_if_banned()
- assert banned_error.value.message() == \
+ assert banned_error.value.message() == (
"You are banned from FAF forever.
Reason:
Test permanent ban"
+ "
If you would like to appeal this ban, please send an email "
+ "to: moderation@faforever.com"
+ )
# test user who is banned for another 46 hours
lobbyconnection.player.id = 204