rodauth-oauth 0.0.2 → 0.1.0

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.
@@ -10,7 +10,7 @@ module Rodauth::OAuth::Rails
10
10
  include ::Rails::Generators::Migration
11
11
 
12
12
  source_root "#{__dir__}/templates"
13
- namespace "roda:oauth:install"
13
+ namespace "rodauth:oauth:install"
14
14
 
15
15
  def create_rodauth_migration
16
16
  return unless defined?(ActiveRecord::Base)
@@ -29,7 +29,8 @@ class CreateRodauthOAuth < ActiveRecord::Migration<%= migration_version %>
29
29
  # uncomment to enable PKCE
30
30
  # t.string :code_challenge
31
31
  # t.string :code_challenge_method
32
-
32
+ # uncomment to use OIDC nonce
33
+ # t.string :nonce
33
34
  t.index(%i[oauth_application_id code], unique: true)
34
35
  end
35
36
 
@@ -54,6 +55,8 @@ class CreateRodauthOAuth < ActiveRecord::Migration<%= migration_version %>
54
55
  t.datetime :revoked_at
55
56
  t.string :scopes, null: false
56
57
  t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
58
+ # uncomment to use OIDC nonce
59
+ # t.string :nonce
57
60
  end
58
61
  end
59
62
  end
@@ -7,7 +7,7 @@ module Rodauth::OAuth
7
7
  module Generators
8
8
  class ViewsGenerator < ::Rails::Generators::Base
9
9
  source_root "#{__dir__}/templates"
10
- namespace "roda:oauth:views"
10
+ namespace "rodauth:oauth:views"
11
11
 
12
12
  DEFAULT = %w[oauth_authorize].freeze
13
13
  VIEWS = {
@@ -16,11 +16,6 @@ module Rodauth::OAuth
16
16
  }.freeze
17
17
 
18
18
  DEPENDENCIES = {
19
- active_sessions: :logout,
20
- otp: :two_factor_base,
21
- sms_codes: :two_factor_base,
22
- recovery_codes: :two_factor_base,
23
- webauthn: :two_factor_base
24
19
  }.freeze
25
20
 
26
21
  class_option :features, type: :array,
@@ -1,8 +1,15 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require "base64"
4
+ require "securerandom"
5
+ require "net/http"
6
+
7
+ require "rodauth/oauth/ttl_store"
8
+
3
9
  module Rodauth
4
10
  Feature.define(:oauth) do
5
11
  # RUBY EXTENSIONS
12
+ # :nocov:
6
13
  unless Regexp.method_defined?(:match?)
7
14
  module RegexpExtensions
8
15
  refine(Regexp) do
@@ -30,33 +37,30 @@ module Rodauth
30
37
  end
31
38
  using(SuffixExtensions)
32
39
  end
40
+ # :nocov:
33
41
 
34
42
  SCOPES = %w[profile.read].freeze
35
43
 
36
- depends :login
37
-
38
44
  before "authorize"
39
45
  after "authorize"
40
- after "authorize_failure"
41
46
 
42
47
  before "token"
43
- after "token"
44
48
 
45
49
  before "revoke"
46
50
  after "revoke"
47
51
 
52
+ before "introspect"
53
+
48
54
  before "create_oauth_application"
49
55
  after "create_oauth_application"
50
56
 
51
- error_flash "OAuth Authorization invalid parameters", "oauth_grant_valid_parameters"
52
-
53
57
  error_flash "Please authorize to continue", "require_authorization"
54
58
  error_flash "There was an error registering your oauth application", "create_oauth_application"
55
59
  notice_flash "Your oauth application has been registered", "create_oauth_application"
56
60
 
57
61
  notice_flash "The oauth token has been revoked", "revoke_oauth_token"
58
62
 
59
- view "oauth_authorize", "Authorize", "authorize"
63
+ view "authorize", "Authorize", "authorize"
60
64
  view "oauth_applications", "Oauth Applications", "oauth_applications"
61
65
  view "oauth_application", "Oauth Application", "oauth_application"
62
66
  view "new_oauth_application", "New Oauth Application", "new_oauth_application"
@@ -66,22 +70,16 @@ module Rodauth
66
70
 
67
71
  auth_value_method :oauth_grant_expires_in, 60 * 5 # 5 minutes
68
72
  auth_value_method :oauth_token_expires_in, 60 * 60 # 60 minutes
69
- auth_value_method :use_oauth_implicit_grant_type, false
73
+ auth_value_method :use_oauth_implicit_grant_type?, false
74
+ auth_value_method :use_oauth_pkce?, true
75
+ auth_value_method :use_oauth_access_type?, true
70
76
 
71
77
  auth_value_method :oauth_require_pkce, false
72
78
  auth_value_method :oauth_pkce_challenge_method, "S256"
73
79
 
74
- # URL PARAMS
80
+ auth_value_method :oauth_valid_uri_schemes, %w[https]
75
81
 
76
- # Authorize / token
77
- %w[
78
- grant_type code refresh_token client_id client_secret scope
79
- state redirect_uri scopes token_type_hint token
80
- access_type approval_prompt response_type
81
- code_challenge code_challenge_method code_verifier
82
- ].each do |param|
83
- auth_value_method :"#{param}_param", param
84
- end
82
+ auth_value_method :oauth_scope_separator, " "
85
83
 
86
84
  # Application
87
85
  APPLICATION_REQUIRED_PARAMS = %w[name description scopes homepage_url redirect_uri client_secret].freeze
@@ -89,7 +87,11 @@ module Rodauth
89
87
 
90
88
  (APPLICATION_REQUIRED_PARAMS + %w[client_id]).each do |param|
91
89
  auth_value_method :"oauth_application_#{param}_param", param
90
+ translatable_method :"#{param}_label", param.gsub("_", " ").capitalize
92
91
  end
92
+ button "Register", "oauth_application"
93
+ button "Authorize", "oauth_authorize"
94
+ button "Revoke", "oauth_token_revoke"
93
95
 
94
96
  # OAuth Token
95
97
  auth_value_method :oauth_tokens_path, "oauth-tokens"
@@ -141,11 +143,9 @@ module Rodauth
141
143
 
142
144
  auth_value_method :oauth_application_default_scope, SCOPES.first
143
145
  auth_value_method :oauth_application_scopes, SCOPES
144
- auth_value_method :oauth_token_type, "Bearer"
146
+ auth_value_method :oauth_token_type, "bearer"
145
147
 
146
- auth_value_method :invalid_request, "Request is missing a required parameter"
147
- auth_value_method :invalid_client, "Invalid client"
148
- auth_value_method :unauthorized_client, "Unauthorized client"
148
+ auth_value_method :invalid_client_message, "Invalid client"
149
149
  auth_value_method :invalid_grant_type_message, "Invalid grant type"
150
150
  auth_value_method :invalid_grant_message, "Invalid grant"
151
151
  auth_value_method :invalid_scope_message, "Invalid scope"
@@ -162,33 +162,40 @@ module Rodauth
162
162
  auth_value_method :unsupported_transform_algorithm_error_code, "invalid_request"
