diff --git a/lib/real_estate/accounts.ex b/lib/real_estate/accounts.ex index b7d3a59..1604f33 100644 --- a/lib/real_estate/accounts.ex +++ b/lib/real_estate/accounts.ex @@ -6,6 +6,7 @@ defmodule RealEstate.Accounts do import Ecto.Query, warn: false alias RealEstate.Repo alias RealEstate.Accounts.{User, UserToken, UserNotifier} + alias RealEstateWeb.UserAuth ## Database getters @@ -97,6 +98,21 @@ defmodule RealEstate.Accounts do |> Repo.insert() end + def logout_user(%User{} = user) do + # Delete all user tokens + Repo.delete_all(UserToken.user_and_contexts_query(user, :all)) + + # Broadcast to all liveviews to immediately disconnect the user + RealEstateWeb.Endpoint.broadcast_from( + self(), + UserAuth.pubsub_topic(), + "logout_user", + %{ + user: user + } + ) + end + @doc """ Returns an `%Ecto.Changeset{}` for tracking user changes. diff --git a/lib/real_estate_web.ex b/lib/real_estate_web.ex index 441b3bc..e671b17 100644 --- a/lib/real_estate_web.ex +++ b/lib/real_estate_web.ex @@ -49,6 +49,19 @@ defmodule RealEstateWeb do unquote(view_helpers()) import RealEstateWeb.LiveHelpers + + alias RealEstate.Accounts.User + + @impl true + def handle_info(%{event: "logout_user", payload: %{user: %User{id: id}}}, socket) do + with %User{id: ^id} <- socket.assigns.current_user do + {:noreply, + socket + |> redirect(to: Routes.user_session_path(socket, :force_logout))} + else + _any -> {:noreply, socket} + end + end end end diff --git a/lib/real_estate_web/controllers/user_auth.ex b/lib/real_estate_web/controllers/user_auth.ex index 9207368..8664c4e 100644 --- a/lib/real_estate_web/controllers/user_auth.ex +++ b/lib/real_estate_web/controllers/user_auth.ex @@ -5,6 +5,8 @@ defmodule RealEstateWeb.UserAuth do alias RealEstate.Accounts alias RealEstateWeb.Router.Helpers, as: Routes + @pubsub_topic "user_updates" + # Make the remember me cookie valid for 60 days. # If you want bump or reduce this value, also change # the token expiry itself in UserToken. @@ -139,6 +141,11 @@ defmodule RealEstateWeb.UserAuth do end end + @doc """ + Returns the pubsub topic name for receiving notifications when a user updated + """ + def pubsub_topic, do: @pubsub_topic + defp maybe_store_return_to(%{method: "GET"} = conn) do %{request_path: request_path, query_string: query_string} = conn return_to = if query_string == "", do: request_path, else: request_path <> "?" <> query_string diff --git a/lib/real_estate_web/controllers/user_session_controller.ex b/lib/real_estate_web/controllers/user_session_controller.ex index bd97330..6817be7 100644 --- a/lib/real_estate_web/controllers/user_session_controller.ex +++ b/lib/real_estate_web/controllers/user_session_controller.ex @@ -23,4 +23,13 @@ defmodule RealEstateWeb.UserSessionController do |> put_flash(:info, "Logged out successfully.") |> UserAuth.log_out_user() end + + def force_logout(conn, _params) do + conn + |> put_flash( + :info, + "You were logged out. Please login again to continue using our application." + ) + |> UserAuth.log_out_user() + end end diff --git a/lib/real_estate_web/live/live_helpers.ex b/lib/real_estate_web/live/live_helpers.ex index 9f085d9..2939122 100644 --- a/lib/real_estate_web/live/live_helpers.ex +++ b/lib/real_estate_web/live/live_helpers.ex @@ -3,6 +3,7 @@ defmodule RealEstateWeb.LiveHelpers do alias RealEstate.Accounts alias RealEstate.Accounts.User alias RealEstateWeb.Router.Helpers, as: Routes + alias RealEstateWeb.UserAuth import Phoenix.LiveView.Helpers @doc """ @@ -26,6 +27,8 @@ defmodule RealEstateWeb.LiveHelpers do end def assign_defaults(session, socket) do + RealEstateWeb.Endpoint.subscribe(UserAuth.pubsub_topic()) + socket = assign_new(socket, :current_user, fn -> find_current_user(session) diff --git a/lib/real_estate_web/router.ex b/lib/real_estate_web/router.ex index 8bf5285..13ffed8 100644 --- a/lib/real_estate_web/router.ex +++ b/lib/real_estate_web/router.ex @@ -76,6 +76,7 @@ defmodule RealEstateWeb.Router do scope "/", RealEstateWeb do pipe_through [:browser] + get "/users/force_logout", UserSessionController, :force_logout delete "/users/log_out", UserSessionController, :delete get "/users/confirm", UserConfirmationController, :new post "/users/confirm", UserConfirmationController, :create