Skip to content

Commit

Permalink
Merge pull request #171 from pow-auth/refactor
Browse files Browse the repository at this point in the history
Refactor
  • Loading branch information
danschultzer authored Dec 29, 2024
2 parents 9a2b5fe + 64c07c4 commit 949a3fc
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 240 deletions.
6 changes: 3 additions & 3 deletions lib/assent/jwt_adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ defmodule Assent.JWTAdapter do
"""
@spec sign(map(), binary(), binary(), Keyword.t()) :: {:ok, binary()} | {:error, term()}
def sign(claims, alg, secret, opts \\ []) do
{adapter, opts} = fetch_adapter(opts)
{adapter, opts} = get_adapter(opts)
adapter.sign(claims, alg, secret, opts)
end

Expand All @@ -51,11 +51,11 @@ defmodule Assent.JWTAdapter do
"""
@spec verify(binary(), binary() | map() | nil, Keyword.t()) :: {:ok, map()} | {:error, any()}
def verify(token, secret, opts \\ []) do
{adapter, opts} = fetch_adapter(opts)
{adapter, opts} = get_adapter(opts)
adapter.verify(token, secret, opts)
end

defp fetch_adapter(opts) do
defp get_adapter(opts) do
default_opts = Keyword.put(opts, :json_library, Config.json_library(opts))
default_jwt_adapter = Application.get_env(:assent, :jwt_adapter, Assent.JWTAdapter.AssentJWT)

Expand Down
109 changes: 47 additions & 62 deletions lib/assent/strategies/oauth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,26 +81,14 @@ defmodule Assent.Strategy.OAuth do
@impl true
@spec authorize_url(Config.t()) :: on_authorize_url()
def authorize_url(config) do
case Config.fetch(config, :redirect_uri) do
{:ok, redirect_uri} -> authorize_url(config, redirect_uri)
{:error, error} -> {:error, error}
with {:ok, redirect_uri} <- Config.fetch(config, :redirect_uri),
{:ok, token} <- fetch_request_token(config, [{"oauth_callback", redirect_uri}]),
{:ok, url, oauth_token_secret} <- gen_authorize_url(config, token) do
{:ok, %{url: url, session_params: %{oauth_token_secret: oauth_token_secret}}}
end
end

defp authorize_url(config, redirect_uri) do
config
|> get_request_token([{"oauth_callback", redirect_uri}])
|> build_authorize_url(config)
|> case do
{:ok, url, oauth_token_secret} ->
{:ok, %{url: url, session_params: %{oauth_token_secret: oauth_token_secret}}}

{:error, error} ->
{:error, error}
end
end

defp get_request_token(config, oauth_params) do
defp fetch_request_token(config, oauth_params) do
with {:ok, base_url} <- Config.__base_url__(config) do
request_token_url = Config.get(config, :request_token_url, "/request_token")
url = process_url(base_url, request_token_url)
Expand Down Expand Up @@ -158,12 +146,16 @@ defmodule Assent.Strategy.OAuth do

defp gen_oauth_params(config, signature_method, oauth_params) do
with {:ok, consumer_key} <- Config.fetch(config, :consumer_key) do
nonce = gen_nonce()
signature_method = signature_method_value(signature_method)
timestamp = to_string(:os.system_time(:second))

params =
[
{"oauth_consumer_key", consumer_key},
{"oauth_nonce", gen_nonce()},
{"oauth_signature_method", to_signature_method_string(signature_method)},
{"oauth_timestamp", timestamp()},
{"oauth_nonce", nonce},
{"oauth_signature_method", signature_method},
{"oauth_timestamp", timestamp},
{"oauth_version", "1.0"}
| oauth_params
]
Expand All @@ -172,6 +164,16 @@ defmodule Assent.Strategy.OAuth do
end
end

defp gen_nonce do
16
|> :crypto.strong_rand_bytes()
|> Base.encode64(padding: false)
end

defp signature_method_value(:hmac_sha1), do: "HMAC-SHA1"
defp signature_method_value(:rsa_sha1), do: "RSA-SHA1"
defp signature_method_value(:plaintext), do: "PLAINTEXT"

defp signed_header(config, signature_method, method, url, oauth_params, params, token_secret) do
uri = URI.parse(url)
query_params = Map.to_list(URI.decode_query(uri.query || ""))
Expand All @@ -188,25 +190,13 @@ defmodule Assent.Strategy.OAuth do
end
end

defp gen_nonce do
16
|> :crypto.strong_rand_bytes()
|> Base.encode64(padding: false)
end

defp to_signature_method_string(:hmac_sha1), do: "HMAC-SHA1"
defp to_signature_method_string(:rsa_sha1), do: "RSA-SHA1"
defp to_signature_method_string(:plaintext), do: "PLAINTEXT"

defp timestamp, do: to_string(:os.system_time(:second))

