user_plane 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +30 -0
  4. data/app/assets/javascripts/user_plane/application.js +13 -0
  5. data/app/assets/stylesheets/user_plane/application.css +15 -0
  6. data/app/concerns/null_object_persistable.rb +62 -0
  7. data/app/controllers/user/confirm_email_addresses_controller.rb +17 -0
  8. data/app/controllers/user/details_controller.rb +48 -0
  9. data/app/controllers/user/invites_controller.rb +69 -0
  10. data/app/controllers/user/reset_passwords_controller.rb +50 -0
  11. data/app/controllers/user/sign_ins_controller.rb +53 -0
  12. data/app/controllers/user/sign_ups_controller.rb +47 -0
  13. data/app/controllers/user_plane/application_controller.rb +5 -0
  14. data/app/helpers/user_plane/application_helper.rb +4 -0
  15. data/app/mailers/user_plane/application_mailer.rb +8 -0
  16. data/app/mailers/user_plane/invite_mailer.rb +14 -0
  17. data/app/mailers/user_plane/verification_mailer.rb +25 -0
  18. data/app/models/session_manager.rb +100 -0
  19. data/app/models/user.rb +5 -0
  20. data/app/models/user/account.rb +26 -0
  21. data/app/models/user/confirm_email_address.rb +55 -0
  22. data/app/models/user/guest.rb +18 -0
  23. data/app/models/user/identities.rb +5 -0
  24. data/app/models/user/identities/email.rb +98 -0
  25. data/app/models/user/identities/email_verification.rb +70 -0
  26. data/app/models/user/identities/facebook.rb +5 -0
  27. data/app/models/user/identities/github.rb +5 -0
  28. data/app/models/user/identities/id_token.rb +7 -0
  29. data/app/models/user/identities/o_auth.rb +67 -0
  30. data/app/models/user/identities/o_auth_endpoint.rb +28 -0
  31. data/app/models/user/identities/twitter.rb +5 -0
  32. data/app/models/user/identity.rb +26 -0
  33. data/app/models/user/reset_password.rb +59 -0
  34. data/app/models/user/send_password_reset.rb +25 -0
  35. data/app/models/user/send_sign_up_invite.rb +27 -0
  36. data/app/models/user/sign_in.rb +42 -0
  37. data/app/models/user/sign_up.rb +47 -0
  38. data/app/models/user/sign_up_invites.rb +5 -0
  39. data/app/models/user/sign_up_invites/invite.rb +46 -0
  40. data/app/models/user/sign_up_invites/stack.rb +22 -0
  41. data/app/models/user/sign_up_with_invite.rb +45 -0
  42. data/app/models/user/suspension.rb +7 -0
  43. data/app/models/user/update_details.rb +103 -0
  44. data/app/views/layouts/user_plane/application.html.erb +14 -0
  45. data/app/views/user/details/edit.html.erb +37 -0
  46. data/app/views/user/invites/edit.html.erb +34 -0
  47. data/app/views/user/invites/new.html.erb +21 -0
  48. data/app/views/user/reset_passwords/edit.html.erb +26 -0
  49. data/app/views/user/reset_passwords/new.html.erb +21 -0
  50. data/app/views/user/sign_ins/new.html.erb +25 -0
  51. data/app/views/user/sign_ups/new.html.erb +33 -0
  52. data/app/views/user_plane/invite_mailer/invite.html.erb +2 -0
  53. data/app/views/user_plane/invite_mailer/invite.text.erb +3 -0
  54. data/app/views/user_plane/verification_mailer/address_verification.html.erb +2 -0
  55. data/app/views/user_plane/verification_mailer/address_verification.text.erb +4 -0
  56. data/app/views/user_plane/verification_mailer/password_reset.html.erb +2 -0
  57. data/app/views/user_plane/verification_mailer/password_reset.text.erb +4 -0
  58. data/config/initializers/inflections.rb +3 -0
  59. data/config/locales/en.yml +63 -0
  60. data/config/locales/it.yml +62 -0
  61. data/config/routes.rb +5 -0
  62. data/db/migrate/20121128143404_create_user_accounts.rb +11 -0
  63. data/db/migrate/20121226202553_create_user_identities_o_auths.rb +12 -0
  64. data/db/migrate/20121226203032_create_user_identities_emails.rb +13 -0
  65. data/db/migrate/20121227144617_create_user_identities_email_verifications.rb +15 -0
  66. data/db/migrate/20130113120152_create_user_identities_id_tokens.rb +12 -0
  67. data/db/migrate/20141025230304_create_user_sign_up_invites_stacks.rb +10 -0
  68. data/db/migrate/20141025230500_create_user_sign_up_invites_invites.rb +13 -0
  69. data/db/migrate/20141026230208_create_user_suspensions.rb +13 -0
  70. data/lib/generators/user_plane/view/details_generator.rb +22 -0
  71. data/lib/generators/user_plane/view/helpers.rb +68 -0
  72. data/lib/generators/user_plane/view/invites_generator.rb +23 -0
  73. data/lib/generators/user_plane/view/reset_passwords_generator.rb +22 -0
  74. data/lib/generators/user_plane/view/sign_ins_generator.rb +18 -0
  75. data/lib/generators/user_plane/view/sign_ups_generator.rb +22 -0
  76. data/lib/generators/user_plane/views_generator.rb +32 -0
  77. data/lib/tasks/user_plane_tasks.rake +4 -0
  78. data/lib/user_plane.rb +43 -0
  79. data/lib/user_plane/command.rb +24 -0
  80. data/lib/user_plane/engine.rb +27 -0
  81. data/lib/user_plane/fresh_validator.rb +9 -0
  82. data/lib/user_plane/omniauth.rb +50 -0
  83. data/lib/user_plane/redirect_to_sign_in.rb +22 -0
  84. data/lib/user_plane/route_concerns.rb +167 -0
  85. data/lib/user_plane/session_manager_concern.rb +9 -0
  86. data/lib/user_plane/signed_in_constraint.rb +11 -0
  87. data/lib/user_plane/token_segment.rb +52 -0
  88. data/lib/user_plane/version.rb +3 -0
  89. data/spec/controllers/user/confirm_email_addresses_controller_spec.rb +5 -0
  90. data/spec/controllers/user/details_controller_spec.rb +5 -0
  91. data/spec/controllers/user/invites_controller_spec.rb +19 -0
  92. data/spec/controllers/user/reset_passwords_controller_spec.rb +5 -0
  93. data/spec/controllers/user/sign_ins_controller_spec.rb +34 -0
  94. data/spec/controllers/user/sign_ups_controller_spec.rb +5 -0
  95. data/spec/dummy/README.rdoc +28 -0
  96. data/spec/dummy/Rakefile +6 -0
  97. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  98. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  99. data/spec/dummy/app/controllers/application_controller.rb +7 -0
  100. data/spec/dummy/app/controllers/welcome_controller.rb +3 -0
  101. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  102. data/spec/dummy/app/views/layouts/application.html.erb +30 -0
  103. data/spec/dummy/app/views/welcome/index.html.erb +1 -0
  104. data/spec/dummy/bin/bundle +3 -0
  105. data/spec/dummy/bin/rails +4 -0
  106. data/spec/dummy/bin/rake +4 -0
  107. data/spec/dummy/config.ru +4 -0
  108. data/spec/dummy/config/application.rb +30 -0
  109. data/spec/dummy/config/boot.rb +5 -0
  110. data/spec/dummy/config/database.yml +49 -0
  111. data/spec/dummy/config/environment.rb +5 -0
  112. data/spec/dummy/config/environments/development.rb +37 -0
  113. data/spec/dummy/config/environments/production.rb +78 -0
  114. data/spec/dummy/config/environments/test.rb +39 -0
  115. data/spec/dummy/config/initializers/assets.rb +8 -0
  116. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  117. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  118. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  119. data/spec/dummy/config/initializers/inflections.rb +16 -0
  120. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  121. data/spec/dummy/config/initializers/session_store.rb +3 -0
  122. data/spec/dummy/config/initializers/user_plane.rb +5 -0
  123. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  124. data/spec/dummy/config/locales/en.yml +23 -0
  125. data/spec/dummy/config/routes.rb +43 -0
  126. data/spec/dummy/config/secrets.yml +22 -0
  127. data/spec/dummy/db/schema.rb +101 -0
  128. data/spec/dummy/log/development.log +0 -0
  129. data/spec/dummy/log/test.log +20185 -0
  130. data/spec/dummy/public/404.html +67 -0
  131. data/spec/dummy/public/422.html +67 -0
  132. data/spec/dummy/public/500.html +66 -0
  133. data/spec/dummy/public/favicon.ico +0 -0
  134. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/-pOuxJZhYk_qXqMNKgm23KfvzyUW71NynNLlcNBOubE.cache +0 -0
  135. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/3AV9ywHBH56Leqey5LeznxK9vu4HD8fF3zSTk4MiDJA.cache +1 -0
  136. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/5fzR1G0D8ukHkPkLXsUu6rP6qV82aIdx3hugKkDy6nM.cache +0 -0
  137. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/9bKtJ2lkHPqtboGfbyknZ1OyH4xYO-aml7U3qhv-3kk.cache +0 -0
  138. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/HjDmE9SFP2wimdNHU8Nff9cm3vFZ5soO1iw7Jdlb6z8.cache +0 -0
  139. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/J9j6OdatarYW7VzVCVttmGphOhJKL0QXasdheyrgsTE.cache +2 -0
  140. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/KqrOQSlg0Th0N3XXx-h4p5BVJCfN0D8rRLoA9VxvXrc.cache +1 -0
  141. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/UCB1W65KwVU8ttOY8jnPRDp8HyyYYEjeTwwPD6R4qy8.cache +1 -0
  142. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/YmMSaaBmIcNZWPVF9jXcGBi-kwEzMuxzwPT_Zrcj1Bo.cache +2 -0
  143. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/g3wU8ajFWb5ZLPvujEt5l9DesbFCiAwqjx1WQgwTtHA.cache +1 -0
  144. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/i5pp88VHKoqlxQJdgmQd_lkgX1-4em_uHqNDjQ4nyHA.cache +0 -0
  145. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/jL74yXjxf8cb6Olkjbw1C28MH_HbZe221l8AI6WVeH0.cache +3 -0
  146. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/puh7X4rfS3eDN9oHTXoQdAgqxivonrwAAdYZ4UB3GIg.cache +1 -0
  147. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/qxPWFIWnE6gOCY-SsdBJe7Cgm5D3YUwaEne78Y7XdRg.cache +1 -0
  148. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/rM9s67WgzKMZ1bRhUdA0yhPZDlyRE5a1kmdt7cS6m4c.cache +3 -0
  149. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/rVoG8EHlrCOgY4ZkzOj64f0jiTcbteQ_SYNzq9RqY0I.cache +0 -0
  150. data/spec/fabricators/user_account_fabricator.rb +11 -0
  151. data/spec/fabricators/user_guest_fabricator.rb +2 -0
  152. data/spec/fabricators/user_identities_email_fabricator.rb +8 -0
  153. data/spec/fabricators/user_identities_email_verification_fabricator.rb +5 -0
  154. data/spec/fabricators/user_identities_id_token_fabricator.rb +2 -0
  155. data/spec/fabricators/user_sign_up_fabricator.rb +51 -0
  156. data/spec/fabricators/user_sign_up_invites_invite_fabricator.rb +3 -0
  157. data/spec/fabricators/user_sign_up_invites_stack_fabricator.rb +4 -0
  158. data/spec/fabricators/user_suspension_fabricator.rb +4 -0
  159. data/spec/fabricators/user_update_detail_fabricator.rb +2 -0
  160. data/spec/features/user_plane/user_plane_invites_spec.rb +31 -0
  161. data/spec/features/user_plane/user_plane_reset_passwords_spec.rb +31 -0
  162. data/spec/features/user_plane/user_plane_sign_ins_spec.rb +44 -0
  163. data/spec/features/user_plane/user_plane_signed_in_only_spec.rb +31 -0
  164. data/spec/features/user_plane/user_plane_update_details_spec.rb +43 -0
  165. data/spec/fixtures/user_plane/invite_mailer/invite +3 -0
  166. data/spec/fixtures/user_plane/verification_mailer/address_verification +3 -0
  167. data/spec/fixtures/user_plane/verification_mailer/password_reset +3 -0
  168. data/spec/lib/generators/views_generator_spec.rb +16 -0
  169. data/spec/lib/route_concerns_spec.rb +54 -0
  170. data/spec/mailers/previews/user_plane/invite_mailer_preview.rb +11 -0
  171. data/spec/mailers/previews/user_plane/verification_mailer_preview.rb +16 -0
  172. data/spec/mailers/user_plane/invite_mailer_spec.rb +25 -0
  173. data/spec/mailers/user_plane/verification_mailer_spec.rb +52 -0
  174. data/spec/models/session_manager_spec.rb +28 -0
  175. data/spec/models/user/account_spec.rb +26 -0
  176. data/spec/models/user/confirm_email_address_spec.rb +101 -0
  177. data/spec/models/user/guest_spec.rb +5 -0
  178. data/spec/models/user/identities/email_spec.rb +5 -0
  179. data/spec/models/user/identities/email_verification_spec.rb +42 -0
  180. data/spec/models/user/identities/facebook_spec.rb +5 -0
  181. data/spec/models/user/identities/github_spec.rb +5 -0
  182. data/spec/models/user/identities/id_token_spec.rb +5 -0
  183. data/spec/models/user/identities/o_auth_spec.rb +12 -0
  184. data/spec/models/user/identities/twitter_spec.rb +5 -0
  185. data/spec/models/user/reset_password_spec.rb +141 -0
  186. data/spec/models/user/send_password_reset_spec.rb +44 -0
  187. data/spec/models/user/send_sign_up_invite_spec.rb +30 -0
  188. data/spec/models/user/sign_in_spec.rb +31 -0
  189. data/spec/models/user/sign_up_invites/invite_spec.rb +13 -0
  190. data/spec/models/user/sign_up_invites/stack_spec.rb +21 -0
  191. data/spec/models/user/sign_up_spec.rb +58 -0
  192. data/spec/models/user/sign_up_with_invite_spec.rb +83 -0
  193. data/spec/models/user/suspension_spec.rb +5 -0
  194. data/spec/models/user/update_details_spec.rb +98 -0
  195. data/spec/routing/invites_spec.rb +49 -0
  196. data/spec/routing/reset_passwords_spec.rb +31 -0
  197. data/spec/routing/sign_ins_spec.rb +36 -0
  198. data/spec/routing/update_details_spec.rb +30 -0
  199. data/spec/shared_contexts/feature_helpers.rb +12 -0
  200. data/spec/shared_contexts/routing.rb +8 -0
  201. data/spec/shared_contexts/user.rb +67 -0
  202. data/spec/spec_helper.rb +38 -0
  203. data/spec/support/fabrication.rb +7 -0
  204. data/spec/support/omniauth.rb +4 -0
  205. metadata +770 -0
