rodauth-oauth 0.10.4 → 1.0.0.pre.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/MIGRATION-GUIDE-v1.md +286 -0
  3. data/README.md +28 -35
  4. data/doc/release_notes/1_0_0_beta1.md +38 -0
  5. data/doc/release_notes/1_0_0_beta2.md +34 -0
  6. data/lib/generators/rodauth/oauth/install_generator.rb +0 -1
  7. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +21 -11
  8. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +1 -1
  9. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +2 -2
  10. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +1 -6
  11. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +0 -2
  12. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_grants.html.erb +41 -0
  13. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +2 -2
  14. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_grants.html.erb +37 -0
  15. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +57 -57
  16. data/lib/rodauth/features/oauth_application_management.rb +61 -74
  17. data/lib/rodauth/features/oauth_assertion_base.rb +19 -23
  18. data/lib/rodauth/features/oauth_authorization_code_grant.rb +62 -90
  19. data/lib/rodauth/features/oauth_authorize_base.rb +115 -22
  20. data/lib/rodauth/features/oauth_base.rb +397 -315
  21. data/lib/rodauth/features/oauth_client_credentials_grant.rb +20 -18
  22. data/lib/rodauth/features/{oauth_device_grant.rb → oauth_device_code_grant.rb} +62 -73
  23. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +52 -31
  24. data/lib/rodauth/features/oauth_grant_management.rb +70 -0
  25. data/lib/rodauth/features/oauth_implicit_grant.rb +29 -27
  26. data/lib/rodauth/features/oauth_jwt.rb +53 -689
  27. data/lib/rodauth/features/oauth_jwt_base.rb +458 -0
  28. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +48 -17
  29. data/lib/rodauth/features/oauth_jwt_jwks.rb +47 -0
  30. data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +116 -0
  31. data/lib/rodauth/features/oauth_management_base.rb +2 -0
  32. data/lib/rodauth/features/oauth_pkce.rb +22 -26
  33. data/lib/rodauth/features/oauth_resource_indicators.rb +33 -25
  34. data/lib/rodauth/features/oauth_resource_server.rb +59 -0
  35. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +7 -1
  36. data/lib/rodauth/features/oauth_token_introspection.rb +76 -46
  37. data/lib/rodauth/features/oauth_token_revocation.rb +46 -33
  38. data/lib/rodauth/features/oidc.rb +382 -241
  39. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +127 -51
  40. data/lib/rodauth/features/oidc_rp_initiated_logout.rb +115 -0
  41. data/lib/rodauth/oauth/database_extensions.rb +8 -6
  42. data/lib/rodauth/oauth/http_extensions.rb +74 -0
  43. data/lib/rodauth/oauth/railtie.rb +20 -0
  44. data/lib/rodauth/oauth/ttl_store.rb +2 -0
  45. data/lib/rodauth/oauth/version.rb +1 -1
  46. data/lib/rodauth/oauth.rb +29 -1
  47. data/locales/en.yml +34 -22
  48. data/locales/pt.yml +34 -22
  49. data/templates/authorize.str +19 -17
  50. data/templates/device_search.str +1 -1
  51. data/templates/device_verification.str +2 -2
  52. data/templates/jwks_field.str +1 -0
  53. data/templates/new_oauth_application.str +1 -2
  54. data/templates/oauth_application.str +2 -2
  55. data/templates/oauth_application_oauth_grants.str +54 -0
  56. data/templates/oauth_applications.str +2 -2
  57. data/templates/oauth_grants.str +52 -0
  58. metadata +23 -16
  59. data/lib/generators/rodauth/oauth/templates/app/models/oauth_token.rb +0 -4
  60. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +0 -39
  61. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +0 -35
  62. data/lib/rodauth/features/oauth.rb +0 -9
  63. data/lib/rodauth/features/oauth_http_mac.rb +0 -86
  64. data/lib/rodauth/features/oauth_token_management.rb +0 -81
  65. data/lib/rodauth/oauth/refinements.rb +0 -48
  66. data/templates/jwt_public_key_field.str +0 -4
  67. data/templates/oauth_application_oauth_tokens.str +0 -52
  68. data/templates/oauth_tokens.str +0 -50
