Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"get_token" code blows on bad "code" #71

Closed
ndrean opened this issue Oct 25, 2022 · 3 comments
Closed

"get_token" code blows on bad "code" #71

ndrean opened this issue Oct 25, 2022 · 3 comments

Comments

@ndrean
Copy link
Contributor

ndrean commented Oct 25, 2022

Test:

The controller that receives Google's code goes like this:

# google_auth_controller.ex

def index(conn, %{"code" => code}) do
  {:ok, code} = ElixirAuthGoogle.get_token(code, conn)
  {:ok, profile} = ElixirAuthGoogle.get_user_profile(code.access_token)
 ...
end

When I run a test with a "bad" code:

iex> AppWeb.GoogleAuthController.index(%Plug.Conn{}, %{"code" => 1})

I receive:

(KeyError) key :access_token not found in: %{error: %{"code" => 400,....

The fix in the package ElxirAuthGoogle is:

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
      })

    {:ok, response} =
      inject_poison().post(@google_token_url, body)
      |> parse_body_response()

    case Map.has_key?(response, :access_token) do
      false -> {:error, :invalid_code_value}
      true -> {:ok, response.access_token}
    end
  end

and it is sufficient to pass directly access_token to the next function ElixirAuthGoogle.get_user_profile(access_token).

If you keep your controller, since you have a "two staged" function, you now have a (MatchError) in case of error but you can capture it now.
The controller should be:

with {:ok, token} <- ElixirAuthGoogle.get_token(code, conn),
       {:ok, profile} <- ElixirAuthGoogle.get_user_profile(token) do
    conn
    |> put_view(AppWeb.PageView)
    |> render(:welcome, profile: profile)
 end

The error {:error, :invalid_code_value} is now captured, ready for the action_callback.

I would even refactor to expose only one function, say get_profile:

def get_profile(code, conn) do
    {:ok, response} =
      Jason.encode!(%{
        client_id: google_client_id(),
        client_secret: google_client_secret(),
        redirect_uri: generate_redirect_uri(conn),
        grant_type: "authorization_code",
        code: code
      })
      |> then(fn body ->
        inject_poison().post(@google_token_url, body)
        |> parse_body_response()
      end)

    case Map.has_key?(response, :access_token) do
      false -> {:error, :invalid_code_value}
      true -> get_user_profile(response.access_token)
    end
  end

so that in the controller, you do:

{:ok, profile} <-ElixirAuthGoogle.get_profile(code, conn)
@ndrean ndrean pinned this issue Oct 25, 2022
@nelsonic
Copy link
Member

Related to: #16 🔗

@ndrean
Copy link
Contributor Author

ndrean commented Oct 26, 2022

It's even written on the code!. Sorry about that, I delete my remark.

@ndrean
Copy link
Contributor Author

ndrean commented Oct 26, 2022

So maybe the full correction since there are only limited possibilities of failure with 4 inputs that boil down to 400,401,404
I used this and it was.

def parse_status(request) do
    case request do
      {:ok, %{status_code: 200} = response} ->
        parse_body_response({:ok, response})

      {:ok, %{status_code: 404}} ->
        {:error, :wrong_url}

      {:ok, %{status_code: 401}} ->
        {:error, :unauthorized_with_bad_secret}

      {:ok, %{status_code: 400}} ->
        {:error, :bad_code}
    end
  end
def get_profile(code, conn) do
    Jason.encode!(%{
      client_id: google_client_id(),
      client_secret: google_client_secret(),
      redirect_uri: generate_redirect_uri(conn),
      grant_type: "authorization_code",
      code: code
    })
    |> then(fn body ->
      inject_poison().post(@google_token_url, body)
      |> parse_status()
    end)
    |> then(fn {status, response} ->
      case {status, response} do
        {:error, response} ->
          {:error, response}

        {:ok, response} ->
          get_user_profile(response.access_token)
      end
    end)
  end

def get_user_profile(access_token) do
    access_token
    |> encode()
    |>then(fn params ->
      (@google_user_profile <> "?" <> params)
      |> inject_poison().get()
      |> parse_status()
    end)
  end

  defp encode(token), do: URI.encode_query(%{access_token: token}, :rfc3986)

@ndrean ndrean closed this as completed Oct 26, 2022
@LuchoTurtle LuchoTurtle unpinned this issue Apr 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants