cm-devise_token_auth 0.1.30.1

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 +7 -0
  2. data/LICENSE +13 -0
  3. data/README.md +688 -0
  4. data/Rakefile +34 -0
  5. data/app/controllers/devise_token_auth/application_controller.rb +17 -0
  6. data/app/controllers/devise_token_auth/concerns/set_user_by_token.rb +109 -0
  7. data/app/controllers/devise_token_auth/confirmations_controller.rb +31 -0
  8. data/app/controllers/devise_token_auth/omniauth_callbacks_controller.rb +171 -0
  9. data/app/controllers/devise_token_auth/passwords_controller.rb +155 -0
  10. data/app/controllers/devise_token_auth/registrations_controller.rb +123 -0
  11. data/app/controllers/devise_token_auth/sessions_controller.rb +98 -0
  12. data/app/controllers/devise_token_auth/token_validations_controller.rb +23 -0
  13. data/app/models/devise_token_auth/concerns/user.rb +231 -0
  14. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  15. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  16. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  17. data/app/views/devise_token_auth/omniauth_failure.html.erb +2 -0
  18. data/app/views/devise_token_auth/omniauth_success.html.erb +8 -0
  19. data/app/views/layouts/omniauth_response.html.erb +31 -0
  20. data/config/initializers/devise.rb +203 -0
  21. data/config/locales/devise.en.yml +59 -0
  22. data/config/routes.rb +5 -0
  23. data/lib/devise_token_auth.rb +7 -0
  24. data/lib/devise_token_auth/controllers/helpers.rb +129 -0
  25. data/lib/devise_token_auth/controllers/url_helpers.rb +8 -0
  26. data/lib/devise_token_auth/engine.rb +25 -0
  27. data/lib/devise_token_auth/rails/routes.rb +65 -0
  28. data/lib/devise_token_auth/version.rb +3 -0
  29. data/lib/generators/devise_token_auth/USAGE +31 -0
  30. data/lib/generators/devise_token_auth/install_generator.rb +115 -0
  31. data/lib/generators/devise_token_auth/install_views_generator.rb +16 -0
  32. data/lib/generators/devise_token_auth/templates/devise_token_auth.rb +22 -0
  33. data/lib/generators/devise_token_auth/templates/devise_token_auth_create_users.rb.erb +54 -0
  34. data/lib/generators/devise_token_auth/templates/user.rb +3 -0
  35. data/lib/tasks/devise_token_auth_tasks.rake +4 -0
  36. data/test/controllers/demo_group_controller_test.rb +126 -0
  37. data/test/controllers/demo_mang_controller_test.rb +263 -0
  38. data/test/controllers/demo_user_controller_test.rb +262 -0
  39. data/test/controllers/devise_token_auth/confirmations_controller_test.rb +107 -0
  40. data/test/controllers/devise_token_auth/omniauth_callbacks_controller_test.rb +167 -0
  41. data/test/controllers/devise_token_auth/passwords_controller_test.rb +287 -0
  42. data/test/controllers/devise_token_auth/registrations_controller_test.rb +458 -0
  43. data/test/controllers/devise_token_auth/sessions_controller_test.rb +221 -0
  44. data/test/controllers/overrides/confirmations_controller_test.rb +44 -0
  45. data/test/controllers/overrides/omniauth_callbacks_controller_test.rb +44 -0
  46. data/test/controllers/overrides/passwords_controller_test.rb +62 -0
  47. data/test/controllers/overrides/registrations_controller_test.rb +40 -0
  48. data/test/controllers/overrides/sessions_controller_test.rb +33 -0
  49. data/test/controllers/overrides/token_validations_controller_test.rb +38 -0
  50. data/test/dummy/README.rdoc +28 -0
  51. data/test/dummy/Rakefile +6 -0
  52. data/test/dummy/app/assets/images/logo.jpg +0 -0
  53. data/test/dummy/app/assets/images/omniauth-provider-settings.png +0 -0
  54. data/test/dummy/app/assets/javascripts/application.js +13 -0
  55. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  56. data/test/dummy/app/controllers/application_controller.rb +16 -0
  57. data/test/dummy/app/controllers/demo_group_controller.rb +13 -0
  58. data/test/dummy/app/controllers/demo_mang_controller.rb +12 -0
  59. data/test/dummy/app/controllers/demo_user_controller.rb +12 -0
  60. data/test/dummy/app/controllers/overrides/confirmations_controller.rb +32 -0
  61. data/test/dummy/app/controllers/overrides/omniauth_callbacks_controller.rb +14 -0
  62. data/test/dummy/app/controllers/overrides/passwords_controller.rb +39 -0
  63. data/test/dummy/app/controllers/overrides/registrations_controller.rb +27 -0
  64. data/test/dummy/app/controllers/overrides/sessions_controller.rb +43 -0
  65. data/test/dummy/app/controllers/overrides/token_validations_controller.rb +23 -0
  66. data/test/dummy/app/helpers/application_helper.rb +1065 -0
  67. data/test/dummy/app/models/evil_user.rb +3 -0
  68. data/test/dummy/app/models/mang.rb +3 -0
  69. data/test/dummy/app/models/user.rb +18 -0
  70. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  71. data/test/dummy/bin/bundle +3 -0
  72. data/test/dummy/bin/rails +8 -0
  73. data/test/dummy/bin/rake +8 -0
  74. data/test/dummy/bin/spring +18 -0
  75. data/test/dummy/config.ru +16 -0
  76. data/test/dummy/config/application.rb +23 -0
  77. data/test/dummy/config/application.yml.bk +0 -0
  78. data/test/dummy/config/boot.rb +5 -0
  79. data/test/dummy/config/database.yml +31 -0
  80. data/test/dummy/config/environment.rb +5 -0
  81. data/test/dummy/config/environments/development.rb +44 -0
  82. data/test/dummy/config/environments/production.rb +82 -0
  83. data/test/dummy/config/environments/test.rb +40 -0
  84. data/test/dummy/config/initializers/assets.rb +8 -0
  85. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  86. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  87. data/test/dummy/config/initializers/devise_token_auth.rb +22 -0
  88. data/test/dummy/config/initializers/figaro.rb +1 -0
  89. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  90. data/test/dummy/config/initializers/inflections.rb +16 -0
  91. data/test/dummy/config/initializers/mime_types.rb +4 -0
  92. data/test/dummy/config/initializers/omniauth.rb +8 -0
  93. data/test/dummy/config/initializers/session_store.rb +3 -0
  94. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  95. data/test/dummy/config/locales/en.yml +23 -0
  96. data/test/dummy/config/routes.rb +30 -0
  97. data/test/dummy/config/secrets.yml +22 -0
  98. data/test/dummy/config/spring.rb +1 -0
  99. data/test/dummy/db/migrate/20140715061447_devise_token_auth_create_users.rb +56 -0
  100. data/test/dummy/db/migrate/20140715061805_devise_token_auth_create_mangs.rb +56 -0
  101. data/test/dummy/db/migrate/20140829044006_add_operating_thetan_to_user.rb +6 -0
  102. data/test/dummy/db/migrate/20140916224624_add_favorite_color_to_mangs.rb +5 -0
  103. data/test/dummy/db/migrate/20140928231203_devise_token_auth_create_evil_users.rb +57 -0
  104. data/test/dummy/db/schema.rb +114 -0
  105. data/test/dummy/public/404.html +67 -0
  106. data/test/dummy/public/422.html +67 -0
  107. data/test/dummy/public/500.html +66 -0
  108. data/test/dummy/public/favicon.ico +0 -0
  109. data/test/fixtures/evil_users.yml +29 -0
  110. data/test/fixtures/mangs.yml +29 -0
  111. data/test/fixtures/users.yml +29 -0
  112. data/test/integration/navigation_test.rb +10 -0
  113. data/test/lib/generators/devise_token_auth/install_generator_test.rb +178 -0
  114. data/test/lib/generators/devise_token_auth/install_views_generator_test.rb +23 -0
  115. data/test/models/user_test.rb +90 -0
  116. data/test/test_helper.rb +60 -0
  117. metadata +310 -0
