From 45fa3e8ad02aef73464ec4ef3af9bf569b98ac37 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 28 Aug 2020 09:23:49 +0200 Subject: [PATCH] Add login information to user Signed-off-by: Thomas Citharel --- lib/graphql/resolvers/user.ex | 41 +++++++++++++++---- lib/mobilizon/users/user.ex | 16 +++++++- lib/web/auth/context.ex | 13 +++++- lib/web/auth/guardian.ex | 2 - ...28071543_add_login_information_to_user.exs | 12 ++++++ 5 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 priv/repo/migrations/20200828071543_add_login_information_to_user.exs diff --git a/lib/graphql/resolvers/user.ex b/lib/graphql/resolvers/user.ex index 027cd28e8..5c3b3b3cc 100644 --- a/lib/graphql/resolvers/user.ex +++ b/lib/graphql/resolvers/user.ex @@ -60,13 +60,17 @@ defmodule Mobilizon.GraphQL.Resolvers.User do @doc """ Login an user. Returns a token and the user """ - def login_user(_parent, %{email: email, password: password}, _resolution) do - case Authenticator.authenticate(email, password) do - {:ok, - %{access_token: _access_token, refresh_token: _refresh_token, user: _user} = - user_and_tokens} -> - {:ok, user_and_tokens} - + def login_user(_parent, %{email: email, password: password}, %{context: context}) do + with {:ok, + %{ + access_token: _access_token, + refresh_token: _refresh_token, + user: %User{} = user + } = user_and_tokens} <- Authenticator.authenticate(email, password), + {:ok, %User{} = user} <- update_user_login_information(user, context), + user_and_tokens <- Map.put(user_and_tokens, :user, user) do + {:ok, user_and_tokens} + else {:error, :user_not_found} -> {:error, "No user with this email was found"} @@ -81,11 +85,12 @@ defmodule Mobilizon.GraphQL.Resolvers.User do @doc """ Refresh a token """ - def refresh_token(_parent, %{refresh_token: refresh_token}, _context) do + def refresh_token(_parent, %{refresh_token: refresh_token}, context) do with {:ok, user, _claims} <- Auth.Guardian.resource_from_token(refresh_token), {:ok, _old, {exchanged_token, _claims}} <- Auth.Guardian.exchange(refresh_token, ["access", "refresh"], "access"), - {:ok, refresh_token} <- Authenticator.generate_refresh_token(user) do + {:ok, refresh_token} <- Authenticator.generate_refresh_token(user), + {:ok, %User{}} <- update_user_login_information(user, context) do {:ok, %{access_token: exchanged_token, refresh_token: refresh_token}} else {:error, message} -> @@ -513,4 +518,22 @@ defmodule Mobilizon.GraphQL.Resolvers.User do {:ok, user} end end + + @spec update_user_login_information(User.t(), map()) :: + {:ok, User.t()} | {:error, Ecto.Changeset.t()} + defp update_user_login_information( + %User{current_sign_in_at: current_sign_in_at, current_sign_in_ip: current_sign_in_ip} = + user, + context + ) do + with current_ip <- Map.get(context, :ip), + now <- DateTime.utc_now() do + Users.update_user(user, %{ + last_sign_in_at: current_sign_in_at || now, + last_sign_in_ip: current_sign_in_ip || current_ip, + current_sign_in_ip: current_ip, + current_sign_in_at: now + }) + end + end end diff --git a/lib/mobilizon/users/user.ex b/lib/mobilizon/users/user.ex index 51b737626..3faab41d3 100644 --- a/lib/mobilizon/users/user.ex +++ b/lib/mobilizon/users/user.ex @@ -27,7 +27,11 @@ defmodule Mobilizon.Users.User do default_actor: Actor.t(), disabled: boolean(), actors: [Actor.t()], - feed_tokens: [FeedToken.t()] + feed_tokens: [FeedToken.t()], + last_sign_in_at: DateTime.t(), + last_sign_in_ip: String.t(), + current_sign_in_ip: String.t(), + current_sign_in_at: DateTime.t() } @required_attrs [:email] @@ -44,7 +48,11 @@ defmodule Mobilizon.Users.User do :locale, :unconfirmed_email, :disabled, - :provider + :provider, + :last_sign_in_at, + :last_sign_in_ip, + :current_sign_in_ip, + :current_sign_in_at ] @attrs @required_attrs ++ @optional_attrs @@ -72,6 +80,10 @@ defmodule Mobilizon.Users.User do field(:locale, :string, default: "en") field(:disabled, :boolean, default: false) field(:provider, :string) + field(:last_sign_in_at, :utc_datetime) + field(:last_sign_in_ip, :string) + field(:current_sign_in_ip, :string) + field(:current_sign_in_at, :utc_datetime) belongs_to(:default_actor, Actor) has_many(:actors, Actor) diff --git a/lib/web/auth/context.ex b/lib/web/auth/context.ex index 209afd8fa..9f41dd61f 100644 --- a/lib/web/auth/context.ex +++ b/lib/web/auth/context.ex @@ -15,10 +15,10 @@ defmodule Mobilizon.Web.Auth.Context do def call(%{assigns: %{ip: _}} = conn, _opts), do: conn def call(conn, _opts) do - set_user_and_ip_in_context(conn) + set_user_information_in_context(conn) end - def set_user_and_ip_in_context(conn) do + def set_user_information_in_context(conn) do context = %{ip: conn.remote_ip |> :inet.ntoa() |> to_string()} context = @@ -30,6 +30,15 @@ defmodule Mobilizon.Web.Auth.Context do context end + context = + case get_req_header(conn, "user-agent") do + [user_agent | _] -> + Map.put(context, :user_agent, user_agent) + + _ -> + context + end + put_private(conn, :absinthe, %{context: context}) end end diff --git a/lib/web/auth/guardian.ex b/lib/web/auth/guardian.ex index 070141a3d..8797dd06c 100644 --- a/lib/web/auth/guardian.ex +++ b/lib/web/auth/guardian.ex @@ -44,8 +44,6 @@ defmodule Mobilizon.Web.Auth.Guardian do end def after_encode_and_sign(resource, claims, token, _options) do - Logger.debug(fn -> "after_encode_and_sign #{inspect(claims)}" end) - with {:ok, _} <- Guardian.DB.after_encode_and_sign(resource, claims["typ"], claims, token) do {:ok, token} end diff --git a/priv/repo/migrations/20200828071543_add_login_information_to_user.exs b/priv/repo/migrations/20200828071543_add_login_information_to_user.exs new file mode 100644 index 000000000..9a2b45432 --- /dev/null +++ b/priv/repo/migrations/20200828071543_add_login_information_to_user.exs @@ -0,0 +1,12 @@ +defmodule Mobilizon.Storage.Repo.Migrations.AddLoginInformationToUser do + use Ecto.Migration + + def change do + alter table(:users) do + add(:last_sign_in_at, :utc_datetime, null: true) + add(:last_sign_in_ip, :string, null: true) + add(:current_sign_in_ip, :string, null: true) + add(:current_sign_in_at, :utc_datetime, null: true) + end + end +end