defp gen_signature(config, method, uri, request_params, :hmac_sha1, token_secret) do
with {:ok, shared_secret} <- encoded_shared_secret(config, token_secret) do
text = signature_base_string(method, uri, request_params)
signature_base = encode_signature_base(method, uri, request_params)

signature =
:hmac
|> :crypto.mac(:sha, shared_secret, text)
|> :crypto.mac(:sha, shared_secret, signature_base)
|> Base.encode64()

{:ok, signature}
Expand All @@ -218,7 +208,7 @@ defmodule Assent.Strategy.OAuth do
{:ok, private_key} <- decode_pem(pem) do
signature =
method
|> signature_base_string(uri, request_params)
|> encode_signature_base(uri, request_params)
|> :public_key.sign(:sha, private_key)
|> Base.encode64()

Expand All @@ -243,7 +233,7 @@ defmodule Assent.Strategy.OAuth do
|> URI.encode(&URI.char_unreserved?/1)
end

defp signature_base_string(method, uri, request_params) do
defp encode_signature_base(method, uri, request_params) do
method =
method
|> to_string()
Expand Down Expand Up @@ -308,7 +298,7 @@ defmodule Assent.Strategy.OAuth do

defp process_response({:error, error}), do: {:error, error}

defp build_authorize_url({:ok, token}, config) do
defp gen_authorize_url(config, token) do
with {:ok, base_url} <- Config.__base_url__(config),
{:ok, oauth_token} <- fetch_from_token(token, "oauth_token"),
{:ok, oauth_token_secret} <- fetch_from_token(token, "oauth_token_secret") do
Expand All @@ -320,8 +310,6 @@ defmodule Assent.Strategy.OAuth do
end
end

defp build_authorize_url({:error, error}, _config), do: {:error, error}

defp fetch_from_token(token, key) do
case Map.fetch(token, key) do
{:ok, value} -> {:ok, value}
Expand Down Expand Up @@ -350,25 +338,22 @@ defmodule Assent.Strategy.OAuth do
@impl true
@spec callback(Config.t(), map(), atom()) :: on_callback()
def callback(config, params, strategy \\ __MODULE__) do
with {:ok, oauth_token} <- fetch_oauth_token(params),
{:ok, oauth_verifier} <- fetch_oauth_verifier(params),
{:ok, token} <- get_access_token(config, oauth_token, oauth_verifier),
with {:ok, oauth_token} <- fetch_param(params, "oauth_token"),
{:ok, oauth_verifier} <- fetch_param(params, "oauth_verifier"),
{:ok, token} <- fetch_access_token(config, oauth_token, oauth_verifier),
{:ok, user} <- strategy.fetch_user(config, token) do
{:ok, %{user: user, token: token}}
end
end

defp fetch_oauth_token(%{"oauth_token" => code}), do: {:ok, code}

defp fetch_oauth_token(params),
do: {:error, MissingParamError.exception(expected_key: "oauth_token", params: params)}

defp fetch_oauth_verifier(%{"oauth_verifier" => code}), do: {:ok, code}

defp fetch_oauth_verifier(params),
do: {:error, MissingParamError.exception(expected_key: "oauth_verifier", params: params)}
defp fetch_param(params, key) do
case Map.fetch(params, key) do
{:ok, value} -> {:ok, value}
:error -> {:error, MissingParamError.exception(expected_key: key, params: params)}
end
end

defp get_access_token(config, oauth_token, oauth_verifier) do
defp fetch_access_token(config, oauth_token, oauth_verifier) do
with {:ok, base_url} <- Config.__base_url__(config) do
access_token_url = Config.get(config, :access_token_url, "/access_token")
url = process_url(base_url, access_token_url)
Expand Down Expand Up @@ -416,16 +401,16 @@ defmodule Assent.Strategy.OAuth do
@spec fetch_user(Config.t(), map()) :: {:ok, map()} | {:error, term()}
def fetch_user(config, token) do
with {:ok, url} <- Config.fetch(config, :user_url) do
config
|> request(token, :get, url)
|> process_user_response()
end
end

defp process_user_response({:ok, %HTTPResponse{status: 200, body: user}}), do: {:ok, user}
case request(config, token, :get, url) do
{:ok, %HTTPResponse{status: 200, body: user}} ->
{:ok, user}

defp process_user_response({:error, %HTTPResponse{status: 401} = response}),
do: {:error, RequestError.exception(message: "Unauthorized token", response: response)}
{:error, %HTTPResponse{status: 401} = response} ->
{:error, RequestError.exception(message: "Unauthorized token", response: response)}

defp process_user_response(any), do: process_response(any)
other ->
process_response(other)
end
end
end
end
110 changes: 54 additions & 56 deletions lib/assent/strategies/oauth2.ex
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,27 @@ defmodule Assent.Strategy.OAuth2 do
end
end

@doc """
Grants an access token.
"""
@spec grant_access_token(Config.t(), binary(), Keyword.t()) :: {:ok, map()} | {:error, term()}
def grant_access_token(config, grant_type, params) do
auth_method = Config.get(config, :auth_method, nil)
token_url = Config.get(config, :token_url, "/oauth/token")

