rodauth-oauth 0.0.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,16 +1,21 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require "time"
3
4
  require "base64"
4
5
  require "securerandom"
5
6
  require "net/http"
6
7
 
7
8
  require "rodauth/oauth/ttl_store"
9
+ require "rodauth/oauth/database_extensions"
8
10
 
9
11
  module Rodauth
10
12
  Feature.define(:oauth) do
11
13
  # RUBY EXTENSIONS
12
- # :nocov:
13
14
  unless Regexp.method_defined?(:match?)
15
+ # If you wonder why this is there: the oauth feature uses a refinement to enhance the
16
+ # Regexp class locally with #match? , but this is never tested, because ActiveSupport
17
+ # monkey-patches the same method... Please ActiveSupport, stop being so intrusive!
18
+ # :nocov:
14
19
  module RegexpExtensions
15
20
  refine(Regexp) do
16
21
  def match?(*args)
@@ -19,6 +24,7 @@ module Rodauth
19
24
  end
20
25
  end
21
26
  using(RegexpExtensions)
27
+ # :nocov:
22
28
  end
23
29
 
24
30
  unless String.method_defined?(:delete_suffix!)
@@ -37,13 +43,13 @@ module Rodauth
37
43
  end
38
44
  using(SuffixExtensions)
39
45
  end
40
- # :nocov:
41
46
 
42
47
  SCOPES = %w[profile.read].freeze
43
48
 
49
+ SERVER_METADATA = OAuth::TtlStore.new
50
+
44
51
  before "authorize"
45
52
  after "authorize"
46
- after "authorize_failure"
47
53
 
48
54
  before "token"
49
55
 
@@ -55,15 +61,13 @@ module Rodauth
55
61
  before "create_oauth_application"
56
62
  after "create_oauth_application"
57
63
 
58
- error_flash "OAuth Authorization invalid parameters", "oauth_grant_valid_parameters"
59
-
60
64
  error_flash "Please authorize to continue", "require_authorization"
61
65
  error_flash "There was an error registering your oauth application", "create_oauth_application"
62
66
  notice_flash "Your oauth application has been registered", "create_oauth_application"
63
67
 
64
68
  notice_flash "The oauth token has been revoked", "revoke_oauth_token"
65
69
 
66
- view "oauth_authorize", "Authorize", "authorize"
70
+ view "authorize", "Authorize", "authorize"
67
71
  view "oauth_applications", "Oauth Applications", "oauth_applications"
68
72
  view "oauth_application", "Oauth Application", "oauth_application"
69
73
  view "new_oauth_application", "New Oauth Application", "new_oauth_application"
@@ -73,14 +77,16 @@ module Rodauth
73
77
 
74
78
  auth_value_method :oauth_grant_expires_in, 60 * 5 # 5 minutes
75
79
  auth_value_method :oauth_token_expires_in, 60 * 60 # 60 minutes
80
+ auth_value_method :oauth_refresh_token_expires_in, 60 * 60 * 24 * 360 # 1 year
76
81
  auth_value_method :use_oauth_implicit_grant_type?, false
77
82
  auth_value_method :use_oauth_pkce?, true
78
83
  auth_value_method :use_oauth_access_type?, true
79
84
 
80
85
  auth_value_method :oauth_require_pkce, false
81
86
  auth_value_method :oauth_pkce_challenge_method, "S256"
87
+ auth_value_method :oauth_response_mode, "query"
82
88
 
83
- auth_value_method :oauth_valid_uri_schemes, %w[http https]
89
+ auth_value_method :oauth_valid_uri_schemes, %w[https]
84
90
 
85
91
  auth_value_method :oauth_scope_separator, " "
86
92
 
@@ -95,6 +101,7 @@ module Rodauth
95
101
  button "Register", "oauth_application"
96
102
  button "Authorize", "oauth_authorize"
97
103
  button "Revoke", "oauth_token_revoke"
104
+ button "Back to Client Application", "oauth_authorize_post"
98
105
 
99
106
  # OAuth Token
100
107
  auth_value_method :oauth_tokens_path, "oauth-tokens"
@@ -113,6 +120,8 @@ module Rodauth
113
120
  auth_value_method :oauth_tokens_token_hash_column, nil
114
121
  auth_value_method :oauth_tokens_refresh_token_hash_column, nil
115
122
 
123
+ # Access Token reuse
124
+ auth_value_method :oauth_reuse_access_token, false
116
125
  # OAuth Grants
117
126
  auth_value_method :oauth_grants_table, :oauth_grants
118
127
  auth_value_method :oauth_grants_id_column, :id
@@ -127,6 +136,7 @@ module Rodauth
127
136
 
128
137
  auth_value_method :authorization_required_error_status, 401
129
138
  auth_value_method :invalid_oauth_response_status, 400
139
+ auth_value_method :already_in_use_response_status, 409
130
140
 
131
141
  # OAuth Applications
132
142
  auth_value_method :oauth_applications_path, "oauth-applications"
@@ -144,13 +154,13 @@ module Rodauth
144
154
  auth_value_method :"oauth_applications_#{column}_column", column
145
155
  end
146
156
 
157
+ # Feature options
147
158
  auth_value_method :oauth_application_default_scope, SCOPES.first
148
159
  auth_value_method :oauth_application_scopes, SCOPES
149
160
  auth_value_method :oauth_token_type, "bearer"
161
+ auth_value_method :oauth_refresh_token_protection_policy, "none" # can be: none, sender_constrained, rotation
150
162
 
151
- auth_value_method :invalid_request, "Request is missing a required parameter"
152
- auth_value_method :invalid_client, "Invalid client"
153
- auth_value_method :unauthorized_client, "Unauthorized client"
163
+ auth_value_method :invalid_client_message, "Invalid client"
154
164
  auth_value_method :invalid_grant_type_message, "Invalid grant type"
155
165
  auth_value_method :invalid_grant_message, "Invalid grant"
156
166
  auth_value_method :invalid_scope_message, "Invalid scope"
@@ -160,6 +170,8 @@ module Rodauth
160
170
 
161
171
  auth_value_method :unique_error_message, "is already in use"
162
172
  auth_value_method :null_error_message, "is not filled"
173
+ auth_value_method :already_in_use_message, "error generating unique token"
174
+ auth_value_method :already_in_use_error_code, "invalid_request"
163
175
 
164
176
  # PKCE
165
177
  auth_value_method :code_challenge_required_error_code, "invalid_request"
@@ -177,6 +189,8 @@ module Rodauth
177
189
  # Only required to use if the plugin is to be used in a resource server
178
190
  auth_value_method :is_authorization_server?, true
179
191
 
