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

Add opts argument to functions in Atex.OAuth module #2

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

This PR adds an opts argument to allow overriding config values. This makes integration with external frameworks easier, as secrets can be provided at the function call level.

Labels

None yet.

assignee

None yet.

Participants 2
AT URI
at://did:plc:dgzvruva4jbzqbta335jtvoz/sh.tangled.repo.pull/3mdgohawhku22
+94 -26
Interdiff #0 โ†’ #1
+94 -26
lib/atex/oauth.ex
··· 52 52 Get a map cnotaining the client metadata information needed for an 53 53 authorization server to validate this client. 54 54 """ 55 - @spec create_client_metadata(list()) :: map() 55 + @type create_client_metadata_option :: 56 + {:key, JOSE.JWK.t()} 57 + | {:client_id, String.t()} 58 + | {:redirect_uri, String.t()} 59 + | {:extra_redirect_uris, list(String.t())} 60 + | {:scopes, String.t()} 61 + @spec create_client_metadata(list(create_client_metadata_option())) :: map() 56 62 def create_client_metadata(opts \\ []) do 57 - key = opts[:key] || Config.get_key() 63 + opts = 64 + Keyword.validate!(opts, 65 + key: Config.get_key(), 66 + client_id: Config.client_id(), 67 + redirect_uri: Config.redirect_uri(), 68 + extra_redirect_uris: Config.extra_redirect_uris(), 69 + scopes: Config.scopes() 70 + ) 71 + 72 + key = Keyword.get(opts, :key) 73 + client_id = Keyword.get(opts, :client_id) 74 + redirect_uri = Keyword.get(opts, :redirect_uri) 75 + extra_redirect_uris = Keyword.get(opts, :extra_redirect_uris) 76 + scopes = Keyword.get(opts, :scopes) 77 + 58 78 {_, jwk} = key |> JOSE.JWK.to_public_map() 59 79 jwk = Map.merge(jwk, %{use: "sig", kid: key.fields["kid"]}) 60 80 61 - redirect_uris = 62 - [ 63 - opts[:redirect_uri] || Config.redirect_uri() 64 - | opts[:extra_redirect_uris] || Config.extra_redirect_uris() 65 - ] 66 - 67 81 %{ 68 - client_id: opts[:client_id] || Config.client_id(), 69 - redirect_uris: redirect_uris, 82 + client_id: client_id, 83 + redirect_uris: [redirect_uri | extra_redirect_uris], 70 84 application_type: "web", 71 85 grant_types: ["authorization_code", "refresh_token"], 72 - scope: opts[:scopes] || Config.scopes(), 86 + scope: scopes, 73 87 response_type: ["code"], 74 88 token_endpoint_auth_method: "private_key_jwt", 75 89 token_endpoint_auth_signing_alg: "ES256", ··· 131 145 - `{:ok, :invalid_par_response}` - Server respondend incorrectly to the request 132 146 - `{:error, reason}` - Error creating authorization URL 133 147 """ 148 + @type create_authorization_url_option :: 149 + {:key, JOSE.JWK.t()} 150 + | {:client_id, String.t()} 151 + | {:redirect_uri, String.t()} 152 + | {:scopes, String.t()} 134 153 @spec create_authorization_url( 135 154 authorization_metadata(), 136 155 String.t(), 137 156 String.t(), 138 157 String.t(), 139 - list() 158 + list(create_authorization_url_option()) 140 159 ) :: {:ok, String.t()} | {:error, any()} 141 160 def create_authorization_url( 142 161 authz_metadata, ··· 145 164 login_hint, 146 165 opts \\ [] 147 166 ) do 167 + opts = 168 + Keyword.validate!(opts, 169 + key: Config.get_key(), 170 + client_id: Config.client_id(), 171 + redirect_uri: Config.redirect_uri(), 172 + scopes: Config.scopes() 173 + ) 174 + 175 + key = Keyword.get(opts, :key) 176 + client_id = Keyword.get(opts, :client_id) 177 + redirect_uri = Keyword.get(opts, :redirect_uri) 178 + scopes = Keyword.get(opts, :scopes) 179 + 148 180 code_challenge = :crypto.hash(:sha256, code_verifier) |> Base.url_encode64(padding: false) 149 - key = opts[:key] || get_key() 150 181 151 182 client_assertion = 152 - create_client_assertion(key, opts[:client_id] || Config.client_id(), authz_metadata.issuer) 183 + create_client_assertion(key, client_id, authz_metadata.issuer) 153 184 154 185 body = 155 186 %{ 156 187 response_type: "code", 157 - client_id: opts[:client_id] || Config.client_id(), 158 - redirect_uri: opts[:redirect_uri] || Config.redirect_uri(), 188 + client_id: client_id, 189 + redirect_uri: redirect_uri, 159 190 state: state, 160 191 code_challenge_method: "S256", 161 192 code_challenge: code_challenge, 162 - scope: opts[:scopes] || Config.scopes(), 193 + scope: scopes, 163 194 client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", 164 195 client_assertion: client_assertion, 165 196 login_hint: login_hint ··· 168 199 case Req.post(authz_metadata.par_endpoint, form: body) do 169 200 {:ok, %{body: %{"request_uri" => request_uri}}} -> 170 201 query = 171 - %{client_id: opts[:client_id] || Config.client_id(), request_uri: request_uri} 202 + %{client_id: client_id, request_uri: request_uri} 172 203 |> URI.encode_query() 173 204 174 205 {:ok, "#{authz_metadata.authorization_endpoint}?#{query}"} ··· 200 231 - `{:ok, tokens, nonce}` - Successfully obtained tokens with returned DPoP nonce 201 232 - `{:error, reason}` - Error exchanging code for tokens 202 233 """ 234 + @type validate_authorization_code_option :: 235 + {:key, JOSE.JWK.t()} 236 + | {:client_id, String.t()} 237 + | {:redirect_uri, String.t()} 238 + | {:scopes, String.t()} 203 239 @spec validate_authorization_code( 204 240 authorization_metadata(), 205 241 JOSE.JWK.t(), 206 242 String.t(), 207 243 String.t(), 208 - list() 244 + list(validate_authorization_code_option()) 209 245 ) :: {:ok, tokens(), String.t()} | {:error, any()} 210 246 def validate_authorization_code( 211 247 authz_metadata, ··· 214 250 code_verifier, 215 251 opts \\ [] 216 252 ) do 217 - key = opts[:key] || get_key() 253 + opts = 254 + Keyword.validate!(opts, 255 + key: get_key(), 256 + client_id: Config.client_id(), 257 + redirect_uri: Config.redirect_uri(), 258 + scopes: Config.scopes() 259 + ) 218 260 261 + key = Keyword.get(opts, :key) 262 + client_id = Keyword.get(opts, :client_id) 263 + redirect_uri = Keyword.get(opts, :redirect_uri) 264 + 219 265 client_assertion = 220 - create_client_assertion(key, opts[:client_id] || Config.client_id(), authz_metadata.issuer) 266 + create_client_assertion(key, client_id, authz_metadata.issuer) 221 267 222 268 body = 223 269 %{ 224 270 grant_type: "authorization_code", 225 - client_id: opts[:client_id] || Config.client_id(), 226 - redirect_uri: opts[:redirect_uri] || Config.redirect_uri(), 271 + client_id: client_id, 272 + redirect_uri: redirect_uri, 227 273 code: code, 228 274 code_verifier: code_verifier, 229 275 client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", ··· 255 301 end 256 302 end 257 303 304 + @type refresh_token_option :: 305 + {:key, JOSE.JWK.t()} 306 + | {:client_id, String.t()} 307 + | {:redirect_uri, String.t()} 308 + | {:scopes, String.t()} 309 + @spec refresh_token( 310 + String.t(), 311 + JOSE.JWK.t(), 312 + String.t(), 313 + String.t(), 314 + list(refresh_token_option()) 315 + ) :: 316 + {:ok, tokens(), String.t()} | {:error, any()} 258 317 def refresh_token(refresh_token, dpop_key, issuer, token_endpoint, opts \\ []) do 259 - key = opts[:key] || get_key() 318 + opts = 319 + Keyword.validate!(opts, 320 + key: get_key(), 321 + client_id: Config.client_id(), 322 + redirect_uri: Config.redirect_uri(), 323 + scopes: Config.scopes() 324 + ) 260 325 326 + key = Keyword.get(opts, :key) 327 + client_id = Keyword.get(opts, :client_id) 328 + 261 329 client_assertion = 262 - create_client_assertion(key, opts[:client_id] || Config.client_id(), issuer) 330 + create_client_assertion(key, client_id, issuer) 263 331 264 332 body = %{ 265 333 grant_type: "refresh_token", 266 334 refresh_token: refresh_token, 267 - client_id: opts[:client_id] || Config.client_id(), 335 + client_id: client_id, 268 336 client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", 269 337 client_assertion: client_assertion 270 338 }

History

2 rounds 5 comments
sign up or login to add to the discussion
1 commit
expand
refactor: add opts argument to Oauth module
expand 1 comment
pull request successfully merged
1 commit
expand
refactor: add opts argument to Oauth module
expand 4 comments

Permissions are a bit broken for my tangled repos at the moment, recreate this PR on https://github.com/cometsh/atex instead and I can merge there.

Nevermind, it's been resolved and I can do things now.

Could you use Keyword.validate & Keyword.get to be consistent with the rest of the codebase?

no problem, i fixed the typespecs too