rodauth-oauth 0.7.4 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -424
  3. data/README.md +30 -390
  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/doc/release_notes/0_9_0.md +56 -0
  28. data/doc/release_notes/0_9_1.md +9 -0
  29. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +25 -4
  30. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +11 -0
  31. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +20 -0
  32. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +27 -10
  33. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +17 -5
  34. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +39 -0
  35. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +6 -5
  36. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +12 -15
  37. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +21 -1
  38. data/lib/rodauth/features/oauth.rb +3 -1418
  39. data/lib/rodauth/features/oauth_application_management.rb +225 -0
  40. data/lib/rodauth/features/oauth_assertion_base.rb +96 -0
  41. data/lib/rodauth/features/oauth_authorization_code_grant.rb +252 -0
  42. data/lib/rodauth/features/oauth_authorization_server.rb +0 -0
  43. data/lib/rodauth/features/oauth_base.rb +778 -0
  44. data/lib/rodauth/features/oauth_client_credentials_grant.rb +33 -0
  45. data/lib/rodauth/features/oauth_device_grant.rb +220 -0
  46. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +252 -0
  47. data/lib/rodauth/features/oauth_http_mac.rb +3 -21
  48. data/lib/rodauth/features/oauth_implicit_grant.rb +59 -0
  49. data/lib/rodauth/features/oauth_jwt.rb +275 -100
  50. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +59 -0
  51. data/lib/rodauth/features/oauth_management_base.rb +68 -0
  52. data/lib/rodauth/features/oauth_pkce.rb +98 -0
  53. data/lib/rodauth/features/oauth_resource_server.rb +21 -0
  54. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +102 -0
  55. data/lib/rodauth/features/oauth_token_introspection.rb +108 -0
  56. data/lib/rodauth/features/oauth_token_management.rb +79 -0
  57. data/lib/rodauth/features/oauth_token_revocation.rb +109 -0
  58. data/lib/rodauth/features/oidc.rb +38 -9
  59. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +147 -0
  60. data/lib/rodauth/oauth/database_extensions.rb +15 -2
  61. data/lib/rodauth/oauth/jwe_extensions.rb +64 -0
  62. data/lib/rodauth/oauth/refinements.rb +48 -0
  63. data/lib/rodauth/oauth/ttl_store.rb +9 -3
  64. data/lib/rodauth/oauth/version.rb +1 -1
  65. data/locales/en.yml +33 -12
  66. data/templates/authorize.str +57 -8
  67. data/templates/client_secret_field.str +2 -2
  68. data/templates/description_field.str +1 -1
  69. data/templates/device_search.str +11 -0
  70. data/templates/device_verification.str +24 -0
  71. data/templates/homepage_url_field.str +2 -2
  72. data/templates/jwks_field.str +4 -0
  73. data/templates/jwt_public_key_field.str +4 -0
  74. data/templates/name_field.str +1 -1
  75. data/templates/new_oauth_application.str +9 -0
  76. data/templates/oauth_application.str +7 -3
  77. data/templates/oauth_application_oauth_tokens.str +52 -0
  78. data/templates/oauth_applications.str +3 -2
  79. data/templates/oauth_tokens.str +10 -11
  80. data/templates/redirect_uri_field.str +2 -2
  81. metadata +80 -3
  82. data/lib/rodauth/features/oauth_saml.rb +0 -104
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oidc_dynamic_client_registration, :OidcDynamicClientRegistration) do
5
+ depends :oauth_dynamic_client_registration, :oidc
6
+
7
+ auth_value_method :oauth_applications_application_type_column, :application_type
8
+
9
+ private
10
+
11
+ def registration_metadata
12
+ openid_configuration_body
13
+ end
14
+
15
+ def validate_client_registration_params
16
+ super
17
+
18
+ if (value = @oauth_application_params[oauth_applications_application_type_column])
19
+ case value
20
+ when "native"
21
+ request.params["redirect_uris"].each do |uri|
22
+ uri = URI(uri)
23
+ # Native Clients MUST only register redirect_uris using custom URI schemes or
24
+ # URLs using the http: scheme with localhost as the hostname.
25
+ case uri.scheme
26
+ when "http"
27
+ register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(uri)) unless uri.host == "localhost"
28
+ when "https"
29
+ register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(uri))
30
+ end
31
+ end
32
+ when "web"
33
+ # Web Clients using the OAuth Implicit Grant Type MUST only register URLs using the https scheme as redirect_uris;
34
+ # they MUST NOT use localhost as the hostname.
35
+ if request.params["grant_types"].include?("implicit")
36
+ request.params["redirect_uris"].each do |uri|
37
+ uri = URI(uri)
38
+ unless uri.scheme == "https" && uri.host != "localhost"
39
+ register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(uri))
40
+ end
41
+ end
42
+ end
43
+ else
44
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_application_type_message(type))
45
+ end
46
+ elsif (value = @oauth_application_params[oauth_applications_subject_type_column])
47
+ unless %w[pairwise public].include?(value)
48
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message("subject_type"))
49
+ end
50
+ elsif (value = @oauth_application_params[oauth_applications_id_token_signed_response_alg_column])
51
+ if value == "none"
52
+ # The value none MUST NOT be used as the ID Token alg value unless the Client uses only Response Types
53
+ # that return no ID Token from the Authorization Endpoint
54
+ response_types = @oauth_application_params[oauth_applications_response_types_column]
55
+ if response_types && response_types.include?("id_token")
56
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message("id_token_signed_response_alg"))
57
+ end
58
+ elsif !oauth_jwt_algorithms_supported.include?(value)
59
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message("id_token_signed_response_alg"))
60
+ end
61
+ elsif (value = @oauth_application_params[oauth_applications_id_token_encrypted_response_alg_column])
62
+ unless oauth_jwt_jwe_algorithms_supported.include?(value)
63
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message("id_token_encrypted_response_alg"))
64
+ end
65
+ elsif (value = @oauth_application_params[oauth_applications_id_token_encrypted_response_enc_column])
66
+ unless oauth_jwt_jwe_encryption_methods_supported.include?(value)
67
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message("id_token_encrypted_response_enc"))
68
+ end
69
+ elsif (value = @oauth_application_params[oauth_applications_userinfo_signed_response_alg_column])
70
+ unless oauth_jwt_algorithms_supported.include?(value)
71
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message("userinfo_signed_response_alg"))
72
+ end
73
+ elsif (value = @oauth_application_params[oauth_applications_userinfo_encrypted_response_alg_column])
74
+ unless oauth_jwt_jwe_algorithms_supported.include?(value)
75
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message("userinfo_encrypted_response_alg"))
76
+ end
77
+ elsif (value = @oauth_application_params[oauth_applications_userinfo_encrypted_response_enc_column])
78
+ unless oauth_jwt_jwe_encryption_methods_supported.include?(value)
79
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message("userinfo_encrypted_response_enc"))
80
+ end
81
+ elsif (value = @oauth_application_params[oauth_applications_request_object_signing_alg_column])
82
+ unless oauth_jwt_algorithms_supported.include?(value)
83
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message("request_object_signing_alg"))
84
+ end
85
+ elsif (value = @oauth_application_params[oauth_applications_request_object_encryption_alg_column])
86
+ unless oauth_jwt_jwe_algorithms_supported.include?(value)
87
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message("request_object_encryption_alg"))
88
+ end
89
+ elsif (value = @oauth_application_params[oauth_applications_request_object_encryption_enc_column])
90
+ unless oauth_jwt_jwe_encryption_methods_supported.include?(value)
91
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message("request_object_encryption_enc"))
92
+ end
93
+ end
94
+ end
95
+
96
+ def validate_client_registration_response_type(response_type, grant_types)
97
+ case response_type
98
+ when "id_token"
99
+ unless grant_types.include?("implicit")
100
+ register_throw_json_response_error("invalid_client_metadata",
101
+ register_invalid_response_type_for_grant_type_message(response_type, "implicit"))
102
+ end
103
+ else
104
+ super
105
+ end
106
+ end
107
+
108
+ def do_register(return_params = request.params.dup)
109
+ # set defaults
110
+
111
+ create_params = @oauth_application_params
112
+
113
+ create_params[oauth_applications_application_type_column] ||= begin
114
+ return_params["application_type"] = "web"
115
+ "web"
116
+ end
117
+ create_params[oauth_applications_id_token_signed_response_alg_column] ||= begin
118
+ return_params["id_token_signed_response_alg"] = oauth_jwt_algorithm
119
+ oauth_jwt_algorithm
120
+ end
121
+ if create_params.key?(oauth_applications_id_token_encrypted_response_alg_column)
122
+ create_params[oauth_applications_id_token_encrypted_response_enc_column] ||= begin
123
+ return_params["id_token_encrypted_response_enc"] = "A128CBC-HS256"
124
+ "A128CBC-HS256"
125
+ end
126
+ end
127
+ if create_params.key?(oauth_applications_userinfo_encrypted_response_alg_column)
128
+ create_params[oauth_applications_userinfo_encrypted_response_enc_column] ||= begin
129
+ return_params["userinfo_encrypted_response_enc"] = "A128CBC-HS256"
130
+ "A128CBC-HS256"
131
+ end
132
+ end
133
+ if create_params.key?(oauth_applications_request_object_encryption_alg_column)
134
+ create_params[oauth_applications_request_object_encryption_enc_column] ||= begin
135
+ return_params["request_object_encryption_enc"] = "A128CBC-HS256"
136
+ "A128CBC-HS256"
137
+ end
138
+ end
139
+
140
+ super(return_params)
141
+ end
142
+
143
+ def register_invalid_application_type_message(application_type)
144
+ "The application type '#{application_type}' is not allowed."
145
+ end
146
+ end
147
+ end
@@ -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,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWE
4
+ #
5
+ # this is a monkey-patch!
6
+ # it's necessary, as the original jwe does not support jwks.
7
+ # if this works long term, it may be merged upstreamm.
8
+ #
9
+ def self.__rodauth_oauth_decrypt_from_jwks(payload, jwks, alg: "RSA-OAEP", enc: "A128GCM")
10
+ header, enc_key, iv, ciphertext, tag = Serialization::Compact.decode(payload)
11
+ header = JSON.parse(header)
12
+
13
+ key = find_key_by_kid(jwks, header["kid"], alg, enc)
14
+
15
+ check_params(header, key)
16
+
17
+ cek = Alg.decrypt_cek(header["alg"], key, enc_key)
18
+ cipher = Enc.for(header["enc"], cek, iv, tag)
19
+
20
+ plaintext = cipher.decrypt(ciphertext, payload.split(".").first)
21
+
22
+ apply_zip(header, plaintext, :decompress)
23
+ end
24
+
25
+ def self.__rodauth_oauth_encrypt_from_jwks(payload, jwks, alg: "RSA-OAEP", enc: "A128GCM", **more_headers)
26
+ header = generate_header(alg, enc, more_headers)
27
+
28
+ key = find_key_by_alg_enc(jwks, alg, enc)
29
+
30
+ check_params(header, key)
31
+ payload = apply_zip(header, payload, :compress)
32
+
33
+ cipher = Enc.for(enc)
34
+ cipher.cek = key if alg == "dir"
35
+
36
+ json_hdr = header.to_json
37
+ ciphertext = cipher.encrypt(payload, Base64.jwe_encode(json_hdr))
38
+
39
+ generate_serialization(json_hdr, Alg.encrypt_cek(alg, key, cipher.cek), ciphertext, cipher)
40
+ end
41
+
42
+ def self.find_key_by_kid(jwks, kid, alg, enc)
43
+ raise DecodeError, "No key id (kid) found from token headers" unless kid
44
+
45
+ jwk = jwks.find { |key, _| (key[:kid] || key["kid"]) == kid }
46
+
47
+ raise DecodeError, "Could not find public key for kid #{kid}" unless jwk
48
+ raise DecodeError, "Expected a different encryption algorithm" unless alg == (jwk[:alg] || jwk["alg"])
49
+ raise DecodeError, "Expected a different encryption method" unless enc == (jwk[:enc] || jwk["enc"])
50
+
51
+ ::JWT::JWK.import(jwk).keypair
52
+ end
53
+
54
+ def self.find_key_by_alg_enc(jwks, alg, enc)
55
+ jwk = jwks.find do |key, _|
56
+ (key[:alg] || key["alg"]) == alg &&
57
+ (key[:enc] || key["enc"]) == enc
58
+ end
59
+
60
+ raise DecodeError, "No key found" unless jwk
61
+
62
+ ::JWT::JWK.import(jwk).keypair
63
+ end
64
+ 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
@@ -24,12 +24,18 @@ class Rodauth::OAuth::TtlStore
24
24
  @store_mutex.synchronize do
