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,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require "bundler/setup"
6
+ require "solidus_jwt"
7
+
8
+ # You can add fixtures and/or initialization code here to make experimenting
9
+ # with your gem easier. You can also use a different console, if you like.
10
+ $LOAD_PATH.unshift(*Dir["#{__dir__}/../app/*"])
11
+
12
+ # (If you use this, don't forget to add pry to your Gemfile!)
13
+ # require "pry"
14
+ # Pry.start
15
+
16
+ require "irb"
17
+ IRB.start(__FILE__)
data/bin/r ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ warn %{
5
+ DEPRECATION: bin/r has been replaced by bin/rails-engine, please use that
6
+ command instead.
7
+ }.strip
8
+
9
+ exec "#{__dir__}/rails-engine"
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ if %w[g generate].include? ARGV.first
5
+ exec "#{__dir__}/rails-engine", *ARGV
6
+ else
7
+ exec "#{__dir__}/rails-sandbox", *ARGV
8
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # This command will automatically be run when you run "rails" with Rails gems
5
+ # installed from the root of your application.
6
+
7
+ ENGINE_ROOT = File.expand_path('..', __dir__)
8
+ ENGINE_PATH = File.expand_path('../lib/solidus_jwt/engine', __dir__)
9
+
10
+ # Set up gems listed in the Gemfile.
11
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
12
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
13
+
14
+ require 'rails/all'
15
+ require 'rails/engine/commands'
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ app_root = 'sandbox'
5
+
6
+ unless File.exist? "#{app_root}/bin/rails"
7
+ warn 'Creating the sandbox app...'
8
+ Dir.chdir "#{__dir__}/.." do
9
+ system "#{__dir__}/sandbox" or begin # rubocop:disable Style/AndOr
10
+ warn 'Automatic creation of the sandbox app failed'
11
+ exit 1
12
+ end
13
+ end
14
+ end
15
+
16
+ Dir.chdir app_root
17
+ exec 'bin/rails', *ARGV
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "rubygems"
5
+ require "bundler/setup"
6
+
7
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ case "$DB" in
6
+ postgres|postgresql)
7
+ RAILSDB="postgresql"
8
+ ;;
9
+ mysql)
10
+ RAILSDB="mysql"
11
+ ;;
12
+ sqlite|'')
13
+ RAILSDB="sqlite3"
14
+ ;;
15
+ *)
16
+ echo "Invalid DB specified: $DB"
17
+ exit 1
18
+ ;;
19
+ esac
20
+
21
+ if [ ! -z $SOLIDUS_BRANCH ]
22
+ then
23
+ BRANCH=$SOLIDUS_BRANCH
24
+ else
25
+ BRANCH="master"
26
+ fi
27
+
28
+ extension_name="solidus_jwt"
29
+
30
+ # Stay away from the bundler env of the containing extension.
31
+ function unbundled {
32
+ ruby -rbundler -e'b = proc {system *ARGV}; Bundler.respond_to?(:with_unbundled_env) ? Bundler.with_unbundled_env(&b) : Bundler.with_clean_env(&b)' -- $@
33
+ }
34
+
35
+ rm -rf ./sandbox
36
+ unbundled bundle exec rails new sandbox --database="$RAILSDB" \
37
+ --skip-bundle \
38
+ --skip-git \
39
+ --skip-keeps \
40
+ --skip-rc \
41
+ --skip-spring \
42
+ --skip-test \
43
+ --skip-javascript
44
+
45
+ if [ ! -d "sandbox" ]; then
46
+ echo 'sandbox rails application failed'
47
+ exit 1
48
+ fi
49
+
50
+ cd ./sandbox
51
+ cat <<RUBY >> Gemfile
52
+ gem 'solidus', github: 'solidusio/solidus', branch: '$BRANCH'
53
+ gem 'solidus_auth_devise', '>= 2.1.0'
54
+ gem 'rails-i18n'
55
+ gem 'solidus_i18n'
56
+
57
+ gem '$extension_name', path: '..'
58
+
59
+ group :test, :development do
60
+ platforms :mri do
61
+ gem 'pry-byebug'
62
+ end
63
+ end
64
+ RUBY
65
+
66
+ unbundled bundle install --gemfile Gemfile
67
+
68
+ unbundled bundle exec rake db:drop db:create
69
+
70
+ unbundled bundle exec rails generate spree:install \
71
+ --auto-accept \
72
+ --user_class=Spree::User \
73
+ --enforce_available_locales=true \
74
+ --with-authentication=false \
75
+ $@
76
+
77
+ unbundled bundle exec rails generate solidus:auth:install
78
+
79
+ echo
80
+ echo "🚀 Sandbox app successfully created for $extension_name!"
81
+ echo "🚀 Using $RAILSDB and Solidus $BRANCH"
82
+ echo "🚀 Use 'export DB=[postgres|mysql|sqlite]' to control the DB adapter"
83
+ echo "🚀 Use 'export SOLIDUS_BRANCH=<BRANCH-NAME>' to control the Solidus version"
84
+ echo "🚀 This app is intended for test purposes."
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ warn %{
5
+ DEPRECATION: bin/sandbox_rails has been replaced by bin/rails-sandbox, please
6
+ use that command instead.
7
+ }.strip
8
+
9
+ exec "#{__dir__}/rails-engine"
@@ -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
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Spree::Core::Engine.routes.draw do
2
4
  # Add your extension routes here
5
+ post 'oauth/token', to: 'api/oauths#token'
3
6
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateSolidusJwtTokens < ActiveRecord::Migration[5.2]
4
+ def change
5
+ create_table :solidus_jwt_tokens do |t|
6
+ t.string :token, index: true
7
+ t.integer :auth_type, default: 0, null: false
8
+ t.integer :user_id, index: true
9
+ t.boolean :active, default: true, null: false
10
+
11
+ t.timestamps null: false
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddForeignKeyToUsersTable < ActiveRecord::Migration[5.2]
4
+ def change
5
+ add_foreign_key :solidus_jwt_tokens, Spree.user_class.table_name, column: :user_id, on_delete: :cascade
6
+ end
7
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Api
5
+ class OauthsController < BaseController
6
+ skip_before_action :authenticate_user
7
+
8
+ def token
9
+ result = catch(:warden) do
10
+ try_authenticate_user
11
+ end
12
+
13
+ case result
14
+ when Spree::User
15
+ render json: token_response_json(result)
16
+ when Hash
17
+ render status: :unauthorized, json: { error: I18n.t(result[:message], scope: 'devise.failure') }
18
+ else
19
+ render status: :unauthorized, json: { error: I18n.t(:invalid_credentials, scope: 'solidus_jwt') }
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def token_response_json(user)
26
+ expires_in = SolidusJwt::Config.jwt_expiration
27
+
28
+ {
29
+ token_type: 'bearer',
30
+ access_token: user.generate_jwt(expires_in: expires_in),
31
+ expires_in: expires_in,
32
+ refresh_token: generate_refresh_token_for(user)
33
+ }
34
+ end
35
+
36
+ def try_authenticate_user
37
+ warden.authenticate(:solidus_jwt_password) ||
38
+ warden.authenticate(:solidus_jwt_refresh_token)
39
+ end
40
+
41
+ def generate_refresh_token_for(user)
42
+ token_resource = user.auth_tokens.create!
43
+ token_resource.token
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,18 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidusJwt
2
4
  module Generators
3
5
  class InstallGenerator < Rails::Generators::Base
4
6
  class_option :auto_run_migrations, type: :boolean, default: false
5
7
 
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
8
  def add_migrations
17
9
  run 'bundle exec rake railties:install:migrations FROM=solidus_jwt'
18
10
  end
@@ -22,7 +14,7 @@ module SolidusJwt
22
14
  if run_migrations
23
15
  run 'bundle exec rake db:migrate'
24
16
  else
25
- puts 'Skipping rake db:migrate, don\'t forget to run it!'
17
+ puts 'Skipping rake db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output
26
18
  end
27
19
  end
28
20
  end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'jwt'
2
4
 
3
5
  require 'solidus_core'
6
+ require 'solidus_support'
7
+ require 'solidus_auth_devise'
4
8
  require 'solidus_jwt/engine'
5
9
 
10
+ require 'solidus_jwt/devise_strategies/base'
11
+ require 'solidus_jwt/devise_strategies/password'
12
+ require 'solidus_jwt/devise_strategies/refresh_token'
13
+
6
14
  require 'solidus_jwt/version'
7
15
  require 'solidus_jwt/config'
8
16
  require 'solidus_jwt/concerns/decodeable'
