slots-jwt 0.0.4
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +214 -0
- data/Rakefile +34 -0
- data/app/controllers/slots/sessions_controller.rb +36 -0
- data/app/mailers/slots/application_mailer.rb +8 -0
- data/app/models/slots/application_record.rb +7 -0
- data/app/models/slots/session.rb +42 -0
- data/config/routes.rb +10 -0
- data/lib/generators/slots/install/USAGE +13 -0
- data/lib/generators/slots/install/install_generator.rb +16 -0
- data/lib/generators/slots/install/templates/create_slots_sessions.rb +13 -0
- data/lib/generators/slots/install/templates/slots.rb +10 -0
- data/lib/generators/slots/model/USAGE +9 -0
- data/lib/generators/slots/model/model_generator.rb +24 -0
- data/lib/generators/slots/model/templates/create_models.rb +12 -0
- data/lib/generators/slots/model/templates/model.rb +5 -0
- data/lib/generators/slots/model/templates/model_test.rb +5 -0
- data/lib/slots.rb +46 -0
- data/lib/slots/authentication_helper.rb +144 -0
- data/lib/slots/configuration.rb +82 -0
- data/lib/slots/database_authentication.rb +19 -0
- data/lib/slots/engine.rb +7 -0
- data/lib/slots/extra_classes.rb +12 -0
- data/lib/slots/generic_methods.rb +51 -0
- data/lib/slots/generic_validations.rb +51 -0
- data/lib/slots/slokens.rb +113 -0
- data/lib/slots/tests.rb +35 -0
- data/lib/slots/tokens.rb +102 -0
- data/lib/slots/version.rb +5 -0
- data/lib/tasks/slots_tasks.rake +28 -0
- metadata +134 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jwt'
|
4
|
+
module Slots
|
5
|
+
class Slokens
|
6
|
+
attr_reader :token, :exp, :iat, :extra_payload, :authentication_model_values, :session
|
7
|
+
def initialize(decode: false, encode: false, token: nil, authentication_record: nil, extra_payload: nil, session: nil)
|
8
|
+
if decode
|
9
|
+
@token = token
|
10
|
+
decode()
|
11
|
+
elsif encode
|
12
|
+
@authentication_model_values = authentication_record.as_json
|
13
|
+
@extra_payload = extra_payload.as_json
|
14
|
+
@session = session
|
15
|
+
update_iat
|
16
|
+
update_exp
|
17
|
+
encode()
|
18
|
+
@valid = true
|
19
|
+
else
|
20
|
+
raise 'must encode or decode'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
def self.decode(token)
|
24
|
+
self.new(decode: true, token: token)
|
25
|
+
end
|
26
|
+
def self.encode(authentication_record, session = '', extra_payload)
|
27
|
+
self.new(encode: true, authentication_record: authentication_record, session: session, extra_payload: extra_payload)
|
28
|
+
end
|
29
|
+
|
30
|
+
def expired?
|
31
|
+
@expired
|
32
|
+
end
|
33
|
+
|
34
|
+
def valid?
|
35
|
+
@valid
|
36
|
+
end
|
37
|
+
|
38
|
+
def valid!
|
39
|
+
raise InvalidToken, "Invalid Token" unless valid?
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def update_token_data(authentication_record, extra_payload)
|
44
|
+
@authentication_model_values = authentication_record.as_json
|
45
|
+
@extra_payload = extra_payload.as_json
|
46
|
+
update_iat
|
47
|
+
encode
|
48
|
+
end
|
49
|
+
|
50
|
+
def update_token(authentication_record, extra_payload)
|
51
|
+
update_exp
|
52
|
+
update_token_data(authentication_record, extra_payload)
|
53
|
+
end
|
54
|
+
|
55
|
+
def payload
|
56
|
+
{
|
57
|
+
authentication_model_key => @authentication_model_values,
|
58
|
+
'exp' => @exp,
|
59
|
+
'iat' => @iat,
|
60
|
+
'session' => @session,
|
61
|
+
'extra_payload' => @extra_payload,
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def authentication_model_key
|
67
|
+
Slots.configuration.authentication_model.name.underscore
|
68
|
+
end
|
69
|
+
|
70
|
+
def default_expected_keys
|
71
|
+
['exp', 'iat', 'session', authentication_model_key]
|
72
|
+
end
|
73
|
+
def secret
|
74
|
+
Slots.configuration.secret(@iat)
|
75
|
+
end
|
76
|
+
def update_iat
|
77
|
+
@iat = Time.now.to_i
|
78
|
+
end
|
79
|
+
def update_exp
|
80
|
+
@exp = Slots.configuration.token_lifetime.from_now.to_i
|
81
|
+
end
|
82
|
+
def encode
|
83
|
+
@token = JWT.encode self.payload, secret, 'HS256'
|
84
|
+
@expired = false
|
85
|
+
@valid = true
|
86
|
+
end
|
87
|
+
|
88
|
+
def decode
|
89
|
+
begin
|
90
|
+
set_payload
|
91
|
+
JWT.decode @token, secret, true, verify_iat: true, algorithm: 'HS256'
|
92
|
+
rescue JWT::ExpiredSignature
|
93
|
+
@expired = true
|
94
|
+
rescue JWT::InvalidIatError, JWT::VerificationError, JWT::DecodeError, Slots::InvalidSecret, NoMethodError, JSON::ParserError
|
95
|
+
@valid = false
|
96
|
+
else
|
97
|
+
@valid = payload.slice(*default_expected_keys).compact.length == default_expected_keys.length
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def set_payload
|
102
|
+
encoded64 = @token.split('.')[1] || ''
|
103
|
+
string_payload = Base64.decode64(encoded64)
|
104
|
+
local_payload = JSON.parse(string_payload)
|
105
|
+
raise JSON::ParserError unless local_payload.is_a?(Hash)
|
106
|
+
@exp = local_payload['exp']&.to_i
|
107
|
+
@iat = local_payload['iat']&.to_i
|
108
|
+
@session = local_payload['session']
|
109
|
+
@authentication_model_values = local_payload[authentication_model_key]
|
110
|
+
@extra_payload = local_payload['extra_payload']
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/slots/tests.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Slots
|
4
|
+
module Tests
|
5
|
+
def authorized_get(current_user, url, headers: {}, **options)
|
6
|
+
authorized_protocal :get, current_user, url, headers: headers, **options
|
7
|
+
end
|
8
|
+
def authorized_post(current_user, url, headers: {}, **options)
|
9
|
+
authorized_protocal :post, current_user, url, headers: headers, **options
|
10
|
+
end
|
11
|
+
def authorized_patch(current_user, url, headers: {}, **options)
|
12
|
+
authorized_protocal :patch, current_user, url, headers: headers, **options
|
13
|
+
end
|
14
|
+
def authorized_put(current_user, url, headers: {}, **options)
|
15
|
+
authorized_protocal :put, current_user, url, headers: headers, **options
|
16
|
+
end
|
17
|
+
def authorized_delete(current_user, url, headers: {}, **options)
|
18
|
+
authorized_protocal :delete, current_user, url, headers: headers, **options
|
19
|
+
end
|
20
|
+
|
21
|
+
def authorized_protocal(type, current_user, url, headers: {}, session: false, **options)
|
22
|
+
@token = current_user&.create_token(session)
|
23
|
+
headers = headers.merge(token_header(@token)) if @token
|
24
|
+
send(type, url, headers: headers, **options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def current_token
|
28
|
+
@token
|
29
|
+
end
|
30
|
+
|
31
|
+
def token_header(token)
|
32
|
+
{'authorization' => %{Bearer token="#{token}"}}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/slots/tokens.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Slots
|
4
|
+
module Tokens
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
end
|
9
|
+
|
10
|
+
def jwt_identifier
|
11
|
+
send(self.class.jwt_identifier_column)
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_token(have_session)
|
15
|
+
session = ''
|
16
|
+
if have_session && Slots.configuration.session_lifetime
|
17
|
+
@new_session = self.sessions.new(jwt_iat: 0)
|
18
|
+
# Session should never be invalid since its all programmed
|
19
|
+
raise 'Session not valid' unless @new_session.valid?
|
20
|
+
session = @new_session.session
|
21
|
+
end
|
22
|
+
@slots_jwt = Slots::Slokens.encode(self, session, extra_payload)
|
23
|
+
if @new_session
|
24
|
+
@new_session.jwt_iat = @slots_jwt.iat
|
25
|
+
@new_session.save!
|
26
|
+
end
|
27
|
+
@new_token = true
|
28
|
+
run_token_created_callback
|
29
|
+
token
|
30
|
+
end
|
31
|
+
|
32
|
+
def extra_payload
|
33
|
+
@extra_payload || {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def token
|
37
|
+
@slots_jwt&.token
|
38
|
+
end
|
39
|
+
|
40
|
+
def jwt
|
41
|
+
@slots_jwt
|
42
|
+
end
|
43
|
+
def set_token!(slots_jwt)
|
44
|
+
@slots_jwt = slots_jwt
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def update_session
|
49
|
+
return false unless valid_in_database?
|
50
|
+
return false unless allowed_new_token?
|
51
|
+
# Need to check if allowed new token after loading
|
52
|
+
session = self.sessions.matches_jwt(jwt)
|
53
|
+
return false unless session
|
54
|
+
old_iat = jwt.iat
|
55
|
+
jwt.update_token(self, extra_payload)
|
56
|
+
if session.jwt_iat == old_iat
|
57
|
+
# if old_iat == previous_jwt_iat dont update and return token
|
58
|
+
session.update(previous_jwt_iat: old_iat, jwt_iat: jwt.iat)
|
59
|
+
@new_token = true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def update_token
|
64
|
+
# This will only update the data in the token
|
65
|
+
# not the experation data or anything else
|
66
|
+
return false unless valid_in_database?
|
67
|
+
return false unless allowed_new_token?
|
68
|
+
|
69
|
+
session = self.sessions.matches_jwt(jwt)
|
70
|
+
old_iat = jwt.iat
|
71
|
+
jwt.update_token_data(self, extra_payload)
|
72
|
+
# Dont worry if session isnt there because exp not updated
|
73
|
+
session&.update(previous_jwt_iat: old_iat, jwt_iat: jwt.iat)
|
74
|
+
@new_token = true
|
75
|
+
end
|
76
|
+
|
77
|
+
def new_token?
|
78
|
+
@new_token
|
79
|
+
end
|
80
|
+
|
81
|
+
def valid_in_database?
|
82
|
+
begin
|
83
|
+
jwt_identifier_was = self.jwt_identifier
|
84
|
+
self.reload
|
85
|
+
return false if jwt_identifier_was != self.jwt_identifier
|
86
|
+
rescue ActiveRecord::RecordNotFound
|
87
|
+
return false
|
88
|
+
end
|
89
|
+
true
|
90
|
+
end
|
91
|
+
|
92
|
+
module ClassMethods
|
93
|
+
def from_sloken(slots_jwt)
|
94
|
+
self.new(slots_jwt.authentication_model_values).set_token!(slots_jwt)
|
95
|
+
end
|
96
|
+
|
97
|
+
def jwt_identifier_column
|
98
|
+
Slots.configuration.logins.keys.first
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :slots do
|
4
|
+
desc "Creates or prepends secret in config/lots_secrets.yml"
|
5
|
+
task :new_secret do
|
6
|
+
FileUtils.touch(Slots.secret_yaml_file)
|
7
|
+
secret_keys = YAML.load_file(Slots.secret_yaml_file) || []
|
8
|
+
# DO 1 minute from now so has time to restart server
|
9
|
+
secret_keys.prepend('SECRET' => SecureRandom.hex(64), 'CREATED_AT' => 1.minute.from_now.to_i)
|
10
|
+
|
11
|
+
# TODO this might not always work
|
12
|
+
slots_initilizer = Rails.root.join('config', 'initializers', 'slots.rb')
|
13
|
+
require slots_initilizer if File.file?(slots_initilizer)
|
14
|
+
|
15
|
+
remove_old_secrets = Slots.configuration.session_lifetime.ago.to_i
|
16
|
+
secret_keys.reject! { |value| remove_old_secrets > value['CREATED_AT'] }
|
17
|
+
File.open(Slots.secret_yaml_file, "w") do |file|
|
18
|
+
file.write secret_keys.to_yaml
|
19
|
+
end
|
20
|
+
Rake::Task["restart"].invoke
|
21
|
+
end
|
22
|
+
desc "Clears config/lots_secrets.yml"
|
23
|
+
task :clear_secrets do
|
24
|
+
File.open(Slots.secret_yaml_file, "w") do |file|
|
25
|
+
file.write [].to_yaml
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: slots-jwt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonathon Gardner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-08-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bcrypt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.1.7
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.1.7
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: jwt
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.1.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.1.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sqlite3
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Token Authentication for Rails using JWT. Slots is designed to keep JWT
|
70
|
+
stateless and minimize database calls. This is done by storing (none sensitive)
|
71
|
+
data in the JWT and populating current_user with the JWT data. This allows for things
|
72
|
+
like `current_user.teams` or other assocations to be called on the user. Unless
|
73
|
+
explicitly told slots will only load the user from the database when creating (or
|
74
|
+
updating an expired) token.
|
75
|
+
email:
|
76
|
+
- TheAppGardner@gmail.com
|
77
|
+
executables: []
|
78
|
+
extensions: []
|
79
|
+
extra_rdoc_files: []
|
80
|
+
files:
|
81
|
+
- MIT-LICENSE
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- app/controllers/slots/sessions_controller.rb
|
85
|
+
- app/mailers/slots/application_mailer.rb
|
86
|
+
- app/models/slots/application_record.rb
|
87
|
+
- app/models/slots/session.rb
|
88
|
+
- config/routes.rb
|
89
|
+
- lib/generators/slots/install/USAGE
|
90
|
+
- lib/generators/slots/install/install_generator.rb
|
91
|
+
- lib/generators/slots/install/templates/create_slots_sessions.rb
|
92
|
+
- lib/generators/slots/install/templates/slots.rb
|
93
|
+
- lib/generators/slots/model/USAGE
|
94
|
+
- lib/generators/slots/model/model_generator.rb
|
95
|
+
- lib/generators/slots/model/templates/create_models.rb
|
96
|
+
- lib/generators/slots/model/templates/model.rb
|
97
|
+
- lib/generators/slots/model/templates/model_test.rb
|
98
|
+
- lib/slots.rb
|
99
|
+
- lib/slots/authentication_helper.rb
|
100
|
+
- lib/slots/configuration.rb
|
101
|
+
- lib/slots/database_authentication.rb
|
102
|
+
- lib/slots/engine.rb
|
103
|
+
- lib/slots/extra_classes.rb
|
104
|
+
- lib/slots/generic_methods.rb
|
105
|
+
- lib/slots/generic_validations.rb
|
106
|
+
- lib/slots/slokens.rb
|
107
|
+
- lib/slots/tests.rb
|
108
|
+
- lib/slots/tokens.rb
|
109
|
+
- lib/slots/version.rb
|
110
|
+
- lib/tasks/slots_tasks.rake
|
111
|
+
homepage: https://github.com/jonathongardner/slots
|
112
|
+
licenses:
|
113
|
+
- MIT
|
114
|
+
metadata: {}
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubygems_version: 3.0.1
|
131
|
+
signing_key:
|
132
|
+
specification_version: 4
|
133
|
+
summary: Token Authentication for Rails using JWT.
|
134
|
+
test_files: []
|