rodauth-oauth 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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