192
+ auth_value_method :oauth_unique_id_generation_retries, 3
193
+
180
194
  auth_value_methods(
181
195
  :fetch_access_token,
182
196
  :oauth_unique_id_generator,
@@ -184,36 +198,231 @@ module Rodauth
184
198
  :secret_hash,
185
199
  :generate_token_hash,
186
200
  :authorization_server_url,
187
- :before_introspection_request
201
+ :before_introspection_request,
202
+ :require_authorizable_account,
203
+ :oauth_tokens_unique_columns
188
204
  )
189
205
 
190
206
  auth_value_methods(:only_json?)
191
207
 
192
- redirect(:oauth_application) do |id|
193
- "/#{oauth_applications_path}/#{id}"
208
+ auth_value_method :json_request_regexp, %r{\bapplication/(?:vnd\.api\+)?json\b}i
209
+
210
+ # /token
211
+ route(:token) do |r|
212
+ next unless is_authorization_server?
213
+
214
+ before_token_route
215
+ require_oauth_application
216
+
217
+ r.post do
218
+ catch_error do
219
+ validate_oauth_token_params
220
+
221
+ oauth_token = nil
222
+ transaction do
223
+ before_token
224
+ oauth_token = create_oauth_token
225
+ end
226
+
227
+ json_response_success(json_access_token_payload(oauth_token))
228
+ end
229
+
230
+ throw_json_response_error(invalid_oauth_response_status, "invalid_request")
231
+ end
194
232
  end
195
233
 
196
- redirect(:require_authorization) do
197
- if logged_in?
198
- oauth_authorize_path
199
- elsif respond_to?(:login_redirect)
200
- login_redirect
201
- else
202
- default_redirect
234
+ # /introspect
235
+ route(:introspect) do |r|
236
+ next unless is_authorization_server?
237
+
238
+ before_introspect_route
239
+
240
+ r.post do
241
+ catch_error do
242
+ validate_oauth_introspect_params
243
+
244
+ before_introspect
245
+ oauth_token = case param("token_type_hint")
246
+ when "access_token"
247
+ oauth_token_by_token(param("token"))
248
+ when "refresh_token"
249
+ oauth_token_by_refresh_token(param("token"))
250
+ else
251
+ oauth_token_by_token(param("token")) || oauth_token_by_refresh_token(param("token"))
252
+ end
253
+
254
+ if oauth_application
255
+ redirect_response_error("invalid_request") if oauth_token && !token_from_application?(oauth_token, oauth_application)
256
+ elsif oauth_token
257
+ @oauth_application = db[oauth_applications_table].where(oauth_applications_id_column =>
258
+ oauth_token[oauth_tokens_oauth_application_id_column]).first
259
+ end
260
+
261
+ json_response_success(json_token_introspect_payload(oauth_token))
262
+ end
263
+
264
+ throw_json_response_error(invalid_oauth_response_status, "invalid_request")
203
265
  end
204
266
  end
205
267
 
206
- auth_value_method :json_request_regexp, %r{\bapplication/(?:vnd\.api\+)?json\b}i
268
+ # /revoke
269
+ route(:revoke) do |r|
270
+ next unless is_authorization_server?
207
271
 
208
- SERVER_METADATA = OAuth::TtlStore.new
272
+ before_revoke_route
273
+ require_oauth_application
274
+
275
+ r.post do
276
+ catch_error do
277
+ validate_oauth_revoke_params
278
+
279
+ oauth_token = nil
280
+ transaction do
281
+ before_revoke
282
+ oauth_token = revoke_oauth_token
283
+ after_revoke
284
+ end
285
+
286
+ if accepts_json?
287
+ json_response_success \
288
+ "token" => oauth_token[oauth_tokens_token_column],
289
+ "refresh_token" => oauth_token[oauth_tokens_refresh_token_column],
290
+ "revoked_at" => convert_timestamp(oauth_token[oauth_tokens_revoked_at_column])
291
+ else
292
+ set_notice_flash revoke_oauth_token_notice_flash
293
+ redirect request.referer || "/"
294
+ end
295
+ end
296
+
297
+ redirect_response_error("invalid_request", request.referer || "/")
298
+ end
299
+ end
300
+
301
+ # /authorize
302
+ route(:authorize) do |r|
303
+ next unless is_authorization_server?
304
+
305
+ before_authorize_route
306
+ require_authorizable_account
307
+
308
+ validate_oauth_grant_params
309
+ try_approval_prompt if use_oauth_access_type? && request.get?
310
+
311
+ r.get do
312
+ authorize_view
313
+ end
314
+
315
+ r.post do
316
+ redirect_url = URI.parse(redirect_uri)
317
+
318
+ params, mode = transaction do
319
+ before_authorize
320
+ do_authorize
321
+ end
322
+
323
+ case mode
324
+ when "query"
325
+ params = params.map { |k, v| "#{k}=#{v}" }
326
+ params << redirect_url.query if redirect_url.query
327
+ redirect_url.query = params.join("&")
328
+ redirect(redirect_url.to_s)
329
+ when "fragment"
330
+ params = params.map { |k, v| "#{k}=#{v}" }
331
+ params << redirect_url.query if redirect_url.query
332
+ redirect_url.fragment = params.join("&")
333
+ redirect(redirect_url.to_s)
334
+ when "form_post"
335
+ scope.view layout: false, inline: <<-FORM
336
+ <html>
337
+ <head><title>Authorized</title></head>
338
+ <body onload="javascript:document.forms[0].submit()">
339
+ <form method="post" action="#{redirect_uri}">
340
+ #{
341
+ params.map do |name, value|
342
+ "<input type=\"hidden\" name=\"#{name}\" value=\"#{scope.h(value)}\" />"
343
+ end.join
344
+ }
345
+ <input type="submit" class="btn btn-outline-primary" value="#{scope.h(oauth_authorize_post_button)}"/>
346
+ </form>
347
+ </body>
348
+ </html>
349
+ FORM
350
+ when "none"
351
+ redirect(redirect_url.to_s)
352
+ end
353
+ end
354
+ end
355
+
356
+ def oauth_server_metadata(issuer = nil)
357
+ request.on(".well-known") do
358
+ request.on("oauth-authorization-server") do
359
+ request.get do
360
+ json_response_success(oauth_server_metadata_body(issuer), true)
361
+ end
362
+ end
363
+ end
364
+ end
365
+
366
+ # /oauth-applications routes
367
+ def oauth_applications
368
+ request.on(oauth_applications_path) do
369
+ require_account
370
+
371
+ request.get "new" do
372
+ new_oauth_application_view
373
+ end
374
+
375
+ request.on(oauth_applications_id_pattern) do |id|
376
+ oauth_application = db[oauth_applications_table].where(oauth_applications_id_column => id).first
377
+ next unless oauth_application
378
+
379
+ scope.instance_variable_set(:@oauth_application, oauth_application)
380
+
381
+ request.is do
382
+ request.get do
383
+ oauth_application_view
384
+ end
385
+ end
386
+
387
+ request.on(oauth_tokens_path) do
388
+ oauth_tokens = db[oauth_tokens_table].where(oauth_tokens_oauth_application_id_column => id)
389
+ scope.instance_variable_set(:@oauth_tokens, oauth_tokens)
390
+ request.get do
391
+ oauth_tokens_view
392
+ end
393
+ end
394
+ end
395
+
396
+ request.get do
397
+ scope.instance_variable_set(:@oauth_applications, db[oauth_applications_table])
398
+ oauth_applications_view
399
+ end
400
+
401
+ request.post do
402
+ catch_error do
403
+ validate_oauth_application_params
404
+
405
+ transaction do
406
+ before_create_oauth_application
407
+ id = create_oauth_application
408
+ after_create_oauth_application
409
+ set_notice_flash create_oauth_application_notice_flash
410
+ redirect "#{request.path}/#{id}"
411
+ end
412
+ end
413
+ set_error_flash create_oauth_application_error_flash
414
+ new_oauth_application_view
415
+ end
416
+ end
417
+ end
209
418
 
