jwt-auth 4.2.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/Gemfile +3 -0
  4. data/README.md +119 -18
  5. data/bin/build +22 -0
  6. data/bin/release +40 -0
  7. data/jwt-auth.gemspec +18 -15
  8. data/lib/jwt/auth.rb +2 -0
  9. data/lib/jwt/auth/access_token.rb +20 -0
  10. data/lib/jwt/auth/authenticatable.rb +16 -0
  11. data/lib/jwt/auth/authentication.rb +63 -22
  12. data/lib/jwt/auth/configuration.rb +4 -1
  13. data/lib/jwt/auth/refresh_token.rb +20 -0
  14. data/lib/jwt/auth/token.rb +49 -41
  15. data/lib/jwt/auth/version.rb +3 -1
  16. data/spec/controllers/content_controller_spec.rb +95 -0
  17. data/spec/controllers/tokens_controller_spec.rb +140 -0
  18. data/spec/dummy/Rakefile +2 -0
  19. data/spec/dummy/app/channels/application_cable/channel.rb +2 -0
  20. data/spec/dummy/app/channels/application_cable/connection.rb +2 -0
  21. data/spec/dummy/app/controllers/application_controller.rb +6 -1
  22. data/spec/dummy/app/controllers/content_controller.rb +29 -0
  23. data/spec/dummy/app/controllers/tokens_controller.rb +53 -0
  24. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  25. data/spec/dummy/app/helpers/authentication_helper.rb +2 -0
  26. data/spec/dummy/app/jobs/application_job.rb +2 -0
  27. data/spec/dummy/app/mailers/application_mailer.rb +3 -1
  28. data/spec/dummy/app/models/application_record.rb +2 -0
  29. data/spec/dummy/app/models/user.rb +3 -6
  30. data/spec/dummy/bin/bundle +2 -0
  31. data/spec/dummy/bin/rails +2 -0
  32. data/spec/dummy/bin/rake +2 -0
  33. data/spec/dummy/bin/setup +2 -0
  34. data/spec/dummy/bin/update +2 -0
  35. data/spec/dummy/bin/yarn +7 -7
  36. data/spec/dummy/config.ru +2 -0
  37. data/spec/dummy/config/application.rb +2 -0
  38. data/spec/dummy/config/boot.rb +3 -1
  39. data/spec/dummy/config/environment.rb +2 -0
  40. data/spec/dummy/config/environments/development.rb +3 -1
  41. data/spec/dummy/config/environments/production.rb +4 -2
  42. data/spec/dummy/config/environments/test.rb +2 -0
  43. data/spec/dummy/config/initializers/application_controller_renderer.rb +2 -0
  44. data/spec/dummy/config/initializers/assets.rb +2 -0
  45. data/spec/dummy/config/initializers/backtrace_silencers.rb +2 -0
  46. data/spec/dummy/config/initializers/content_security_policy.rb +2 -0
  47. data/spec/dummy/config/initializers/cookies_serializer.rb +2 -0
  48. data/spec/dummy/config/initializers/filter_parameter_logging.rb +2 -0
  49. data/spec/dummy/config/initializers/inflections.rb +2 -0
  50. data/spec/dummy/config/initializers/jwt_auth.rb +9 -2
  51. data/spec/dummy/config/initializers/mime_types.rb +2 -0
  52. data/spec/dummy/config/initializers/new_framework_defaults_5_2.rb +2 -0
  53. data/spec/dummy/config/initializers/wrap_parameters.rb +3 -1
  54. data/spec/dummy/config/puma.rb +5 -3
  55. data/spec/dummy/config/routes.rb +5 -4
  56. data/spec/dummy/config/spring.rb +4 -2
  57. data/spec/dummy/db/migrate/20170726110751_create_users.rb +2 -0
  58. data/spec/dummy/db/migrate/20170726110825_add_token_version_to_user.rb +2 -0
  59. data/spec/dummy/db/migrate/20170726112117_add_activated_to_user.rb +2 -0
  60. data/spec/dummy/db/migrate/20190221100103_add_password_to_user.rb +7 -0
  61. data/spec/dummy/db/schema.rb +10 -9
  62. data/spec/jwt/auth/access_token_spec.rb +35 -0
  63. data/spec/jwt/auth/configuration_spec.rb +36 -0
  64. data/spec/jwt/auth/refresh_token_spec.rb +35 -0
  65. data/spec/jwt/auth/token_spec.rb +144 -0
  66. data/spec/models/user_spec.rb +24 -0
  67. data/spec/rails_helper.rb +8 -0
  68. data/spec/spec_helper.rb +51 -53
  69. data/spec/support/database_cleaner.rb +22 -0
  70. data/spec/support/matchers/return_token.rb +33 -0
  71. data/version.yml +1 -0
  72. metadata +119 -54
  73. data/spec/authentication_spec.rb +0 -136
  74. data/spec/configuration_spec.rb +0 -18
  75. data/spec/dummy/app/controllers/authentication_controller.rb +0 -22
  76. data/spec/token_spec.rb +0 -125
