diff --git a/mix.exs b/mix.exs index 868fcc0..cb3ad2d 100644 --- a/mix.exs +++ b/mix.exs @@ -28,8 +28,8 @@ defmodule AuthToken.Mixfile do ] end - def application do - [applications: [:logger, :plug], + def extra_application do + [applications: [:logger], env: [ timeout: 86400, refresh: 1800 @@ -47,6 +47,7 @@ defmodule AuthToken.Mixfile do {:phoenix, "~> 1.3"}, {:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false}, + {:stream_data, "~> 0.1", only: [:dev, :test]}, {:ex_doc, "~> 0.18", only: :dev} ] end diff --git a/mix.lock b/mix.lock index c4bd822..5dc176e 100644 --- a/mix.lock +++ b/mix.lock @@ -7,4 +7,5 @@ "phoenix": {:hex, :phoenix, "1.3.0", "1c01124caa1b4a7af46f2050ff11b267baa3edb441b45dbf243e979cd4c5891b", [], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [], [], "hexpm"}, "plug": {:hex, :plug, "1.4.3", "236d77ce7bf3e3a2668dc0d32a9b6f1f9b1f05361019946aae49874904be4aed", [], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [], [], "hexpm"}} + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [], [], "hexpm"}, + "stream_data": {:hex, :stream_data, "0.4.0", "128c01bfd0fae0108d169eee1772aeed6958604f8782abc2d6e11da4e52468b0", [], [], "hexpm"}} diff --git a/test/authtoken_test.exs b/test/authtoken_test.exs index db8a959..04cba35 100644 --- a/test/authtoken_test.exs +++ b/test/authtoken_test.exs @@ -1,57 +1,127 @@ defmodule AuthTokenTest do - use ConnCase + use ConnCase, async: true + import ExUnitProperties @user %{id: 123} - setup do + defp gen_authtoken_key() do + gen_ticker = fn _ -> + StreamData.constant(AuthToken.generate_key()) + end + + StreamData.constant(:unused) + |> StreamData.bind(gen_ticker) + |> StreamData.unshrinkable() + end + + defp gen_usermap() do + StreamData.fixed_map(%{ id: StreamData.integer() }) + end + + defp gen_authtoken_token(user) do + gen_ticker = fn user -> + StreamData.constant(AuthToken.generate_token(user)) + end + + StreamData.constant(user) + |> StreamData.bind(gen_ticker) + |> StreamData.unshrinkable() + end + + defp gen_authtoken_stale_token(user) do + gen_ticker = fn user -> + StreamData.constant(AuthToken.generate_token(user)) + end + + StreamData.constant(user) + |> StreamData.bind(gen_ticker) + |> StreamData.unshrinkable() + end + + defp reset_timeout_env() do Application.put_env(:authtoken, :timeout, 86400) Application.put_env(:authtoken, :refresh, 1800) end + setup do + reset_timeout_env() + end + describe "keys" do - test "generate_key/0 returns a valid AES128 key" do - {:ok, key} = AuthToken.generate_key() + property "generate_key/0 returns a valid AES128 key" do + check all authtoken_key <- gen_authtoken_key() do + {:ok, key} = authtoken_key + assert byte_size(key) == 16 + end + end + end - assert byte_size(key) == 16 + describe "unique keys" do + property "generate_key/0 always returns a unique AES128 key" do + # n.b.: not using `check all` here, since that would require we we + # construct a StreamData list. but all we want to do is ensure a large list is unique + authtoken_keys = Enum.take(gen_authtoken_key(), 99_999) + + assert authtoken_keys == Enum.uniq(authtoken_keys) end end - describe "tokens" do - test "token generation" do - {:ok, encrypted_token} = AuthToken.generate_token(@user) + describe "token properties (and life-cycle)" do + property "generate_token/1 and decrypt_token/1 are reversible" do + check all user <- gen_usermap(), + authtoken_token <- gen_authtoken_token(user) do - assert {:ok, token} = AuthToken.decrypt_token(encrypted_token) - assert token["id"] == @user.id + {:ok, encrypted_token} = authtoken_token + assert {:ok, token} = AuthToken.decrypt_token(encrypted_token) + assert token["id"] == user.id + end + end - refute AuthToken.is_timedout?(token) - refute AuthToken.needs_refresh?(token) + property "authtoken can timeout" do + check all user <- gen_usermap(), + authtoken_token <- gen_authtoken_token(user) do - Application.put_env(:authtoken, :timeout, -1) - Application.put_env(:authtoken, :refresh, -1) + reset_timeout_env() - assert AuthToken.is_timedout?(token) - assert AuthToken.needs_refresh?(token) + {:ok, encrypted_token} = authtoken_token + {:ok, token} = AuthToken.decrypt_token(encrypted_token) + + refute AuthToken.is_timedout?(token) + refute AuthToken.needs_refresh?(token) + + Application.put_env(:authtoken, :timeout, -1) + Application.put_env(:authtoken, :refresh, -1) + + assert AuthToken.is_timedout?(token) + assert AuthToken.needs_refresh?(token) + end end - test "token refresh" do - {:ok, encrypted_token} = AuthToken.generate_token(@user) - {:ok, token} = AuthToken.decrypt_token(encrypted_token) + # property "token refresh" do + # check all user <- gen_usermap(), + # authtoken_token <- gen_authtoken_token(user) do - assert AuthToken.refresh_token(token) == {:error, :stillfresh} - assert AuthToken.refresh_token(encrypted_token) == {:error, :stillfresh} + # reset_timeout_env() - :timer.sleep(1000) + # {:ok, encrypted_token} = authtoken_token + # {:ok, token} = AuthToken.decrypt_token(encrypted_token) - Application.put_env(:authtoken, :refresh, -1) - assert {:ok, fresh_token} = AuthToken.refresh_token(token) - assert {:ok, fresh_token} = AuthToken.refresh_token(encrypted_token) + # assert AuthToken.refresh_token(token) == {:error, :stillfresh} + # assert AuthToken.refresh_token(encrypted_token) == {:error, :stillfresh} - {:ok, token} = AuthToken.decrypt_token(fresh_token) - assert token["ct"] < token["rt"] + # :timer.sleep(1000) - Application.put_env(:authtoken, :timeout, -1) - assert AuthToken.refresh_token(token) == {:error, :timedout} - end + # Application.put_env(:authtoken, :refresh, -1) + # assert {:ok, fresh_token} = AuthToken.refresh_token(token) + # assert {:ok, fresh_token} = AuthToken.refresh_token(encrypted_token) + + # {:ok, token} = AuthToken.decrypt_token(fresh_token) + # assert token["ct"] < token["rt"] + + # Application.put_env(:authtoken, :timeout, -1) + # assert AuthToken.refresh_token(token) == {:error, :timedout} + # end + # end end describe "plug verifying and testing tokens" do