rodauth-oauth 0.7.4 → 0.8.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -424
  3. data/README.md +26 -389
  4. data/doc/release_notes/0_0_1.md +3 -0
  5. data/doc/release_notes/0_0_2.md +15 -0
  6. data/doc/release_notes/0_0_3.md +31 -0
  7. data/doc/release_notes/0_0_4.md +36 -0
  8. data/doc/release_notes/0_0_5.md +36 -0
  9. data/doc/release_notes/0_0_6.md +21 -0
  10. data/doc/release_notes/0_1_0.md +44 -0
  11. data/doc/release_notes/0_2_0.md +43 -0
  12. data/doc/release_notes/0_3_0.md +28 -0
  13. data/doc/release_notes/0_4_0.md +18 -0
  14. data/doc/release_notes/0_4_1.md +9 -0
  15. data/doc/release_notes/0_4_2.md +5 -0
  16. data/doc/release_notes/0_4_3.md +3 -0
  17. data/doc/release_notes/0_5_0.md +11 -0
  18. data/doc/release_notes/0_5_1.md +13 -0
  19. data/doc/release_notes/0_6_0.md +9 -0
  20. data/doc/release_notes/0_6_1.md +6 -0
  21. data/doc/release_notes/0_7_0.md +20 -0
  22. data/doc/release_notes/0_7_1.md +10 -0
  23. data/doc/release_notes/0_7_2.md +21 -0
  24. data/doc/release_notes/0_7_3.md +10 -0
  25. data/doc/release_notes/0_7_4.md +5 -0
  26. data/doc/release_notes/0_8_0.md +37 -0
  27. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +3 -3
  28. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +11 -0
  29. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +20 -0
  30. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +22 -10
  31. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +11 -5
  32. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +38 -0
  33. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +5 -5
  34. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +11 -15
  35. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +9 -1
  36. data/lib/rodauth/features/oauth.rb +3 -1418
  37. data/lib/rodauth/features/oauth_application_management.rb +209 -0
  38. data/lib/rodauth/features/oauth_assertion_base.rb +96 -0
  39. data/lib/rodauth/features/oauth_authorization_code_grant.rb +249 -0
  40. data/lib/rodauth/features/oauth_authorization_server.rb +0 -0
  41. data/lib/rodauth/features/oauth_base.rb +735 -0
  42. data/lib/rodauth/features/oauth_device_grant.rb +221 -0
  43. data/lib/rodauth/features/oauth_http_mac.rb +3 -21
  44. data/lib/rodauth/features/oauth_implicit_grant.rb +59 -0
  45. data/lib/rodauth/features/oauth_jwt.rb +37 -60
  46. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +59 -0
  47. data/lib/rodauth/features/oauth_pkce.rb +98 -0
  48. data/lib/rodauth/features/oauth_resource_server.rb +21 -0
  49. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +102 -0
  50. data/lib/rodauth/features/oauth_token_introspection.rb +108 -0
  51. data/lib/rodauth/features/oauth_token_management.rb +77 -0
  52. data/lib/rodauth/features/oauth_token_revocation.rb +109 -0
  53. data/lib/rodauth/features/oidc.rb +4 -3
  54. data/lib/rodauth/oauth/database_extensions.rb +15 -2
  55. data/lib/rodauth/oauth/refinements.rb +48 -0
  56. data/lib/rodauth/oauth/version.rb +1 -1
  57. data/locales/en.yml +28 -12
  58. data/templates/authorize.str +7 -7
  59. data/templates/client_secret_field.str +2 -2
  60. data/templates/description_field.str +1 -1
  61. data/templates/device_search.str +11 -0
  62. data/templates/device_verification.str +24 -0
  63. data/templates/homepage_url_field.str +2 -2
  64. data/templates/jws_jwk_field.str +4 -0
  65. data/templates/jwt_public_key_field.str +4 -0
  66. data/templates/name_field.str +1 -1
  67. data/templates/new_oauth_application.str +9 -0
  68. data/templates/oauth_application.str +7 -3
  69. data/templates/oauth_application_oauth_tokens.str +51 -0
  70. data/templates/oauth_applications.str +2 -2
  71. data/templates/oauth_tokens.str +9 -11
  72. data/templates/redirect_uri_field.str +2 -2
  73. metadata +71 -3
  74. data/lib/rodauth/features/oauth_saml.rb +0 -104