25
25
  # short circuit
26
26
  return @store[key][:payload] if @store[key] && @store[key][:ttl] < now
27
+ end
27
28
 
28
- payload, ttl = block.call
29
- @store[key] = { payload: payload, ttl: (ttl || (now + DEFAULT_TTL)) }
29
+ payload, ttl = block.call
30
+
31
+ @store_mutex.synchronize do
32
+ # given that the block call triggers network, and two requests for the same key be processed
33
+ # at the same time, this ensures the first one wins.
34
+ return @store[key][:payload] if @store[key] && @store[key][:ttl] < now
30
35
 
31
- @store[key][:payload]
36
+ @store[key] = { payload: payload, ttl: (ttl || (now + DEFAULT_TTL)) }
32
37
  end
38
+ @store[key][:payload]
33
39
  end
34
40
 
35
41
  def uncache(key)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rodauth
4
4
  module OAuth
5
- VERSION = "0.7.4"
5
+ VERSION = "0.9.1"
6
6
  end
7
7
  end
data/locales/en.yml CHANGED
@@ -5,22 +5,39 @@ 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_management_pagination_previous_button: "Previous"
19
+ oauth_management_pagination_next_button: "Next"
20
+ oauth_applications_name_label: "Name"
21
+ oauth_applications_description_label: "Description"
22
+ oauth_applications_scopes_label: "Scopes"
23
+ oauth_applications_contacts_label: "Contacts"
24
+ oauth_applications_homepage_url_label: "Homepage URL"
25
+ oauth_applications_tos_uri_label: "Terms of Service URL"
26
+ oauth_applications_policy_uri_label: "Policy URL"
27
+ oauth_applications_redirect_uri_label: "Redirect URL"
28
+ oauth_applications_client_secret_label: "Client Secret"
29
+ oauth_applications_client_id_label: "Client ID"
30
+ oauth_grant_user_code_label: "User code"
31
+ oauth_grant_user_jws_jwk_label: "JSON Web Keys"
32
+ oauth_grant_user_jwt_public_key_label: "Public key"
33
+ oauth_application_button: "Register"
21
34
  oauth_authorize_button: "Authorize"
