diff --git a/.formatter.exs b/.formatter.exs
index d97672b..7f59b1f 100644
--- a/.formatter.exs
+++ b/.formatter.exs
@@ -1,12 +1,4 @@
-# Used by "mix format" and to export configuration.
-export_locals_without_parens = [
- plug: 1,
- plug: 2,
- get: 2
-]
-
+# Used by "mix format"
[
- inputs: ["{mix,.formatter}.exs", "{integration,lib,test}/**/*.{ex,exs}"],
- locals_without_parens: export_locals_without_parens,
- export: [locals_without_parens: export_locals_without_parens]
+ inputs: ["{mix,.formatter}.exs", "{lib,test}/**/*.{ex,exs}"]
]
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9200b4b..cfe667a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -19,17 +19,44 @@ jobs:
otp-version: 27.0
elixir-version: 1.18
- uses: actions/cache@v4
+ env:
+ cache-name: assent
with:
path: |
- _build/test
+ _build
deps
- key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
+ key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
- run: mix deps.get
- run: mix compile --warnings-as-errors
- run: mix credo --strict --ignore design.tagtodo
- run: mix format --check-formatted
- run: mix dialyzer --format github
+ integration:
+ runs-on: ubuntu-latest
+ name: Integration server
+ defaults:
+ run:
+ working-directory: integration
+ steps:
+ - uses: actions/checkout@v3
+ - uses: erlef/setup-beam@v1
+ with:
+ otp-version: 27.0
+ elixir-version: 1.18
+ - uses: actions/cache@v4
+ env:
+ cache-name: integration-server
+ with:
+ path: |
+ integration/_build
+ integration/deps
+ key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/integration/mix.lock') }}
+ - run: mix deps.get
+ - run: mix compile --warnings-as-errors
+ - run: mix format --check-formatted
+ - run: mix dialyzer --format github
+
test:
strategy:
matrix:
diff --git a/integration/.formatter.exs b/integration/.formatter.exs
new file mode 100644
index 0000000..d97672b
--- /dev/null
+++ b/integration/.formatter.exs
@@ -0,0 +1,12 @@
+# Used by "mix format" and to export configuration.
+export_locals_without_parens = [
+ plug: 1,
+ plug: 2,
+ get: 2
+]
+
+[
+ inputs: ["{mix,.formatter}.exs", "{integration,lib,test}/**/*.{ex,exs}"],
+ locals_without_parens: export_locals_without_parens,
+ export: [locals_without_parens: export_locals_without_parens]
+]
diff --git a/integration/.gitignore b/integration/.gitignore
new file mode 100644
index 0000000..671aba8
--- /dev/null
+++ b/integration/.gitignore
@@ -0,0 +1,23 @@
+# The directory Mix will write compiled artifacts to.
+/_build/
+
+# If you run "mix test --cover", coverage assets end up here.
+/cover/
+
+# The directory Mix downloads your dependencies sources to.
+/deps/
+
+# Where third-party dependencies like ExDoc output generated docs.
+/doc/
+
+# If the VM crashes, it generates a dump, let's ignore it too.
+erl_crash.dump
+
+# Also ignore archive artifacts (built via "mix archive.build").
+*.ez
+
+# Ignore package tarball (built via "mix hex.build").
+project-*.tar
+
+# Temporary files, for example, from tests.
+/tmp/
\ No newline at end of file
diff --git a/integration/README.md b/integration/README.md
index ec20fd3..8463bef 100644
--- a/integration/README.md
+++ b/integration/README.md
@@ -3,5 +3,5 @@
Simple plug-based server to test assent integration with all providers.
```bash
-CLIENT_ID=CLIENT_ID CLIENT_SECRET=CLIENT_SECRET MIX_ENV=test mix run --no-halt integration/server.exs
+mix run --no-halt
```
diff --git a/integration/lib/application.ex b/integration/lib/application.ex
new file mode 100644
index 0000000..2bae81f
--- /dev/null
+++ b/integration/lib/application.ex
@@ -0,0 +1,12 @@
+defmodule IntegrationServer.Application do
+ use Application
+
+ def start(_type, _args) do
+ children = [
+ {Bandit, plug: IntegrationServer.Router}
+ ]
+
+ opts = [strategy: :one_for_one, name: IntegrationServer.Supervisor]
+ Supervisor.start_link(children, opts)
+ end
+end
diff --git a/integration/lib/router.ex b/integration/lib/router.ex
new file mode 100644
index 0000000..4fb88e6
--- /dev/null
+++ b/integration/lib/router.ex
@@ -0,0 +1,122 @@
+defmodule IntegrationServer.Router do
+ use Plug.Router
+ use Plug.Debugger
+ use Plug.ErrorHandler
+
+ require Logger
+
+ import Plug.Conn
+
+ @session_options [
+ store: :cookie,
+ key: "_assent_integration_server",
+ signing_salt: "Q1OaP6Pz",
+ same_site: "Lax"
+ ]
+
+ plug :secret_key_base
+ plug Plug.Session, @session_options
+ plug :fetch_session
+ plug :match
+ plug :dispatch
+
+ {:ok, modules} = :application.get_key(:assent, :modules)
+
+ @path_modules modules
+ |> Enum.map(&{Module.split(&1), &1})
+ |> Enum.map(fn
+ {["Assent", "Strategy", uri], module} ->
+ path = Macro.underscore(uri)
+ fun_name = String.to_atom(path)
+ {path, module, fun_name}
+
+ _any ->
+ nil
+ end)
+ |> Enum.reject(&is_nil/1)
+
+ defp secret_key_base(conn, _opts) do
+ %{conn | secret_key_base: "LG8WiSkAlUlwVJpISmRYsi7aJV/Qlv65FXyxwWXxp1QUzQY3hzEfg73YKfKZPpe0"}
+ end
+
+ for {path, module, fun_name} <- @path_modules do
+ @file "#{__ENV__.file}##{path}_auth"
+ defp unquote(fun_name)(conn, :auth) do
+ unquote(path)
+ |> config!()
+ |> unquote(module).authorize_url()
+ |> case do
+ {:ok, %{session_params: session_params, url: url}} ->
+ Logger.info("Redirecting to #{inspect(url)} with session params #{inspect(session_params)}")
+
+ html = Plug.HTML.html_escape(url)
+ body = "
You are being redirected."
+
+ conn
+ |> put_session(:session_params, session_params)
+ |> put_resp_header("location", url)
+ |> put_resp_header("content-type", "text/html")
+ |> send_resp(302, body)
+
+ {:error, error} ->
+ body = "An error occurred: #{inspect error}."
+
+ conn
+ |> put_resp_header("content-type", "text/html")
+ |> send_resp(500, body)
+ end
+ end
+
+ @file "#{__ENV__.file}##{path}_callback"
+ defp unquote(fun_name)(conn, :callback) do
+ conn = fetch_query_params(conn)
+
+ unquote(path)
+ |> config!()
+ |> Assent.Config.put(:session_params, get_session(conn, :session_params))
+ |> unquote(module).callback(conn.params)
+ |> case do
+ {:ok, %{user: user, token: token}} ->
+ body = "#{inspect(%{user: user, token: token})}"
+
+ send_resp(conn, 200, body)
+
+ {:error, error} ->
+ body = "An error occurred: #{inspect error}."
+
+ conn
+ |> put_resp_header("content-type", "text/html")
+ |> send_resp(500, body)
+ end
+ end
+ end
+
+ get "/" do
+ list =
+ @path_modules
+ |> Enum.map(&"#{elem(&1, 0)}")
+ |> Enum.join()
+
+ body = ""
+
+ send_resp(conn, 200, body)
+ end
+
+ for {path, _module, fun_name} <- @path_modules do
+ get "/#{path}", do: unquote(fun_name)(conn, :auth)
+ get "/#{path}/callback", do: unquote(fun_name)(conn, :callback)
+ end
+
+ def config!(path) do
+ [
+ client_id: System.fetch_env!("CLIENT_ID"),
+ client_secret: System.fetch_env!("CLIENT_SECRET"),
+ redirect_uri: "http://localhost:4000/#{path}/callback"
+ ]
+ end
+
+ @impl true
+ def handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
+ send_resp(conn, conn.status, "Something went wrong")
+ end
+end
diff --git a/integration/mix.exs b/integration/mix.exs
new file mode 100644
index 0000000..c675461
--- /dev/null
+++ b/integration/mix.exs
@@ -0,0 +1,32 @@
+defmodule IntegrationServer.MixProject do
+ use Mix.Project
+
+ def project do
+ [
+ app: :integration_server,
+ version: "0.0.1",
+ elixir: "~> 1.13",
+ elixirc_paths: elixirc_paths(Mix.env()),
+ start_permanent: Mix.env() == :prod,
+ deps: deps()
+ ]
+ end
+
+ def application do
+ [
+ mod: {IntegrationServer.Application, []},
+ extra_applications: [:logger, :runtime_tools]
+ ]
+ end
+
+ defp elixirc_paths(_), do: ["lib"]
+
+ defp deps do
+ [
+ {:assent, path: "../"},
+ {:plug, ">= 0.0.0"},
+ {:bandit, ">= 0.0.0"},
+ {:dialyxir, "~> 1.4", runtime: false}
+ ]
+ end
+end
diff --git a/integration/mix.lock b/integration/mix.lock
new file mode 100644
index 0000000..b351320
--- /dev/null
+++ b/integration/mix.lock
@@ -0,0 +1,12 @@
+%{
+ "bandit": {:hex, :bandit, "1.6.1", "9e01b93d72ddc21d8c576a704949e86ee6cde7d11270a1d3073787876527a48f", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5a904bf010ea24b67979835e0507688e31ac873d4ffc8ed0e5413e8d77455031"},
+ "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"},
+ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
+ "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"},
+ "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
+ "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
+ "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
+ "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
+ "thousand_island": {:hex, :thousand_island, "1.3.7", "1da7598c0f4f5f50562c097a3f8af308ded48cd35139f0e6f17d9443e4d0c9c5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0139335079953de41d381a6134d8b618d53d084f558c734f2662d1a72818dd12"},
+ "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
+}
diff --git a/integration/server.exs b/integration/server.exs
deleted file mode 100644
index 4172c9d..0000000
--- a/integration/server.exs
+++ /dev/null
@@ -1,96 +0,0 @@
-defmodule IntegrationServer.Router do
- use Plug.Router
- use Plug.Debugger
- use Plug.ErrorHandler
-
- require Logger
-
- import Plug.Conn
-
- @session_options [
- store: :cookie,
- key: "_assent_integration_server",
- signing_salt: "Q1OaP6Pz",
- same_site: "Lax"
- ]
-
- plug :secret_key_base
- plug Plug.Session, @session_options
- plug :fetch_session
- plug :match
- plug :dispatch
-
- {:ok, modules} = :application.get_key(:assent, :modules)
-
- @path_modules modules
- |> Enum.map(&{Module.split(&1), &1})
- |> Enum.map(fn
- {["Assent", "Strategy", uri], module} -> {uri, module}
- _any -> nil
- end)
- |> Enum.reject(&is_nil/1)
- |> Enum.into(%{})
-
- defp secret_key_base(conn, _opts) do
- %{conn | secret_key_base: "LG8WiSkAlUlwVJpISmRYsi7aJV/Qlv65FXyxwWXxp1QUzQY3hzEfg73YKfKZPpe0"}
- end
-
- get "/" do
- list =
- @path_modules
- |> Enum.map(&"#{elem(&1, 0)}")
- |> Enum.join()
-
- body = ""
-
- send_resp(conn, 200, body)
- end
-
- get "/:provider" do
- module = Map.fetch!(@path_modules, provider)
-
- {:ok, %{url: url, session_params: session_params}} = module.authorize_url(config!(provider))
-
- Logger.info("Redirecting to #{inspect(url)} with session params #{inspect(session_params)}")
-
- html = Plug.HTML.html_escape(url)
- body = "You are being redirected."
-
- conn
- |> put_session(:session_params, session_params)
- |> put_resp_header("location", url)
- |> put_resp_header("content-type", "text/html")
- |> send_resp(302, body)
- end
-
- get "/:provider/callback" do
- module = Map.fetch!(@path_modules, provider)
-
- conn = fetch_query_params(conn)
-
- {:ok, %{user: user, token: token}} =
- provider
- |> config!()
- |> Assent.Config.put(:session_params, get_session(conn, :session_params))
- |> module.callback(conn.params)
-
- body = "#{inspect(%{user: user, token: token})}"
-
- send_resp(conn, 200, body)
- end
-
- def config!(provider) do
- [
- client_id: System.fetch_env!("CLIENT_ID"),
- client_secret: System.fetch_env!("CLIENT_SECRET"),
- redirect_uri: "http://localhost:4000/#{provider}/callback"
- ]
- end
-
- defp handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
- send_resp(conn, conn.status, "Something went wrong")
- end
-end
-
-{:ok, pid} = Bandit.start_link(plug: IntegrationServer.Router)
-Process.unlink(pid)
diff --git a/mix.exs b/mix.exs
index 93e76f0..f80e113 100644
--- a/mix.exs
+++ b/mix.exs
@@ -47,6 +47,7 @@ defmodule Assent.MixProject do
{:credo, "~> 1.1", only: [:dev, :test]},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
{:test_server, "~> 0.1.0", only: :test},
+ {:plug, ">= 0.0.0", only: [:dev, :test]},
{:bandit, ">= 0.0.0", only: :test}
]
end