solidus_jwt 0.1.0 → 1.2.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +35 -0
  3. data/.gem_release.yml +5 -0
  4. data/.github/CODEOWNERS +4 -0
  5. data/.github/stale.yml +17 -0
  6. data/.gitignore +19 -0
  7. data/.rspec +1 -0
  8. data/.rubocop.yml +16 -0
  9. data/.ruby-gemset +1 -0
  10. data/.ruby-version +1 -0
  11. data/Gemfile +33 -0
  12. data/README.md +71 -2
  13. data/Rakefile +4 -28
  14. data/_config.yml +1 -0
  15. data/app/decorators/controllers/solidus_jwt/spree/api/base_controller_decorator.rb +41 -0
  16. data/app/decorators/models/solidus_jwt/spree/user_decorator.rb +58 -0
  17. data/app/models/solidus_jwt/base_record.rb +13 -0
  18. data/app/models/solidus_jwt/token.rb +63 -0
  19. data/bin/console +17 -0
  20. data/bin/r +9 -0
  21. data/bin/rails +8 -0
  22. data/bin/rails-engine +15 -0
  23. data/bin/rails-sandbox +17 -0
  24. data/bin/rake +7 -0
  25. data/bin/sandbox +84 -0
  26. data/bin/sandbox_rails +9 -0
  27. data/bin/setup +8 -0
  28. data/config/locales/en.yml +2 -1
  29. data/config/routes.rb +3 -0
  30. data/db/migrate/20190222220038_create_solidus_jwt_tokens.rb +14 -0
  31. data/db/migrate/20191212083655_add_foreign_key_to_users_table.rb +7 -0
  32. data/lib/controllers/api/spree/api/oauths_controller.rb +47 -0
  33. data/lib/generators/solidus_jwt/install/install_generator.rb +3 -11
  34. data/lib/solidus_jwt.rb +8 -0
  35. data/lib/solidus_jwt/concerns/decodeable.rb +7 -1
  36. data/lib/solidus_jwt/concerns/encodeable.rb +14 -2
  37. data/lib/solidus_jwt/config.rb +2 -0
  38. data/lib/solidus_jwt/devise_strategies/base.rb +25 -0
  39. data/lib/solidus_jwt/devise_strategies/password.rb +46 -0
  40. data/lib/solidus_jwt/devise_strategies/refresh_token.rb +48 -0
  41. data/lib/solidus_jwt/distributor/devise.rb +3 -1
  42. data/lib/solidus_jwt/engine.rb +8 -12
  43. data/lib/solidus_jwt/factories.rb +13 -0
  44. data/lib/solidus_jwt/preferences.rb +8 -0
  45. data/lib/solidus_jwt/version.rb +3 -9
  46. data/solidus_jwt.gemspec +38 -0
  47. data/spec/lib/solidus_jwt/concerns/decodeable_spec.rb +0 -0
  48. data/spec/lib/solidus_jwt/concerns/encodeable_spec.rb +0 -0
  49. data/spec/lib/solidus_jwt/config_spec.rb +7 -0
  50. data/spec/lib/solidus_jwt/devise_strategies/password_spec.rb +78 -0
  51. data/spec/lib/solidus_jwt/devise_strategies/refresh_token_spec.rb +74 -0
  52. data/spec/lib/solidus_jwt/preferences_spec.rb +39 -0
  53. data/spec/lib/solidus_jwt_spec.rb +8 -0
  54. data/spec/models/solidus_jwt/token_spec.rb +43 -0
  55. data/spec/requests/spree/api/json_web_tokens_spec.rb +77 -0
  56. data/spec/requests/spree/api/oauths_spec.rb +122 -0
  57. data/spec/spec_helper.rb +26 -0
  58. data/spec/support/shared_examples/decodeable_examples.rb +23 -0
  59. data/spec/support/shared_examples/encodeable_examples.rb +29 -0
  60. metadata +86 -222
  61. data/app/assets/javascripts/spree/backend/solidus_jwt.js +0 -2
  62. data/app/assets/javascripts/spree/frontend/solidus_jwt.js +0 -2
  63. data/app/assets/stylesheets/spree/backend/solidus_jwt.css +0 -4
  64. data/app/assets/stylesheets/spree/frontend/solidus_jwt.css +0 -4
  65. data/app/controllers/spree/api/base_controller/json_web_tokens.rb +0 -22
  66. data/app/controllers/spree/api/base_controller_decorator.rb +0 -17
  67. data/app/models/spree/user_decorator.rb +0 -30
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusJwt
4
+ module DeviseStrategies
5
+ class RefreshToken < Base
6
+ def authenticate!
7
+ return fail!(:invalid) if resource.nil? || resource.user.nil?
8
+
9
+ block = proc do
10
+ # If we honor then mark the refresh token as stale for one time use
11
+ # rubocop:disable Rails/SkipsModelValidations
12
+ resource.honor? && resource.update_columns(active: false)
13
+ # rubocop:enable Rails/SkipsModelValidations
14
+ end
15
+
16
+ if resource.user.valid_for_authentication?(&block)
17
+ return success!(resource.user)
18
+ end
19
+
20
+ fail!(:invalid)
21
+ end
22
+
23
+ private
24
+
25
+ def resource
26
+ @resource ||= SolidusJwt::Token.find_by(auth_hash)
27
+ end
28
+
29
+ def auth_hash
30
+ { auth_type: :refresh, token: refresh_token }
31
+ end
32
+
33
+ def refresh_token
34
+ params[:refresh_token]
35
+ end
36
+
37
+ def valid_grant_type?
38
+ grant_type == 'refresh_token'
39
+ end
40
+
41
+ def valid_params?
42
+ refresh_token.present?
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ Warden::Strategies.add(:solidus_jwt_refresh_token, SolidusJwt::DeviseStrategies::RefreshToken)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidusJwt
2
4
  module Distributor
3
5
  module Devise
@@ -5,7 +7,7 @@ module SolidusJwt
5
7
  # Send back json web token in redirect header
6
8
  if try_spree_current_user
7
9
  response.headers['X-SPREE-TOKEN'] = try_spree_current_user.
8
- generate_jwt_token(expires_in: SolidusJwt::Config.jwt_expiration)
10
+ generate_jwt_token(expires_in: SolidusJwt::Config.jwt_expiration)
9
11
  end
10
12
 
11
13
  super
@@ -1,22 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spree/core'
4
+
1
5
  module SolidusJwt
2
6
  class Engine < Rails::Engine
3
- require 'spree/core'
4
- isolate_namespace Spree
7
+ include SolidusSupport::EngineExtensions
8
+
9
+ isolate_namespace ::Spree
10
+
5
11
  engine_name 'solidus_jwt'
6
12
 
7
13
  # use rspec for tests
8
14
  config.generators do |g|
9
15
  g.test_framework :rspec
10
16
  end
11
-
12
- def self.activate
13
- decorator_pattern = File.join(__dir__, '../../app/**/*_decorator*.rb')
14
-
15
- Dir.glob(decorator_pattern) do |c|
16
- Rails.configuration.cache_classes ? require(c) : load(c)
17
- end
18
- end
19
-
20
- config.to_prepare(&method(:activate).to_proc)
21
17
  end
22
18
  end
@@ -1,6 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  FactoryBot.define do
2
4
  # Define your Spree extensions Factories within this file to enable applications, and other extensions to use and override them.
3
5
  #
4
6
  # Example adding this to your spec_helper will load these Factories for use:
5
7
  # require 'solidus_jwt/factories'
8
+ factory :token, class: SolidusJwt::Token do
9
+ association :user
10
+
11
+ trait :expired do
12
+ created_at { 1.year.ago }
13
+ end
14
+
15
+ trait :inactive do
16
+ active { false }
17
+ end
18
+ end
6
19
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spree/preferences/configuration'
2
4
 
3
5
  module SolidusJwt
@@ -34,6 +36,12 @@ module SolidusJwt
34
36
  #
35
37
  preference :jwt_options, :hash, default: { only: %i[email first_name id last_name] }
36
38
 
39
+ # @!attribute [rw] refresh_expriation
40
+ # @return [String] How long until the refresh token expires in seconds
41
+ # (default: +2592000+)
42
+ #
43
+ preference :refresh_expiration, :integer, default: 2_592_000
44
+
37
45
  ##
38
46
  # Get the secret token to encrypt json web tokens with.
39
47
  # @return [String] The secret used to encrypt json web tokens
@@ -1,11 +1,5 @@
1
- module SolidusJwt
2
- MAJOR = 0
3
- MINOR = 1
4
- PATCH = 0
5
- PRERELEASE = nil
1
+ # frozen_string_literal: true
6
2
 