163
163
  auth_value_method :unsupported_transform_algorithm_message, "transform algorithm not supported"
164
164
 
165
+ # METADATA
166
+ auth_value_method :oauth_metadata_service_documentation, nil
167
+ auth_value_method :oauth_metadata_ui_locales_supported, nil
168
+ auth_value_method :oauth_metadata_op_policy_uri, nil
169
+ auth_value_method :oauth_metadata_op_tos_uri, nil
170
+
171
+ # Resource Server params
172
+ # Only required to use if the plugin is to be used in a resource server
173
+ auth_value_method :is_authorization_server?, true
174
+
165
175
  auth_value_methods(
176
+ :fetch_access_token,
166
177
  :oauth_unique_id_generator,
167
178
  :secret_matches?,
168
- :secret_hash
179
+ :secret_hash,
180
+ :generate_token_hash,
181
+ :authorization_server_url,
182
+ :before_introspection_request
169
183
  )
170
184
 
171
- redirect(:oauth_application) do |id|
172
- "/#{oauth_applications_path}/#{id}"
173
- end
185
+ auth_value_methods(:only_json?)
174
186
 
175
- redirect(:require_authorization) do
176
- if logged_in?
177
- oauth_authorize_path
178
- else
179
- login_redirect
180
- end
181
- end
187
+ auth_value_method :json_request_regexp, %r{\bapplication/(?:vnd\.api\+)?json\b}i
182
188
 
183
- auth_value_method :json_request_accept_regexp, %r{\bapplication/(?:vnd\.api\+)?json\b}i
184
- auth_methods(:json_request?)
189
+ SERVER_METADATA = OAuth::TtlStore.new
185
190
 
186
191
  def check_csrf?
187
192
  case request.path
188
- when oauth_token_path
193
+ when token_path, introspect_path
189
194
  false
190
- when oauth_revoke_path
195
+ when revoke_path
191
196
  !json_request?
