token_authenticate_me 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/{LICENSE → MIT-LICENSE} +1 -1
  3. data/Rakefile +27 -11
  4. data/app/controllers/token_authenticate_me/api/v1/base_controller.rb +9 -0
  5. data/app/controllers/token_authenticate_me/api/v1/invites_controller.rb +14 -0
  6. data/app/controllers/token_authenticate_me/api/v1/password_resets_controller.rb +11 -0
  7. data/app/controllers/token_authenticate_me/api/v1/sessions_controller.rb +11 -0
  8. data/app/controllers/token_authenticate_me/api/v1/users_controller.rb +17 -0
  9. data/app/controllers/token_authenticate_me/application_controller.rb +5 -0
  10. data/app/helpers/token_authenticate_me/application_helper.rb +4 -0
  11. data/app/mailers/token_authenticate_me_mailer.rb +16 -9
  12. data/app/models/token_authenticate_me/invite.rb +11 -0
  13. data/app/models/token_authenticate_me/session.rb +8 -0
  14. data/app/models/token_authenticate_me/user.rb +11 -0
  15. data/app/views/token_authenticate_me_mailer/invite_user_email.html.erb +0 -0
  16. data/config/routes.rb +23 -0
  17. data/db/migrate/20160620184327_create_token_authenticate_me_invites.rb +14 -0
  18. data/db/migrate/20160621211347_create_token_authenticate_me_users.rb +18 -0
  19. data/db/migrate/20160622203801_create_token_authenticate_me_sessions.rb +14 -0
  20. data/lib/generators/token_authenticate_me/controllers/controllers_generator.rb +1 -76
  21. data/lib/generators/token_authenticate_me/install/install_generator.rb +9 -3
  22. data/lib/generators/token_authenticate_me/models/models_generator.rb +1 -59
  23. data/lib/generators/token_authenticate_me/policies/policies_generator.rb +15 -0
  24. data/lib/generators/token_authenticate_me/policies/templates/invite_policy.rb +31 -0
  25. data/lib/generators/token_authenticate_me/policies/templates/user_policy.rb +23 -0
  26. data/lib/tasks/token_authenticate_me_tasks.rake +4 -0
  27. data/lib/token_authenticate_me.rb +11 -0
  28. data/lib/token_authenticate_me/concerns/controllers/invitable.rb +58 -0
  29. data/lib/token_authenticate_me/concerns/controllers/password_resetable.rb +97 -0
  30. data/lib/token_authenticate_me/concerns/controllers/sessionable.rb +55 -0
  31. data/lib/token_authenticate_me/concerns/controllers/token_authenticateable.rb +45 -0
  32. data/lib/token_authenticate_me/concerns/models/authenticatable.rb +102 -0
  33. data/lib/token_authenticate_me/concerns/models/invitable.rb +20 -0
  34. data/lib/token_authenticate_me/concerns/models/sessionable.rb +44 -0
  35. data/lib/token_authenticate_me/configuration.rb +16 -0
  36. data/lib/token_authenticate_me/engine.rb +2 -2
  37. data/lib/token_authenticate_me/models.rb +4 -0
  38. data/lib/token_authenticate_me/version.rb +1 -1
  39. data/test/dummy/README.rdoc +28 -0
  40. data/test/dummy/Rakefile +6 -0
  41. data/test/dummy/app/assets/javascripts/application.js +13 -0
  42. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  43. data/{spec/internal → test/dummy}/app/controllers/application_controller.rb +0 -0
  44. data/test/dummy/app/helpers/application_helper.rb +2 -0
  45. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  46. data/test/dummy/bin/bundle +3 -0
  47. data/test/dummy/bin/rails +4 -0
  48. data/test/dummy/bin/rake +4 -0
  49. data/test/dummy/bin/setup +29 -0
  50. data/test/dummy/config.ru +4 -0
  51. data/test/dummy/config/application.rb +25 -0
  52. data/test/dummy/config/boot.rb +5 -0
  53. data/test/dummy/config/database.yml +25 -0
  54. data/test/dummy/config/environment.rb +5 -0
  55. data/test/dummy/config/environments/development.rb +41 -0
  56. data/test/dummy/config/environments/production.rb +79 -0
  57. data/test/dummy/config/environments/test.rb +42 -0
  58. data/test/dummy/config/initializers/assets.rb +11 -0
  59. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  61. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  62. data/test/dummy/config/initializers/inflections.rb +16 -0
  63. data/test/dummy/config/initializers/mime_types.rb +4 -0
  64. data/test/dummy/config/initializers/session_store.rb +3 -0
  65. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  66. data/test/dummy/config/locales/en.yml +23 -0
  67. data/test/dummy/config/routes.rb +3 -0
  68. data/test/dummy/config/secrets.yml +22 -0
  69. data/test/dummy/log/test.log +0 -0
  70. data/test/dummy/public/404.html +67 -0
  71. data/test/dummy/public/422.html +67 -0
  72. data/test/dummy/public/500.html +66 -0
  73. data/test/dummy/public/favicon.ico +0 -0
  74. data/test/fixtures/token_authenticate_me/invites.yml +11 -0
  75. data/test/fixtures/token_authenticate_me/sessions.yml +11 -0
  76. data/test/fixtures/token_authenticate_me/users.yml +11 -0
  77. data/test/integration/navigation_test.rb +7 -0
  78. data/test/models/token_authenticate_me/invite_test.rb +9 -0
  79. data/test/models/token_authenticate_me/session_test.rb +9 -0
  80. data/test/models/token_authenticate_me/user_test.rb +9 -0
  81. data/test/test_helper.rb +21 -0
  82. data/test/token_authenticate_me_test.rb +7 -0
  83. metadata +129 -160
  84. data/.editorconfig +0 -41
  85. data/.gitignore +0 -4
  86. data/.rubocop.yml +0 -8
  87. data/CHANGELOG.md +0 -2
  88. data/Gemfile +0 -7
  89. data/config.ru +0 -7
  90. data/lib/generators/token_authenticate_me/controllers/templates/password_reset.rb +0 -6
  91. data/lib/generators/token_authenticate_me/controllers/templates/sessions.rb +0 -6
  92. data/lib/generators/token_authenticate_me/controllers/templates/users.rb +0 -8
  93. data/lib/generators/token_authenticate_me/models/templates/authentication_migration.rb +0 -20
  94. data/lib/generators/token_authenticate_me/models/templates/authentication_model.rb +0 -11
  95. data/lib/generators/token_authenticate_me/models/templates/session_migration.rb +0 -17
  96. data/lib/generators/token_authenticate_me/models/templates/session_model.rb +0 -12
  97. data/lib/token_authenticate_me/controllers/password_resetable.rb +0 -95
  98. data/lib/token_authenticate_me/controllers/sessionable.rb +0 -53
  99. data/lib/token_authenticate_me/controllers/token_authenticateable.rb +0 -52
  100. data/lib/token_authenticate_me/models/authenticatable.rb +0 -93
  101. data/lib/token_authenticate_me/models/sessionable.rb +0 -36
  102. data/spec/acceptance/password_reset_api_spec.rb +0 -111
  103. data/spec/acceptance/session_api_spec.rb +0 -95
  104. data/spec/acceptance/users_api_spec.rb +0 -70
  105. data/spec/internal/app/controllers/password_resets_controller.rb +0 -5
  106. data/spec/internal/app/controllers/sessions_controller.rb +0 -5
  107. data/spec/internal/app/controllers/users_controller.rb +0 -7
  108. data/spec/internal/app/models/session.rb +0 -11
  109. data/spec/internal/app/models/user.rb +0 -11
  110. data/spec/internal/app/policies/user_policy.rb +0 -29
  111. data/spec/internal/app/serializers/user_serializer.rb +0 -3
  112. data/spec/internal/config/database.yml +0 -3
  113. data/spec/internal/config/routes.rb +0 -13
  114. data/spec/internal/db/fixtures/users.rb +0 -11
  115. data/spec/internal/db/schema.rb +0 -19
  116. data/spec/spec_helper.rb +0 -38
  117. data/token_authenticate_me.gemspec +0 -32
@@ -0,0 +1,15 @@
1
+ module TokenAuthenticateMe
2
+ module Generators
3
+ class PoliciesGenerator < ::Rails::Generators::Base
4
+ source_root File.expand_path('../templates', __FILE__)
5
+
6
+ def create_invite_poliocy
7
+ copy_file 'invite_policy.rb', 'app/policies/token_authenticate_me/invite_policy.rb'
8
+ end
9
+
10
+ def create_user_poliocy
11
+ copy_file 'user_policy.rb', 'app/policies/token_authenticate_me/user_policy.rb'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,31 @@
1
+ module TokenAuthenticateMe
2
+ class InvitePolicy < ApplicationPolicy
3
+ def create?
4
+ record.owner_id == current_user.id || super
5
+ end
6
+
7
+ def show?
8
+ true
9
+ end
10
+
11
+ def update?
12
+ create?
13
+ end
14
+
15
+ def destroy?
16
+ record.owner_id == current_user.id || super
17
+ end
18
+
19
+ def accept?
20
+ current_user
21
+ end
22
+
23
+ def decline?
24
+ current_user
25
+ end
26
+
27
+ def permitted_attributes
28
+ [:email, :accepted, :meta]
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ module TokenAuthenticateMe
2
+ class UserPolicy < ApplicationPolicy
3
+ def create?
4
+ true
5
+ end
6
+
7
+ def show?
8
+ true
9
+ end
10
+
11
+ def update?
12
+ record.id == current_user.id || super
13
+ end
14
+
15
+ def destroy?
16
+ record.id == current_user.id || super
17
+ end
18
+
19
+ def permitted_attributes
20
+ [:username, :email, :password, :password_confirmation]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :token_authenticate_me do
3
+ # # Task goes here
4
+ # end
@@ -1,6 +1,17 @@
1
1
  require 'token_authenticate_me/engine'
2
2
  require 'token_authenticate_me/version'
3
+ require 'token_authenticate_me/configuration'
3
4
 
4
5
  module TokenAuthenticateMe
6
+ extend self
7
+
5
8
  UUID_REGEX = /([a-f0-9]){32}/
9
+
10
+ def configure
11
+ yield configuration
12
+ end
13
+
14
+ def configuration
15
+ Configuration.instance
16
+ end
6
17
  end
@@ -0,0 +1,58 @@
1
+ require 'api_me'
2
+
3
+ module TokenAuthenticateMe
4
+ module Concerns
5
+ module Controllers
6
+ module Invitable
7
+ extend ActiveSupport::Concern
8
+
9
+ include TokenAuthenticateMe::Controllers::TokenAuthenticateable
10
+ include ApiMe
11
+
12
+ included do |_base|
13
+ skip_before_action :authenticate, only: [:show]
14
+
15
+ def create
16
+ @object = model_klass.new(object_params)
17
+ authorize @object
18
+ @object.save!(object_params)
19
+
20
+ TokenAuthenticateMeMailer.invite_email(
21
+ @objec5,
22
+ request.base_url
23
+ ).deliver_later
24
+ render status: 201, json: @object, serializer: serializer_klass
25
+ rescue ActiveRecord::RecordInvalid => e
26
+ handle_errors(e)
27
+ end
28
+
29
+ def accept
30
+ @object = model_klass.find(params[:id])
31
+
32
+ if @object.accepted.nil?
33
+ ActiveRecord::Base.transaction do
34
+ @object.accept!(current_user)
35
+ @object.update!(accepted: true)
36
+ end
37
+
38
+ render status: 204, nothing: true
39
+ else
40
+ render json: { message: 'The request has already been processed' }, status: 422
41
+ end
42
+ end
43
+
44
+ def decline
45
+ @object = model_klass.find(params[:id])
46
+
47
+ if @object.accepted.nil?
48
+ @object.update!(accepted: false)
49
+ render status: 204, nothing: true
50
+ else
51
+ render json: { message: 'The request has already been processed' }, status: 422
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,97 @@
1
+ require 'active_support/concern'
2
+
3
+ require 'token_authenticate_me/concerns/controllers/token_authenticateable'
4
+
5
+ module TokenAuthenticateMe
6
+ module Concerns
7
+ module Controllers
8
+ module PasswordResetable
9
+ extend ActiveSupport::Concern
10
+
11
+ include TokenAuthenticateMe::Concerns::Controllers::TokenAuthenticateable
12
+
13
+ included do
14
+ skip_before_action :authenticate, only: [:create, :update]
15
+ before_action :validate_reset_token, only: [:update]
16
+
17
+ # Send reset token to user with e-mail address
18
+ def create
19
+ @user = User.find_by_email(params[:email])
20
+
21
+ if @user
22
+ send_valid_reset_email(@user)
23
+ else
24
+ send_invalid_reset_email(params[:email])
25
+ end
26
+
27
+ render status: 204, nothing: true
28
+ end
29
+
30
+ # Allow user to reset password when the token is valid
31
+ # and not expired
32
+ def update
33
+ @user.update!(
34
+ password: params[:password],
35
+ password_confirmation: params[:password_confirmation],
36
+ reset_password_token: nil,
37
+ reset_password_token_exp: nil
38
+ )
39
+
40
+ render status: 204, nothing: true
41
+ rescue ActiveRecord::RecordInvalid => e
42
+ handle_errors(e)
43
+ end
44
+
45
+ private
46
+
47
+ def send_valid_reset_email(user)
48
+ user.create_reset_token!
49
+
50
+ TokenAuthenticateMeMailer.valid_user_reset_password_email(
51
+ request.base_url,
52
+ user
53
+ ).deliver
54
+ end
55
+
56
+ def send_invalid_reset_email(email)
57
+ TokenAuthenticateMeMailer.invalid_user_reset_password_email(
58
+ request.base_url,
59
+ email
60
+ ).deliver
61
+ end
62
+
63
+ def session_params
64
+ params.permit(:password, :password_confirmation)
65
+ end
66
+
67
+ def render_errors(errors, status = 422)
68
+ render(json: { errors: errors }, status: status)
69
+ end
70
+
71
+ def handle_errors(e)
72
+ render_errors(e.record.errors.messages)
73
+ end
74
+
75
+ def validate_reset_token
76
+ valid_reset_token? || render_not_found
77
+ end
78
+
79
+ def render_not_found
80
+ render status: 404, nothing: true
81
+ end
82
+
83
+ def valid_reset_token?
84
+ # Check for
85
+ # https://github.com/rails/rails/commit/e8572cf2f94872d81e7145da31d55c6e1b074247
86
+ # security issue when config.action_dispatch.perform_deep_munge = false is set
87
+ # which is common for JSON APIs
88
+ return false if params[:id].class == Array || params[:id].nil?
89
+
90
+ @user = User.find_by_reset_password_token(params[:id])
91
+ @user && @user.reset_password_token_exp > DateTime.now
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,55 @@
1
+ require 'active_support/concern'
2
+
3
+ require 'token_authenticate_me/concerns/controllers/token_authenticateable'
4
+
5
+ module TokenAuthenticateMe
6
+ module Concerns
7
+ module Controllers
8
+ module Sessionable
9
+ extend ActiveSupport::Concern
10
+
11
+ include TokenAuthenticateMe::Concerns::Controllers::TokenAuthenticateable
12
+
13
+ included do
14
+ skip_before_action :authenticate, only: [:create]
15
+ after_action :cleanup_sessions, only: [:destroy]
16
+
17
+ def create
18
+ resource = User.where('username=? OR email=?', params[:username], params[:username]).first
19
+ if resource && resource.authenticate(params[:password])
20
+ @session = Session.create(user_id: resource.id)
21
+ render json: @session, status: 201
22
+ else
23
+ render json: { message: 'Bad credentials' }, status: 401
24
+ end
25
+ end
26
+
27
+ def show
28
+ @session = authenticate_token
29
+ render json: @session
30
+ end
31
+
32
+ def destroy
33
+ authenticate_token.destroy
34
+
35
+ render status: 204, nothing: true
36
+ rescue
37
+ render_unauthorized
38
+ end
39
+
40
+ private
41
+
42
+ def session_params
43
+ params.permit(:username, :email, :password)
44
+ end
45
+
46
+ def cleanup_sessions
47
+ ApiSession.where('expiration < ?', DateTime.now).delete_all
48
+ rescue
49
+ Rails.logger.warn 'Error cleaning up old authentication sessions'
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,45 @@
1
+ require 'active_support/concern'
2
+
3
+ module TokenAuthenticateMe
4
+ module Concerns
5
+ module Controllers
6
+ module TokenAuthenticateable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ before_action :authenticate
11
+ end
12
+
13
+ protected
14
+
15
+ def authenticate
16
+ authenticate_token || render_unauthorized
17
+ end
18
+
19
+ def current_user
20
+ if authenticate_token
21
+ @current_user ||= User.find_by_id(authenticate_token.user_id)
22
+ end
23
+ end
24
+
25
+ def authenticate_token
26
+ @session ||= authenticate_with_http_token(&method(:token_handler))
27
+ end
28
+
29
+ def render_unauthorized
30
+ headers['WWW-Authenticate'] = 'Token realm="Application"'
31
+ render json: 'Bad credentials', status: 401
32
+ end
33
+
34
+ def token_handler(token, _options)
35
+ session = TokenAuthenticateMe::Session.find_by_key(token)
36
+ if session && session.expiration > DateTime.now
37
+ session
38
+ else
39
+ false
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,102 @@
1
+ require 'active_support/concern'
2
+
3
+ module TokenAuthenticateMe
4
+ module Concerns
5
+ module Models
6
+ module Authenticatable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ has_secure_password validations: false
11
+ attr_accessor :current_password
12
+
13
+ has_many :sessions
14
+ has_many :invites, inverse_of: 'creator', foreign_key: 'creator_id'
15
+
16
+ validates(
17
+ :email,
18
+ presence: true,
19
+ uniqueness: { case_sensitive: false },
20
+ format: {
21
+ with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i,
22
+ message: 'invalid e-mail address'
23
+ }
24
+ )
25
+
26
+ validates(
27
+ :username,
28
+ format: { with: /\A[a-zA-Z0-9]+\Z/ },
29
+ presence: true,
30
+ uniqueness: { case_sensitive: false }
31
+ )
32
+
33
+ validates(
34
+ :password,
35
+ presence: true,
36
+ length: { in: 8..72 },
37
+ confirmation: true,
38
+ if: :password_required?
39
+ )
40
+
41
+ validate(
42
+ :current_password_correct,
43
+ if: :current_password_required?
44
+ )
45
+
46
+ def attributes
47
+ {
48
+ 'id' => id,
49
+ 'username' => username,
50
+ 'email' => email,
51
+ 'created_at' => created_at,
52
+ 'updated_at' => updated_at
53
+ }
54
+ end
55
+
56
+ def as_json(options = nil)
57
+ { user: super(options) }
58
+ end
59
+
60
+ def create_reset_token!
61
+ # rubocop:disable Lint/Loop
62
+ begin
63
+ self.reset_password_token = SecureRandom.hex
64
+ end while self.class.exists?(reset_password_token: reset_password_token)
65
+
66
+ self.reset_password_token_exp = password_expiration_hours.hours.from_now
67
+ save!
68
+ end
69
+
70
+ def password_expiration_hours
71
+ 8
72
+ end
73
+
74
+ def password=(unencrypted_password)
75
+ super(unencrypted_password) unless unencrypted_password.blank? && !password_required?
76
+ end
77
+
78
+ def current_password_correct
79
+ errors.add(:current_password, 'is required to change email and/or password') if current_password.blank? # rubocop:disable Metrics/LineLength
80
+ errors.add(:current_password, 'is incorrect') unless authenticate(current_password)
81
+ end
82
+
83
+ def current_password_required?
84
+ !new_record? && (email_changed? || attempting_to_change_password?) && !password_resetting?
85
+ end
86
+
87
+ def password_resetting?
88
+ reset_password_token_changed? && reset_password_token_exp_changed?
89
+ end
90
+
91
+ def password_required?
92
+ attempting_to_change_password? || new_record?
93
+ end
94
+
95
+ def attempting_to_change_password?
96
+ (!password.blank? || !password_confirmation.blank?) && password_digest_changed?
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end