@@ -0,0 +1,102 @@
1
+ # frozen-string-literal: true
2
+
3
+ require "onelogin/ruby-saml"
4
+
5
+ module Rodauth
6
+ Feature.define(:oauth_saml_bearer_grant, :OauthSamlBearerGrant) do
7
+ depends :oauth_assertion_base
8
+
9
+ auth_value_method :oauth_saml_cert_fingerprint, "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D"
10
+ auth_value_method :oauth_saml_cert, nil
11
+ auth_value_method :oauth_saml_cert_fingerprint_algorithm, nil
12
+ auth_value_method :oauth_saml_name_identifier_format, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
13
+
14
+ auth_value_method :oauth_saml_security_authn_requests_signed, true
15
+ auth_value_method :oauth_saml_security_metadata_signed, true
16
+ auth_value_method :oauth_saml_security_digest_method, XMLSecurity::Document::SHA1
17
+ auth_value_method :oauth_saml_security_signature_method, XMLSecurity::Document::RSA_SHA1
18
+
19
+ auth_value_methods(
20
+ :require_oauth_application_from_saml2_bearer_assertion_issuer,
21
+ :require_oauth_application_from_saml2_bearer_assertion_subject,
22
+ :account_from_saml2_bearer_assertion
23
+ )
24
+
25
+ private
26
+
27
+ def require_oauth_application_from_saml2_bearer_assertion_issuer(assertion)
28
+ saml = saml_assertion(assertion)
29
+
30
+ return unless saml
31
+
32
+ db[oauth_applications_table].where(
33
+ oauth_applications_homepage_url_column => saml.issuers
34
+ ).first
35
+ end
36
+
37
+ def require_oauth_application_from_saml2_bearer_assertion_subject(assertion)
38
+ saml = saml_assertion(assertion)
39
+
40
+ return unless saml
41
+
42
+ db[oauth_applications_table].where(
43
+ oauth_applications_client_id_column => saml.nameid
44
+ ).first
45
+ end
46
+
47
+ def account_from_saml2_bearer_assertion(assertion)
48
+ saml = saml_assertion(assertion)
49
+
50
+ return unless saml
51
+
52
+ account_from_bearer_assertion_subject(saml.nameid)
53
+ end
54
+
55
+ def saml_assertion(assertion)
56
+ settings = OneLogin::RubySaml::Settings.new
57
+ settings.idp_cert = oauth_saml_cert
58
+ settings.idp_cert_fingerprint = oauth_saml_cert_fingerprint
59
+ settings.idp_cert_fingerprint_algorithm = oauth_saml_cert_fingerprint_algorithm
60
+ settings.name_identifier_format = oauth_saml_name_identifier_format
61
+ settings.security[:authn_requests_signed] = oauth_saml_security_authn_requests_signed
62
+ settings.security[:metadata_signed] = oauth_saml_security_metadata_signed
63
+ settings.security[:digest_method] = oauth_saml_security_digest_method
64
+ settings.security[:signature_method] = oauth_saml_security_signature_method
65
+
66
+ response = OneLogin::RubySaml::Response.new(assertion, settings: settings, skip_recipient_check: true)
67
+
68
+ # 3. he Assertion MUST have an expiry that limits the time window ...
69
+ # 4. The Assertion MUST have an expiry that limits the time window ...
70
+ # 5. The <Subject> element MUST contain at least one ...
71
+ # 6. The authorization server MUST reject the entire Assertion if the ...
72
+ # 7. If the Assertion issuer directly authenticated the subject, ...
73
+ redirect_response_error("invalid_grant") unless response.is_valid?
74
+
75
+ # In order to issue an access token response as described in OAuth 2.0
76
+ # [RFC6749] or to rely on an Assertion for client authentication, the
77
+ # authorization server MUST validate the Assertion according to the
78
+ # criteria below.
79
+
80
+ # 1. The Assertion's <Issuer> element MUST contain a unique identifier
81
+ # for the entity that issued the Assertion.
82
+ redirect_response_error("invalid_grant") unless response.issuers.size == 1
83
+
84
+ # 2. in addition to the URI references
85
+ # discussed there, the token endpoint URL of the authorization
86
+ # server MAY be used as a URI that identifies the authorization
87
+ # server as an intended audience. The authorization server MUST
88
+ # reject any Assertion that does not contain its own identity as
89
+ # the intended audience.
90
+ redirect_response_error("invalid_grant") if response.audiences && !response.audiences.include?(token_url)
91
+
92
+ response
93
+ end
94
+
95
+ def oauth_server_metadata_body(*)
96
+ super.tap do |data|
97
+ data[:grant_types_supported] << "urn:ietf:params:oauth:grant-type:saml2-bearer"
98
+ data[:token_endpoint_auth_methods_supported] << "urn:ietf:params:oauth:client-assertion-type:saml2-bearer"
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oauth_token_introspection, :OauthTokenIntrospection) do
5
+ depends :oauth_base
6
+
7
+ before "introspect"
8
+
9
+ auth_value_methods(
10
+ :before_introspection_request
11
+ )
12
+
13
+ # /introspect
14
+ route(:introspect) do |r|
15
+ next unless is_authorization_server?
16
+
17
+ before_introspect_route
18
+
19
+ r.post do
20
+ catch_error do
21
+ validate_oauth_introspect_params
22
+
23
+ before_introspect
24
+ oauth_token = case param("token_type_hint")
25
+ when "access_token"
26
+ oauth_token_by_token(param("token"))
27
+ when "refresh_token"
28
+ oauth_token_by_refresh_token(param("token"))
29
+ else
30
+ oauth_token_by_token(param("token")) || oauth_token_by_refresh_token(param("token"))
31
+ end
32
+
33
+ if oauth_application
34
+ redirect_response_error("invalid_request") if oauth_token && !token_from_application?(oauth_token, oauth_application)
35
+ elsif oauth_token
36
+ @oauth_application = db[oauth_applications_table].where(oauth_applications_id_column =>
37
+ oauth_token[oauth_tokens_oauth_application_id_column]).first
38
+ end
39
+
40
+ json_response_success(json_token_introspect_payload(oauth_token))
41
+ end
42
+
43
+ throw_json_response_error(invalid_oauth_response_status, "invalid_request")
44
+ end
45
+ end
46
+
47
+ # Token introspect
48
+
49
+ def validate_oauth_introspect_params(token_hint_types = %w[access_token refresh_token].freeze)
50
+ # check if valid token hint type
51
+ if param_or_nil("token_type_hint") && !token_hint_types.include?(param("token_type_hint"))
52
+ redirect_response_error("unsupported_token_type")
53
+ end
54
+
55
+ redirect_response_error("invalid_request") unless param_or_nil("token")
56
+ end
57
+
58
+ def json_token_introspect_payload(token)
59
+ return { active: false } unless token
60
+
61
+ {
62
+ active: true,
63
+ scope: token[oauth_tokens_scopes_column],
64
+ client_id: oauth_application[oauth_applications_client_id_column],
65
+ # username
66
+ token_type: oauth_token_type,
67
+ exp: token[oauth_tokens_expires_in_column].to_i
68
+ }
69
+ end
70
+
71
+ def check_csrf?
72
+ case request.path
73
+ when introspect_path
74
+ false
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def introspection_request(token_type_hint, token)
83
+ auth_url = URI(authorization_server_url)
84
+ http = Net::HTTP.new(auth_url.host, auth_url.port)
85
+ http.use_ssl = auth_url.scheme == "https"
86
+
87
+ request = Net::HTTP::Post.new(introspect_path)
88
+ request["content-type"] = "application/x-www-form-urlencoded"
89
+ request["accept"] = json_response_content_type
90
+ request.set_form_data({ "token_type_hint" => token_type_hint, "token" => token })
91
+
92
+ before_introspection_request(request)
93
+ response = http.request(request)
94
+ authorization_required unless response.code.to_i == 200
95
+
96
+ JSON.parse(response.body)
97
+ end
98
+
99
+ def before_introspection_request(request); end
100
+
101
+ def oauth_server_metadata_body(*)
102
+ super.tap do |data|
103
+ data[:introspection_endpoint] = introspect_url
104
+ data[:introspection_endpoint_auth_methods_supported] = %w[client_secret_basic]
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oauth_token_management, :OauthTokenManagement) do
5
+ using RegexpExtensions
6
+
7
+ depends :oauth_base
8
+
9
+ view "oauth_tokens", "My Oauth Tokens", "oauth_tokens"
10
+
11
+ button "Revoke", "oauth_token_revoke"
12
+
13
+ auth_value_method :oauth_tokens_path, "oauth-tokens"
14
+
15
+ %w[token refresh_token expires_in revoked_at].each do |param|
16
+ translatable_method :"oauth_tokens_#{param}_label", param.gsub("_", " ").capitalize
17
+ end
18
+
19
+ auth_value_method :oauth_tokens_route, "oauth-tokens"
20
+ auth_value_method :oauth_tokens_id_pattern, Integer
21
+
22
+ auth_value_methods(
23
+ :oauth_token_path
24
+ )
25
+
26
+ def oauth_tokens_path(opts = {})
27
+ route_path(oauth_tokens_route, opts)
28
+ end
29
+
30
+ def oauth_tokens_url(opts = {})
31
+ route_url(oauth_tokens_route, opts)
32
+ end
33
+
34
+ def oauth_token_path(id)
35
+ "#{oauth_tokens_path}/#{id}"
36
+ end
37
+
38
+ def oauth_tokens
39
+ request.on(oauth_tokens_route) do
40
+ require_account
41
+
42
+ request.get do
43
+ scope.instance_variable_set(:@oauth_tokens, db[oauth_tokens_table]
44
+ .select(Sequel[oauth_tokens_table].*, Sequel[oauth_applications_table][oauth_applications_name_column])
45
+ .join(oauth_applications_table, Sequel[oauth_tokens_table][oauth_tokens_oauth_application_id_column] =>
46
+ Sequel[oauth_applications_table][oauth_applications_id_column])
47
+ .where(Sequel[oauth_tokens_table][oauth_tokens_account_id_column] => account_id)
48
+ .where(oauth_tokens_revoked_at_column => nil))
49
+ oauth_tokens_view
50
+ end
51
+
52
+ request.post(oauth_tokens_id_pattern) do |id|
53
+ db[oauth_tokens_table]
54
+ .where(oauth_tokens_id_column => id)
55
+ .where(oauth_tokens_account_id_column => account_id)
56
+ .update(oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
57
+
58
+ set_notice_flash revoke_oauth_token_notice_flash
59
+ redirect oauth_tokens_path || "/"
60
+ end
61
+ end
62
+ end
63
+
64
+ def check_csrf?
65
+ case request.path
66
+ when oauth_tokens_path
67
+ only_json? ? false : super
68
+ else
69
+ super
70
+ end
71
+ end
72
+
73
+ def check_valid_uri?(uri)
74
+ URI::DEFAULT_PARSER.make_regexp(oauth_valid_uri_schemes).match?(uri)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oauth_token_revocation, :OauthTokenRevocation) do
5
+ depends :oauth_base
6
+
7
+ before "revoke"
8
+ after "revoke"
9
+
10
+ notice_flash "The oauth token has been revoked", "revoke_oauth_token"
11
+
12
+ # /revoke
13
+ route(:revoke) do |r|
14
+ next unless is_authorization_server?
15
+
16
+ before_revoke_route
17
+
18
+ if logged_in?
19
+ require_account
20
+ require_oauth_application_from_account
21
+ else
22
+ require_oauth_application
23
+ end
24
+
25
+ r.post do
26
+ catch_error do
27
+ validate_oauth_revoke_params
28
+
29
+ oauth_token = nil
30
+ transaction do
31
+ before_revoke
32
+ oauth_token = revoke_oauth_token
33
+ after_revoke
34
+ end
35
+
36
+ if accepts_json?
37
+ json_response_success \
38
+ "token" => oauth_token[oauth_tokens_token_column],
39
+ "refresh_token" => oauth_token[oauth_tokens_refresh_token_column],
40
+ "revoked_at" => convert_timestamp(oauth_token[oauth_tokens_revoked_at_column])
41
+ else
42
+ set_notice_flash revoke_oauth_token_notice_flash
43
+ redirect request.referer || "/"
44
+ end
45
+ end
46
+
47
+ redirect_response_error("invalid_request", request.referer || "/")
48
+ end
49
+ end
50
+
51
+ def validate_oauth_revoke_params(token_hint_types = %w[access_token refresh_token].freeze)
52
+ # check if valid token hint type
53
+ if param_or_nil("token_type_hint") && !token_hint_types.include?(param("token_type_hint"))
54
+ redirect_response_error("unsupported_token_type")
55
+ end
56
+
57
+ redirect_response_error("invalid_request") unless param_or_nil("token")
58
+ end
59
+
60
+ def check_csrf?
61
+ case request.path
62
+ when revoke_path
63
+ !json_request?
64
+ else
65
+ super
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def revoke_oauth_token
72
+ token = param("token")
73
+
74
+ oauth_token = if param("token_type_hint") == "refresh_token"
75
+ oauth_token_by_refresh_token(token)
76
+ else
77
+ oauth_token_by_token(token)
78
+ end
79
+
80
+ redirect_response_error("invalid_request") unless oauth_token
81
+
82
+ redirect_response_error("invalid_request") unless token_from_application?(oauth_token, oauth_application)
83
+
84
+ update_params = { oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP }
85
+
86
+ ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
87
+
88
+ oauth_token = __update_and_return__(ds, update_params)
89
+
90
+ oauth_token[oauth_tokens_token_column] = token
91
+ oauth_token
92
+
93
+ # If the particular
94
+ # token is a refresh token and the authorization server supports the
95
+ # revocation of access tokens, then the authorization server SHOULD
96
+ # also invalidate all access tokens based on the same authorization
97
+ # grant
98
+ #
99
+ # we don't need to do anything here, as we revalidate existing tokens
100
+ end
101
+
102
+ def oauth_server_metadata_body(*)
103
+ super.tap do |data|
104
+ data[:revocation_endpoint] = revoke_url
105
+ data[:revocation_endpoint_auth_methods_supported] = nil # because it's client_secret_basic
106
+ end
107
+ end
108
+ end
109
+ end
@@ -283,7 +283,7 @@ module Rodauth
283
283
  redirect_response_error("consent_required")