with {:ok, base_url} <- Config.__base_url__(config),
{:ok, auth_headers, auth_body} <- authentication_params(auth_method, config) do
headers = [{"content-type", "application/x-www-form-urlencoded"}] ++ auth_headers
params = Keyword.merge(params, Keyword.put(auth_body, :grant_type, grant_type))
url = Helpers.to_url(base_url, token_url)
body = URI.encode_query(params)

:post
|> Helpers.request(url, body, headers, config)
|> process_access_token_response()
end
end

defp authentication_params(nil, config) do
with {:ok, client_id} <- Config.fetch(config, :client_id) do
headers = []
Expand Down Expand Up @@ -352,27 +373,6 @@ defmodule Assent.Strategy.OAuth2 do
end
end

@doc """
Grants an access token.
"""
@spec grant_access_token(Config.t(), binary(), Keyword.t()) :: {:ok, map()} | {:error, term()}
def grant_access_token(config, grant_type, params) do
auth_method = Config.get(config, :auth_method, nil)
token_url = Config.get(config, :token_url, "/oauth/token")

with {:ok, base_url} <- Config.__base_url__(config),
{:ok, auth_headers, auth_body} <- authentication_params(auth_method, config) do
headers = [{"content-type", "application/x-www-form-urlencoded"}] ++ auth_headers
params = Keyword.merge(params, Keyword.put(auth_body, :grant_type, grant_type))
url = Helpers.to_url(base_url, token_url)
body = URI.encode_query(params)

:post
|> Helpers.request(url, body, headers, config)
|> process_access_token_response()
end
end

defp process_access_token_response(
{:ok, %HTTPResponse{status: status, body: %{"access_token" => _} = token}}
)
Expand Down Expand Up @@ -420,7 +420,7 @@ defmodule Assent.Strategy.OAuth2 do
{:ok, map()} | {:error, term()}
def request(config, token, method, url, params \\ [], headers \\ []) do
with {:ok, base_url} <- Config.__base_url__(config),
{:ok, auth_headers} <- authorization_headers(config, token) do
{:ok, auth_headers} <- authorization_headers(token) do
req_headers = request_headers(method, auth_headers ++ headers)
req_body = request_body(method, params)
params = url_params(method, params)
Expand All @@ -430,6 +430,30 @@ defmodule Assent.Strategy.OAuth2 do
end
end

defp authorization_headers(token) do
token
|> Map.get("token_type", "Bearer")
|> String.downcase()
|> authorization_headers(token)
end

defp authorization_headers("bearer", token) do
with {:ok, access_token} <- fetch_from_token(token, "access_token") do
{:ok, [{"authorization", "Bearer #{access_token}"}]}
end
end

defp authorization_headers(type, _token) do
{:error, "Authorization with token type `#{type}` not supported"}
end

defp fetch_from_token(token, key) do
case Map.fetch(token, key) do
{:ok, value} -> {:ok, value}
:error -> {:error, "No `#{key}` in token map"}
end
end

defp request_headers(:post, headers),
do: [{"content-type", "application/x-www-form-urlencoded"}] ++ headers

Expand All @@ -450,42 +474,16 @@ defmodule Assent.Strategy.OAuth2 do
{:ok, map()} | {:error, term()}
def fetch_user(config, token, params \\ [], headers \\ []) do
with {:ok, user_url} <- Config.fetch(config, :user_url) do
config
|> request(token, :get, user_url, params, headers)
|> process_user_response()
end
end
case request(config, token, :get, user_url, params, headers) do
{:ok, %HTTPResponse{status: 200, body: user}} ->
{:ok, user}

defp authorization_headers(config, token) do
type =
token
|> Map.get("token_type", "Bearer")
|> String.downcase()
{:error, %HTTPResponse{status: 401} = response} ->
{:error, RequestError.exception(message: "Unauthorized token", response: response)}

authorization_headers(config, token, type)
end

defp authorization_headers(_config, token, "bearer") do
with {:ok, access_token} <- fetch_from_token(token, "access_token") do
{:ok, [{"authorization", "Bearer #{access_token}"}]}
other ->
process_response(other)
end
end
end

defp authorization_headers(_config, _token, type) do
{:error, "Authorization with token type `#{type}` not supported"}
end

defp fetch_from_token(token, key) do
case Map.fetch(token, key) do
{:ok, value} -> {:ok, value}
:error -> {:error, "No `#{key}` in token map"}
end
end

defp process_user_response({:ok, %HTTPResponse{status: 200, body: user}}), do: {:ok, user}

defp process_user_response({:error, %HTTPResponse{status: 401} = response}),
do: {:error, RequestError.exception(message: "Unauthorized token", response: response)}

defp process_user_response(any), do: process_response(any)
end
Loading

0 comments on commit 949a3fc

Please sign in to comment.