solidus_jwt 0.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +35 -0
- data/.gem_release.yml +5 -0
- data/.github/CODEOWNERS +4 -0
- data/.github/stale.yml +17 -0
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/.rubocop.yml +16 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +33 -0
- data/README.md +71 -2
- data/Rakefile +4 -28
- data/_config.yml +1 -0
- data/app/decorators/controllers/solidus_jwt/spree/api/base_controller_decorator.rb +41 -0
- data/app/decorators/models/solidus_jwt/spree/user_decorator.rb +58 -0
- data/app/models/solidus_jwt/base_record.rb +13 -0
- data/app/models/solidus_jwt/token.rb +63 -0
- data/bin/console +17 -0
- data/bin/r +9 -0
- data/bin/rails +8 -0
- data/bin/rails-engine +15 -0
- data/bin/rails-sandbox +17 -0
- data/bin/rake +7 -0
- data/bin/sandbox +84 -0
- data/bin/sandbox_rails +9 -0
- data/bin/setup +8 -0
- data/config/locales/en.yml +2 -1
- data/config/routes.rb +3 -0
- data/db/migrate/20190222220038_create_solidus_jwt_tokens.rb +14 -0
- data/db/migrate/20191212083655_add_foreign_key_to_users_table.rb +7 -0
- data/lib/controllers/api/spree/api/oauths_controller.rb +47 -0
- data/lib/generators/solidus_jwt/install/install_generator.rb +3 -11
- data/lib/solidus_jwt.rb +8 -0
- data/lib/solidus_jwt/concerns/decodeable.rb +7 -1
- data/lib/solidus_jwt/concerns/encodeable.rb +14 -2
- data/lib/solidus_jwt/config.rb +2 -0
- data/lib/solidus_jwt/devise_strategies/base.rb +25 -0
- data/lib/solidus_jwt/devise_strategies/password.rb +46 -0
- data/lib/solidus_jwt/devise_strategies/refresh_token.rb +48 -0
- data/lib/solidus_jwt/distributor/devise.rb +3 -1
- data/lib/solidus_jwt/engine.rb +8 -12
- data/lib/solidus_jwt/factories.rb +13 -0
- data/lib/solidus_jwt/preferences.rb +8 -0
- data/lib/solidus_jwt/version.rb +3 -9
- data/solidus_jwt.gemspec +38 -0
- data/spec/lib/solidus_jwt/concerns/decodeable_spec.rb +0 -0
- data/spec/lib/solidus_jwt/concerns/encodeable_spec.rb +0 -0
- data/spec/lib/solidus_jwt/config_spec.rb +7 -0
- data/spec/lib/solidus_jwt/devise_strategies/password_spec.rb +78 -0
- data/spec/lib/solidus_jwt/devise_strategies/refresh_token_spec.rb +74 -0
- data/spec/lib/solidus_jwt/preferences_spec.rb +39 -0
- data/spec/lib/solidus_jwt_spec.rb +8 -0
- data/spec/models/solidus_jwt/token_spec.rb +43 -0
- data/spec/requests/spree/api/json_web_tokens_spec.rb +77 -0
- data/spec/requests/spree/api/oauths_spec.rb +122 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/shared_examples/decodeable_examples.rb +23 -0
- data/spec/support/shared_examples/encodeable_examples.rb +29 -0
- metadata +86 -222
- data/app/assets/javascripts/spree/backend/solidus_jwt.js +0 -2
- data/app/assets/javascripts/spree/frontend/solidus_jwt.js +0 -2
- data/app/assets/stylesheets/spree/backend/solidus_jwt.css +0 -4
- data/app/assets/stylesheets/spree/frontend/solidus_jwt.css +0 -4
- data/app/controllers/spree/api/base_controller/json_web_tokens.rb +0 -22
- data/app/controllers/spree/api/base_controller_decorator.rb +0 -17
- 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
|
-
|
10
|
+
generate_jwt_token(expires_in: SolidusJwt::Config.jwt_expiration)
|
9
11
|
end
|
10
12
|
|
11
13
|
super
|
data/lib/solidus_jwt/engine.rb
CHANGED
@@ -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
|
-
|
4
|
-
|
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
|
data/lib/solidus_jwt/version.rb
CHANGED
@@ -1,11 +1,5 @@
|
|
1
|
-
|
2
|
-
MAJOR = 0
|
3
|
-
MINOR = 1
|
4
|
-
PATCH = 0
|
5
|
-
PRERELEASE = nil
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
[version, PRERELEASE].compact.join('.')
|
10
|
-
end
|
3
|
+
module SolidusJwt
|
4
|
+
VERSION = '1.2.0'
|
11
5
|
end
|
data/solidus_jwt.gemspec
ADDED
@@ -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
|
File without changes
|
File without changes
|
@@ -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,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
|