rodauth-oauth 0.7.4 → 0.9.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.
- 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>
|