rodauth 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +28 -0
- data/README.rdoc +1 -1
- data/doc/base.rdoc +2 -0
- data/doc/jwt.rdoc +30 -3
- data/doc/release_notes/1.5.0.txt +74 -0
- data/lib/rodauth.rb +1 -0
- data/lib/rodauth/features/base.rb +9 -5
- data/lib/rodauth/features/jwt.rb +72 -27
- data/lib/rodauth/version.rb +1 -1
- data/spec/jwt_spec.rb +125 -0
- data/spec/login_spec.rb +1 -7
- data/spec/rodauth_spec.rb +13 -0
- data/spec/spec_helper.rb +23 -5
- data/spec/views/layout-other.str +11 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 03da9b51a0536d056d085d1d6c7d69f037e63825
|
4
|
+
data.tar.gz: 6c554cef3c2f122fce444146f7032c922ef93b9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37a8361ce43dfcf74bf5768e386eba59555c44fb3da0b29ae638eefa61ad6f9db70e9f25cf42831fddef200b10db9bb36fd67d60d630dfe11e96275d3f2be5a4
|
7
|
+
data.tar.gz: d88123e032493908205f0f93f93f8ae813942d3574adddb89fb77cf9053f255f3d814c9c84ae84ee5c939ac1f946c60032b6bdba89b96644abb139ffea69fd3a
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
=== 1.5.0 (2016-09-22)
|
2
|
+
|
3
|
+
* Return error instead of raising exception in the jwt feature if an invalid jwt format is submitted in the Authorization header (jeremyevans)
|
4
|
+
|
5
|
+
* Add jwt_authorization_remove configuration method to jwt feature, for regexp to remove from Authorization header before JWT processing (jeremyevans)
|
6
|
+
|
7
|
+
* Add jwt_authorization_ignore configuration method to jwt feature, for regexp to skip processing of JWTs in Authorization header (jeremyevans)
|
8
|
+
|
9
|
+
* Add json_accept_regexp configuration method to jwt feature, for the regexp used to match against the Accept header (jeremyevans)
|
10
|
+
|
11
|
+
* Add use_jwt? configuration method to jwt feature, for whether to use the JWT token or rack session for authentication information (jeremyevans)
|
12
|
+
|
13
|
+
* Add jwt_check_accept? configuration method to jwt feature, to return 406 error if Accept header is present and json is not accepted (jeremyevans)
|
14
|
+
|
15
|
+
* Add json_response_content_type configuration method to jwt feature, for the content type to set for json responses, default to application/json (jeremyevans)
|
16
|
+
|
17
|
+
* Add json_request_content_type_regexp configuration method to the jwt feature, for the regexp that recognize a request as a json request (jeremyevans)
|
18
|
+
|
19
|
+
* Add session_jwt method to the jwt feature, which returns a string for the encoded JWT for the current session (jeremyevans)
|
20
|
+
|
21
|
+
* If the only_json? setting is true, return a 400 error if the request content type to a rodauth endpoint is not json (jeremyevans)
|
22
|
+
|
23
|
+
* The only_json? setting in the jwt feature is now only true by default if :json=>:only plugin option was used (jeremyevans)
|
24
|
+
|
25
|
+
* Don't have jwt feature break if HTTP Basic/Digest authentication is used (jeremyevans)
|
26
|
+
|
27
|
+
* Add template_opts configuration method, for overriding view/method options (jeremyevans)
|
28
|
+
|
1
29
|
=== 1.4.0 (2016-08-18)
|
2
30
|
|
3
31
|
* Add update_password_hash feature, for updating the password hash when the hash cost changes (jeremyevans)
|
data/README.rdoc
CHANGED
@@ -897,7 +897,7 @@ authentication only for particular branches:
|
|
897
897
|
# ...
|
898
898
|
end
|
899
899
|
|
900
|
-
|
900
|
+
=== JSON API Support
|
901
901
|
|
902
902
|
To add support for handling JSON responses, you can pass the +:json+
|
903
903
|
option to the plugin, and enable the JWT feature in addition to
|
data/doc/base.rdoc
CHANGED
@@ -59,6 +59,8 @@ set_deadline_values? :: Whether deadline values should be set. True by default
|
|
59
59
|
on MySQL, as that doesn't support default values that
|
60
60
|
are not constant. Can be set to true on other databases
|
61
61
|
if you want to vary the value based on a request parameter.
|
62
|
+
template_opts :: Any template options to pass to view/render. This can be used
|
63
|
+
to set a custom layout, for example.
|
62
64
|
use_date_arithmetic? :: Whether the date_arithmetic extension should be loaded into
|
63
65
|
the database. Defaults to whether deadline values should
|
64
66
|
be set.
|
data/doc/jwt.rdoc
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
= Documentation for JWT Feature
|
2
2
|
|
3
3
|
The jwt feature adds support for JSON API access for all other features
|
4
|
-
that ship with Rodauth, using JWT
|
4
|
+
that ship with Rodauth, using JWT (JSON Web Tokens) to hold the
|
5
|
+
authentication data.
|
5
6
|
|
6
7
|
When this feature is used, all other features become accessible via a
|
7
8
|
JSON API. The JSON API uses the POST method for all requests, using
|
8
|
-
the same parameter names as the features uses.
|
9
|
+
the same parameter names as the features uses. JSON API requests to
|
10
|
+
Rodauth endpoints that use a method other than POST will result in a
|
11
|
+
405 Method Not Allowed response.
|
9
12
|
|
10
13
|
Responses are returned as JSON hashes. In case of an error, the "error"
|
11
14
|
entry is set to an error message, and the "field-error" entry is set to
|
@@ -16,20 +19,44 @@ message, though that can be disabled.
|
|
16
19
|
In order to use this feature, you have to set the +jwt_secret+ configuration
|
17
20
|
option the secret used to cryptographically protect the token.
|
18
21
|
|
22
|
+
To use this JSON API, when processing responses for requests to a Rodauth
|
23
|
+
endpoint, check for the Authorization header, and use the value of the
|
24
|
+
response Authorization header as the request Authorization header in
|
25
|
+
future requests, if the response Authorization header is set. If the
|
26
|
+
response Authorization header is not set, then continue to use the
|
27
|
+
previous Authorization header.
|
28
|
+
|
29
|
+
When using this feature, consider using the :json=>:only option when
|
30
|
+
loading the rodauth plugin, if you want Rodauth to only handle
|
31
|
+
JSON requests. If you don't use the :json=>:only option, the jwt feature
|
32
|
+
will probably result in an error if a request to a Rodauth endpoint comes
|
33
|
+
in with a Content-Type that isn't application/json, unless you also set
|
34
|
+
<tt>only_json? false</tt> in your rodauth configuration.
|
35
|
+
|
19
36
|
== Auth Value Methods
|
20
37
|
|
38
|
+
invalid_jwt_format_error_message :: The error message to use when a JWT with an invalid format is submitted in the Authorization header.
|
39
|
+
json_accept_regexp :: The regexp to use to check the Accept header for JSON if jwt_check_accept? is true.
|
21
40
|
json_non_post_error_message :: The error message to use when a JSON non-POST request is sent.
|
41
|
+
json_not_accepted_error_message :: The error message to display if jwt_check_accept? is true and the Accept header is present but does not match json_request_content_type_regexp.
|
42
|
+
json_request_content_type_regexp :: The regexp to use to recognize a request as a json request.
|
43
|
+
json_response_content_type :: The content type to set for json responses, application/json by default.
|
22
44
|
json_response_error_key :: The JSON result key containing an error message, "error" by default.
|
23
45
|
json_response_error_status :: The HTTP status code to use for JSON error responses, 400 by default.
|
24
46
|
json_response_field_error_key :: The JSON result key containing an field error message, "field-error" by default.
|
25
47
|
json_response_success_key :: The JSON result key containing a success message for successful request, if set. nil by default to not set success messages.
|
26
48
|
jwt_algorithm :: The JWT algorithm to use, "HS256" by default.
|
27
49
|
non_json_request_error_message :: The error message to use when a non-JSON request is sent and +only_json?+ is set.
|
28
|
-
only_json? :: Whether to have Rodauth only allow JSON requests. True by default
|
50
|
+
only_json? :: Whether to have Rodauth only allow JSON requests. True by default if :json=>:only option was given when loading the plugin. If set, rodauth endpoints will issue an error for non-JSON requests.
|
51
|
+
jwt_authorization_ignore :: A regexp matched against the Authorization header, which skips JWT processing if it matches. By default, HTTP Basic and Digest authentication are ignored.
|
52
|
+
jwt_authorization_remove :: A regexp to remove from the Authorization header before processing the JWT. By default, a Bearer prefix is removed.
|
53
|
+
jwt_check_accept? :: Whether to check the Accept header to see if the client supports JSON responses, false by default for backwards compatibility.
|
29
54
|
jwt_secret :: The JWT secret to use. Access to this should be protected the same as a session secret.
|
55
|
+
use_jwt? :: Whether to use the JWT in the Authorization header for authentication information. If false, falls back to using the rack session. By default, the Authorization header is used if it is present, if only_json? is true, or if the request uses a json content type.
|
30
56
|
|
31
57
|
== Auth Methods
|
32
58
|
|
33
59
|
json_request? :: Whether the current request is a JSON request, looks at the Content-Type request header by default.
|
34
60
|
jwt_token :: Retrieve the JWT token from the request, by default taking it from the Authorization header.
|
61
|
+
session_jwt :: An encoded JWT for the current session.
|
35
62
|
set_jwt_token(token) :: Set the JWT token in the response, by default storing it in the Authorization header.
|
@@ -0,0 +1,74 @@
|
|
1
|
+
= jwt Feature Additions/Improvements
|
2
|
+
|
3
|
+
* JSON format responses now have the response content type set to
|
4
|
+
application/json.
|
5
|
+
|
6
|
+
* The jwt feature now does not break if HTTP Basic or Digest
|
7
|
+
authentication is used.
|
8
|
+
|
9
|
+
* If jwt_check_accept? is true, Rodauth will return a 406 error if
|
10
|
+
a request Accept header is provided and it does not indicate that
|
11
|
+
JSON is acceptable.
|
12
|
+
|
13
|
+
* Many new configuration methods have been added:
|
14
|
+
|
15
|
+
* invalid_jwt_format_error_message: The error message to use when a
|
16
|
+
JWT with an invalid format is submitted in the Authorization
|
17
|
+
header.
|
18
|
+
|
19
|
+
* json_accept_regexp: The regexp to use to check the Accept header
|
20
|
+
for JSON if jwt_check_accept? is true.
|
21
|
+
|
22
|
+
* json_not_accepted_error_message: The error message to display if
|
23
|
+
jwt_check_accept? is true and the Accept header is present but
|
24
|
+
does not match json_request_content_type_regexp.
|
25
|
+
|
26
|
+
* json_request_content_type_regexp: The regexp to use to recognize
|
27
|
+
a request as a json request.
|
28
|
+
|
29
|
+
* json_response_content_type: The content type to set for json
|
30
|
+
responses, application/json by default.
|
31
|
+
|
32
|
+
* jwt_authorization_ignore: A regexp matched against the
|
33
|
+
Authorization header, which skips JWT processing if it matches.
|
34
|
+
By default, HTTP Basic and Digest authentication are ignored.
|
35
|
+
|
36
|
+
* jwt_authorization_remove: A regexp to remove from the
|
37
|
+
Authorization header before processing the JWT. By default, a
|
38
|
+
Bearer prefix is removed.
|
39
|
+
|
40
|
+
* jwt_check_accept?: Whether to check the Accept header to see if
|
41
|
+
the client supports JSON responses, false by default for backwards
|
42
|
+
compatibility.
|
43
|
+
|
44
|
+
* session_jwt: An encoded JWT for the current session.
|
45
|
+
|
46
|
+
* use_jwt?: Whether to use the JWT in the Authorization header for
|
47
|
+
authentication information. If false, falls back to using the
|
48
|
+
rack session. By default, the Authorization header is used if it
|
49
|
+
is present, if only_json? is true, or if the request uses a json
|
50
|
+
content type.
|
51
|
+
|
52
|
+
= jwt Feature Backwards Compatibility Issues
|
53
|
+
|
54
|
+
* The only_json? setting in the jwt feature is now only true by
|
55
|
+
default if the :json=>:only option was used when loading the
|
56
|
+
rodauth plugin into the roda app. Previously, it was always true,
|
57
|
+
but it only was considered in requests to Rodauth endpoints. It
|
58
|
+
now also is considered in most Rodauth calls, and if true will use
|
59
|
+
an empty session instead of falling back to the rack session if an
|
60
|
+
Authorization header is not present.
|
61
|
+
|
62
|
+
* Previously, the jwt feature only handled requests where the
|
63
|
+
request content-type is JSON. It now also handles non-JSON
|
64
|
+
requests if the Authorization header is present or if only_json?
|
65
|
+
is true.
|
66
|
+
|
67
|
+
* If an invalid JWT format is used in the Authorization header,
|
68
|
+
Rodauth now returns a 400 error, instead of raising an exception.
|
69
|
+
|
70
|
+
= Other Improvements
|
71
|
+
|
72
|
+
* A template_opts configuration method has been added, for
|
73
|
+
overriding the view/render options. One possible use for this is
|
74
|
+
to specify a non-default layout.
|
data/lib/rodauth.rb
CHANGED
@@ -29,6 +29,7 @@ module Rodauth
|
|
29
29
|
auth_value_method :prefix, ''
|
30
30
|
auth_value_method :require_bcrypt?, true
|
31
31
|
auth_value_method :skip_status_checks?, true
|
32
|
+
auth_value_method :template_opts, {}
|
32
33
|
auth_value_method :title_instance_variable, nil
|
33
34
|
auth_value_method :unverified_account_message, "unverified account, please verify account before logging in"
|
34
35
|
|
@@ -471,14 +472,17 @@ module Rodauth
|
|
471
472
|
end
|
472
473
|
|
473
474
|
def _view(meth, page)
|
474
|
-
auth = self
|
475
475
|
auth_template_path = template_path(page)
|
476
|
+
opts = template_opts.dup
|
477
|
+
opts[:locals] = opts[:locals] ? opts[:locals].dup : {}
|
478
|
+
opts[:locals][:rodauth] = self
|
479
|
+
|
476
480
|
scope.instance_exec do
|
477
|
-
|
478
|
-
unless File.file?(template_path(
|
479
|
-
|
481
|
+
opts = find_template(parse_template_opts(page, opts))
|
482
|
+
unless File.file?(template_path(opts))
|
483
|
+
opts[:path] = auth_template_path
|
480
484
|
end
|
481
|
-
send(meth,
|
485
|
+
send(meth, opts)
|
482
486
|
end
|
483
487
|
end
|
484
488
|
end
|
data/lib/rodauth/features/jwt.rb
CHANGED
@@ -4,30 +4,42 @@ require 'jwt'
|
|
4
4
|
|
5
5
|
module Rodauth
|
6
6
|
Jwt = Feature.define(:jwt) do
|
7
|
+
auth_value_method :invalid_jwt_format_error_message, "invalid JWT format in Authorization header"
|
7
8
|
auth_value_method :json_non_post_error_message, 'non-POST method used in JSON API'
|
9
|
+
auth_value_method :json_not_accepted_error_message, 'Unsupported Accept header. Must accept "application/json" or compatible content type'
|
10
|
+
auth_value_method :json_accept_regexp, /(?:(?:\*|\bapplication)\/\*|\bapplication\/(?:vnd\.api\+)?json\b)/i
|
11
|
+
auth_value_method :json_request_content_type_regexp, /\bapplication\/(?:vnd\.api\+)?json\b/i
|
12
|
+
auth_value_method :json_response_content_type, 'application/json'
|
8
13
|
auth_value_method :json_response_error_status, 400
|
9
14
|
auth_value_method :json_response_error_key, "error"
|
10
15
|
auth_value_method :json_response_field_error_key, "field-error"
|
11
16
|
auth_value_method :json_response_success_key, nil
|
12
17
|
auth_value_method :jwt_algorithm, "HS256"
|
18
|
+
auth_value_method :jwt_authorization_ignore, /\A(?:Basic|Digest) /
|
19
|
+
auth_value_method :jwt_authorization_remove, /\ABearer:?\s+/
|
20
|
+
auth_value_method :jwt_check_accept?, false
|
13
21
|
auth_value_method :non_json_request_error_message, 'Only JSON format requests are allowed'
|
14
|
-
auth_value_method :only_json?, true
|
15
22
|
|
16
|
-
auth_value_methods
|
23
|
+
auth_value_methods(
|
24
|
+
:only_json?,
|
25
|
+
:jwt_secret,
|
26
|
+
:use_jwt?
|
27
|
+
)
|
17
28
|
|
18
29
|
auth_methods(
|
19
30
|
:json_request?,
|
20
31
|
:jwt_token,
|
32
|
+
:session_jwt,
|
21
33
|
:set_jwt_token
|
22
34
|
)
|
23
35
|
|
24
36
|
def session
|
25
|
-
return super unless json_request?
|
26
37
|
return @session if defined?(@session)
|
27
|
-
|
38
|
+
return super unless use_jwt?
|
39
|
+
|
40
|
+
@session = if jwt_token
|
28
41
|
s = {}
|
29
|
-
|
30
|
-
payload.each do |k,v|
|
42
|
+
jwt_payload.each do |k,v|
|
31
43
|
s[k.to_sym] = v
|
32
44
|
end
|
33
45
|
s
|
@@ -38,37 +50,47 @@ module Rodauth
|
|
38
50
|
|
39
51
|
def clear_session
|
40
52
|
super
|
41
|
-
set_jwt if
|
53
|
+
set_jwt if use_jwt?
|
54
|
+
end
|
55
|
+
|
56
|
+
def only_json?
|
57
|
+
scope.class.opts[:rodauth_json] == :only
|
42
58
|
end
|
43
59
|
|
44
60
|
def set_field_error(field, message)
|
45
|
-
return super unless
|
61
|
+
return super unless use_jwt?
|
46
62
|
json_response[json_response_field_error_key] = [field, message]
|
47
63
|
end
|
48
64
|
|
49
65
|
def set_error_flash(message)
|
50
|
-
return super unless
|
66
|
+
return super unless use_jwt?
|
51
67
|
json_response[json_response_error_key] = message
|
52
68
|
end
|
53
69
|
|
54
70
|
def set_redirect_error_flash(message)
|
55
|
-
return super unless
|
71
|
+
return super unless use_jwt?
|
56
72
|
json_response[json_response_error_key] = message
|
57
73
|
end
|
58
74
|
|
59
75
|
def set_notice_flash(message)
|
60
|
-
return super unless
|
76
|
+
return super unless use_jwt?
|
61
77
|
json_response[json_response_success_key] = message if include_success_messages?
|
62
78
|
end
|
63
79
|
|
64
80
|
def set_notice_now_flash(message)
|
65
|
-
return super unless
|
81
|
+
return super unless use_jwt?
|
66
82
|
json_response[json_response_success_key] = message if include_success_messages?
|
67
83
|
end
|
68
84
|
|
85
|
+
def session_jwt
|
86
|
+
JWT.encode(session, jwt_secret, jwt_algorithm)
|
87
|
+
end
|
88
|
+
|
69
89
|
def jwt_token
|
70
|
-
|
71
|
-
|
90
|
+
return @jwt_token if defined?(@jwt_token)
|
91
|
+
|
92
|
+
if (v = request.env['HTTP_AUTHORIZATION']) && v !~ jwt_authorization_ignore
|
93
|
+
@jwt_token = v.sub(jwt_authorization_remove, '')
|
72
94
|
end
|
73
95
|
end
|
74
96
|
|
@@ -79,19 +101,27 @@ module Rodauth
|
|
79
101
|
private
|
80
102
|
|
81
103
|
def before_rodauth
|
82
|
-
if
|
104
|
+
if json_request?
|
105
|
+
if jwt_check_accept? && (accept = request.env['HTTP_ACCEPT']) && accept !~ json_accept_regexp
|
106
|
+
response.status = 406
|
107
|
+
json_response[json_response_error_key] = json_not_accepted_error_message
|
108
|
+
response['Content-Type'] ||= json_response_content_type
|
109
|
+
response.write(request.send(:convert_to_json, json_response))
|
110
|
+
request.halt
|
111
|
+
end
|
112
|
+
|
113
|
+
unless request.post?
|
114
|
+
response.status = 405
|
115
|
+
response.headers['Allow'] = 'POST'
|
116
|
+
json_response[json_response_error_key] = json_non_post_error_message
|
117
|
+
return_json_response
|
118
|
+
end
|
119
|
+
elsif only_json?
|
83
120
|
response.status = json_response_error_status
|
84
121
|
response.write non_json_request_error_message
|
85
122
|
request.halt
|
86
123
|
end
|
87
124
|
|
88
|
-
if json_request? && !request.post?
|
89
|
-
response.status = 405
|
90
|
-
response.headers['Allow'] = 'POST'
|
91
|
-
json_response[json_response_error_key] = json_non_post_error_message
|
92
|
-
return_json_response
|
93
|
-
end
|
94
|
-
|
95
125
|
super
|
96
126
|
end
|
97
127
|
|
@@ -103,12 +133,22 @@ module Rodauth
|
|
103
133
|
end
|
104
134
|
end
|
105
135
|
|
136
|
+
def jwt_payload
|
137
|
+
JWT.decode(jwt_token, jwt_secret, true, :algorithm=>jwt_algorithm)[0]
|
138
|
+
rescue JWT::DecodeError
|
139
|
+
json_response[json_response_error_key] = invalid_jwt_format_error_message
|
140
|
+
response.status ||= json_response_error_status
|
141
|
+
response['Content-Type'] ||= json_response_content_type
|
142
|
+
response.write(request.send(:convert_to_json, json_response))
|
143
|
+
request.halt
|
144
|
+
end
|
145
|
+
|
106
146
|
def jwt_secret
|
107
147
|
raise ArgumentError, "jwt_secret not set"
|
108
148
|
end
|
109
149
|
|
110
150
|
def redirect(_)
|
111
|
-
return super unless
|
151
|
+
return super unless use_jwt?
|
112
152
|
return_json_response
|
113
153
|
end
|
114
154
|
|
@@ -118,7 +158,7 @@ module Rodauth
|
|
118
158
|
|
119
159
|
def set_session_value(key, value)
|
120
160
|
super
|
121
|
-
set_jwt if
|
161
|
+
set_jwt if use_jwt?
|
122
162
|
value
|
123
163
|
end
|
124
164
|
|
@@ -127,7 +167,7 @@ module Rodauth
|
|
127
167
|
end
|
128
168
|
|
129
169
|
def _view(meth, page)
|
130
|
-
return super unless
|
170
|
+
return super unless use_jwt?
|
131
171
|
return super if meth == :render
|
132
172
|
return_json_response
|
133
173
|
end
|
@@ -135,17 +175,22 @@ module Rodauth
|
|
135
175
|
def return_json_response
|
136
176
|
response.status ||= json_response_error_status if json_response[json_response_error_key]
|
137
177
|
set_jwt
|
178
|
+
response['Content-Type'] ||= json_response_content_type
|
138
179
|
response.write(request.send(:convert_to_json, json_response))
|
139
180
|
request.halt
|
140
181
|
end
|
141
182
|
|
142
183
|
def set_jwt
|
143
|
-
set_jwt_token(
|
184
|
+
set_jwt_token(session_jwt)
|
144
185
|
end
|
145
186
|
|
146
187
|
def json_request?
|
147
188
|
return @json_request if defined?(@json_request)
|
148
|
-
@json_request = request.content_type =~
|
189
|
+
@json_request = request.content_type =~ json_request_content_type_regexp
|
190
|
+
end
|
191
|
+
|
192
|
+
def use_jwt?
|
193
|
+
jwt_token || only_json? || json_request?
|
149
194
|
end
|
150
195
|
end
|
151
196
|
end
|
data/lib/rodauth/version.rb
CHANGED
data/spec/jwt_spec.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe 'Rodauth login feature' do
|
4
|
+
it "should not have jwt feature assume JWT token given during Basic/Digest authentication" do
|
5
|
+
rodauth do
|
6
|
+
enable :login, :logout
|
7
|
+
end
|
8
|
+
roda(:jwt) do |r|
|
9
|
+
rodauth.require_authentication
|
10
|
+
'1'
|
11
|
+
end
|
12
|
+
|
13
|
+
res = json_request("/", :headers=>{'HTTP_AUTHORIZATION'=>'Basic foo'})
|
14
|
+
res.must_equal [400, {'error'=>'Please login to continue'}]
|
15
|
+
|
16
|
+
res = json_request("/", :headers=>{'HTTP_AUTHORIZATION'=>'Digest foo'})
|
17
|
+
res.must_equal [400, {'error'=>'Please login to continue'}]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should return error message if invalid JWT format used in request Authorization header" do
|
21
|
+
rodauth do
|
22
|
+
enable :login, :logout
|
23
|
+
end
|
24
|
+
roda(:jwt) do |r|
|
25
|
+
r.rodauth
|
26
|
+
rodauth.require_authentication
|
27
|
+
'1'
|
28
|
+
end
|
29
|
+
|
30
|
+
res = json_request('/login', :include_headers=>true, :login=>'foo@example.com', :password=>'0123456789')
|
31
|
+
|
32
|
+
res = json_request("/", :headers=>{'HTTP_AUTHORIZATION'=>res[1]['Authorization'][1..-1]})
|
33
|
+
res.must_equal [400, {'error'=>'invalid JWT format in Authorization header'}]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should require json request content type in only json mode for rodauth endpoints only" do
|
37
|
+
oj = false
|
38
|
+
rodauth do
|
39
|
+
enable :login, :logout, :jwt
|
40
|
+
jwt_secret '1'
|
41
|
+
json_response_success_key 'success'
|
42
|
+
only_json?{oj}
|
43
|
+
end
|
44
|
+
roda(:csrf=>false, :json=>true) do |r|
|
45
|
+
r.rodauth
|
46
|
+
rodauth.require_authentication
|
47
|
+
'1'
|
48
|
+
end
|
49
|
+
|
50
|
+
res = json_request("/", :content_type=>'application/x-www-form-urlencoded', :include_headers=>true, :method=>'GET')
|
51
|
+
res[1].delete('Set-Cookie')
|
52
|
+
res.must_equal [302, {"Content-Type"=>'text/html', "Content-Length"=>'0', "Location"=>"/login",}, []]
|
53
|
+
|
54
|
+
res = json_request("/", :content_type=>'application/vnd.api+json', :method=>'GET')
|
55
|
+
res.must_equal [400, ['{"error":"Please login to continue"}']]
|
56
|
+
|
57
|
+
oj = true
|
58
|
+
|
59
|
+
res = json_request("/", :content_type=>'application/x-www-form-urlencoded', :method=>'GET')
|
60
|
+
res.must_equal [400, ['{"error":"Please login to continue"}']]
|
61
|
+
|
62
|
+
res = json_request("/", :method=>'GET')
|
63
|
+
res.must_equal [400, {'error'=>'Please login to continue'}]
|
64
|
+
|
65
|
+
res = json_request("/login", :content_type=>'application/x-www-form-urlencoded', :include_headers=>true, :method=>'GET')
|
66
|
+
msg = "Only JSON format requests are allowed"
|
67
|
+
res[1].delete('Set-Cookie')
|
68
|
+
res.must_equal [400, {"Content-Type"=>'text/html', "Content-Length"=>msg.length.to_s}, [msg]]
|
69
|
+
|
70
|
+
json_login
|
71
|
+
|
72
|
+
res = json_request("/", :content_type=>'application/x-www-form-urlencoded', :include_headers=>true, :method=>'GET')
|
73
|
+
res.must_equal [200, {"Content-Type"=>'text/html', "Content-Length"=>'1'}, ['1']]
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should allow non-json requests if only_json? is false" do
|
77
|
+
rodauth do
|
78
|
+
enable :login, :logout, :jwt
|
79
|
+
jwt_secret '1'
|
80
|
+
only_json? false
|
81
|
+
end
|
82
|
+
roda(:jwt_html) do |r|
|
83
|
+
r.rodauth
|
84
|
+
rodauth.require_authentication
|
85
|
+
view(:content=>'1')
|
86
|
+
end
|
87
|
+
|
88
|
+
login
|
89
|
+
page.find('#notice_flash').text.must_equal 'You have been logged in'
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should require POST for json requests" do
|
93
|
+
rodauth do
|
94
|
+
enable :login, :logout, :jwt
|
95
|
+
jwt_secret '1'
|
96
|
+
json_response_success_key 'success'
|
97
|
+
end
|
98
|
+
roda(:jwt) do |r|
|
99
|
+
r.rodauth
|
100
|
+
end
|
101
|
+
|
102
|
+
res = json_request("/login", :method=>'GET')
|
103
|
+
res.must_equal [405, {'error'=>'non-POST method used in JSON API'}]
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should require Accept contain application/json if jwt_check_accept? is true and Accept is present" do
|
107
|
+
rodauth do
|
108
|
+
enable :login, :logout, :jwt
|
109
|
+
jwt_secret '1'
|
110
|
+
json_response_success_key 'success'
|
111
|
+
jwt_check_accept? true
|
112
|
+
end
|
113
|
+
roda(:jwt) do |r|
|
114
|
+
r.rodauth
|
115
|
+
end
|
116
|
+
|
117
|
+
res = json_request("/login", :headers=>{'HTTP_ACCEPT'=>'text/html'})
|
118
|
+
res.must_equal [406, {'error'=>'Unsupported Accept header. Must accept "application/json" or compatible content type'}]
|
119
|
+
|
120
|
+
json_request("/login", :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
|
121
|
+
json_request("/login", :headers=>{'HTTP_ACCEPT'=>'*/*'}, :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
|
122
|
+
json_request("/login", :headers=>{'HTTP_ACCEPT'=>'application/*'}, :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
|
123
|
+
json_request("/login", :headers=>{'HTTP_ACCEPT'=>'application/vnd.api+json'}, :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
|
124
|
+
end
|
125
|
+
end
|
data/spec/login_spec.rb
CHANGED
@@ -161,18 +161,12 @@ describe 'Rodauth login feature' do
|
|
161
161
|
end
|
162
162
|
roda(:jwt) do |r|
|
163
163
|
r.rodauth
|
164
|
+
response['Content-Type'] = 'application/json'
|
164
165
|
rodauth.logged_in? ? '1' : '2'
|
165
166
|
end
|
166
167
|
|
167
168
|
json_request.must_equal [200, 2]
|
168
169
|
|
169
|
-
visit '/login'
|
170
|
-
page.status_code.must_equal 400
|
171
|
-
page.body.must_equal "Only JSON format requests are allowed"
|
172
|
-
|
173
|
-
res = json_request("/login", :method=>'GET')
|
174
|
-
res.must_equal [405, {'error'=>'non-POST method used in JSON API'}]
|
175
|
-
|
176
170
|
res = json_request("/login", :login=>'foo@example2.com', :password=>'0123456789')
|
177
171
|
res.must_equal [400, {'error'=>"There was an error logging in", "field-error"=>["login", "no matching login"]}]
|
178
172
|
|
data/spec/rodauth_spec.rb
CHANGED
@@ -12,6 +12,19 @@ describe 'Rodauth' do
|
|
12
12
|
proc{visit '/'}.must_raise NoMethodError
|
13
13
|
end
|
14
14
|
|
15
|
+
it "should support template_opts" do
|
16
|
+
rodauth do
|
17
|
+
enable :login
|
18
|
+
template_opts(:layout_opts=>{:path=>'spec/views/layout-other.str'})
|
19
|
+
end
|
20
|
+
roda do |r|
|
21
|
+
r.rodauth
|
22
|
+
end
|
23
|
+
|
24
|
+
visit '/login'
|
25
|
+
page.title.must_equal 'Foo Login'
|
26
|
+
end
|
27
|
+
|
15
28
|
it "should require login to perform certain actions" do
|
16
29
|
rodauth do
|
17
30
|
enable :login, :change_password, :change_login, :close_account
|
data/spec/spec_helper.rb
CHANGED
@@ -57,7 +57,7 @@ ENV['RACK_ENV'] = 'test'
|
|
57
57
|
end
|
58
58
|
|
59
59
|
Base = Class.new(Roda)
|
60
|
-
Base.plugin :render, :
|
60
|
+
Base.plugin :render, :layout_opts=>{:path=>'spec/views/layout.str'}
|
61
61
|
Base.plugin(:not_found){raise "path #{request.path_info} not found"}
|
62
62
|
Base.use Rack::Session::Cookie, :secret=>'0123456789'
|
63
63
|
class Base
|
@@ -86,11 +86,17 @@ class Minitest::HooksSpec
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def roda(type=nil, &block)
|
89
|
-
|
90
|
-
|
89
|
+
jwt_only = type == :jwt
|
90
|
+
jwt = type == :jwt || type == :jwt_html
|
91
|
+
|
92
|
+
app = Class.new(jwt_only ? JsonBase : Base)
|
91
93
|
rodauth_block = @rodauth_block
|
92
94
|
opts = type.is_a?(Hash) ? type : {}
|
93
|
-
|
95
|
+
|
96
|
+
if jwt
|
97
|
+
opts[:json] = jwt_only ? :only : true
|
98
|
+
end
|
99
|
+
|
94
100
|
app.plugin(:rodauth, opts) do
|
95
101
|
title_instance_variable :@title
|
96
102
|
if jwt
|
@@ -129,6 +135,9 @@ class Minitest::HooksSpec
|
|
129
135
|
end
|
130
136
|
|
131
137
|
def json_request(path='/', params={})
|
138
|
+
include_headers = params.delete(:include_headers)
|
139
|
+
headers = params.delete(:headers)
|
140
|
+
|
132
141
|
env = {"REQUEST_METHOD" => params.delete(:method) || "POST",
|
133
142
|
"PATH_INFO" => path,
|
134
143
|
"SCRIPT_NAME" => "",
|
@@ -144,6 +153,8 @@ class Minitest::HooksSpec
|
|
144
153
|
env["HTTP_COOKIE"] = @cookie
|
145
154
|
end
|
146
155
|
|
156
|
+
env.merge!(headers) if headers
|
157
|
+
|
147
158
|
r = @app.call(env)
|
148
159
|
|
149
160
|
if cookie = r[1]['Set-Cookie']
|
@@ -152,7 +163,14 @@ class Minitest::HooksSpec
|
|
152
163
|
if authorization = r[1]['Authorization']
|
153
164
|
@authorization = authorization
|
154
165
|
end
|
155
|
-
|
166
|
+
|
167
|
+
if env["CONTENT_TYPE"] == "application/json"
|
168
|
+
r[1]['Content-Type'].must_equal 'application/json'
|
169
|
+
r[2] = JSON.parse("[#{r[2].join}]").first
|
170
|
+
end
|
171
|
+
|
172
|
+
r.delete_at(1) unless include_headers
|
173
|
+
r
|
156
174
|
end
|
157
175
|
|
158
176
|
def json_login(opts={})
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -230,6 +230,7 @@ extra_rdoc_files:
|
|
230
230
|
- doc/release_notes/1.2.0.txt
|
231
231
|
- doc/release_notes/1.3.0.txt
|
232
232
|
- doc/release_notes/1.4.0.txt
|
233
|
+
- doc/release_notes/1.5.0.txt
|
233
234
|
files:
|
234
235
|
- CHANGELOG
|
235
236
|
- MIT-LICENSE
|
@@ -259,6 +260,7 @@ files:
|
|
259
260
|
- doc/release_notes/1.2.0.txt
|
260
261
|
- doc/release_notes/1.3.0.txt
|
261
262
|
- doc/release_notes/1.4.0.txt
|
263
|
+
- doc/release_notes/1.5.0.txt
|
262
264
|
- doc/remember.rdoc
|
263
265
|
- doc/reset_password.rdoc
|
264
266
|
- doc/session_expiration.rdoc
|
@@ -310,6 +312,7 @@ files:
|
|
310
312
|
- spec/confirm_password_spec.rb
|
311
313
|
- spec/create_account_spec.rb
|
312
314
|
- spec/disallow_password_reuse_spec.rb
|
315
|
+
- spec/jwt_spec.rb
|
313
316
|
- spec/lockout_spec.rb
|
314
317
|
- spec/login_spec.rb
|
315
318
|
- spec/migrate/001_tables.rb
|
@@ -330,6 +333,7 @@ files:
|
|
330
333
|
- spec/verify_account_grace_period_spec.rb
|
331
334
|
- spec/verify_account_spec.rb
|
332
335
|
- spec/verify_change_login_spec.rb
|
336
|
+
- spec/views/layout-other.str
|
333
337
|
- spec/views/layout.str
|
334
338
|
- spec/views/login.str
|
335
339
|
- templates/add-recovery-codes.str
|