devise_token 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +69 -0
  4. data/Rakefile +36 -0
  5. data/app/assets/config/devise_token_manifest.js +2 -0
  6. data/app/assets/javascripts/devise_token/application.js +13 -0
  7. data/app/assets/stylesheets/devise_token/application.css +15 -0
  8. data/app/controllers/devise_token/application_controller.rb +77 -0
  9. data/app/controllers/devise_token/authentications_controller.rb +87 -0
  10. data/app/controllers/devise_token/concerns/authenticate_token.rb +71 -0
  11. data/app/controllers/devise_token/confirmations_controller.rb +18 -0
  12. data/app/controllers/devise_token/registrations_controller.rb +189 -0
  13. data/app/controllers/devise_token/token_validations_controller.rb +29 -0
  14. data/app/helpers/devise_token/application_helper.rb +4 -0
  15. data/app/jobs/devise_token/application_job.rb +4 -0
  16. data/app/mailers/devise_token/application_mailer.rb +6 -0
  17. data/app/models/devise_token/application_record.rb +5 -0
  18. data/app/models/devise_token/concerns/user.rb +98 -0
  19. data/app/models/devise_token/json_web_token.rb +80 -0
  20. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  21. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  22. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  23. data/config/locales/en.yml +50 -0
  24. data/config/routes.rb +2 -0
  25. data/lib/devise_token.rb +8 -0
  26. data/lib/devise_token/authenticable.rb +23 -0
  27. data/lib/devise_token/controllers/helpers.rb +70 -0
  28. data/lib/devise_token/controllers/url_helpers.rb +8 -0
  29. data/lib/devise_token/engine.rb +44 -0
  30. data/lib/devise_token/rails/routes.rb +66 -0
  31. data/lib/devise_token/version.rb +3 -0
  32. data/lib/generators/devise_token/install_generator.rb +160 -0
  33. data/lib/generators/devise_token/install_views_generator.rb +16 -0
  34. data/lib/generators/devise_token/templates/devise_token.rb +16 -0
  35. data/lib/generators/devise_token/templates/devise_token_create_users.rb.erb +55 -0
  36. data/lib/tasks/devise_token_tasks.rake +4 -0
  37. metadata +175 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9ad1e97c17af8e19492051f2a5c76db00e591f06