@@ -0,0 +1,123 @@
1
+ module DeviseTokenAuth
2
+ class RegistrationsController < DeviseTokenAuth::ApplicationController
3
+ before_filter :set_user_by_token, :only => [:destroy, :update]
4
+ skip_after_filter :update_auth_header, :only => [:create, :destroy]
5
+
6
+ respond_to :json
7
+
8
+ def create
9
+ @resource = resource_class.new(sign_up_params)
10
+ @resource.provider = "email"
11
+
12
+ # honor devise configuration for case_insensitive_keys
13
+ if resource_class.case_insensitive_keys.include?(:email)
14
+ @resource.email = sign_up_params[:email].downcase
15
+ else
16
+ @resource.email = sign_up_params[:email]
17
+ end
18
+
19
+ # success redirect url is required
20
+ unless params[:confirm_success_url]
21
+ return render json: {
22
+ status: 'error',
23
+ data: @resource,
24
+ errors: ["Missing `confirm_success_url` param."]
25
+ }, status: 403
26
+ end
27
+
28
+ begin
29
+ # override email confirmation, must be sent manually from ctrl
30
+ resource_class.skip_callback("create", :after, :send_on_create_confirmation_instructions)
31
+ if @resource.save
32
+
33
+ unless @resource.confirmed?
34
+ # user will require email authentication
35
+ @resource.send_confirmation_instructions({
36
+ client_config: params[:config_name],
37
+ redirect_url: params[:confirm_success_url]
38
+ })
39
+
40
+ else
41
+ # email auth has been bypassed, authenticate user
42
+ @client_id = SecureRandom.urlsafe_base64(nil, false)
43
+ @token = SecureRandom.urlsafe_base64(nil, false)
44
+
45
+ @resource.tokens[@client_id] = {
46
+ token: BCrypt::Password.create(@token),
47
+ expiry: (Time.now + DeviseTokenAuth.token_lifespan).to_i
48
+ }
49
+
50
+ @resource.save!
51
+
52
+ update_auth_header
53
+ end
54
+
55
+ render json: {
56
+ status: 'success',
57
+ data: @resource.as_json
58
+ }
59
+ else
60
+ clean_up_passwords @resource
61
+ render json: {
62
+ status: 'error',
63
+ data: @resource,
64
+ errors: @resource.errors.to_hash.merge(full_messages: @resource.errors.full_messages)
65
+ }, status: 403
66
+ end
67
+ rescue ActiveRecord::RecordNotUnique
68
+ clean_up_passwords @resource
69
+ render json: {
70
+ status: 'error',
71
+ data: @resource,
72
+ errors: ["An account already exists for #{@resource.email}"]
73
+ }, status: 403
74
+ end
75
+ end
76
+
77
+ def update
78
+ if @resource
79
+
80
+ if @resource.update_attributes(account_update_params)
81
+ render json: {
82
+ status: 'success',
83
+ data: @resource.as_json
84
+ }
85
+ else
86
+ render json: {
87
+ status: 'error',
88
+ errors: @resource.errors
89
+ }, status: 403
90
+ end
91
+ else
92
+ render json: {
93
+ status: 'error',
94
+ errors: ["User not found."]
95
+ }, status: 404
96
+ end
97
+ end
98
+
99
+ def destroy
100
+ if @resource
101
+ @resource.destroy
102
+
103
+ render json: {
104
+ status: 'success',
105
+ message: "Account with uid #{@resource.uid} has been destroyed."
106
+ }
107
+ else
108
+ render json: {
109
+ status: 'error',
110
+ errors: ["Unable to locate account for destruction."]
111
+ }, status: 404
112
+ end
113
+ end
114
+
115
+ def sign_up_params
116
+ params.permit(devise_parameter_sanitizer.for(:sign_up))
117
+ end
118
+
119
+ def account_update_params
120
+ params.permit(devise_parameter_sanitizer.for(:account_update))
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,98 @@
1
+ # see http://www.emilsoman.com/blog/2013/05/18/building-a-tested/
2
+ module DeviseTokenAuth
3
+ class SessionsController < DeviseTokenAuth::ApplicationController
4
+ I18N_ERRORS_KEY = "devise_token_auth.errors"
5
+
6
+ before_filter :set_user_by_token, :only => [:destroy]
7
+
8
+ def create
9
+ if valid_params?
10
+ # honor devise configuration for case_insensitive_keys
11
+ if resource_class.case_insensitive_keys.include?(:email)
12
+ email = resource_params[:email].downcase
13
+ else
14
+ email = resource_params[:email]
15
+ end
16
+ else
17
+ render_json_error :unauthorized, :invalid_login, default: "Invalid login credentials. Please try again."
18
+ return
19
+ end
20
+
21
+ q = "uid='#{email}' AND provider='email'"
22
+
23
+ if ActiveRecord::Base.connection.adapter_name.downcase.starts_with? 'mysql'
24
+ q = "BINARY uid='#{email}' AND provider='email'"
25
+ end
26
+
27
+ resources = resource_class.where(q)
28
+ resources = resources.active if resource_class.respond_to?(:active)
29
+ @resource = resources.first
30
+
31
+ if @resource and not (!@resource.respond_to?(:active_for_authentication?) or @resource.active_for_authentication?)
32
+ render_json_error :unauthorized, :invalid_login, default: "Invalid login credentials. Please try again."
33
+
34
+ elsif @resource and valid_params? and @resource.valid_password?(resource_params[:password]) and @resource.confirmed?
35
+ # create client id
36
+ @client_id = SecureRandom.urlsafe_base64(nil, false)
37
+ @token = SecureRandom.urlsafe_base64(nil, false)
38
+
39
+ @resource.tokens[@client_id] = {
40
+ token: BCrypt::Password.create(@token),
41
+ expiry: (Time.now + DeviseTokenAuth.token_lifespan).to_i
42
+ }
43
+ @resource.save
44
+
45
+ sign_in(:user, @resource, store: false, bypass: false)
46
+
47
+ render json: {
48
+ data: @resource.as_json(except: [
49
+ :tokens, :created_at, :updated_at
50
+ ])
51
+ }
52
+
53
+ elsif @resource and not @resource.confirmed?
54
+ default_message = "A confirmation email was sent to your account at #{@resource.email}. "+
55
+ "You must follow the instructions in the email before your account "+
56
+ "can be activated"
57
+ render_json_error :unauthorized, :confirmation_was_sent, default: default_message, email: @resource.email
58
+
59
+ else
60
+ render_json_error :unauthorized, :invalid_login, default: "Invalid login credentials. Please try again."
61
+ end
62
+ end
63
+
64
+ def destroy
65
+ # remove auth instance variables so that after_filter does not run
66
+ user = remove_instance_variable(:@resource) if @resource
67
+ client_id = remove_instance_variable(:@client_id) if @client_id
68
+ remove_instance_variable(:@token) if @token
69
+
70
+ if user and client_id and user.tokens[client_id]
71
+ user.tokens.delete(client_id)
72
+ user.save!
73
+
74
+ render json: {
75
+ success:true
76
+ }, status: 200
77
+
78
+ else
79
+ render_json_error :not_found, :user_not_found, default: "User was not found or was not logged in."
80
+ end
81
+ end
82
+
83
+ def valid_params?
84
+ resource_params[:password] && resource_params[:email]
85
+ end
86
+
87
+ def resource_params
88
+ params.permit(devise_parameter_sanitizer.for(:sign_in))
89
+ end
90
+
91
+ def render_json_error(status, error, options)
92
+ message = I18n.t("#{I18N_ERRORS_KEY}.#{error}", options)
93
+ render json: {
94
+ errors: [message]
95
+ }, status: status
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,23 @@
1
+ module DeviseTokenAuth
2
+ class TokenValidationsController < DeviseTokenAuth::ApplicationController
3
+ skip_before_filter :assert_is_devise_resource!, :only => [:validate_token]
4
+ before_filter :set_user_by_token, :only => [:validate_token]
5
+
6
+ def validate_token
7
+ # @resource will have been set by set_user_token concern
8
+ if @resource
9
+ render json: {
10
+ success: true,
11
+ data: @resource.as_json(except: [
12
+ :tokens, :created_at, :updated_at
13
+ ])
14
+ }
15
+ else
16
+ render json: {
17
+ success: false,
18
+ errors: ["Invalid login credentials"]
19
+ }, status: 401
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,231 @@
1
+ module DeviseTokenAuth::Concerns::User
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # Include default devise modules. Others available are:
6
+ # :confirmable, :lockable, :timeoutable and :omniauthable
7
+ devise :database_authenticatable, :registerable,
8
+ :recoverable, :rememberable, :trackable, :validatable,
9
+ :confirmable
10
+
11
+ serialize :tokens, JSON
12
+
13
+ validates_presence_of :email, if: Proc.new { |u| u.provider == 'email' }
14
+ validates_presence_of :uid, if: Proc.new { |u| u.provider != 'email' }
15
+
16
+ # only validate unique emails among email registration users
17
+ validate :unique_email_user, on: :create
18
+
19
+ # can't set default on text fields in mysql, simulate here instead.
20
+ after_save :set_empty_token_hash
21
+ after_initialize :set_empty_token_hash
22
+
23
+ # keep uid in sync with email
24
+ before_save :sync_uid
25
+ before_create :sync_uid
26
+
27
+ # get rid of dead tokens
28
+ before_save :destroy_expired_tokens
29
+
30
+
31
+ # don't use default devise email validation
32
+ def email_required?
33
+ false
34
+ end
35
+
36
+ def email_changed?
37
+ false
38
+ end
39
+
40
+
41
+ # override devise method to include additional info as opts hash
42
+ def send_confirmation_instructions(opts=nil)
43
+ unless @raw_confirmation_token
44
+ generate_confirmation_token!
45
+ end
46
+
47
+ opts ||= {}
48
+
49
+ # fall back to "default" config name
50
+ opts[:client_config] ||= "default"
51
+
52
+ if pending_reconfirmation?
53
+ opts[:to] = unconfirmed_email
54
+ end
55
+
56
+ send_devise_notification(:confirmation_instructions, @raw_confirmation_token, opts)
57
+ end
58
+
59
+ # override devise method to include additional info as opts hash
60
+ def send_reset_password_instructions(opts=nil)
61
+ token = set_reset_password_token
62
+
63
+ opts ||= {}
64
+
65
+ # fall back to "default" config name
66
+ opts[:client_config] ||= "default"
67
+
68
+ if pending_reconfirmation?
69
+ opts[:to] = unconfirmed_email
70
+ else
71
+ opts[:to] = email
72
+ end
73
+
74
+ send_devise_notification(:reset_password_instructions, token, opts)
75
+
76
+ token
77
+ end
78
+ end
79
+
80
+
81
+ def valid_token?(token, client_id='default')
82
+ client_id ||= 'default'
83
+
84
+ return false unless self.tokens[client_id]
85
+
86
+ return true if token_is_current?(token, client_id)
87
+ return true if token_can_be_reused?(token, client_id)
88
+
89
+ # return false if none of the above conditions are met
90
+ return false
91
+ end
92
+
93
+
94
+ # this must be done from the controller so that additional params
95
+ # can be passed on from the client
96
+ def send_confirmation_notification?
97
+ false
98
+ end
99
+
100
+
101
+ def token_is_current?(token, client_id)
102
+ return true if (
103
+ # ensure that expiry and token are set
104
+ self.tokens[client_id]['expiry'] and
105
+ self.tokens[client_id]['token'] and
106
+
107
+ # ensure that the token has not yet expired
108
+ DateTime.strptime(self.tokens[client_id]['expiry'].to_s, '%s') > Time.now and
109
+
110
+ # ensure that the token is valid
111
+ BCrypt::Password.new(self.tokens[client_id]['token']) == token
112
+ )
113
+ end
114
+
115
+
116
+ # allow batch requests to use the previous token
117
+ def token_can_be_reused?(token, client_id)
118
+ return true if (
119
+ # ensure that the last token and its creation time exist
120
+ self.tokens[client_id]['updated_at'] and
121
+ self.tokens[client_id]['last_token'] and
122
+
123
+ # ensure that previous token falls within the batch buffer throttle time of the last request
124
+ Time.parse(self.tokens[client_id]['updated_at']) > Time.now - DeviseTokenAuth.batch_request_buffer_throttle and
125
+
126
+ # ensure that the token is valid
127
+ BCrypt::Password.new(self.tokens[client_id]['last_token']) == token
128
+ )
129
+ end
130
+
131
+
132
+ # update user's auth token (should happen on each request)
133
+ def create_new_auth_token(client_id=nil)
134
+ client_id ||= SecureRandom.urlsafe_base64(nil, false)
135
+ last_token ||= nil
136
+ token = SecureRandom.urlsafe_base64(nil, false)
137
+ token_hash = BCrypt::Password.create(token)
138
+ expiry = (Time.now + DeviseTokenAuth.token_lifespan).to_i
139
+
140
+ if self.tokens[client_id] and self.tokens[client_id]['token']
141
+ last_token = self.tokens[client_id]['token']
142
+ end
143
+
144
+ self.tokens[client_id] = {
145
+ token: token_hash,
146
+ expiry: expiry,
147
+ last_token: last_token,
148
+ updated_at: Time.now
149
+ }
150
+
151
+ self.save!
152
+
153
+ return build_auth_header(token, client_id)
154
+ end
155
+
156
+
157
+ def build_auth_header(token, client_id='default')
158
+ return unless self.tokens[client_id]
159
+
160
+ # client may use expiry to prevent validation request if expired
161
+ # must be cast as string or headers will break
162
+ expiry = self.tokens[client_id]['expiry'].to_s
163
+
164
+ return {
165
+ "access-token" => token,
166
+ "token-type" => "Bearer",
167
+ "client" => client_id,
168
+ "expiry" => expiry,
169
+ "uid" => self.uid
170
+ }
171
+ end
172
+
173
+
174
+ def build_auth_url(base_url, args)
175
+ args[:uid] = self.uid
176
+ args[:expiry] = self.tokens[args[:client_id]]['expiry']
177
+
178
+ generate_url(base_url, args)
179
+ end
180
+
181
+
182
+ def extend_batch_buffer(token, client_id)
183
+ self.tokens[client_id]['updated_at'] = Time.now
184
+ self.save!
185
+
186
+ return build_auth_header(token, client_id)
187
+ end
188
+
189
+
190
+ protected
191
+
192
+
193
+ # NOTE: ensure that fragment comes AFTER querystring for proper $location
194
+ # parsing using AngularJS.
195
+ def generate_url(url, params = {})
196
+ uri = URI(url)
197
+
198
+ res = "#{uri.scheme}://#{uri.host}"
199
+ res += ":#{uri.port}" if (uri.port and uri.port != 80 and uri.port != 443)
200
+ res += "#{uri.path}" if uri.path
201
+ res += '#'
202
+ res += "#{uri.fragment}" if uri.fragment
203
+ res += "?#{params.to_query}"
204
+
205
+ return res
206
+ end
207
+
208
+
209
+ # only validate unique email among users that registered by email
210
+ def unique_email_user
211
+ if provider == 'email' and self.class.where(provider: 'email', email: email).count > 0
212
+ errors.add(:email, :already_in_use, default: "This email address is already in use")
213
+ end
214
+ end
215
+
216
+ def set_empty_token_hash
217
+ self.tokens ||= {} if has_attribute?(:tokens)
218
+ end
219
+
220
+ def sync_uid
221
+ self.uid = email if provider == 'email'
222
+ end
223
+
224
+ def destroy_expired_tokens
225
+ self.tokens.delete_if{|cid,v|
226
+ expiry = v[:expiry] || v["expiry"]
227
+ DateTime.strptime(expiry.to_s, '%s') < Time.now
228
+ }
229
+ end
230
+
231
+ end