22
35
  oauth_token_revoke_button: "Revoke"
23
36
  oauth_authorize_post_button: "Back to Client Application"
37
+ oauth_device_verification_button: "Verify"
38
+ oauth_device_search_button: "Search"
39
+ invalid_client_message: "Client authentication failed"
40
+ invalid_grant_type_message: "Invalid grant type"
24
41
  invalid_grant_message: "Invalid grant"
25
42
  invalid_scope_message: "Invalid scope"
26
43
  invalid_url_message: "Invalid URL"
@@ -28,6 +45,10 @@ en:
28
45
  unique_error_message: "is already in use"
29
46
  null_error_message: "is not filled"
30
47
  already_in_use_message: "error generating unique token"
48
+ expired_token_message: "the device code has expired"
49
+ access_denied_message: "the authorization request has been denied"
50
+ authorization_pending_message: "the authorization request is still pending"
51
+ slow_down_message: "authorization request is still pending but poll interval should be increased"
31
52
  code_challenge_required_message: "code challenge required"
32
53
  unsupported_transform_algorithm_message: "transform algorithm not supported"
33
54
  request_uri_not_supported_message: "request uri is unsupported"
@@ -1,25 +1,74 @@
1
1
  <form method="post" action="#{rodauth.authorize_path}" class="form-horizontal" role="form" id="authorize-form">
