Skip to content

Commit

Permalink
Merge pull request #95 from dwyl/x-forwarded-proto-https-issue#94
Browse files Browse the repository at this point in the history
PR: Add `generate_redirect_uri/1` receives `Endpoint.url()` as argument #94
  • Loading branch information
SimonLab authored May 18, 2023
2 parents efba82a + 7ae05b5 commit 6004168
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 25 deletions.
33 changes: 31 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Add a line for **`:elixir_auth_google`** in the **`deps`** list:
```elixir
def deps do
[
{:elixir_auth_google, "~> 1.6.5"}
{:elixir_auth_google, "~> 1.6.8"}
]
end
```
Expand Down Expand Up @@ -147,7 +147,7 @@ defmodule AppWeb.GoogleAuthController do
`index/2` handles the callback from Google Auth API redirect.
"""
def index(conn, %{"code" => code}) do
{:ok, token} = ElixirAuthGoogle.get_token(code, conn)
{:ok, token} = ElixirAuthGoogle.get_token(code, MyAppWeb.Endpoint.url())
{:ok, profile} = ElixirAuthGoogle.get_user_profile(token.access_token)
conn
|> put_view(AppWeb.PageView)
Expand Down Expand Up @@ -234,6 +234,35 @@ oauth_google_url = ElixirAuthGoogle.generate_oauth_url(conn, %{lang: 'pt-BR'})

Will return a url with `lang=pt-BR` included in the sign in request.

#### _Alternatively_ pass the `url` of your `App` into `generate_oauth_url/1`

We have noticed that on `fly.io`
where the `Phoenix` App is proxied,
passing the `conn` struct
to `ElixirAuthGoogle.generate_oauth_url/2`
is not effective.
See [dwyl/elixir-auth-google/issues/94](https://github.com/dwyl/elixir-auth-google/issues/94)

So we added an alternative way
of invoking `generate_oauth_url/2`
passing in the `url` of your `App`:

```elixir
def index(conn, _params) do
base_url = MyAppWeb.Endpoint.url()
oauth_google_url = ElixirAuthGoogle.generate_oauth_url(base_url)
render(conn, "index.html",[oauth_google_url: oauth_google_url])
end
```

This uses
[Phoenix.Endpoint.url/0](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#c:url/0)
which is available in any `Phoenix` App.

Just remember to replace `MyAppWeb` with the name of your `App`. 😉

<br />

### 6.1 Update the `page/index.html.eex` Template

Open the `/lib/app_web/templates/page/index.html.eex` file
Expand Down
77 changes: 60 additions & 17 deletions lib/elixir_auth_google.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ defmodule ElixirAuthGoogle do
ElixirAuthGoogle.HTTPoisonMock) || HTTPoison

@type conn :: map
@type url :: String.t()

@doc """
`inject_poison/0` injects a TestDouble of HTTPoison in Test
so that we don't have duplicate mock in consuming apps.
see: https://github.com/dwyl/elixir-auth-google/issues/35
see: github.com/dwyl/elixir-auth-google/issues/35
"""
def inject_poison, do: @httpoison

Expand All @@ -44,8 +45,28 @@ defmodule ElixirAuthGoogle do
end

@doc """
`generate_redirect_uri/1` generates the Google redirect uri based on conn
`generate_redirect_uri/1` generates the Google redirect uri based on `conn`
or the `url`. If the `App.Endpoint.url()`
e.g: auth.dwyl.com or https://gcal.fly.dev
is passed into `generate_redirect_uri/1`,
return that `url` with the callback appended to it.
See: github.com/dwyl/elixir-auth-google/issues/94
"""
@spec generate_redirect_uri(url) :: String.t()
def generate_redirect_uri(url) when is_binary(url) do
scheme =
cond do
# url already contains scheme return empty
String.contains?(url, "https") -> ""
# url contains ":" is localhost:4000 no need for scheme
String.contains?(url, ":") -> ""
# Default to https if scheme not set e.g: app.fly.dev -> https://app.fly.fev
true -> "https://"
end

"#{scheme}#{url}" <> get_app_callback_url()
end

@spec generate_redirect_uri(conn) :: String.t()
def generate_redirect_uri(conn) do
get_baseurl_from_conn(conn) <> get_app_callback_url()
Expand All @@ -57,8 +78,20 @@ defmodule ElixirAuthGoogle do
This is the URL you need to use for your "Login with Google" button.
See step 5 of the instructions.
"""
def generate_oauth_url(url) when is_binary(url) do
query = %{
client_id: google_client_id(),
scope: google_scope(),
redirect_uri: generate_redirect_uri(url)
}

params = URI.encode_query(query, :rfc3986)

"#{@google_auth_url}&#{params}"
end

