solidus_jwt 0.1.0 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cf6dd72fda604868c1589819000a6d1607a8078fa642ce2a17cb071695b26f1
4
- data.tar.gz: ca122c197a880c21398f6bcf28a2e8dd7709f90613e0fe41c4f24ea166802e09
3
+ metadata.gz: e295cf784cd7480da864f9f402f75b9c8e9acb1ecdd611c7bb592121c329ea46
4
+ data.tar.gz: 31705028f11d7134864145b845e345f1816b0cb2e67bb21b7761ec15ce3f356a
5
5
  SHA512:
6
- metadata.gz: c1a2929d69915b28aec1df8c2a4fa450191ae0c465c57e5b7992938a88c9fb6363ae71282930b4dcea644add482d54c3406eb471a93a0c74a520ad8e11e9e98e
7
- data.tar.gz: b7938c410ed7e14bc8098aa7314490beaff2bfc29b86249c72c9a2411f48b3a2b71441b6c17a159fff864caaf1923e348ba5b01c5acfcc570be3c60c1e75a571
6
+ metadata.gz: 638d994f7ef700464469cfd1f601edce39118dc41bc221970e56bcc4814a5aa1792573b01c7fd06a16ae2a939ddcfe158f6151f0d184c688d84359f5c41eb0cd
7
+ data.tar.gz: 3102363bd70e336b49ec0b4287fae49b1b0bd2f5414e011fde6f8db3fc9e1e0d23fd42d97ce75f85c03feff2c03a08dd86f2779547c0e1132ff888eabd76dcf9
data/README.md CHANGED
@@ -36,8 +36,9 @@ SolidusJwt::Config.configure do |config|
36
36
  config.jwt_secret = 'secret'
37
37
  config.allow_spree_api_key = true
38
38
  config.jwt_algorithm = 'HS256'
39
- config.jwt_expiration = 3600
39
+ config.jwt_expiration = 3_600
40
40
  config.jwt_options = { only: %i[email first_name id last_name] }
41
+ config.refresh_expiration = 2_592_000
41
42
  end
42
43
  ```
43
44
 
@@ -56,6 +57,9 @@ Defaults to `3600` (1 hour). The amount of time in seconds that the token should
56
57
  #### `jwt_options`
57
58
  Defaults to `{ only: %i[email first_name id last_name] }`. These options are passed into `Spree::User#as_json` when serializing the token's payload. Keep in mind that the more information included, the larger the token will be. It may be in your best interest to keep it short and simple.
58
59
 
60
+ #### `refresh_expiration`:
61
+ Defaults to `2592000` (30 days). The amount of time in seconds that the token should last for.
62
+
59
63
  Usage
60
64
  -------------
61
65
  ### Generating and decoding a token:
@@ -83,7 +87,59 @@ SolidusJwt.decode(token)
83
87
  # ]
