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.
+102
-24
lib/atex/oauth.ex
+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
lekkice.moe
submitted
#1
1 commit
expand
collapse
refactor: add opts argument to Oauth module
expand 1 comment
pull request successfully merged
lekkice.moe
submitted
#0
1 commit
expand
collapse
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
Thank you!