7
- def self.version
8
- version = [MAJOR, MINOR, PATCH].join('.')
9
- [version, PRERELEASE].compact.join('.')
10
- end
3
+ module SolidusJwt
4
+ VERSION = '1.2.0'
11
5
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/solidus_jwt/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'solidus_jwt'
7
+ s.version = SolidusJwt::VERSION
8
+ s.summary = 'Add Json Web Tokens to Solidus API'
9
+ s.description = 'Add Json Web Tokens to Solidus API'
10
+ s.license = 'BSD-3-Clause'
11
+
12
+ s.author = 'Taylor Scott'
13
+ s.email = 't.skukx@gmail.com'
14
+ s.homepage = 'https://github.com/skukx/solidus_jwt'
15
+
16
+ s.metadata['homepage_uri'] = s.homepage
17
+ s.metadata['source_code_uri'] = s.homepage
18
+
19
+ s.required_ruby_version = Gem::Requirement.new('~> 2.4')
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
24
+
25
+ s.files = files.grep_v(%r{^(test|spec|features)/})
26
+ s.test_files = files.grep(%r{^(test|spec|features)/})
27
+ s.bindir = "exe"
28
+ s.executables = files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ s.require_paths = ["lib"]
30
+
31
+ s.add_dependency 'jwt'
32
+ s.add_dependency 'solidus_auth_devise'
33
+ s.add_dependency 'solidus_core', ['>= 2.0.0', '< 3']
34
+ s.add_dependency 'solidus_support', '~> 0.5.0'
35
+
36
+ s.add_development_dependency 'byebug'
37
+ s.add_development_dependency 'solidus_dev_support'
38
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusJwt::Config do
6
+ it { is_expected.to be_kind_of SolidusJwt::Preferences }
7
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'spree/testing_support/factories/user_factory'
5
+
6
+ RSpec.describe SolidusJwt::DeviseStrategies::Password do
7
+ let(:request) { instance_double('ActionController::Request') }
8
+ let(:strategy) { described_class.new(nil, :spree_user) }
9
+
10
+ let(:params) do
11
+ {
12
+ username: user.email,
13
+ password: password,
14
+ grant_type: 'password'
15
+ }
16
+ end
17
+
18
+ let(:headers) { {} }
19
+ let(:user) { FactoryBot.create(:user, password: password) }
20
+ let(:password) { 'secret' }
21
+
22
+ before do
23
+ allow(request).to receive(:headers).and_return(:headers)
24
+
25
+ allow(strategy).to receive(:request).and_return(request)
26
+ allow(strategy).to receive(:params).and_return(params)
27
+ end
28
+
29
+ describe '#valid?' do
30
+ subject { strategy.valid? }
31
+
32
+ it { is_expected.to be true }
33
+
34
+ context 'when username is missing' do
35
+ before { params.delete(:username) }
36
+
37
+ it { is_expected.to be false }
38
+ end
39
+
40
+ context 'when password is missing' do
41
+ before { params.delete(:password) }
42
+
43
+ it { is_expected.to be false }
44
+ end
45
+
46
+ context 'when grant_type is not password' do
47
+ before { params[:grant_type] = 'invalid' }
48
+
49
+ it { is_expected.to be false }
50
+ end
51
+ end
52
+
53
+ describe '#authenticate!' do
54
+ subject { strategy.authenticate! }
55
+
56
+ it { is_expected.to be :success }
57
+
58
+ context 'when auth is invalid' do
59
+ let(:params) do
60
+ {
61
+ username: user.email,
62
+ password: 'invalid',
63
+ grant_type: password
64
+ }
65
+ end
66
+
67
+ it { is_expected.to be :failure }
68
+ end
69
+
70
+ context 'when user is not valid for authentication' do
71
+ before do
72
+ allow_any_instance_of(Spree::User).to receive(:valid_for_authentication?).and_return(false) # rubocop:disable RSpec/AnyInstance
73
+ end
74
+
75
+ it { is_expected.to be :failure }
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'spree/testing_support/factories/user_factory'
5
+
6
+ RSpec.describe SolidusJwt::DeviseStrategies::RefreshToken do
7
+ let(:request) { instance_double('ActionController::Request') }
8
+ let(:strategy) { described_class.new(nil, :spree_user) }
9
+
10
+ let(:params) do
11
+ {
12
+ refresh_token: token.token,
13
+ grant_type: 'refresh_token'
14
+ }
15
+ end
16
+
17
+ let(:headers) { {} }
18
+ let(:user) { FactoryBot.create(:user, password: password) }
19
+ let(:password) { 'secret' }
20
+ let(:token) { user.auth_tokens.create! }
21
+
22
+ before do
23
+ allow(request).to receive(:headers).and_return(:headers)
24
+
25
+ allow(strategy).to receive(:request).and_return(request)
26
+ allow(strategy).to receive(:params).and_return(params)
27
+ end
28
+
29
+ describe '#valid?' do
30
+ subject { strategy.valid? }
31
+
32
+ it { is_expected.to be true }
33
+
34
+ context 'when refresh_token is missing' do
35
+ before { params.delete(:refresh_token) }
36
+
37
+ it { is_expected.to be false }
38
+ end
39
+
40
+ context 'when grant_type is not refresh_token' do
41
+ before { params[:grant_type] = 'invalid' }
42
+
43
+ it { is_expected.to be false }
44
+ end
45
+ end
46
+
47
+ describe '#authenticate!' do
48
+ subject { strategy.authenticate! }
49
+
50
+ it { is_expected.to be :success }
51
+
52
+ context 'when token is not honorable' do
53
+ before do
54
+ allow_any_instance_of(SolidusJwt::Token).to receive(:honor?).and_return false # rubocop:disable RSpec/AnyInstance
55
+ end
56
+
57
+ it { is_expected.to be :failure }
58
+ end
59
+
60
+ context 'when user is not valid for authentication' do
61
+ before do
62
+ allow_any_instance_of(Spree::User).to receive(:valid_for_authentication?).and_return(false) # rubocop:disable RSpec/AnyInstance
63
+ end
64
+
65
+ it { is_expected.to be :failure }
66
+ end
67
+
68
+ context 'when token is used more than once' do
69
+ before { strategy.authenticate! }
70
+
71
+ it { is_expected.to be :failure }
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusJwt::Preferences do
6
+ let(:instance) { described_class.new }
7
+
8
+ it { is_expected.to be_kind_of(Spree::Preferences::Configuration) }
9
+
10
+ describe '#jwt_secret' do
11
+ subject { instance.jwt_secret }
12
+
13
+ it { is_expected.to eql Rails.application.secret_key_base }
14
+ end
15
+
16
+ describe '#allow_spree_api_key' do
17
+ subject { instance.allow_spree_api_key }
18
+
19
+ it { is_expected.to be true }
20
+ end
21
+
22
+ describe '#jwt_algorithm' do
23
+ subject { instance.jwt_algorithm }
24
+
25
+ it { is_expected.to eql 'HS256' }
26
+ end
27
+
28
+ describe '#jwt_expiration' do
29
+ subject { instance.jwt_expiration }
30
+
31
+ it { is_expected.to be 3600 }
32
+ end
33
+
34
+ describe '#jwt_options' do
35
+ subject { instance.jwt_options }
36
+
37
+ it { is_expected.to be_kind_of Hash }
38
+ end
39
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusJwt do
6
+ include_examples 'Decodeable Examples'
7
+ include_examples 'Encodeable Examples'
8
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SolidusJwt::Token, type: :model do
6
+ subject { instance }
7
+
8
+ let(:instance) { FactoryBot.create(:token) }
9
+
10
+ it { is_expected.to be_valid }
11
+ it { is_expected.to be_active }
12
+ it { is_expected.not_to be_expired }
13
+
14
+ context 'when token is nil' do
15
+ let(:instance) { FactoryBot.build(:token, token: nil) }
16
+
17
+ it 'generates one automatically' do
18
+ expect(instance.token).to be_nil
19
+ instance.save
20
+ expect(instance.token).to be_present
21
+ end
22
+ end
23
+
24
+ describe '.honor?' do
25
+ subject { described_class.honor?(token) }
26
+
27
+ let(:token) { instance.token }
28
+
29
+ it { is_expected.to be true }
30
+
31
+ context 'when token is inactive' do
32
+ let(:instance) { FactoryBot.create(:token, :inactive) }
33
+
34
+ it { is_expected.to be false }
35
+ end
36
+
37
+ context 'when token is expired' do
38
+ let(:instance) { FactoryBot.create(:token, :expired) }
39
+
40
+ it { is_expected.to be false }
41
+ end
42
+ end
43
+ end