slots-jwt 0.0.4 → 0.1.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +121 -6
  3. data/app/controllers/slots/jwt/sessions_controller.rb +38 -0
  4. data/app/models/slots/jwt/application_record.rb +9 -0
  5. data/app/models/slots/jwt/session.rb +44 -0
  6. data/config/initializers/inflections.rb +3 -0
  7. data/config/routes.rb +2 -2
  8. data/lib/generators/slots/install/USAGE +1 -1
  9. data/lib/generators/slots/install/install_generator.rb +1 -1
  10. data/lib/generators/slots/install/templates/create_slots_sessions.rb +2 -2
  11. data/lib/generators/slots/install/templates/slots.rb +1 -1
  12. data/lib/generators/slots/model/model_generator.rb +1 -1
  13. data/lib/slots.rb +1 -44
  14. data/lib/slots/jwt.rb +49 -0
  15. data/lib/slots/jwt/authentication_helper.rb +147 -0
  16. data/lib/slots/jwt/configuration.rb +84 -0
  17. data/lib/slots/jwt/database_authentication.rb +21 -0
  18. data/lib/slots/jwt/engine.rb +9 -0
  19. data/lib/slots/jwt/extra_classes.rb +14 -0
  20. data/lib/slots/jwt/generic_methods.rb +53 -0
  21. data/lib/slots/jwt/generic_validations.rb +53 -0
  22. data/lib/slots/jwt/permission_filter.rb +37 -0
  23. data/lib/slots/jwt/slokens.rb +115 -0
  24. data/lib/slots/jwt/tests.rb +37 -0
  25. data/lib/slots/jwt/tokens.rb +104 -0
  26. data/lib/slots/jwt/type_helper.rb +30 -0
  27. data/lib/slots/{version.rb → jwt/version.rb} +3 -1
  28. data/lib/tasks/slots_tasks.rake +5 -5
  29. metadata +23 -19
  30. data/app/controllers/slots/sessions_controller.rb +0 -36
  31. data/app/mailers/slots/application_mailer.rb +0 -8
  32. data/app/models/slots/application_record.rb +0 -7
  33. data/app/models/slots/session.rb +0 -42
  34. data/lib/slots/authentication_helper.rb +0 -144
  35. data/lib/slots/configuration.rb +0 -82
  36. data/lib/slots/database_authentication.rb +0 -19
  37. data/lib/slots/engine.rb +0 -7
  38. data/lib/slots/extra_classes.rb +0 -12
  39. data/lib/slots/generic_methods.rb +0 -51
  40. data/lib/slots/generic_validations.rb +0 -51
  41. data/lib/slots/slokens.rb +0 -113
  42. data/lib/slots/tests.rb +0 -35
  43. data/lib/slots/tokens.rb +0 -102
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slots
4
+ module JWT
5
+ module Tests
6
+ def authorized_get(current_user, url, headers: {}, **options)
7
+ authorized_protocal :get, current_user, url, headers: headers, **options
8
+ end
9
+ def authorized_post(current_user, url, headers: {}, **options)
10
+ authorized_protocal :post, current_user, url, headers: headers, **options
11
+ end
12
+ def authorized_patch(current_user, url, headers: {}, **options)
13
+ authorized_protocal :patch, current_user, url, headers: headers, **options
14
+ end
15
+ def authorized_put(current_user, url, headers: {}, **options)
16
+ authorized_protocal :put, current_user, url, headers: headers, **options
17
+ end
18
+ def authorized_delete(current_user, url, headers: {}, **options)
19
+ authorized_protocal :delete, current_user, url, headers: headers, **options
20
+ end
21
+
22
+ def authorized_protocal(type, current_user, url, headers: {}, session: false, **options)
23
+ @token = current_user&.create_token(session)
24
+ headers = headers.merge(token_header(@token)) if @token
25
+ send(type, url, headers: headers, **options)
26
+ end
27
+
28
+ def current_token
29
+ @token
30
+ end
31
+
32
+ def token_header(token)
33
+ {'authorization' => %{Bearer token="#{token}"}}
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slots
4
+ module JWT
5
+ module Tokens
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ end
10
+
11
+ def jwt_identifier
12
+ send(self.class.jwt_identifier_column)
13
+ end
14
+
15
+ def create_token(have_session)
16
+ session = ''
17
+ if have_session && Slots::JWT.configuration.session_lifetime
18
+ @new_session = self.sessions.new(jwt_iat: 0)
19
+ # Session should never be invalid since its all programmed
20
+ raise 'Session not valid' unless @new_session.valid?
21
+ session = @new_session.session
22
+ end
23
+ @slots_jwt = Slots::JWT::Slokens.encode(self, session, extra_payload)
24
+ if @new_session
25
+ @new_session.jwt_iat = @slots_jwt.iat
26
+ @new_session.save!
27
+ end
28
+ @new_token = true
29
+ run_token_created_callback
30
+ token
31
+ end
32
+
33
+ def extra_payload
34
+ @extra_payload || {}
35
+ end
36
+
37
+ def token
38
+ @slots_jwt&.token
39
+ end
40
+
41
+ def jwt
42
+ @slots_jwt
43
+ end
44
+ def set_token!(slots_jwt)
45
+ @slots_jwt = slots_jwt
46
+ self
47
+ end
48
+
49
+ def update_session
50
+ return false unless valid_in_database?
51
+ return false unless allowed_new_token?
52
+ # Need to check if allowed new token after loading
53
+ session = self.sessions.matches_jwt(jwt)
54
+ return false unless session
55
+ old_iat = jwt.iat
56
+ jwt.update_token(self, extra_payload)
57
+ if session.jwt_iat == old_iat
58
+ # if old_iat == previous_jwt_iat dont update and return token
59
+ session.update(previous_jwt_iat: old_iat, jwt_iat: jwt.iat)
60
+ @new_token = true
61
+ end
62
+ end
63
+
64
+ def update_token
65
+ # This will only update the data in the token
66
+ # not the experation data or anything else
67
+ return false unless valid_in_database?
68
+ return false unless allowed_new_token?
69
+
70
+ session = self.sessions.matches_jwt(jwt)
71
+ old_iat = jwt.iat
72
+ jwt.update_token_data(self, extra_payload)
73
+ # Dont worry if session isnt there because exp not updated
74
+ session&.update(previous_jwt_iat: old_iat, jwt_iat: jwt.iat)
75
+ @new_token = true
76
+ end
77
+
78
+ def new_token?
79
+ @new_token
80
+ end
81
+
82
+ def valid_in_database?
83
+ begin
84
+ jwt_identifier_was = self.jwt_identifier
85
+ self.reload
86
+ return false if jwt_identifier_was != self.jwt_identifier
87
+ rescue ActiveRecord::RecordNotFound
88
+ return false
89
+ end
90
+ true
91
+ end
92
+
93
+ module ClassMethods
94
+ def from_sloken(slots_jwt)
95
+ self.new(slots_jwt.authentication_model_values).set_token!(slots_jwt)
96
+ end
97
+
98
+ def jwt_identifier_column
99
+ Slots::JWT.configuration.logins.keys.first
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slots
4
+ module JWT
5
+ module TypeHelper
6
+ def self.included(mod)
7
+ mod.module_eval do
8
+ def initialize(*args, required_permission: nil, **kwargs, &block)
9
+ required_permission(required_permission)
10
+ # Pass on the default args:
11
+ super(*args, **kwargs, &block)
12
+ end
13
+ end
14
+ end
15
+ # Call this method in an Object class to set the permission level:
16
+ def required_permission(permission_level)
17
+ @_required_permission = permission_level
18
+ end
19
+
20
+ # This method is overridden to customize object types:
21
+ def to_graphql
22
+ type_defn = super # returns a GraphQL::ObjectType
23
+ # Get a configured value and assign it to metadata
24
+ type_defn.metadata[:has_required_permission] = true
25
+ type_defn.metadata[:required_permission] = @_required_permission
26
+ type_defn
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Slots
4
- VERSION = "0.0.4"
4
+ module JWT
5
+ VERSION = "0.1.0"
6
+ end
5
7
  end