@@ -1,33 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rodauth/oauth"
4
+
3
5
  module Rodauth
4
6
  Feature.define(:oauth_client_credentials_grant, :OauthClientCredentialsGrant) do
5
7
  depends :oauth_base
6
8
 
7
- auth_value_method :use_oauth_client_credentials_grant_type?, false
9
+ def oauth_grant_types_supported
10
+ super | %w[client_credentials]
11
+ end
8
12
 
9
13
  private
10
14
 
11
- def create_oauth_token(grant_type)
12
- return super unless grant_type == "client_credentials" && use_oauth_client_credentials_grant_type?
15
+ def create_token(grant_type)
16
+ return super unless supported_grant_type?(grant_type, "client_credentials")
13
17
 
14
- create_params = {
15
- oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
16
- oauth_tokens_scopes_column => scopes.join(oauth_scope_separator)
17
- }
18
- generate_oauth_token(create_params, false)
19
- end
18
+ grant_scopes = scopes
20
19
 
21
- def oauth_server_metadata_body(*)
22
- super.tap do |data|
23
- data[:grant_types_supported] << "client_credentials" if use_oauth_client_credentials_grant_type?
24
- end
25
- end
26
-
27
- def check_valid_response_type?
28
- return true if use_oauth_implicit_grant_type? && param_or_nil("response_type") == "token"
20
+ grant_scopes = if grant_scopes
21
+ redirect_response_error("invalid_scope") unless check_valid_scopes?
22
+ grant_scopes.join(oauth_scope_separator)
23
+ else
24
+ oauth_application[oauth_applications_scopes_column]
25
+ end
29
26
 
30
- super
27
+ grant_params = {
28
+ oauth_grants_type_column => "client_credentials",
29
+ oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
30
+ oauth_grants_scopes_column => grant_scopes
31
+ }
32
+ generate_token(grant_params, false)
31
33
  end
32
34
  end
33
35
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rodauth/oauth"
4
+
3
5
  module Rodauth
4
- Feature.define(:oauth_device_grant, :OauthDeviceGrant) do
6
+ Feature.define(:oauth_device_code_grant, :OauthDeviceCodeGrant) do
5
7
  depends :oauth_authorize_base
6
8
 
7
- auth_value_method :use_oauth_device_code_grant_type?, false
8
-
9
9
  before "device_authorization"
10
10
  before "device_verification"
11
11
 
@@ -21,10 +21,12 @@ module Rodauth
21
21
  auth_value_method :oauth_grants_user_code_column, :user_code
22
22
  auth_value_method :oauth_grants_last_polled_at_column, :last_polled_at
23
23
 
24
- translatable_method :expired_token_message, "the device code has expired"
25
- translatable_method :access_denied_message, "the authorization request has been denied"
26
- translatable_method :authorization_pending_message, "the authorization request is still pending"
27
- translatable_method :slow_down_message, "authorization request is still pending but poll interval should be increased"
24
+ translatable_method :oauth_device_search_page_lead, "Insert the user code from the device you'd like to authorize."
25
+ translatable_method :oauth_device_verification_page_lead, "The device with user code %<user_code>s would like to access your data."
26
+ translatable_method :oauth_expired_token_message, "the device code has expired"
27
+ translatable_method :oauth_access_denied_message, "the authorization request has been denied"
28
+ translatable_method :oauth_authorization_pending_message, "the authorization request is still pending"
29
+ translatable_method :oauth_slow_down_message, "authorization request is still pending but poll interval should be increased"
28
30
 
29
31
  auth_value_method :oauth_device_code_grant_polling_interval, 5 # seconds
30
32
  auth_value_method :oauth_device_code_grant_user_code_size, 8 # characters
