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

Bearer token can be fetched dynamically. #417

Merged
merged 6 commits into from
Oct 7, 2024

Conversation

chgeuer
Copy link
Contributor

@chgeuer chgeuer commented Oct 4, 2024

Hi @wojtekmach,

This PR allows the bearer token to be fetched dynamically, by supplying a 0-arity function:

Req.new(
   auth: {:bearer, fn -> "..." end}
)

This is e.g. helpful when having a GenServer (like here) that constantly keeps a valid token in-memory. This way, you just tell :req to grab the latest token whenever it needs to make a request.

I haven't open an issuer for this, can do so if you'd prefer that.

Without a bearer token being dynamically fetched, I do something like this (which is like, nah):

defmodule SomeClientWithDynamicBearerFetching do
  @new_opts NimbleOptions.new!(bearer_token_func: [type: {:fun, 0}, required: true])

  def new(req, opts) do
    %{
      bearer_token_func: bearer_token_func
    } = opts |> opts_to_map(@new_opts)

    req
    |> attach_dynamic_bearer_token(bearer_token_func)
  end

  def new(opts \\ []) do
    %{
      bearer_token_func: bearer_token_func
    } = opts |> opts_to_map(@new_opts)

    Req.new()
    |> attach_dynamic_bearer_token(bearer_token_func)
  end

  def attach_dynamic_bearer_token(req, bearer_token_func)
      when is_function(bearer_token_func, 0) do
    req
    |> Req.Request.put_private(:bearer_token_func, bearer_token_func)
    |> Req.Request.prepend_request_steps(dynamically_add_auth_bearer: &add_fresh_bearer/1)
  end

  defp add_fresh_bearer(req) do
    case Req.Request.get_private(req, :bearer_token_func) do
      func when is_function(func, 0) ->
        req
        |> Req.Request.merge_options(auth: {:bearer, func.()})

      nil ->
        req
    end
  end

  def opts_to_map(opts, nimble_options_schema) do
    {:ok, opts} = NimbleOptions.validate(opts, nimble_options_schema)
    Map.new(opts)
  end
end

@chgeuer
Copy link
Contributor Author

chgeuer commented Oct 7, 2024

Hi @wojtekmach, do you prefer having a conversation in an issue, or is just opening a PR OK?

@wojtekmach
Copy link
Owner

PR is much appreciated, thank you!

I'd like to take some time thinking this through. If we allow auth: {:bearer, fun} do we allow auth: {:basic, fun} too? It doesn't seem that useful however I think there's an argument to be made to make things uniform.

FWIW we can already achieve this today like this:

Req.new()
|> Req.Request.prepend_request_steps(
  auth_bearer_dynamic: fn req ->
    with %{options: %{auth: {:bearer, fun}}} when is_function(fun) <- req do
      put_in(req.options[:auth], {:bearer, fun.()})
    end
  end
)

@chgeuer
Copy link
Contributor Author

chgeuer commented Oct 7, 2024

In my opinion, the dynamic capability is not so important for basic auth (unless you want symmetry). A basic authN is certainly a known user password, which doesn't change or expire frequently. However, a bearer token typically expires after a (short) period of time.

@wojtekmach
Copy link
Owner

Right, sorry, I meant to say for basic auth dynamic function seems unnecessary but we may need to add it for consistency if we were add it for bearer auth. But maybe we don't. I'll think about it some more. Thank you.

@wojtekmach
Copy link
Owner

@chgeuer OK, I think I have the answer, a more generalised solution without explicitly supporting {:basic, fun}, instead of:

auth: {:bearer, fn -> ... end}

let's go with:

auth: fn -> {:bearer, ...} end

in other words instead of:

@type auth :: string | {:basic, string} | {:bearer, string}

we have:

@type auth :: auth | (-> auth)
      when auth:
             string
             | {:basic, string}
             | {:bearer, string}

WDYT?

If this sounds good, please remember to update "redact" test in test/req_test.exs.

@chgeuer chgeuer marked this pull request as draft October 7, 2024 17:34
@chgeuer chgeuer marked this pull request as ready for review October 7, 2024 17:55
@chgeuer
Copy link
Contributor Author

chgeuer commented Oct 7, 2024

@wojtekmach Can we run this in the CI/CD here?

lib/req.ex Outdated Show resolved Hide resolved
lib/req/request.ex Outdated Show resolved Hide resolved
lib/req/steps.ex Outdated Show resolved Hide resolved
@wojtekmach wojtekmach merged commit c789ff9 into wojtekmach:main Oct 7, 2024
2 checks passed
@wojtekmach
Copy link
Owner

Thank you!

@chgeuer
Copy link
Contributor Author

chgeuer commented Oct 7, 2024

Thank you for creating and maintaining it, good stuff.

@chgeuer
Copy link
Contributor Author

chgeuer commented Oct 11, 2024

QQ, would it be possible to release this as 0.5.7 to hex.pm?

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

Successfully merging this pull request may close these issues.

2 participants