devise_meteor 0.1.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 (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +4 -0
  4. data/CODE_OF_CONDUCT.md +13 -0
  5. data/Gemfile +16 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +78 -0
  8. data/Rakefile +7 -0
  9. data/app/models/devise_meteor/meteor_profile.rb +12 -0
  10. data/app/models/devise_meteor/meteor_service.rb +16 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +7 -0
  13. data/devise_meteor.gemspec +37 -0
  14. data/lib/devise/strategies/meteor.rb +92 -0
  15. data/lib/devise_meteor/concerns/meteor_user_model.rb +148 -0
  16. data/lib/devise_meteor/engine.rb +10 -0
  17. data/lib/devise_meteor/railtie.rb +7 -0
  18. data/lib/devise_meteor/strategies/encrypter.rb +15 -0
  19. data/lib/devise_meteor/strategies/hasher.rb +91 -0
  20. data/lib/devise_meteor/strategies/strategy.rb +92 -0
  21. data/lib/devise_meteor/version.rb +3 -0
  22. data/lib/devise_meteor.rb +37 -0
  23. data/lib/generators/devise_meteor/install_generator.rb +16 -0
  24. data/lib/generators/templates/meteor_initializer.rb +12 -0
  25. data/spec/factories/users.rb +19 -0
  26. data/spec/models/meteor_authentication_spec.rb +261 -0
  27. data/spec/rails_helper.rb +72 -0
  28. data/spec/spec_helper.rb +92 -0
  29. data/spec/support/api_macros.rb +30 -0
  30. data/spec/support/controller_macros.rb +18 -0
  31. data/spec/support/devise_macros.rb +58 -0
  32. data/spec/support/omniauth_macros.rb +44 -0
  33. data/spec/support/request_macros.rb +13 -0
  34. data/spec/test_app/README.rdoc +3 -0
  35. data/spec/test_app/Rakefile +6 -0
  36. data/spec/test_app/app/assets/javascripts/application.js +13 -0
  37. data/spec/test_app/app/assets/stylesheets/application.css +15 -0
  38. data/spec/test_app/app/controllers/application_controller.rb +5 -0
  39. data/spec/test_app/app/controllers/products_controller.rb +89 -0
  40. data/spec/test_app/app/helpers/application_helper.rb +2 -0
  41. data/spec/test_app/app/models/user.rb +43 -0
  42. data/spec/test_app/app/views/layouts/application.html.erb +14 -0
  43. data/spec/test_app/app/views/products/_form.html.erb +29 -0
  44. data/spec/test_app/app/views/products/edit.html.erb +6 -0
  45. data/spec/test_app/app/views/products/index.html.erb +33 -0
  46. data/spec/test_app/app/views/products/new.html.erb +5 -0
  47. data/spec/test_app/app/views/products/show.html.erb +25 -0
  48. data/spec/test_app/bin/bundle +3 -0
  49. data/spec/test_app/bin/rails +4 -0
  50. data/spec/test_app/bin/rake +4 -0
  51. data/spec/test_app/bin/setup +29 -0
  52. data/spec/test_app/config/application.rb +28 -0
  53. data/spec/test_app/config/boot.rb +5 -0
  54. data/spec/test_app/config/environment.rb +5 -0
  55. data/spec/test_app/config/environments/development.rb +38 -0
  56. data/spec/test_app/config/environments/production.rb +77 -0
  57. data/spec/test_app/config/environments/test.rb +45 -0
  58. data/spec/test_app/config/initializers/assets.rb +11 -0
  59. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  60. data/spec/test_app/config/initializers/cookies_serializer.rb +3 -0
  61. data/spec/test_app/config/initializers/devise.rb +268 -0
  62. data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
  63. data/spec/test_app/config/initializers/inflections.rb +16 -0
  64. data/spec/test_app/config/initializers/mime_types.rb +4 -0
  65. data/spec/test_app/config/initializers/secret_token.rb +3 -0
  66. data/spec/test_app/config/initializers/session_store.rb +3 -0
  67. data/spec/test_app/config/initializers/wrap_parameters.rb +9 -0
  68. data/spec/test_app/config/locales/en.yml +23 -0
  69. data/spec/test_app/config/mongoid.yml +80 -0
  70. data/spec/test_app/config/routes.rb +5 -0
  71. data/spec/test_app/config/secrets.yml +22 -0
  72. data/spec/test_app/config.ru +4 -0
  73. data/spec/test_app/lib/tasks/cucumber.rake +65 -0
  74. data/spec/test_app/log/test.log +23511 -0
  75. data/spec/test_app/public/404.html +67 -0
  76. data/spec/test_app/public/422.html +67 -0
  77. data/spec/test_app/public/500.html +66 -0
  78. data/spec/test_app/public/favicon.ico +0 -0
  79. metadata +350 -0
@@ -0,0 +1,10 @@
1
+ module DeviseMeteor
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace DeviseMeteor
4
+
5
+ config.generators do |g|
6
+ g.test_framework :rspec
7
+ g.fixture_replacement :factory_girl, :dir => 'spec/factories'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'rails'
2
+
3
+ module DeviseMeteor
4
+ class Railtie < Rails::Railtie
5
+
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ module DeviseMeteor
2
+ module Encrypter
3
+
4
+ def self.digest(password)
5
+ sha256_hasher = DeviseMeteor::BCryptSHA256Hasher.new
6
+ sha256_hasher.encode(password, sha256_hasher.salt)
7
+ end
8
+
9
+
10
+ def self.compare(password, encrypted_password)
11
+ sha256_hasher = DeviseMeteor::BCryptSHA256Hasher.new
12
+ sha256_hasher.verify(password, encrypted_password)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,91 @@
1
+ require "base64"
2
+ require "bcrypt"
3
+ require "openssl"
4
+
5
+ module DeviseMeteor
6
+ class Hasher
7
+ attr_reader :algorithm
8
+
9
+ # Returns salt value to be used for hashing.
10
+ #
11
+ # @return [String] random salt value.
12
+ def salt
13
+ SecureRandom.hex(9)
14
+ end
15
+
16
+ # Returns if the given password match the encoded password.
17
+ #
18
+ # @param [String] password in plain text
19
+ # @param [String] encoded password to be matched
20
+ # @return [Boolean] if password match encoded password.
21
+ def verify(password, encoded)
22
+ raise NotImplementedError
23
+ end
24
+
25
+ # Returns given password encoded with the given salt.
26
+ #
27
+ # @param [String] password in plain text
28
+ # @param [String] salt to be used during hashing
29
+ # @return [String] given password hashed using the given salt
30
+ def encode(password, salt)
31
+ raise NotImplementedError
32
+ end
33
+
34
+ # Returns if given encoded password needs to be updated.
35
+ #
36
+ # @param [String] encoded password
37
+ # @return [Boolean] if encoded password needs to be updated
38
+ def must_update(encoded)
39
+ false
40
+ end
41
+
42
+ private
43
+
44
+ def constant_time_compare(a, b)
45
+ check = a.bytesize ^ b.bytesize
46
+ a.bytes.zip(b.bytes) { |x, y| check |= x ^ y }
47
+ check == 0
48
+ end
49
+ end
50
+
51
+ # BCryptSHA256Hasher implements a BCrypt password hasher using SHA256.
52
+ class BCryptSHA256Hasher < Hasher
53
+ def initialize
54
+ @algorithm = :bcrypt_sha256
55
+ @cost = 10
56
+ @digest = OpenSSL::Digest::SHA256.new
57
+ end
58
+
59
+ def salt
60
+ BCrypt::Engine.generate_salt(@cost)
61
+ end
62
+
63
+ def get_password_string(password)
64
+ @digest.digest(password) unless @digest.nil?
65
+ end
66
+
67
+ def encode(password, salt)
68
+ password = get_password_string(password)
69
+ hash = BCrypt::Engine.hash_secret(password, salt)
70
+ return hash
71
+ end
72
+
73
+ def verify(password, encoded)
74
+ password_digest = get_password_string(password)
75
+ hash = BCrypt::Engine.hash_secret(password_digest, encoded)
76
+ Devise.secure_compare(encoded, hash)
77
+
78
+ constant_time_compare(encoded, hash)
79
+ end
80
+
81
+ end
82
+
83
+ # BCryptHasher implements a BCrypt password hasher.
84
+ class BCryptHasher < BCryptSHA256Hasher
85
+ def initialize
86
+ @algorithm = :bcrypt
87
+ @cost = 10
88
+ @digest = nil
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,92 @@
1
+ require 'devise/strategies/authenticatable'
2
+
3
+ module Devise
4
+ module Strategies
5
+ class Meteor < Authenticatable
6
+
7
+ def valid?
8
+ valid_for_params_auth? || valid_for_http_auth?
9
+ end
10
+
11
+ def authenticate!
12
+ self.password = params_auth_hash[:password]
13
+
14
+ # search for user email
15
+ devise_resource = valid_for_params_auth? && mapping.to.find_for_database_authentication(authentication_hash)
16
+ meteor_resource = User.where(:emails.elem_match => {address: params_auth_hash[:email]}).first unless devise_resource
17
+ resource = devise_resource || meteor_resource
18
+
19
+ return fail!(:not_found_in_database) unless resource
20
+
21
+ # before continuing authentication process
22
+ # check existing passwords and synchronize them
23
+ if meteor_auth_missing?(resource)
24
+ # when already devise credentials stored
25
+ # assign them to devise_meteor fields
26
+ new_hashed_password = User.new(:password => password).encrypted_password
27
+ resource.services.set(password: {bcrypt: new_hashed_password})
28
+
29
+ elsif devise_auth_missing?(resource)
30
+ # when user registered through devise_meteor
31
+ # and credentials for devise not present
32
+ email = params_auth_hash[:email]
33
+ crypt = resource.services.password[:bcrypt]
34
+
35
+ unless resource.update_attributes(encrypted_password: crypt, email: email)
36
+ fail(resource.unauthenticated_message)
37
+ end
38
+ resource.reload
39
+
40
+ elsif both_auth_present?(resource)
41
+ #when both passwords already set
42
+ else
43
+ return pass
44
+ end
45
+
46
+ # now do validation
47
+ # this code is copied from Devise::Strategies::DatabaseAuthenticable
48
+ encrypted = false
49
+ if validate(resource) { encrypted = true; resource.valid_password?(password) }
50
+ remember_me(resource)
51
+ resource.after_database_authentication
52
+ success!(resource)
53
+ end
54
+
55
+ mapping.to.new.password = password if !encrypted && Devise.paranoid
56
+ fail(:not_found_in_database) unless resource
57
+ end
58
+
59
+ private
60
+
61
+ def both_auth_present?(resource)
62
+ has_a_meteor_password?(resource) && has_a_devise_password?(resource)
63
+ end
64
+
65
+ def devise_auth_missing?(resource)
66
+ has_a_meteor_password?(resource) && !has_a_devise_password?(resource)
67
+ end
68
+
69
+ def meteor_auth_missing?(resource)
70
+ has_a_devise_password?(resource) && !has_a_meteor_password?(resource)
71
+ end
72
+
73
+ def has_a_devise_password?(resource)
74
+ resource.encrypted_password.present?
75
+ end
76
+
77
+ def has_a_meteor_password?(resource)
78
+ resource.services.password.present?
79
+ end
80
+
81
+ # if you migrate from some other strategies than bcrypt
82
+ def legacy_authenticates?(resource, password)
83
+ # resource.encrypted_password == encrypt(resource, password)
84
+ end
85
+
86
+ def encrypt(resource, password)
87
+ # some old strategies code
88
+ end
89
+
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,3 @@
1
+ module DeviseMeteor
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,37 @@
1
+ require 'devise_meteor/engine'
2
+
3
+
4
+ require "devise_meteor/version"
5
+
6
+ require 'rails'
7
+
8
+ require 'devise_meteor/railtie' if defined?(Rails)
9
+ require 'devise_meteor/railtie'
10
+
11
+ require 'devise'
12
+ require 'devise_meteor/strategies/encrypter'
13
+ require 'devise_meteor/strategies/hasher'
14
+ require 'devise/strategies/meteor'
15
+
16
+ require 'devise_meteor/concerns/meteor_user_model'
17
+
18
+ module DeviseMeteor
19
+
20
+ # enable configuration
21
+ class << self
22
+ attr_accessor :configuration
23
+ end
24
+
25
+ def self.configure
26
+ self.configuration ||= Configuration.new
27
+ yield(configuration)
28
+ end
29
+
30
+ class Configuration
31
+ attr_accessor :option
32
+
33
+ def initialize
34
+ @option = 'default_option'
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ module DeviseMeteor
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+
5
+ source_root File.expand_path("../../templates", __FILE__)
6
+
7
+ desc "Creates initializer for devise_meteor specific config"
8
+
9
+ def copy_initializer
10
+ template "meteor_initializer.rb", "config/initializers/devise_meteor.rb"
11
+
12
+ puts "Added initializer to your app."
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ DeviseMeteor.configure do |config|
2
+
3
+
4
+ end
5
+
6
+ # you can uncomment this lines to match up "strings" ObjectIds from accounts-* packages
7
+
8
+ #class BSON::ObjectId
9
+ # def self.mongoize(k)
10
+ # k.to_s
11
+ # end
12
+ #end
@@ -0,0 +1,19 @@
1
+ FactoryGirl.define do
2
+
3
+ factory :user do
4
+ #after(:build) { |u| u.password_confirmation = u.password = mypassword }
5
+ email { Faker::Internet.email }
6
+ password "password"
7
+ password_confirmation "password"
8
+
9
+ factory :confirmed_user do
10
+ confirmed_at Date.today
11
+ end
12
+ end
13
+
14
+ factory :invalid_user, parent: :user do
15
+ email { Faker::Internet.email }
16
+ password "short"
17
+ password_confirmation "short"
18
+ end
19
+ end
@@ -0,0 +1,261 @@
1
+ require 'rails_helper'
2
+
3
+ describe DeviseMeteor::MeteorUserModel do
4
+
5
+ let(:meteor_user_attributes) {
6
+
7
+ {
8
+ '_id' => "633DYLkePqv2xMbDi",
9
+ "createdAt" => Time.now,
10
+ "emails" => [
11
+ {"address" => Faker::Internet.email,
12
+ "verified" => true}
13
+ ],
14
+ "profile" => {"firstName" => "DemoFirstName",
15
+ "lastName" => "DemoLastName"
16
+ },
17
+ "services" => {
18
+ "password" => {
19
+ "bcrypt" => "$2a$10$4my6JyO3FxMDr4xm24k2Kev7Jqu0E.8KecXEv8fHhZQMSCHrQCUM."
20
+ },
21
+ "resume" => {
22
+ "loginTokens" => [
23
+ {
24
+ "when" => Time.now,
25
+ "hashedToken" => "fPtV4ud7WzT9nKwgfRb56MwI8GORe3kGc4cO6FfM8xY="
26
+ }
27
+ ]
28
+ }
29
+ },
30
+ "username" => "demouser"
31
+ }
32
+
33
+ }
34
+
35
+ describe "Meteor User attribute mapping" do
36
+
37
+ it "calls meteor_map_attributes before saving" do
38
+ user = User.new
39
+
40
+ expect(user).to receive(:meteor_map_attributes)
41
+ user.save
42
+
43
+ end
44
+
45
+ before :each do
46
+ @user = create :user
47
+ end
48
+
49
+ context 'emails' do
50
+
51
+ it 'should be an array' do
52
+ expect(@user.emails).to be_an Array
53
+ end
54
+
55
+ describe 'when changing the email' do
56
+
57
+ it 'does not verify on new unconfirmed' do
58
+ @user.confirm
59
+ @user.email = "test@test.com"
60
+ result2 = {address: @user.email, verified: false}
61
+
62
+ @user.save!
63
+ expect(@user.emails).to include result2
64
+ end
65
+
66
+ it 'does verify on confirm' do
67
+ @user.confirm
68
+ @user.email = "test@test.com"
69
+ @user.save!
70
+ result2 = {address: @user.email, verified: true}
71
+
72
+ @user.confirm
73
+
74
+ expect(@user.emails).to include result2
75
+ end
76
+
77
+ it 'does not overwrite old email' do
78
+ result1 = {address: @user.email, verified: true}
79
+
80
+ @user.confirm
81
+ @user.email = "test@test.com"
82
+ result2 = {address: @user.email, verified: false}
83
+
84
+ @user.save!
85
+ expect(@user.emails).to include result1
86
+ expect(@user.emails).to include result2
87
+ end
88
+
89
+ it 'does not have duplicated emails with different verified status' do
90
+ confirmed = {address: @user.email, verified: true}
91
+ @user.confirm
92
+
93
+ @user.email = "another@email.com"
94
+ unconfirmed = {address: @user.email, verified: false}
95
+ @user.save!
96
+ expect(@user.emails).to include confirmed
97
+ expect(@user.emails).to include unconfirmed
98
+ expect(@user.emails.size).to eql 2
99
+ end
100
+
101
+ end
102
+
103
+ describe 'verified' do
104
+
105
+ specify 'on new email should be false' do
106
+ result = {address: @user.email, verified: false}
107
+
108
+ expect(@user.emails).to include(result)
109
+ end
110
+
111
+ it 'on confirmed email should be true' do
112
+ result = {address: @user.email, verified: true}
113
+
114
+ @user.confirm
115
+
116
+ expect(@user.emails).to include(result)
117
+ end
118
+
119
+ end
120
+
121
+ end
122
+
123
+ describe 'createdAt' do
124
+ it 'should match created_at' do
125
+ expect(@user.created_at).to eql @user.createdAt
126
+ end
127
+ end
128
+
129
+ describe 'profile' do
130
+
131
+ it 'has a relation to Profile' do
132
+ expect(@user.profile).to be_a DeviseMeteor::MeteorProfile
133
+ end
134
+
135
+ describe 'name' do
136
+
137
+ it 'writes into name field' do
138
+ name = "John Doe"
139
+ @user.update_attribute(:name, name)
140
+ expect(User.last.profile.name).to eql name
141
+ end
142
+
143
+ it 'reads from name field' do
144
+ name = "John Doe"
145
+ @user.update_attribute(:name, name)
146
+ expect(@user.name).to eql @user.profile.name
147
+ end
148
+ end
149
+
150
+ describe 'username' do
151
+
152
+ it 'writes into name field' do
153
+ username = "John Doe"
154
+ @user.update_attribute(:username, username)
155
+ expect(User.last.profile.username).to eql username
156
+ end
157
+
158
+ it 'reads from name field' do
159
+ username = "John Doe"
160
+ @user.update_attribute(:username, username)
161
+ expect(@user.username).to eql @user.profile.username
162
+ end
163
+ end
164
+
165
+ end
166
+
167
+ describe 'services' do
168
+
169
+ # this example is copy pasted from the devise_meteor documentation
170
+ # http://docs.meteor.com/#/full/meteor_users
171
+ # bcrypt: password decrypted is "asdfasdf"
172
+ let(:valid_services_hash) {
173
+ {"password" => {
174
+ "bcrypt" => "$2a$10$4my6JyO3FxMDr4xm24k2Kev7Jqu0E.8KecXEv8fHhZQMSCHrQCUM."
175
+ },
176
+ "facebook" => {
177
+ "id" => "709050",
178
+ "accessToken" => "S0M3_R34LLY-CMPLX-T0K3N"
179
+ },
180
+ "resume" => {
181
+ "loginTokens" => [
182
+ {"token" => "4no7eR-c0mpl3x-c0d3-with-numb3r5",
183
+ "when" => 1234567890}
184
+ ]
185
+ }}
186
+ }
187
+
188
+ let(:decrypted_password) {
189
+ 'asdfasdf'
190
+ }
191
+
192
+ it 'must have a service relation' do
193
+ expect(@user.services).to be_a DeviseMeteor::MeteorService
194
+ end
195
+
196
+ context 'password' do
197
+ before :each do
198
+ @user.confirm
199
+ end
200
+
201
+ let(:valid_password_hash) {
202
+ valid_services_hash[:password]
203
+ }
204
+
205
+ context 'changed by devise' do
206
+ it 'writes password on creation' do
207
+ expect(User.last.services.password[:bcrypt]).to eql @user.services.password[:bcrypt]
208
+ end
209
+
210
+ it 'writes new password also to session.password' do
211
+ new_password = 'N3W_P455W0RD'
212
+
213
+ @user.password = new_password
214
+ @user.password_confirmation = new_password
215
+ @user.save
216
+
217
+ expect(User.last.services.password[:bcrypt]).to eql @user.services.password[:bcrypt]
218
+ end
219
+
220
+ it 'tries to use the meteors password' do
221
+ password = @user.services.password[:bcrypt]
222
+ true
223
+ end
224
+
225
+ end
226
+
227
+ end
228
+
229
+ context 'facebook' do
230
+
231
+ it 'allows multiple provider assignment' do
232
+ @user.services.update_attributes(facebook: valid_services_hash['facebook'])
233
+
234
+ expect(User.last.services.facebook[:id]).to eq valid_services_hash['facebook']['id']
235
+ expect(User.last.services.facebook[:id]).to eq valid_services_hash['facebook']['id']
236
+ end
237
+
238
+ it 'allows single provider assignment' do
239
+ @user.services.update_attribute(:facebook, valid_services_hash['facebook'])
240
+
241
+ expect(User.last.services.facebook[:id]).to eq valid_services_hash['facebook']['id']
242
+ expect(User.last.services.facebook[:accessToken]).to eq valid_services_hash['facebook']['accessToken']
243
+ end
244
+
245
+ it 'allows to change provider data' do
246
+ new_facebook_hash = {id: '12345678', accessToken: 'New_Access_token'}
247
+
248
+ @user.services.update_attribute(:facebook, valid_services_hash['facebook'])
249
+ expect(User.last.services.facebook[:id]).to eq valid_services_hash['facebook']['id']
250
+ expect(User.last.services.facebook[:accessToken]).to eq valid_services_hash['facebook']['accessToken']
251
+
252
+ @user.services.update_attribute(:facebook, new_facebook_hash)
253
+
254
+ expect(User.last.services.facebook[:id]).to eq new_facebook_hash[:id]
255
+ expect(User.last.services.facebook[:accessToken]).to eq new_facebook_hash[:accessToken]
256
+ end
257
+
258
+ end
259
+ end
260
+ end
261
+ end