@@ -3,7 +3,10 @@
3
3
  module JWT
4
4
  module Auth
5
5
  class << self
6
- attr_accessor :model, :secret, :token_lifetime
6
+ attr_accessor :model,
7
+ :secret,
8
+ :refresh_token_lifetime,
9
+ :access_token_lifetime
7
10
 
8
11
  def configure
9
12
  yield JWT::Auth
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt/auth/configuration'
4
+
5
+ module JWT
6
+ module Auth
7
+ ##
8
+ # JWT refresh token
9
+ #
10
+ class RefreshToken < Token
11
+ def type
12
+ :refresh
13
+ end
14
+
15
+ def lifetime
16
+ JWT::Auth.refresh_token_lifetime
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # require 'active_support/core_ext/numeric/time'
4
-
5
3
  require 'jwt/auth/configuration'
6
4
 
7
5
  module JWT
@@ -10,68 +8,67 @@ module JWT
10
8
  # In-memory representation of JWT
11
9
  #
12
10
  class Token
13
- attr_accessor :issued_at, :subject, :token_version
11
+ attr_accessor :issued_at,
12
+ :subject,
13
+ :version
14
+
15
+ def initialize(params = {})
16
+ params.each { |key, value| send "#{key}=", value }
17
+ end
14
18
 
15
19
  def valid?
16
20
  # Reload subject to prevent caching the old token_version
17
- subject && subject.reload
21
+ subject&.reload
18
22
 
19
- return false if subject.nil? || issued_at.nil? || token_version.nil?
23
+ return false if subject.nil? || issued_at.nil? || version.nil?
20
24
  return false if Time.at(issued_at + lifetime.to_i).past?
21
25
  return false if Time.at(issued_at).future?
22
- return false if token_version != subject.token_version
26
+ return false if version != subject.token_version
23
27
 
24
28
  true
25
29
  rescue ActiveRecord::RecordNotFound
26
30
  false
27
31
  end
28
32
 
29
- def renew!
30
- self.issued_at = nil
31
- self.token_version = nil
32
- end
33
-
34
33
  def to_jwt
35
34
  JWT.encode payload, JWT::Auth.secret
36
35
  end
37
36
 
38
- def self.from_user(subject)
39
- token = self.new
40
- token.subject = subject
41
-
42
- token
43
- end
44
-
45
- def payload
46
- {
47
- :iat => issued_at || Time.now.to_i,
48
- :sub => subject.id,
49
- :ver => token_version || subject.token_version
50
- }
37
+ ##
38
+ # Override this method in subclasses
39
+ #
40
+ def type
41
+ raise NotImplementedError
51
42
  end
52
43
 
44
+ ##
45
+ # Override this method in subclasses
46
+ #
53
47
  def lifetime
54
- JWT::Auth.token_lifetime
48
+ raise NotImplementedError
55
49
  end
56
50
 
57
51
  class << self
58
- def from_token(token)
59
- begin
60
- @decoded_payload = JWT.decode(token, JWT::Auth.secret).first
61
- rescue JWT::DecodeError
62
- @decoded_payload = {}
52
+ def from_jwt(token)
53
+ @decoded_payload = JWT.decode(token, JWT::Auth.secret).first
54
+
55
+ params = {
56
+ :issued_at => @decoded_payload['iat'],
57
+ :version => @decoded_payload['ver'],
58
+ :subject => model.find_by_token(:id => @decoded_payload['sub'],
59
+ :token_version => @decoded_payload['ver'])
60
+ }
61
+
62
+ case @decoded_payload['typ']
63
+ when 'access'
64
+ return AccessToken.new params
65
+ when 'refresh'
66
+ return RefreshToken.new params
67
+ else
68
+ return nil
63
69
  end
64
-
65
- token = self.new
66
- token.issued_at = @decoded_payload['iat']
67
- token.token_version = @decoded_payload['ver']
68
-
69
- if @decoded_payload['sub']
70
- find_method = model.respond_to?(:find_by_token) ? :find_by_token : :find_by
71
- token.subject = model.send find_method, :id => @decoded_payload['sub'], :token_version => @decoded_payload['ver']
72
- end
73
-
74
- token
70
+ rescue JWT::DecodeError
71
+ nil
75
72
  end
