rodauth-oauth 0.9.2 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2514b45f82f9e8dda98f15dc2c1ccc0eeba306c9d1ee40e6fa47e4999d766c1c
4
- data.tar.gz: d68579829772121a157b7bd654fb40999921af46673910caa21892462655ed0e
3
+ metadata.gz: 9a8ec41b29b514398bc764c53190df1b832387e1c392dd4c03988ffc16bdf0c5
4
+ data.tar.gz: 565c9ebb6871cd7a36b2ddf549cd368c509bce9303fb308cda4620d96a191647
5
5
  SHA512:
6
- metadata.gz: 8121458a789119610c920c835fc99cde76d4eca41fb7bb48acbe9c1e2f4be89f68c9370235335d7dc8c6b3b715b6825a4d77bafbda37551464ebf35a516f55de
7
- data.tar.gz: bdd2c1d2bee336459186606b2bfb293cd690333a58c421798155e6bc53e418b71bbd142ac19e48ddcff701f28eaaa6c65c8d045c715a7f84690fb5dd6865ddbe
6
+ metadata.gz: 724c9d6bf98689b63f1919ea9e513907c784152b7a164797e152f6cf5c9bacf19364bf8ea0df8dd3b03f281f2190eaafb0927d70d040e1c636eea8e7a4846269
7
+ data.tar.gz: f89c6dea666c7bfe2b8722d84cbc04cf38c42dc9827df4ec05596d490dc73cbd0f1ac3e57adf358a564dc37ef774f913df383192ee63771a91a6cdc454d35c4e
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Rodauth::Oauth
2
2
 
