solidus_jwt 1.0.0 → 1.1.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 (58) 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 +33 -0
  9. data/.ruby-gemset +1 -0
  10. data/.ruby-version +1 -0
  11. data/Gemfile +31 -0
  12. data/Rakefile +4 -28
  13. data/_config.yml +1 -0
  14. data/app/controllers/spree/api/oauths_controller.rb +12 -5
  15. data/app/decorators/solidus_jwt/spree/api/base_controller_decorator.rb +39 -0
  16. data/app/decorators/solidus_jwt/spree/user_decorator.rb +36 -0
  17. data/app/models/solidus_jwt/base_record.rb +11 -0
  18. data/app/models/solidus_jwt/token.rb +17 -4
  19. data/bin/console +17 -0
  20. data/bin/rails +18 -0
  21. data/bin/sandbox +81 -0
  22. data/bin/setup +8 -0
  23. data/config/locales/en.yml +2 -1
  24. data/db/migrate/20191212083655_add_foreign_key_to_users_table.rb +5 -0
  25. data/lib/generators/solidus_jwt/install/install_generator.rb +1 -11
  26. data/lib/solidus_jwt.rb +2 -0
  27. data/lib/solidus_jwt/concerns/decodeable.rb +5 -1
  28. data/lib/solidus_jwt/concerns/encodeable.rb +12 -2
  29. data/lib/solidus_jwt/devise_strategies/base.rb +23 -0
  30. data/lib/solidus_jwt/devise_strategies/password.rb +5 -11
  31. data/lib/solidus_jwt/devise_strategies/refresh_token.rb +7 -10
  32. data/lib/solidus_jwt/distributor/devise.rb +1 -1
  33. data/lib/solidus_jwt/engine.rb +6 -12
  34. data/lib/solidus_jwt/version.rb +1 -9
  35. data/solidus_jwt.gemspec +36 -0
  36. data/spec/lib/solidus_jwt/concerns/decodeable_spec.rb +0 -0
  37. data/spec/lib/solidus_jwt/concerns/encodeable_spec.rb +0 -0
  38. data/spec/lib/solidus_jwt/config_spec.rb +5 -0
  39. data/spec/lib/solidus_jwt/devise_strategies/password_spec.rb +76 -0
  40. data/spec/lib/solidus_jwt/devise_strategies/refresh_token_spec.rb +72 -0
  41. data/spec/lib/solidus_jwt/preferences_spec.rb +37 -0
  42. data/spec/lib/solidus_jwt_spec.rb +6 -0
  43. data/spec/models/solidus_jwt/token_spec.rb +41 -0
  44. data/spec/requests/spree/api/json_web_tokens_spec.rb +75 -0
  45. data/spec/requests/spree/api/oauths_spec.rb +120 -0
  46. data/spec/spec_helper.rb +24 -0
  47. data/spec/support/shared_examples/decodeable_examples.rb +21 -0
  48. data/spec/support/shared_examples/encodeable_examples.rb +27 -0
  49. metadata +65 -227
  50. data/app/assets/javascripts/spree/backend/solidus_jwt.js +0 -2
  51. data/app/assets/javascripts/spree/frontend/solidus_jwt.js +0 -2
  52. data/app/assets/stylesheets/spree/backend/solidus_jwt.css +0 -4
  53. data/app/assets/stylesheets/spree/frontend/solidus_jwt.css +0 -4
  54. data/app/controllers/spree/api/base_controller/json_web_tokens.rb +0 -22
  55. data/app/controllers/spree/api/base_controller_decorator.rb +0 -17
  56. data/app/models/application_record.rb +0 -3
  57. data/app/models/solidus_jwt/application_record.rb +0 -9
  58. data/app/models/spree/user_decorator.rb +0 -34
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ gem install bundler --conservative
7
+ bundle update
8
+ bin/rake clobber
@@ -2,4 +2,5 @@
2
2
  # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3
3
 
4
4
  en:
5
- hello: Hello world
5
+ solidus_jwt:
6
+ invalid_credentials: invalid username or password
@@ -0,0 +1,5 @@
1
+ class AddForeignKeyToUsersTable < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_foreign_key :solidus_jwt_tokens, Spree.user_class.table_name, column: :user_id, on_delete: :cascade
4
+ end
5
+ end
@@ -3,16 +3,6 @@ module SolidusJwt
3
3
  class InstallGenerator < Rails::Generators::Base
4
4
  class_option :auto_run_migrations, type: :boolean, default: false
5
5
 
6
- def add_javascripts
7
- append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/solidus_jwt\n"
8
- append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_jwt\n"
9
- end
10
-
11
- def add_stylesheets
12
- inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/solidus_jwt\n", before: /\*\//, verbose: true
13
- inject_into_file 'vendor/assets/stylesheets/spree/backend/all.css', " *= require spree/backend/solidus_jwt\n", before: /\*\//, verbose: true
14
- end
15
-
16
6
  def add_migrations
17
7
  run 'bundle exec rake railties:install:migrations FROM=solidus_jwt'
18
8
  end
@@ -22,7 +12,7 @@ module SolidusJwt
22
12
  if run_migrations
23
13
  run 'bundle exec rake db:migrate'
24
14
  else
25
- puts 'Skipping rake db:migrate, don\'t forget to run it!'
15
+ puts 'Skipping rake db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output
26
16
  end
27
17
  end
28
18
  end
data/lib/solidus_jwt.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  require 'jwt'
2
2
 
3
3
  require 'solidus_core'
4
+ require 'solidus_support'
4
5
  require 'solidus_auth_devise'
5
6
  require 'solidus_jwt/engine'
6
7
 
8
+ require 'solidus_jwt/devise_strategies/base'
7
9
  require 'solidus_jwt/devise_strategies/password'
8
10
  require 'solidus_jwt/devise_strategies/refresh_token'
9
11
 
@@ -4,12 +4,16 @@ module SolidusJwt
4
4
  # Decode a token generated by SolidusJwt
5
5
  # @see https://github.com/jwt/ruby-jwt
6
6
  #
7
+ # @example decode a token.
8
+ # SolidusJwt.decode('abc.123.efg')
9
+ # #=> [{"sub"=>"1234567890", "name"=>"John Doe", "iat"=>1516239022}, {"alg"=>"HS256", "typ"=>"JWT"}]
10
+ #
7
11
  # @param token [String] The token to decode
8
12
  # @return [Array<Hash>]
9
13
  #
10
14
  def decode(token)
11
15
  JWT.decode(token, SolidusJwt::Config.jwt_secret, true,
12
- algorithm: SolidusJwt::Config.jwt_algorithm)
16
+ algorithm: SolidusJwt::Config.jwt_algorithm)
13
17
  end
14
18
  end
15
19
  end
@@ -4,6 +4,16 @@ module SolidusJwt
4
4
  # Encode a specified payload
5
5
  # @see https://github.com/jwt/ruby-jwt
6
6
  #
7
+ # @example encode data into token
8
+ # payload = {
9
+ # sub: 1,
10
+ # iat: DateTime.current.to_i,
11
+ # exp: 1.hour.from_now.to_i
12
+ # }
13
+ #
14
+ # SolidusJwt.encode payload: payload
15
+ # #=> 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlhdCI6MTU4NDEzMjExOCwiZXhwIjoxNTg0MTM1NzE4LCJpc3MiOiJzb2xpZHVzIn0.OKZOGlawx435GdgKp2AGD8SKxW7sqn0h-Ef2qdVSxqQ'
16
+ #
7
17
  # @param payload [Hash] Attributes to place within the jwt
8
18
  # @param expires_in [Integer] How long until token expires in Seconds (*Optional*).
9
19
  # Note that if no expires at is set, then the token will last forever.
@@ -15,12 +25,12 @@ module SolidusJwt
15
25
  current_time = Time.current.to_i
16
26
 
17
27
  # @see https://github.com/jwt/ruby-jwt#support-for-reserved-claim-names
18
- jwt_payload[:exp] ||= current_time + expires_in if expires_in.present?
28
+ jwt_payload[:exp] ||= current_time + expires_in.to_i if expires_in.present?
19
29
  jwt_payload[:iat] ||= current_time
20
30
  jwt_payload[:iss] ||= 'solidus'
21
31
 
22
32
  JWT.encode(jwt_payload, SolidusJwt::Config.jwt_secret,
23
- SolidusJwt::Config.jwt_algorithm)
33
+ SolidusJwt::Config.jwt_algorithm)
24
34
  end
25
35
  end
26
36
  end
@@ -0,0 +1,23 @@
1
+ module SolidusJwt
2
+ module DeviseStrategies
3
+ class Base < Devise::Strategies::Authenticatable
4
+ def valid?
5
+ valid_grant_type? && valid_params?
6
+ end
7
+
8
+ private
9
+
10
+ def grant_type
11
+ params[:grant_type]
12
+ end
13
+
14
+ def valid_grant_type?
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def valid_params?
19
+ raise NotImplementedError
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,13 +1,7 @@
1
1
  module SolidusJwt
2
2
  module DeviseStrategies
3
- class Password < Devise::Strategies::Base
4
- def valid?
5
- valid_grant_type? && valid_params?
6
- end
7
-
3
+ class Password < Base
8
4
  def authenticate!
9
- resource = mapping.to.find_for_database_authentication(auth_hash)
10
-
11
5
  block = proc { resource.valid_password?(password) }
12
6
 
13
7
  if resource&.valid_for_authentication?(&block)
@@ -20,12 +14,12 @@ module SolidusJwt
20
14
 
21
15
  private
22
16
 
23
- def auth_hash
24
- { email: username }
17
+ def resource
18
+ @resource ||= mapping.to.find_for_database_authentication(auth_hash)
25
19
  end
26
20
 
27
- def grant_type
28
- params[:grant_type]
21
+ def auth_hash
22
+ { email: username }
29
23
  end
30
24
 
31
25
  def username
@@ -1,17 +1,14 @@
1
1
  module SolidusJwt
2
2
  module DeviseStrategies
3
- class RefreshToken < Devise::Strategies::Base
4
- def valid?
5
- valid_grant_type? && valid_params?
6
- end
7
-
3
+ class RefreshToken < Base
8
4
  def authenticate!
9
- resource = SolidusJwt::Token.find_by(auth_hash)
10
5
  return fail!(:invalid) if resource.nil? || resource.user.nil?
11
6
 
12
7
  block = proc do
13
8
  # If we honor then mark the refresh token as stale for one time use
9
+ # rubocop:disable Rails/SkipsModelValidations
14
10
  resource.honor? && resource.update_columns(active: false)
11
+ # rubocop:enable Rails/SkipsModelValidations
15
12
  end
16
13
 
17
14
  if resource.user.valid_for_authentication?(&block)
@@ -23,12 +20,12 @@ module SolidusJwt
23
20
 
24
21
  private
25
22
 
26
- def auth_hash
27
- { auth_type: :refresh, token: refresh_token }
23
+ def resource
24
+ @resource ||= SolidusJwt::Token.find_by(auth_hash)
28
25
  end
29
26
 
30
- def grant_type
31
- params[:grant_type]
27
+ def auth_hash
28
+ { auth_type: :refresh, token: refresh_token }
32
29
  end
33
30
 
34
31
  def refresh_token
@@ -5,7 +5,7 @@ module SolidusJwt
5
5
  # Send back json web token in redirect header
6
6
  if try_spree_current_user
7
7
  response.headers['X-SPREE-TOKEN'] = try_spree_current_user.
8
- generate_jwt_token(expires_in: SolidusJwt::Config.jwt_expiration)
8
+ generate_jwt_token(expires_in: SolidusJwt::Config.jwt_expiration)
9
9
  end
10
10
 
11
11
  super
@@ -1,22 +1,16 @@
1
+ require 'spree/core'
2
+
1
3
  module SolidusJwt
2
4
  class Engine < Rails::Engine
3
- require 'spree/core'
4
- isolate_namespace Spree
5
+ include SolidusSupport::EngineExtensions::Decorators
6
+
7
+ isolate_namespace ::Spree
8
+
5
9
  engine_name 'solidus_jwt'
6
10
 
7
11
  # use rspec for tests
8
12
  config.generators do |g|
9
13
  g.test_framework :rspec
10
14
  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
15
  end
22
16
  end
@@ -1,11 +1,3 @@
1
1
  module SolidusJwt
2
- MAJOR = 1
3
- MINOR = 0
4
- PATCH = 0
5
- PRERELEASE = nil
6
-
7
- def self.version
8
- version = [MAJOR, MINOR, PATCH].join('.')
9
- [version, PRERELEASE].compact.join('.')
10
- end
2
+ VERSION = '1.1.0'
11
3
  end