84
88
  ```
85
89
 
86
- ### Distributing a Token Using 'solidus_auth_devise':
90
+ ### Autenticate through the API
91
+
92
+ If authenticating through the API, you must have
93
+ [solidus_auth_devise](https://github.com/solidusio/solidus_auth_devise) setup
94
+ because `solidus_jwt` piggybacks off of the [Devise](https://github.com/plataformatec/devise)
95
+ gem. This enables authentication through a single point. If you implement
96
+ [Devise Lockable](https://www.rubydoc.info/github/plataformatec/devise/master/Devise/Models/Lockable),
97
+ then locking is respected both on the front-end as well as on the API.
98
+
99
+ ```ruby
100
+ POST /oauth/token
101
+ {
102
+ "username": "user@example.com"
103
+ "password": "secret"
104
+ "grant_type": "password"
105
+ }
106
+
107
+ # { "access_token": "abc.123.efg", "refresh_token": "123456" }
108
+ ```
109
+
110
+ You can now use the `access_token` to authentication with the
111
+ [Solidus API](https://github.com/solidusio/solidus/tree/master/api) in place
112
+ of the `spree_api_key`.
113
+
114
+ ### Obtain a refresh token
115
+
116
+ To refresh your access token, instead of re-authenticating you can send
117
+ a refresh token.
118
+
119
+ ```ruby
120
+ POST /oauth/token
121
+ {
122
+ "refresh_token": "123456"
123
+ "grant_type": "refresh_token"
124
+ }
125
+
126
+ # { "access_token": "hij.456.klm", "refresh_token": "789abc" }
127
+ ```
128
+
129
+ ### Invalidate refresh tokens for a user
130
+
131
+ It is good practice set the lifetime of an access token to be short. In case an
132
+ access token is compromised, the attacker will only have access for a short time.
133
+
134
+ To force a user to have to reauthencate rather than using a refresh token,
135
+ you can do the following:
136
+
137
+ ```ruby
138
+ # Invalidate all refresh tokens for a user
139
+ SolidusJwt::Token.invalidate(user)
140
+ ```
141
+
142
+ ### Distributing a Token Using 'solidus_auth_devise' on front-end:
87
143
 
88
144
  To have the `solidus_auth_devise` gem distribute a token back to the client
89
145
  you can do the following:
@@ -0,0 +1,34 @@
1
+ module Spree
2
+ module Api
3
+ class OauthsController < BaseController
4
+ skip_before_action :authenticate_user
5
+
6
+ def token
7
+ if user = try_authenticate_user
8
+ render_token_for(user)
9
+ else
10
+ render status: 401, json: { error: 'invalid username or password' }
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def render_token_for(user)
17
+ render json: {
18
+ access_token: user.generate_jwt,
19
+ refresh_token: generate_refresh_token_for(user)
20
+ }
21
+ end
22
+
23
+ def try_authenticate_user
24
+ warden.authenticate(:solidus_jwt_password) ||
25
+ warden.authenticate(:solidus_jwt_refresh_token)
26
+ end
27
+
28
+ def generate_refresh_token_for(user)
29
+ token_resource = user.auth_tokens.create!
30
+ token_resource.token
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ class ApplicationRecord < ActiveRecord::Base
2
+ self.abstract_class = true
3
+ end
@@ -0,0 +1,9 @@
1
+ module SolidusJwt
2
+ class ApplicationRecord < ::ApplicationRecord
3
+ self.abstract_class = true
4
+
5
+ def self.table_name_prefix
6
+ 'solidus_jwt_'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,48 @@
1
+ module SolidusJwt
2
+ class Token < ApplicationRecord
3
+ attr_readonly :token
4
+ enum auth_type: %i[refresh_token access_token]
5
+
6
+ belongs_to :user, class_name: Spree::UserClassHandle.new
7
+
8
+ scope :non_expired, -> {
9
+ where(
10
+ 'solidus_jwt_tokens.created_at >= ?',
11
+ SolidusJwt::Config.refresh_expiration.seconds.ago
12
+ )
13
+ }
14
+
15
+ enum auth_type: %i[refresh access]
16
+
17
+ validates :token, presence: true
18
+
19
+ before_validation(on: :create) do
20
+ self.token ||= SecureRandom.uuid
21
+ end
22
+
23
+ ##
24
+ # Set all non expired refresh tokens to inactive
25
+ #
26
+ def self.invalidate(user)
27
+ non_expired.
28
+ where(user_id: user.to_param).
29
+ update_all(active: false)
30
+ end
31
+
32
+ ##
33
+ # Whether to honor a token or not.
34
+ # @return [Boolean]
35
+ #
36
+ def self.honor?(token)
37
+ non_expired.where(active: true).find_by(token: token).present?
38
+ end
39
+
40
+ def honor?
41
+ active? && !expired?
42
+ end
43
+
44
+ def expired?
45
+ created_at < SolidusJwt::Config.refresh_expiration.seconds.ago
46
+ end
47
+ end
48
+ end
@@ -1,5 +1,9 @@
1
1
  module Spree
2
2
  module UserDecorator
3
+ def self.prepended(base)
4
+ base.has_many :auth_tokens, class_name: 'SolidusJwt::Token'
5
+ end
6
+
3
7
  ##
4
8
  # Generate a json web token
5
9
  # @see https://github.com/jwt/ruby-jwt
data/config/routes.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  Spree::Core::Engine.routes.draw do
2
2
  # Add your extension routes here
3
+ post 'oauth/token', to: 'api/oauths#token'
3
4
  end
@@ -0,0 +1,12 @@
1
+ class CreateSolidusJwtTokens < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :solidus_jwt_tokens do |t|
4
+ t.string :token, index: true
5
+ t.integer :auth_type, default: 0, null: false
6
+ t.integer :user_id, index: true
7
+ t.boolean :active, default: true, null: false
8
+
9
+ t.timestamps null: false
10
+ end
11
+ end
12
+ end
data/lib/solidus_jwt.rb CHANGED
@@ -1,8 +1,12 @@
1
1
  require 'jwt'
2
2
 
3
3
  require 'solidus_core'
4
+ require 'solidus_auth_devise'
4
5
  require 'solidus_jwt/engine'
5
6
 
7
+ require 'solidus_jwt/devise_strategies/password'
8
+ require 'solidus_jwt/devise_strategies/refresh_token'
9
+
6
10
  require 'solidus_jwt/version'
7
11
  require 'solidus_jwt/config'
8
12
  require 'solidus_jwt/concerns/decodeable'
@@ -0,0 +1,50 @@
1
+ module SolidusJwt
2
+ module DeviseStrategies
3
+ class Password < Devise::Strategies::Base
4
+ def valid?
5
+ valid_grant_type? && valid_params?
6
+ end
7
+
8
+ def authenticate!
9
+ resource = mapping.to.find_for_database_authentication(auth_hash)
10
+
11
+ block = proc { resource.valid_password?(password) }
12
+
13
+ if resource&.valid_for_authentication?(&block)
14
+ resource.after_database_authentication
15
+ return success!(resource)
16
+ end
17
+
18
+ fail!(:invalid)
19
+ end
20
+
21
+ private
22
+
23
+ def auth_hash
24
+ { email: username }
25
+ end
26
+
27
+ def grant_type
28
+ params[:grant_type]
29
+ end
30
+
31
+ def username
32
+ params[:username]
33
+ end
34
+
35
+ def password
36
+ params[:password]
37
+ end
38
+
39
+ def valid_grant_type?
40
+ grant_type == 'password'
41
+ end
42
+
43
+ def valid_params?
44
+ username.present? && password.present?
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ Warden::Strategies.add(:solidus_jwt_password, SolidusJwt::DeviseStrategies::Password)
@@ -0,0 +1,49 @@
1
+ module SolidusJwt
2
+ module DeviseStrategies
3
+ class RefreshToken < Devise::Strategies::Base
4
+ def valid?
5
+ valid_grant_type? && valid_params?
6
+ end
7
+
8
+ def authenticate!
9
+ resource = SolidusJwt::Token.find_by(auth_hash)
10
+ return fail!(:invalid) if resource.nil? || resource.user.nil?
11
+
12
+ block = proc do
13
+ # If we honor then mark the refresh token as stale for one time use
14
+ resource.honor? && resource.update_columns(active: false)
15
+ end
16
+
17
+ if resource.user.valid_for_authentication?(&block)
18
+ return success!(resource.user)
19
+ end
20
+
21
+ fail!(:invalid)
22
+ end
23
+
24
+ private
25
+
26
+ def auth_hash
27
+ { auth_type: :refresh, token: refresh_token }
28
+ end
29
+
30
+ def grant_type
31
+ params[:grant_type]
32
+ end
33
+
34
+ def refresh_token
35
+ params[:refresh_token]
36
+ end
37
+
38
+ def valid_grant_type?
39
+ grant_type == 'refresh_token'
40
+ end
41
+
42
+ def valid_params?
43
+ refresh_token.present?
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ Warden::Strategies.add(:solidus_jwt_refresh_token, SolidusJwt::DeviseStrategies::RefreshToken)
@@ -3,4 +3,15 @@ FactoryBot.define do
3
3
  #
4
4
  # Example adding this to your spec_helper will load these Factories for use:
5
5
  # require 'solidus_jwt/factories'
6
+ factory :token, class: SolidusJwt::Token do
7
+ association :user
8
+
9
+ trait :expired do
10
+ created_at { 1.year.ago }
11
+ end
12
+
13
+ trait :inactive do
14
+ active { false }
15
+ end
16
+ end
6
17
  end
@@ -34,6 +34,12 @@ module SolidusJwt
34
34
  #
35
35
  preference :jwt_options, :hash, default: { only: %i[email first_name id last_name] }
36
36
 
37
+ # @!attribute [rw] refresh_expriation
38
+ # @return [String] How long until the refresh token expires in seconds
39
+ # (default: +2592000+)
40
+ #
41
+ preference :refresh_expiration, :integer, default: 2_592_000
42
+
37
43
  ##
38
44
  # Get the secret token to encrypt json web tokens with.
39
45
  # @return [String] The secret used to encrypt json web tokens
@@ -1,8 +1,8 @@
1
1
  module SolidusJwt
2
- MAJOR = 0
3
- MINOR = 1
2
+ MAJOR = 1
3
+ MINOR = 0
4
4
  PATCH = 0
5
- PRERELEASE = nil
5
+ PRERELEASE = 'beta1'
6
6
 
7
7
  def self.version
8
8
  version = [MAJOR, MINOR, PATCH].join('.')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidus_jwt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taylor Scott
@@ -11,25 +11,33 @@ cert_chain: []
11
11
  date: 2019-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: solidus_core
14
+ name: jwt
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.0'
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: '3'
19
+ version: '0'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
- version: '1.0'
30
- - - "<"
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: solidus_auth_devise
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
31
32
  - !ruby/object:Gem::Version
32
- version: '3'
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
33
41
  - !ruby/object:Gem::Dependency
34
42
  name: solidus_backend
35
43
  requirement: !ruby/object:Gem::Requirement
@@ -51,33 +59,39 @@ dependencies:
51
59
  - !ruby/object:Gem::Version
52
60
  version: '3'
53
61
  - !ruby/object:Gem::Dependency
54
- name: solidus_support
62
+ name: solidus_core
55
63
  requirement: !ruby/object:Gem::Requirement
56
64
  requirements:
57
65
  - - ">="
58
66
  - !ruby/object:Gem::Version
59
- version: 0.1.3
67
+ version: '1.0'
68
+ - - "<"
69
+ - !ruby/object:Gem::Version
70
+ version: '3'
60
71
  type: :runtime
61
72
  prerelease: false
62
73
  version_requirements: !ruby/object:Gem::Requirement
63
74
  requirements:
64
75
  - - ">="
65
76
  - !ruby/object:Gem::Version
66
- version: 0.1.3
77
+ version: '1.0'
78
+ - - "<"
79
+ - !ruby/object:Gem::Version
80
+ version: '3'
67
81
  - !ruby/object:Gem::Dependency
68
- name: jwt
82
+ name: solidus_support
69
83
  requirement: !ruby/object:Gem::Requirement
70
84
  requirements:
71
85
  - - ">="
72
86
  - !ruby/object:Gem::Version
73
- version: '0'
87
+ version: 0.1.3
74
88
  type: :runtime
75
89
  prerelease: false
76
90
  version_requirements: !ruby/object:Gem::Requirement
77
91
  requirements:
78
92
  - - ">="
79
93
  - !ruby/object:Gem::Version
80
- version: '0'
94
+ version: 0.1.3
81
95
  - !ruby/object:Gem::Dependency
82
96
  name: byebug
83
97
  requirement: !ruby/object:Gem::Requirement
@@ -303,14 +317,21 @@ files:
303
317
  - app/assets/stylesheets/spree/frontend/solidus_jwt.css
304
318
  - app/controllers/spree/api/base_controller/json_web_tokens.rb
305
319
  - app/controllers/spree/api/base_controller_decorator.rb
320
+ - app/controllers/spree/api/oauths_controller.rb
321
+ - app/models/application_record.rb
322
+ - app/models/solidus_jwt/application_record.rb
323
+ - app/models/solidus_jwt/token.rb
306
324
  - app/models/spree/user_decorator.rb
307
325
  - config/locales/en.yml
308
326
  - config/routes.rb
327
+ - db/migrate/20190222220038_create_solidus_jwt_tokens.rb
309
328
  - lib/generators/solidus_jwt/install/install_generator.rb
310
329
  - lib/solidus_jwt.rb
311
330
  - lib/solidus_jwt/concerns/decodeable.rb
312
331
  - lib/solidus_jwt/concerns/encodeable.rb
313
332
  - lib/solidus_jwt/config.rb
333
+ - lib/solidus_jwt/devise_strategies/password.rb
334
+ - lib/solidus_jwt/devise_strategies/refresh_token.rb
314
335
  - lib/solidus_jwt/distributor/devise.rb
315
336
  - lib/solidus_jwt/engine.rb
316
337
  - lib/solidus_jwt/factories.rb
@@ -331,9 +352,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
331
352
  version: '0'
332
353
  required_rubygems_version: !ruby/object:Gem::Requirement
333
354
  requirements:
334
- - - ">="
355
+ - - ">"
335
356
  - !ruby/object:Gem::Version
336
- version: '0'
357
+ version: 1.3.1
337
358
  requirements: []
338
359
  rubygems_version: 3.0.3
339
360
  signing_key: