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 +4 -4
- data/README.md +58 -2
- data/app/controllers/spree/api/oauths_controller.rb +34 -0
- data/app/models/application_record.rb +3 -0
- data/app/models/solidus_jwt/application_record.rb +9 -0
- data/app/models/solidus_jwt/token.rb +48 -0
- data/app/models/spree/user_decorator.rb +4 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20190222220038_create_solidus_jwt_tokens.rb +12 -0
- data/lib/solidus_jwt.rb +4 -0
- data/lib/solidus_jwt/devise_strategies/password.rb +50 -0
- data/lib/solidus_jwt/devise_strategies/refresh_token.rb +49 -0
- data/lib/solidus_jwt/factories.rb +11 -0
- data/lib/solidus_jwt/preferences.rb +6 -0
- data/lib/solidus_jwt/version.rb +3 -3
- metadata +38 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e295cf784cd7480da864f9f402f75b9c8e9acb1ecdd611c7bb592121c329ea46
|
4
|
+
data.tar.gz: 31705028f11d7134864145b845e345f1816b0cb2e67bb21b7761ec15ce3f356a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 =
|
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
|
-
###
|
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,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
|
data/config/routes.rb
CHANGED
@@ -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
|
data/lib/solidus_jwt/version.rb
CHANGED
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.
|
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:
|
14
|
+
name: jwt
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
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: '
|
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: '
|
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:
|
62
|
+
name: solidus_core
|
55
63
|
requirement: !ruby/object:Gem::Requirement
|
56
64
|
requirements:
|
57
65
|
- - ">="
|
58
66
|
- !ruby/object:Gem::Version
|
59
|
-
version:
|
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:
|
77
|
+
version: '1.0'
|
78
|
+
- - "<"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '3'
|
67
81
|
- !ruby/object:Gem::Dependency
|
68
|
-
name:
|
82
|
+
name: solidus_support
|
69
83
|
requirement: !ruby/object:Gem::Requirement
|
70
84
|
requirements:
|
71
85
|
- - ">="
|
72
86
|
- !ruby/object:Gem::Version
|
73
|
-
version:
|
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:
|
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:
|
357
|
+
version: 1.3.1
|
337
358
|
requirements: []
|
338
359
|
rubygems_version: 3.0.3
|
339
360
|
signing_key:
|