Skip to content

Commit

Permalink
Pull out channel valid_token? (#441)
Browse files Browse the repository at this point in the history
  • Loading branch information
skyqrose authored Feb 7, 2020
1 parent 2fe8577 commit 04480e8
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 156 deletions.
10 changes: 4 additions & 6 deletions lib/realtime/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ defmodule Realtime.Server do
@spec subscribe(GenServer.server(), :all_shuttles) :: [Vehicle.t()]
@spec subscribe(GenServer.server(), {:search, search_params()}) :: [VehicleOrGhost.t()]
defp subscribe(server, subscription_key) do
{registry_key, data} = GenServer.call(server, {:subscribe, subscription_key})
{registry_key, ets} = GenServer.call(server, :subscription_info)
Registry.register(Realtime.Registry, registry_key, subscription_key)
data
lookup({ets, subscription_key})
end

@spec update({Route.by_id([VehicleOrGhost.t()]), [Vehicle.t()]}, GenServer.server()) :: term()
Expand Down Expand Up @@ -131,11 +131,9 @@ defmodule Realtime.Server do
do: {:noreply, state}

@impl true
def handle_call({:subscribe, subscription_key}, _from, %__MODULE__{} = state) do
def handle_call(:subscription_info, _from, %__MODULE__{} = state) do
registry_key = self()

data = lookup({state.ets, subscription_key})
{:reply, {registry_key, data}, state}
{:reply, {registry_key, state.ets}, state}
end

def handle_call(:ets, _from, %__MODULE__{ets: ets} = state) do
Expand Down
51 changes: 51 additions & 0 deletions lib/skate_web/channels/channel_auth.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
defmodule SkateWeb.ChannelAuth do
alias SkateWeb.AuthManager

@doc """
Checks wether a socket has a valid token.
Attempts to refresh the token.
Only returns false if all refreshing fails,
and the user needs to log in again.
"""
@spec valid_token?(Phoenix.Socket.t()) :: boolean()
def valid_token?(socket) do
token = Guardian.Phoenix.Socket.current_token(socket)

case AuthManager.decode_and_verify(token) do
{:ok, _claims} ->
# Refresh a token before it expires
case AuthManager.refresh(token) do
{:ok, _old_claims, {_new_token, _new_claims}} ->
true

{:error, :token_expired} ->
valid_token_after_refresh?(socket)
end

{:error, :token_expired} ->
valid_token_after_refresh?(socket)

_ ->
false
end
end

@spec valid_token_after_refresh?(Phoenix.Socket.t()) :: boolean()
defp valid_token_after_refresh?(socket) do
refresh_token_store = Application.get_env(:skate, :refresh_token_store)

refresh_token =
socket
|> Guardian.Phoenix.Socket.current_resource()
|> refresh_token_store.get_refresh_token()

# Exchange a token of type "refresh" for a new token of type "access"
case AuthManager.exchange(refresh_token, "refresh", "access") do
{:ok, _old_stuff, {_new_token, _new_claims}} ->
true

_ ->
false
end
end
end
76 changes: 15 additions & 61 deletions lib/skate_web/channels/vehicles_channel.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ defmodule SkateWeb.VehiclesChannel do
require Logger

alias Realtime.Server
alias SkateWeb.AuthManager
alias Util.Duration

@impl Phoenix.Channel
Expand Down Expand Up @@ -54,67 +53,22 @@ defmodule SkateWeb.VehiclesChannel do

@impl Phoenix.Channel
def handle_info({:new_realtime_data, lookup_args}, socket) do
token = Guardian.Phoenix.Socket.current_token(socket)

case AuthManager.decode_and_verify(token) do
{:ok, _claims} ->
# Refresh a token before it expires
case AuthManager.refresh(token) do
{:ok, _old_claims, {_new_token, _new_claims}} ->
push_vehicles(socket, lookup_args)

{:error, :token_expired} ->
handle_expired_token(socket, lookup_args)
end

{:error, :token_expired} ->
handle_expired_token(socket, lookup_args)

_ ->
{:stop, :normal, send_auth_expired_message(socket)}
valid_token_fn =
Application.get_env(:skate, :valid_token_fn, &SkateWeb.ChannelAuth.valid_token?/1)

if valid_token_fn.(socket) do
event_name = event_name(lookup_args)
data = Server.lookup(lookup_args)
:ok = push(socket, event_name, %{data: data})
{:noreply, socket}
else
:ok = push(socket, "auth_expired", %{})
{:stop, :normal, socket}
end
end

@spec push_vehicles(Phoenix.Socket.t(), Server.lookup_key()) ::
{:noreply, Phoenix.Socket.t()}
defp push_vehicles(socket, {_ets, :all_shuttles} = lookup) do
push(socket, "shuttles", %{data: Server.lookup(lookup)})
{:noreply, socket}
end

defp push_vehicles(socket, {_ets, {:search, _}} = lookup) do
push(socket, "search", %{data: Server.lookup(lookup)})
{:noreply, socket}
end

defp push_vehicles(socket, lookup) do
push(socket, "vehicles", %{data: Server.lookup(lookup)})
{:noreply, socket}
end

@spec handle_expired_token(Phoenix.Socket.t(), Server.lookup_key()) ::
{:noreply, Phoenix.Socket.t()} | {:stop, :normal, Phoenix.Socket.t()}
defp handle_expired_token(socket, lookup_args) do
refresh_token_store = Application.get_env(:skate, :refresh_token_store)

refresh_token =
socket
|> Guardian.Phoenix.Socket.current_resource()
|> refresh_token_store.get_refresh_token()

# Exchange a token of type "refresh" for a new token of type "access"
case AuthManager.exchange(refresh_token, "refresh", "access") do
{:ok, _old_stuff, {_new_token, _new_claims}} ->
push_vehicles(socket, lookup_args)

_ ->
{:stop, :normal, send_auth_expired_message(socket)}
end
end

@spec send_auth_expired_message(Phoenix.Socket.t()) :: Phoenix.Socket.t()
defp send_auth_expired_message(socket) do
:ok = push(socket, "auth_expired", %{})
socket
end
@spec event_name(Server.lookup_key()) :: String.t()
defp event_name({_ets, :all_shuttles}), do: "shuttles"
defp event_name({_ets, {:search, _}}), do: "search"
defp event_name({_ets, _}), do: "vehicles"
end
86 changes: 7 additions & 79 deletions test/channels/vehicles_channel_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule SkateWeb.VehiclesChannelTest do

alias Phoenix.Socket
alias Realtime.Vehicle
alias SkateWeb.{AuthManager, UserSocket, VehiclesChannel}
alias SkateWeb.{UserSocket, VehiclesChannel}

@vehicle %Vehicle{
id: "y0507",
Expand Down Expand Up @@ -41,8 +41,7 @@ defmodule SkateWeb.VehiclesChannelTest do
}

setup do
reassign_env(:realtime, :trip_fn, fn _trip_id -> nil end)
reassign_env(:realtime, :block_fn, fn _block_id, _service_id -> nil end)
reassign_env(:skate, :valid_token_fn, fn _socket -> true end)

socket = socket(UserSocket, "", %{guardian_default_resource: "test_uid"})

Expand Down Expand Up @@ -95,30 +94,18 @@ defmodule SkateWeb.VehiclesChannelTest do

describe "handle_info/2" do
setup do
reassign_env(
:skate,
:refresh_token_store,
SkateWeb.VehiclesChannelTest.FakeRefreshTokenStore
)

ets = GenServer.call(Realtime.Server.default_name(), :ets)

{:ok, ets: ets}
end

test "pushes new vehicle data onto the socket when socket is authenticated", %{
test "pushes new vehicle data onto the socket", %{
socket: socket,
ets: ets
} do
assert Realtime.Server.update({%{"1" => [@vehicle]}, []}) == :ok

{:ok, token, claims} =
AuthManager.encode_and_sign("[email protected]", %{
"exp" => System.system_time(:second) + 500
})

{:ok, _, socket} = subscribe_and_join(socket, VehiclesChannel, "vehicles:route:1")
socket = Guardian.Phoenix.Socket.assign_rtc(socket, "[email protected]", token, claims)

assert {:noreply, socket} =
VehiclesChannel.handle_info(
Expand All @@ -130,19 +117,13 @@ defmodule SkateWeb.VehiclesChannelTest do
assert_push("vehicles", %{data: [^vehicle]})
end

test "pushes new shuttle data onto the socket when socket is authenticated", %{
test "pushes new shuttle data onto the socket", %{
socket: socket,
ets: ets
} do
assert Realtime.Server.update({%{}, [@vehicle]}) == :ok

{:ok, token, claims} =
AuthManager.encode_and_sign("[email protected]", %{
"exp" => System.system_time(:second) + 500
})

{:ok, _, socket} = subscribe_and_join(socket, VehiclesChannel, "vehicles:shuttle:all")
socket = Guardian.Phoenix.Socket.assign_rtc(socket, "[email protected]", token, claims)

assert {:noreply, socket} =
VehiclesChannel.handle_info(
Expand All @@ -154,19 +135,13 @@ defmodule SkateWeb.VehiclesChannelTest do
assert_push("shuttles", %{data: [^vehicle]})
end

test "pushes new search results data onto the socket when socket is authenticated", %{
test "pushes new search results data onto the socket", %{
socket: socket,
ets: ets
} do
assert Realtime.Server.update({%{}, [@vehicle]}) == :ok

{:ok, token, claims} =
AuthManager.encode_and_sign("[email protected]", %{
"exp" => System.system_time(:second) + 500
})

{:ok, _, socket} = subscribe_and_join(socket, VehiclesChannel, "vehicles:search:all:507")
socket = Guardian.Phoenix.Socket.assign_rtc(socket, "[email protected]", token, claims)

assert {:noreply, socket} =
VehiclesChannel.handle_info(
Expand All @@ -178,45 +153,15 @@ defmodule SkateWeb.VehiclesChannelTest do
assert_push("search", %{data: [^vehicle]})
end

test "refresh the authentication using the refresh token if we have one", %{
socket: socket,
ets: ets
} do
assert Realtime.Server.update({%{"1" => [@vehicle]}, []})

{:ok, token, claims} =
AuthManager.encode_and_sign("[email protected]", %{
"exp" => System.system_time(:second) - 100
})

{:ok, _, socket} = subscribe_and_join(socket, VehiclesChannel, "vehicles:route:1")

socket = Guardian.Phoenix.Socket.assign_rtc(socket, "[email protected]", token, claims)

assert {:noreply, socket} =
VehiclesChannel.handle_info(
{:new_realtime_data, {ets, {:route_id, "1"}}},
socket
)

vehicle = @vehicle
assert_push("vehicles", %{data: [^vehicle]})
end

test "rejects sending vehicle data when socket is not authenticated", %{
socket: socket,
ets: ets
} do
{:ok, token, claims} =
AuthManager.encode_and_sign("[email protected]", %{
"exp" => System.system_time(:second) - 100
})
assert Realtime.Server.update({%{}, [@vehicle]}) == :ok
reassign_env(:skate, :valid_token_fn, fn _socket -> false end)

{:ok, _, socket} = subscribe_and_join(socket, VehiclesChannel, "vehicles:route:1")

socket =
Guardian.Phoenix.Socket.assign_rtc(socket, "[email protected]", token, claims)

{:stop, :normal, _socket} =
VehiclesChannel.handle_info(
{:new_realtime_data, {ets, {:route_id, "1"}}},
Expand All @@ -226,21 +171,4 @@ defmodule SkateWeb.VehiclesChannelTest do
assert_push("auth_expired", _)
end
end

defmodule FakeRefreshTokenStore do
def get_refresh_token("[email protected]") do
{:ok, token, _claims} =
AuthManager.encode_and_sign(
"[email protected]",
%{
"exp" => System.system_time(:second) + 500
},
token_type: "refresh"
)

token
end

def get_refresh_token(_), do: nil
end
end
10 changes: 0 additions & 10 deletions test/realtime/server_test.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
defmodule Realtime.ServerTest do
use ExUnit.Case, async: true
import Test.Support.Helpers

alias Realtime.{Ghost, Server, Vehicle}

Expand Down Expand Up @@ -66,9 +65,6 @@ defmodule Realtime.ServerTest do

describe "subscribe_to_route" do
setup do
reassign_env(:realtime, :trip_fn, fn _trip_id -> nil end)
reassign_env(:realtime, :block_fn, fn _block_id, _service_id -> nil end)

{:ok, server_pid} = Server.start_link([])

Server.update({@vehicles_by_route_id, []}, server_pid)
Expand Down Expand Up @@ -134,9 +130,6 @@ defmodule Realtime.ServerTest do

describe "subscribe_to_all_shuttles" do
setup do
reassign_env(:realtime, :trip_fn, fn _trip_id -> nil end)
reassign_env(:realtime, :block_fn, fn _block_id, _service_id -> nil end)

{:ok, server_pid} = Server.start_link([])

:ok = Server.update({%{}, [@shuttle]}, server_pid)
Expand All @@ -160,9 +153,6 @@ defmodule Realtime.ServerTest do

describe "subscribe_to_search" do
setup do
reassign_env(:realtime, :trip_fn, fn _trip_id -> nil end)
reassign_env(:realtime, :block_fn, fn _block_id, _service_id -> nil end)

{:ok, server_pid} = Server.start_link([])

:ok = Server.update({@vehicles_by_route_id, [@shuttle]}, server_pid)
Expand Down
Loading

0 comments on commit 04480e8

Please sign in to comment.