@@ -3,8 +3,8 @@
3
3
  namespace :slots do
4
4
  desc "Creates or prepends secret in config/lots_secrets.yml"
5
5
  task :new_secret do
6
- FileUtils.touch(Slots.secret_yaml_file)
7
- secret_keys = YAML.load_file(Slots.secret_yaml_file) || []
6
+ FileUtils.touch(Slots::JWT.secret_yaml_file)
7
+ secret_keys = YAML.load_file(Slots::JWT.secret_yaml_file) || []
8
8
  # DO 1 minute from now so has time to restart server
9
9
  secret_keys.prepend('SECRET' => SecureRandom.hex(64), 'CREATED_AT' => 1.minute.from_now.to_i)
10
10
 
@@ -12,16 +12,16 @@ namespace :slots do
12
12
  slots_initilizer = Rails.root.join('config', 'initializers', 'slots.rb')
13
13
  require slots_initilizer if File.file?(slots_initilizer)
14
14
 
15
- remove_old_secrets = Slots.configuration.session_lifetime.ago.to_i
15
+ remove_old_secrets = Slots::JWT.configuration.session_lifetime.ago.to_i
16
16
  secret_keys.reject! { |value| remove_old_secrets > value['CREATED_AT'] }
17
- File.open(Slots.secret_yaml_file, "w") do |file|
17
+ File.open(Slots::JWT.secret_yaml_file, "w") do |file|
18
18
  file.write secret_keys.to_yaml
