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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -424
- data/README.md +30 -390
- data/doc/release_notes/0_0_1.md +3 -0
- data/doc/release_notes/0_0_2.md +15 -0
- data/doc/release_notes/0_0_3.md +31 -0
- data/doc/release_notes/0_0_4.md +36 -0
- data/doc/release_notes/0_0_5.md +36 -0
- data/doc/release_notes/0_0_6.md +21 -0
- data/doc/release_notes/0_1_0.md +44 -0
- data/doc/release_notes/0_2_0.md +43 -0
- data/doc/release_notes/0_3_0.md +28 -0
- data/doc/release_notes/0_4_0.md +18 -0
- data/doc/release_notes/0_4_1.md +9 -0
- data/doc/release_notes/0_4_2.md +5 -0
- data/doc/release_notes/0_4_3.md +3 -0
- data/doc/release_notes/0_5_0.md +11 -0
- data/doc/release_notes/0_5_1.md +13 -0
- data/doc/release_notes/0_6_0.md +9 -0
- data/doc/release_notes/0_6_1.md +6 -0
- data/doc/release_notes/0_7_0.md +20 -0
- data/doc/release_notes/0_7_1.md +10 -0
- data/doc/release_notes/0_7_2.md +21 -0
- data/doc/release_notes/0_7_3.md +10 -0
- data/doc/release_notes/0_7_4.md +5 -0
- data/doc/release_notes/0_8_0.md +37 -0
- data/doc/release_notes/0_9_0.md +56 -0
- data/doc/release_notes/0_9_1.md +9 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +25 -4
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +11 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +20 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +27 -10
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +17 -5
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +39 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +6 -5
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +12 -15
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +21 -1
- data/lib/rodauth/features/oauth.rb +3 -1418
- data/lib/rodauth/features/oauth_application_management.rb +225 -0
- data/lib/rodauth/features/oauth_assertion_base.rb +96 -0
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +252 -0
- data/lib/rodauth/features/oauth_authorization_server.rb +0 -0
- data/lib/rodauth/features/oauth_base.rb +778 -0
- data/lib/rodauth/features/oauth_client_credentials_grant.rb +33 -0
- data/lib/rodauth/features/oauth_device_grant.rb +220 -0
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +252 -0
- data/lib/rodauth/features/oauth_http_mac.rb +3 -21
- data/lib/rodauth/features/oauth_implicit_grant.rb +59 -0
- data/lib/rodauth/features/oauth_jwt.rb +275 -100
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +59 -0
- data/lib/rodauth/features/oauth_management_base.rb +68 -0
- data/lib/rodauth/features/oauth_pkce.rb +98 -0
- data/lib/rodauth/features/oauth_resource_server.rb +21 -0
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +102 -0
- data/lib/rodauth/features/oauth_token_introspection.rb +108 -0
- data/lib/rodauth/features/oauth_token_management.rb +79 -0
- data/lib/rodauth/features/oauth_token_revocation.rb +109 -0
- data/lib/rodauth/features/oidc.rb +38 -9
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +147 -0
- data/lib/rodauth/oauth/database_extensions.rb +15 -2
- data/lib/rodauth/oauth/jwe_extensions.rb +64 -0
- data/lib/rodauth/oauth/refinements.rb +48 -0
- data/lib/rodauth/oauth/ttl_store.rb +9 -3
- data/lib/rodauth/oauth/version.rb +1 -1
- data/locales/en.yml +33 -12
- data/templates/authorize.str +57 -8
- data/templates/client_secret_field.str +2 -2
- data/templates/description_field.str +1 -1
- data/templates/device_search.str +11 -0
- data/templates/device_verification.str +24 -0
- data/templates/homepage_url_field.str +2 -2
- data/templates/jwks_field.str +4 -0
- data/templates/jwt_public_key_field.str +4 -0
- data/templates/name_field.str +1 -1
- data/templates/new_oauth_application.str +9 -0
- data/templates/oauth_application.str +7 -3
- data/templates/oauth_application_oauth_tokens.str +52 -0
- data/templates/oauth_applications.str +3 -2
- data/templates/oauth_tokens.str +10 -11
- data/templates/redirect_uri_field.str +2 -2
- metadata +80 -3
- 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
|
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
|
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
|
-
|
29
|
-
|
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]
|
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)
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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"
|
data/templates/authorize.str
CHANGED
@@ -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
|
-
|
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.
|
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"
|
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.
|
3
|
-
#{rodauth.input_field_string(rodauth.oauth_application_client_secret_param, "
|
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.
|
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.
|
3
|
-
#{rodauth.input_field_string(rodauth.oauth_application_homepage_url_param, "
|
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>
|
data/templates/name_field.str
CHANGED
@@ -1,4 +1,4 @@
|
|
1
1
|
<div class="form-group">
|
2
|
-
<label for="name">#{rodauth.
|
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
|
-
|
5
|
-
|
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[
|
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>
|