2
2
  #{csrf_tag(rodauth.authorize_path) if respond_to?(:csrf_tag)}
3
- <p class="lead">The application #{rodauth.oauth_application[rodauth.oauth_applications_name_column]} would like to access your data.</p>
3
+ #{
4
+ if rodauth.oauth_application[rodauth.oauth_applications_logo_uri_column]
5
+ <<-HTML
6
+ <img src="#{h(rodauth.oauth_application[rodauth.oauth_applications_logo_uri_column])}" />
7
+ HTML
8
+ end
9
+ }
10
+ <p class="lead">
11
+ The application
12
+ <a target="_blank" href="#{h(rodauth.oauth_application[rodauth.oauth_applications_homepage_url_column])}">
13
+ #{h(rodauth.oauth_application[rodauth.oauth_applications_name_column])}
14
+ </a> would like to access your data.
15
+ </p>
16
+ <div class="list-group">
17
+ #{
18
+ if rodauth.oauth_application[rodauth.oauth_applications_tos_uri_column]
19
+ <<-HTML
20
+ <a class="list-group-item" target="_blank" href="#{h(rodauth.oauth_application[rodauth.oauth_applications_tos_uri_column])}">
21
+ #{rodauth.oauth_applications_tos_uri_label}
22
+ </a>
23
+ HTML
24
+ end
25
+ }
26
+ #{
27
+ if rodauth.oauth_application[rodauth.oauth_applications_policy_uri_column]
28
+ <<-HTML
29
+ <a class="list-group-item" target="_blank" href="#{h(rodauth.oauth_application[rodauth.oauth_applications_policy_uri_column])}">
30
+ #{rodauth.oauth_applications_policy_uri_label}
31
+ </a>
32
+ HTML
33
+ end
34
+ }
35
+ </div>
36
+
37
+ #{
38
+ if rodauth.oauth_application[rodauth.oauth_applications_contacts_column]
39
+ data = <<-HTML
40
+ <div class="list-group">
41
+ <h3 class="display-6">#{rodauth.oauth_applications_contacts_label}</h3>
42
+ HTML
43
+ rodauth.oauth_application[rodauth.oauth_applications_contacts_column].split(/ +/).each do |contact|
44
+ data << <<-HTML
45
+ <div class="list-group-item">
46
+ #{h(contact)}
47
+ </div>
48
+ HTML
49
+ end
50
+ data << "</div>"
51
+ end
52
+ }
4
53
 
