rodauth-oauth 0.0.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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