jwt-auth 4.2.0 → 5.0.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.
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