@@ -0,0 +1,5 @@
1
+ module User::Identities
2
+ class Facebook < OAuth
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module User::Identities
2
+ class Github < OAuth
3
+
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class User::Identities::IdToken < ActiveRecord::Base
2
+ include TokenSegment
3
+
4
+ belongs_to :identity, polymorphic: true
5
+ has_token :key
6
+
7
+ end
@@ -0,0 +1,67 @@
1
+ module User
2
+ module Identities
3
+
4
+ # The class methods have been moved into a separate module so that they
5
+ # could be added as association extensions if necessary.
6
+ module OauthBuildCreateAndFind
7
+
8
+ def find_identity sign_in
9
+ find_from_omniauth(sign_in[:oauth_data])
10
+ end
11
+
12
+ def build_identity sign_up
13
+ identity = initialize_from_omniauth(sign_up[:oauth_data])
14
+ sign_up.account.oauth_identities << identity
15
+
16
+ identity
17
+ end
18
+
19
+ def initialize_from_omniauth ominauth_data
20
+ identity_provider = provider_from_ominauth(ominauth_data)
21
+ identity_provider.new(ominauth_data: ominauth_data)
22
+ end
23
+
24
+ def find_from_omniauth ominauth_data
25
+ identity_provider = provider_from_ominauth(ominauth_data)
26
+ identity_provider.find_by(uid: ominauth_data[:uid])
27
+ end
28
+
29
+ def find_or_build_from_omniauth ominauth_data
30
+ find_from_omniauth(ominauth_data) || initialize_from_omniauth(ominauth_data)
31
+ end
32
+
33
+ private
34
+ def provider_from_ominauth ominauth_data
35
+ User::Identities.const_get(ominauth_data[:provider].camelize)
36
+ end
37
+ end
38
+
39
+ class OAuth < Identity
40
+ # Base class for omniauth-based user authentication
41
+
42
+ self.inheritance_column = :provider
43
+ include SupportSegment::StiHelpers
44
+ extend OauthBuildCreateAndFind
45
+
46
+ belongs_to :account, class_name: 'User::Account'
47
+ has_one :id_token, as: :identity
48
+
49
+ validates :uid, presence: true,
50
+ uniqueness: {scope: :provider}
51
+
52
+ def self.callback
53
+ OAuthCallback.new(self.name.underscore.to_sym)
54
+ end
55
+
56
+ before_validation do
57
+ self.build_id_token
58
+ end
59
+
60
+ def ominauth_data= new_ominauth_data
61
+ self.uid = new_ominauth_data[:uid]
62
+ self.handle = new_ominauth_data[:info][:name]
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,28 @@
1
+ module User::Identities
2
+ class OAuthEndpoint
3
+ extend ActiveModel::Naming
4
+
5
+ attr_accessor :provider
6
+
7
+ def initialize provider
8
+ @provider = provider
9
+ end
10
+
11
+ def to_param
12
+ provider
13
+ end
14
+
15
+ def to_model
16
+ self
17
+ end
18
+
19
+ def persisted?
20
+ true
21
+ end
22
+
23
+ def self.model_name
24
+ ActiveModel::Name.new(self, nil, "OAuthEndpoint")
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ module User::Identities
2
+ class Twitter < OAuth
3
+
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ module User
2
+ class Identity < ActiveRecord::Base
3
+ self.abstract_class = true
4
+
5
+ def serialize
6
+ # TODO: create the token when it's missing
7
+ id_token.key
8
+ end
9
+
10
+ def self.deserialize id_token
11
+ token = Identities::IdToken.find_by_key(id_token)
12
+ token ? token.identity : nil
13
+ end
14
+
15
+ def self.deserialize! id_token
16
+ identity = deserialize(id_token)
17
+ if identity
18
+ raise User::AccountSuspended unless identity.account.suspensions.empty?
19
+ identity
20
+ else
21
+ raise ActiveRecord::RecordNotFound
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,59 @@
1
+ module User
2
+ class ResetPassword < UserPlane::Command
3
+ include ActiveModel::Validations::Callbacks
4
+
5
+ attribute :password
6
+ attribute :password_confirmation
7
+
8
+ attr_accessor :code
9
+ attr_accessor :verification
10
+ attr_accessor :identity
11
+
12
+ validates :password, :password_confirmation, presence: true
13
+ validate {|r| r.errors.add(:code, 'is not valid') unless r.verification}
14
+ validates :verification, presence: true,
15
+ receiver: {map_attributes: {created_at: :code,
16
+ base: :code,
17
+ spent_at: :code}}
18
+ validates :identity, receiver: {map_attributes: {password: :password,
19
+ password_confirmation: :password_confirmation}}
20
+
21
+ def to_param
22
+ self.code
23
+ end
24
+
25
+ def persisted?
26
+ verification && verification.persisted?
27
+ end
28
+
29
+ def code= token
30
+ @code = token
31
+
32
+ password_reset_query = User::Identities::EmailVerification.password_reset.where(token: code)
33
+ if @identity = User::Identities::Email.joins(:verifications).merge(password_reset_query).first
34
+ @verification = identity.verifications.detect {|v| v.token == code}
35
+ else
36
+ raise ActiveRecord::RecordNotFound
37
+ end
38
+ end
39
+
40
+ def verification= verification
41
+ return unless verification
42
+ @code = verification.token
43
+ @verification = verification
44
+ @identity = verification.email
45
+ end
46
+
47
+ before_validation do
48
+ if verification
49
+ identity.attributes = {password: password,
50
+ password_confirmation: password_confirmation}
51
+ verification.spend
52
+ end
53
+ end
54
+
55
+ action do
56
+ identity.save
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,25 @@
1
+ module User
2
+ class SendPasswordReset < UserPlane::Command
3
+ attribute :email
4
+ attr_accessor :code
5
+ attr_accessor :identity
6
+ attr_accessor :verification
7
+
8
+ def email= address
9
+ @identity = User::Identities::Email.find_by_address(address)
10
+ @email = address
11
+ end
12
+
13
+ def persisited?
14
+ verification ? verification.persisted? : false
15
+ end
16
+
17
+ action do
18
+ if identity
19
+ @verification ||= identity.reset_password!
20
+ @code ||= verification.token
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ module User
2
+ class SendSignUpInvite < UserPlane::Command
3
+ include ActiveModel::Validations::Callbacks
4
+
5
+ attribute :recipient
6
+ attribute :invite
7
+ attribute :stack
8
+ attribute :sender
9
+
10
+ validates :invite, receiver: {map_attributes: {recipient: :recipient}}
11
+ validates :stack, receiver: {map_attributes: {remaining_invites: :remaining_invites}}
12
+
13
+ before_validation do
14
+ # The stack can be provided directly if other models have a stack of invites
15
+ @stack ||= sender.invites_stack
16
+ @invite ||= stack.invites.build(recipient: recipient, sender: sender)
17
+ end
18
+
19
+ def persisited?
20
+ invite ? invite.persisted? : false
21
+ end
22
+
23
+ action do
24
+ invite.save
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ module User
2
+ class SignIn < UserPlane::Command
3
+ include ActiveModel::Validations::Callbacks
4
+
5
+ model_name.instance_variable_set(:@route_key, 'user_sign_in')
6
+
7
+ attribute :email
8
+ attribute :password
9
+ attribute :oauth_data
10
+ attribute :strategy
11
+ attr_accessor :ominauth_error
12
+
13
+ attribute :identity
14
+
15
+ validates :ominauth_error, absence: true
16
+
17
+
18
+ validate do |command|
19
+ if identity = command.identity
20
+ unless identity.account.suspensions.empty?
21
+ command.errors.add(:base, :suspended)
22
+ end
23
+ elsif strategy = command.strategy
24
+ sign_in_error = :"unknown_#{strategy.model_name.singular}"
25
+ command.errors.add(:base, sign_in_error)
26
+ else
27
+ raise "Please choose a strategy to perform the sign in"
28
+ end
29
+ end
30
+
31
+ def sign_in_with strategy
32
+ @strategy = strategy
33
+ @identity = strategy.find_identity(self) if identity.nil?
34
+ self
35
+ end
36
+
37
+ action do
38
+ @identity
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,47 @@
1
+ module User
2
+ class SignUp < UserPlane::Command
3
+ include ActiveModel::Validations::Callbacks
4
+
5
+ attribute :user_name
6
+
7
+ attribute :email
8
+ attribute :password
9
+ attribute :password_confirmation
10
+
11
+ attribute :account
12
+ attribute :identity
13
+
14
+ attribute :oauth_data
15
+
16
+ # Validate command receivers
17
+ validates :identity, receiver: {map_attributes: {address: :email,
18
+ password: :password,
19
+ password_confirmation: :password_confirmation,
20
+ uid: :account}}
21
+ validates :account, receiver: {map_attributes: {name: :user_name,
22
+ identities: :email_or_omniauth}}
23
+
24
+ def sign_up_with sign_up_strategy
25
+ @identity = sign_up_strategy.build_identity(self) if identity.nil?
26
+ self
27
+ end
28
+
29
+ def account
30
+ @account ||= new_account
31
+ end
32
+
33
+ action do
34
+ ActiveRecord::Base.transaction do
35
+ raise ActiveRecord::Rollback unless account.save
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def new_account
42
+ Account.new(name: user_name)
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,5 @@
1
+ module User::SignUpInvites
2
+ def self.table_name_prefix
3
+ 'user_sign_up_invites_'
4
+ end
5
+ end
@@ -0,0 +1,46 @@
1
+ module User::SignUpInvites
2
+ class Invite < ActiveRecord::Base
3
+ include TokenSegment
4
+
5
+
6
+ belongs_to :stack
7
+ belongs_to :sender, class_name: 'User::Account'
8
+ belongs_to :account, class_name: 'User::Account'
9
+ has_token :code
10
+
11
+ validates :recipient, email: true
12
+
13
+ # Once an invite has been associated with an account it cannot be reused
14
+ validate on: :update do |r|
15
+ if account_id_changed?
16
+ previous, current = account_id_change
17
+ r.errors.add(:base, :redeemed) unless previous.nil?
18
+ end
19
+ end
20
+
21
+ # Returns a Null invite
22
+ def self.find_by_code code
23
+ find_by(code: code) || Null.new(code)
24
+ end
25
+
26
+ end
27
+
28
+ # A Null invite is an invite-like object created when the invite code is not
29
+ # valid. It will
30
+ Null = Struct.new(:code) do
31
+ include ActiveModel::Validations
32
+ include NullObjectPersistable
33
+
34
+ mimics_persistence_from Invite
35
+ validate {|r| r.errors.add(:base, :invalid) }
36
+
37
+ def method_missing method_name, *arguments
38
+ return nil if [:stack, :sender, :recipient].include? method_name
39
+ super
40
+ end
41
+
42
+ def persisted?
43
+ true
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ class User::SignUpInvites::Stack < ActiveRecord::Base
2
+ belongs_to :owner, polymorphic: true
3
+ has_many :invites, before_add: :decrement_remaining_invites
4
+ after_initialize :set_remaining_invites
5
+
6
+ # FIXME: the validation above should be enough, but doesn't seem to take effect
7
+ # validates :remaining_invites, numericality: {greater_than_or_equal: 0}
8
+ validate do |record|
9
+ record.errors.add(:remaining_invites, :greater_than_or_equal, value: 0) unless (record.remaining_invites >= 0)
10
+ end
11
+
12
+ private
13
+
14
+ def decrement_remaining_invites invite
15
+ self.remaining_invites -= 1
16
+ end
17
+
18
+ def set_remaining_invites
19
+ self.remaining_invites ||= 0
20
+ end
21
+
22
+ end
@@ -0,0 +1,45 @@
1
+ # set_callback :invite_set, :after, :set_email_from_invite
2
+ module User
3
+ class SignUpWithInvite < SignUp
4
+ attribute :invite
5
+ attr_accessor :code
6
+
7
+
8
+ define_callbacks :invite_set
9
+
10
+ validates :invite, receiver: {map_attributes: {created_at: :invite,
11
+ base: :invite,
12
+ spent: :invite}}
13
+
14
+ validate do |command|
15
+ # Enforces the need of an invite if the account does not exist
16
+ if command.account.new_record?
17
+ command.errors.add_on_blank(:invite) if command.invite.nil?
18
+ else
19
+ command.errors.add(:base, :exists)
20
+ end
21
+ end
22
+
23
+ def to_param
24
+ self.code
25
+ end
26
+
27
+ def persisted?
28
+ invite && invite.persisted?
29
+ end
30
+
31
+ def code= code
32
+ self.invite = SignUpInvites::Invite.find_by_code(code)
33
+ end
34
+
35
+ def invite= invite
36
+ @code = invite.code
37
+ run_callbacks(:invite_set) {@invite = invite}
38
+ end
39
+
40
+ before_validation do
41
+ account.invite = invite
42
+ end
43
+
44
+ end
45
+ end