284
284
  end
285
285
  when "select-account"
286
- # obly works if select_account plugin is available
286
+ # only works if select_account plugin is available
287
287
  require_select_account if respond_to?(:require_select_account)
288
288
  else
289
289
  redirect_response_error("invalid_request")
@@ -302,7 +302,7 @@ module Rodauth
302
302
  super(oauth_grant, create_params.merge(oauth_tokens_nonce_column => oauth_grant[oauth_grants_nonce_column]))
303
303
  end
304
304
 
305
- def create_oauth_token
305
+ def create_oauth_token(*)
306
306
  oauth_token = super
307
307
  generate_id_token(oauth_token)
308
308
  oauth_token
@@ -461,7 +461,8 @@ module Rodauth
461
461
  scope_claims.unshift("auth_time") if last_account_login_at
462
462
 
463
463
  response_types_supported = metadata[:response_types_supported]
464
- if use_oauth_implicit_grant_type?
464
+
465
+ if metadata[:grant_types_supported].include?("implicit")
465
466
  response_types_supported += ["none", "id_token", "code token", "code id_token", "id_token token", "code id_token token"]
466
467
  end
467
468
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rodauth
4
4
  module OAuth
5
- # rubocop:disable Naming/MethodName, Metrics/ParameterLists
5
+ # rubocop:disable Naming/MethodName
6
6
  def self.ExtendDatabase(db)
7
7
  Module.new do
8
8
  dataset = db.dataset
@@ -42,6 +42,14 @@ module Rodauth
42
42
 
43
43
  __insert_and_return__(dataset, pkey, params)
44
44
  end
45
+
46
+ def __insert_or_do_nothing_and_return__(dataset, pkey, unique_columns, params)
47
+ __insert_and_return__(
48
+ dataset.insert_conflict(target: unique_columns),
49
+ pkey,
50
+ params
51
+ ) || dataset.where(params).first
52
+ end
45
53
  else
46
54
  def __insert_or_update_and_return__(dataset, pkey, unique_columns, params, conds = nil, exclude_on_update = nil)
47
55
  find_params, update_params = params.partition { |key, _| unique_columns.include?(key) }.map { |h| Hash[h] }
@@ -65,9 +73,14 @@ module Rodauth
65
73
  __insert_and_return__(dataset, pkey, params)
66
74
  end
67
75
  end
76
+
77
+ def __insert_or_do_nothing_and_return__(dataset, pkey, unique_columns, params)
78
+ find_params = params.select { |key, _| unique_columns.include?(key) }
79
+ dataset.where(find_params).first || __insert_and_return__(dataset, pkey, params)
80
+ end
68
81
  end
69
82
  end
70
83
  end
71
- # rubocop:enable Naming/MethodName, Metrics/ParameterLists
84
+ # rubocop:enable Naming/MethodName
72
85
  end
73
86
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ module PrefixExtensions
5
+ unless String.method_defined?(:delete_prefix)
6
+ refine(String) do
7
+ def delete_suffix(suffix)
8
+ suffix = suffix.to_s
9
+ len = suffix.length
10
+ return dup unless len.positive? && index(suffix, -len)
11
+
12
+ self[0...-len]
13
+ end
14
+
15
+ def delete_prefix(prefix)
16
+ prefix = prefix.to_s
17
+ return dup unless rindex(prefix, 0)
18
+
19
+ self[prefix.length..-1]
20
+ end
21
+ end
22
+ end
23
+
24
+ unless String.method_defined?(:delete_suffix!)
25
+ refine(String) do
26
+ def delete_suffix!(suffix)
27
+ suffix = suffix.to_s
28
+ chomp! if frozen?
29
+ len = suffix.length
30
+ return unless len.positive? && index(suffix, -len)
31
+
32
+ self[-len..-1] = ""
33
+ self
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ module RegexpExtensions
40
+ unless Regexp.method_defined?(:match?)
41
+ refine(Regexp) do
42
+ def match?(*args)
43
+ !match(*args).nil?
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rodauth
4
4
  module OAuth