@@ -38,18 +40,18 @@ module Rodauth
38
40
  )
39
41
 
40
42
  # /device-authorization
41
- route(:device_authorization) do |r|
42
- next unless is_authorization_server? && use_oauth_device_code_grant_type?
43
-
43
+ auth_server_route(:device_authorization) do |r|
44
+ require_oauth_application
44
45
  before_device_authorization_route
45
46
 
46
47
  r.post do
47
- require_oauth_application
48
-
49
48
  user_code = generate_user_code
50
49
  device_code = transaction do
51
50
  before_device_authorization
52
- create_oauth_grant(oauth_grants_user_code_column => user_code)
51
+ create_oauth_grant(
52
+ oauth_grants_type_column => "device_code",
53
+ oauth_grants_user_code_column => user_code
54
+ )
53
55
  end
54
56
 
55
57
  json_response_success \
@@ -63,18 +65,13 @@ module Rodauth
63
65
  end
64
66
 
65
67
  # /device
66
- route(:device) do |r|
67
- next unless is_authorization_server? && use_oauth_device_code_grant_type?
68
-
69
- before_device_route
68
+ auth_server_route(:device) do |r|
70
69
  require_authorizable_account
70
+ before_device_route
71
71
 
72
72
  r.get do
73
73
  if (user_code = param_or_nil("user_code"))
74
- oauth_grant = db[oauth_grants_table].where(
75
- oauth_grants_user_code_column => user_code,
76
- oauth_grants_revoked_at_column => nil
77
- ).where(Sequel[oauth_grants_expires_in_column] >= Sequel::CURRENT_TIMESTAMP).first
74
+ oauth_grant = valid_oauth_grant_ds(oauth_grants_user_code_column => user_code).first
78
75
 
79
76
  unless oauth_grant
80
77
  set_redirect_error_flash user_code_not_found_error_flash
@@ -90,14 +87,14 @@ module Rodauth
90
87
 
91
88
  r.post do
92
89
  catch_error do
93
- unless param_or_nil("user_code")
94
- set_redirect_error_flash invalid_grant_message
90
+ unless (user_code = param_or_nil("user_code")) && !user_code.empty?
91
+ set_redirect_error_flash oauth_invalid_grant_message
95
92
  redirect device_path
96
93
  end
97
94
 
98
95
  transaction do
99
96
  before_device_verification
100
- create_oauth_token("device_code")
97
+ create_token("device_code")
101
98
  end
102
99
  end
103
100
  set_notice_flash device_verification_notice_flash
@@ -114,6 +111,10 @@ module Rodauth
114
111
  end
115
112
  end
116
113
 
114
+ def oauth_grant_types_supported
115
+ super | %w[urn:ietf:params:oauth:grant-type:device_code]
116
+ end
117
+
117
118
  private
118
119
 
119
120
  def generate_user_code
@@ -124,82 +125,61 @@ module Rodauth
124
125
  .rjust(user_code_size, "0")
125
126
  end
126
127
 
127
- def authorized_oauth_application?(oauth_application, client_secret, _)
128
- # skip if using device grant
129
- #
130
- # requests may be performed by devices with no knowledge of client secret.
131
- return true if !client_secret && use_oauth_device_code_grant_type?
128
+ # TODO: think about removing this and recommend PKCE
129
+ def supports_auth_method?(oauth_application, auth_method)
130
+ return super unless auth_method == "none"
132
131
 
133
- super
132
+ request.path == device_authorization_path || request.params.key?("device_code") || super
134
133
  end
135
134
 
136
- def create_oauth_token(grant_type)
135
+ def create_token(grant_type)
137
136
  if supported_grant_type?(grant_type, "urn:ietf:params:oauth:grant-type:device_code")
138
- throw_json_response_error(invalid_oauth_response_status, "invalid_grant_type") unless use_oauth_device_code_grant_type?
139
137
 
