rodauth-oauth 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -2
- data/doc/release_notes/0_9_0.md +56 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +22 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +8 -3
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +8 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +1 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +1 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +1 -0
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +13 -1
- data/lib/rodauth/features/oauth.rb +2 -2
- data/lib/rodauth/features/oauth_application_management.rb +22 -6
- data/lib/rodauth/features/oauth_assertion_base.rb +1 -1
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +4 -1
- data/lib/rodauth/features/oauth_base.rb +46 -10
- data/lib/rodauth/features/oauth_client_credentials_grant.rb +33 -0
- data/lib/rodauth/features/oauth_device_grant.rb +4 -5
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +252 -0
- data/lib/rodauth/features/oauth_jwt.rb +248 -49
- data/lib/rodauth/features/oauth_management_base.rb +68 -0
- data/lib/rodauth/features/oauth_pkce.rb +1 -1
- data/lib/rodauth/features/oauth_token_management.rb +8 -6
- data/lib/rodauth/features/oidc.rb +32 -3
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +147 -0
- data/lib/rodauth/oauth/jwe_extensions.rb +64 -0
- data/lib/rodauth/oauth/ttl_store.rb +9 -3
- data/lib/rodauth/oauth/version.rb +1 -1
- data/locales/en.yml +5 -0
- data/templates/authorize.str +50 -1
- data/templates/jwks_field.str +4 -0
- data/templates/oauth_application.str +1 -1
- data/templates/oauth_application_oauth_tokens.str +1 -0
- data/templates/oauth_applications.str +1 -0
- data/templates/oauth_tokens.str +1 -0
- metadata +10 -3
- data/templates/jws_jwk_field.str +0 -4
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rodauth
|
4
|
+
Feature.define(:oauth_management_base, :OauthManagementBase) do
|
5
|
+
depends :oauth_base
|
6
|
+
|
7
|
+
button "Previous", "oauth_management_pagination_previous"
|
8
|
+
button "Next", "oauth_management_pagination_next"
|
9
|
+
|
10
|
+
def oauth_management_pagination_links(paginated_ds)
|
11
|
+
html = +'<nav aria-label="Pagination"><ul class="pagination">'
|
12
|
+
html << oauth_management_pagination_link(paginated_ds.prev_page, label: oauth_management_pagination_previous_button)
|
13
|
+
html << oauth_management_pagination_link(paginated_ds.current_page - 1) unless paginated_ds.first_page?
|
14
|
+
html << oauth_management_pagination_link(paginated_ds.current_page, label: paginated_ds.current_page, current: true)
|
15
|
+
html << oauth_management_pagination_link(paginated_ds.current_page + 1) unless paginated_ds.last_page?
|
16
|
+
html << oauth_management_pagination_link(paginated_ds.next_page, label: oauth_management_pagination_next_button)
|
17
|
+
html << "</ul></nav>"
|
18
|
+
end
|
19
|
+
|
20
|
+
def oauth_management_pagination_link(page, label: page, current: false, classes: "")
|
21
|
+
classes += " disabled" if current || !page
|
22
|
+
classes += " active" if current
|
23
|
+
if page
|
24
|
+
params = request.GET.merge("page" => page).map do |k, v|
|
25
|
+
v ? "#{CGI.escape(String(k))}=#{CGI.escape(String(v))}" : CGI.escape(String(k))
|
26
|
+
end.join("&")
|
27
|
+
|
28
|
+
href = "#{request.path}?#{params}"
|
29
|
+
|
30
|
+
<<-HTML
|
31
|
+
<li class="page-item #{classes}" #{'aria-current="page"' if current}>
|
32
|
+
<a class="page-link" href="#{href}" tabindex="-1" aria-disabled="#{current || !page}">
|
33
|
+
#{label}
|
34
|
+
</a>
|
35
|
+
</li>
|
36
|
+
HTML
|
37
|
+
else
|
38
|
+
<<-HTML
|
39
|
+
<li class="page-item #{classes}">
|
40
|
+
<span class="page-link">
|
41
|
+
#{label}
|
42
|
+
#{'<span class="sr-only">(current)</span>' if current}
|
43
|
+
</span>
|
44
|
+
</li>
|
45
|
+
HTML
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def post_configure
|
50
|
+
super
|
51
|
+
db.extension :pagination
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def per_page_param(default_per_page)
|
57
|
+
per_page = param_or_nil("per_page")
|
58
|
+
|
59
|
+
return default_per_page unless per_page
|
60
|
+
|
61
|
+
per_page = per_page.to_i
|
62
|
+
|
63
|
+
return default_per_page if per_page <= 0
|
64
|
+
|
65
|
+
[per_page, default_per_page].min
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -4,7 +4,7 @@ module Rodauth
|
|
4
4
|
Feature.define(:oauth_token_management, :OauthTokenManagement) do
|
5
5
|
using RegexpExtensions
|
6
6
|
|
7
|
-
depends :
|
7
|
+
depends :oauth_management_base
|
8
8
|
|
9
9
|
view "oauth_tokens", "My Oauth Tokens", "oauth_tokens"
|
10
10
|
|
@@ -18,6 +18,7 @@ module Rodauth
|
|
18
18
|
|
19
19
|
auth_value_method :oauth_tokens_route, "oauth-tokens"
|
20
20
|
auth_value_method :oauth_tokens_id_pattern, Integer
|
21
|
+
auth_value_method :oauth_tokens_per_page, 20
|
21
22
|
|
22
23
|
auth_value_methods(
|
23
24
|
:oauth_token_path
|
@@ -40,12 +41,17 @@ module Rodauth
|
|
40
41
|
require_account
|
41
42
|
|
42
43
|
request.get do
|
44
|
+
page = Integer(param_or_nil("page") || 1)
|
45
|
+
per_page = per_page_param(oauth_tokens_per_page)
|
46
|
+
|
43
47
|
scope.instance_variable_set(:@oauth_tokens, db[oauth_tokens_table]
|
44
48
|
.select(Sequel[oauth_tokens_table].*, Sequel[oauth_applications_table][oauth_applications_name_column])
|
45
49
|
.join(oauth_applications_table, Sequel[oauth_tokens_table][oauth_tokens_oauth_application_id_column] =>
|
46
50
|
Sequel[oauth_applications_table][oauth_applications_id_column])
|
47
51
|
.where(Sequel[oauth_tokens_table][oauth_tokens_account_id_column] => account_id)
|
48
|
-
|
52
|
+
.where(oauth_tokens_revoked_at_column => nil)
|
53
|
+
.order(Sequel.desc(oauth_tokens_id_column))
|
54
|
+
.paginate(page, per_page))
|
49
55
|
oauth_tokens_view
|
50
56
|
end
|
51
57
|
|
@@ -69,9 +75,5 @@ module Rodauth
|
|
69
75
|
super
|
70
76
|
end
|
71
77
|
end
|
72
|
-
|
73
|
-
def check_valid_uri?(uri)
|
74
|
-
URI::DEFAULT_PARSER.make_regexp(oauth_valid_uri_schemes).match?(uri)
|
75
|
-
end
|
76
78
|
end
|
77
79
|
end
|
@@ -65,6 +65,13 @@ module Rodauth
|
|
65
65
|
auth_value_method :oauth_application_default_scope, "openid"
|
66
66
|
auth_value_method :oauth_application_scopes, %w[openid]
|
67
67
|
|
68
|
+
auth_value_method :oauth_applications_id_token_signed_response_alg_column, :id_token_signed_response_alg
|
69
|
+
auth_value_method :oauth_applications_id_token_encrypted_response_alg_column, :id_token_encrypted_response_alg
|
70
|
+
auth_value_method :oauth_applications_id_token_encrypted_response_enc_column, :id_token_encrypted_response_enc
|
71
|
+
auth_value_method :oauth_applications_userinfo_signed_response_alg_column, :userinfo_signed_response_alg
|
72
|
+
auth_value_method :oauth_applications_userinfo_encrypted_response_alg_column, :userinfo_encrypted_response_alg
|
73
|
+
auth_value_method :oauth_applications_userinfo_encrypted_response_enc_column, :userinfo_encrypted_response_enc
|
74
|
+
|
68
75
|
auth_value_method :oauth_grants_nonce_column, :nonce
|
69
76
|
auth_value_method :oauth_tokens_nonce_column, :nonce
|
70
77
|
|
@@ -106,7 +113,23 @@ module Rodauth
|
|
106
113
|
|
107
114
|
fill_with_account_claims(oidc_claims, account, oauth_scopes)
|
108
115
|
|
109
|
-
|
116
|
+
@oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => oauth_token["client_id"]).first
|
117
|
+
|
118
|
+
if (algo = @oauth_application && @oauth_application[oauth_applications_userinfo_signed_response_alg_column])
|
119
|
+
params = {
|
120
|
+
jwks: oauth_application_jwks,
|
121
|
+
encryption_algorithm: @oauth_application[oauth_applications_userinfo_encrypted_response_alg_column],
|
122
|
+
encryption_method: @oauth_application[oauth_applications_userinfo_encrypted_response_enc_column]
|
123
|
+
}
|
124
|
+
jwt = jwt_encode(
|
125
|
+
oidc_claims,
|
126
|
+
signing_algorithm: algo,
|
127
|
+
**params
|
128
|
+
)
|
129
|
+
jwt_response_success(jwt)
|
130
|
+
else
|
131
|
+
json_response_success(oidc_claims)
|
132
|
+
end
|
110
133
|
end
|
111
134
|
|
112
135
|
throw_json_response_error(authorization_required_error_status, "invalid_token")
|
@@ -330,7 +353,13 @@ module Rodauth
|
|
330
353
|
|
331
354
|
fill_with_account_claims(id_token_claims, account, oauth_scopes)
|
332
355
|
|
333
|
-
|
356
|
+
params = {
|
357
|
+
jwks: oauth_application_jwks,
|
358
|
+
signing_algorithm: oauth_application[oauth_applications_id_token_signed_response_alg_column] || oauth_jwt_algorithm,
|
359
|
+
encryption_algorithm: oauth_application[oauth_applications_id_token_encrypted_response_alg_column],
|
360
|
+
encryption_method: oauth_application[oauth_applications_id_token_encrypted_response_enc_column]
|
361
|
+
}
|
362
|
+
oauth_token[:id_token] = jwt_encode(id_token_claims, **params)
|
334
363
|
end
|
335
364
|
|
336
365
|
# aka fill_with_standard_claims
|
@@ -443,7 +472,7 @@ module Rodauth
|
|
443
472
|
|
444
473
|
# Metadata
|
445
474
|
|
446
|
-
def openid_configuration_body(path)
|
475
|
+
def openid_configuration_body(path = nil)
|
447
476
|
metadata = oauth_server_metadata_body(path).select do |k, _|
|
448
477
|
VALID_METADATA_KEYS.include?(k)
|
449
478
|
end
|
@@ -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
|
@@ -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
|
@@ -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
@@ -15,10 +15,15 @@ en:
|
|
15
15
|
oauth_tokens_page_title: "My Oauth Tokens"
|
16
16
|
device_verification_page_title: "Device Verification"
|
17
17
|
device_search_page_title: "Device Search"
|
18
|
+
oauth_management_pagination_previous_button: "Previous"
|
19
|
+
oauth_management_pagination_next_button: "Next"
|
18
20
|
oauth_applications_name_label: "Name"
|
19
21
|
oauth_applications_description_label: "Description"
|
20
22
|
oauth_applications_scopes_label: "Scopes"
|
23
|
+
oauth_applications_contacts_label: "Contacts"
|
21
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"
|
22
27
|
oauth_applications_redirect_uri_label: "Redirect URL"
|
23
28
|
oauth_applications_client_secret_label: "Client Secret"
|
24
29
|
oauth_applications_client_id_label: "Client ID"
|
data/templates/authorize.str
CHANGED
@@ -1,6 +1,55 @@
|
|
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
55
|
<h1 class="display-6">#{rodauth.oauth_tokens_scopes_label}</h1>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#{
|
4
4
|
params = [*rodauth.oauth_application_required_params, "client_id", "client_secret"]
|
5
5
|
if rodauth.features.include?(:oauth_jwt)
|
6
|
-
params += %w[
|
6
|
+
params += %w[jwks jwt_public_key]
|
7
7
|
end
|
8
8
|
params.map do |param|
|
9
9
|
"<dt class=\"#{param}\">#{rodauth.send(:"oauth_applications_#{param}_label")}: </dt>" +
|
data/templates/oauth_tokens.str
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth-oauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Cardoso
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rodauth
|
@@ -56,6 +56,7 @@ extra_rdoc_files:
|
|
56
56
|
- doc/release_notes/0_7_3.md
|
57
57
|
- doc/release_notes/0_7_4.md
|
58
58
|
- doc/release_notes/0_8_0.md
|
59
|
+
- doc/release_notes/0_9_0.md
|
59
60
|
files:
|
60
61
|
- CHANGELOG.md
|
61
62
|
- LICENSE.txt
|
@@ -83,6 +84,7 @@ files:
|
|
83
84
|
- doc/release_notes/0_7_3.md
|
84
85
|
- doc/release_notes/0_7_4.md
|
85
86
|
- doc/release_notes/0_8_0.md
|
87
|
+
- doc/release_notes/0_9_0.md
|
86
88
|
- lib/generators/rodauth/oauth/install_generator.rb
|
87
89
|
- lib/generators/rodauth/oauth/templates/app/models/oauth_application.rb
|
88
90
|
- lib/generators/rodauth/oauth/templates/app/models/oauth_grant.rb
|
@@ -103,11 +105,14 @@ files:
|
|
103
105
|
- lib/rodauth/features/oauth_authorization_code_grant.rb
|
104
106
|
- lib/rodauth/features/oauth_authorization_server.rb
|
105
107
|
- lib/rodauth/features/oauth_base.rb
|
108
|
+
- lib/rodauth/features/oauth_client_credentials_grant.rb
|
106
109
|
- lib/rodauth/features/oauth_device_grant.rb
|
110
|
+
- lib/rodauth/features/oauth_dynamic_client_registration.rb
|
107
111
|
- lib/rodauth/features/oauth_http_mac.rb
|
108
112
|
- lib/rodauth/features/oauth_implicit_grant.rb
|
109
113
|
- lib/rodauth/features/oauth_jwt.rb
|
110
114
|
- lib/rodauth/features/oauth_jwt_bearer_grant.rb
|
115
|
+
- lib/rodauth/features/oauth_management_base.rb
|
111
116
|
- lib/rodauth/features/oauth_pkce.rb
|
112
117
|
- lib/rodauth/features/oauth_resource_server.rb
|
113
118
|
- lib/rodauth/features/oauth_saml_bearer_grant.rb
|
@@ -115,8 +120,10 @@ files:
|
|
115
120
|
- lib/rodauth/features/oauth_token_management.rb
|
116
121
|
- lib/rodauth/features/oauth_token_revocation.rb
|
117
122
|
- lib/rodauth/features/oidc.rb
|
123
|
+
- lib/rodauth/features/oidc_dynamic_client_registration.rb
|
118
124
|
- lib/rodauth/oauth.rb
|
119
125
|
- lib/rodauth/oauth/database_extensions.rb
|
126
|
+
- lib/rodauth/oauth/jwe_extensions.rb
|
120
127
|
- lib/rodauth/oauth/railtie.rb
|
121
128
|
- lib/rodauth/oauth/refinements.rb
|
122
129
|
- lib/rodauth/oauth/ttl_store.rb
|
@@ -128,7 +135,7 @@ files:
|
|
128
135
|
- templates/device_search.str
|
129
136
|
- templates/device_verification.str
|
130
137
|
- templates/homepage_url_field.str
|
131
|
-
- templates/
|
138
|
+
- templates/jwks_field.str
|
132
139
|
- templates/jwt_public_key_field.str
|
133
140
|
- templates/name_field.str
|
134
141
|
- templates/new_oauth_application.str
|
data/templates/jws_jwk_field.str
DELETED