4
+ data.tar.gz: 1ee7490041ba11f1b6537f9a7ac54e1585fb1cf0
5
+ SHA512:
6
+ metadata.gz: eab33efbe9beea3f593908abd137dd3e6a769ff3844262809b33ba7baa0534fac73cb69ddf1f65da1bbe67765021525d951c762ab753601ef752bf63b2b46415
7
+ data.tar.gz: a340fdae974d327ba22725b0acf12ab95e0ec463ab7e7625e6908595ad716cb25fb5e7e442c1929a286d8bff7adcfca10ec4907276fd9cfbc9b4668e4f4bdaca
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Samuel Akrah
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,69 @@
1
+ # DeviseToken
2
+ A lightweight authentication gem based on devise and jwt gems.
3
+
4
+ ## Description
5
+ DeviseToken is a stripped down version of devise_token_auth, which removes the oauth and token implementation in devise_token_auth and replaces them with jwt for authentication.
6
+
7
+ ### What are JSON Web Tokens?
8
+
9
+ [![JWT](http://jwt.io/assets/badge.svg)](http://jwt.io/)
10
+
11
+
12
+ ## Usage
13
+
14
+ Include the `DeviseToken::Concerns::AuthenticateToken` module in your base controller, ie `ApplicationController` if it is not included by default.
15
+
16
+ ```ruby
17
+ class ApplicationController < ActionController::API
18
+ include DeviseToken::Concerns::AuthenticateToken
19
+ end
20
+ ```
21
+ This will help you scope your resources by calling `authenticate_user!` as a before_action
22
+ inside your controllers:
23
+ ```ruby
24
+ class TasksController < ApplicationController
25
+ before_action :authenticate_user!
26
+
27
+ end
28
+ ```
29
+
30
+ This will make available available methods such us `current_user` in your controller actions.
31
+
32
+ ## Installation
33
+ Add this line to your application's Gemfile:
34
+
35
+ ```ruby
36
+ gem 'devise_token'
37
+ ```
38
+
39
+ And then execute:
40
+ ```bash
41
+ $ bundle
42
+ ```
43
+
44
+ Or install it yourself as:
45
+ ```bash
46
+ $ gem install devise_token
47
+ ```
48
+
49
+ Finally, run the install generator:
50
+
51
+ ```bash
52
+ $ rails g devise_token:install User auth
53
+
54
+ This generator accepts the following optional arguments:
55
+
56
+ | Argument | Default | Description |
57
+ |---|---|---|
58
+ | USER_CLASS | `User` | The name of the class to use for user authentication. |
59
+ | MOUNT_PATH | `auth` | The path at which to mount the authentication routes.
60
+
61
+ This will create the following:
62
+
63
+ * An initializer will be created at `config/initializers/devise_token.rb`
64
+
65
+ * A model will be created in the `app/models` directory. If the model already exists, a concern will be included at the top of the file.
66
+ ## Contributing
67
+ Contribution directions go here.
68
+
69
+ ## License
@@ -0,0 +1,36 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'DeviseToken'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ require 'bundler/gem_tasks'
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'test'
31
+ t.pattern = 'test/**/*_test.rb'
32
+ t.verbose = false
33
+ end
34
+
35
+
36
+ task default: :test
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/devise_token .js
2
+ //= link_directory ../stylesheets/devise_token .css
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,77 @@
1
+ module DeviseToken
2
+ class ApplicationController < DeviseController
3
+ include ::DeviseToken::Concerns::AuthenticateToken
4
+
5
+ before_action :set_default_format
6
+
7
+
8
+ def resource_data(opts={})
9
+ response_data = opts[:resource_json] || @resource.as_json
10
+ response_data
11
+ end
12
+
13
+ def resource_errors
14
+ return @resource.errors.to_hash.merge(full_messages: @resource.errors.full_messages)
15
+ end
16
+
17
+ protected
18
+
19
+
20
+ def params_for_resource(resource)
21
+ devise_parameter_sanitizer.instance_values['permitted'][resource].each do |type|
22
+ params[type.to_s] ||= request.headers[type.to_s] unless request.headers[type.to_s].nil?
23
+ end
24
+ devise_parameter_sanitizer.instance_values['permitted'][resource]
25
+ end
26
+
27
+ def resource_class(m=nil)
28
+ if m
29
+ mapping = Devise.mappings[m]
30
+ else
31
+ mapping = Devise.mappings[resource_name] || Devise.mappings.values.first
32
+ end
33
+
34
+ mapping.to
35
+ end
36
+
37
+ def recoverable_enabled?
38
+ resource_class.devise_modules.include?(:recoverable)
39
+ end
40
+
41
+ def confirmable_enabled?
42
+ resource_class.devise_modules.include?(:confirmable)
43
+ end
44
+
45
+ def render_error(status, message, data = nil)
46
+ response = {
47
+ success: false,
48
+ errors: [message]
49
+ }
50
+ response = response.merge(data) if data
51
+ render json: response, status: status
52
+ end
53
+
54
+
55
+ def set_default_format
56
+ request.format = :json
57
+ end
58
+
59
+ def auth_token
60
+ if resource.respond_to? :auth_response_token
61
+ DeviseToken::JsonWebToken.new payload: resource.auth_response_token
62
+ else
63
+ DeviseToken::JsonWebToken.new payload: { sub: resource.id }
64
+ end
65
+ end
66
+
67
+ def resource
68
+ @resource ||=
69
+ if resource_class.respond_to? :auth_request_payload
70
+ resource_class.auth_request_payload request
71
+ else
72
+ resource_class.find_by email: resource_params[:email]
73
+ end
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,87 @@
1
+ module DeviseToken
2
+ class AuthenticationsController < DeviseToken::ApplicationController
3
+
4
+ def new
5
+ render_new_error
6
+ end
7
+
8
+ def create
9
+ # Check
10
+ field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
11
+
12
+ @resource = nil
13
+ if field
14
+ q_value = get_case_insensitive_field_from_resource_params(field)
15
+
16
+ @resource = find_resource(field, q_value)
17
+ end
18
+
19
+ if @resource && valid_params?(field, q_value) && (!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
20
+ valid_password = @resource.valid_password?(resource_params[:password])
21
+ if (@resource.respond_to?(:valid_for_authentication?) && !@resource.valid_for_authentication? { valid_password }) || !valid_password
22
+ render_create_error_bad_credentials
23
+ return
24
+ end
25
+
26
+ @resource.save
27
+
28
+ sign_in(:user, @resource, store: false, bypass: false)
29
+
30
+ yield @resource if block_given?
31
+
32
+ render_create_success
33
+
34
+ elsif @resource && !(!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
35
+ render_create_error_not_confirmed
36
+ else
37
+ render_create_error_bad_credentials
38
+ end
39
+ end
40
+
41
+
42
+
43
+ protected
44
+
45
+ def valid_params?(key, val)
46
+ resource_params[:password] && key && val
47
+ end
48
+
49
+
50
+ def render_new_error
51
+ render_error(405, I18n.t("devise_token.sessions.not_supported"))
52
+ end
53
+
54
+ def render_create_success
55
+ render json: {
56
+ status: 'success',
57
+ header: auth_token,
58
+ data: resource_data
59
+ }
60
+ end
61
+
62
+ def render_create_error_not_confirmed
63
+ render_error(401, I18n.t("devise_token.sessions.not_confirmed", email: @resource.email))
64
+ end
65
+
66
+ def render_create_error_bad_credentials
67
+ render_error(401, I18n.t("devise_token.sessions.bad_credentials"))
68
+ end
69
+
70
+ def render_destroy_success
71
+ render json: {
72
+ success:true
73
+ }, status: 200
74
+ end
75
+
76
+ def render_destroy_error
77
+ render_error(404, I18n.t("devise_token.sessions.user_not_found"))
78
+ end
79
+
80
+ private
81
+
82
+ def resource_params
83
+ params.permit(*params_for_resource(:sign_in))
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,71 @@
1
+ module DeviseToken::Concerns::AuthenticateToken
2
+ extend ActiveSupport::Concern
3
+ include DeviseToken::Controllers::Helpers
4
+ include DeviseToken::Authenticable
5
+
6
+
7
+
8
+ def get_case_insensitive_field_from_resource_params(field)
9
+ # honor Devise configuration for case_insensitive keys
10
+ q_value = resource_params[field.to_sym]
11
+
12
+ if resource_class.case_insensitive_keys.include?(field.to_sym)
13
+ q_value.downcase!
14
+ end
15
+ q_value
16
+ end
17
+
18
+ def find_resource(field, value)
19
+ # fix for mysql default case insensitivity
20
+ q = "#{field.to_s} = ? AND provider='#{provider.to_s}'"
21
+ if ActiveRecord::Base.connection.adapter_name.downcase.starts_with? 'mysql'
22
+ q = "BINARY " + q
23
+ end
24
+
25
+ @resource = resource_class.where(q, value).first
26
+ end
27
+
28
+ def resource_class(m=nil)
29
+ if m
30
+ mapping = Devise.mappings[m]
31
+ else
32
+ mapping = Devise.mappings[resource_name] || Devise.mappings.values.first
33
+ end
34
+
35
+ mapping.to
36
+ end
37
+
38
+ def provider
39
+ 'email'
40
+ end
41
+
42
+
43
+ protected
44
+
45
+
46
+ # user auth
47
+ def authenticate_token(mapping=nil)
48
+ # determine target authentication class
49
+ rc = resource_class(mapping)
50
+ @token = token
51
+
52
+ return false unless @token
53
+ # no default user defined
54
+ return unless rc
55
+ @resource = authenticate_auth(rc)
56
+ # user has already been found and authenticated
57
+ return @resource if @resource && @resource.is_a?(rc)
58
+
59
+ end
60
+
61
+ def resource_class(m=nil)
62
+ if m
63
+ mapping = Devise.mappings[m]
64
+ else
65
+ mapping = Devise.mappings[resource_name] || Devise.mappings.values.first
66
+ end
67
+
68
+ mapping.to
69
+ end
70
+
71
+ end
@@ -0,0 +1,18 @@
1
+ module DeviseToken
2
+ class ConfirmationsController < DeviseToken::ApplicationController
3
+ def show
4
+ @resource = resource_class.confirm_by_token(params[:confirmation_token])
5
+
6
+ if @resource && @resource.id
7
+
8
+ sign_in(@resource)
9
+ @resource.save!
10
+
11
+ yield @resource if block_given?
12
+
13
+ else
14
+ raise ActionController::RoutingError.new('Not Found')
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,189 @@
1
+ module DeviseToken
2
+ class RegistrationsController < DeviseToken::ApplicationController
3
+ before_action :validate_sign_up_params, :only => :create
4
+ before_action :validate_account_update_params, :only => :update
5
+
6
+ def create
7
+ @resource = resource_class.new(sign_up_params.except(:confirm_success_url))
8
+ @resource.provider = provider
9
+
10
+
11
+ if resource_class.case_insensitive_keys.include?(:email)
12
+ @resource.email = sign_up_params[:email].try :downcase
13
+ else
14
+ @resource.email = sign_up_params[:email]
15
+ end
16
+
17
+
18
+ @redirect_url = sign_up_params[:confirm_success_url]
19
+
20
+
21
+ @redirect_url ||= DeviseToken.default_confirm_success_url
22
+
23
+
24
+ if confirmable_enabled? && !@redirect_url
25
+ return render_create_error_missing_confirm_success_url
26
+ end
27
+
28
+
29
+ if DeviseToken.redirect_whitelist
30
+ unless DeviseToken::Url.whitelisted?(@redirect_url)
31
+ return render_create_error_redirect_url_not_allowed
32
+ end
33
+ end
34
+
35
+ begin
36
+ # override email confirmation, must be sent manually from ctrl
37
+ resource_class.set_callback("create", :after, :send_on_create_confirmation_instructions)
38
+ resource_class.skip_callback("create", :after, :send_on_create_confirmation_instructions)
39
+ if @resource.respond_to? :skip_confirmation_notification!
40
+ # Fix duplicate e-mails by disabling Devise confirmation e-mail
41
+ @resource.skip_confirmation_notification!
42
+ end
43
+ if @resource.save
44
+ yield @resource if block_given?
45
+
46
+ unless @resource.confirmed?
47
+ # user will require email authentication
48
+ @resource.send_confirmation_instructions({
49
+ redirect_url: @redirect_url
50
+ })
51
+
52
+ else
53
+
54
+ @resource.save!
55
+
56
+
57
+ end
58
+ render_create_success
59
+ else
60
+ clean_up_passwords @resource
61
+ render_create_error
62
+ end
63
+ rescue ActiveRecord::RecordNotUnique
64
+ clean_up_passwords @resource
65
+ render_create_error_email_already_exists
66
+ end
67
+ end
68
+
69
+
70
+ def destroy
71
+ if @resource
72
+ @resource.destroy
73
+ yield @resource if block_given?
74
+
75
+ render_destroy_success
76
+ else
77
+ render_destroy_error
78
+ end
79
+ end
80
+
81
+ def sign_up_params
82
+ params.permit([*params_for_resource(:sign_up), :confirm_success_url])
83
+ end
84
+
85
+ def account_update_params
86
+ params.permit(*params_for_resource(:account_update))
87
+ end
88
+
89
+ protected
90
+
91
+ def render_create_error_missing_confirm_success_url
92
+ response = {
93
+ status: 'error',
94
+ data: resource_data
95
+ }
96
+ message = I18n.t("devise_token_auth.registrations.missing_confirm_success_url")
97
+ render_error(422, message, response)
98
+ end
99
+
100
+ def render_create_error_redirect_url_not_allowed
101
+ response = {
102
+ status: 'error',
103
+ data: resource_data
104
+ }
105
+ message = I18n.t("devise_token_auth.registrations.redirect_url_not_allowed", redirect_url: @redirect_url)
106
+ render_error(422, message, response)
107
+ end
108
+
109
+ def render_create_success
110
+ render json: {
111
+ status: 'success',
112
+ header: auth_token,
113
+ data: resource_data
114
+ }
115
+ end
116
+
117
+ def render_create_error
118
+ render json: {
119
+ status: 'error',
120
+ data: resource_data,
121
+ errors: resource_errors
122
+ }, status: 422
123
+ end
124
+
125
+ def render_create_error_email_already_exists
126
+ response = {
127
+ status: 'error',
128
+ data: resource_data
129
+ }
130
+ message = I18n.t("devise_token_auth.registrations.email_already_exists", email: @resource.email)
131
+ render_error(422, message, response)
132
+ end
133
+
134
+ def render_update_success
135
+ render json: {
136
+ status: 'success',
137
+ data: resource_data
138
+ }
139
+ end
140
+
141
+ def render_update_error
142
+ render json: {
143
+ status: 'error',
144
+ errors: resource_errors
145
+ }, status: 422
146
+ end
147
+
148
+ def render_update_error_user_not_found
149
+ render_error(404, I18n.t("devise_token_auth.registrations.user_not_found"), { status: 'error' })
150
+ end
151
+
152
+ def render_destroy_success
153
+ render json: {
154
+ status: 'success',
155
+ message: I18n.t("devise_token_auth.registrations.account_with_uid_destroyed", uid: @resource.uid)
156
+ }
157
+ end
158
+
159
+ def render_destroy_error
160
+ render_error(404, I18n.t("devise_token_auth.registrations.account_to_destroy_not_found"), { status: 'error' })
161
+ end
162
+
163
+ private
164
+
165
+ def resource_update_method
166
+ if DeviseToken.check_current_password_before_update == :attributes
167
+ "update_with_password"
168
+ elsif DeviseToken.check_current_password_before_update == :password && account_update_params.has_key?(:password)
169
+ "update_with_password"
170
+ elsif account_update_params.has_key?(:current_password)
171
+ "update_with_password"
172
+ else
173
+ "update_attributes"
174
+ end
175
+ end
176
+
177
+ def validate_sign_up_params
178
+ validate_post_data sign_up_params, I18n.t("errors.messages.validate_sign_up_params")
179
+ end
180
+
181
+ def validate_account_update_params
182
+ validate_post_data account_update_params, I18n.t("errors.messages.validate_account_update_params")
183
+ end
184
+
185
+ def validate_post_data which, message
186
+ render_error(:unprocessable_entity, message, { status: 'error' }) if which.empty?
187
+ end
188
+ end
189
+ end