5
- VERSION = "0.7.4"
5
+ VERSION = "0.8.0"
6
6
  end
7
7
  end
data/locales/en.yml CHANGED
@@ -5,22 +5,34 @@ en:
5
5
  create_oauth_application_notice_flash: "Your oauth application has been registered"
6
6
  revoke_unauthorized_account_error_flash: "You are not authorized to revoke this token"
7
7
  revoke_oauth_token_notice_flash: "The oauth token has been revoked"
8
+ device_verification_notice_flash: "The device is verified"
9
+ user_code_not_found_error_flash: "No device to authorize with the given user code"
8
10
  oauth_authorize_title: "Authorize"
9
- oauth_oauth_applications_page_title: "Oauth Applications"
10
- oauth_oauth_application_page_title: "Oauth Application"
11
- oauth_new_oauth_application_page_title: "New Oauth Application"
12
- oauth_oauth_tokens_page_title: "Oauth Tokens"
13
- name_label: "Name"
14
- description_label: "Description"
15
- scopes_label: "Scopes"
16
- homepage_url_label: "Homepage URL"
17
- redirect_uri_label: "Redirect URL"
18
- client_secret_label: "Client Secret"
19
- client_id_label: "Client ID"
20
- oauth_applications_button: "Register"
11
+ oauth_applications_page_title: "Oauth Applications"
12
+ oauth_application_page_title: "Oauth Application"
13
+ new_oauth_application_page_title: "New Oauth Application"
14
+ oauth_application_oauth_tokens_page_title: "Application Oauth Tokens"
15
+ oauth_tokens_page_title: "My Oauth Tokens"
16
+ device_verification_page_title: "Device Verification"
17
+ device_search_page_title: "Device Search"
18
+ oauth_applications_name_label: "Name"
19
+ oauth_applications_description_label: "Description"
20
+ oauth_applications_scopes_label: "Scopes"
21
+ oauth_applications_homepage_url_label: "Homepage URL"
22
+ oauth_applications_redirect_uri_label: "Redirect URL"
23
+ oauth_applications_client_secret_label: "Client Secret"
24
+ oauth_applications_client_id_label: "Client ID"
25
+ oauth_grant_user_code_label: "User code"
26
+ oauth_grant_user_jws_jwk_label: "JSON Web Keys"
27
+ oauth_grant_user_jwt_public_key_label: "Public key"
28
+ oauth_application_button: "Register"
21
29
  oauth_authorize_button: "Authorize"