@@ -0,0 +1,36 @@
1
+ require_relative 'lib/solidus_jwt/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'solidus_jwt'
5
+ s.version = SolidusJwt::VERSION
6
+ s.summary = 'Add Json Web Tokens to Solidus API'
7
+ s.description = 'Add Json Web Tokens to Solidus API'
8
+ s.license = 'BSD-3-Clause'
9
+
10
+ s.author = 'Taylor Scott'
11
+ s.email = 't.skukx@gmail.com'
12
+ s.homepage = 'https://github.com/skukx/solidus_jwt'
13
+
14
+ s.metadata['homepage_uri'] = s.homepage
15
+ s.metadata['source_code_uri'] = s.homepage
16
+
17
+ s.required_ruby_version = Gem::Requirement.new('~> 2.4')
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
22
+
23
+ s.files = files.grep_v(%r{^(test|spec|features)/})
24
+ s.test_files = files.grep(%r{^(test|spec|features)/})
25
+ s.bindir = "exe"
26
+ s.executables = files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ s.require_paths = ["lib"]
28
+
29
+ s.add_dependency 'jwt'
30
+ s.add_dependency 'solidus_auth_devise'
31
+ s.add_dependency 'solidus_core', ['>= 2.0.0', '< 3']
32
+ s.add_dependency 'solidus_support', '~> 0.4.0'
33
+
34
+ s.add_development_dependency 'byebug'
35
+ s.add_development_dependency 'solidus_dev_support'
36
+ end
File without changes
File without changes
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SolidusJwt::Config do
4
+ it { is_expected.to be_kind_of SolidusJwt::Preferences }
5
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+ require 'spree/testing_support/factories/user_factory'
3
+
4
+ RSpec.describe SolidusJwt::DeviseStrategies::Password do
5
+ let(:request) { instance_double('ActionController::Request') }
6
+ let(:strategy) { described_class.new(nil, :spree_user) }
7
+
8
+ let(:params) do
9
+ {
10
+ username: user.email,
11
+ password: password,
12
+ grant_type: 'password'
13
+ }
14
+ end
15
+
16
+ let(:headers) { {} }
17
+ let(:user) { FactoryBot.create(:user, password: password) }
18
+ let(:password) { 'secret' }
19
+
20
+ before do
21
+ allow(request).to receive(:headers).and_return(:headers)
22
+
23
+ allow(strategy).to receive(:request).and_return(request)
24
+ allow(strategy).to receive(:params).and_return(params)
25
+ end
26
+
27
+ describe '#valid?' do
28
+ subject { strategy.valid? }
29
+
30
+ it { is_expected.to be true }
31
+
32
+ context 'when username is missing' do
33
+ before { params.delete(:username) }
34
+
35
+ it { is_expected.to be false }
36
+ end
37
+
38
+ context 'when password is missing' do
39
+ before { params.delete(:password) }
40
+
41
+ it { is_expected.to be false }
42
+ end
43
+
44
+ context 'when grant_type is not password' do
45
+ before { params[:grant_type] = 'invalid' }
46
+
47
+ it { is_expected.to be false }
48
+ end
49
+ end
50
+
51
+ describe '#authenticate!' do
52
+ subject { strategy.authenticate! }
53
+
54
+ it { is_expected.to be :success }
55
+
56
+ context 'when auth is invalid' do
57
+ let(:params) do
58
+ {
59
+ username: user.email,
60
+ password: 'invalid',
61
+ grant_type: password
62
+ }
63
+ end
64
+
65
+ it { is_expected.to be :failure }
66
+ end
67
+
68
+ context 'when user is not valid for authentication' do
69
+ before do
70
+ allow_any_instance_of(Spree::User).to receive(:valid_for_authentication?).and_return(false) # rubocop:disable RSpec/AnyInstance
71
+ end
72
+
73
+ it { is_expected.to be :failure }
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ require 'spree/testing_support/factories/user_factory'
3
+
4
+ RSpec.describe SolidusJwt::DeviseStrategies::RefreshToken do
5
+ let(:request) { instance_double('ActionController::Request') }
6
+ let(:strategy) { described_class.new(nil, :spree_user) }
7
+
8
+ let(:params) do
9
+ {
10
+ refresh_token: token.token,
11
+ grant_type: 'refresh_token'
12
+ }
13
+ end
14
+
15
+ let(:headers) { {} }
16
+ let(:user) { FactoryBot.create(:user, password: password) }
17
+ let(:password) { 'secret' }
18
+ let(:token) { user.auth_tokens.create! }
19
+
20
+ before do
21
+ allow(request).to receive(:headers).and_return(:headers)
22
+
23
+ allow(strategy).to receive(:request).and_return(request)
24
+ allow(strategy).to receive(:params).and_return(params)
25
+ end
26
+
27
+ describe '#valid?' do
28
+ subject { strategy.valid? }
29
+
30
+ it { is_expected.to be true }
31
+
32
+ context 'when refresh_token is missing' do
33
+ before { params.delete(:refresh_token) }
34
+
35
+ it { is_expected.to be false }
36
+ end
37
+
38
+ context 'when grant_type is not refresh_token' do
39
+ before { params[:grant_type] = 'invalid' }
40
+
41
+ it { is_expected.to be false }
42
+ end
43
+ end
44
+
45
+ describe '#authenticate!' do
46
+ subject { strategy.authenticate! }
47
+
48
+ it { is_expected.to be :success }
49
+
50
+ context 'when token is not honorable' do
51
+ before do
52
+ allow_any_instance_of(SolidusJwt::Token).to receive(:honor?).and_return false # rubocop:disable RSpec/AnyInstance
53
+ end
54
+
55
+ it { is_expected.to be :failure }
56
+ end
57
+
58
+ context 'when user is not valid for authentication' do
59
+ before do
60
+ allow_any_instance_of(Spree::User).to receive(:valid_for_authentication?).and_return(false) # rubocop:disable RSpec/AnyInstance
61
+ end
62
+
63
+ it { is_expected.to be :failure }
64
+ end
65
+
66
+ context 'when token is used more than once' do
67
+ before { strategy.authenticate! }
68
+
69
+ it { is_expected.to be :failure }
70
+ end
71
+ end
72
+ end