19
19
  end
20
20
  Rake::Task["restart"].invoke
21
21
  end
22
22
  desc "Clears config/lots_secrets.yml"
23
23
  task :clear_secrets do
24
- File.open(Slots.secret_yaml_file, "w") do |file|
24
+ File.open(Slots::JWT.secret_yaml_file, "w") do |file|
25
25
  file.write [].to_yaml
26
26
  end
27
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slots-jwt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathon Gardner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-26 00:00:00.000000000 Z
11
+ date: 2019-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -81,10 +81,10 @@ files:
81
81
  - MIT-LICENSE
82
82
  - README.md
83
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
84
+ - app/controllers/slots/jwt/sessions_controller.rb
85
+ - app/models/slots/jwt/application_record.rb
86
+ - app/models/slots/jwt/session.rb
87
+ - config/initializers/inflections.rb
88
88
  - config/routes.rb
89
89
  - lib/generators/slots/install/USAGE
90
90
  - lib/generators/slots/install/install_generator.rb
@@ -96,19 +96,22 @@ files:
96
96
  - lib/generators/slots/model/templates/model.rb
97
97
  - lib/generators/slots/model/templates/model_test.rb
98
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
99
+ - lib/slots/jwt.rb
100
+ - lib/slots/jwt/authentication_helper.rb
101
+ - lib/slots/jwt/configuration.rb
102
+ - lib/slots/jwt/database_authentication.rb
103
+ - lib/slots/jwt/engine.rb
104
+ - lib/slots/jwt/extra_classes.rb
105
+ - lib/slots/jwt/generic_methods.rb
106
+ - lib/slots/jwt/generic_validations.rb
107
+ - lib/slots/jwt/permission_filter.rb
108
+ - lib/slots/jwt/slokens.rb
109
+ - lib/slots/jwt/tests.rb
110
+ - lib/slots/jwt/tokens.rb
111
+ - lib/slots/jwt/type_helper.rb
112
+ - lib/slots/jwt/version.rb
110
113
  - lib/tasks/slots_tasks.rake
111
- homepage: https://github.com/jonathongardner/slots
114
+ homepage: https://github.com/jonathongardner/slots-jwt
112
115
  licenses:
113
116
  - MIT
114
117
  metadata: {}
@@ -127,7 +130,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
130
  - !ruby/object:Gem::Version
128
131
  version: '0'
129
132
  requirements: []
130
- rubygems_version: 3.0.1
133
+ rubyforge_project:
134
+ rubygems_version: 2.7.6
131
135
  signing_key:
132
136
  specification_version: 4
