rodauth-oauth 0.0.1

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.
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module Rodauth::OAuth
6
+ module Rails
7
+ module Generators
8
+ class ViewsGenerator < ::Rails::Generators::Base
9
+ source_root "#{__dir__}/templates"
10
+ namespace "roda:oauth:views"
11
+
12
+ DEFAULT = %w[oauth_authorize].freeze
13
+ VIEWS = {
14
+ oauth_authorize: DEFAULT,
15
+ oauth_applications: %w[oauth_applications oauth_application new_oauth_application]
16
+ }.freeze
17
+
18
+ DEPENDENCIES = {
19
+ active_sessions: :logout,
20
+ otp: :two_factor_base,
21
+ sms_codes: :two_factor_base,
22
+ recovery_codes: :two_factor_base,
23
+ webauthn: :two_factor_base
24
+ }.freeze
25
+
26
+ class_option :features, type: :array,
27
+ desc: "Roda OAuth features to generate views for (oauth_applications etc.)",
28
+ default: DEFAULT
29
+
30
+ class_option :all, aliases: "-a", type: :boolean,
31
+ desc: "Generates views for all Roda OAuth features",
32
+ default: false
33
+
34
+ class_option :directory, aliases: "-d", type: :string,
35
+ desc: "The directory under app/views/* into which to create views",
36
+ default: "rodauth"
37
+
38
+ def create_views
39
+ features = options[:all] ? VIEWS.keys : (DEFAULT + options[:features]).map(&:to_sym)
40
+
41
+ views = features.inject([]) do |list, feature|
42
+ list |= VIEWS[feature] || []
43
+ list |= VIEWS[DEPENDENCIES[feature]] || []
44
+ end
45
+
46
+ views.each do |view|
47
+ template "app/views/rodauth/#{view}.html.erb",
48
+ "app/views/#{options[:directory].underscore}/#{view}.html.erb"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,802 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oauth) do
5
+ # RUBY EXTENSIONS
6
+ unless Regexp.method_defined?(:match?)
7
+ module RegexpExtensions
8
+ refine(Regexp) do
9
+ def match?(*args)
10
+ !match(*args).nil?
11
+ end
12
+ end
13
+ end
14
+ using(RegexpExtensions)
15
+ end
16
+
17
+ SCOPES = %w[profile.read].freeze
18
+
19
+ depends :login
20
+
21
+ before "authorize"
22
+ after "authorize"
23
+ after "authorize_failure"
24
+
25
+ before "token"
26
+ after "token"
27
+
28
+ before "revoke"
29
+ after "revoke"
30
+
31
+ before "create_oauth_application"
32
+ after "create_oauth_application"
33
+
34
+ error_flash "OAuth Authorization invalid parameters", "oauth_grant_valid_parameters"
35
+
36
+ error_flash "Please authorize to continue", "require_authorization"
37
+ error_flash "There was an error registering your oauth application", "create_oauth_application"
38
+ notice_flash "Your oauth application has been registered", "create_oauth_application"
39
+
40
+ notice_flash "The oauth token has been revoked", "revoke_oauth_token"
41
+
42
+ view "oauth_authorize", "Authorize", "authorize"
43
+ view "oauth_applications", "Oauth Applications", "oauth_applications"
44
+ view "oauth_application", "Oauth Application", "oauth_application"
45
+ view "new_oauth_application", "New Oauth Application", "new_oauth_application"
46
+ view "oauth_tokens", "Oauth Tokens", "oauth_tokens"
47
+
48
+ auth_value_method :json_response_content_type, "application/json"
49
+
50
+ auth_value_method :oauth_grant_expires_in, 60 * 5 # 5 minutes
51
+ auth_value_method :oauth_token_expires_in, 60 * 60 # 60 minutes
52
+ auth_value_method :use_oauth_implicit_grant_type, false
53
+
54
+ # URL PARAMS
55
+
56
+ # Authorize / token
57
+ %w[
58
+ grant_type code refresh_token client_id scope
59
+ state redirect_uri scopes token_type_hint token
60
+ access_type response_type
61
+ ].each do |param|
62
+ auth_value_method :"#{param}_param", param
63
+ end
64
+
65
+ # Application
66
+ APPLICATION_REQUIRED_PARAMS = %w[name description scopes homepage_url redirect_uri].freeze
67
+ auth_value_method :oauth_application_required_params, APPLICATION_REQUIRED_PARAMS
68
+
69
+ (APPLICATION_REQUIRED_PARAMS + %w[client_id client_secret]).each do |param|
70
+ auth_value_method :"oauth_application_#{param}_param", param
71
+ end
72
+
73
+ # OAuth Token
74
+ auth_value_method :oauth_tokens_path, "oauth-tokens"
75
+ auth_value_method :oauth_tokens_table, :oauth_tokens
76
+ auth_value_method :oauth_tokens_id_column, :id
77
+
78
+ %i[
79
+ oauth_application_id oauth_token_id oauth_grant_id account_id
80
+ token refresh_token scopes
81
+ expires_in revoked_at
82
+ ].each do |column|
83
+ auth_value_method :"oauth_tokens_#{column}_column", column
84
+ end
85
+
86
+ # OAuth Grants
87
+ auth_value_method :oauth_grants_table, :oauth_grants
88
+ auth_value_method :oauth_grants_id_column, :id
89
+ %i[
90
+ account_id oauth_application_id
91
+ redirect_uri code scopes access_type
92
+ expires_in revoked_at
93
+ ].each do |column|
94
+ auth_value_method :"oauth_grants_#{column}_column", column
95
+ end
96
+
97
+ auth_value_method :authorization_required_error_status, 401
98
+ auth_value_method :invalid_oauth_response_status, 400
99
+
100
+ # OAuth Applications
101
+ auth_value_method :oauth_applications_path, "oauth-applications"
102
+ auth_value_method :oauth_applications_table, :oauth_applications
103
+
104
+ auth_value_method :oauth_applications_id_column, :id
105
+ auth_value_method :oauth_applications_id_pattern, Integer
106
+
107
+ %i[
108
+ account_id
109
+ name description scopes
110
+ client_id client_secret
111
+ homepage_url redirect_uri
112
+ ].each do |column|
113
+ auth_value_method :"oauth_applications_#{column}_column", column
114
+ end
115
+
116
+ auth_value_method :oauth_application_default_scope, SCOPES.first
117
+ auth_value_method :oauth_application_scopes, SCOPES
118
+ auth_value_method :oauth_token_type, "Bearer"
119
+
120
+ auth_value_method :invalid_request, "Request is missing a required parameter"
121
+ auth_value_method :invalid_client, "Invalid client"
122
+ auth_value_method :unauthorized_client, "Unauthorized client"
123
+ auth_value_method :invalid_grant_type_message, "Invalid grant type"
124
+ auth_value_method :invalid_grant_message, "Invalid grant"
125
+ auth_value_method :invalid_scope_message, "Invalid scope"
126
+
127
+ auth_value_method :invalid_url_message, "Invalid URL"
128
+ auth_value_method :unsupported_token_type_message, "Invalid token type hint"
129
+
130
+ auth_value_method :unique_error_message, "is already in use"
131
+ auth_value_method :null_error_message, "is not filled"
132
+
133
+ auth_value_methods(
134
+ :oauth_unique_id_generator
135
+ )
136
+
137
+ redirect(:oauth_application) do |id|
138
+ "/#{oauth_applications_path}/#{id}"
139
+ end
140
+
141
+ redirect(:require_authorization) do
142
+ if logged_in?
143
+ oauth_authorize_path
144
+ else
145
+ login_redirect
146
+ end
147
+ end
148
+
149
+ auth_value_method :json_request_accept_regexp, %r{\bapplication/(?:vnd\.api\+)?json\b}i
150
+ auth_methods(:json_request?)
151
+
152
+ def check_csrf?
153
+ case request.path
154
+ when oauth_token_path
155
+ false
156
+ when oauth_revoke_path
157
+ !json_request?
158
+ else
159
+ super
160
+ end
161
+ end
162
+
163
+ # Overrides logged_in?, so that a valid authorization token also authnenticates a request
164
+ def logged_in?
165
+ super || authorization_token
166
+ end
167
+
168
+ def json_request?
169
+ return @json_request if defined?(@json_request)
170
+
171
+ @json_request = request.get_header("HTTP_ACCEPT") =~ json_request_accept_regexp
172
+ end
173
+
174
+ attr_reader :oauth_application
175
+
176
+ def initialize(scope)
177
+ @scope = scope
178
+ end
179
+
180
+ def state
181
+ state = param(state_param)
182
+
183
+ return unless state && !state.empty?
184
+
185
+ state
186
+ end
187
+
188
+ def scopes
189
+ scopes = param(scopes_param)
190
+
191
+ return [oauth_application_default_scope] unless scopes && !scopes.empty?
192
+
193
+ scopes.split(" ")
194
+ end
195
+
196
+ def client_id
197
+ client_id = param(client_id_param)
198
+
199
+ return unless client_id && !client_id.empty?
200
+
201
+ client_id
202
+ end
203
+
204
+ def redirect_uri
205
+ redirect_uri = param(redirect_uri_param)
206
+
207
+ return oauth_application[oauth_applications_redirect_uri_column] unless redirect_uri && !redirect_uri.empty?
208
+
209
+ redirect_uri
210
+ end
211
+
212
+ def token_type_hint
213
+ token_type_hint = param(token_type_hint_param)
214
+
215
+ return "access_token" unless token_type_hint && !token_type_hint.empty?
216
+
217
+ token_type_hint
218
+ end
219
+
220
+ def token
221
+ token = param(token_param)
222
+
223
+ return unless token && !token.empty?
224
+
225
+ token
226
+ end
227
+
228
+ def oauth_application
229
+ return @oauth_application if defined?(@oauth_application)
230
+
231
+ @oauth_application = begin
232
+ client_id = param(client_id_param)
233
+
234
+ return unless client_id
235
+
236
+ db[oauth_applications_table].filter(oauth_applications_client_id_column => client_id).first
237
+ end
238
+ end
239
+
240
+ def authorization_token
241
+ return @authorization_token if defined?(@authorization_token)
242
+
243
+ @authorization_token = begin
244
+ value = request.get_header("HTTP_AUTHORIZATION").to_s
245
+
246
+ scheme, token = value.split(" ", 2)
247
+
248
+ return unless scheme == "Bearer"
249
+
250
+ # check if there is a token
251
+ # check if token has not expired
252
+ # check if token has been revoked
253
+ db[oauth_tokens_table].where(oauth_tokens_token_column => token)
254
+ .where(Sequel[oauth_tokens_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
255
+ .where(oauth_tokens_revoked_at_column => nil)
256
+ .first
257
+ end
258
+ end
259
+
260
+ def require_oauth_authorization(*scopes)
261
+ authorization_required unless authorization_token
262
+
263
+ scopes << oauth_application_default_scope if scopes.empty?
264
+
265
+ token_scopes = authorization_token[:scopes].split(",")
266
+
267
+ authorization_required unless scopes.any? { |scope| token_scopes.include?(scope) }
268
+ end
269
+
270
+ # /oauth-applications routes
271
+ def oauth_applications
272
+ request.on(oauth_applications_path) do
273
+ require_account
274
+
275
+ request.get "new" do
276
+ new_oauth_application_view
277
+ end
278
+ request.on(oauth_applications_id_pattern) do |id|
279
+ oauth_application = db[oauth_applications_table].where(oauth_applications_id_column => id).first
280
+ scope.instance_variable_set(:@oauth_application, oauth_application)
281
+
282
+ request.is do
283
+ request.get do
284
+ oauth_application_view
285
+ end
286
+ end
287
+
288
+ request.on(oauth_tokens_path) do
289
+ oauth_tokens = db[oauth_tokens_table].where(oauth_tokens_oauth_application_id_column => id)
290
+ scope.instance_variable_set(:@oauth_tokens, oauth_tokens)
291
+ oauth_tokens_view
292
+ end
293
+ end
294
+
295
+ request.get do
296
+ scope.instance_variable_set(:@oauth_applications, db[:oauth_applications])
297
+ oauth_applications_view
298
+ end
299
+
300
+ request.post do
301
+ catch_error do
302
+ validate_oauth_application_params
303
+
304
+ transaction do
305
+ before_create_oauth_application
306
+ id = create_oauth_application
307
+ after_create_oauth_application
308
+ set_notice_flash create_oauth_application_notice_flash
309
+ redirect oauth_application_redirect(id)
310
+ end
311
+ end
312
+ set_error_flash create_oauth_application_error_flash
313
+ new_oauth_application_view
314
+ end
315
+ end
316
+ end
317
+
318
+ private
319
+
320
+ def oauth_unique_id_generator
321
+ SecureRandom.uuid
322
+ end
323
+
324
+ # Oauth Application
325
+
326
+ def oauth_application_params
327
+ @oauth_application_params ||= oauth_application_required_params.each_with_object({}) do |param, params|
328
+ value = request.params[__send__(:"oauth_application_#{param}_param")]
329
+ if value && !value.empty?
330
+ params[param] = value
331
+ else
332
+ set_field_error(param, null_error_message)
333
+ end
334
+ end
335
+ end
336
+
337
+ def validate_oauth_application_params
338
+ oauth_application_params.each do |key, value|
339
+ if key == oauth_application_homepage_url_param ||
340
+ key == oauth_application_redirect_uri_param
341
+
342
+ set_field_error(key, invalid_url_message) unless URI::DEFAULT_PARSER.make_regexp(%w[http https]).match?(value)
343
+
344
+ elsif key == oauth_application_scopes_param
345
+
346
+ value.each do |scope|
347
+ set_field_error(key, invalid_scope_message) unless oauth_application_scopes.include?(scope)
348
+ end
349
+ end
350
+ end
351
+
352
+ throw :rodauth_error if @field_errors && !@field_errors.empty?
353
+ end
354
+
355
+ def create_oauth_application
356
+ create_params = {
357
+ oauth_applications_account_id_column => account_id,
358
+ oauth_applications_name_column => oauth_application_params[oauth_application_name_param],
359
+ oauth_applications_description_column => oauth_application_params[oauth_application_description_param],
360
+ oauth_applications_scopes_column => oauth_application_params[oauth_application_scopes_param],
361
+ oauth_applications_homepage_url_column => oauth_application_params[oauth_application_homepage_url_param],
362
+ oauth_applications_redirect_uri_column => oauth_application_params[oauth_application_redirect_uri_param]
363
+ }
364
+
365
+ # set client ID/secret pairs
366
+ create_params.merge! \
367
+ oauth_applications_client_id_column => oauth_unique_id_generator,
368
+ oauth_applications_client_secret_column => oauth_unique_id_generator
369
+
370
+ create_params[oauth_applications_scopes_column] = if create_params[oauth_applications_scopes_column]
371
+ create_params[oauth_applications_scopes_column].join(",")
372
+ else
373
+ oauth_application_default_scope
374
+ end
375
+
376
+ ds = db[oauth_applications_table]
377
+
378
+ id = nil
379
+ raised = begin
380
+ id = if ds.supports_returning?(:insert)
381
+ ds.returning(oauth_applications_id_column).insert(create_params)
382
+ else
383
+ id = db[oauth_applications_table].insert(create_params)
384
+ db[oauth_applications_table].where(oauth_applications_id_column => id).get(oauth_applications_id_column)
385
+ end
386
+ false
387
+ rescue Sequel::ConstraintViolation => e
388
+ e
389
+ end
390
+
391
+ if raised
392
+ field = raised.message[/\.(.*)$/, 1]
393
+ case raised
394
+ when Sequel::UniqueConstraintViolation
395
+ throw_error(field, unique_error_message)
396
+ when Sequel::NotNullConstraintViolation
397
+ throw_error(field, null_error_message)
398
+ end
399
+ end
400
+
401
+ !raised && id
402
+ end
403
+
404
+ # Authorize
405
+
406
+ def validate_oauth_grant_params
407
+ unless oauth_application && check_valid_redirect_uri? && check_valid_access_type?
408
+ redirect_response_error("invalid_request")
409
+ end
410
+ redirect_response_error("invalid_scope") unless check_valid_scopes?
411
+ end
412
+
413
+ def create_oauth_grant
414
+ create_params = {
415
+ oauth_grants_account_id_column => account_id,
416
+ oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
417
+ oauth_grants_redirect_uri_column => redirect_uri,
418
+ oauth_grants_code_column => oauth_unique_id_generator,
419
+ oauth_grants_expires_in_column => Time.now + oauth_grant_expires_in,
420
+ oauth_grants_scopes_column => scopes.join(",")
421
+ }
422
+
423
+ unless (access_type = param("access_type")).empty?
424
+ create_params[oauth_grants_access_type_column] = access_type
425
+ end
426
+
427
+ ds = db[oauth_grants_table]
428
+
429
+ begin
430
+ if ds.supports_returning?(:insert)
431
+ ds.returning(authorize_code_column).insert(create_params)
432
+ else
433
+ id = ds.insert(create_params)
434
+ ds.where(oauth_grants_id_column => id).get(oauth_grants_code_column)
435
+ end
436
+ rescue Sequel::UniqueConstraintViolation
437
+ retry
438
+ end
439
+ end
440
+
441
+ # Access Tokens
442
+
443
+ def validate_oauth_token_params
444
+ redirect_response_error("invalid_request") unless param(client_id_param)
445
+
446
+ unless (grant_type = param(grant_type_param))
447
+ redirect_response_error("invalid_request")
448
+ end
449
+
450
+ case grant_type
451
+ when "authorization_code"
452
+ redirect_response_error("invalid_request") unless param(code_param)
453
+
454
+ when "refresh_token"
455
+ redirect_response_error("invalid_request") unless param(refresh_token_param)
456
+ else
457
+ redirect_response_error("invalid_request")
458
+ end
459
+ end
460
+
461
+ def generate_oauth_token(params = {})
462
+ create_params = {
463
+ oauth_grants_expires_in_column => Time.now + oauth_token_expires_in,
464
+ oauth_tokens_token_column => oauth_unique_id_generator
465
+ }.merge(params)
466
+
467
+ ds = db[oauth_tokens_table]
468
+
469
+ begin
470
+ if ds.supports_returning?(:insert)
471
+ ds.returning.insert(create_params)
472
+ else
473
+ id = ds.insert(create_params)
474
+ ds.where(oauth_tokens_id_column => id).first
475
+ end
476
+ rescue Sequel::UniqueConstraintViolation
477
+ retry
478
+ end
479
+ end
480
+
481
+ def create_oauth_token
482
+ case param(grant_type_param)
483
+ when "authorization_code"
484
+ # fetch oauth grant
485
+ oauth_grant = db[oauth_grants_table].where(
486
+ oauth_grants_code_column => param(code_param),
487
+ oauth_grants_redirect_uri_column => param(redirect_uri_param),
488
+ oauth_grants_oauth_application_id_column => db[oauth_applications_table].where(
489
+ oauth_applications_client_id_column => param(client_id_param),
490
+ oauth_applications_account_id_column => oauth_applications_account_id_column
491
+ ).select(oauth_applications_id_column)
492
+ ).where(Sequel[oauth_grants_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
493
+ .where(oauth_grants_revoked_at_column => nil)
494
+ .first
495
+
496
+ redirect_response_error("invalid_grant") unless oauth_grant
497
+
498
+ create_params = {
499
+ oauth_tokens_account_id_column => oauth_grant[oauth_grants_account_id_column],
500
+ oauth_tokens_oauth_application_id_column => oauth_grant[oauth_grants_oauth_application_id_column],
501
+ oauth_tokens_oauth_grant_id_column => oauth_grant[oauth_grants_id_column],
502
+ oauth_tokens_scopes_column => oauth_grant[oauth_grants_scopes_column]
503
+ }
504
+
505
+ if oauth_grant[oauth_grants_access_type_column] == "offline"
506
+ create_params[oauth_tokens_refresh_token_column] = oauth_unique_id_generator
507
+ end
508
+ # revoke oauth grant
509
+ db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
510
+ .update(oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
511
+
512
+ generate_oauth_token(create_params)
513
+ when "refresh_token"
514
+ # fetch oauth grant
515
+ oauth_token = db[oauth_tokens_table].where(
516
+ oauth_tokens_refresh_token_column => param(refresh_token_param),
517
+ oauth_tokens_oauth_application_id_column => db[oauth_applications_table].where(
518
+ oauth_applications_client_id_column => param(client_id_param),
519
+ oauth_applications_account_id_column => account_id
520
+ ).select(oauth_applications_id_column)
521
+ ).where(oauth_grants_revoked_at_column => nil).first
522
+
523
+ redirect_response_error("invalid_grant") unless oauth_token
524
+
525
+ update_params = {
526
+ oauth_tokens_oauth_application_id_column => oauth_token[oauth_grants_oauth_application_id_column],
527
+ oauth_tokens_expires_in_column => Time.now + oauth_token_expires_in,
528
+ oauth_tokens_token_column => oauth_unique_id_generator
529
+ }
530
+
531
+ ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
532
+ begin
533
+ if ds.supports_returning?(:update)
534
+ ds.returning.update(update_params)
535
+ else
536
+ ds.update(update_params)
537
+ ds.first
538
+ end
539
+ rescue Sequel::UniqueConstraintViolation
540
+ retry
541
+ end
542
+ else
543
+ redirect_response_error("invalid_grant")
544
+ end
545
+ end
546
+
547
+ # Token revocation
548
+
549
+ TOKEN_HINT_TYPES = %w[access_token refresh_token].freeze
550
+
551
+ def validate_oauth_revoke_params
552
+ # check if valid token hint type
553
+ redirect_response_error("unsupported_token_type") unless TOKEN_HINT_TYPES.include?(token_type_hint)
554
+
555
+ redirect_response_error("invalid_request") unless param(token_param)
556
+ end
557
+
558
+ def revoke_oauth_token
559
+ # one can only revoke tokens which haven't been revoked before, and which are
560
+ # either our tokens, or tokens from applications we own.
561
+ ds = db[oauth_tokens_table]
562
+ .where(oauth_tokens_revoked_at_column => nil)
563
+ .where(
564
+ Sequel.or(
565
+ oauth_tokens_account_id_column => account_id,
566
+ oauth_tokens_oauth_application_id_column => db[oauth_applications_table].where(
567
+ oauth_applications_client_id_column => param(client_id_param),
568
+ oauth_applications_account_id_column => account_id
569
+ ).select(oauth_applications_id_column)
570
+ )
571
+ )
572
+ ds = case token_type_hint
573
+ when "access_token"
574
+ ds.where(oauth_tokens_token_column => token)
575
+ when "refresh_token"
576
+ ds.where(oauth_tokens_refresh_token_column => token)
577
+ end
578
+
579
+ oauth_token = ds.first
580
+ redirect_response_error("invalid_request") unless oauth_token
581
+
582
+ update_params = { oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP }
583
+
584
+ ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
585
+
586
+ if ds.supports_returning?(:update)
587
+ ds.returning.update(update_params)
588
+ else
589
+ ds.update(update_params)
590
+ ds.first
591
+ end
592
+
593
+ # If the particular
594
+ # token is a refresh token and the authorization server supports the
595
+ # revocation of access tokens, then the authorization server SHOULD
596
+ # also invalidate all access tokens based on the same authorization
597
+ # grant
598
+ #
599
+ # we don't need to do anything here, as we revalidate existing tokens
600
+ end
601
+
602
+ # Response helpers
603
+
604
+ def redirect_response_error(error_code, redirect_url = request.referer || default_redirect)
605
+ if json_request?
606
+ throw_json_response_error(invalid_oauth_response_status, error_code)
607
+ else
608
+ redirect_url = URI.parse(redirect_url)
609
+ query_params = ["error=#{error_code}"]
610
+ if respond_to?(:"#{error_code}_message")
611
+ message = send(:"#{error_code}_message")
612
+ query_params << ["error_description=#{CGI.escape(message)}"]
613
+ end
614
+ query_params << redirect_url.query if redirect_url.query
615
+ redirect_url.query = query_params.join("&")
616
+ redirect(redirect_url.to_s)
617
+ end
618
+ end
619
+
620
+ def throw_json_response_error(status, error_code)
621
+ set_response_error_status(status)
622
+ payload = { "error" => error_code }
623
+ payload["error_description"] = send(:"#{error_code}_message") if respond_to?(:"#{error_code}_message")
624
+ json_payload = if request.respond_to?(:convert_to_json)
625
+ request.send(:convert_to_json, payload)
626
+ else
627
+ JSON.dump(payload)
628
+ end
629
+ response["Content-Type"] ||= json_response_content_type
630
+ response["WWW-Authenticate"] = "Bearer" if status == 401
631
+ response.write(json_payload)
632
+ request.halt
633
+ end
634
+
635
+ def authorization_required
636
+ if json_request?
637
+ throw_json_response_error(authorization_required_error_status, "invalid_client")
638
+ else
639
+ set_redirect_error_flash(require_authorization_error_flash)
640
+ redirect(require_authorization_redirect)
641
+ end
642
+ end
643
+
644
+ def check_valid_scopes?
645
+ return false unless scopes
646
+
647
+ (scopes - oauth_application[oauth_applications_scopes_column].split(",")).empty?
648
+ end
649
+
650
+ def check_valid_redirect_uri?
651
+ redirect_uri == oauth_application[oauth_applications_redirect_uri_column]
652
+ end
653
+
654
+ ACCESS_TYPES = %w[offline online].freeze
655
+
656
+ def check_valid_access_type?
657
+ access_type = param("access_type")
658
+ access_type.empty? || ACCESS_TYPES.include?(access_type)
659
+ end
660
+
661
+ def check_valid_response_type?
662
+ response_type = param("response_type")
663
+
664
+ return true if response_type.empty? || response_type == "code"
665
+
666
+ return use_oauth_implicit_grant_type if response_type == "token"
667
+
668
+ false
669
+ end
670
+
671
+ # /oauth-token
672
+ route(:oauth_token) do |r|
673
+ throw_json_response_error(authorization_required_error_status, "invalid_client") unless logged_in?
674
+
675
+ # access-token
676
+ r.post do
677
+ catch_error do
678
+ validate_oauth_token_params
679
+
680
+ oauth_token = nil
681
+ transaction do
682
+ before_token
683
+ oauth_token = create_oauth_token
684
+ after_token
685
+ end
686
+
687
+ response.status = 200
688
+ response["Content-Type"] ||= json_response_content_type
689
+ json_response = {
690
+ "token" => oauth_token[:token],
691
+ "token_type" => oauth_token_type,
692
+ "expires_in" => oauth_token_expires_in
693
+ }
694
+
695
+ json_response["refresh_token"] = oauth_token[:refresh_token] if oauth_token[:refresh_token]
696
+
697
+ json_payload = if request.respond_to?(:convert_to_json)
698
+ request.send(:convert_to_json, json_response)
699
+ else
700
+ JSON.dump(json_response)
701
+ end
702
+ response.write(json_payload)
703
+ request.halt
704
+ end
705
+
706
+ throw_json_response_error(invalid_oauth_response_status, "invalid_request")
707
+ end
708
+ end
709
+
710
+ # /oauth-revoke
711
+ route(:oauth_revoke) do |r|
712
+ require_account
713
+
714
+ # access-token
715
+ r.post do
716
+ catch_error do
717
+ validate_oauth_revoke_params
718
+
719
+ oauth_token = nil
720
+ transaction do
721
+ before_revoke
722
+ oauth_token = revoke_oauth_token
723
+ after_revoke
724
+ end
725
+
726
+ if json_request?
727
+ response.status = 200
728
+ response["Content-Type"] ||= json_response_content_type
729
+ json_response = {
730
+ "token" => oauth_token[:token],
731
+ "refresh_token" => oauth_token[:refresh_token],
732
+ "revoked_at" => oauth_token[:revoked_at]
733
+ }
734
+ json_payload = if request.respond_to?(:convert_to_json)
735
+ request.send(:convert_to_json, json_response)
736
+ else
737
+ JSON.dump(json_response)
738
+ end
739
+ response.write(json_payload)
740
+ request.halt
741
+ else
742
+ set_notice_flash revoke_oauth_token_notice_flash
743
+ redirect request.referer || "/"
744
+ end
745
+ end
746
+
747
+ throw_json_response_error(invalid_oauth_response_status, "invalid_request")
748
+ end
749
+ end
750
+
751
+ # /oauth-authorize
752
+ route(:oauth_authorize) do |r|
753
+ require_account
754
+
755
+ r.get do
756
+ validate_oauth_grant_params
757
+ authorize_view
758
+ end
759
+
760
+ r.post do
761
+ validate_oauth_grant_params
762
+
763
+ code = nil
764
+ query_params = []
765
+ fragment_params = []
766
+
767
+ transaction do
768
+ before_authorize
769
+ case param(response_type_param)
770
+ when "token"
771
+ redirect_response_error("invalid_request", redirect_uri) unless use_oauth_implicit_grant_type
772
+
773
+ create_params = {
774
+ oauth_tokens_account_id_column => account_id,
775
+ oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
776
+ oauth_tokens_scopes_column => scopes
777
+ }
778
+ oauth_token = generate_oauth_token(create_params)
779
+
780
+ fragment_params << ["access_token=#{oauth_token[:token]}"]
781
+ fragment_params << ["token_type=#{oauth_token_type}"]
782
+ fragment_params << ["expires_in=#{oauth_token_expires_in}"]
783
+ when "code", "", nil
784
+ code = create_oauth_grant
785
+ query_params << ["code=#{code}"]
786
+ else
787
+ redirect_response_error("invalid_request")
788
+ end
789
+ after_authorize
790
+ end
791
+
792
+ redirect_url = URI.parse(redirect_uri)
793
+ query_params << "state=#{state}" if state
794
+ query_params << redirect_url.query if redirect_url.query
795
+ redirect_url.query = query_params.join("&") unless query_params.empty?
796
+ redirect_url.fragment = fragment_params.join("&") unless fragment_params.empty?
797
+
798
+ redirect(redirect_url.to_s)
799
+ end
800
+ end
801
+ end
802
+ end