5
54
  <div class="form-group">
6
- <h1 class="display-6">#{rodauth.scopes_label}</h1>
55
+ <h1 class="display-6">#{rodauth.oauth_tokens_scopes_label}</h1>
7
56
 
8
57
  #{
9
58
  rodauth.scopes.map do |scope|
10
59
  if scope == rodauth.oauth_application_default_scope
11
60
  <<-HTML
12
61
  <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}">
62
+ <input id="#{scope}" class="form-check-input" type="checkbox" name="scope[]" value="#{h(scope)}" checked disabled>
63
+ <label class="form-check-label" for="#{scope}">#{h(scope)}</label>
64
+ <input type="hidden" name="scope[]" value="#{h(scope)}">
16
65
  </div>
17
66
  HTML
18
67
  else
19
68
  <<-HTML
20
69
  <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>
70
+ <input id="#{scope}" class="form-check-input" type="checkbox" name="scope[]" value="#{h(scope)}">
71
+ <label class="form-check-label" for="#{scope}">#{h(scope)}</label>
23
72
  </div>
24
73
  HTML
25
74
  end
@@ -39,6 +88,6 @@
39
88
  </div>
40
89
  <p class="text-center">
41
90
  <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>
91
+ <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
92
  </p>
44
93
  </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>
@@ -1,4 +1,4 @@
1
1
  <div class="form-group">
