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.
@@ -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
@@ -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
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slots
4
+ VERSION = "0.0.4"
5
+ 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: []