cm-devise_token_auth 0.1.30.1

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 +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