From 42425b02260d279cd9c12fb3e625282979b9e308 Mon Sep 17 00:00:00 2001 From: Simponic Date: Fri, 30 Dec 2022 05:46:35 -0700 Subject: Add scalable session thresholds --- lib/chessh/schema/node.ex | 4 +-- lib/chessh/schema/player.ex | 7 +++++ lib/chessh/schema/player_session.ex | 52 ++++++++++++++++++++++++++++++++++--- 3 files changed, 58 insertions(+), 5 deletions(-) (limited to 'lib/chessh/schema') diff --git a/lib/chessh/schema/node.ex b/lib/chessh/schema/node.ex index 867a6e2..8834aef 100644 --- a/lib/chessh/schema/node.ex +++ b/lib/chessh/schema/node.ex @@ -5,7 +5,7 @@ defmodule Chessh.Node do @primary_key {:id, :string, []} schema "nodes" do - field(:last_start, :utc_datetime) + field(:last_start, :utc_datetime_usec) end def changeset(node, attrs) do @@ -18,7 +18,7 @@ defmodule Chessh.Node do nil -> %Chessh.Node{id: node_id} node -> node end - |> Chessh.Node.changeset(%{last_start: DateTime.utc_now()}) + |> changeset(%{last_start: DateTime.utc_now()}) |> Repo.insert_or_update() end end diff --git a/lib/chessh/schema/player.ex b/lib/chessh/schema/player.ex index 4b6a324..8eaffee 100644 --- a/lib/chessh/schema/player.ex +++ b/lib/chessh/schema/player.ex @@ -9,11 +9,18 @@ defmodule Chessh.Player do field(:password, :string, virtual: true) field(:hashed_password, :string) + field(:authentications, :integer, default: 0) + has_many(:keys, Chessh.Key) timestamps() end + def authentications_changeset(player, attrs) do + player + |> cast(attrs, [:authentications]) + end + def registration_changeset(player, attrs, opts \\ []) do player |> cast(attrs, [:username, :password]) diff --git a/lib/chessh/schema/player_session.ex b/lib/chessh/schema/player_session.ex index 84f15ee..ce3fc1f 100644 --- a/lib/chessh/schema/player_session.ex +++ b/lib/chessh/schema/player_session.ex @@ -1,10 +1,12 @@ defmodule Chessh.PlayerSession do - alias Chessh.Repo + alias Chessh.{Repo, Player, PlayerSession, Utils} use Ecto.Schema import Ecto.{Query, Changeset} + require Logger schema "player_sessions" do - field(:login, :utc_datetime) + field(:process, :string) + field(:login, :utc_datetime_usec) belongs_to(:node, Chessh.Node, type: :string) belongs_to(:player, Chessh.Player) @@ -17,7 +19,7 @@ defmodule Chessh.PlayerSession do def concurrent_sessions(player) do Repo.aggregate( - from(p in Chessh.PlayerSession, + from(p in PlayerSession, where: p.player_id == ^player.id ), :count @@ -31,4 +33,48 @@ defmodule Chessh.PlayerSession do ) ) end + + def player_within_concurrent_sessions_and_satisfies(username, auth_fn) do + max_sessions = + Application.get_env(:chessh, RateLimits) + |> Keyword.get(:max_concurrent_user_sessions) + + Repo.transaction(fn -> + case Repo.one( + from(p in Player, + where: p.username == ^String.Chars.to_string(username), + lock: "FOR UPDATE" + ) + ) do + nil -> + Logger.error("Player with username #{username} does not exist") + send(self(), {:authed, false}) + + player -> + authed = + auth_fn.(player) && + PlayerSession.concurrent_sessions(player) < max_sessions + + Repo.insert(%PlayerSession{ + login: DateTime.utc_now(), + node_id: System.fetch_env!("NODE_ID"), + player: player, + # TODO: This PID may be wrong - need to determine if this PID is shared with disconnectfun + process: Utils.pid_to_str(self()) + }) + + player + |> Player.authentications_changeset(%{authentications: player.authentications + 1}) + |> Repo.update() + + send(self(), {:authed, authed}) + end + end) + + receive do + {:authed, authed} -> authed + after + 3_000 -> false + end + end end -- cgit v1.2.3-70-g09d2