An Elixir toolkit for the AT Protocol. hexdocs.pm/atex
elixir bluesky atproto decentralization

Fix crash in Oauth module when config isn't provided but opts are #3

merged opened by lekkice.moe targeting main from lekkice.moe/atex: oauth-lazy-eval

I found a small bug with the changes i made earlier. For example, in this part:

⁨⁨⁨⁨```elixir opts = Keyword.validate!(opts, key: Config.get_key(), client_id: Config.client_id(), redirect_uri: Config.redirect_uri(), extra_redirect_uris: Config.extra_redirect_uris(), scopes: Config.scopes() )

⁩⁩⁩
Elixir evaluates every function call before passing it to the keyword list in the second argument, so Config.get_key() always gets called. The crash occurs in that function:

⁨⁨⁨⁨```elixir
  def get_key() do
    private_key =
      Application.fetch_env!(:atex, Atex.OAuth)[:private_key]
      |> Base.decode64!()
```⁩⁩⁩⁩

so, if :private_key isn't defined, it tries to call Base.decode64!(nil), causing it to throw an error.

The solution i propose is using Keyword.validate! without any default values, then using Keyword.get_lazy() to evaluate a default function only in case the opt wasn't passed:

⁨⁨```elixir
    opts =
      Keyword.validate!(
        opts,
        [:key, :client_id, :redirect_uri, :extra_redirect_uris, :scopes]
      )

    key = Keyword.get_lazy(opts, :key, &Config.get_key/0)
    client_id = Keyword.get_lazy(opts, :client_id, &Config.client_id/0)
    redirect_uri = Keyword.get_lazy(opts, :redirect_uri, &Config.redirect_uri/0)
```⁩⁩

This should prevent the config functions from being called when not necessary.

When i tested locally last time i forgot to try without the runtime config, so i didn't catch the bug. Sorry for the inconvenience
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:dgzvruva4jbzqbta335jtvoz/sh.tangled.repo.pull/3mdpunui4wr22
+29 -35
Diff #0
+29 -35
lib/atex/oauth.ex
··· 80 80 @spec create_client_metadata(list(create_client_metadata_option())) :: map() 81 81 def create_client_metadata(opts \\ []) do 82 82 opts = 83 - Keyword.validate!(opts, 84 - key: Config.get_key(), 85 - client_id: Config.client_id(), 86 - redirect_uri: Config.redirect_uri(), 87 - extra_redirect_uris: Config.extra_redirect_uris(), 88 - scopes: Config.scopes() 83 + Keyword.validate!( 84 + opts, 85 + [:key, :client_id, :redirect_uri, :extra_redirect_uris, :scopes] 89 86 ) 90 87 91 - key = Keyword.get(opts, :key) 92 - client_id = Keyword.get(opts, :client_id) 93 - redirect_uri = Keyword.get(opts, :redirect_uri) 94 - extra_redirect_uris = Keyword.get(opts, :extra_redirect_uris) 95 - scopes = Keyword.get(opts, :scopes) 88 + key = Keyword.get_lazy(opts, :key, &Config.get_key/0) 89 + client_id = Keyword.get_lazy(opts, :client_id, &Config.client_id/0) 90 + redirect_uri = Keyword.get_lazy(opts, :redirect_uri, &Config.redirect_uri/0) 91 + 92 + extra_redirect_uris = 93 + Keyword.get_lazy(opts, :extra_redirect_uris, &Config.extra_redirect_uris/0) 94 + 95 + scopes = Keyword.get_lazy(opts, :scopes, &Config.scopes/0) 96 96 97 97 {_, jwk} = key |> JOSE.JWK.to_public_map() 98 98 jwk = Map.merge(jwk, %{use: "sig", kid: key.fields["kid"]}) ··· 179 179 opts \\ [] 180 180 ) do 181 181 opts = 182 - Keyword.validate!(opts, 183 - key: Config.get_key(), 184 - client_id: Config.client_id(), 185 - redirect_uri: Config.redirect_uri(), 186 - scopes: Config.scopes() 182 + Keyword.validate!( 183 + opts, 184 + [:key, :client_id, :redirect_uri, :scopes] 187 185 ) 188 186 189 - key = Keyword.get(opts, :key) 190 - client_id = Keyword.get(opts, :client_id) 191 - redirect_uri = Keyword.get(opts, :redirect_uri) 192 - scopes = Keyword.get(opts, :scopes) 187 + key = Keyword.get_lazy(opts, :key, &Config.get_key/0) 188 + client_id = Keyword.get_lazy(opts, :client_id, &Config.client_id/0) 189 + redirect_uri = Keyword.get_lazy(opts, :redirect_uri, &Config.redirect_uri/0) 190 + scopes = Keyword.get_lazy(opts, :scopes, &Config.scopes/0) 193 191 194 192 code_challenge = :crypto.hash(:sha256, code_verifier) |> Base.url_encode64(padding: false) 195 193 ··· 260 258 opts \\ [] 261 259 ) do 262 260 opts = 263 - Keyword.validate!(opts, 264 - key: get_key(), 265 - client_id: Config.client_id(), 266 - redirect_uri: Config.redirect_uri(), 267 - scopes: Config.scopes() 261 + Keyword.validate!( 262 + opts, 263 + [:key, :client_id, :redirect_uri, :scopes] 268 264 ) 269 265 270 - key = Keyword.get(opts, :key) 271 - client_id = Keyword.get(opts, :client_id) 272 - redirect_uri = Keyword.get(opts, :redirect_uri) 266 + key = Keyword.get_lazy(opts, :key, &get_key/0) 267 + client_id = Keyword.get_lazy(opts, :client_id, &Config.client_id/0) 268 + redirect_uri = Keyword.get_lazy(opts, :redirect_uri, &Config.redirect_uri/0) 273 269 274 270 client_assertion = 275 271 create_client_assertion(key, client_id, authz_metadata.issuer) ··· 320 316 {:ok, tokens(), String.t()} | {:error, any()} 321 317 def refresh_token(refresh_token, dpop_key, issuer, token_endpoint, opts \\ []) do 322 318 opts = 323 - Keyword.validate!(opts, 324 - key: get_key(), 325 - client_id: Config.client_id(), 326 - redirect_uri: Config.redirect_uri(), 327 - scopes: Config.scopes() 319 + Keyword.validate!( 320 + opts, 321 + [:key, :client_id, :redirect_uri, :scopes] 328 322 ) 329 323 330 - key = Keyword.get(opts, :key) 331 - client_id = Keyword.get(opts, :client_id) 324 + key = Keyword.get_lazy(opts, :key, &get_key/0) 325 + client_id = Keyword.get_lazy(opts, :client_id, &Config.client_id/0) 332 326 333 327 client_assertion = 334 328 create_client_assertion(key, client_id, issuer)

History

1 round 0 comments
sign up or login to add to the discussion
lekkice.moe submitted #0
1 commit
expand
fix: use lazy evaluation for fetching Oauth config
expand 0 comments
pull request successfully merged