22
30
  oauth_token_revoke_button: "Revoke"
23
31
  oauth_authorize_post_button: "Back to Client Application"
32
+ oauth_device_verification_button: "Verify"
33
+ oauth_device_search_button: "Search"
34
+ invalid_client_message: "Client authentication failed"
35
+ invalid_grant_type_message: "Invalid grant type"
24
36
  invalid_grant_message: "Invalid grant"
25
37
  invalid_scope_message: "Invalid scope"
26
38
  invalid_url_message: "Invalid URL"
@@ -28,6 +40,10 @@ en:
28
40
  unique_error_message: "is already in use"
29
41
  null_error_message: "is not filled"
30
42
  already_in_use_message: "error generating unique token"
43
+ expired_token_message: "the device code has expired"
44
+ access_denied_message: "the authorization request has been denied"
45
+ authorization_pending_message: "the authorization request is still pending"
46
+ slow_down_message: "authorization request is still pending but poll interval should be increased"
31
47
  code_challenge_required_message: "code challenge required"
32
48
  unsupported_transform_algorithm_message: "transform algorithm not supported"
33
49
  request_uri_not_supported_message: "request uri is unsupported"
@@ -3,23 +3,23 @@
3
3
  <p class="lead">The application #{rodauth.oauth_application[rodauth.oauth_applications_name_column]} would like to access your data.</p>
4
4
 
5
5
  <div class="form-group">
6
- <h1 class="display-6">#{rodauth.scopes_label}</h1>
6
+ <h1 class="display-6">#{rodauth.oauth_tokens_scopes_label}</h1>
7
7
 
8
8
  #{
9
9
  rodauth.scopes.map do |scope|
10
10
  if scope == rodauth.oauth_application_default_scope
11
11
  <<-HTML
12
12
  <div class="form-check">
13
- <input id="#{scope}" class="form-check-input" type="checkbox" name="scope[]" value="#{scope}" checked disabled>
14
- <label class="form-check-label" for="#{scope}">#{scope}</label>
15
- <input type="hidden" name="scope[]" value="#{scope}">
13
+ <input id="#{scope}" class="form-check-input" type="checkbox" name="scope[]" value="#{h(scope)}" checked disabled>
14
+ <label class="form-check-label" for="#{scope}">#{h(scope)}</label>
15
+ <input type="hidden" name="scope[]" value="#{h(scope)}">
16
16
  </div>
17
17
  HTML
18
18
  else
19
19
  <<-HTML
20
20
  <div class="form-check">
21
- <input id="#{scope}" class="form-check-input" type="checkbox" name="scope[]" value="#{scope}">
22
- <label class="form-check-label" for="#{scope}">#{scope}</label>
21
+ <input id="#{scope}" class="form-check-input" type="checkbox" name="scope[]" value="#{h(scope)}">
22
+ <label class="form-check-label" for="#{scope}">#{h(scope)}</label>
23
23
  </div>
