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

Thank you!

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