76
73
 
77
74
  private
@@ -80,6 +77,17 @@ module JWT
80
77
  const_get JWT::Auth.model
81
78
  end
82
79
  end
80
+
81
+ private
82
+
83
+ def payload
84
+ {
85
+ :iat => issued_at || Time.now.to_i,
86
+ :sub => subject.id,
87
+ :ver => version || subject.token_version,
88
+ :typ => type
89
+ }
90
+ end
83
91
  end
84
92
  end
85
93
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'yaml'
4
+
3
5
  module JWT
4
6
  module Auth
5
- VERSION = '4.2.0'
7
+ VERSION = YAML.load_file File.join __dir__, '..', '..', '..', 'version.yml'
6
8
  end
7
9
  end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe ContentController do
6
+ ##
7
+ # Configuration
8
+ #
9
+ ##
10
+ # Test variables
11
+ #
12
+ let(:user) { User.create :activated => true }
13
+
14
+ let(:headers) do
15
+ { 'Authorization' => "Bearer #{token.to_jwt}" }
16
+ end
17
+
18
+ # Ensure the user is created, which autoloads the model and initializes the JWT::Auth.model configuration entry
19
+ before { user }
20
+
21
+ ##
22
+ # Tests
23
+ #
24
+ describe 'GET /unauthenticated' do
25
+ subject { get :unauthenticated }
26
+
27
+ context 'when no token was specified' do
28
+ it { is_expected.to have_http_status :no_content }
29
+ end
30
+
31
+ context 'when a refresh token was specified' do
32
+ let(:token) { JWT::Auth::RefreshToken.new :subject => user }
33
+
34
+ before { @request.headers.merge! headers }
35
+
36
+ it { is_expected.to have_http_status :unauthorized }
37
+
38
+ context 'when the token was invalid' do
39
+ before { user.increment! :token_version }
40
+
41
+ it { is_expected.to have_http_status :unauthorized }
42
+ end
43
+ end
44
+
45
+ context 'when an access token was specified' do
46
+ let(:token) { JWT::Auth::AccessToken.new :subject => user }
47
+
48
+ before { @request.headers.merge! headers }
49
+
50
+ it { is_expected.to have_http_status :no_content }
51
+
52
+ context 'when the token was invalid' do
53
+ before { user.increment! :token_version }
54
+
55
+ it { is_expected.to have_http_status :unauthorized }
56
+ end
57
+ end
58
+ end
59
+
60
+ describe 'GET /authenticated' do
61
+ subject { get :authenticated }
62
+
63
+ context 'when no token was specified' do
64
+ it { is_expected.to have_http_status :unauthorized }
65
+ end
66
+
67
+ context 'when a refresh token was specified' do
68
+ let(:token) { JWT::Auth::RefreshToken.new :subject => user }
69
+
70
+ before { @request.headers.merge! headers }
71
+
72
+ it { is_expected.to have_http_status :unauthorized }
73
+
74
+ context 'when the token was invalid' do
75
+ before { user.increment! :token_version }
76
+
77
+ it { is_expected.to have_http_status :unauthorized }
78
+ end
79
+ end
80
+
81
+ context 'when an access token was specified' do
82
+ let(:token) { JWT::Auth::AccessToken.new :subject => user }
83
+
84
+ before { @request.headers.merge! headers }
85
+
86
+ it { is_expected.to have_http_status :no_content }
87
+
88
+ context 'when the token was invalid' do
89
+ before { user.increment! :token_version }
90
+
91
+ it { is_expected.to have_http_status :unauthorized }
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe TokensController do
6
+ ##
7
+ # Configuration
8
+ #
9
+ ##
10
+ # Test variables
11
+ #
12
+ let(:user) { User.create :activated => true, :email => 'foo@bar', :password => 'foobar' }
13
+
14
+ let(:headers) do
15
+ { 'Authorization' => "Bearer #{token.to_jwt}" }
16
+ end
17
+
18
+ # Ensure the user is created, which autoloads the model and initializes the JWT::Auth.model configuration entry
19
+ before { user }
20
+
21
+ ##
22
+ # Tests
23
+ #
24
+ describe 'POST /' do
25
+ let(:user) { User.create :activated => activated, :email => 'foo@bar', :password => 'foobar' }
26
+ let(:activated) { true }
27
+ let(:password) { 'foobar' }
28
+
29
+ subject { post :create, :params => { :email => 'foo@bar', :password => password } }
30
+
31
+ it { is_expected.to have_http_status :no_content }
32
+ it { is_expected.to return_token JWT::Auth::RefreshToken }
33
+
34
+ context 'when the credentials are incorrect' do
35
+ let(:password) { 'barfoo' }
36
+
37
+ it { is_expected.to have_http_status :unauthorized }
38
+ it { is_expected.not_to return_token }
39
+ end
40
+
41
+ context 'when the user is not activated' do
42
+ let(:activated) { false }
43
+
44
+ it { is_expected.to have_http_status :unauthorized }
45
+ it { is_expected.not_to return_token }
46
+
47
+ context 'when the credentials are incorrect' do
48
+ let(:password) { 'barfoo' }
49
+
50
+ it { is_expected.to have_http_status :unauthorized }
51
+ it { is_expected.not_to return_token }
52
+ end
53
+ end
54
+ end
55
+
56
+ describe 'PATCH /' do
57
+ let(:user) { User.create :activated => activated, :email => 'foo@bar', :password => 'foobar' }
58
+ let(:activated) { true }
59
+
60
+ subject { patch :update }
61
+
62
+ context 'when no token is present' do
63
+ it { is_expected.to have_http_status :unauthorized }
64
+ it { is_expected.not_to return_token }
65
+ end
66
+
67
+ context 'when a refresh token is present' do
68
+ let(:token) { JWT::Auth::RefreshToken.new :subject => user }
69
+
70
+ before { @request.headers.merge! headers }
71
+
72
+ it { is_expected.to have_http_status :no_content }
73
+ it { is_expected.to return_token JWT::Auth::AccessToken }
74
+
75
+ context 'when the token is invalid' do
76
+ before { user.increment! :token_version }
77
+
78
+ it { is_expected.to have_http_status :unauthorized }
79
+ it { is_expected.not_to return_token }
80
+ end
81
+ end
82
+
83
+ context 'when an access token is present' do
84
+ let(:token) { JWT::Auth::AccessToken.new :subject => user }
85
+
86
+ before { @request.headers.merge! headers }
87
+
88
+ it { is_expected.to have_http_status :unauthorized }
89
+ it { is_expected.not_to return_token }
90
+
91
+ context 'when the token is invalid' do
92
+ before { user.increment! :token_version }
93
+
94
+ it { is_expected.to have_http_status :unauthorized }
95
+ it { is_expected.not_to return_token }
96
+ end
97
+ end
98
+
99
+ context 'when the user is not activated' do
100
+ let(:activated) { false }
101
+
102
+ context 'when no token is present' do
103
+ it { is_expected.to have_http_status :unauthorized }
104
+ it { is_expected.not_to return_token }
105
+ end
106
+
107
+ context 'when a refresh token is present' do
108
+ let(:token) { JWT::Auth::RefreshToken.new :subject => user }
109
+
110
+ before { @request.headers.merge! headers }
111
+
112
+ it { is_expected.to have_http_status :unauthorized }
113
+ it { is_expected.not_to return_token }
114
+
115
+ context 'when the token is invalid' do
116
+ before { user.increment! :token_version }
117
+
118
+ it { is_expected.to have_http_status :unauthorized }
119
+ it { is_expected.not_to return_token }
120
+ end
121
+ end
122
+
123
+ context 'when an access token is present' do
124
+ let(:token) { JWT::Auth::AccessToken.new :subject => user }
125
+
126
+ before { @request.headers.merge! headers }
127
+
128
+ it { is_expected.to have_http_status :unauthorized }
129
+ it { is_expected.not_to return_token }
130
+
131
+ context 'when the token is invalid' do
132
+ before { user.increment! :token_version }
133
+
134
+ it { is_expected.to have_http_status :unauthorized }
135
+ it { is_expected.not_to return_token }
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Add your own tasks in files placed in lib/tasks ending in .rake,
2
4
  # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ApplicationCable
2
4
  class Channel < ActionCable::Channel::Base
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ApplicationCable
2
4
  class Connection < ActionCable::Connection::Base
3
5
  end
@@ -1,10 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ApplicationController < ActionController::Base
2
- protect_from_forgery with: :exception
4
+ protect_from_forgery :with => :exception
3
5
 
4
6
  include JWT::Auth::Authentication
5
7
 
6
8
  rescue_from JWT::Auth::UnauthorizedError, :with => :handle_unauthorized
7
9
 
10
+ # Validate validity of token (if present) on all routes
11
+ before_action :validate_token
12
+
8
13
  protected
9
14
 
10
15
  def handle_unauthorized