token_authenticate_me 0.4.3 → 0.5.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 (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