2
- <label for="homepage_url">#{rodauth.homepage_url_label}#{rodauth.input_field_label_suffix}</label>
3
- #{rodauth.input_field_string(rodauth.oauth_application_homepage_url_param, "homepage_url", :type=>"text")}
2
+ <label for="homepage_url">#{rodauth.oauth_applications_homepage_url_label}#{rodauth.input_field_label_suffix}</label>
3
+ #{rodauth.input_field_string(rodauth.oauth_application_homepage_url_param, "homepage-url", :type=>"text")}
4
4
  </div>
@@ -0,0 +1,4 @@
1
+ <div class="form-group">
2
+ <label for="name">#{rodauth.oauth_applications_jwks_label}#{rodauth.input_field_label_suffix}</label>
3
+ #{rodauth.input_field_string(rodauth.oauth_application_jwks_param, "jwks", :type=>"text")}
4
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="form-group">
2
+ <label for="name">#{rodauth.oauth_applications_jwt_public_key_label}#{rodauth.input_field_label_suffix}</label>
3
+ #{rodauth.input_field_string(rodauth.oauth_application_jwt_public_key_param, "jwt_public_key", :type=>"text")}
4
+ </div>
@@ -1,4 +1,4 @@
1
1
  <div class="form-group">
2
- <label for="name">#{rodauth.name_label}#{rodauth.input_field_label_suffix}</label>
2
+ <label for="name">#{rodauth.oauth_applications_name_label}#{rodauth.input_field_label_suffix}</label>
3
3
  #{rodauth.input_field_string(rodauth.oauth_application_name_param, "name", :type=>"text")}
4
4
  </div>
@@ -1,3 +1,4 @@
1
+ <h2>#{rodauth.new_oauth_application_page_title}</h2>
1
2
  <form method="post" action="#{rodauth.oauth_applications_path}" class="rodauth" role="form" id="oauth-application-form">
2
3
  #{rodauth.csrf_tag}
3
4
  #{rodauth.render('name_field')}
@@ -6,5 +7,13 @@
6
7
  #{rodauth.render('redirect_uri_field')}
7
8
  #{rodauth.render('client_secret_field')}
8
9
  #{rodauth.render('scope_field')}
10
+ #{
11
+ if rodauth.features.include?(:oauth_jwt)
12
+ <<-HTML
13
+ #{rodauth.render('jwt_public_key_field')}
14
+ #{rodauth.render('jws_jwk_field')}
15
+ HTML
16
+ end
17
+ }
9
18
  #{rodauth.button(rodauth.oauth_application_button)}
10
19
  </form>
@@ -1,11 +1,15 @@
1
1
  <div id="oauth-application">
2
2
  <dl>
3
3
  #{
4
- (rodauth.oauth_application_required_params + %w[client_id] - %w[client_secret]).map do |param|
5
- "<dt class=\"#{param}\">#{rodauth.send(:"#{param}_label")}</dt>" +
4
+ params = [*rodauth.oauth_application_required_params, "client_id", "client_secret"]
5
+ if rodauth.features.include?(:oauth_jwt)
6
+ params += %w[jwks jwt_public_key]
7
+ end
8
+ params.map do |param|
9
+ "<dt class=\"#{param}\">#{rodauth.send(:"oauth_applications_#{param}_label")}: </dt>" +
6
10
  "<dd class=\"#{param}\">#{@oauth_application[rodauth.send(:"oauth_applications_#{param}_column")]}</dd>"
7
11
  end.join
8
12
  }
9
13
  </dl>
10
- <a href="#{rodauth.oauth_applications_path}/#{@oauth_application[:id]}/#{rodauth.oauth_tokens_path}" class="btn btn-outline-secondary">Oauth Tokens</a>
14
+ <a href="#{rodauth.oauth_applications_path}/#{@oauth_application[rodauth.oauth_applications_id_column]}/#{rodauth.oauth_applications_oauth_tokens_path}" class="btn btn-outline-secondary">#{rodauth.oauth_application_oauth_tokens_page_title}</a>
11
15
  </div>