@@ -1,15 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidusJwt
2
4
  module Decodeable
3
5
  ##
4
6
  # Decode a token generated by SolidusJwt
5
7
  # @see https://github.com/jwt/ruby-jwt
6
8
  #
9
+ # @example decode a token.
10
+ # SolidusJwt.decode('abc.123.efg')
11
+ # #=> [{"sub"=>"1234567890", "name"=>"John Doe", "iat"=>1516239022}, {"alg"=>"HS256", "typ"=>"JWT"}]
12
+ #
7
13
  # @param token [String] The token to decode
8
14
  # @return [Array<Hash>]
9
15
  #
10
16
  def decode(token)
11
17
  JWT.decode(token, SolidusJwt::Config.jwt_secret, true,
12
- algorithm: SolidusJwt::Config.jwt_algorithm)
18
+ algorithm: SolidusJwt::Config.jwt_algorithm)
13
19
  end
14
20
  end
15
21
  end
@@ -1,9 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidusJwt
2
4
  module Encodeable
3
5
  ##
4
6
  # Encode a specified payload
5
7
  # @see https://github.com/jwt/ruby-jwt
6
8
  #
9
+ # @example encode data into token
10
+ # payload = {
11
+ # sub: 1,
12
+ # iat: DateTime.current.to_i,
13
+ # exp: 1.hour.from_now.to_i
14
+ # }
15
+ #
16
+ # SolidusJwt.encode payload: payload
17
+ # #=> 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlhdCI6MTU4NDEzMjExOCwiZXhwIjoxNTg0MTM1NzE4LCJpc3MiOiJzb2xpZHVzIn0.OKZOGlawx435GdgKp2AGD8SKxW7sqn0h-Ef2qdVSxqQ'
18
+ #
7
19
  # @param payload [Hash] Attributes to place within the jwt
8
20
  # @param expires_in [Integer] How long until token expires in Seconds (*Optional*).
9
21
  # Note that if no expires at is set, then the token will last forever.
@@ -15,12 +27,12 @@ module SolidusJwt
15
27
  current_time = Time.current.to_i
16
28
 
17
29
  # @see https://github.com/jwt/ruby-jwt#support-for-reserved-claim-names
18
- jwt_payload[:exp] ||= current_time + expires_in if expires_in.present?
30
+ jwt_payload[:exp] ||= current_time + expires_in.to_i if expires_in.present?
19
31
  jwt_payload[:iat] ||= current_time
20
32
  jwt_payload[:iss] ||= 'solidus'
21
33
 
22
34
  JWT.encode(jwt_payload, SolidusJwt::Config.jwt_secret,
23
- SolidusJwt::Config.jwt_algorithm)
35
+ SolidusJwt::Config.jwt_algorithm)
24
36
  end
25
37
  end
26
38
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'solidus_jwt/preferences'
2
4
 
3
5
  SolidusJwt::Config = SolidusJwt::Preferences.new
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusJwt
4
+ module DeviseStrategies
5
+ class Base < Devise::Strategies::Authenticatable
6
+ def valid?
7
+ valid_grant_type? && valid_params?
8
+ end
9
+
10
+ private
11
+
12
+ def grant_type
13
+ params[:grant_type]
14
+ end
15
+
16
+ def valid_grant_type?
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def valid_params?
21
+ raise NotImplementedError
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusJwt
4
+ module DeviseStrategies
5
+ class Password < Base
6
+ def authenticate!
7
+ block = proc { resource.valid_password?(password) }
8
+
9
+ if resource&.valid_for_authentication?(&block)
10
+ resource.after_database_authentication
11
+ return success!(resource)
12
+ end
13
+
14
+ fail!(:invalid)
15
+ end
16
+
17
+ private
18
+
19
+ def resource
20
+ @resource ||= mapping.to.find_for_database_authentication(auth_hash)
21
+ end
22
+
23
+ def auth_hash
24
+ { email: username }
25
+ end
26
+
27
+ def username
28
+ params[:username]
29
+ end
30
+
31
+ def password
32
+ params[:password]
33
+ end
34
+
35
+ def valid_grant_type?
36
+ grant_type == 'password'
37
+ end
38
+
39
+ def valid_params?
40
+ username.present? && password.present?
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ Warden::Strategies.add(:solidus_jwt_password, SolidusJwt::DeviseStrategies::Password)