24
24
  HTML
25
25
  end
@@ -39,6 +39,6 @@
39
39
  </div>
40
40
  <p class="text-center">
41
41
  <input type="submit" class="btn btn-outline-primary" value="#{h(rodauth.oauth_authorize_button)}"/>
42
- <a href="#{rodauth.redirect_uri}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request#{ "&state=#{rodauth.param("state")}" if rodauth.param_or_nil("state")}" class="btn btn-outline-danger">Cancel</a>
42
+ <a href="#{rodauth.redirect_uri}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request#{ "&state=#{rodauth.param("state")}" if rodauth.param_or_nil("state")}" class="btn btn-outline-danger">#{rodauth.oauth_cancel_button}</a>
43
43
  </p>
44
44
  </form>
@@ -1,4 +1,4 @@
1
1
  <div class="form-group">
2
- <label for="client_secret">#{rodauth.client_secret_label}#{rodauth.input_field_label_suffix}</label>
3
- #{rodauth.input_field_string(rodauth.oauth_application_client_secret_param, "client_secret", :type=>"text")}
2
+ <label for="client_secret">#{rodauth.oauth_applications_client_secret_label}#{rodauth.input_field_label_suffix}</label>
3
+ #{rodauth.input_field_string(rodauth.oauth_application_client_secret_param, "client-secret", :type=>"text")}
4
4
  </div>
@@ -1,4 +1,4 @@
1
1
  <div class="form-group">
2
- <label for="description">#{rodauth.description_label}#{rodauth.input_field_label_suffix}</label>
2
+ <label for="description">#{rodauth.oauth_applications_description_label}#{rodauth.input_field_label_suffix}</label>
3
3
  #{rodauth.input_field_string(rodauth.oauth_application_description_param, "description", :type=>"text", :required => false)}
4
4
  </div>
@@ -0,0 +1,11 @@
1
+ <form method="get" action="#{rodauth.device_path}" class="form-horizontal" role="form" id="device-search-form">
2
+ <p class="lead">Insert the user code from the device you'd like to authorize.</p>
3
+
4
+ <div class="form-group">
5
+ <label for="user_code">#{rodauth.oauth_grant_user_code_label}</label>
6
+ #{rodauth.input_field_string("user_code", "user_code", :value => rodauth.param_or_nil(rodauth.oauth_grant_user_code_param))}
7
+ </div>
8
+ <p class="text-center">
9
+ <input type="submit" class="btn btn-outline-primary" value="#{h(rodauth.oauth_device_search_button)}"/>
10
+ </p>
11
+ </form>
@@ -0,0 +1,24 @@
1
+ <form method="post" action="#{rodauth.device_path}" class="form-horizontal" role="form" id="device-verification-form">
2
+ #{csrf_tag(rodauth.device_path) if respond_to?(:csrf_tag)}
3
+ <p class="lead">The device with user code #{@oauth_grant[rodauth.oauth_grants_user_code_column]} would like to access your data.</p>
4
+
5
+ <div class="form-group">
6
+ <h1 class="display-6">#{rodauth.oauth_tokens_scopes_label}</h1>
7
+
8
+ <ul class="list-group">
9
+ #{
10
+ scopes = @oauth_grant[rodauth.oauth_grants_scopes_column].split(rodauth.oauth_scope_separator)
11
+ scopes.map do |scope|
12
+ <<-HTML
13
+ <li class="list-group-item">#{scope}</li>
14
+ HTML
15
+ end.join
16
+ }
17
+ </ul>
18
+ </div>
19
+ <input type="hidden" name="user_code" value="#{rodauth.param("user_code")}"/>
20
+ <p class="text-center">
21
+ <input type="submit" class="btn btn-outline-primary" value="#{h(rodauth.oauth_device_verification_button)}"/>
22
+ <a href="#{rodauth.device_path}?error=access_denied" class="btn btn-outline-danger">#{rodauth.oauth_cancel_button}</a>
23
+ </p>
24
+ </form>