133
137
  summary: Token Authentication for Rails using JWT.
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Slots
4
- class SessionsController < ApplicationController
5
- update_expired_session_tokens! only: :update_session_token # needed if token is expired
6
- require_user_load! only: :update_session_token
7
- require_login! only: [:update_session_token, :sign_out]
8
- skip_callback!
9
-
10
- def sign_in
11
- @_current_user = _authentication_model.find_for_authentication(params[:login])
12
-
13
- current_user.authenticate!(params[:password])
14
-
15
- new_token!(ActiveModel::Type::Boolean.new.cast(params[:session]))
16
- render json: current_user.as_json, status: :accepted
17
- end
18
-
19
- def sign_out
20
- Slots::Session.find_by(session: jw_token.session)&.delete if jw_token.session.present?
21
- head :ok
22
- end
23
-
24
- def update_session_token
25
- # TODO think about not allowing user to get new token here because then there
26
- current_user.update_token unless current_user.new_token?
27
- render json: current_user.as_json, status: :accepted
28
- end
29
-
30
- private
31
-
32
- def _authentication_model
33
- Slots.configuration.authentication_model
34
- end
35
- end
36
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Slots
4
- class ApplicationMailer < ActionMailer::Base
5
- default from: "from@example.com"
6
- layout "mailer"
7
- end
8
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Slots
4
- class ApplicationRecord < ActiveRecord::Base
5
- self.abstract_class = true
6
- end
7
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Slots
4
- class Session < ApplicationRecord
5
- belongs_to :user, session_assocaition
6
- before_validation :create_random_session, on: :create
7
- validates :session, :jwt_iat, presence: true
8
- validates :session, uniqueness: true
9
-
10
- def update_random_session
11
- self.session = SecureRandom.hex(32)
12
- end
13
-
14
- def self.expired
15
- self.where(self.arel_table[:created_at].lte(Slots.configuration.session_lifetime.ago))
16
- end
17
-
18
- def self.not_expired
19
- self.where(self.arel_table[:created_at].gt(Slots.configuration.session_lifetime.ago))
20
- end
21
-
22
- def self.matches_jwt(sloken_jws)
23
- jwt_where = self.arel_table[:jwt_iat].eq(sloken_jws.iat)
24
- if Slots.configuration.previous_jwt_lifetime
25
- jwt_where = jwt_where.or(
26
- Arel::Nodes::Grouping.new(
27
- self.arel_table[:previous_jwt_iat].eq(sloken_jws.iat)
28
- .and(self.arel_table[:jwt_iat].gt(Slots.configuration.previous_jwt_lifetime.ago.to_i))
29
- )
30
- )
31
- end
32
-
33
- self.not_expired
34
- .where(jwt_where)
35
- .find_by(session: sloken_jws.session)
36
- end
37
- private
38
- def create_random_session
39
- update_random_session unless self.session
40
- end
41
- end
42
- end
@@ -1,144 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Slots
4
- module AuthenticationHelper
5
- ALL = Object.new
6
-
7
- extend ActiveSupport::Concern
8
-
9
- included do
10
- include ActionController::HttpAuthentication::Token::ControllerMethods
11
- end
12
-
13
- def jw_token
14
- return @_jw_token if @_jw_token&.valid!
15
- @_jw_token = Slots::Slokens.decode(authenticate_with_http_token { |t, _| t })
16
- @_jw_token.valid!
17
- end
18
-
19
- def update_expired_session_tokens
20
- return false unless Slots.configuration.session_lifetime
21
- @_jw_token = Slots::Slokens.decode(authenticate_with_http_token { |t, _| t })
22
- return false unless @_jw_token.expired? && @_jw_token.session.present?
23
- new_session_token
24
- end
25
-
26
- def new_session_token
27
- _current_user = Slots.configuration.authentication_model.from_sloken(@_jw_token)
28
- return false unless _current_user&.update_session
29
- @_current_user = _current_user
30
- true
31
- end
32
-
33
- def current_user
34
- return @_current_user if instance_variable_defined?(:@_current_user)
35
- current_user = Slots.configuration.authentication_model.from_sloken(jw_token)
36
- # So if jw_token initalize current_user if expired
37
- @_current_user ||= current_user
38
- end
39
- def load_user
40
- current_user&.valid_in_database? && current_user.allowed_new_token?
41
- end
42
-
43
- def set_token_header!
44
- # check if current user for logout
45
- response.set_header('authorization', "Bearer token=#{current_user.token}") if current_user&.new_token?
46
- end
47
-
48
- def require_valid_user
49
- # Load user will make sure it is in the database and valid in the database
50
- raise Slots::InvalidToken, "User doesnt exist" if @_require_load_user && !load_user
51
- access_denied! unless current_user && (@_ignore_callbacks || token_allowed?)
52
- end
53
- def require_load_user
54
- # Use varaible so that if this action is prepended it will still only be called when checking for valid user,
55
- # i.e. so its not called before update_expired_session_tokens if set
56
- @_require_load_user = true
57
- end
58
- def ignore_callbacks
59
- @_ignore_callbacks = true
60
- end
61
-
62
- def access_denied!
63
- raise Slots::AccessDenied
64
- end
65
-
66
- def token_allowed?
67
- !(self.class._reject_token?(self))
68
- end
69
-
70
- def new_token!(session)
71
- current_user.create_token(session)
72
- set_token_header!
73
- end
74
-
75
- def update_token!
76
- current_user.update_token
77
- end
78
-
79
- module ClassMethods
80
- def update_expired_session_tokens!(**options)
81
- prepend_before_action :update_expired_session_tokens, **options
82
- after_action :set_token_header!, **options
83
- end
84
-
85
- def require_login!(load_user: false, **options)
86
- before_action :require_load_user, **options if load_user
87
- before_action :require_valid_user, **options
88
- end
89
-
90
- def require_user_load!(**options)
91
- prepend_before_action :require_load_user, **options
92
- end
93
-
94
- def skip_callback!(**options)
95
- prepend_before_action :ignore_callbacks, **options
96
- end
97
-
98
- def ignore_login!(**options)
99
- skip_before_action :require_valid_user, **options
100
- skip_before_action :require_load_user, **options, raise: false
101
- skip_before_action :update_expired_session_tokens, **options, raise: false
102
- skip_after_action :set_token_header!, **options, raise: false
103
- end
104
-
105
- def catch_invalid_login(response: {errors: {authentication: ['login or password is invalid']}}, status: :unauthorized)
106
- rescue_from Slots::AuthenticationFailed do |exception|
107
- render json: response, status: status
108
- end
109
- end
110
-
111
- def catch_invalid_token(response: {errors: {authentication: ['invalid or missing token']}}, status: :unauthorized)
112
- rescue_from Slots::InvalidToken do |exception|
113
- render json: response, status: status
114
- end
115
- end
116
-
117
- def catch_access_denied(response: {errors: {authorization: ["can't access"]}}, status: :forbidden)
118
- rescue_from Slots::AccessDenied do |exception|
119
- render json: response, status: status
120
- end
121
- end
122
-
123
- def reject_token(only: ALL, except: ALL, &block)
124
- raise 'Cant pass both only and except' unless only == ALL || except == ALL
125
- only = Array(only) if only != ALL
126
- except = Array(except) if except != ALL
127
-
128
- (@_reject_token ||= []).push([only, except, block])
129
- end
130
- def _reject_token?(con)
131
- (@_reject_token ||= []).any? { |o, e, b| _check_to_reject?(con, o, e, b) } || _superclass_reject_token?(con)
132
- end
133
- def _check_to_reject?(con, only, except, block)
134
- return false unless only == ALL || only.any? { |o| o.to_sym == con.action_name.to_sym }
135
- return false if except != ALL && except.any? { |e| e.to_sym == con.action_name.to_sym }
136
- (con.instance_eval &block)
137
- end
138
-
139
- def _superclass_reject_token?(con)
140
- self.superclass.respond_to?('_reject_token?') && self.superclass._reject_token?(con)
141
- end
142
- end
143
- end
144
- end