197
+ when authorize_path, %r{/#{oauth_applications_path}}
198
+ only_json? ? false : super
192
199
  else
193
200
  super
194
201
  end
@@ -199,51 +206,53 @@ module Rodauth
199
206
  super || authorization_token
200
207
  end
201
208
 
202
- def json_request?
203
- return @json_request if defined?(@json_request)
209
+ def accepts_json?
210
+ return true if only_json?
204
211
 
205
- @json_request = request.get_header("HTTP_ACCEPT") =~ json_request_accept_regexp
212
+ (accept = request.env["HTTP_ACCEPT"]) && accept =~ json_request_regexp
206
213
  end
207
214
 
208
- attr_reader :oauth_application
215
+ unless method_defined?(:json_request?)
216
+ # :nocov:
217
+ # copied from the jwt feature
218
+ def json_request?
219
+ return @json_request if defined?(@json_request)
209
220
 
210
- def initialize(scope)
211
- @scope = scope
221
+ @json_request = request.content_type =~ json_request_regexp
222
+ end
223
+ # :nocov:
212
224
  end
213
225
 
214
- def state
215
- param_or_nil(state_param)
226
+ def initialize(scope)
227
+ @scope = scope
216
228
  end
217
229
 
218
230
  def scopes
219
- (param_or_nil(scopes_param) || oauth_application_default_scope).split(" ")
220
- end
221
-
222
- def client_id
223
- param_or_nil(client_id_param)
224
- end
225
-
226
- def client_secret
227
- param_or_nil(client_secret_param)
231
+ scope = request.params["scope"]
232
+ case scope
233
+ when Array
234
+ scope
235
+ when String
236
+ scope.split(" ")
237
+ when nil
238
+ [oauth_application_default_scope]
239
+ end
228
240
  end
229
241
 
230
242
  def redirect_uri
231
- param_or_nil(redirect_uri_param) || oauth_application[oauth_applications_redirect_uri_column]
232
- end
243
+ param_or_nil("redirect_uri") || begin
244
+ return unless oauth_application
233
245
 
234
- def token_type_hint
235
- param_or_nil(token_type_hint_param) || "access_token"
236
- end
237
-
238
- def token
239
- param_or_nil(token_param)
246
+ redirect_uris = oauth_application[oauth_applications_redirect_uri_column].split(" ")
247
+ redirect_uris.size == 1 ? redirect_uris.first : nil
248
+ end
240
249
  end
241
250
 
242
251
  def oauth_application
243
252
  return @oauth_application if defined?(@oauth_application)
244
253
 
245
254
  @oauth_application = begin
246
- client_id = param(client_id_param)
255
+ client_id = param_or_nil("client_id")
247
256
 
248
257
  return unless client_id
249
258
 
@@ -251,31 +260,54 @@ module Rodauth
251
260
  end
252
261
  end
253
262
 
263
+ def fetch_access_token
264
+ value = request.env["HTTP_AUTHORIZATION"]
265
+
266
+ return unless value
267
+
268
+ scheme, token = value.split(" ", 2)
269
+
270
+ return unless scheme.downcase == oauth_token_type
271
+
272
+ return if token.empty?
273
+
274
+ token
275
+ end
276
+
254
277
  def authorization_token
255
278
  return @authorization_token if defined?(@authorization_token)
256
279
 
257
- @authorization_token = begin
258
- value = request.get_header("HTTP_AUTHORIZATION").to_s
280
+ # check if there is a token
281
+ bearer_token = fetch_access_token
259
282
 
260
- scheme, token = value.split(" ", 2)
283
+ return unless bearer_token
261
284
 
262
- return unless scheme == "Bearer"
263
-
264
- # check if there is a token
265
- # check if token has not expired
266
- # check if token has been revoked
267
- oauth_token_by_token(token).where(Sequel[oauth_tokens_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
268
- .where(oauth_tokens_revoked_at_column => nil)
269
- .first
270
- end
285
+ # check if token has not expired
286
+ # check if token has been revoked
287
+ @authorization_token = oauth_token_by_token(bearer_token)
271
288
  end
272
289
 
273
290
  def require_oauth_authorization(*scopes)
274
- authorization_required unless authorization_token
291
+ token_scopes = if is_authorization_server?
292
+ authorization_required unless authorization_token
293
+
294
+ scopes << oauth_application_default_scope if scopes.empty?
295
+
296
+ authorization_token[oauth_tokens_scopes_column].split(oauth_scope_separator)
297
+ else
298
+ bearer_token = fetch_access_token
299
+
300
+ authorization_required unless bearer_token
301
+
302
+ scopes << oauth_application_default_scope if scopes.empty?
275
303
 
276
- scopes << oauth_application_default_scope if scopes.empty?
304
+ # where in resource server, NOT the authorization server.
305
+ payload = introspection_request("access_token", bearer_token)
277
306
 
278
- token_scopes = authorization_token[:scopes].split(",")
307
+ authorization_required unless payload["active"]
308
+
309
+ payload["scope"].split(oauth_scope_separator)
310
+ end
279
311
 
280
312
  authorization_required unless scopes.any? { |scope| token_scopes.include?(scope) }
281
313
  end
@@ -288,8 +320,11 @@ module Rodauth
288
320
  request.get "new" do
289
321
  new_oauth_application_view
290
322
  end
323
+
291
324
  request.on(oauth_applications_id_pattern) do |id|
292
325
  oauth_application = db[oauth_applications_table].where(oauth_applications_id_column => id).first
326
+ next unless oauth_application
327
+
293
328
  scope.instance_variable_set(:@oauth_application, oauth_application)
294
329
 
295
330
  request.is do
@@ -301,7 +336,9 @@ module Rodauth
301
336
  request.on(oauth_tokens_path) do
302
337
  oauth_tokens = db[oauth_tokens_table].where(oauth_tokens_oauth_application_id_column => id)
303
338
  scope.instance_variable_set(:@oauth_tokens, oauth_tokens)
304
- oauth_tokens_view
339
+ request.get do
340
+ oauth_tokens_view
341
+ end
305
342
  end
306
343
  end
307
344
 
@@ -319,7 +356,7 @@ module Rodauth
319
356
  id = create_oauth_application
320
357
  after_create_oauth_application
321
358
  set_notice_flash create_oauth_application_notice_flash
322
- redirect oauth_application_redirect(id)
359
+ redirect "#{request.path}/#{id}"
323
360
  end
324
361
  end
325
362
  set_error_flash create_oauth_application_error_flash
@@ -328,8 +365,102 @@ module Rodauth
328
365
  end
329
366
  end
330
367
 
368
+ def oauth_server_metadata(issuer = nil)
369
+ request.on(".well-known") do
370
+ request.on("oauth-authorization-server") do
371
+ request.get do
372
+ json_response_success(oauth_server_metadata_body(issuer))
373
+ end
374
+ end
375
+ end
376
+ end
377
+
331
378
  private
332
379
 
380
+ def authorization_server_url
381
+ base_url
382
+ end
383
+
384
+ def authorization_server_metadata
385
+ auth_url = URI(authorization_server_url)
386
+
387
+ server_metadata = SERVER_METADATA[auth_url]
388
+
389
+ return server_metadata if server_metadata
390
+
391
+ SERVER_METADATA.set(auth_url) do
392
+ http = Net::HTTP.new(auth_url.host, auth_url.port)
393
+ http.use_ssl = auth_url.scheme == "https"
394
+
395
+ request = Net::HTTP::Get.new("/.well-known/oauth-authorization-server")
396
+ request["accept"] = json_response_content_type
397
+ response = http.request(request)
398
+ authorization_required unless response.code.to_i == 200
399
+
400
+ # time-to-live
401
+ ttl = if response.key?("cache-control")
402
+ cache_control = response["cache_control"]
403
+ cache_control[/max-age=(\d+)/, 1]
404
+ elsif response.key?("expires")
405
+ Time.httpdate(response["expires"]).utc.to_i - Time.now.utc.to_i
406
+ end
407
+
408
+ [JSON.parse(response.body, symbolize_names: true), ttl]
409
+ end
410
+ end
411
+
412
+ def introspection_request(token_type_hint, token)
413
+ auth_url = URI(authorization_server_url)
414
+ http = Net::HTTP.new(auth_url.host, auth_url.port)
415
+ http.use_ssl = auth_url.scheme == "https"
416
+
417
+ request = Net::HTTP::Post.new(introspect_path)
418
+ request["content-type"] = json_response_content_type
419
+ request["accept"] = json_response_content_type
420
+ request.body = JSON.dump({ "token_type_hint" => token_type_hint, "token" => token })
421
+
422
+ before_introspection_request(request)
423
+ response = http.request(request)
424
+ authorization_required unless response.code.to_i == 200
425
+
426
+ JSON.parse(response.body)
427
+ end
428
+
429
+ def before_introspection_request(request); end
430
+
431
+ def template_path(page)
432
+ path = File.join(File.dirname(__FILE__), "../../../templates", "#{page}.str")
433
+ return super unless File.exist?(path)
434
+
435
+ path
436
+ end
437
+
438
+ # to be used internally. Same semantics as require account, must:
439
+ # fetch an authorization basic header
440
+ # parse client id and secret
441
+ #
442
+ def require_oauth_application
443
+ # get client credenntials
444
+ client_id = client_secret = nil
445
+
446
+ # client_secret_basic
447
+ if (token = ((v = request.env["HTTP_AUTHORIZATION"]) && v[/\A *Basic (.*)\Z/, 1]))
448
+ client_id, client_secret = Base64.decode64(token).split(/:/, 2)
449
+ else
450
+ client_id = param_or_nil("client_id")
451
+ client_secret = param_or_nil("client_secret")
452
+ end
453
+
454
+ authorization_required unless client_id
455
+
456
+ @oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => client_id).first
457
+
458
+ # skip if using pkce
459
+ return if @oauth_application && use_oauth_pkce? && param_or_nil("code_verifier")
460
+
461
+ authorization_required unless @oauth_application && secret_matches?(@oauth_application, client_secret)
462
+ end
463
+
333
464
  def secret_matches?(oauth_application, secret)
334
465
  BCrypt::Password.new(oauth_application[oauth_applications_client_secret_column]) == secret
335
466
  end
@@ -346,23 +477,27 @@ module Rodauth
346
477
  Base64.urlsafe_encode64(Digest::SHA256.digest(token))
347
478
  end
348
479
 
480
+ def token_from_application?(oauth_token, oauth_application)
481
+ oauth_token[oauth_tokens_oauth_application_id_column] == oauth_application[oauth_applications_id_column]
482
+ end
483
+
349
484
  unless method_defined?(:password_hash)
485
+ # :nocov:
350
486
  # From login_requirements_base feature
351
487
  if ENV["RACK_ENV"] == "test"
352
488
  def password_hash_cost
353
489
  BCrypt::Engine::MIN_COST
354
490
  end
355
491
  else
356
- # :nocov:
357
492
  def password_hash_cost
358
493
  BCrypt::Engine::DEFAULT_COST
359
494
  end
360
- # :nocov:
361
495
  end
362
496
 
363
497
  def password_hash(password)
364
498
  BCrypt::Password.create(password, cost: password_hash_cost)
365
499
  end
500
+ # :nocov:
366
501
  end
367
502
 
368
503
  def generate_oauth_token(params = {}, should_generate_refresh_token = true)
@@ -400,7 +535,7 @@ module Rodauth
400
535
 
401
536
  begin
402
537
  if ds.supports_returning?(:insert)
403
- ds.returning.insert(params)
538
+ ds.returning.insert(params).first
404
539
  else
405
540
  id = ds.insert(params)
406
541
  ds.where(oauth_tokens_id_column => id).first
@@ -410,20 +545,36 @@ module Rodauth
410
545
  end
411
546
  end
412
547
 
413
- def oauth_token_by_token(token)
414
- if oauth_tokens_token_hash_column
415
- db[oauth_tokens_table].where(oauth_tokens_token_hash_column => generate_token_hash(token))
416
- else
417
- db[oauth_tokens_table].where(oauth_tokens_token_column => token)
418
- end
548
+ def oauth_token_by_token(token, dataset = db[oauth_tokens_table])
549
+ ds = if oauth_tokens_token_hash_column
550
+ dataset.where(oauth_tokens_token_hash_column => generate_token_hash(token))
551
+ else
552
+ dataset.where(oauth_tokens_token_column => token)
553
+ end
554
+
555
+ ds.where(Sequel[oauth_tokens_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
556
+ .where(oauth_tokens_revoked_at_column => nil).first
419
557
  end
420
558
 
421
- def oauth_token_by_refresh_token(token)
422
- if oauth_tokens_refresh_token_hash_column
423
- db[oauth_tokens_table].where(oauth_tokens_refresh_token_hash_column => generate_token_hash(token))
424
- else
425
- db[oauth_tokens_table].where(oauth_tokens_refresh_token_column => token)
426
- end
559
+ def oauth_token_by_refresh_token(token, dataset = db[oauth_tokens_table])
560
+ ds = if oauth_tokens_refresh_token_hash_column
561
+ dataset.where(oauth_tokens_refresh_token_hash_column => generate_token_hash(token))
562
+ else
563
+ dataset.where(oauth_tokens_refresh_token_column => token)
564
+ end
565
+
566
+ ds.where(Sequel[oauth_tokens_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
567
+ .where(oauth_tokens_revoked_at_column => nil).first
568
+ end
569
+
570
+ def json_access_token_payload(oauth_token)
571
+ payload = {
572
+ "access_token" => oauth_token[oauth_tokens_token_column],
573
+ "token_type" => oauth_token_type,
574
+ "expires_in" => oauth_token_expires_in
575
+ }
576
+ payload["refresh_token"] = oauth_token[oauth_tokens_refresh_token_column] if oauth_token[oauth_tokens_refresh_token_column]
577
+ payload
427
578
  end
428
579
 
429
580
  # Oauth Application
@@ -441,11 +592,21 @@ module Rodauth
441
592
 
442
593
  def validate_oauth_application_params
443
594
  oauth_application_params.each do |key, value|
444
- if key == oauth_application_homepage_url_param ||
445
- key == oauth_application_redirect_uri_param
595
+ if key == oauth_application_homepage_url_param
596
+
597
+ set_field_error(key, invalid_url_message) unless check_valid_uri?(value)
598
+
599
+ elsif key == oauth_application_redirect_uri_param
446
600
 
447
- set_field_error(key, invalid_url_message) unless URI::DEFAULT_PARSER.make_regexp(%w[http https]).match?(value)
601
+ if value.respond_to?(:each)
602
+ value.each do |uri|
603
+ next if uri.empty?
448
604
 
605
+ set_field_error(key, invalid_url_message) unless check_valid_uri?(uri)
606
+ end
607
+ else
608
+ set_field_error(key, invalid_url_message) unless check_valid_uri?(value)
609
+ end
449
610
  elsif key == oauth_application_scopes_param
450
611
 
451
612
  value.each do |scope|
@@ -463,10 +624,12 @@ module Rodauth
463
624
  oauth_applications_name_column => oauth_application_params[oauth_application_name_param],
464
625
  oauth_applications_description_column => oauth_application_params[oauth_application_description_param],
465
626
  oauth_applications_scopes_column => oauth_application_params[oauth_application_scopes_param],
466
- oauth_applications_homepage_url_column => oauth_application_params[oauth_application_homepage_url_param],
467
- oauth_applications_redirect_uri_column => oauth_application_params[oauth_application_redirect_uri_param]
627
+ oauth_applications_homepage_url_column => oauth_application_params[oauth_application_homepage_url_param]
468
628
  }
469
629
 
630
+ redirect_uris = oauth_application_params[oauth_application_redirect_uri_param]
631
+ redirect_uris = redirect_uris.to_a.reject(&:empty?).join(" ") if redirect_uris.respond_to?(:each)
632
+ create_params[oauth_applications_redirect_uri_column] = redirect_uris unless redirect_uris.empty?
470
633
  # set client ID/secret pairs
471
634
 
472
635
  create_params.merge! \
@@ -475,25 +638,18 @@ module Rodauth
475
638
  secret_hash(oauth_application_params[oauth_application_client_secret_param])
476
639
 
477
640
  create_params[oauth_applications_scopes_column] = if create_params[oauth_applications_scopes_column]
478
- create_params[oauth_applications_scopes_column].join(",")
641
+ create_params[oauth_applications_scopes_column].join(oauth_scope_separator)
479
642
  else
480
643
  oauth_application_default_scope
481
644
  end
482
645
 
483
- ds = db[oauth_applications_table]
484
-
485
646
  id = nil
486
647
  raised = begin
487
- id = if ds.supports_returning?(:insert)
488
- ds.returning(oauth_applications_id_column).insert(create_params)
489
- else
490
- id = db[oauth_applications_table].insert(create_params)
491
- db[oauth_applications_table].where(oauth_applications_id_column => id).get(oauth_applications_id_column)
492
- end
493
- false
648
+ id = db[oauth_applications_table].insert(create_params)
649
+ false
494
650
  rescue Sequel::ConstraintViolation => e
495
651
  e
496
- end
652
+ end
497
653
 
498
654
  if raised
499
655
  field = raised.message[/\.(.*)$/, 1]
@@ -509,19 +665,24 @@ module Rodauth
509
665
  end
510
666
 
511
667
  # Authorize
668
+ def before_authorize
669
+ require_account
670
+ end
512
671
 
513
672
  def validate_oauth_grant_params
673
+ redirect_response_error("invalid_request", request.referer || default_redirect) unless oauth_application && check_valid_redirect_uri?
674
+
514
675
  unless oauth_application && check_valid_redirect_uri? && check_valid_access_type? &&
515
676
  check_valid_approval_prompt? && check_valid_response_type?
516
677
  redirect_response_error("invalid_request")
517
678
  end
518
679
  redirect_response_error("invalid_scope") unless check_valid_scopes?
519
680
 
520
- validate_pkce_challenge_params
681
+ validate_pkce_challenge_params if use_oauth_pkce?
521
682
  end
522
683
 
523
684
  def try_approval_prompt
524
- approval_prompt = param_or_nil(approval_prompt_param)
685
+ approval_prompt = param_or_nil("approval_prompt")
525
686
 
526
687
  return unless approval_prompt && approval_prompt == "auto"
527
688
 
@@ -529,7 +690,7 @@ module Rodauth
529
690
  oauth_grants_account_id_column => account_id,
530
691
  oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
531
692
  oauth_grants_redirect_uri_column => redirect_uri,
532
- oauth_grants_scopes_column => scopes.join(","),
693
+ oauth_grants_scopes_column => scopes.join(oauth_scope_separator),
533
694
  oauth_grants_access_type_column => "online"
534
695
  ).count.zero?
535
696
 
@@ -538,197 +699,262 @@ module Rodauth
538
699
  request.env["REQUEST_METHOD"] = "POST"
539
700
  end
540
701
 
541
- def create_oauth_grant
542
- create_params = {
702
+ def create_oauth_grant(create_params = {})
703
+ create_params.merge!(
543
704
  oauth_grants_account_id_column => account_id,
544
705
  oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
545
706
  oauth_grants_redirect_uri_column => redirect_uri,
546
- oauth_grants_code_column => oauth_unique_id_generator,
547
707
  oauth_grants_expires_in_column => Time.now + oauth_grant_expires_in,
548
- oauth_grants_scopes_column => scopes.join(",")
549
- }
708
+ oauth_grants_scopes_column => scopes.join(oauth_scope_separator)
709
+ )
550
710
 
551
- if (access_type = param_or_nil(access_type_param))
552
- create_params[oauth_grants_access_type_column] = access_type
711
+ # Access Type flow
712
+ if use_oauth_access_type?
713
+ if (access_type = param_or_nil("access_type"))
714
+ create_params[oauth_grants_access_type_column] = access_type
715
+ end
553
716
  end
554
717
 
555
718
  # PKCE flow
556
- if (code_challenge = param_or_nil(code_challenge_param))
557
- code_challenge_method = param_or_nil(code_challenge_method_param)
719
+ if use_oauth_pkce?
558
720
 
559
- create_params[oauth_grants_code_challenge_column] = code_challenge
560
- create_params[oauth_grants_code_challenge_method_column] = code_challenge_method
561
- elsif oauth_require_pkce
562
- redirect_response_error("code_challenge_required")
721
+ if (code_challenge = param_or_nil("code_challenge"))
722
+ code_challenge_method = param_or_nil("code_challenge_method")
723
+
724
+ create_params[oauth_grants_code_challenge_column] = code_challenge
725
+ create_params[oauth_grants_code_challenge_method_column] = code_challenge_method
726
+ elsif oauth_require_pkce
727
+ redirect_response_error("code_challenge_required")
728
+ end
563
729
  end
564
730
 
565
731
  ds = db[oauth_grants_table]
566
732
 
567
733
  begin
568
- if ds.supports_returning?(:insert)
569
- ds.returning(authorize_code_column).insert(create_params)
570
- else
571
- id = ds.insert(create_params)
572
- ds.where(oauth_grants_id_column => id).get(oauth_grants_code_column)
573
- end
734
+ authorization_code = oauth_unique_id_generator
735
+ create_params[oauth_grants_code_column] = authorization_code
736
+ ds.insert(create_params)
737
+ authorization_code
574
738
  rescue Sequel::UniqueConstraintViolation
575
739
  retry
576
740
  end
577
741
  end
578
742
 
579
- # Access Tokens
743
+ def do_authorize(redirect_url, query_params = [], fragment_params = [])
744
+ case param("response_type")
745
+ when "token"
746
+ redirect_response_error("invalid_request") unless use_oauth_implicit_grant_type?
580
747
 
581
- def validate_oauth_token_params
582
- redirect_response_error("invalid_request") unless param_or_nil(client_id_param)
748
+ fragment_params.replace(_do_authorize_token.map { |k, v| "#{k}=#{v}" })
749
+ when "code", "", nil
750
+ query_params.replace(_do_authorize_code.map { |k, v| "#{k}=#{v}" })
751
+ end
583
752
 
584
- unless param_or_nil(client_secret_param)
585
- redirect_response_error("invalid_request") unless param_or_nil(code_verifier_param)
753
+ if param_or_nil("state")
754
+ if !fragment_params.empty?
755
+ fragment_params << "state=#{param('state')}"
756
+ else
757
+ query_params << "state=#{param('state')}"
758
+ end
586
759
  end
587
760
 
588
- unless (grant_type = param_or_nil(grant_type_param))
761
+ query_params << redirect_url.query if redirect_url.query
762
+
763
+ redirect_url.query = query_params.join("&") unless query_params.empty?
764
+ redirect_url.fragment = fragment_params.join("&") unless fragment_params.empty?
765
+ end
766
+
767
+ def _do_authorize_code
768
+ { "code" => create_oauth_grant }
769
+ end
770
+
771
+ def _do_authorize_token
772
+ create_params = {
773
+ oauth_tokens_account_id_column => account_id,
774
+ oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
775
+ oauth_tokens_scopes_column => scopes
776
+ }
777
+ oauth_token = generate_oauth_token(create_params, false)
778
+
779
+ json_access_token_payload(oauth_token)
780
+ end
781
+
782
+ # Access Tokens
783
+
784
+ def before_token
785
+ require_oauth_application
786
+ end
787
+
788
+ def validate_oauth_token_params
789
+ unless (grant_type = param_or_nil("grant_type"))
589
790
  redirect_response_error("invalid_request")
590
791
  end
591
792
 
592
793
  case grant_type
593
794
  when "authorization_code"
594
- redirect_response_error("invalid_request") unless param_or_nil(code_param)
795
+ redirect_response_error("invalid_request") unless param_or_nil("code")
595
796
 
596
797
  when "refresh_token"
597
- redirect_response_error("invalid_request") unless param_or_nil(refresh_token_param)
798
+ redirect_response_error("invalid_request") unless param_or_nil("refresh_token")
598
799
  else
599
800
  redirect_response_error("invalid_request")
600
801
  end
601
802
  end
602
803
 
603
804
  def create_oauth_token
604
- oauth_application = db[oauth_applications_table].where(
605
- oauth_applications_client_id_column => param(client_id_param)
606
- ).first
607
-
608
- redirect_response_error("invalid_request") unless oauth_application
609
-
610
- if (client_secret = param_or_nil(client_secret_param))
611
- redirect_response_error("invalid_request") unless secret_matches?(oauth_application, client_secret)
612
- end
613
-
614
- case param(grant_type_param)
805
+ case param("grant_type")
615
806
  when "authorization_code"
616
-
617
807
  # fetch oauth grant
618
808
  oauth_grant = db[oauth_grants_table].where(
619
- oauth_grants_code_column => param(code_param),
620
- oauth_grants_redirect_uri_column => param(redirect_uri_param),
809
+ oauth_grants_code_column => param("code"),
810
+ oauth_grants_redirect_uri_column => param("redirect_uri"),
621
811
  oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
622
812
  oauth_grants_revoked_at_column => nil
623
813
  ).where(Sequel[oauth_grants_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
814
+ .for_update
624
815
  .first
625
816
 
626
817
  redirect_response_error("invalid_grant") unless oauth_grant
627
818
 
628
- # PKCE
629
- if oauth_grant[oauth_grants_code_challenge_column]
630
- code_verifier = param_or_nil(code_verifier_param)
631
-
632
- unless code_verifier && check_valid_grant_challenge?(oauth_grant, code_verifier)
633
- redirect_response_error("invalid_request")
634
- end
635
- elsif oauth_require_pkce
636
- redirect_response_error("code_challenge_required")
637
- end
638
-
639
819
  create_params = {
640
820
  oauth_tokens_account_id_column => oauth_grant[oauth_grants_account_id_column],
641
821
  oauth_tokens_oauth_application_id_column => oauth_grant[oauth_grants_oauth_application_id_column],
642
822
  oauth_tokens_oauth_grant_id_column => oauth_grant[oauth_grants_id_column],
643
823
  oauth_tokens_scopes_column => oauth_grant[oauth_grants_scopes_column]
644
824
  }
645
-
646
- # revoke oauth grant
647
- db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
648
- .update(oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
649
-
650
- generate_oauth_token(create_params, oauth_grant[oauth_grants_access_type_column] == "offline")
651
-
825
+ create_oauth_token_from_authorization_code(oauth_grant, create_params)
652
826
  when "refresh_token"
653
827
  # fetch oauth token
654
- oauth_token = oauth_token_by_refresh_token(param(refresh_token_param)).where(
655
- oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column]
656
- ).where(oauth_grants_revoked_at_column => nil).first
828
+ oauth_token = oauth_token_by_refresh_token(param("refresh_token"))
657
829
 
658
830
  redirect_response_error("invalid_grant") unless oauth_token
659
831
 
660
- token = oauth_unique_id_generator
661
-
662
832
  update_params = {
663
833
  oauth_tokens_oauth_application_id_column => oauth_token[oauth_grants_oauth_application_id_column],
664
834
  oauth_tokens_expires_in_column => Time.now + oauth_token_expires_in
665
835
  }
836
+ create_oauth_token_from_token(oauth_token, update_params)
837
+ else
838
+ redirect_response_error("invalid_grant")
839
+ end
840
+ end
666
841
 
667
- if oauth_tokens_token_hash_column
668
- update_params[oauth_tokens_token_hash_column] = generate_token_hash(token)
669
- else
670
- update_params[oauth_tokens_token_column] = token
842
+ def create_oauth_token_from_authorization_code(oauth_grant, create_params)
843
+ # PKCE
844
+ if use_oauth_pkce?
845
+ if oauth_grant[oauth_grants_code_challenge_column]
846
+ code_verifier = param_or_nil("code_verifier")
847
+
848
+ redirect_response_error("invalid_request") unless code_verifier && check_valid_grant_challenge?(oauth_grant, code_verifier)
849
+ elsif oauth_require_pkce
850
+ redirect_response_error("code_challenge_required")
671
851
  end
852
+ end
672
853
 
673
- ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
854
+ # revoke oauth grant
855
+ db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
856
+ .update(oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
674
857
 
675
- oauth_token = begin
676
- if ds.supports_returning?(:update)
677
- ds.returning.update(update_params)
678
- else
679
- ds.update(update_params)
680
- ds.first
681
- end
682
- rescue Sequel::UniqueConstraintViolation
683
- retry
684
- end
858
+ should_generate_refresh_token = !use_oauth_access_type? ||
859
+ oauth_grant[oauth_grants_access_type_column] == "offline"
860
+
861
+ generate_oauth_token(create_params, should_generate_refresh_token)
862
+ end
863
+
864
+ def create_oauth_token_from_token(oauth_token, update_params)
865
+ redirect_response_error("invalid_grant") unless token_from_application?(oauth_token, oauth_application)
685
866
 
686
- oauth_token[oauth_tokens_token_column] = token
687
- oauth_token
867
+ token = oauth_unique_id_generator
868
+
869
+ if oauth_tokens_token_hash_column
870
+ update_params[oauth_tokens_token_hash_column] = generate_token_hash(token)
688
871
  else
689
- redirect_response_error("invalid_grant")
872
+ update_params[oauth_tokens_token_column] = token
873
+ end
874
+
875
+ ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
876
+
877
+ oauth_token = begin
878
+ if ds.supports_returning?(:update)
879
+ ds.returning.update(update_params).first
880
+ else
881
+ ds.update(update_params)
882
+ ds.first
883
+ end
884
+ rescue Sequel::UniqueConstraintViolation
885
+ retry
886
+ end
887
+
888
+ oauth_token[oauth_tokens_token_column] = token
889
+ oauth_token
890
+ end
891
+
892
+ TOKEN_HINT_TYPES = %w[access_token refresh_token].freeze
893
+
894
+ # Token introspect
895
+
896
+ def validate_oauth_introspect_params
897
+ # check if valid token hint type
898
+ if param_or_nil("token_type_hint")
899
+ redirect_response_error("unsupported_token_type") unless TOKEN_HINT_TYPES.include?(param("token_type_hint"))
690
900
  end
901
+
902
+ redirect_response_error("invalid_request") unless param_or_nil("token")
903
+ end
904
+
905
+ def json_token_introspect_payload(token)
906
+ return { active: false } unless token
907
+
908
+ {
909
+ active: true,
910
+ scope: token[oauth_tokens_scopes_column],
911
+ client_id: oauth_application[oauth_applications_client_id_column],
912
+ # username
913
+ token_type: oauth_token_type
914
+ }
691
915
  end
692
916
 
917
+ def before_introspect; end
918
+
693
919
  # Token revocation
694
920
 
695
- TOKEN_HINT_TYPES = %w[access_token refresh_token].freeze
921
+ def before_revoke
922
+ require_oauth_application
923
+ end
696
924
 
697
925
  def validate_oauth_revoke_params
698
926
  # check if valid token hint type
699
- redirect_response_error("unsupported_token_type") unless TOKEN_HINT_TYPES.include?(token_type_hint)
927
+ if param_or_nil("token_type_hint")
928
+ redirect_response_error("unsupported_token_type") unless TOKEN_HINT_TYPES.include?(param("token_type_hint"))
929
+ end
700
930
 
701
- redirect_response_error("invalid_request") unless param(token_param)
931
+ redirect_response_error("invalid_request") unless param_or_nil("token")
702
932
  end
703
933
 
704
934
  def revoke_oauth_token
705
- ds = case token_type_hint
706
- when "access_token"
707
- oauth_token_by_token(token)
708
- when "refresh_token"
709
- oauth_token_by_refresh_token(token)
710
- end
711
- # one can only revoke tokens which haven't been revoked before, and which are
712
- # either our tokens, or tokens from applications we own.
713
- oauth_token = ds.where(oauth_tokens_revoked_at_column => nil)
714
- .where(
715
- Sequel.or(
716
- oauth_tokens_account_id_column => account_id,
717
- oauth_tokens_oauth_application_id_column => db[oauth_applications_table].where(
718
- oauth_applications_client_id_column => param(client_id_param),
719
- oauth_applications_account_id_column => account_id
720
- ).select(oauth_applications_id_column)
721
- )
722
- ).first
935
+ token = param("token")
936
+
937
+ oauth_token = if param("token_type_hint") == "refresh_token"
938
+ oauth_token_by_refresh_token(token)
939
+ else
940
+ oauth_token_by_token(token)
941
+ end
723
942
 
724
943
  redirect_response_error("invalid_request") unless oauth_token
725
944
 
945
+ if oauth_application
946
+ redirect_response_error("invalid_request") unless token_from_application?(oauth_token, oauth_application)
947
+ else
948
+ @oauth_application = db[oauth_applications_table].where(oauth_applications_id_column =>
949
+ oauth_token[oauth_tokens_oauth_application_id_column]).first
950
+ end
951
+
726
952
  update_params = { oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP }
727
953
 
728
954
  ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
729
955
 
730
956
  oauth_token = if ds.supports_returning?(:update)
731
- ds.returning.update(update_params)
957
+ ds.returning.update(update_params).first
732
958
  else
733
959
  ds.update(update_params)
734
960
  ds.first
@@ -748,8 +974,8 @@ module Rodauth
748
974
 
749
975
  # Response helpers
750
976
 
751
- def redirect_response_error(error_code, redirect_url = request.referer || default_redirect)
752
- if json_request?
977
+ def redirect_response_error(error_code, redirect_url = redirect_uri || request.referer || default_redirect)
978
+ if accepts_json?
753
979
  throw_json_response_error(invalid_oauth_response_status, error_code)
754
980
  else
755
981
  redirect_url = URI.parse(redirect_url)
@@ -772,6 +998,14 @@ module Rodauth
772
998
  end
773
999
  end
774
1000
 
1001
+ def json_response_success(body)
1002
+ response.status = 200
1003
+ response["Content-Type"] ||= json_response_content_type
1004
+ json_payload = _json_response_body(body)
1005
+ response.write(json_payload)
1006
+ request.halt
1007
+ end
1008
+
775
1009
  def throw_json_response_error(status, error_code)
776
1010
  set_response_error_status(status)
777
1011
  code = if respond_to?(:"#{error_code}_error_code")
@@ -783,12 +1017,13 @@ module Rodauth
783
1017
  payload["error_description"] = send(:"#{error_code}_message") if respond_to?(:"#{error_code}_message")
784
1018
  json_payload = _json_response_body(payload)
785
1019
  response["Content-Type"] ||= json_response_content_type
786
- response["WWW-Authenticate"] = "Bearer" if status == 401
1020
+ response["WWW-Authenticate"] = oauth_token_type.upcase if status == 401
787
1021
  response.write(json_payload)
788
1022
  request.halt
789
1023
  end
790
1024
 
791
1025
  unless method_defined?(:_json_response_body)
1026
+ # :nocov:
792
1027
  def _json_response_body(hash)
793
1028
  if request.respond_to?(:convert_to_json)
794
1029
  request.send(:convert_to_json, hash)
@@ -796,47 +1031,56 @@ module Rodauth
796
1031
  JSON.dump(hash)
797
1032
  end
798
1033
  end
1034
+ # :nocov:
799
1035
  end
800
1036
 
801
1037
  def authorization_required
802
- if json_request?
1038
+ if accepts_json?
803
1039
  throw_json_response_error(authorization_required_error_status, "invalid_client")
804
1040
  else
805
1041
  set_redirect_error_flash(require_authorization_error_flash)
806
- redirect(require_authorization_redirect)
1042
+ redirect(authorize_path)
807
1043
  end
808
1044
  end
809
1045
 
1046
+ def check_valid_uri?(uri)
1047
+ URI::DEFAULT_PARSER.make_regexp(oauth_valid_uri_schemes).match?(uri)
1048
+ end
1049
+
810
1050
  def check_valid_scopes?
811
1051
  return false unless scopes
812
1052
 
813
- (scopes - oauth_application[oauth_applications_scopes_column].split(",")).empty?
1053
+ (scopes - oauth_application[oauth_applications_scopes_column].split(oauth_scope_separator)).empty?
814
1054
  end
815
1055
 
816
1056
  def check_valid_redirect_uri?
817
- redirect_uri == oauth_application[oauth_applications_redirect_uri_column]
1057
+ oauth_application[oauth_applications_redirect_uri_column].split(" ").include?(redirect_uri)
818
1058
  end
819
1059
 
820
1060
  ACCESS_TYPES = %w[offline online].freeze
821
1061
 
822
1062
  def check_valid_access_type?
823
- access_type = param_or_nil(access_type_param)
1063
+ return true unless use_oauth_access_type?
1064
+
1065
+ access_type = param_or_nil("access_type")
824
1066
  !access_type || ACCESS_TYPES.include?(access_type)
825
1067
  end
826
1068
 
827
1069
  APPROVAL_PROMPTS = %w[force auto].freeze
828
1070
 
829
1071
  def check_valid_approval_prompt?
830
- approval_prompt = param_or_nil(approval_prompt_param)
1072
+ return true unless use_oauth_access_type?
1073
+
1074
+ approval_prompt = param_or_nil("approval_prompt")
831
1075
  !approval_prompt || APPROVAL_PROMPTS.include?(approval_prompt)
832
1076
  end
833
1077
 
834
1078
  def check_valid_response_type?
835
- response_type = param_or_nil(response_type_param)
1079
+ response_type = param_or_nil("response_type")
836
1080
 
837
1081
  return true if response_type.nil? || response_type == "code"
838
1082
 
839
- return use_oauth_implicit_grant_type if response_type == "token"
1083
+ return use_oauth_implicit_grant_type? if response_type == "token"
840
1084
 
841
1085
  false
842
1086
  end
@@ -844,9 +1088,9 @@ module Rodauth
844
1088
  # PKCE
845
1089
 
846
1090
  def validate_pkce_challenge_params
847
- if param_or_nil(code_challenge_param)
1091
+ if param_or_nil("code_challenge")
848
1092
 
849
- challenge_method = param_or_nil(code_challenge_method_param)
1093
+ challenge_method = param_or_nil("code_challenge_method")
850
1094
  redirect_response_error("code_challenge_required") unless oauth_pkce_challenge_method == challenge_method
851
1095
  else
852
1096
  return unless oauth_require_pkce
@@ -871,121 +1115,149 @@ module Rodauth
871
1115
  end
872
1116
  end
873
1117
 
874
- # /oauth-token
875
- route(:oauth_token) do |r|
1118
+ # Server metadata
1119
+
1120
+ def oauth_server_metadata_body(path)
1121
+ issuer = base_url
1122
+ issuer += "/#{path}" if path
1123
+
1124
+ responses_supported = %w[code]
1125
+ response_modes_supported = %w[query]
1126
+ grant_types_supported = %w[authorization_code]
1127
+
1128
+ if use_oauth_implicit_grant_type?
1129
+ responses_supported << "token"
1130
+ response_modes_supported << "fragment"
1131
+ grant_types_supported << "implicit"
1132
+ end
1133
+ {
1134
+ issuer: issuer,
1135
+ authorization_endpoint: authorize_url,
1136
+ token_endpoint: token_url,
1137
+ registration_endpoint: "#{base_url}/#{oauth_applications_path}",
1138
+ scopes_supported: oauth_application_scopes,
1139
+ response_types_supported: responses_supported,
1140
+ response_modes_supported: response_modes_supported,
1141
+ grant_types_supported: grant_types_supported,
1142
+ token_endpoint_auth_methods_supported: %w[client_secret_basic client_secret_post],
1143
+ service_documentation: oauth_metadata_service_documentation,
1144
+ ui_locales_supported: oauth_metadata_ui_locales_supported,
1145
+ op_policy_uri: oauth_metadata_op_policy_uri,
1146
+ op_tos_uri: oauth_metadata_op_tos_uri,
1147
+ revocation_endpoint: revoke_url,
1148
+ revocation_endpoint_auth_methods_supported: nil, # because it's client_secret_basic
1149
+ introspection_endpoint: introspect_url,
1150
+ introspection_endpoint_auth_methods_supported: %w[client_secret_basic],
1151
+ code_challenge_methods_supported: (use_oauth_pkce? ? oauth_pkce_challenge_method : nil)
1152
+ }
1153
+ end
1154
+
1155
+ # /token
1156
+ route(:token) do |r|
1157
+ next unless is_authorization_server?
1158
+
1159
+ before_token
1160
+
876
1161
  r.post do
877
1162
  catch_error do
878
1163
  validate_oauth_token_params
879
1164
 
880
1165
  oauth_token = nil
881
1166
  transaction do
882
- before_token
883
1167
  oauth_token = create_oauth_token
884
- after_token
885
1168
  end
886
1169
 
887
- response.status = 200
888
- response["Content-Type"] ||= json_response_content_type
889
- json_response = {
890
- "token" => oauth_token[oauth_tokens_token_column],
891
- "token_type" => oauth_token_type,
892
- "expires_in" => oauth_token_expires_in
893
- }
1170
+ json_response_success(json_access_token_payload(oauth_token))
1171
+ end
1172
+
1173
+ throw_json_response_error(invalid_oauth_response_status, "invalid_request")
1174
+ end
1175
+ end
1176
+
1177
+ # /introspect
1178
+ route(:introspect) do |r|
1179
+ next unless is_authorization_server?
1180
+
1181
+ before_introspect
1182
+
1183
+ r.post do
1184
+ catch_error do
1185
+ validate_oauth_introspect_params
894
1186
 
895
- json_response["refresh_token"] = oauth_token[oauth_tokens_refresh_token_column] if oauth_token[:refresh_token]
1187
+ oauth_token = case param("token_type_hint")
1188
+ when "access_token"
1189
+ oauth_token_by_token(param("token"))
1190
+ when "refresh_token"
1191
+ oauth_token_by_refresh_token(param("token"))
1192
+ else
1193
+ oauth_token_by_token(param("token")) || oauth_token_by_refresh_token(param("token"))
1194
+ end
896
1195
 
897
- json_payload = _json_response_body(json_response)
898
- response.write(json_payload)
899
- request.halt
1196
+ if oauth_application
1197
+ redirect_response_error("invalid_request") if oauth_token && !token_from_application?(oauth_token, oauth_application)
1198
+ elsif oauth_token
1199
+ @oauth_application = db[oauth_applications_table].where(oauth_applications_id_column =>
1200
+ oauth_token[oauth_tokens_oauth_application_id_column]).first
1201
+ end
1202
+
1203
+ json_response_success(json_token_introspect_payload(oauth_token))
900
1204
  end
901
1205
 
902
1206
  throw_json_response_error(invalid_oauth_response_status, "invalid_request")
903
1207
  end
904
1208
  end
905
1209
 
906
- # /oauth-revoke
907
- route(:oauth_revoke) do |r|
908
- require_account
1210
+ # /revoke
1211
+ route(:revoke) do |r|
1212
+ next unless is_authorization_server?
1213
+
1214
+ before_revoke
909
1215
 
910
- # access-token
911
1216
  r.post do
912
1217
  catch_error do
913
1218
  validate_oauth_revoke_params
914
1219
 
915
1220
  oauth_token = nil
916
1221
  transaction do
917
- before_revoke
918
1222
  oauth_token = revoke_oauth_token
919
1223
  after_revoke
920
1224
  end
921
1225
 
922
- if json_request?
923
- response.status = 200
924
- response["Content-Type"] ||= json_response_content_type
925
- json_response = {
1226
+ if accepts_json?
1227
+ json_response_success \
926
1228
  "token" => oauth_token[oauth_tokens_token_column],
927
1229
  "refresh_token" => oauth_token[oauth_tokens_refresh_token_column],
928
1230
  "revoked_at" => oauth_token[oauth_tokens_revoked_at_column]
929
- }
930
- json_payload = _json_response_body(json_response)
931
- response.write(json_payload)
932
- request.halt
933
1231
  else
934
1232
  set_notice_flash revoke_oauth_token_notice_flash
935
1233
  redirect request.referer || "/"
936
1234
  end
937
1235
  end
938
1236
 
939
- throw_json_response_error(invalid_oauth_response_status, "invalid_request")
1237
+ redirect_response_error("invalid_request", request.referer || "/")
940
1238
  end
941
1239
  end
942
1240
 
943
- # /oauth-authorize
944
- route(:oauth_authorize) do |r|
1241
+ # /authorize
1242
+ route(:authorize) do |r|
1243
+ next unless is_authorization_server?
1244
+
945
1245
  require_account
946
1246
  validate_oauth_grant_params
947
- try_approval_prompt if request.get?
1247
+ try_approval_prompt if use_oauth_access_type? && request.get?
1248
+
1249
+ before_authorize
948
1250
 
949
1251
  r.get do
950
1252
  authorize_view
951
1253
  end
952
1254
 
953
1255
  r.post do
954
- code = nil
955
- query_params = []
956
- fragment_params = []
1256
+ redirect_url = URI.parse(redirect_uri)
957
1257
 
958
1258
  transaction do
959
- before_authorize
960
- case param(response_type_param)
961
- when "token"
962
- redirect_response_error("invalid_request", redirect_uri) unless use_oauth_implicit_grant_type
963
-
964
- create_params = {
965
- oauth_tokens_account_id_column => account_id,
966
- oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
967
- oauth_tokens_scopes_column => scopes
968
- }
969
- oauth_token = generate_oauth_token(create_params, false)
970
-
971
- fragment_params << ["access_token=#{oauth_token[oauth_tokens_token_column]}"]
972
- fragment_params << ["token_type=#{oauth_token_type}"]
973
- fragment_params << ["expires_in=#{oauth_token_expires_in}"]
974
- when "code", "", nil
975
- code = create_oauth_grant
976
- query_params << ["code=#{code}"]
977
- else
978
- redirect_response_error("invalid_request")
979
- end
980
- after_authorize
1259
+ do_authorize(redirect_url)
981
1260
  end
982
-
983
- redirect_url = URI.parse(redirect_uri)
984
- query_params << "state=#{state}" if state
985
- query_params << redirect_url.query if redirect_url.query
986
- redirect_url.query = query_params.join("&") unless query_params.empty?
987
- redirect_url.fragment = fragment_params.join("&") unless fragment_params.empty?
988
-
989
1261
  redirect(redirect_url.to_s)
990
1262
  end
991
1263
  end