210
419
  def check_csrf?
211
420
  case request.path
212
- when oauth_token_path, oauth_introspect_path
421
+ when token_path, introspect_path
213
422
  false
214
- when oauth_revoke_path
423
+ when revoke_path
215
424
  !json_request?
216
- when oauth_authorize_path, %r{/#{oauth_applications_path}}
425
+ when authorize_path, %r{/#{oauth_applications_path}}
217
426
  only_json? ? false : super
218
427
  else
219
428
  super
@@ -232,30 +441,28 @@ module Rodauth
232
441
  end
233
442
 
234
443
  unless method_defined?(:json_request?)
235
- # :nocov:
236
444
  # copied from the jwt feature
237
445
  def json_request?
238
446
  return @json_request if defined?(@json_request)
239
447
 
240
448
  @json_request = request.content_type =~ json_request_regexp
241
449
  end
242
- # :nocov:
243
450
  end
244
451
 
245
452
  def initialize(scope)
246
453
  @scope = scope
247
454
  end
248
455
 
249
- def state
250
- param_or_nil("state")
251
- end
252
-
253
456
  def scopes
254
- (param_or_nil("scope") || oauth_application_default_scope).split(" ")
255
- end
256
-
257
- def client_id
258
- param_or_nil("client_id")
457
+ scope = request.params["scope"]
458
+ case scope
459
+ when Array
460
+ scope
461
+ when String
462
+ scope.split(" ")
463
+ when nil
464
+ [oauth_application_default_scope]
465
+ end
259
466
  end
260
467
 
261
468
  def redirect_uri
@@ -267,14 +474,6 @@ module Rodauth
267
474
  end
268
475
  end
269
476
 
270
- def token_type_hint
271
- param_or_nil("token_type_hint") || "access_token"
272
- end
273
-
274
- def token
275
- param_or_nil("token")
276
- end
277
-
278
477
  def oauth_application
279
478
  return @oauth_application if defined?(@oauth_application)
280
479
 
@@ -296,6 +495,8 @@ module Rodauth
296
495
 
297
496
  return unless scheme.downcase == oauth_token_type
298
497
 
498
+ return if token.empty?
499
+
299
500
  token
300
501
  end
301
502
 
@@ -337,67 +538,46 @@ module Rodauth
337
538
  authorization_required unless scopes.any? { |scope| token_scopes.include?(scope) }
338
539
  end
339
540
 
340
- # /oauth-applications routes
341
- def oauth_applications
342
- request.on(oauth_applications_path) do
343
- require_account
541
+ def post_configure
542
+ super
543
+ self.class.__send__(:include, Rodauth::OAuth::ExtendDatabase(db))
344
544
 
345
- request.get "new" do
346
- new_oauth_application_view
347
- end
348
-
349
- request.on(oauth_applications_id_pattern) do |id|
350
- oauth_application = db[oauth_applications_table].where(oauth_applications_id_column => id).first
351
- scope.instance_variable_set(:@oauth_application, oauth_application)
352
-
353
- request.is do
354
- request.get do
355
- oauth_application_view
356
- end
357
- end
358
-
359
- request.on(oauth_tokens_path) do
360
- oauth_tokens = db[oauth_tokens_table].where(oauth_tokens_oauth_application_id_column => id)
361
- scope.instance_variable_set(:@oauth_tokens, oauth_tokens)
362
- oauth_tokens_view
363
- end
545
+ # Check whether we can reutilize db entries for the same account / application pair
546
+ one_oauth_token_per_account = begin
547
+ db.indexes(oauth_tokens_table).values.any? do |definition|
548
+ definition[:unique] &&
549
+ definition[:columns] == oauth_tokens_unique_columns
364
550
  end
551
+ end
552
+ self.class.send(:define_method, :__one_oauth_token_per_account) { one_oauth_token_per_account }
553
+ end
365
554
 
366
- request.get do
367
- scope.instance_variable_set(:@oauth_applications, db[:oauth_applications])
368
- oauth_applications_view
369
- end
555
+ def use_date_arithmetic?
556
+ true
557
+ end
370
558
 
371
- request.post do
372
- catch_error do
373
- validate_oauth_application_params
559
+ private
374
560
 
375
- transaction do
376
- before_create_oauth_application
377
- id = create_oauth_application
378
- after_create_oauth_application
379
- set_notice_flash create_oauth_application_notice_flash
380
- redirect oauth_application_redirect(id)
381
- end
382
- end
383
- set_error_flash create_oauth_application_error_flash
384
- new_oauth_application_view
385
- end
561
+ def rescue_from_uniqueness_error(&block)
562
+ retries = oauth_unique_id_generation_retries
563
+ begin
564
+ transaction(savepoint: :only, &block)
565
+ rescue Sequel::UniqueConstraintViolation
566
+ redirect_response_error("already_in_use") if retries.zero?
567
+ retries -= 1
568
+ retry
386
569
  end
387
570
  end
388
571
 
389
- def oauth_server_metadata(issuer = nil)
390
- request.on(".well-known") do
391
- request.on("oauth-authorization-server") do
392
- request.get do
393
- json_response_success(oauth_server_metadata_body(issuer))
394
- end
395
- end
396
- end
572
+ # OAuth Token Unique/Reuse
573
+ def oauth_tokens_unique_columns
574
+ [
575
+ oauth_tokens_oauth_application_id_column,
576
+ oauth_tokens_account_id_column,
577
+ oauth_tokens_scopes_column
578
+ ]
397
579
  end
398
580
 
399
- private
400
-
401
581
  def authorization_server_url
402
582
  base_url
403
583
  end
@@ -420,10 +600,10 @@ module Rodauth
420
600
 
421
601
  # time-to-live
422
602
  ttl = if response.key?("cache-control")
423
- cache_control = response["cache_control"]
424
- cache_control[/max-age=(\d+)/, 1]
603
+ cache_control = response["cache-control"]
604
+ cache_control[/max-age=(\d+)/, 1].to_i
425
605
  elsif response.key?("expires")
426
- Time.httpdate(response["expires"]).utc.to_i - Time.now.utc.to_i
606
+ Time.parse(response["expires"]).to_i - Time.now.to_i
427
607
  end
428
608
 
429
609
  [JSON.parse(response.body, symbolize_names: true), ttl]
@@ -435,7 +615,7 @@ module Rodauth
435
615
  http = Net::HTTP.new(auth_url.host, auth_url.port)
436
616
  http.use_ssl = auth_url.scheme == "https"
437
617
 
438
- request = Net::HTTP::Post.new(oauth_introspect_path)
618
+ request = Net::HTTP::Post.new(introspect_path)
439
619
  request["content-type"] = json_response_content_type
440
620
  request["accept"] = json_response_content_type
441
621
  request.body = JSON.dump({ "token_type_hint" => token_type_hint, "token" => token })
@@ -491,7 +671,7 @@ module Rodauth
491
671
  end
492
672
 
493
673
  def oauth_unique_id_generator
494
- SecureRandom.hex(32)
674
+ SecureRandom.urlsafe_base64(32)
495
675
  end
496
676
 
497
677
  def generate_token_hash(token)
@@ -503,89 +683,106 @@ module Rodauth
503
683
  end
504
684
 
505
685
  unless method_defined?(:password_hash)
506
- # :nocov:
507
686
  # From login_requirements_base feature
508
- if ENV["RACK_ENV"] == "test"
509
- def password_hash_cost
510
- BCrypt::Engine::MIN_COST
511
- end
512
- else
513
- def password_hash_cost
514
- BCrypt::Engine::DEFAULT_COST
515
- end
516
- end
517
687
 
518
688
  def password_hash(password)
519
- BCrypt::Password.create(password, cost: password_hash_cost)
689
+ BCrypt::Password.create(password, cost: BCrypt::Engine::DEFAULT_COST)
520
690
  end
521
- # :nocov:
522
691
  end
523
692
 
524
693
  def generate_oauth_token(params = {}, should_generate_refresh_token = true)
525
694
  create_params = {
526
- oauth_grants_expires_in_column => Time.now + oauth_token_expires_in
695
+ oauth_grants_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_token_expires_in)
527
696
  }.merge(params)
528
697
 
529
- token = oauth_unique_id_generator
530
- refresh_token = nil
698
+ rescue_from_uniqueness_error do
699
+ token = oauth_unique_id_generator
531
700
 
532
- if oauth_tokens_token_hash_column
533
- create_params[oauth_tokens_token_hash_column] = generate_token_hash(token)
534
- else
535
- create_params[oauth_tokens_token_column] = token
536
- end
701
+ if oauth_tokens_token_hash_column
702
+ create_params[oauth_tokens_token_hash_column] = generate_token_hash(token)
703
+ else
704
+ create_params[oauth_tokens_token_column] = token
705
+ end
537
706
 
538
- if should_generate_refresh_token
539
- refresh_token = oauth_unique_id_generator
707
+ refresh_token = nil
708
+ if should_generate_refresh_token
709
+ refresh_token = oauth_unique_id_generator
540
710
 
541
- if oauth_tokens_refresh_token_hash_column
542
- create_params[oauth_tokens_refresh_token_hash_column] = generate_token_hash(refresh_token)
543
- else
544
- create_params[oauth_tokens_refresh_token_column] = refresh_token
711
+ if oauth_tokens_refresh_token_hash_column
712
+ create_params[oauth_tokens_refresh_token_hash_column] = generate_token_hash(refresh_token)
713
+ else
714
+ create_params[oauth_tokens_refresh_token_column] = refresh_token
715
+ end
545
716
  end
717
+ oauth_token = _generate_oauth_token(create_params)
718
+ oauth_token[oauth_tokens_token_column] = token
719
+ oauth_token[oauth_tokens_refresh_token_column] = refresh_token if refresh_token
720
+ oauth_token
546
721
  end
547
- oauth_token = _generate_oauth_token(create_params)
548
-
549
- oauth_token[oauth_tokens_token_column] = token
550
- oauth_token[oauth_tokens_refresh_token_column] = refresh_token if refresh_token
551
- oauth_token
552
722
  end
553
723
 
554
724
  def _generate_oauth_token(params = {})
555
725
  ds = db[oauth_tokens_table]
556
726
 
557
- begin
558
- if ds.supports_returning?(:insert)
559
- ds.returning.insert(params).first
560
- else
561
- id = ds.insert(params)
562
- ds.where(oauth_tokens_id_column => id).first
727
+ if __one_oauth_token_per_account
728
+
729
+ token = __insert_or_update_and_return__(
730
+ ds,
731
+ oauth_tokens_id_column,
732
+ oauth_tokens_unique_columns,
733
+ params,
734
+ Sequel.expr(Sequel[oauth_tokens_table][oauth_tokens_expires_in_column]) > Sequel::CURRENT_TIMESTAMP,
735
+ ([oauth_tokens_token_column, oauth_tokens_refresh_token_column] if oauth_reuse_access_token)
736
+ )
737
+
738
+ # if the previous operation didn't return a row, it means that the conditions
739
+ # invalidated the update, and the existing token is still valid.
740
+ token || ds.where(
741
+ oauth_tokens_account_id_column => params[oauth_tokens_account_id_column],
742
+ oauth_tokens_oauth_application_id_column => params[oauth_tokens_oauth_application_id_column]
743
+ ).first
744
+ else
745
+ if oauth_reuse_access_token
746
+ unique_conds = Hash[oauth_tokens_unique_columns.map { |column| [column, params[column]] }]
747
+ valid_token = ds.where(Sequel.expr(Sequel[oauth_tokens_table][oauth_tokens_expires_in_column]) > Sequel::CURRENT_TIMESTAMP)
748
+ .where(unique_conds).first
749
+ return valid_token if valid_token
563
750
  end
564
- rescue Sequel::UniqueConstraintViolation
565
- retry
751
+ __insert_and_return__(ds, oauth_tokens_id_column, params)
566
752
  end
567
753
  end
568
754
 
569
- def oauth_token_by_token(token, dataset = db[oauth_tokens_table])
755
+ def oauth_token_by_token(token)
756
+ ds = db[oauth_tokens_table]
757
+
570
758
  ds = if oauth_tokens_token_hash_column
571
- dataset.where(oauth_tokens_token_hash_column => generate_token_hash(token))
759
+ ds.where(oauth_tokens_token_hash_column => generate_token_hash(token))
572
760
  else
573
- dataset.where(oauth_tokens_token_column => token)
761
+ ds.where(oauth_tokens_token_column => token)
574
762
  end
575
763
 
576
764
  ds.where(Sequel[oauth_tokens_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
577
765
  .where(oauth_tokens_revoked_at_column => nil).first
578
766
  end
579
767
 
580
- def oauth_token_by_refresh_token(token, dataset = db[oauth_tokens_table])
768
+ def oauth_token_by_refresh_token(token, revoked: false)
769
+ ds = db[oauth_tokens_table]
770
+ #
771
+ # filter expired refresh tokens out.
772
+ # an expired refresh token is a token whose access token expired for a period longer than the
773
+ # refresh token expiration period.
774
+ #
775
+ ds = ds.where(Sequel.date_add(oauth_tokens_expires_in_column, seconds: oauth_refresh_token_expires_in) >= Sequel::CURRENT_TIMESTAMP)
776
+
581
777
  ds = if oauth_tokens_refresh_token_hash_column
582
- dataset.where(oauth_tokens_refresh_token_hash_column => generate_token_hash(token))
778
+ ds.where(oauth_tokens_refresh_token_hash_column => generate_token_hash(token))
583
779
  else
584
- dataset.where(oauth_tokens_refresh_token_column => token)
780
+ ds.where(oauth_tokens_refresh_token_column => token)
585
781
  end
586
782
 
587
- ds.where(Sequel[oauth_tokens_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
588
- .where(oauth_tokens_revoked_at_column => nil).first
783
+ ds = ds.where(oauth_tokens_revoked_at_column => nil) unless revoked
784
+
785
+ ds.first
589
786
  end
590
787
 
591
788
  def json_access_token_payload(oauth_token)
@@ -654,7 +851,6 @@ module Rodauth
654
851
  # set client ID/secret pairs
655
852
 
656
853
  create_params.merge! \
657
- oauth_applications_client_id_column => oauth_unique_id_generator,
658
854
  oauth_applications_client_secret_column => \
659
855
  secret_hash(oauth_application_params[oauth_application_client_secret_param])
660
856
 
@@ -664,29 +860,14 @@ module Rodauth
664
860
  oauth_application_default_scope
665
861
  end
666
862
 
667
- id = nil
668
- raised = begin
669
- id = db[oauth_applications_table].insert(create_params)
670
- false
671
- rescue Sequel::ConstraintViolation => e
672
- e
673
- end
674
-
675
- if raised
676
- field = raised.message[/\.(.*)$/, 1]
677
- case raised
678
- when Sequel::UniqueConstraintViolation
679
- throw_error(field, unique_error_message)
680
- when Sequel::NotNullConstraintViolation
681
- throw_error(field, null_error_message)
682
- end
863
+ rescue_from_uniqueness_error do
864
+ create_params[oauth_applications_client_id_column] = oauth_unique_id_generator
865
+ db[oauth_applications_table].insert(create_params)
683
866
  end
684
-
685
- !raised && id
686
867
  end
687
868
 
688
869
  # Authorize
689
- def before_authorize
870
+ def require_authorizable_account
690
871
  require_account
691
872
  end
692
873
 
@@ -699,6 +880,9 @@ module Rodauth
699
880
  end
700
881
  redirect_response_error("invalid_scope") unless check_valid_scopes?
701
882
 
883
+ if (response_mode = param_or_nil("response_mode")) && response_mode != "form_post"
884
+ redirect_response_error("invalid_request")
885
+ end
702
886
  validate_pkce_challenge_params if use_oauth_pkce?
703
887
  end
704
888
 
@@ -716,57 +900,79 @@ module Rodauth
716
900
  ).count.zero?
717
901
 
718
902
  # if there's a previous oauth grant for the params combo, it means that this user has approved before.
719
-
720
903
  request.env["REQUEST_METHOD"] = "POST"
721
904
  end
722
905
 
723
- def create_oauth_grant
724
- create_params = {
906
+ def create_oauth_grant(create_params = {})
907
+ create_params.merge!(
725
908
  oauth_grants_account_id_column => account_id,
726
909
  oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
727
910
  oauth_grants_redirect_uri_column => redirect_uri,
728
- oauth_grants_expires_in_column => Time.now + oauth_grant_expires_in,
911
+ oauth_grants_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_grant_expires_in),
729
912
  oauth_grants_scopes_column => scopes.join(oauth_scope_separator)
730
- }
913
+ )
731
914
 
732
915
  # Access Type flow
733
- if use_oauth_access_type?
734
- if (access_type = param_or_nil("access_type"))
735
- create_params[oauth_grants_access_type_column] = access_type
736
- end
916
+ if use_oauth_access_type? && (access_type = param_or_nil("access_type"))
917
+ create_params[oauth_grants_access_type_column] = access_type
737
918
  end
738
919
 
739
920
  # PKCE flow
740
- if use_oauth_pkce?
921
+ if use_oauth_pkce? && (code_challenge = param_or_nil("code_challenge"))
922
+ code_challenge_method = param_or_nil("code_challenge_method")
741
923
 
742
- if (code_challenge = param_or_nil("code_challenge"))
743
- code_challenge_method = param_or_nil("code_challenge_method")
744
-
745
- create_params[oauth_grants_code_challenge_column] = code_challenge
746
- create_params[oauth_grants_code_challenge_method_column] = code_challenge_method
747
- elsif oauth_require_pkce
748
- redirect_response_error("code_challenge_required")
749
- end
924
+ create_params[oauth_grants_code_challenge_column] = code_challenge
925
+ create_params[oauth_grants_code_challenge_method_column] = code_challenge_method
750
926
  end
751
927
 
752
928
  ds = db[oauth_grants_table]
753
929
 
754
- begin
755
- authorization_code = oauth_unique_id_generator
756
- create_params[oauth_grants_code_column] = authorization_code
757
- ds.insert(create_params)
758
- authorization_code
759
- rescue Sequel::UniqueConstraintViolation
760
- retry
930
+ rescue_from_uniqueness_error do
931
+ create_params[oauth_grants_code_column] = oauth_unique_id_generator
932
+ __insert_and_return__(ds, oauth_grants_id_column, create_params)
933
+ end
934
+ create_params[oauth_grants_code_column]
935
+ end
936
+
937
+ def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
938
+ case param("response_type")
939
+ when "token"
940
+ redirect_response_error("invalid_request") unless use_oauth_implicit_grant_type?
941
+
942
+ response_mode ||= "fragment"
943
+ response_params.replace(_do_authorize_token)
944
+ when "code"
945
+ response_mode ||= "query"
946
+ response_params.replace(_do_authorize_code)
947
+ when "none"
948
+ response_mode ||= "none"
949
+ when "", nil
950
+ response_mode ||= oauth_response_mode
951
+ response_params.replace(_do_authorize_code)
761
952
  end
953
+
954
+ response_params["state"] = param("state") if param_or_nil("state")
955
+
956
+ [response_params, response_mode]
762
957
  end
763
958
 
764
- # Access Tokens
959
+ def _do_authorize_code
960
+ { "code" => create_oauth_grant }
961
+ end
765
962
 
766
- def before_token
767
- require_oauth_application
963
+ def _do_authorize_token
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
+ json_access_token_payload(oauth_token)
768
972
  end
769
973
 
974
+ # Access Tokens
975
+
770
976
  def validate_oauth_token_params
771
977
  unless (grant_type = param_or_nil("grant_type"))
772
978
  redirect_response_error("invalid_request")
@@ -786,27 +992,56 @@ module Rodauth
786
992
  def create_oauth_token
787
993
  case param("grant_type")
788
994
  when "authorization_code"
789
- create_oauth_token_from_authorization_code(oauth_application)
995
+ # fetch oauth grant
996
+ oauth_grant = db[oauth_grants_table].where(
997
+ oauth_grants_code_column => param("code"),
998
+ oauth_grants_redirect_uri_column => param("redirect_uri"),
999
+ oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
1000
+ oauth_grants_revoked_at_column => nil
1001
+ ).where(Sequel[oauth_grants_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
1002
+ .for_update
1003
+ .first
1004
+
1005
+ redirect_response_error("invalid_grant") unless oauth_grant
1006
+
1007
+ create_params = {
1008
+ oauth_tokens_account_id_column => oauth_grant[oauth_grants_account_id_column],
1009
+ oauth_tokens_oauth_application_id_column => oauth_grant[oauth_grants_oauth_application_id_column],
1010
+ oauth_tokens_oauth_grant_id_column => oauth_grant[oauth_grants_id_column],
1011
+ oauth_tokens_scopes_column => oauth_grant[oauth_grants_scopes_column]
1012
+ }
1013
+ create_oauth_token_from_authorization_code(oauth_grant, create_params)
790
1014
  when "refresh_token"
791
- create_oauth_token_from_token(oauth_application)
792
- else
793
- redirect_response_error("invalid_grant")
1015
+ # fetch potentially revoked oauth token
1016
+ oauth_token = oauth_token_by_refresh_token(param("refresh_token"), revoked: true)
1017
+
1018
+ if !oauth_token
1019
+ redirect_response_error("invalid_grant")
1020
+ elsif oauth_token[oauth_tokens_revoked_at_column]
1021
+ if oauth_refresh_token_protection_policy == "rotation"
1022
+ # https://tools.ietf.org/html/draft-ietf-oauth-v2-1-00#section-6.1
1023
+ #
1024
+ # If a refresh token is compromised and subsequently used by both the attacker and the legitimate
1025
+ # client, one of them will present an invalidated refresh token, which will inform the authorization
1026
+ # server of the breach. The authorization server cannot determine which party submitted the invalid
1027
+ # refresh token, but it will revoke the active refresh token. This stops the attack at the cost of
1028
+ # forcing the legitimate client to obtain a fresh authorization grant.
1029
+
1030
+ db[oauth_tokens_table].where(oauth_tokens_oauth_token_id_column => oauth_token[oauth_tokens_id_column])
1031
+ .update(oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
1032
+ end
1033
+ redirect_response_error("invalid_grant")
1034
+ end
1035
+
1036
+ update_params = {
1037
+ oauth_tokens_oauth_application_id_column => oauth_token[oauth_grants_oauth_application_id_column],
1038
+ oauth_tokens_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_token_expires_in)
1039
+ }
1040
+ create_oauth_token_from_token(oauth_token, update_params)
794
1041
  end
795
1042
  end
796
1043
 
797
- def create_oauth_token_from_authorization_code(oauth_application)
798
- # fetch oauth grant
799
- oauth_grant = db[oauth_grants_table].where(
800
- oauth_grants_code_column => param("code"),
801
- oauth_grants_redirect_uri_column => param("redirect_uri"),
802
- oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
803
- oauth_grants_revoked_at_column => nil
804
- ).where(Sequel[oauth_grants_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
805
- .for_update
806
- .first
807
-
808
- redirect_response_error("invalid_grant") unless oauth_grant
809
-
1044
+ def create_oauth_token_from_authorization_code(oauth_grant, create_params)
810
1045
  # PKCE
811
1046
  if use_oauth_pkce?
812
1047
  if oauth_grant[oauth_grants_code_challenge_column]
@@ -818,13 +1053,6 @@ module Rodauth
818
1053
  end
819
1054
  end
820
1055
 
821
- create_params = {
822
- oauth_tokens_account_id_column => oauth_grant[oauth_grants_account_id_column],
823
- oauth_tokens_oauth_application_id_column => oauth_grant[oauth_grants_oauth_application_id_column],
824
- oauth_tokens_oauth_grant_id_column => oauth_grant[oauth_grants_id_column],
825
- oauth_tokens_scopes_column => oauth_grant[oauth_grants_scopes_column]
826
- }
827
-
828
1056
  # revoke oauth grant
829
1057
  db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
830
1058
  .update(oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
@@ -835,40 +1063,41 @@ module Rodauth
835
1063
  generate_oauth_token(create_params, should_generate_refresh_token)
836
1064
  end
837
1065
 
838
- def create_oauth_token_from_token(oauth_application)
839
- # fetch oauth token
840
- oauth_token = oauth_token_by_refresh_token(param("refresh_token"))
1066
+ def create_oauth_token_from_token(oauth_token, update_params)
1067
+ redirect_response_error("invalid_grant") unless token_from_application?(oauth_token, oauth_application)
841
1068
 
842
- redirect_response_error("invalid_grant") unless oauth_token && token_from_application?(oauth_token, oauth_application)
843
-
844
- token = oauth_unique_id_generator
845
-
846
- update_params = {
847
- oauth_tokens_oauth_application_id_column => oauth_token[oauth_grants_oauth_application_id_column],
848
- oauth_tokens_expires_in_column => Time.now + oauth_token_expires_in
849
- }
1069
+ rescue_from_uniqueness_error do
1070
+ oauth_tokens_ds = db[oauth_tokens_table]
1071
+ token = oauth_unique_id_generator
850
1072
 
851
- if oauth_tokens_token_hash_column
852
- update_params[oauth_tokens_token_hash_column] = generate_token_hash(token)
853
- else
854
- update_params[oauth_tokens_token_column] = token
855
- end
856
-
857
- ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
858
-
859
- oauth_token = begin
860
- if ds.supports_returning?(:update)
861
- ds.returning.update(update_params).first
1073
+ if oauth_tokens_token_hash_column
1074
+ update_params[oauth_tokens_token_hash_column] = generate_token_hash(token)
862
1075
  else
863
- ds.update(update_params)
864
- ds.first
1076
+ update_params[oauth_tokens_token_column] = token
865
1077
  end
866
- rescue Sequel::UniqueConstraintViolation
867
- retry
868
- end
869
1078
 
870
- oauth_token[oauth_tokens_token_column] = token
871
- oauth_token
1079
+ oauth_token = if oauth_refresh_token_protection_policy == "rotation"
1080
+ insert_params = {
1081
+ **update_params,
1082
+ oauth_tokens_oauth_token_id_column => oauth_token[oauth_tokens_id_column],
1083
+ oauth_tokens_scopes_column => oauth_token[oauth_tokens_scopes_column]
1084
+ }
1085
+
1086
+ # revoke the refresh token
1087
+ oauth_tokens_ds.where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
1088
+ .update(oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
1089
+
1090
+ insert_params[oauth_tokens_oauth_token_id_column] = oauth_token[oauth_tokens_id_column]
1091
+ __insert_and_return__(oauth_tokens_ds, oauth_tokens_id_column, insert_params)
1092
+ else
1093
+ # includes none
1094
+ ds = oauth_tokens_ds.where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
1095
+ __update_and_return__(ds, update_params)
1096
+ end
1097
+
1098
+ oauth_token[oauth_tokens_token_column] = token
1099
+ oauth_token
1100
+ end
872
1101
  end
873
1102
 
874
1103
  TOKEN_HINT_TYPES = %w[access_token refresh_token].freeze
@@ -877,8 +1106,8 @@ module Rodauth
877
1106
 
878
1107
  def validate_oauth_introspect_params
879
1108
  # check if valid token hint type
880
- if token_type_hint
881
- redirect_response_error("unsupported_token_type") unless TOKEN_HINT_TYPES.include?(token_type_hint)
1109
+ if param_or_nil("token_type_hint") && !TOKEN_HINT_TYPES.include?(param("token_type_hint"))
1110
+ redirect_response_error("unsupported_token_type")
882
1111
  end
883
1112
 
884
1113
  redirect_response_error("invalid_request") unless param_or_nil("token")
@@ -896,48 +1125,35 @@ module Rodauth
896
1125
  }
897
1126
  end
898
1127
 
899
- def before_introspect; end
900
-
901
1128
  # Token revocation
902
1129
 
903
- def before_revoke
904
- require_oauth_application
905
- end
906
-
907
1130
  def validate_oauth_revoke_params
908
1131
  # check if valid token hint type
909
- redirect_response_error("unsupported_token_type") unless TOKEN_HINT_TYPES.include?(token_type_hint)
1132
+ if param_or_nil("token_type_hint") && !TOKEN_HINT_TYPES.include?(param("token_type_hint"))
1133
+ redirect_response_error("unsupported_token_type")
1134
+ end
910
1135
 
911
1136
  redirect_response_error("invalid_request") unless param_or_nil("token")
912
1137
  end
913
1138
 
914
1139
  def revoke_oauth_token
915
- oauth_token = case token_type_hint
916
- when "access_token"
917
- oauth_token_by_token(token)
918
- when "refresh_token"
1140
+ token = param("token")
1141
+
1142
+ oauth_token = if param("token_type_hint") == "refresh_token"
919
1143
  oauth_token_by_refresh_token(token)
1144
+ else
1145
+ oauth_token_by_token(token)
920
1146
  end
921
1147
 
922
1148
  redirect_response_error("invalid_request") unless oauth_token
923
1149
 
924
- if oauth_application
925
- redirect_response_error("invalid_request") unless token_from_application?(oauth_token, oauth_application)
926
- else
927
- @oauth_application = db[oauth_applications_table].where(oauth_applications_id_column =>
928
- oauth_token[oauth_tokens_oauth_application_id_column]).first
929
- end
1150
+ redirect_response_error("invalid_request") unless token_from_application?(oauth_token, oauth_application)
930
1151
 
931
1152
  update_params = { oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP }
932
1153
 
933
1154
  ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
934
1155
 
935
- oauth_token = if ds.supports_returning?(:update)
936
- ds.returning.update(update_params).first
937
- else
938
- ds.update(update_params)
939
- ds.first
940
- end
1156
+ oauth_token = __update_and_return__(ds, update_params)
941
1157
 
942
1158
  oauth_token[oauth_tokens_token_column] = token
943
1159
  oauth_token
@@ -955,7 +1171,13 @@ module Rodauth
955
1171
 
956
1172
  def redirect_response_error(error_code, redirect_url = redirect_uri || request.referer || default_redirect)
957
1173
  if accepts_json?
958
- throw_json_response_error(invalid_oauth_response_status, error_code)
1174
+ status_code = if respond_to?(:"#{error_code}_response_status")
1175
+ send(:"#{error_code}_response_status")
1176
+ else
1177
+ invalid_oauth_response_status
1178
+ end
1179
+
1180
+ throw_json_response_error(status_code, error_code)
959
1181
  else
960
1182
  redirect_url = URI.parse(redirect_url)
961
1183
  query_params = []
@@ -977,9 +1199,17 @@ module Rodauth
977
1199
  end
978
1200
  end
979
1201
 
980
- def json_response_success(body)
1202
+ def json_response_success(body, cache = false)
981
1203
  response.status = 200
982
1204
  response["Content-Type"] ||= json_response_content_type
1205
+ if cache
1206
+ # defaulting to 1-day for everyone, for now at least
1207
+ max_age = 60 * 60 * 24
1208
+ response["Cache-Control"] = "private, max-age=#{max_age}"
1209
+ else
1210
+ response["Cache-Control"] = "no-store"
1211
+ response["Pragma"] = "no-cache"
1212
+ end
983
1213
  json_payload = _json_response_body(body)
984
1214
  response.write(json_payload)
985
1215
  request.halt
@@ -1002,7 +1232,6 @@ module Rodauth
1002
1232
  end
1003
1233
 
1004
1234
  unless method_defined?(:_json_response_body)
1005
- # :nocov:
1006
1235
  def _json_response_body(hash)
1007
1236
  if request.respond_to?(:convert_to_json)
1008
1237
  request.send(:convert_to_json, hash)
@@ -1010,7 +1239,6 @@ module Rodauth
1010
1239
  JSON.dump(hash)
1011
1240
  end
1012
1241
  end
1013
- # :nocov:
1014
1242
  end
1015
1243
 
1016
1244
  def authorization_required
@@ -1018,7 +1246,7 @@ module Rodauth
1018
1246
  throw_json_response_error(authorization_required_error_status, "invalid_client")
1019
1247
  else
1020
1248
  set_redirect_error_flash(require_authorization_error_flash)
1021
- redirect(require_authorization_redirect)
1249
+ redirect(authorize_path)
1022
1250
  end
1023
1251
  end
1024
1252
 
@@ -1098,10 +1326,10 @@ module Rodauth
1098
1326
 
1099
1327
  def oauth_server_metadata_body(path)
1100
1328
  issuer = base_url
1101
- issuer += "/#{path}" if issuer
1329
+ issuer += "/#{path}" if path
1102
1330
 
1103
1331
  responses_supported = %w[code]
1104
- response_modes_supported = %w[query]
1332
+ response_modes_supported = %w[query form_post]
1105
1333
  grant_types_supported = %w[authorization_code]
1106
1334
 
1107
1335
  if use_oauth_implicit_grant_type?
@@ -1109,11 +1337,12 @@ module Rodauth
1109
1337
  response_modes_supported << "fragment"
1110
1338
  grant_types_supported << "implicit"
1111
1339
  end
1340
+
1112
1341
  {
1113
1342
  issuer: issuer,
1114
- authorization_endpoint: oauth_authorize_url,
1115
- token_endpoint: oauth_token_url,
1116
- registration_endpoint: "#{base_url}/#{oauth_applications_path}",
1343
+ authorization_endpoint: authorize_url,
1344
+ token_endpoint: token_url,
1345
+ registration_endpoint: route_url(oauth_applications_path),
1117
1346
  scopes_supported: oauth_application_scopes,
1118
1347
  response_types_supported: responses_supported,
1119
1348
  response_modes_supported: response_modes_supported,
@@ -1123,143 +1352,12 @@ module Rodauth
1123
1352
  ui_locales_supported: oauth_metadata_ui_locales_supported,
1124
1353
  op_policy_uri: oauth_metadata_op_policy_uri,
1125
1354
  op_tos_uri: oauth_metadata_op_tos_uri,
1126
- revocation_endpoint: oauth_revoke_url,
1355
+ revocation_endpoint: revoke_url,
1127
1356
  revocation_endpoint_auth_methods_supported: nil, # because it's client_secret_basic
1128
- introspection_endpoint: oauth_introspect_url,
1357
+ introspection_endpoint: introspect_url,
1129
1358
  introspection_endpoint_auth_methods_supported: %w[client_secret_basic],
1130
1359
  code_challenge_methods_supported: (use_oauth_pkce? ? oauth_pkce_challenge_method : nil)
1131
1360
  }
1132
1361
  end
1133
-
1134
- # /oauth-token
1135
- route(:oauth_token) do |r|
1136
- before_token
1137
-
1138
- r.post do
1139
- catch_error do
1140
- validate_oauth_token_params
1141
-
1142
- oauth_token = nil
1143
- transaction do
1144
- oauth_token = create_oauth_token
1145
- end
1146
-
1147
- json_response_success(json_access_token_payload(oauth_token))
1148
- end
1149
-
1150
- throw_json_response_error(invalid_oauth_response_status, "invalid_request")
1151
- end
1152
- end
1153
-
1154
- # /oauth-introspect
1155
- route(:oauth_introspect) do |r|
1156
- before_introspect
1157
-
1158
- r.post do
1159
- catch_error do
1160
- validate_oauth_introspect_params
1161
-
1162
- oauth_token = case param("token_type_hint")
1163
- when "access_token"
1164
- oauth_token_by_token(param("token"))
1165
- when "refresh_token"
1166
- oauth_token_by_refresh_token(param("token"))
1167
- else
1168
- oauth_token_by_token(param("token")) || oauth_token_by_refresh_token(param("token"))
1169
- end
1170
-
1171
- if oauth_application
1172
- redirect_response_error("invalid_request") if oauth_token && !token_from_application?(oauth_token, oauth_application)
1173
- elsif oauth_token
1174
- @oauth_application = db[oauth_applications_table].where(oauth_applications_id_column =>
1175
- oauth_token[oauth_tokens_oauth_application_id_column]).first
1176
- end
1177
-
1178
- json_response_success(json_token_introspect_payload(oauth_token))
1179
- end
1180
-
1181
- throw_json_response_error(invalid_oauth_response_status, "invalid_request")
1182
- end
1183
- end
1184
-
1185
- # /oauth-revoke
1186
- route(:oauth_revoke) do |r|
1187
- before_revoke
1188
-
1189
- # access-token
1190
- r.post do
1191
- catch_error do
1192
- validate_oauth_revoke_params
1193
-
1194
- oauth_token = nil
1195
- transaction do
1196
- oauth_token = revoke_oauth_token
1197
- after_revoke
1198
- end
1199
-
1200
- if accepts_json?
1201
- json_response_success \
1202
- "token" => oauth_token[oauth_tokens_token_column],
1203
- "refresh_token" => oauth_token[oauth_tokens_refresh_token_column],
1204
- "revoked_at" => oauth_token[oauth_tokens_revoked_at_column]
1205
- else
1206
- set_notice_flash revoke_oauth_token_notice_flash
1207
- redirect request.referer || "/"
1208
- end
1209
- end
1210
-
1211
- throw_json_response_error(invalid_oauth_response_status, "invalid_request")
1212
- end
1213
- end
1214
-
1215
- # /oauth-authorize
1216
- route(:oauth_authorize) do |r|
1217
- require_account
1218
- validate_oauth_grant_params
1219
- try_approval_prompt if use_oauth_access_type? && request.get?
1220
-
1221
- before_authorize
1222
-
1223
- r.get do
1224
- authorize_view
1225
- end
1226
-
1227
- r.post do
1228
- code = nil
1229
- query_params = []
1230
- fragment_params = []
1231
-
1232
- transaction do
1233
- case param("response_type")
1234
- when "token"
1235
- redirect_response_error("invalid_request") unless use_oauth_implicit_grant_type?
1236
-
1237
- create_params = {
1238
- oauth_tokens_account_id_column => account_id,
1239
- oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
1240
- oauth_tokens_scopes_column => scopes
1241
- }
1242
- oauth_token = generate_oauth_token(create_params, false)
1243
-
1244
- token_payload = json_access_token_payload(oauth_token)
1245
- fragment_params.replace(token_payload.map { |k, v| "#{k}=#{v}" })
1246
- when "code", "", nil
1247
- code = create_oauth_grant
1248
- query_params << "code=#{code}"
1249
- else
1250
- redirect_response_error("invalid_request")
1251
- end
1252
- after_authorize
1253
- end
1254
-
1255
- redirect_url = URI.parse(redirect_uri)
1256
- query_params << "state=#{state}" if state
1257
- query_params << redirect_url.query if redirect_url.query
1258
- redirect_url.query = query_params.join("&") unless query_params.empty?
1259
- redirect_url.fragment = fragment_params.join("&") unless fragment_params.empty?
1260
-
1261
- redirect(redirect_url.to_s)
1262
- end
1263
- end
1264
1362
  end
1265
1363
  end