140
138
  oauth_grant = db[oauth_grants_table].where(
139
+ oauth_grants_type_column => "device_code",
141
140
  oauth_grants_code_column => param("device_code"),
142
141
  oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column]
143
142
  ).for_update.first
144
143
 
145
- throw_json_response_error(invalid_oauth_response_status, "invalid_grant") unless oauth_grant
144
+ throw_json_response_error(oauth_invalid_response_status, "invalid_grant") unless oauth_grant
146
145
 
147
146
  now = Time.now
148
147
 
149
- if oauth_grant[oauth_grants_revoked_at_column]
150
- oauth_token = db[oauth_tokens_table]
151
- .where(Sequel[oauth_tokens_table][oauth_tokens_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
152
- .where(Sequel[oauth_tokens_table][oauth_tokens_revoked_at_column] => nil)
153
- .where(oauth_tokens_oauth_grant_id_column => oauth_grant[oauth_grants_id_column])
154
- .first
148
+ if oauth_grant[oauth_grants_user_code_column].nil?
149
+ return create_token_from_authorization_code(
150
+ { oauth_grants_id_column => oauth_grant[oauth_grants_id_column] },
151
+ oauth_grant: oauth_grant
152
+ )
153
+ end
155
154
 
156
- throw_json_response_error(invalid_oauth_response_status, "access_denied") unless oauth_token
155
+ if oauth_grant[oauth_grants_revoked_at_column]
156
+ throw_json_response_error(oauth_invalid_response_status, "access_denied")
157
157
  elsif oauth_grant[oauth_grants_expires_in_column] < now
158
- throw_json_response_error(invalid_oauth_response_status, "expired_token")
158
+ throw_json_response_error(oauth_invalid_response_status, "expired_token")
159
159
  else
160
160
  last_polled_at = oauth_grant[oauth_grants_last_polled_at_column]
161
161
  if last_polled_at && convert_timestamp(last_polled_at) + oauth_device_code_grant_polling_interval > now
162
- throw_json_response_error(invalid_oauth_response_status, "slow_down")
162
+ throw_json_response_error(oauth_invalid_response_status, "slow_down")
163
163
  else
164
164
  db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
165
165
  .update(oauth_grants_last_polled_at_column => Sequel::CURRENT_TIMESTAMP)
166
- throw_json_response_error(invalid_oauth_response_status, "authorization_pending")
166
+ throw_json_response_error(oauth_invalid_response_status, "authorization_pending")
167
167
  end
168
168
  end
169
- oauth_token
170
169
  elsif grant_type == "device_code"
171
- redirect_response_error("invalid_grant_type") unless use_oauth_device_code_grant_type?
172
170
 
173
171
  # fetch oauth grant
174
- oauth_grant = db[oauth_grants_table].where(
175
- oauth_grants_user_code_column => param("user_code"),
176
- oauth_grants_revoked_at_column => nil
177
- ).where(Sequel[oauth_grants_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
178
- .for_update
179
- .first
180
-
181
- return unless oauth_grant
182
-
183
- # update ownership
184
- db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
185
- .update(
186
- oauth_grants_user_code_column => nil,
187
- oauth_grants_account_id_column => account_id
188
- )
189
-
190
- create_params = {
191
- oauth_tokens_account_id_column => account_id,
192
- oauth_tokens_oauth_application_id_column => oauth_grant[oauth_grants_oauth_application_id_column],
193
- oauth_tokens_oauth_grant_id_column => oauth_grant[oauth_grants_id_column],
194
- oauth_tokens_scopes_column => oauth_grant[oauth_grants_scopes_column]
195
- }
196
- create_oauth_token_from_authorization_code(oauth_grant, create_params)
172
+ rs = valid_oauth_grant_ds(
173
+ oauth_grants_user_code_column => param("user_code")
174
+ ).update(oauth_grants_user_code_column => nil, oauth_grants_type_column => "device_code")
175
+
176
+ return unless rs.positive?
197
177
  else
198
178
  super
199
179
  end
200
180
  end
201
181
 
202
- def validate_oauth_token_params
182
+ def validate_token_params
203
183
  grant_type = param_or_nil("grant_type")
204
184
 
205
185
  if grant_type == "urn:ietf:params:oauth:grant-type:device_code" && !param_or_nil("device_code")
@@ -208,12 +188,21 @@ module Rodauth
208
188
  super
209
189
  end
210
190
 
191
+ def store_token(grant_params, update_params = {})
192
+ return super unless grant_params[oauth_grants_user_code_column]
193
+
194
+ # do not clean up device code just yet
195
+ update_params.delete(oauth_grants_code_column)
196
+
197
+ update_params[oauth_grants_user_code_column] = nil
198
+ update_params[oauth_grants_account_id_column] = account_id
199
+
200
+ super(grant_params, update_params)
201
+ end
202
+
211
203
  def oauth_server_metadata_body(*)
212
204
  super.tap do |data|
213
- if use_oauth_device_code_grant_type?
214
- data[:grant_types_supported] << "urn:ietf:params:oauth:grant-type:device_code"
215
- data[:device_authorization_endpoint] = device_authorization_url
216
- end
205
+ data[:device_authorization_endpoint] = device_authorization_url
217
206
  end
218
207
  end
219
208
  end
@@ -1,19 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rodauth/oauth"
4
+
3
5
  module Rodauth
4
6
  Feature.define(:oauth_dynamic_client_registration, :OauthDynamicClientRegistration) do
5
7
  depends :oauth_base
6
8
 
7
9
  before "register"
8
10
 
9
- auth_value_method :oauth_client_registration_required_params, %w[redirect_uris client_name client_uri]
11
+ auth_value_method :oauth_client_registration_required_params, %w[redirect_uris client_name]
10
12
 
11
- PROTECTED_APPLICATION_ATTRIBUTES = %i[account_id client_id].freeze
13
+ PROTECTED_APPLICATION_ATTRIBUTES = %w[account_id client_id].freeze
12
14
 
13
15
  # /register
14
- route(:register) do |r|
15
- next unless is_authorization_server?
16
-
16
+ auth_server_route(:register) do |r|
17
17
  before_register_route
18
18
 
19
19
  validate_client_registration_params
@@ -43,8 +43,17 @@ module Rodauth
43
43
 
44
44
  private
45
45
 
46
- def registration_metadata
47
- oauth_server_metadata_body
46
+ def _before_register
47
+ raise %{dynamic client registration requires authentication.
48
+ Override ´before_register` to perform it.
49
+ example:
50
+
51
+ before_register do
52
+ account = _account_from_login(request.env["HTTP_X_USER_EMAIL"])
53
+ authorization_required unless account
54
+ @oauth_application_params[:account_id] = account[:id]
55
+ end
56
+ }
48
57
  end
49
58
 
50
59
  def validate_client_registration_params
@@ -53,41 +62,43 @@ module Rodauth
53
62
  register_throw_json_response_error("invalid_client_metadata", register_required_param_message(required_param))
54
63
  end
55
64
  end
56
- metadata = registration_metadata
57
65
 
58
66
  @oauth_application_params = request.params.each_with_object({}) do |(key, value), params|
59
67
  case key
60
68
  when "redirect_uris"
61
69
  if value.is_a?(Array)
62
70
  value = value.each do |uri|
63
- register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(uri)) unless check_valid_uri?(uri)
71
+ unless check_valid_no_fragment_uri?(uri)
72
+ register_throw_json_response_error("invalid_redirect_uri",
73
+ register_invalid_uri_message(uri))
74
+ end
64
75
  end.join(" ")
65
76
  else
66
77
  register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(value))
67
78
  end
68
79
  key = oauth_applications_redirect_uri_column
69
80
  when "token_endpoint_auth_method"
70
- unless oauth_auth_methods_supported.include?(value)
71
- register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
81
+ unless oauth_token_endpoint_auth_methods_supported.include?(value)
82
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
72
83
  end
73
84
  # verify if in range
74
85
  key = oauth_applications_token_endpoint_auth_method_column
75
86
  when "grant_types"
76
87
  if value.is_a?(Array)
77
88
  value = value.each do |grant_type|
78
- unless metadata[:grant_types_supported].include?(grant_type)
79
- register_throw_json_response_error("invalid_client_metadata", register_invalid_grant_type_message(grant_type))
89
+ unless oauth_grant_types_supported.include?(grant_type)
90
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(grant_type, value))
80
91
  end
81
92
  end.join(" ")
82
93
  else
83
- set_field_error(key, invalid_client_metadata_message)
94
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
84
95
  end
85
96
  key = oauth_applications_grant_types_column
86
97
  when "response_types"
87
98
  if value.is_a?(Array)
88
- grant_types = request.params["grant_types"] || metadata[:grant_types_supported]
99
+ grant_types = request.params["grant_types"] || oauth_grant_types_supported
89
100
  value = value.each do |response_type|
90
- unless metadata[:response_types_supported].include?(response_type)
101
+ unless oauth_response_types_supported.include?(response_type)
91
102
  register_throw_json_response_error("invalid_client_metadata",
92
103
  register_invalid_response_type_message(response_type))
93
104
  end
@@ -95,7 +106,7 @@ module Rodauth
95
106
  validate_client_registration_response_type(response_type, grant_types)
96
107
  end.join(" ")
97
108
  else
98
- set_field_error(key, invalid_client_metadata_message)
109
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
99
110
  end
100
111
  key = oauth_applications_response_types_column
101
112
  # verify if in range and match grant type
@@ -133,10 +144,10 @@ module Rodauth
133
144
  key = oauth_applications_name_column
134
145
  else
135
146
  if respond_to?(:"oauth_applications_#{key}_column")
136
- property = :"oauth_applications_#{key}_column"
137
- if PROTECTED_APPLICATION_ATTRIBUTES.include?(property)
147
+ if PROTECTED_APPLICATION_ATTRIBUTES.include?(key)
138
148
  register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
139
149
  end
150
+ property = :"oauth_applications_#{key}_column"
140
151
  key = __send__(property)
141
152
  elsif !db[oauth_applications_table].columns.include?(key.to_sym)
142
153
  register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
@@ -167,16 +178,20 @@ module Rodauth
167
178
  end
168
179
 
169
180
  def do_register(return_params = request.params.dup)
181
+ applications_ds = db[oauth_applications_table]
182
+ application_columns = applications_ds.columns
183
+
170
184
  # set defaults
171
185
  create_params = @oauth_application_params
172
- create_params[oauth_applications_scopes_column] ||= return_params["scopes"] = oauth_application_default_scope.join(" ")
173
- create_params[oauth_applications_token_endpoint_auth_method_column] ||= begin
174
- return_params["token_endpoint_auth_method"] = "client_secret_basic"
175
- "client_secret_basic"
176
- end
177
- create_params[oauth_applications_grant_types_column] ||= begin
178
- return_params["grant_types"] = %w[authorization_code]
186
+ create_params[oauth_applications_scopes_column] ||= return_params["scopes"] = oauth_application_scopes.join(" ")
187
+ if create_params[oauth_applications_grant_types_column] ||= begin
188
+ return_params["grant_types"] = %w[authorization_code] # rubocop:disable Lint/AssignmentInCondition
179
189
  "authorization_code"
190
+ end
191
+ create_params[oauth_applications_token_endpoint_auth_method_column] ||= begin
192
+ return_params["token_endpoint_auth_method"] = "client_secret_basic"
193
+ "client_secret_basic"
194
+ end
180
195
  end
181
196
  create_params[oauth_applications_response_types_column] ||= begin
182
197
  return_params["response_types"] = %w[code]
@@ -188,22 +203,24 @@ module Rodauth
188
203
  return_params["client_id"] = client_id
189
204
  return_params["client_id_issued_at"] = Time.now.utc.iso8601
190
205
  if create_params.key?(oauth_applications_client_secret_column)
191
- create_params[oauth_applications_client_secret_column] = secret_hash(create_params[oauth_applications_client_secret_column])
206
+ set_client_secret(create_params, create_params[oauth_applications_client_secret_column])
192
207
  return_params.delete("client_secret")
193
208
  else
194
209
  client_secret = oauth_unique_id_generator
195
- create_params[oauth_applications_client_secret_column] = secret_hash(client_secret)
210
+ set_client_secret(create_params, client_secret)
196
211
  return_params["client_secret"] = client_secret
197
212
  return_params["client_secret_expires_at"] = 0
213
+
214
+ create_params.delete_if { |k, _| !application_columns.include?(k) }
198
215
  end
199
- db[oauth_applications_table].insert(create_params)
216
+ applications_ds.insert(create_params)
200
217
  end
201
218
 
202
219
  return_params
203
220
  end
204
221
 
205
222
  def register_throw_json_response_error(code, message)
206
- throw_json_response_error(invalid_oauth_response_status, code, message)
223
+ throw_json_response_error(oauth_invalid_response_status, code, message)
207
224
  end
208
225
 
209
226
  def register_required_param_message(key)
@@ -214,6 +231,10 @@ module Rodauth
214
231
  "The param '#{key}' is not supported by this server."
215
232
  end
216
233
 
234
+ def register_invalid_client_metadata_message(key, value)
235
+ "The value '#{value}' is not supported by this server for param '#{key}'."
236
+ end
237
+
217
238
  def register_invalid_contacts_message(contacts)
218
239
  "The contacts '#{contacts}' are not allowed by this server."
219
240
  end
@@ -230,7 +251,7 @@ module Rodauth
230
251
  "The given scopes (#{scopes}) are not allowed by this server."
231
252
  end
232
253
 
233
- def register_invalid_grant_type_message(grant_type)
254
+ def register_oauth_invalid_grant_type_message(grant_type)
234
255
  "The grant type #{grant_type} is not allowed by this server."
235
256
  end
236
257
 
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rodauth/oauth"
4
+
5
+ module Rodauth
6
+ Feature.define(:oauth_grant_management, :OauthTokenManagement) do
7
+ depends :oauth_management_base, :oauth_token_revocation
8
+
9
+ view "oauth_grants", "My Oauth Grants", "oauth_grants"
10
+
11
+ button "Revoke", "oauth_grant_revoke"
12
+
13
+ auth_value_method :oauth_grants_path, "oauth-grants"
14
+
15
+ %w[type token refresh_token expires_in revoked_at].each do |param|
16
+ translatable_method :"oauth_grants_#{param}_label", param.gsub("_", " ").capitalize
17
+ end
18
+ translatable_method :oauth_no_grants_text, "No oauth grants yet!"
19
+
20
+ auth_value_method :oauth_grants_route, "oauth-grants"
21
+ auth_value_method :oauth_grants_id_pattern, Integer
22
+ auth_value_method :oauth_grants_per_page, 20
23
+
24
+ auth_value_methods(
25
+ :oauth_grant_path
26
+ )
27
+
28
+ def oauth_grants_path(opts = {})
29
+ route_path(oauth_grants_route, opts)
30
+ end
31
+
32
+ def oauth_grant_path(id)
33
+ "#{oauth_grants_path}/#{id}"
34
+ end
35
+
36
+ def load_oauth_grant_management_routes
37
+ request.on(oauth_grants_route) do
38
+ check_csrf if check_csrf?
39
+ require_account
40
+
41
+ request.post(oauth_grants_id_pattern) do |id|
42
+ db[oauth_grants_table]
43
+ .where(oauth_grants_id_column => id)
44
+ .where(oauth_grants_account_id_column => account_id)
45
+ .update(oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
46
+
47
+ set_notice_flash revoke_oauth_grant_notice_flash
48
+ redirect oauth_grants_path || "/"
49
+ end
50
+
51
+ request.is do
52
+ request.get do
53
+ page = Integer(param_or_nil("page") || 1)
54
+ per_page = per_page_param(oauth_grants_per_page)
55
+
56
+ scope.instance_variable_set(:@oauth_grants, db[oauth_grants_table]
57
+ .select(Sequel[oauth_grants_table].*, Sequel[oauth_applications_table][oauth_applications_name_column])
58
+ .join(oauth_applications_table, Sequel[oauth_grants_table][oauth_grants_oauth_application_id_column] =>
59
+ Sequel[oauth_applications_table][oauth_applications_id_column])
60
+ .where(Sequel[oauth_grants_table][oauth_grants_account_id_column] => account_id)
61
+ .where(oauth_grants_revoked_at_column => nil)
62
+ .order(Sequel.desc(oauth_grants_id_column))
63
+ .paginate(page, per_page))
64
+ oauth_grants_view
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,39 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rodauth/oauth"
4
+
3
5
  module Rodauth
4
6
  Feature.define(:oauth_implicit_grant, :OauthImplicitGrant) do
5
7
  depends :oauth_authorize_base
6
8
 
7
- auth_value_method :use_oauth_implicit_grant_type?, false
8
-
9
- private
9
+ def oauth_grant_types_supported
10
+ super | %w[implicit]
11
+ end
10
12
 
11
- def check_valid_response_type?
12
- response_type = param_or_nil("response_type")
13
+ def oauth_response_types_supported
14
+ super | %w[token]
15
+ end
13
16
 
14
- response_type.nil? || response_type == "token" || super
17
+ def oauth_response_modes_supported
18
+ super | %w[fragment]
15
19
  end
16
20
 
21
+ private
22
+
17
23
  def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
18
- return super unless param("response_type") == "token" && use_oauth_implicit_grant_type?
24
+ response_type = param("response_type")
25
+ return super unless response_type == "token" && supported_response_type?(response_type)
19
26
 
20
27
  response_mode ||= "fragment"
21
- response_params.replace(_do_authorize_token)
28
+
29
+ redirect_response_error("invalid_request") unless supported_response_mode?(response_mode)
30
+
31
+ oauth_grant = _do_authorize_token
32
+
33
+ response_params.replace(json_access_token_payload(oauth_grant))
22
34
 
23
35
  response_params["state"] = param("state") if param_or_nil("state")
24
36
 
25
37
  [response_params, response_mode]
26
38
  end
27
39
 
28
- def _do_authorize_token
29
- create_params = {
30
- oauth_tokens_account_id_column => account_id,
31
- oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
32
- oauth_tokens_scopes_column => scopes
33
- }
34
- oauth_token = generate_oauth_token(create_params, false)
40
+ def _do_authorize_token(grant_params = {})
41
+ grant_params = {
42
+ oauth_grants_type_column => "implicit",
43
+ oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
44
+ oauth_grants_scopes_column => scopes,
45
+ oauth_grants_account_id_column => account_id
46
+ }.merge(grant_params)
35
47
 
36
- json_access_token_payload(oauth_token)
48
+ generate_token(grant_params, false)
37
49
  end
38
50
 
39
51
  def authorize_response(params, mode)
@@ -46,18 +58,8 @@ module Rodauth
46
58
  redirect(redirect_url.to_s)
47
59
  end
48
60
 
49
- def oauth_server_metadata_body(*)
50
- super.tap do |data|
51
- if use_oauth_implicit_grant_type?
52
- data[:response_types_supported] << "token"
53
- data[:response_modes_supported] << "fragment"
54
- data[:grant_types_supported] << "implicit"
55
- end
56
- end
57
- end
58
-
59
61
  def check_valid_response_type?
60
- return true if use_oauth_implicit_grant_type? && param_or_nil("response_type") == "token"
62
+ return true if param_or_nil("response_type") == "token"
61
63
 
62
64
  super
63
65
  end