From a1a4d420a8e21666cac588ca5e51529d6c66a47e Mon Sep 17 00:00:00 2001 From: James Smith Date: Tue, 16 Jul 2024 00:14:57 +0000 Subject: [PATCH 1/3] Add restriction to number of symbols allowed in a username. Adds an additional check at registration/rename time, which considers how many times each non-alphanumeric character appears in a requested username. If any individual symbol appears three or more times, then the new username is rejected (and registration / renaming fails). --- lib/teiserver/data/cache_user.ex | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/teiserver/data/cache_user.ex b/lib/teiserver/data/cache_user.ex index 47f9cef4f..bed35c74d 100644 --- a/lib/teiserver/data/cache_user.ex +++ b/lib/teiserver/data/cache_user.ex @@ -94,6 +94,11 @@ defmodule Teiserver.CacheUser do |> Regex.replace(name, "") end + @spec check_symbol_limit(String.t()) :: Boolean.t() + def check_symbol_limit(name) do + name |> String.replace(~r/[[:alnum:]]/, "") |> String.graphemes |> Enum.frequencies |> Enum.filter(fn {_, val} -> (val > 2) end) |> Enum.count() |> Kernel.>(0) + end + @spec encrypt_password(any) :: binary | {binary, binary, {any, any, any, any, any}} def encrypt_password(password) do Argon2.hash_pwd_salt(password) @@ -184,6 +189,9 @@ defmodule Teiserver.CacheUser do clean_name(name) != name -> {:failure, "Invalid characters in name (only a-z, A-Z, 0-9, [, ] and _ allowed)"} + check_symbol_limit(name) -> + {:error, "Too many repeated symbols in name"} + get_user_by_name(name) -> {:failure, "Username already taken"} @@ -230,6 +238,9 @@ defmodule Teiserver.CacheUser do clean_name(name) != name -> {:error, "Invalid characters in name (only a-z, A-Z, 0-9, [, ] and _ allowed)"} + check_symbol_limit(name) -> + {:error, "Too many repeated symbols in name"} + get_user_by_name(name) -> {:error, "Username already taken"} @@ -432,6 +443,9 @@ defmodule Teiserver.CacheUser do clean_name(new_name) != new_name -> {:error, "Invalid characters in name (only a-z, A-Z, 0-9, [, ] allowed)"} + check_symbol_limit(new_name) -> + {:error, "Too many repeated symbols in name"} + get_user_by_name(new_name) && get_user_by_name(new_name).name |> String.downcase() == String.downcase(new_name) -> {:error, "Username already taken"} From ef96b95d8bcc247ecfcb18bbd402031a5f5467a7 Mon Sep 17 00:00:00 2001 From: James Smith Date: Tue, 16 Jul 2024 16:00:11 +0000 Subject: [PATCH 2/3] Update cache_user.ex to pass style checks. --- lib/teiserver/data/cache_user.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/teiserver/data/cache_user.ex b/lib/teiserver/data/cache_user.ex index bed35c74d..368fa4dd2 100644 --- a/lib/teiserver/data/cache_user.ex +++ b/lib/teiserver/data/cache_user.ex @@ -96,7 +96,13 @@ defmodule Teiserver.CacheUser do @spec check_symbol_limit(String.t()) :: Boolean.t() def check_symbol_limit(name) do - name |> String.replace(~r/[[:alnum:]]/, "") |> String.graphemes |> Enum.frequencies |> Enum.filter(fn {_, val} -> (val > 2) end) |> Enum.count() |> Kernel.>(0) + name + |> String.replace(~r/[[:alnum:]]/, "") + |> String.graphemes() + |> Enum.frequencies() + |> Enum.filter(fn {_, val} -> val > 2 end) + |> Enum.count() + |> Kernel.>(0) end @spec encrypt_password(any) :: binary | {binary, binary, {any, any, any, any, any}} From 3407253def85c1999012b5f1d3725c850fd058d0 Mon Sep 17 00:00:00 2001 From: James Smith Date: Tue, 16 Jul 2024 17:52:12 +0000 Subject: [PATCH 3/3] Add test for username symbol restrictions. --- test/teiserver/data/user_test.exs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/teiserver/data/user_test.exs b/test/teiserver/data/user_test.exs index 5c91ad166..8353270ba 100644 --- a/test/teiserver/data/user_test.exs +++ b/test/teiserver/data/user_test.exs @@ -133,4 +133,24 @@ defmodule Teiserver.Data.UserTest do assert result == expected, message: "Bad result for email '#{value}'" end end + + test "username symbol restrictions" do + data = [ + {"abc123", false}, + {"[abc]_123", false}, + {"a_b_c[[123]]", false}, + {"[[__]]", false}, + {"___", true}, + {"[[[", true}, + {"]]]", true}, + {"_abc_123_", true}, + {"[]_[]_[]_", true}, + {"[[[abc123]]]", true} + ] + + for {value, expected} <- data do + result = CacheUser.check_symbol_limit(value) + assert result == expected, message: "Bad result for username '#{value}'" + end + end end