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
+102 -24
Diff #1
+102 -24
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() :: map() 56 - def create_client_metadata() do 57 - 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 %{ 62 - client_id: Config.client_id(), 63 - redirect_uris: [Config.redirect_uri() | Config.extra_redirect_uris()], 64 application_type: "web", 65 grant_types: ["authorization_code", "refresh_token"], 66 - scope: Config.scopes(), 67 response_type: ["code"], 68 token_endpoint_auth_method: "private_key_jwt", 69 token_endpoint_auth_signing_alg: "ES256", ··· 125 - `{:ok, :invalid_par_response}` - Server respondend incorrectly to the request 126 - `{:error, reason}` - Error creating authorization URL 127 """ 128 @spec create_authorization_url( 129 authorization_metadata(), 130 String.t(), 131 String.t(), 132 - String.t() 133 ) :: {:ok, String.t()} | {:error, any()} 134 def create_authorization_url( 135 authz_metadata, 136 state, 137 code_verifier, 138 - login_hint 139 ) do 140 code_challenge = :crypto.hash(:sha256, code_verifier) |> Base.url_encode64(padding: false) 141 - key = get_key() 142 143 client_assertion = 144 - create_client_assertion(key, Config.client_id(), authz_metadata.issuer) 145 146 body = 147 %{ 148 response_type: "code", 149 - client_id: Config.client_id(), 150 - redirect_uri: Config.redirect_uri(), 151 state: state, 152 code_challenge_method: "S256", 153 code_challenge: code_challenge, 154 - scope: Config.scopes(), 155 client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", 156 client_assertion: client_assertion, 157 login_hint: login_hint ··· 160 case Req.post(authz_metadata.par_endpoint, form: body) do 161 {:ok, %{body: %{"request_uri" => request_uri}}} -> 162 query = 163 - %{client_id: Config.client_id(), request_uri: request_uri} 164 |> URI.encode_query() 165 166 {:ok, "#{authz_metadata.authorization_endpoint}?#{query}"} ··· 192 - `{:ok, tokens, nonce}` - Successfully obtained tokens with returned DPoP nonce 193 - `{:error, reason}` - Error exchanging code for tokens 194 """ 195 @spec validate_authorization_code( 196 authorization_metadata(), 197 JOSE.JWK.t(), 198 String.t(), 199 - String.t() 200 ) :: {:ok, tokens(), String.t()} | {:error, any()} 201 def validate_authorization_code( 202 authz_metadata, 203 dpop_key, 204 code, 205 - code_verifier 206 ) do 207 - key = get_key() 208 209 client_assertion = 210 - create_client_assertion(key, Config.client_id(), authz_metadata.issuer) 211 212 body = 213 %{ 214 grant_type: "authorization_code", 215 - client_id: Config.client_id(), 216 - redirect_uri: Config.redirect_uri(), 217 code: code, 218 code_verifier: code_verifier, 219 client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", ··· 245 end 246 end 247 248 - def refresh_token(refresh_token, dpop_key, issuer, token_endpoint) do 249 - key = get_key() 250 251 client_assertion = 252 - create_client_assertion(key, Config.client_id(), issuer) 253 254 body = %{ 255 grant_type: "refresh_token", 256 refresh_token: refresh_token, 257 - client_id: Config.client_id(), 258 client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", 259 client_assertion: client_assertion 260 }
··· 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, 162 state, 163 code_verifier, 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, 248 dpop_key, 249 code, 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