rodauth 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 03da9b51a0536d056d085d1d6c7d69f037e63825
4
- data.tar.gz: 6c554cef3c2f122fce444146f7032c922ef93b9d
3
+ metadata.gz: 8be62d375631a0d62337cc9da83782e1a765ecc1
4
+ data.tar.gz: e5cc52f5bec8e0f7820b1b16ac11d96d385e0187
5
5
  SHA512:
6
- metadata.gz: 37a8361ce43dfcf74bf5768e386eba59555c44fb3da0b29ae638eefa61ad6f9db70e9f25cf42831fddef200b10db9bb36fd67d60d630dfe11e96275d3f2be5a4
7
- data.tar.gz: d88123e032493908205f0f93f93f8ae813942d3574adddb89fb77cf9053f255f3d814c9c84ae84ee5c939ac1f946c60032b6bdba89b96644abb139ffea69fd3a
6
+ metadata.gz: cd373d957b4b636afab06b24a7c782c9b3e605de844f0083c7eb2953779715dc4fec20418984ef4cdbce93d9f65e4848eed11a8318985d52d03ecdb9b2f1301f
7
+ data.tar.gz: d156aaad0e89d164a467e44c946088f28487db41a4b0cb1ec57dda6584ca36ffbb8c87daf02bcc868dcfe8322bf56fc4fa005ab9fdfd2cd0b8620bce6334d593
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ === 1.6.0 (2016-10-24)
2
+
3
+ * Add http_basic_auth feature (TiagoCardoso1983, jeremyevans) (#12)
4
+
5
+ * Move login hooks from login feature to base, to be usable by other features (jeremyevans)
6
+
7
+ * Make reset_password feature not attempt to render a template in json-only mode (jeremyevans) (#11)
8
+
9
+ * Memoize jwt_payload in jwt feature, as it may be called more than once (mwpastore) (#10)
10
+
11
+ * Add jwt_decode_opts configuration method to jwt feature, for specifying options to JWT.decode, allowing for JWT claim verification (mwpastore, jeremyevans) (#9)
12
+
13
+ * Add jwt_session_hash configuration method to jwt feature, for modifying the session information stored in the JWT hash, allowing for setting JWT claims (mwpastore, jeremyevans) (#9)
14
+
15
+ * Add jwt_session_key configuration method to jwt feature, for nesting the session under a key in the JWT, avoiding reserve claim names (mwpastore, jeremyevans) (#9)
16
+
17
+ * Add jwt_symbolize_deeply? configuration method to jwt feature, for symbolizing nested keys in session hash when using JWT (mwpastore) (#9)
18
+
1
19
  === 1.5.0 (2016-09-22)
2
20
 
3
21
  * Return error instead of raising exception in the jwt feature if an invalid jwt format is submitted in the Authorization header (jeremyevans)
data/README.rdoc CHANGED
@@ -40,6 +40,7 @@ hashes by protecting access via database functions.
40
40
  * Single Session (Only one active session per account)
41
41
  * JWT (JSON API support for all other features)
42
42
  * Update Password Hash (when hash cost changes)
43
+ * HTTP Basic Auth
43
44
 
44
45
  == Resources
45
46
 
@@ -505,7 +506,7 @@ add additional logging when a user logs in:
505
506
  plugin :rodauth do
506
507
  enable :login, :logout
507
508
  after_login do
508
- LOGGER.info "#{account.email} logged in!"
509
+ LOGGER.info "#{account[:email]} logged in!"
509
510
  end
510
511
  end
511
512
 
@@ -525,7 +526,7 @@ So if you want to log the IP address for the user during login:
525
526
  plugin :rodauth do
526
527
  enable :login, :logout
527
528
  after_login do
528
- LOGGER.info "#{account.email} logged in from #{request.ip}"
529
+ LOGGER.info "#{account[:email]} logged in from #{request.ip}"
529
530
  end
530
531
  end
531
532
 
@@ -836,14 +837,15 @@ By setting <tt>env['rodauth'] = rodauth</tt> in the route block
836
837
  inside the middleware, you can easily provide a way for your
837
838
  application to call Rodauth methods.
838
839
 
839
- For an example of integrating Rodauth into a real application that
840
- doesn't use Roda, see
841
- {this example integrating Rodauth into Ginatra, a Sinatra-based git repository viewer}[https://github.com/jeremyevans/ginatra/commit/28108ebec96e8d42596ee55b01c3f7b50c155dd1].
840
+ Here are some examples of integrating Rodauth into applications that
841
+ doesn't use Roda:
842
842
 
843
- To see an example of integrating Rodauth into a Rails application, see
844
- {this example porting Rodauth's demo site to Rails}[https://github.com/jeremyevans/rodauth-demo-rails].
845
- This uses the {roda-rails gem}[https://github.com/jeremyevans/roda-rails]
846
- so that Rodauth uses Rails' CSRF and flash support.
843
+ * {Ginatra, a Sinatra-based git repository viewer}[https://github.com/jeremyevans/ginatra/commit/28108ebec96e8d42596ee55b01c3f7b50c155dd1]
844
+ * {Rodauth's demo site as a Rails application}[https://github.com/jeremyevans/rodauth-demo-rails] (
845
+ This uses the {roda-rails gem}[https://github.com/jeremyevans/roda-rails]
846
+ so that Rodauth uses Rails' CSRF and flash support)
847
+ * {Grape application}[https://github.com/davydovanton/grape-rodauth]
848
+ * {Hanami application}[https://github.com/davydovanton/rodauth_hanami]
847
849
 
848
850
  === Using 2 Factor Authentication
849
851
 
data/doc/base.rdoc CHANGED
@@ -69,6 +69,13 @@ use_database_authentication_functions? :: Whether to use functions to do authent
69
69
 
70
70
  == Auth Methods
71
71
 
72
+ after_login :: Run arbitrary code after a successful login.
73
+ after_login_failure :: Run arbitrary code after a login failure due to
74
+ an invalid password.
75
+ before_login :: Run arbitrary code after password has been checked, but
76
+ before updating the session.
77
+ before_login_attempt :: Run arbitrary code after an account has been
78
+ located, but before the password has been checked.
72
79
  before_rodauth :: Run arbitrary code before handling any rodauth route.
73
80
  account_from_login(login) :: Retrieve the account model instance related to the
74
81
  given login or nil if no login matches.
@@ -0,0 +1,8 @@
1
+ = Documentation for HTTP Basic Auth Feature
2
+
3
+ The HTTP basic auth feature allows logins using HTTP basic authentication,
4
+ described in RFC 1945.
5
+
6
+ == Auth Value Methods
7
+
8
+ http_basic_auth_realm :: The realm to return in the WWW-Authenticate header.
data/doc/jwt.rdoc CHANGED
@@ -46,17 +46,21 @@ json_response_error_status :: The HTTP status code to use for JSON error respons
46
46
  json_response_field_error_key :: The JSON result key containing an field error message, "field-error" by default.
47
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.
48
48
  jwt_algorithm :: The JWT algorithm to use, "HS256" by default.
49
- non_json_request_error_message :: The error message to use when a non-JSON request is sent and +only_json?+ is set.
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
49
  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
50
  jwt_authorization_remove :: A regexp to remove from the Authorization header before processing the JWT. By default, a Bearer prefix is removed.
53
51
  jwt_check_accept? :: Whether to check the Accept header to see if the client supports JSON responses, false by default for backwards compatibility.
52
+ jwt_decode_opts :: An optional hash to pass to JWT.decode. Can be used to set JWT verifiers.
54
53
  jwt_secret :: The JWT secret to use. Access to this should be protected the same as a session secret.
54
+ jwt_session_key :: A key to nest the session hash under in the JWT payload. nil by default, for no nesting.
55
+ jwt_symbolize_deeply? :: Whether to symbolize the session hash deeply. false by default.
56
+ non_json_request_error_message :: The error message to use when a non-JSON request is sent and +only_json?+ is set.
57
+ 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.
55
58
  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.
56
59
 
57
60
  == Auth Methods
58
61
 
59
62
  json_request? :: Whether the current request is a JSON request, looks at the Content-Type request header by default.
63
+ jwt_session_hash :: The session hash used to create the session_jwt. Can be used to set JWT claims.
60
64
  jwt_token :: Retrieve the JWT token from the request, by default taking it from the Authorization header.
61
65
  session_jwt :: An encoded JWT for the current session.
62
66
  set_jwt_token(token) :: Set the JWT token in the response, by default storing it in the Authorization header.
data/doc/login.rdoc CHANGED
@@ -16,12 +16,5 @@ login_route :: The route to the login action.
16
16
 
17
17
  == Auth Methods
18
18
 
19
- after_login :: Run arbitrary code after a successful login.
20
- after_login_failure :: Run arbitrary code after a login failure due to
21
- an invalid password.
22
- before_login :: Run arbitrary code after password has been checked, but
23
- before updating the session.
24
- before_login_attempt :: Run arbitrary code after an account has been
25
- located, but before the password has been checked.
26
19
  before_login_route :: Run arbitrary code before handling a login route.
27
20
  login_view :: The HTML to use for the login form.
@@ -0,0 +1,37 @@
1
+ = New Feature
2
+
3
+ * An http_basic_auth feature has been added, allowing the use of
4
+ HTTP Basic Auth to login.
5
+
6
+ = New Configuration Options for jwt Feature
7
+
8
+ * jwt_session_hash has been added, for modifying the hash given before
9
+ creating the JWT. This can be used for setting JWT claims.
10
+ Example:
11
+
12
+ jwt_session_hash do
13
+ super().merge(:exp=>Time.now.to_i + 120)
14
+ end
15
+
16
+ * jwt_decode_opts has been added for specifying additional options to
17
+ JWT.decode. Among other things, this allows for JWT claim
18
+ verification. Example:
19
+
20
+ jwt_decode_opts(:verify_expiration=>true)
21
+
22
+ * jwt_session_key has been added, specifying a key in the JWT that
23
+ will be used to store session information, instead of storing
24
+ session keys in the root of the JWT. Use of this option can avoid
25
+ issues with reserved JWT claim names, and will probably be enabled
26
+ by default starting in Rodauth 2.
27
+
28
+ * jwt_symbolize_deeply? configuration method has been added, for
29
+ whether to symbolize nested keys when decoding a JWT session hash.
30
+
31
+ = Other Improvements
32
+
33
+ * The reset_password feature no longer attempts to render a template
34
+ in json-only mode.
35
+
36
+ * The jwt_payload method is now memoized by default.
37
+
@@ -2,6 +2,10 @@
2
2
 
3
3
  module Rodauth
4
4
  Base = Feature.define(:base) do
5
+ after 'login'
6
+ after 'login_failure'
7
+ before 'login'
8
+ before 'login_attempt'
5
9
  before 'rodauth'
6
10
 
7
11
  error_flash "Please login to continue", 'require_login'
@@ -382,6 +386,10 @@ module Rodauth
382
386
  {account_status_column=>account_open_status_value}
383
387
  end
384
388
 
389
+ def only_json?
390
+ scope.class.opts[:rodauth_json] == :only
391
+ end
392
+
385
393
  def template_path(page)
386
394
  File.join(File.dirname(__FILE__), '../../../templates', "#{page}.str")
387
395
  end
@@ -0,0 +1,50 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ HTTTBasicAuth = Feature.define(:http_basic_auth) do
5
+ auth_value_method :http_basic_auth_realm, "protected"
6
+
7
+ def session
8
+ return @session if defined?(@session)
9
+ sess = super
10
+ return sess if sess[session_key]
11
+ return sess unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
12
+ username, password = token.unpack("m*").first.split(/:/, 2)
13
+
14
+ if username && password
15
+ catch_error do
16
+ unless account_from_login(username)
17
+ throw_basic_auth_error(login_param, no_matching_login_message)
18
+ end
19
+
20
+ before_login_attempt
21
+
22
+ unless open_account?
23
+ throw_basic_auth_error(login_param, no_matching_login_message)
24
+ end
25
+
26
+ unless password_match?(password)
27
+ after_login_failure
28
+ throw_basic_auth_error(password_param, invalid_password_message)
29
+ end
30
+
31
+ transaction do
32
+ before_login
33
+ sess[session_key] = account_session_value
34
+ after_login
35
+ end
36
+ end
37
+ end
38
+
39
+ sess
40
+ end
41
+
42
+ private
43
+
44
+ def throw_basic_auth_error(*args)
45
+ response.status = 401
46
+ response.headers["WWW-Authenticate"] = "Basic realm=\"#{http_basic_auth_realm}\""
47
+ throw_error(*args)
48
+ end
49
+ end
50
+ end
@@ -4,7 +4,7 @@ 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
+ auth_value_method :invalid_jwt_format_error_message, "invalid JWT format or claim in Authorization header"
8
8
  auth_value_method :json_non_post_error_message, 'non-POST method used in JSON API'
9
9
  auth_value_method :json_not_accepted_error_message, 'Unsupported Accept header. Must accept "application/json" or compatible content type'
10
10
  auth_value_method :json_accept_regexp, /(?:(?:\*|\bapplication)\/\*|\bapplication\/(?:vnd\.api\+)?json\b)/i
@@ -18,6 +18,9 @@ module Rodauth
18
18
  auth_value_method :jwt_authorization_ignore, /\A(?:Basic|Digest) /
19
19
  auth_value_method :jwt_authorization_remove, /\ABearer:?\s+/
20
20
  auth_value_method :jwt_check_accept?, false
21
+ auth_value_method :jwt_decode_opts, {}
22
+ auth_value_method :jwt_session_key, nil
23
+ auth_value_method :jwt_symbolize_deeply?, false
21
24
  auth_value_method :non_json_request_error_message, 'Only JSON format requests are allowed'
22
25
 
23
26
  auth_value_methods(
@@ -28,6 +31,7 @@ module Rodauth
28
31
 
29
32
  auth_methods(
30
33
  :json_request?,
34
+ :jwt_session_hash,
31
35
  :jwt_token,
32
36
  :session_jwt,
33
37
  :set_jwt_token
@@ -37,15 +41,15 @@ module Rodauth
37
41
  return @session if defined?(@session)
38
42
  return super unless use_jwt?
39
43
 
40
- @session = if jwt_token
41
- s = {}
42
- jwt_payload.each do |k,v|
43
- s[k.to_sym] = v
44
+ s = {}
45
+ if jwt_token && (session_data = jwt_session_key ? jwt_payload[jwt_session_key] : jwt_payload)
46
+ if jwt_symbolize_deeply?
47
+ s = JSON.parse(JSON.fast_generate(session_data), :symbolize_names=>true)
48
+ else
49
+ session_data.each{|k,v| s[k.to_sym] = v}
44
50
  end
45
- s
46
- else
47
- {}
48
51
  end
52
+ @session = s
49
53
  end
50
54
 
51
55
  def clear_session
@@ -53,10 +57,6 @@ module Rodauth
53
57
  set_jwt if use_jwt?
54
58
  end
55
59
 
56
- def only_json?
57
- scope.class.opts[:rodauth_json] == :only
58
- end
59
-
60
60
  def set_field_error(field, message)
61
61
  return super unless use_jwt?
62
62
  json_response[json_response_field_error_key] = [field, message]
@@ -82,8 +82,12 @@ module Rodauth
82
82
  json_response[json_response_success_key] = message if include_success_messages?
83
83
  end
84
84
 
85
+ def jwt_session_hash
86
+ jwt_session_key ? {jwt_session_key=>session} : session
87
+ end
88
+
85
89
  def session_jwt
86
- JWT.encode(session, jwt_secret, jwt_algorithm)
90
+ JWT.encode(jwt_session_hash, jwt_secret, jwt_algorithm)
87
91
  end
88
92
 
89
93
  def jwt_token
@@ -134,7 +138,7 @@ module Rodauth
134
138
  end
135
139
 
136
140
  def jwt_payload
137
- JWT.decode(jwt_token, jwt_secret, true, :algorithm=>jwt_algorithm)[0]
141
+ @jwt_payload ||= JWT.decode(jwt_token, jwt_secret, true, jwt_decode_opts.merge(:algorithm=>jwt_algorithm))[0]
138
142
  rescue JWT::DecodeError
139
143
  json_response[json_response_error_key] = invalid_jwt_format_error_message
140
144
  response.status ||= json_response_error_status
@@ -5,10 +5,6 @@ module Rodauth
5
5
  notice_flash "You have been logged in"
6
6
  error_flash "There was an error logging in"
7
7
  view 'login', 'Login'
8
- after
9
- after 'login_failure'
10
- before
11
- before 'login_attempt'
12
8
  additional_form_tags
13
9
  button 'Login'
14
10
  redirect
@@ -165,7 +165,9 @@ module Rodauth
165
165
  attr_reader :reset_password_key_value
166
166
 
167
167
  def after_login_failure
168
- @login_form_header = render("reset-password-request")
168
+ unless only_json?
169
+ @login_form_header = render("reset-password-request")
170
+ end
169
171
  super
170
172
  end
171
173
 
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- VERSION = '1.5.0'.freeze
4
+ VERSION = '1.6.0'.freeze
5
5
 
6
6
  def self.version
7
7
  VERSION
@@ -0,0 +1,125 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe "Rodauth http basic auth feature" do
4
+ def basic_auth_visit(opts={})
5
+ page.driver.browser.basic_authorize(opts.fetch(:username,"foo@example.com"), opts.fetch(:password, "0123456789"))
6
+ visit(opts.fetch(:path, '/'))
7
+ end
8
+
9
+ def authorization_header(opts={})
10
+ ["#{opts.delete(:username)||'foo@example.com'}:#{opts.delete(:password)||'0123456789'}"].pack("m*")
11
+ end
12
+
13
+ def basic_auth_json_request(opts={})
14
+ auth = opts.delete(:auth) || authorization_header(opts)
15
+ path = opts.delete(:path) || '/'
16
+ json_request(path, opts.merge(:headers => {"HTTP_AUTHORIZATION" => "Basic #{auth}"}, :method=>'GET'))
17
+ end
18
+
19
+ def newline_basic_auth_json_request(opts={})
20
+ auth = opts.delete(:auth) || authorization_header(opts)
21
+ auth.chomp!
22
+ basic_auth_json_request(opts.merge(:auth => auth))
23
+ end
24
+
25
+ describe "on page visit" do
26
+ before do
27
+ rodauth do
28
+ enable :http_basic_auth
29
+ end
30
+ roda do |r|
31
+ r.rodauth
32
+ r.root{view :content=>(rodauth.logged_in? ? "Logged In" : 'Not Logged')}
33
+ end
34
+ end
35
+
36
+ it "handles logins" do
37
+ basic_auth_visit
38
+ page.text.must_include "Logged In"
39
+ end
40
+
41
+ it "keeps the user logged in" do
42
+ visit '/'
43
+ page.text.must_include "Not Logged"
44
+
45
+ basic_auth_visit
46
+ page.text.must_include "Logged In"
47
+
48
+ visit '/'
49
+ page.text.must_include "Logged In"
50
+ end
51
+
52
+ it "fails when no login is found" do
53
+ basic_auth_visit(:username => "foo2@example.com")
54
+ page.text.must_include "Not Logged"
55
+ page.response_headers.keys.must_include("WWW-Authenticate")
56
+ end
57
+
58
+ it "fails when passowrd does not match" do
59
+ basic_auth_visit(:password => "1111111111")
60
+ page.text.must_include "Not Logged"
61
+ page.response_headers.keys.must_include("WWW-Authenticate")
62
+ end
63
+ end
64
+
65
+ it "works with standard authentication" do
66
+ rodauth do
67
+ enable :login, :http_basic_auth
68
+ end
69
+ roda do |r|
70
+ r.rodauth
71
+ r.root{view :content=>(rodauth.logged_in? ? "Logged In" : 'Not Logged')}
72
+ end
73
+
74
+ login
75
+ page.text.must_include "Logged In"
76
+ end
77
+
78
+ it "does not allow login to unverified account" do
79
+ rodauth do
80
+ enable :http_basic_auth
81
+ skip_status_checks? false
82
+ end
83
+ roda do |r|
84
+ r.rodauth
85
+ r.root{view :content=>(rodauth.logged_in? ? "Logged In" : 'Not Logged')}
86
+ end
87
+ DB[:accounts].update(:status_id=>1)
88
+
89
+ basic_auth_visit
90
+ page.text.must_include "Not Logged"
91
+ page.response_headers.keys.must_include("WWW-Authenticate")
92
+ end
93
+
94
+ it "should login via jwt" do
95
+ rodauth do
96
+ enable :http_basic_auth
97
+ end
98
+ roda(:jwt) do |r|
99
+ r.rodauth
100
+ response['Content-Type'] = 'application/json'
101
+ rodauth.require_authentication
102
+ {"success"=>'You have been logged in'}
103
+ end
104
+
105
+ @authorization = nil
106
+ res = basic_auth_json_request(:auth=>'.')
107
+ res.must_equal [400, {'error'=>"Please login to continue"}]
108
+
109
+ @authorization = nil
110
+ res = basic_auth_json_request(:username=>'foo@example2.com')
111
+ res.must_equal [401, {'error'=>"Please login to continue", "field-error"=>["login", "no matching login"]}]
112
+
113
+ @authorization = nil
114
+ res = basic_auth_json_request(:password=>'012345678')
115
+ res.must_equal [401, {'error'=>"Please login to continue", "field-error"=>["password", "invalid password"]}]
116
+
117
+ @authorization = nil
118
+ res = newline_basic_auth_json_request
119
+ res.must_equal [200, {"success"=>'You have been logged in'}]
120
+
121
+ @authorization = nil
122
+ res = basic_auth_json_request
123
+ res.must_equal [200, {"success"=>'You have been logged in'}]
124
+ end
125
+ end
data/spec/jwt_spec.rb CHANGED
@@ -30,7 +30,7 @@ describe 'Rodauth login feature' do
30
30
  res = json_request('/login', :include_headers=>true, :login=>'foo@example.com', :password=>'0123456789')
31
31
 
32
32
  res = json_request("/", :headers=>{'HTTP_AUTHORIZATION'=>res[1]['Authorization'][1..-1]})
33
- res.must_equal [400, {'error'=>'invalid JWT format in Authorization header'}]
33
+ res.must_equal [400, {'error'=>'invalid JWT format or claim in Authorization header'}]
34
34
  end
35
35
 
36
36
  it "should require json request content type in only json mode for rodauth endpoints only" do
@@ -122,4 +122,62 @@ describe 'Rodauth login feature' do
122
122
  json_request("/login", :headers=>{'HTTP_ACCEPT'=>'application/*'}, :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
123
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
124
  end
125
+
126
+ it "generates and verifies JWTs with claims" do
127
+ invalid_jti = false
128
+
129
+ rodauth do
130
+ enable :login, :logout, :jwt
131
+ jwt_secret '1'
132
+ json_response_success_key 'success'
133
+ jwt_session_key 'data'
134
+ jwt_symbolize_deeply? true
135
+ jwt_session_hash do
136
+ h = super()
137
+ h['data']['foo'] = {:bar=>[1]}
138
+ h.merge(
139
+ :aud => %w[Young Old],
140
+ :exp => Time.now.to_i + 120,
141
+ :iat => Time.now.to_i,
142
+ :iss => "Foobar, Inc.",
143
+ :jti => SecureRandom.hex(10),
144
+ :nbf => Time.now.to_i - 30,
145
+ :sub => session_value
146
+ )
147
+ end
148
+ jwt_decode_opts(
149
+ :aud => 'Old',
150
+ :iss => "Foobar, Inc.",
151
+ :leeway => 30,
152
+ :verify_aud => true,
153
+ :verify_expiration => true,
154
+ :verify_iat => true,
155
+ :verify_iss => true,
156
+ :verify_jti => proc{|jti| invalid_jti ? false : !!jti},
157
+ :verify_not_before => true
158
+ )
159
+ end
160
+ roda(:jwt) do |r|
161
+ r.rodauth
162
+ r.post{rodauth.session[:foo][:bar]}
163
+ end
164
+
165
+ json_login.must_equal [200, {"success"=>'You have been logged in'}]
166
+
167
+ payload = JWT.decode(@authorization, nil, false)[0]
168
+ payload['sub'].must_equal payload['data']['account_id']
169
+ payload['iat'].must_be_kind_of Integer
170
+ payload['exp'].must_be_kind_of Integer
171
+ payload['nbf'].must_be_kind_of Integer
172
+ payload['iss'].must_equal "Foobar, Inc."
173
+ payload['aud'].must_equal %w[Young Old]
174
+ payload['jti'].must_match(/^[0-9a-f]{20}$/)
175
+
176
+ json_request.must_equal [200, [1]]
177
+
178
+ invalid_jti = true
179
+ if RUBY_VERSION >= '1.9'
180
+ json_login(:no_check=>true).must_equal [400, {"error"=>'invalid JWT format or claim in Authorization header'}]
181
+ end
182
+ end
125
183
  end
@@ -155,6 +155,9 @@ describe 'Rodauth reset_password feature' do
155
155
  r.rodauth
156
156
  end
157
157
 
158
+ res = json_login(:pass=>'1', :no_check=>true)
159
+ res.must_equal [400, {"field-error"=>["password", "invalid password"], "error"=>"There was an error logging in"}]
160
+
158
161
  res = json_request('/reset-password')
159
162
  res.must_equal [400, {"error"=>"There was an error resetting your password"}]
160
163
 
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.5.0
4
+ version: 1.6.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-09-22 00:00:00.000000000 Z
11
+ date: 2016-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -225,12 +225,14 @@ extra_rdoc_files:
225
225
  - doc/confirm_password.rdoc
226
226
  - doc/verify_change_login.rdoc
227
227
  - doc/update_password_hash.rdoc
228
+ - doc/http_basic_auth.rdoc
228
229
  - doc/release_notes/1.0.0.txt
229
230
  - doc/release_notes/1.1.0.txt
230
231
  - doc/release_notes/1.2.0.txt
231
232
  - doc/release_notes/1.3.0.txt
232
233
  - doc/release_notes/1.4.0.txt
233
234
  - doc/release_notes/1.5.0.txt
235
+ - doc/release_notes/1.6.0.txt
234
236
  files:
235
237
  - CHANGELOG
236
238
  - MIT-LICENSE
@@ -245,6 +247,7 @@ files:
245
247
  - doc/create_account.rdoc
246
248
  - doc/disallow_password_reuse.rdoc
247
249
  - doc/email_base.rdoc
250
+ - doc/http_basic_auth.rdoc
248
251
  - doc/jwt.rdoc
249
252
  - doc/lockout.rdoc
250
253
  - doc/login.rdoc
@@ -261,6 +264,7 @@ files:
261
264
  - doc/release_notes/1.3.0.txt
262
265
  - doc/release_notes/1.4.0.txt
263
266
  - doc/release_notes/1.5.0.txt
267
+ - doc/release_notes/1.6.0.txt
264
268
  - doc/remember.rdoc
265
269
  - doc/reset_password.rdoc
266
270
  - doc/session_expiration.rdoc
@@ -282,6 +286,7 @@ files:
282
286
  - lib/rodauth/features/create_account.rb
283
287
  - lib/rodauth/features/disallow_password_reuse.rb
284
288
  - lib/rodauth/features/email_base.rb
289
+ - lib/rodauth/features/http_basic_auth.rb
285
290
  - lib/rodauth/features/jwt.rb
286
291
  - lib/rodauth/features/lockout.rb
287
292
  - lib/rodauth/features/login.rb
@@ -312,6 +317,7 @@ files:
312
317
  - spec/confirm_password_spec.rb
313
318
  - spec/create_account_spec.rb
314
319
  - spec/disallow_password_reuse_spec.rb
320
+ - spec/http_basic_auth_spec.rb
315
321
  - spec/jwt_spec.rb
316
322
  - spec/lockout_spec.rb
317
323
  - spec/login_spec.rb
@@ -398,7 +404,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
398
404
  version: '0'
399
405
  requirements: []
400
406
  rubyforge_project:
401
- rubygems_version: 2.5.1
407
+ rubygems_version: 2.6.6
402
408
  signing_key:
403
409
  specification_version: 4
404
410
  summary: Authentication and Account Management Framework for Rack Applications