@spec generate_oauth_url(conn) :: String.t()
def generate_oauth_url(conn) do
def generate_oauth_url(conn) when is_map(conn) do
query = %{
client_id: google_client_id(),
scope: google_scope(),
Expand All @@ -74,7 +107,7 @@ defmodule ElixirAuthGoogle do
Same as `generate_oauth_url/1` with `state` query parameter,
or a `map` of key/pair values to be included in the urls query string.
"""
@spec generate_oauth_url(conn, String.t | map) :: String.t()
@spec generate_oauth_url(conn, String.t() | map) :: String.t()
def generate_oauth_url(conn, state) when is_binary(state) do
params = URI.encode_query(%{state: state}, :rfc3986)
generate_oauth_url(conn) <> "&#{params}"
Expand All @@ -85,28 +118,38 @@ defmodule ElixirAuthGoogle do
generate_oauth_url(conn) <> "&#{query}"
end


@doc """
`get_token/2` encodes the secret keys and authorization code returned by Google
and issues an HTTP request to get a person's profile data.
**TODO**: we still need to handle the various failure conditions >> issues/16
"""
@spec get_token(String.t(), conn) :: {:ok, map} | {:error, any}
def get_token(code, conn) do
body =
Jason.encode!(%{
client_id: google_client_id(),
client_secret: google_client_secret(),
redirect_uri: generate_redirect_uri(conn),
grant_type: "authorization_code",
code: code
})

inject_poison().post(@google_token_url, body)
def get_token(code, conn) when is_map(conn) do
redirect_uri = generate_redirect_uri(conn)

inject_poison().post(@google_token_url, req_body(code, redirect_uri))
|> parse_body_response()
end

@spec get_token(String.t(), url) :: {:ok, map} | {:error, any}
def get_token(code, url) when is_binary(url) do
redirect_uri = generate_redirect_uri(url)

inject_poison().post(@google_token_url, req_body(code, redirect_uri))
|> parse_body_response()
end

defp req_body(code, redirect_uri) do
Jason.encode!(%{
client_id: google_client_id(),
client_secret: google_client_secret(),
redirect_uri: redirect_uri,
grant_type: "authorization_code",
code: code
})
end

@doc """
`get_user_profile/1` requests the Google User's userinfo profile data
providing the access_token received in the `get_token/1` above.
Expand Down Expand Up @@ -145,7 +188,7 @@ defmodule ElixirAuthGoogle do
# https://stackoverflow.com/questions/31990134
end

defp google_client_id do
def google_client_id do
System.get_env("GOOGLE_CLIENT_ID") || Application.get_env(:elixir_auth_google, :client_id)
end

Expand Down
10 changes: 5 additions & 5 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule ElixirAuthGoogle.MixProject do
use Mix.Project

@description "Minimalist Google OAuth Authentication for Elixir Apps"
@version "1.6.5"
@version "1.6.8"

def project do
[
Expand Down Expand Up @@ -40,9 +40,10 @@ defmodule ElixirAuthGoogle.MixProject do
{:httpoison, "~> 2.1.0"},
{:jason, "~> 1.2"},

# tracking test coverage
# Track test coverage: github.com/parroty/excoveralls
{:excoveralls, "~> 0.16.0", only: [:test, :dev]},
# mock stuffs in test

# Mock stuffs in test: github.com/jjh42/mock
{:mock, "~> 0.3.0", only: :test},

# documentation
Expand All @@ -53,7 +54,7 @@ defmodule ElixirAuthGoogle.MixProject do
defp package do
[
maintainers: ["dwyl"],
licenses: ["GNU GPL v2.0"],
licenses: ["GPL-2.0-or-later"],
links: %{github: "https://github.com/dwyl/elixir-auth-google"},
files: ~w(lib LICENSE mix.exs README.md .formatter.exs)
]
Expand All @@ -65,5 +66,4 @@ defmodule ElixirAuthGoogle.MixProject do
c: ["coveralls.html"]
]
end

end
4 changes: 4 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
}
29 changes: 28 additions & 1 deletion test/elixir_auth_google_test.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
defmodule ElixirAuthGoogleTest do
use ExUnit.Case, async: true
# use Plug.Test
doctest ElixirAuthGoogle

import Mock
Expand Down Expand Up @@ -126,6 +125,11 @@ defmodule ElixirAuthGoogleTest do
assert res == %{access_token: "token1"}
end

test "get Google token with url as second param #94" do
{:ok, res} = ElixirAuthGoogle.get_token("ok_code", "gcal.fly.dev")
assert res == %{access_token: "token1"}
end

test "get Google token (config redirect uri)" do
conn = %{
host: "localhost",
Expand Down Expand Up @@ -165,6 +169,29 @@ defmodule ElixirAuthGoogleTest do
"https://foobar.com/auth/google/callback"
end

test "generate_oauth_url(url) passing in App.Endpoint.url() #94" do
url = "gcal.fly.dev"
client_id = ElixirAuthGoogle.google_client_id()
https = "https%3A%2F%2F#{url}"

auth_url =
"https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=#{client_id}&redirect_uri=#{https}%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email"

assert ElixirAuthGoogle.generate_oauth_url(url) =~ auth_url
end

test "generate_oauth_url(url) with scheme e.g. https://gcal.fly.dev #94" do
no_scheme = "gcal.fly.dev"
url = "https://#{no_scheme}"
client_id = ElixirAuthGoogle.google_client_id()
https = "https%3A%2F%2F#{no_scheme}"

auth_url =
"https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=#{client_id}&redirect_uri=#{https}%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email"

assert ElixirAuthGoogle.generate_oauth_url(url) =~ auth_url
end

test "generate_redirect_uri(conn) generate correct callback url with custom url path from application environment variable" do
conn = %{
host: "foobar.com",
Expand Down

0 comments on commit 6004168

Please sign in to comment.