3
- [![pipeline status](https://gitlab.com/honeyryderchuck/rodauth-oauth/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/pipelines?page=1&ref=master)
3
+ [![Gem Version](https://badge.fury.io/rb/rodauth-oauth.svg)](http://rubygems.org/gems/rodauth-oauth)
4
+ [![pipeline status](https://gitlab.com/honeyryderchuck/rodauth-oauth/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/rodauth-oauth/pipelines?page=1&scope=all&ref=master)
4
5
  [![coverage report](https://gitlab.com/honeyryderchuck/rodauth-oauth/badges/master/coverage.svg?job=coverage)](https://honeyryderchuck.gitlab.io/rodauth-oauth/coverage/#_AllFiles)
5
6
 
6
7
  This is an extension to the `rodauth` gem which implements the [OAuth 2.0 framework](https://tools.ietf.org/html/rfc6749) for an authorization server.
@@ -20,14 +21,16 @@ This gem implements the following RFCs and features of OAuth:
20
21
  * `oauth_token_introspection` - [Token introspection](https://tools.ietf.org/html/rfc7662);
21
22
  * [Authorization Server Metadata](https://tools.ietf.org/html/rfc8414);
22
23
  * `oauth_pkce` - [PKCE](https://tools.ietf.org/html/rfc7636);
23
- * Access Type (Token refresh online and offline);
24
24
  * `oauth_jwt` - [JWT Access Tokens](https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-07);
25
+ * Supports [JWT Secured Authorization Request](https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-20);
26
+ * `oauth_resource_indicators` - [Resource Indicators](https://datatracker.ietf.org/doc/html/rfc8707);
27
+ * Access Type (Token refresh online and offline);
25
28
  * `oauth_http_mac` - [MAC Authentication Scheme](https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-02);
26
29
  * `oauth_assertion_base` - [Assertion Framework](https://datatracker.ietf.org/doc/html/rfc7521);
27
30
  * `oauth_saml_bearer_grant` - [SAML 2.0 Bearer Assertion](https://datatracker.ietf.org/doc/html/rfc7522);
28
31
  * `oauth_jwt_bearer_grant` - [JWT Bearer Assertion](https://datatracker.ietf.org/doc/html/rfc7523);
29
- * [JWT Secured Authorization Requests](https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-20);
30
- * [Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591);
32
+
33
+ * `oauth_dynamic_client_registration` - [Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591);
31
34
  * OAuth application and token management dashboards;
32
35
 
33
36
  It also implements the [OpenID Connect layer](https://openid.net/connect/) (via the `openid` feature) on top of the OAuth features it provides, including:
@@ -0,0 +1,100 @@
1
+ ## 0.10.0 (10/06/2022)
2
+
3
+ ### Features
4
+
5
+ #### Resource Indicators
6
+
7
+ RFC: https://datatracker.ietf.org/doc/html/rfc8707
8
+
9
+ `rodauth-oauth` now supports Resource Indicators, via the optional `:oauth_resource_indicators` feature.
10
+
11
+ #### JWT: extra options
12
+
13
+ The following extra option values were added:
14
+
15
+ * `oauth_jwt_jwe_keys`
16
+ * `oauth_jwt_public_keys`
17
+ * `oauth_jwt_jwe_public_keys`
18
+
19
+ `:oauth_jwt_jwe_keys` should be used to store all provider combos of encryption keys, indexed by an algo/method tuple:
20
+
21
+ ```ruby
22
+ oauth_jwt_jwe_keys { { %w[RSA-OAEP A128CBC-HS256] => key } }
23
+ ```
24
+
25
+ The first element of the hash should indicate the preferred encryption mode, when no combination is specifically requested.
26
+
27
+ It should be considered the most future-proof way of declaring JWE keys, and support for `oauth_jwt_jwe_key` and friends should be soon deprecated.
28
+
29
+ Both `oauth_jwt_public_keys` and `oauth_jwt_jwe_public_keys` provide a way to declare multiple keys to be exposed as the provider JWKs in the `/jwks` endpoint.
30
+
31
+ ### Improvements
32
+
33
+ * Added translations for portuguese.
34
+
35
+ #### OpenID Connect improvements
36
+
37
+ * The `:oidc` feature now depends on `rodauth`'s [account_expiration](http://rodauth.jeremyevans.net/rdoc/files/doc/account_expiration_rdoc.html) feature.
38
+
39
+ Although a more-involved-somewhat-breaking change, it was required in order to keep track of account login event timestamps, necessary for correct `"auth_time"` calculation (see the first bugfix mention for more details, and Breaking Changes for migration path).
40
+
41
+
42
+ * Support for the `ui_locales` parameter was added. This feature depends on the `:i18n` feature provided by [rodauth-i18n](https://github.com/janko/rodauth-i18n).
43
+ * Support for the `claims_locales` parameter was added, in that the `get_oidc_param` and `get_additional_param`, when accepting a 3rd parameter, will be passed a locale code:
44
+
45
+ ```ruby
46
+ # given "claims_locales=en pt"
47
+
48
+ get_oidc_param { |account, param, locale| }
49
+ # will be called twice for the same param, one with locale as "en", another as "pt"
50
+
51
+ get_oidc_param { |account, param| }
52
+ # will be called once without locale
53
+ ```
54
+
55
+ * Support for `max_age` parameter was added.
56
+
57
+ * Support for `acr_values` parameter was added.
58
+
59
+ When "phr", and a `rodauth` 2-factor feature (like [otp](http://rodauth.jeremyevans.net/rdoc/files/doc/otp_rdoc.html)) is enabled, the user will be requested for 2-factor authentication before performing the OpenID Authorization Request.
60
+
61
+ When "phrh", and `rodauth`'s [webauthn_login](http://rodauth.jeremyevans.net/rdoc/files/doc/webauthn_login_rdoc.html) feature is enabled, the user will be requested for WebAuthn authentication before performing the OpenID Authorization Request.
62
+
63
+ Any other acr values are considered provider-specific, and the `require_acr_value(acr_value)` option should be provided to deal with it (it'll be called after authentication is ensured and before the authorization request is processed).
64
+
65
+ ### Bugfixes
66
+
67
+ * reverted the `"auth_time"` calculation "fix" introduced in 0.9.3, which broke compliance with the RFC (the implementation prior to that was also broken, hence why `"account_expiration"` plugin was introduced as a dependency).
68
+
69
+ ### Breaking Changes
70
+
71
+ As you read already, the `"account_expiration"` feature is now required by default by `"oidc"`. In order to migrate to it, here's a suggested strategy:
72
+
73
+ 1. Add the relevant database tables
74
+
75
+ Add a migration looking roughly like this:
76
+
77
+ ```ruby
78
+ create_table(:account_activity_times) do
79
+ foreign_key :id, :accounts, primary_key: true, type: Integer
80
+ DateTime :last_activity_at, null: false
81
+ DateTime :last_login_at, null: false
82
+ DateTime :expired_at
83
+ end
84
+ ```
85
+
86
+ 2. Update and deploy `rodauth-oauth` 0.10.0
87
+
88
+ (Nothing required beyond `enable :oidc`.)
89
+
90
+ 3. Set `:last_login_at` to a value.
91
+
92
+ Like now. You can , for example, run this SQL:
93
+
94
+ ```sql
95
+ UPDATE account_activity_times SET last_login_at = CURRENT_TIMESTAMP;
96
+ ```
97
+
98
+ ---
99
+
100
+ That's it, nothing fancy or accurate. Yes, the `last_login_at` is wrong, but as sessions expire, it should go back to normal.
@@ -0,0 +1,5 @@
1
+ ### 0.10.1 (20/06/2022)
2
+
3
+ #### Bugfixes
4
+
5
+ * refresh token grant logic wasn't scoping the token to be revoked/retokened, which was a bug introduced in a recent refactoring (commit 83e3f183f6c9941d37c8fe8cfd3fc258ab9c576a).
@@ -4,7 +4,7 @@
4
4
 
5
5
  * Fixed remaining namespacing fix issues requiring usage of `require "rodauth-oauth"`.
6
6
  * Fixed wrong expectation of database for resource-server mode when `:oauth_management_base` plugin was used.
7
- * oidc: fixed incorrect grant creation flow whenn using `nonce` param.
8
- * oidc: fixed jwt encoding regression when not setting encryption method/algorithmm for client applications.
7
+ * oidc: fixed incorrect grant creation flow when using `nonce` param.
8
+ * oidc: fixed jwt encoding regression when not setting encryption method/algorithm for client applications.
9
9
  * templates: added missing jwks field to the "New oauth application" form.
10
10
  * Several fixes on the example OIDC applications, mostly around CSRF breakage when using latest version of `omniauth`.
@@ -0,0 +1,9 @@
1
+ ### 0.9.3 (30/05/2022)
2
+
3
+ #### Bugfixes
4
+
5
+ * `oauth_jwt`: new access tokens generated via the `"refresh_token"` grant type are now JWT (it was falling back to non JWT behaviour);
6
+ * `oidc`: a new `id_token` is now generated via the `"refresh_token"` grant type with "rotation" policy (it was being omitted from the response);
7
+ * `oidc`: fixing calculation of `"auth_time"` claim, which (as per RFC) needs to stay the same across first authentication and subsequent `"refresh_token"` requests;
8
+ * it requires a new db column (default: `"auth_time"`, datetime) in the `"oauth_tokens"` database;
9
+ * hash-column `"refresh_token"` will now expose the refresh token (instead of the hash column version) in the `"refresh_token"` grant type response payload (only happened in "non-rotation" refresh token mode).
@@ -34,13 +34,37 @@
34
34
  </div>
35
35
  <% end %>
36
36
  <%= hidden_field_tag :client_id, params[:client_id] %>
37
- <% %i[access_type response_type state nonce redirect_uri code_challenge code_challenge_method].each do |oauth_param| %>
37
+ <% %i[access_type response_type response_mode state redirect_uri].each do |oauth_param| %>
38
38
  <% if params[oauth_param] %>
39
39
  <%= hidden_field_tag oauth_param, params[oauth_param] %>
40
40
  <% end %>
41
41
  <% end %>
42
- <% if params[:response_mode] %>
43
- <%= hidden_field_tag :response_mode, params[:response_mode] %>
42
+ <% if rodauth.features.include?(:oauth_resource_indicators) && rodauth.resource_indicators %>
43
+ <% rodauth.resource_indicators.each do |resource| %>
44
+ <%= hidden_field_tag "resource", resource %>
45
+ <% end %>
46
+ <% end %>
47
+ <% if rodauth.features.include?(:oauth_pkce) %>
48
+ <% if params[:code_challenge] %>
49
+ <%= hidden_field_tag :code_challenge, params[:code_challenge] %>
50
+ <% end %>
51
+ <% if params[:code_challenge_method] %>
52
+ <%= hidden_field_tag :code_challenge_method, params[:code_challenge_method] %>
53
+ <% end %>
54
+ <% end %>
55
+ <% if rodauth.features.include?(:oidc) %>
56
+ <% if params[:nonce] %>
57
+ <%= hidden_field_tag :nonce, params[:nonce] %>
58
+ <% end %>
59
+ <% if params[:ui_locales] %>
60
+ <%= hidden_field_tag :ui_locales, params[:ui_locales] %>
61
+ <% end %>
62
+ <% if params[:claims_locales] %>
63
+ <%= hidden_field_tag :claims_locales, params[:claims_locales] %>
64
+ <% end %>
65
+ <% if params[:acr_values] %>
66
+ <%= hidden_field_tag :acr, params[:acr_values] %>
67
+ <% end %>
44
68
  <% end %>
45
69
  </div>
46
70
  <p class="text-center">
@@ -52,6 +52,8 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
52
52
  # device code grant
53
53
  # t.string :user_code, null: true, unique: true
54
54
  # t.datetime :last_polled_at, null: true
55
+ # when using :oauth_resource_indicators feature
56
+ # t.string :resource
55
57
  end
56
58
 
57
59
  create_table :oauth_tokens do |t|
@@ -77,6 +79,9 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
77
79
  t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
78
80
  # uncomment to use OIDC nonce
79
81
  # t.string :nonce
82
+ # t.datetime :auth_time
83
+ # when using :oauth_resource_indicators feature
84
+ # t.string :resource
80
85
  end
81
86
  end
82
87
  end
@@ -425,33 +425,46 @@ module Rodauth
425
425
  oauth_tokens_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_token_expires_in)
426
426
  }.merge(params)
427
427
 
428
+ if create_params[oauth_tokens_scopes_column].is_a?(Array)
429
+ create_params[oauth_tokens_scopes_column] =
430
+ create_params[oauth_tokens_scopes_column].join(" ")
431
+ end
432
+
428
433
  rescue_from_uniqueness_error do
429
- token = oauth_unique_id_generator
434
+ access_token = _generate_access_token(create_params)
435
+ refresh_token = _generate_refresh_token(create_params) if should_generate_refresh_token
436
+ oauth_token = _store_oauth_token(create_params)
437
+ oauth_token[oauth_tokens_token_column] = access_token
438
+ oauth_token[oauth_tokens_refresh_token_column] = refresh_token if refresh_token
439
+ oauth_token
440
+ end
441
+ end
430
442
 
431
- if oauth_tokens_token_hash_column
432
- create_params[oauth_tokens_token_hash_column] = generate_token_hash(token)
433
- else
434
- create_params[oauth_tokens_token_column] = token
435
- end
443
+ def _generate_access_token(params = {})
444
+ token = oauth_unique_id_generator
436
445
 
437
- refresh_token = nil
438
- if should_generate_refresh_token
439
- refresh_token = oauth_unique_id_generator
446
+ if oauth_tokens_token_hash_column
447
+ params[oauth_tokens_token_hash_column] = generate_token_hash(token)
448
+ else
449
+ params[oauth_tokens_token_column] = token
450
+ end
440
451
 
441
- if oauth_tokens_refresh_token_hash_column
442
- create_params[oauth_tokens_refresh_token_hash_column] = generate_token_hash(refresh_token)
443
- else
444
- create_params[oauth_tokens_refresh_token_column] = refresh_token
445
- end
446
- end
447
- oauth_token = _generate_oauth_token(create_params)
448
- oauth_token[oauth_tokens_token_column] = token
449
- oauth_token[oauth_tokens_refresh_token_column] = refresh_token if refresh_token
450
- oauth_token
452
+ token
453
+ end
454
+
455
+ def _generate_refresh_token(params)
456
+ token = oauth_unique_id_generator
457
+
458
+ if oauth_tokens_refresh_token_hash_column
459
+ params[oauth_tokens_refresh_token_hash_column] = generate_token_hash(token)
460
+ else
461
+ params[oauth_tokens_refresh_token_column] = token
451
462
  end
463
+
464
+ token
452
465
  end
453
466
 
454
- def _generate_oauth_token(params = {})
467
+ def _store_oauth_token(params = {})
455
468
  ds = db[oauth_tokens_table]
456
469
 
457
470
  if __one_oauth_token_per_account
@@ -576,44 +589,25 @@ module Rodauth
576
589
  redirect_response_error("invalid_grant") unless token_from_application?(oauth_token, oauth_application)
577
590
 
578
591
  rescue_from_uniqueness_error do
579
- oauth_tokens_ds = db[oauth_tokens_table]
580
- token = oauth_unique_id_generator
581
-
582
- if oauth_tokens_token_hash_column
583
- update_params[oauth_tokens_token_hash_column] = generate_token_hash(token)
592
+ oauth_tokens_ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
593
+ access_token = _generate_access_token(update_params)
594
+
595
+ if oauth_refresh_token_protection_policy == "rotation"
596
+ update_params = {
597
+ **update_params,
598
+ oauth_tokens_oauth_token_id_column => oauth_token[oauth_tokens_id_column],
599
+ oauth_tokens_account_id_column => oauth_token[oauth_tokens_account_id_column],
600
+ oauth_tokens_scopes_column => oauth_token[oauth_tokens_scopes_column]
601
+ }
602
+
603
+ refresh_token = _generate_refresh_token(update_params)
584
604
  else
585
- update_params[oauth_tokens_token_column] = token
605
+ refresh_token = param("refresh_token")
586
606
  end
607
+ oauth_token = __update_and_return__(oauth_tokens_ds, update_params)
587
608
 
588
- oauth_token = if oauth_refresh_token_protection_policy == "rotation"
589
- insert_params = {
590
- **update_params,
591
- oauth_tokens_oauth_token_id_column => oauth_token[oauth_tokens_id_column],
592
- oauth_tokens_scopes_column => oauth_token[oauth_tokens_scopes_column]
593
- }
594
-
595
- refresh_token = oauth_unique_id_generator
596
-
597
- if oauth_tokens_refresh_token_hash_column
598
- insert_params[oauth_tokens_refresh_token_hash_column] = generate_token_hash(refresh_token)
599
- else
600
- insert_params[oauth_tokens_refresh_token_column] = refresh_token
601
- end
602
-
603
- # revoke the refresh token
604
- oauth_tokens_ds.where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
605
- .update(oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
606
-
607
- insert_params[oauth_tokens_oauth_token_id_column] = oauth_token[oauth_tokens_id_column]
608
- __insert_and_return__(oauth_tokens_ds, oauth_tokens_id_column, insert_params)
609
- else
610
- # includes none
611
- ds = oauth_tokens_ds.where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
612
- __update_and_return__(ds, update_params)
613
- end
614
-
615
- oauth_token[oauth_tokens_token_column] = token
616
- oauth_token[oauth_tokens_refresh_token_column] = refresh_token if refresh_token
609
+ oauth_token[oauth_tokens_token_column] = access_token
610
+ oauth_token[oauth_tokens_refresh_token_column] = refresh_token
617
611
  oauth_token
618
612
  end
619
613
  end
@@ -44,10 +44,13 @@ module Rodauth
44
44
 
45
45
  auth_value_method :oauth_jwt_keys, {}
46
46
  auth_value_method :oauth_jwt_key, nil
47
+ auth_value_method :oauth_jwt_public_keys, {}
47
48
  auth_value_method :oauth_jwt_public_key, nil
48
49
  auth_value_method :oauth_jwt_algorithm, "RS256"
49
50
 
51
+ auth_value_method :oauth_jwt_jwe_keys, {}
50
52
  auth_value_method :oauth_jwt_jwe_key, nil
53
+ auth_value_method :oauth_jwt_jwe_public_keys, {}
51
54
  auth_value_method :oauth_jwt_jwe_public_key, nil
52
55
  auth_value_method :oauth_jwt_jwe_algorithm, nil
53
56
  auth_value_method :oauth_jwt_jwe_encryption_method, nil
@@ -66,7 +69,6 @@ module Rodauth
66
69
  :jwt_encode,
67
70
  :jwt_decode,
68
71
  :jwks_set,
69
- :last_account_login_at,
70
72
  :generate_jti
71
73
  )
72
74
 
@@ -99,12 +101,6 @@ module Rodauth
99
101
 
100
102
  private
101
103
 
102
- unless method_defined?(:last_account_login_at)
103
- def last_account_login_at
104
- nil
105
- end
106
- end
107
-
108
104
  def issuer
109
105
  @issuer ||= oauth_jwt_token_issuer || authorization_server_url
110
106
  end
@@ -175,41 +171,38 @@ module Rodauth
175
171
 
176
172
  # /token
177
173
 
178
- def generate_oauth_token(params = {}, should_generate_refresh_token = true)
179
- create_params = {
180
- oauth_grants_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_token_expires_in)
181
- }.merge(params)
182
-
183
- oauth_token = rescue_from_uniqueness_error do
184
- if should_generate_refresh_token
185
- refresh_token = oauth_unique_id_generator
186
-
187
- if oauth_tokens_refresh_token_hash_column
188
- create_params[oauth_tokens_refresh_token_hash_column] = generate_token_hash(refresh_token)
189
- else
190
- create_params[oauth_tokens_refresh_token_column] = refresh_token
191
- end
192
- end
174
+ def create_oauth_token_from_token(oauth_token, update_params)
175
+ otoken = super
176
+ access_token = _generate_jwt_access_token(otoken)
177
+ otoken[oauth_tokens_token_column] = access_token
178
+ otoken
179
+ end
193
180
 
194
- _generate_oauth_token(create_params)
195
- end
181
+ def generate_oauth_token(params = {}, should_generate_refresh_token = true)
182
+ oauth_token = super
183
+ access_token = _generate_jwt_access_token(oauth_token)
184
+ oauth_token[oauth_tokens_token_column] = access_token
185
+ oauth_token
186
+ end
196
187
 
188
+ def _generate_jwt_access_token(oauth_token)
197
189
  claims = jwt_claims(oauth_token)
198
190
 
199
191
  # one of the points of using jwt is avoiding database lookups, so we put here all relevant
200
192
  # token data.
201
193
  claims[:scope] = oauth_token[oauth_tokens_scopes_column]
202
194
 
203
- token = jwt_encode(claims)
195
+ jwt_encode(claims)
196
+ end
204
197
 
205
- oauth_token[oauth_tokens_token_column] = token
206
- oauth_token
198
+ def _generate_access_token(*)
199
+ # no op
207
200
  end
208
201
 
209
202
  def jwt_claims(oauth_token)
210
203
  issued_at = Time.now.to_i
211
204
 
212
- claims = {
205
+ {
213
206
  iss: issuer, # issuer
214
207
  iat: issued_at, # issued at
215
208
  #
@@ -227,10 +220,6 @@ module Rodauth
227
220
  exp: issued_at + oauth_token_expires_in,
228
221
  aud: (oauth_jwt_audience || oauth_application[oauth_applications_client_id_column])
229
222
  }
230
-
231
- claims[:auth_time] = last_account_login_at.to_i if last_account_login_at
232
-
233
- claims
234
223
  end
235
224
 
236
225
  def jwt_subject(oauth_token)
@@ -421,10 +410,11 @@ module Rodauth
421
410
 
422
411
  def jwt_encode(payload,
423
412
  jwks: nil,
424
- jwe_key: oauth_jwt_jwe_public_key || oauth_jwt_jwe_key,
425
- signing_algorithm: oauth_jwt_algorithm,
426
413
  encryption_algorithm: oauth_jwt_jwe_algorithm,
427
- encryption_method: oauth_jwt_jwe_encryption_method)
414
+ encryption_method: oauth_jwt_jwe_encryption_method,
415
+ jwe_key: oauth_jwt_jwe_keys[[encryption_algorithm,
416
+ encryption_method]] || oauth_jwt_jwe_public_key || oauth_jwt_jwe_key,
417
+ signing_algorithm: oauth_jwt_algorithm || oauth_jwt_keys.keys.first)
428
418
  payload[:jti] = generate_jti(payload)
429
419
  jwt = JSON::JWT.new(payload)
430
420
 
@@ -441,6 +431,7 @@ module Rodauth
441
431
  jwe = jwt.encrypt(jwk, encryption_algorithm.to_sym, encryption_method.to_sym)
442
432
  jwe.to_s
443
433
  elsif jwe_key
434
+ jwe_key = jwe_key.first if jwe_key.is_a?(Array)
444
435
  algorithm = encryption_algorithm.to_sym if encryption_algorithm
445
436
  meth = encryption_method.to_sym if encryption_method
446
437
  jwt.encrypt(jwe_key, algorithm, meth)
@@ -452,18 +443,23 @@ module Rodauth
452
443
  def jwt_decode(
453
444
  token,
454
445
  jwks: nil,
455
- jws_key: oauth_jwt_public_key || _jwt_key,
456
- jws_algorithm: oauth_jwt_algorithm,
457
- jwe_key: oauth_jwt_jwe_key,
446
+ jws_algorithm: oauth_jwt_algorithm || oauth_jwt_public_key.keys.first || oauth_jwt_keys.keys.first,
447
+ jws_key: oauth_jwt_public_key || oauth_jwt_keys[jws_algorithm] || _jwt_key,
458
448
  jws_encryption_algorithm: oauth_jwt_jwe_algorithm,
459
449
  jws_encryption_method: oauth_jwt_jwe_encryption_method,
450
+ jwe_key: oauth_jwt_jwe_keys[[jws_encryption_algorithm, jws_encryption_method]] || oauth_jwt_jwe_key,
460
451
  verify_claims: true,
461
452
  verify_jti: true,
462
453
  verify_iss: true,
463
454
  verify_aud: false,
464
455
  **
465
456
  )
466
- token = JSON::JWT.decode(token, oauth_jwt_jwe_key).plain_text if jwe_key
457
+ jws_key = jws_key.first if jws_key.is_a?(Array)
458
+
459
+ if jwe_key
460
+ jwe_key = jwe_key.first if jwe_key.is_a?(Array)
461
+ token = JSON::JWT.decode(token, jwe_key).plain_text
462
+ end
467
463
 
468
464
  claims = if is_authorization_server?
469
465
  if oauth_jwt_legacy_public_key
@@ -501,6 +497,21 @@ module Rodauth
501
497
 
502
498
  def jwks_set
503
499
  @jwks_set ||= [
500
+ *(
501
+ unless oauth_jwt_public_keys.empty?
502
+ oauth_jwt_public_keys.flat_map { |algo, pkeys| pkeys.map { |pkey| JSON::JWK.new(pkey).merge(use: "sig", alg: algo) } }
503
+ end
504
+ ),
505
+ *(
506
+ unless oauth_jwt_jwe_public_keys.empty?
507
+ oauth_jwt_jwe_public_keys.flat_map do |(algo, _enc), pkeys|
508
+ pkeys.map do |pkey|
509
+ JSON::JWK.new(pkey).merge(use: "enc", alg: algo)
510
+ end
511
+ end
512
+ end
513
+ ),
514
+ # legacy
504
515
  (JSON::JWK.new(oauth_jwt_public_key).merge(use: "sig", alg: oauth_jwt_algorithm) if oauth_jwt_public_key),
505
516
  (JSON::JWK.new(oauth_jwt_legacy_public_key).merge(use: "sig", alg: oauth_jwt_legacy_algorithm) if oauth_jwt_legacy_public_key),
506
517
  (JSON::JWK.new(oauth_jwt_jwe_public_key).merge(use: "enc", alg: oauth_jwt_jwe_algorithm) if oauth_jwt_jwe_public_key)
@@ -536,7 +547,8 @@ module Rodauth
536
547
  JWT::JWK.import(data).keypair
537
548
  end
538
549
 
539
- def jwt_encode(payload, signing_algorithm: oauth_jwt_algorithm)
550
+ def jwt_encode(payload,
551
+ signing_algorithm: oauth_jwt_algorithm || oauth_jwt_keys.keys.first)
540
552
  headers = {}
541
553
 
542
554
  key = oauth_jwt_keys[signing_algorithm] || _jwt_key
@@ -559,11 +571,11 @@ module Rodauth
559
571
  def jwt_encode_with_jwe(
560
572
  payload,
561
573
  jwks: nil,
562
- jwe_key: oauth_jwt_jwe_public_key || oauth_jwt_jwe_key,
563
574
  encryption_algorithm: oauth_jwt_jwe_algorithm,
564
- encryption_method: oauth_jwt_jwe_encryption_method, **args
575
+ encryption_method: oauth_jwt_jwe_encryption_method,
576
+ jwe_key: oauth_jwt_jwe_public_key || oauth_jwt_jwe_keys[[encryption_algorithm, encryption_method]] || oauth_jwt_jwe_key,
577
+ **args
565
578
  )
566
-
567
579
  token = jwt_encode_without_jwe(payload, **args)
568
580
 
569
581
  return token unless encryption_algorithm && encryption_method
@@ -571,6 +583,7 @@ module Rodauth
571
583
  if jwks && jwks.any? { |k| k[:use] == "enc" }
572
584
  JWE.__rodauth_oauth_encrypt_from_jwks(token, jwks, alg: encryption_algorithm, enc: encryption_method)
573
585
  elsif jwe_key
586
+ jwe_key = jwe_key.first if jwe_key.is_a?(Array)
574
587
  params = {
575
588
  zip: "DEF",
576
589
  copyright: oauth_jwt_jwe_copyright
@@ -590,13 +603,15 @@ module Rodauth
590
603
  def jwt_decode(
591
604
  token,
592
605
  jwks: nil,
593
- jws_key: oauth_jwt_public_key || _jwt_key,
594
- jws_algorithm: oauth_jwt_algorithm,
606
+ jws_algorithm: oauth_jwt_algorithm || oauth_jwt_public_key.keys.first || oauth_jwt_keys.keys.first,
607
+ jws_key: oauth_jwt_public_key || oauth_jwt_keys[jws_algorithm] || _jwt_key,
595
608
  verify_claims: true,
596
609
  verify_jti: true,
597
610
  verify_iss: true,
598
611
  verify_aud: false
599
612
  )
613
+ jws_key = jws_key.first if jws_key.is_a?(Array)
614
+
600
615
  # verifying the JWT implies verifying:
601
616
  #
602
617
  # issuer: check that server generated the token
@@ -645,15 +660,16 @@ module Rodauth
645
660
  def jwt_decode_with_jwe(
646
661
  token,
647
662
  jwks: nil,
648
- jwe_key: oauth_jwt_jwe_key,
649
663
  jws_encryption_algorithm: oauth_jwt_jwe_algorithm,
650
664
  jws_encryption_method: oauth_jwt_jwe_encryption_method,
665
+ jwe_key: oauth_jwt_jwe_keys[[jws_encryption_algorithm, jws_encryption_method]] || oauth_jwt_jwe_key,
651
666
  **args
652
667
  )
653
668
 
654
669
  token = if jwks && jwks.any? { |k| k[:use] == "enc" }
655
670
  JWE.__rodauth_oauth_decrypt_from_jwks(token, jwks, alg: jws_encryption_algorithm, enc: jws_encryption_method)
656
671
  elsif jwe_key
672
+ jwe_key = jwe_key.first if jwe_key.is_a?(Array)
657
673
  JWE.decrypt(token, jwe_key)
658
674
  else
659
675
  token
@@ -670,6 +686,21 @@ module Rodauth
670
686
 
671
687
  def jwks_set
672
688
  @jwks_set ||= [
689
+ *(
690
+ unless oauth_jwt_public_keys.empty?
691
+ oauth_jwt_public_keys.flat_map { |algo, pkeys| pkeys.map { |pkey| JWT::JWK.new(pkey).export.merge(use: "sig", alg: algo) } }
692
+ end
693
+ ),
694
+ *(
695
+ unless oauth_jwt_jwe_public_keys.empty?
696
+ oauth_jwt_jwe_public_keys.flat_map do |(algo, _enc), pkeys|
697
+ pkeys.map do |pkey|
698
+ JWT::JWK.new(pkey).export.merge(use: "enc", alg: algo)
699
+ end
700
+ end
701
+ end
702
+ ),
703
+ # legacy
673
704
  (JWT::JWK.new(oauth_jwt_public_key).export.merge(use: "sig", alg: oauth_jwt_algorithm) if oauth_jwt_public_key),
674
705
  (
675
706
  if oauth_jwt_legacy_public_key
@@ -0,0 +1,153 @@
1
+ # frozen-string-literal: true
2
+
3
+ require "rodauth/oauth/version"
4
+ require "rodauth/oauth/ttl_store"
5
+
6
+ module Rodauth
7
+ Feature.define(:oauth_resource_indicators, :OauthResourceIndicators) do
8
+ depends :oauth_base
9
+
10
+ auth_value_method :oauth_grants_resource_column, :resource
11
+ auth_value_method :oauth_tokens_resource_column, :resource
12
+
13
+ def resource_indicators
14
+ return @resource_indicators if defined?(@resource_indicators)
15
+
16
+ resources = param_or_nil("resource")
17
+
18
+ return unless resources
19
+
20
+ if json_request? || param_or_nil("request") # signed request
21
+ resources = Array(resources)
22
+ else
23
+ query = request.form_data? ? request.body.read : request.query_string
24
+ # resource query param does not conform to rack parsing rules
25
+ resources = URI.decode_www_form(query).each_with_object([]) do |(k, v), memo|
26
+ memo << v if k == "resource"
27
+ end
28
+ end
29
+
30
+ @resource_indicators = resources
31
+ end
32
+
33
+ def require_oauth_authorization(*)
34
+ super
35
+
36
+ return unless authorization_token[oauth_tokens_resource_column]
37
+
38
+ token_indicators = authorization_token[oauth_tokens_resource_column]
39
+
40
+ token_indicators = token_indicators.split(" ") if token_indicators.is_a?(String)
41
+
42
+ authorization_required unless token_indicators.any? { |resource| base_url.start_with?(resource) }
43
+ end
44
+
45
+ private
46
+
47
+ def validate_oauth_token_params
48
+ super
49
+
50
+ return unless resource_indicators
51
+
52
+ resource_indicators.each do |resource|
53
+ redirect_response_error("invalid_target") unless check_valid_no_fragment_uri?(resource)
54
+ end
55
+ end
56
+
57
+ def create_oauth_token_from_token(oauth_token, update_params)
58
+ return super unless resource_indicators
59
+
60
+ return super unless oauth_token[oauth_tokens_oauth_grant_id_column]
61
+
62
+ oauth_grant = db[oauth_grants_table].where(
63
+ oauth_grants_id_column => oauth_token[oauth_tokens_oauth_grant_id_column],
64
+ oauth_grants_revoked_at_column => nil
65
+ ).first
66
+
67
+ grant_indicators = oauth_grant[oauth_grants_resource_column]
68
+
69
+ grant_indicators = grant_indicators.split(" ") if grant_indicators.is_a?(String)
70
+
71
+ redirect_response_error("invalid_target") unless (grant_indicators - resource_indicators) != grant_indicators
72
+
73
+ super(oauth_token, update_params.merge(oauth_tokens_resource_column => resource_indicators))
74
+ end
75
+
76
+ def check_valid_no_fragment_uri?(uri)
77
+ check_valid_uri?(uri) && URI.parse(uri).fragment.nil?
78
+ end
79
+
80
+ module IndicatorAuthorizationCodeGrant
81
+ private
82
+
83
+ def validate_oauth_grant_params
84
+ super
85
+
86
+ return unless resource_indicators
87
+
88
+ resource_indicators.each do |resource|
89
+ redirect_response_error("invalid_target") unless check_valid_no_fragment_uri?(resource)
90
+ end
91
+ end
92
+
93
+ def create_oauth_token_from_authorization_code(oauth_grant, create_params)
94
+ return super unless resource_indicators
95
+
96
+ redirect_response_error("invalid_target") unless oauth_grant[oauth_grants_resource_column]
97
+
98
+ grant_indicators = oauth_grant[oauth_grants_resource_column]
99
+
100
+ grant_indicators = grant_indicators.split(" ") if grant_indicators.is_a?(String)
101
+
102
+ redirect_response_error("invalid_target") unless (grant_indicators - resource_indicators) != grant_indicators
103
+
104
+ super(oauth_grant, create_params.merge(oauth_tokens_resource_column => resource_indicators))
105
+ end
106
+
107
+ def create_oauth_grant(create_params = {})
108
+ create_params[oauth_grants_resource_column] = resource_indicators.join(" ") if resource_indicators
109
+
110
+ super
111
+ end
112
+ end
113
+
114
+ module IndicatorIntrospection
115
+ def json_token_introspect_payload(token)
116
+ return super unless token[oauth_tokens_oauth_grant_id_column]
117
+
118
+ payload = super
119
+
120
+ token_indicators = token[oauth_tokens_resource_column]
121
+
122
+ token_indicators = token_indicators.split(" ") if token_indicators.is_a?(String)
123
+
124
+ payload[:aud] = token_indicators
125
+
126
+ payload
127
+ end
128
+
129
+ def introspection_request(*)
130
+ payload = super
131
+
132
+ payload[oauth_tokens_resource_column] = payload["aud"] if payload["aud"]
133
+
134
+ payload
135
+ end
136
+ end
137
+
138
+ module IndicatorJwt
139
+ def jwt_claims(*)
140
+ return super unless resource_indicators
141
+
142
+ super.merge(aud: resource_indicators)
143
+ end
144
+ end
145
+
146
+ def self.included(rodauth)
147
+ super
148
+ rodauth.send(:include, IndicatorAuthorizationCodeGrant) if rodauth.features.include?(:oauth_authorization_code_grant)
149
+ rodauth.send(:include, IndicatorIntrospection) if rodauth.features.include?(:oauth_token_introspection)
150
+ rodauth.send(:include, IndicatorJwt) if rodauth.features.include?(:oauth_jwt)
151
+ end
152
+ end
153
+ end
@@ -60,7 +60,7 @@ module Rodauth
60
60
  id_token_signing_alg_values_supported
61
61
  ].freeze
62
62
 
63
- depends :oauth_jwt
63
+ depends :account_expiration, :oauth_jwt
64
64
 
65
65
  auth_value_method :oauth_application_default_scope, "openid"
66
66
  auth_value_method :oauth_application_scopes, %w[openid]
@@ -73,7 +73,9 @@ module Rodauth
73
73
  auth_value_method :oauth_applications_userinfo_encrypted_response_enc_column, :userinfo_encrypted_response_enc
74
74
 
75
75
  auth_value_method :oauth_grants_nonce_column, :nonce
76
+ auth_value_method :oauth_grants_acr_column, :acr
76
77
  auth_value_method :oauth_tokens_nonce_column, :nonce
78
+ auth_value_method :oauth_tokens_acr_column, :acr
77
79
 
78
80
  translatable_method :invalid_scope_message, "The Access Token expired"
79
81
 
@@ -87,7 +89,13 @@ module Rodauth
87
89
  auth_value_method :oauth_applications_post_logout_redirect_uri_column, :post_logout_redirect_uri
88
90
  auth_value_method :use_rp_initiated_logout?, false
89
91
 
90
- auth_value_methods(:get_oidc_param, :get_additional_param)
92
+ auth_value_methods(
93
+ :get_oidc_param,
94
+ :get_additional_param,
95
+ :require_acr_value_phr,
96
+ :require_acr_value_phrh,
97
+ :require_acr_value
98
+ )
91
99
 
92
100
  # /userinfo
93
101
  route(:userinfo) do |r|
@@ -251,14 +259,43 @@ module Rodauth
251
259
 
252
260
  private
253
261
 
262
+ if defined?(::I18n)
263
+ def before_authorize_route
264
+ if (ui_locales = param_or_nil("ui_locales"))
265
+ ui_locales = ui_locales.split(" ").map(&:to_sym)
266
+ ui_locales &= ::I18n.available_locales
267
+
268
+ ::I18n.locale = ui_locales.first unless ui_locales.empty?
269
+ end
270
+
271
+ super
272
+ end
273
+ end
274
+
275
+ def validate_oauth_grant_params
276
+ return super unless (max_age = param_or_nil("max_age"))
277
+
278
+ max_age = Integer(max_age)
279
+
280
+ redirect_response_error("invalid_request") unless max_age.positive?
281
+
282
+ return unless Time.now - last_account_login_at > max_age
283
+
284
+ # force user to re-login
285
+ clear_session
286
+ set_session_value(login_redirect_session_key, request.fullpath)
287
+ redirect require_login_redirect
288
+ end
289
+
254
290
  def require_authorizable_account
255
- try_prompt if param_or_nil("prompt")
291
+ try_prompt
256
292
  super
293
+ try_acr_values
257
294
  end
258
295
 
259
296
  # this executes before checking for a logged in account
260
297
  def try_prompt
261
- prompt = param_or_nil("prompt")
298
+ return unless (prompt = param_or_nil("prompt"))
262
299
 
263
300
  case prompt
264
301
  when "none"
@@ -313,16 +350,46 @@ module Rodauth
313
350
  end
314
351
  end
315
352
 
316
- def create_oauth_grant(create_params = {})
317
- return super unless (nonce = param_or_nil("nonce"))
353
+ def try_acr_values
354
+ return unless (acr_values = param_or_nil("acr_values"))
355
+
356
+ acr_values.split(" ").each do |acr_value|
357
+ case acr_value
358
+ when "phr" then require_acr_value_phr
359
+ when "phrh" then require_acr_value_phrh
360
+ else
361
+ require_acr_value(acr_value)
362
+ end
363
+ end
364
+ end
318
365
 
319
- super(create_params.merge(oauth_grants_nonce_column => nonce))
366
+ def require_acr_value_phr
367
+ return unless respond_to?(:require_two_factor_authenticated)
368
+
369
+ require_two_factor_authenticated
370
+ end
371
+
372
+ def require_acr_value_phrh
373
+ require_acr_value_phr && two_factor_login_type_match?("webauthn")
374
+ end
375
+
376
+ def require_acr_value(_acr); end
377
+
378
+ def create_oauth_grant(create_params = {})
379
+ if (nonce = param_or_nil("nonce"))
380
+ create_params[oauth_grants_nonce_column] = nonce
381
+ end
382
+ if (acr = param_or_nil("acr"))
383
+ create_params[oauth_grants_acr_column] = acr
384
+ end
385
+ super
320
386
  end
321
387
 
322
388
  def create_oauth_token_from_authorization_code(oauth_grant, create_params)
323
- return super unless oauth_grant[oauth_grants_nonce_column]
389
+ create_params[oauth_tokens_nonce_column] = oauth_grant[oauth_grants_nonce_column] if oauth_grant[oauth_grants_nonce_column]
390
+ create_params[oauth_tokens_acr_column] = oauth_grant[oauth_grants_acr_column] if oauth_grant[oauth_grants_acr_column]
324
391
 
325
- super(oauth_grant, create_params.merge(oauth_tokens_nonce_column => oauth_grant[oauth_grants_nonce_column]))
392
+ super
326
393
  end
327
394
 
328
395
  def create_oauth_token(*)
@@ -337,12 +404,13 @@ module Rodauth
337
404
  return unless oauth_scopes.include?("openid")
338
405
 
339
406
  id_token_claims = jwt_claims(oauth_token)
407
+
340
408
  id_token_claims[:nonce] = oauth_token[oauth_tokens_nonce_column] if oauth_token[oauth_tokens_nonce_column]
341
409
 
410
+ id_token_claims[:acr] = oauth_token[oauth_tokens_acr_column] if oauth_token[oauth_tokens_acr_column]
411
+
342
412
  # Time when the End-User authentication occurred.
343
- #
344
- # Sounds like the same as issued at claim.
345
- id_token_claims[:auth_time] = id_token_claims[:iat]
413
+ id_token_claims[:auth_time] = last_account_login_at.to_i
346
414
 
347
415
  account = db[accounts_table].where(account_id_column => oauth_token[oauth_tokens_account_id_column]).first
348
416
 
@@ -377,16 +445,23 @@ module Rodauth
377
445
 
378
446
  oidc_scopes, additional_scopes = scopes_by_claim.keys.partition { |key| OIDC_SCOPES_MAP.key?(key) }
379
447
 
448
+ if (claims_locales = param_or_nil("claims_locales"))
449
+ claims_locales = claims_locales.split(" ").map(&:to_sym)
450
+ end
451
+
380
452
  unless oidc_scopes.empty?
381
453
  if respond_to?(:get_oidc_param)
454
+ get_oidc_param = proxy_get_param(:get_oidc_param, claims, claims_locales)
455
+
382
456
  oidc_scopes.each do |scope|
383
457
  scope_claims = claims
384
458
  params = scopes_by_claim[scope]
385
459
  params = params.empty? ? OIDC_SCOPES_MAP[scope] : (OIDC_SCOPES_MAP[scope] & params)
386
460
 
387
461
  scope_claims = (claims["address"] = {}) if scope == "address"
462
+
388
463
  params.each do |param|
389
- scope_claims[param] = __send__(:get_oidc_param, account, param)
464
+ get_oidc_param[account, param, scope_claims]
390
465
  end
391
466
  end
392
467
  else
@@ -397,14 +472,39 @@ module Rodauth
397
472
  return if additional_scopes.empty?
398
473
 
399
474
  if respond_to?(:get_additional_param)
475
+ get_additional_param = proxy_get_param(:get_additional_param, claims, claims_locales)
476
+
400
477
  additional_scopes.each do |scope|
401
- claims[scope] = __send__(:get_additional_param, account, scope.to_sym)
478
+ get_additional_param[account, scope.to_sym]
402
479
  end
403
480
  else
404
481
  warn "`get_additional_param(account, claim)` must be implemented to use oidc scopes."
405
482
  end
406
483
  end
407
484
 
485
+ def proxy_get_param(get_param_func, claims, claims_locales)
486
+ meth = method(get_param_func)
487
+ if meth.arity == 2
488
+ ->(account, param, cl = claims) { cl[param] = meth[account, param] }
489
+ elsif claims_locales.nil?
490
+ ->(account, param, cl = claims) { cl[param] = meth[account, param, nil] }
491
+ else
492
+ lambda do |account, param, cl = claims|
493
+ claims_values = claims_locales.map do |locale|
494
+ meth[account, param, locale]
495
+ end
496
+
497
+ if claims_values.uniq.size == 1
498
+ cl[param] = claims_values.first
499
+ else
500
+ claims_locales.zip(claims_values).each do |locale, value|
501
+ cl["#{param}##{locale}"] = value
502
+ end
503
+ end
504
+ end
505
+ end
506
+ end
507
+
408
508
  def json_access_token_payload(oauth_token)
409
509
  payload = super
410
510
  payload["id_token"] = oauth_token[:id_token] if oauth_token[:id_token]
@@ -452,6 +552,12 @@ module Rodauth
452
552
  oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
453
553
  oauth_tokens_scopes_column => scopes
454
554
  }
555
+ if (nonce = param_or_nil("nonce"))
556
+ create_params[oauth_grants_nonce_column] = nonce
557
+ end
558
+ if (acr = param_or_nil("acr"))
559
+ create_params[oauth_grants_acr_column] = acr
560
+ end
455
561
  oauth_token = generate_oauth_token(create_params, false)
456
562
  generate_id_token(oauth_token)
457
563
  params = json_access_token_payload(oauth_token)
@@ -488,7 +594,7 @@ module Rodauth
488
594
  end
489
595
  end
490
596
 
491
- scope_claims.unshift("auth_time") if last_account_login_at
597
+ scope_claims.unshift("auth_time")
492
598
 
493
599
  response_types_supported = metadata[:response_types_supported]
494
600
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rodauth
4
4
  module OAuth
5
- VERSION = "0.9.2"
5
+ VERSION = "0.10.1"
6
6
  end
7
7
  end
data/locales/en.yml CHANGED
@@ -7,7 +7,7 @@ en:
7
7
  revoke_oauth_token_notice_flash: "The oauth token has been revoked"
8
8
  device_verification_notice_flash: "The device is verified"
9
9
  user_code_not_found_error_flash: "No device to authorize with the given user code"
10
- oauth_authorize_title: "Authorize"
10
+ authorize_page_title: "Authorize"
11
11
  oauth_applications_page_title: "Oauth Applications"
12
12
  oauth_application_page_title: "Oauth Application"
13
13
  new_oauth_application_page_title: "New Oauth Application"
@@ -17,6 +17,7 @@ en:
17
17
  device_search_page_title: "Device Search"
18
18
  oauth_management_pagination_previous_button: "Previous"
19
19
  oauth_management_pagination_next_button: "Next"
20
+ oauth_tokens_scopes_label: "Scopes"
20
21
  oauth_applications_name_label: "Name"
21
22
  oauth_applications_description_label: "Description"
22
23
  oauth_applications_scopes_label: "Default scopes"
data/locales/pt.yml ADDED
@@ -0,0 +1,57 @@
1
+ pt:
2
+ rodauth:
3
+ require_authorization_error_flash: "Autorize para continuar"
4
+ create_oauth_application_error_flash: "Aconteceu um erro ao registar o aplicativo oauth"
5
+ create_oauth_application_notice_flash: "O seu aplicativo oauth foi registado com sucesso"
6
+ revoke_unauthorized_account_error_flash: "Não está autorizado a revogar este token"
7
+ revoke_oauth_token_notice_flash: "O token oauth foi revogado com sucesso"
8
+ device_verification_notice_flash: "O dispositivo foi verificado com sucesso"
9
+ user_code_not_found_error_flash: "Não existe nenhum dispositivo a ser autorizado com o código de usuário inserido"
10
+ authorize_page_title: "Autorizar"
11
+ oauth_applications_page_title: "Aplicativos OAuth"
12
+ oauth_application_page_title: "Aplicativo Oauth"
13
+ new_oauth_application_page_title: "Novo Aplicativo Oauth"
14
+ oauth_application_oauth_tokens_page_title: "Tokens Oauth do Aplicativo"
15
+ oauth_tokens_page_title: "Os meus Tokens Oauth"
16
+ device_verification_page_title: "Verificação de dispositivo"
17
+ device_search_page_title: "Pesquisa de dispositivo"
18
+ oauth_management_pagination_previous_button: "Anterior"
19
+ oauth_management_pagination_next_button: "Próxima"
20
+ oauth_tokens_scopes_label: "Escopos"
21
+ oauth_applications_name_label: "Nome"
22
+ oauth_applications_description_label: "Descrição"
23
+ oauth_applications_scopes_label: "Escopos prédefinidos"
24
+ oauth_applications_contacts_label: "Contactos"
25
+ oauth_applications_homepage_url_label: "URL da página principal"
26
+ oauth_applications_tos_uri_label: "URL dos termos de serviço"
27
+ oauth_applications_policy_uri_label: "URL das diretrizes"
28
+ oauth_applications_redirect_uri_label: "URL para redireccionamento"
29
+ oauth_applications_client_secret_label: "Segredo de cliente"
30
+ oauth_applications_client_id_label: "ID do cliente"
31
+ oauth_grant_user_code_label: "Código do usuário"
32
+ oauth_grant_user_jws_jwk_label: "Chaves JSON Web"
33
+ oauth_grant_user_jwt_public_key_label: "Chave pública"
34
+ oauth_application_button: "Registar"
35
+ oauth_authorize_button: "Autorizar"
36
+ oauth_token_revoke_button: "Revogar"
37
+ oauth_authorize_post_button: "Voltar para o aplicativo cliente"
38
+ oauth_device_verification_button: "Verificar"
39
+ oauth_device_search_button: "Pesquisar"
40
+ invalid_client_message: "A autenticação do cliente falhou"
41
+ invalid_grant_type_message: "Tipo de atribuição inválida"
42
+ invalid_grant_message: "Atribuição inválida"
43
+ invalid_scope_message: "Escopo inválido"
44
+ invalid_url_message: "URL inválido"
45
+ unsupported_token_type_message: "Sugestão de tipo de token inválida"
46
+ unique_error_message: "já está sendo utilizado"
47
+ null_error_message: "não está preenchido"
48
+ already_in_use_message: "erro ao gerar token único"
49
+ expired_token_message: "o código de dispositivo expirou"
50
+ access_denied_message: "o pedido de autorização foi negado"
51
+ authorization_pending_message: "o pedido de autorização ainda está pendente"
52
+ slow_down_message: "o pedido de autorização ainda está pendente mas o intervalo de actualização deve ser aumentado"
53
+ code_challenge_required_message: "código de negociação necessário"
54
+ unsupported_transform_algorithm_message: "algoritmo de transformação não suportado"
55
+ request_uri_not_supported_message: "request_uri não é suportado"
56
+ invalid_request_object_message: "request_object é inválido"
57
+ invalid_scope_message: "O Token de acesso expirou"
@@ -81,10 +81,20 @@
81
81
  #{"<input type=\"hidden\" name=\"response_type\" value=\"#{rodauth.param("response_type")}\"/>" if rodauth.param_or_nil("response_type")}
82
82
  #{"<input type=\"hidden\" name=\"response_mode\" value=\"#{rodauth.param("response_mode")}\"/>" if rodauth.param_or_nil("response_mode")}
83
83
  #{"<input type=\"hidden\" name=\"state\" value=\"#{rodauth.param("state")}\"/>" if rodauth.param_or_nil("state")}
84
- #{"<input type=\"hidden\" name=\"nonce\" value=\"#{rodauth.param("nonce")}\"/>" if rodauth.param_or_nil("nonce")}
85
84
  #{"<input type=\"hidden\" name=\"redirect_uri\" value=\"#{rodauth.redirect_uri}\"/>" if rodauth.param_or_nil("redirect_uri")}
86
- #{"<input type=\"hidden\" name=\"code_challenge\" value=\"#{rodauth.param("code_challenge")}\"/>" if rodauth.param_or_nil("code_challenge")}
87
- #{"<input type=\"hidden\" name=\"code_challenge_method\" value=\"#{rodauth.param("code_challenge_method")}\"/>" if rodauth.param_or_nil("code_challenge_method")}
85
+ #{"<input type=\"hidden\" name=\"code_challenge\" value=\"#{rodauth.param("code_challenge")}\"/>" if rodauth.features.include?(:oauth_pkce) && rodauth.param_or_nil("code_challenge")}
86
+ #{"<input type=\"hidden\" name=\"code_challenge_method\" value=\"#{rodauth.param("code_challenge_method")}\"/>" if rodauth.features.include?(:oauth_pkce) && rodauth.param_or_nil("code_challenge_method")}
87
+ #{"<input type=\"hidden\" name=\"nonce\" value=\"#{rodauth.param("nonce")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("nonce")}
88
+ #{"<input type=\"hidden\" name=\"ui_locales\" value=\"#{rodauth.param("ui_locales")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("ui_locales")}
89
+ #{"<input type=\"hidden\" name=\"claims_locales\" value=\"#{rodauth.param("claims_locales")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("claims_locales")}
90
+ #{"<input type=\"hidden\" name=\"acr\" value=\"#{rodauth.param("acr_values")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("acr_values")}
91
+ #{
92
+ if rodauth.features.include?(:oauth_resource_indicators) && rodauth.resource_indicators
93
+ rodauth.resource_indicators.map do |resource|
94
+ "<input type=\"hidden\" name=\"resource\" value=\"#{resource}\"/>"
95
+ end.join
96
+ end
97
+ }
88
98
  </div>
89
99
  <p class="text-center">
90
100
  <input type="submit" class="btn btn-outline-primary" value="#{h(rodauth.oauth_authorize_button)}"/>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rodauth-oauth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-11 00:00:00.000000000 Z
11
+ date: 2022-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rodauth
@@ -39,6 +39,8 @@ extra_rdoc_files:
39
39
  - doc/release_notes/0_0_4.md
40
40
  - doc/release_notes/0_0_5.md
41
41
  - doc/release_notes/0_0_6.md
42
+ - doc/release_notes/0_10_0.md
43
+ - doc/release_notes/0_10_1.md
42
44
  - doc/release_notes/0_1_0.md
43
45
  - doc/release_notes/0_2_0.md
44
46
  - doc/release_notes/0_3_0.md
@@ -59,6 +61,7 @@ extra_rdoc_files:
59
61
  - doc/release_notes/0_9_0.md
60
62
  - doc/release_notes/0_9_1.md
61
63
  - doc/release_notes/0_9_2.md
64
+ - doc/release_notes/0_9_3.md
62
65
  files:
63
66
  - CHANGELOG.md
64
67
  - LICENSE.txt
@@ -69,6 +72,8 @@ files:
69
72
  - doc/release_notes/0_0_4.md
70
73
  - doc/release_notes/0_0_5.md
71
74
  - doc/release_notes/0_0_6.md
75
+ - doc/release_notes/0_10_0.md
76
+ - doc/release_notes/0_10_1.md
72
77
  - doc/release_notes/0_1_0.md
73
78
  - doc/release_notes/0_2_0.md
74
79
  - doc/release_notes/0_3_0.md
@@ -89,6 +94,7 @@ files:
89
94
  - doc/release_notes/0_9_0.md
90
95
  - doc/release_notes/0_9_1.md
91
96
  - doc/release_notes/0_9_2.md
97
+ - doc/release_notes/0_9_3.md
92
98
  - lib/generators/rodauth/oauth/install_generator.rb
93
99
  - lib/generators/rodauth/oauth/templates/app/models/oauth_application.rb
94
100
  - lib/generators/rodauth/oauth/templates/app/models/oauth_grant.rb
@@ -118,6 +124,7 @@ files:
118
124
  - lib/rodauth/features/oauth_jwt_bearer_grant.rb
119
125
  - lib/rodauth/features/oauth_management_base.rb
120
126
  - lib/rodauth/features/oauth_pkce.rb
127
+ - lib/rodauth/features/oauth_resource_indicators.rb
121
128
  - lib/rodauth/features/oauth_resource_server.rb
122
129
  - lib/rodauth/features/oauth_saml_bearer_grant.rb
123
130
  - lib/rodauth/features/oauth_token_introspection.rb
@@ -133,6 +140,7 @@ files:
133
140
  - lib/rodauth/oauth/ttl_store.rb
134
141
  - lib/rodauth/oauth/version.rb
135
142
  - locales/en.yml
143
+ - locales/pt.yml
136
144
  - templates/authorize.str
137
145
  - templates/client_secret_field.str
138
146
  - templates/description_field.str