slots-jwt 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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