user_plane 0.0.15

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 (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 UserPlane
2
+ class ApplicationController < UserPlane.parent_controller.constantize
3
+ include UserPlane::SessionManagerConcern
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module UserPlane
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,8 @@
1
+ module UserPlane
2
+ class ApplicationMailer < ActionMailer::Base
3
+ include Rails.application.routes.url_helpers
4
+
5
+ default from: UserPlane.send_emails_from
6
+
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ module UserPlane
2
+ class InviteMailer < UserPlane.parent_mailer.constantize
3
+ # Subject can be set in your I18n file at config/locales/en.yml
4
+ # with the following lookup:
5
+ #
6
+ # en.invite_mailer.invite.subject
7
+ #
8
+ def invite invite
9
+ @sign_up_with_invite = User::SignUpWithInvite.new(invite: invite)
10
+
11
+ mail to: @sign_up_with_invite.invite.recipient
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ module UserPlane
2
+ class VerificationMailer < UserPlane.parent_mailer.constantize
3
+ # Subject can be set in your I18n file at config/locales/en.yml
4
+ # with the following lookup:
5
+ #
6
+ # en.verification_mailer.address_verification.subject
7
+ #
8
+ def address_verification confirm_email_address
9
+ @confirm_email_address = confirm_email_address
10
+
11
+ mail to: confirm_email_address.verification.recipient
12
+ end
13
+
14
+ # Subject can be set in your I18n file at config/locales/en.yml
15
+ # with the following lookup:
16
+ #
17
+ # en.verification_mailer.password_reset.subject
18
+ #
19
+ def password_reset reset_password
20
+ @reset_password = reset_password
21
+
22
+ mail to: reset_password.verification.recipient
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,100 @@
1
+ class SessionManager
2
+
3
+ class << self
4
+ attr_accessor :identity_lifetime
5
+ end
6
+
7
+ # What else can go in the session
8
+ # Analytics stuff
9
+ # Location information
10
+ # some form of robot detection
11
+
12
+ @identity_lifetime = 1.hour
13
+
14
+ def initialize(request_session)
15
+ @session = request_session
16
+ refresh!
17
+ @session[:created_at] ||= Time.now
18
+ @session[:previous_page] = @session[:last_page]
19
+ end
20
+
21
+ def created_at
22
+ @session[:created_at]
23
+ end
24
+
25
+ # Stores the url in the user session to provide a redirect after the login.
26
+ def remember_page(url)
27
+ @session[:last_page] = url
28
+ end
29
+
30
+ # Provides the url to redirect to for a login.
31
+ def previous_page
32
+ @session[:previous_page]
33
+ end
34
+
35
+ def last_announcements_seen_on
36
+ @session[:last_announcements_seen_on] ||= 1
37
+ end
38
+
39
+ def last_announcements_seen!
40
+ @session[:last_announcements_seen_on] = Time.now
41
+ end
42
+
43
+ # Sets into the session the identity with which the user has logged in.
44
+ def identity=(identity)
45
+ unless identity.nil?
46
+ @session[:identity] = {'id_token' => identity.serialize,
47
+ 'expires_at' => SessionManager.identity_lifetime.from_now}
48
+ else
49
+ @session[:identity] = nil
50
+ end
51
+ @identity = identity
52
+ end
53
+
54
+ # Deletes the user login information for the session.
55
+ def sign_out
56
+ self.identity = nil
57
+ end
58
+
59
+ # Retrieves the identity from using the identity id and creation time stored in the session
60
+ def identity
61
+ serialized_identity = @session[:identity]
62
+
63
+ if serialized_identity && serialized_identity['expires_at'] > Time.now
64
+ begin
65
+ @identity ||= User::Identity.deserialize!(serialized_identity['id_token'])
66
+ rescue
67
+ nil
68
+ end
69
+ else
70
+ nil
71
+ end
72
+ end
73
+
74
+ # Retrieves the user account from the database through an identity.
75
+ def account
76
+ identity ? identity.account : User::Guest.find_or_create_from_session(@session)
77
+ end
78
+
79
+ # Returns _true_ if the user is logged in, otherwise returns _false_.
80
+ def signed_in?
81
+ identity ? true : false
82
+ end
83
+
84
+ # Sets the login expiration to one year from now.
85
+ def remember_me!
86
+ @session[:identity]['expires_at'] = 1.year.from_now
87
+ end
88
+
89
+ private
90
+
91
+ # Renews session expiration for the next 45 minutes if the session is still fresh
92
+ def refresh!
93
+ identity = @session[:identity]
94
+ if signed_in? && identity['expires_at'] < SessionManager.identity_lifetime.from_now
95
+ identity['expires_at'] = SessionManager.identity_lifetime.from_now
96
+ end
97
+ end
98
+
99
+ end
100
+
@@ -0,0 +1,5 @@
1
+ module User
2
+ def self.table_name_prefix
3
+ 'user_'
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ module User
2
+
3
+ class Account < ActiveRecord::Base
4
+
5
+ has_many :oauth_identities, class_name: 'User::Identities::OAuth',
6
+ autosave: true
7
+ has_one :email, class_name: 'User::Identities::Email',
8
+ autosave: true
9
+ has_many :suspensions, class_name: 'User::Suspension'
10
+
11
+ validates :identities, presence: true
12
+
13
+ # TODO: turn this into a configuration option?
14
+ # in the event scenario the invites are shared across a party.
15
+ has_one :invites_stack, class_name: 'User::SignUpInvites::Stack',
16
+ as: :owner
17
+ has_one :invite, class_name: 'User::SignUpInvites::Invite'
18
+
19
+ before_create :build_invites_stack
20
+
21
+ def identities
22
+ Array(email).concat(oauth_identities)
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,55 @@
1
+ module User
2
+ class ConfirmEmailAddress < UserPlane::Command
3
+ include ActiveModel::Validations::Callbacks
4
+
5
+ attr_accessor :verification
6
+ attr_accessor :identity
7
+ attr_accessor :code
8
+
9
+ validates :verification, presence: true
10
+ # TODO: the next two validations and #code= are duplicated with the
11
+ validates :verification, presence: true,
12
+ receiver: {map_attributes: {created_at: :code,
13
+ base: :code,
14
+ spent_at: :code}}
15
+ validate {|r| r.errors.add(:code, 'Is not valid') unless r.verification}
16
+ validates :identity, receiver: {map_attributes: {address: :email}}
17
+
18
+ def to_param
19
+ self.code
20
+ end
21
+
22
+ def persisted?
23
+ verification && verification.persisted?
24
+ end
25
+
26
+ # FIXME: a lot of duplication with reset_password
27
+ def code= token
28
+ @code = token
29
+
30
+ address_verification_query = User::Identities::EmailVerification.address_verification.where(token: code)
31
+ if @identity = User::Identities::Email.joins(:verifications).merge(address_verification_query).first
32
+ @verification = identity.verifications.detect {|v| v.token == code}
33
+ end
34
+ end
35
+
36
+ def verification= verification
37
+ @code = verification.token
38
+ @verification = verification
39
+ @identity = verification.email
40
+ end
41
+
42
+
43
+ before_validation do
44
+ if verification
45
+ identity.address = verification.recipient
46
+ verification.spend
47
+ end
48
+ end
49
+
50
+ action do
51
+ identity.save
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,18 @@
1
+ class User::Guest
2
+
3
+ def self.find_or_create_from_session session
4
+ self.new(session)
5
+ end
6
+
7
+ def initialize session
8
+ @session = session
9
+ end
10
+
11
+ def name
12
+ 'guest user'
13
+ end
14
+
15
+ def uid
16
+ ''
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module User::Identities
2
+ def self.table_name_prefix
3
+ 'user_identities_'
4
+ end
5
+ end
@@ -0,0 +1,98 @@
1
+ module User
2
+ module Identities
3
+ class Email < Identity
4
+ has_many :verifications, class_name: 'User::Identities::EmailVerification',
5
+ autosave: true
6
+ belongs_to :account, class_name: 'User::Account'
7
+ has_one :id_token, as: :identity
8
+ attr_accessor :address_change_verification
9
+
10
+ has_secure_password
11
+
12
+ before_validation do
13
+ self.build_id_token if (address_changed? || password_digest_changed?)
14
+ end
15
+
16
+ validates :password, length: {within: 8..56},
17
+ if: :password_digest_changed?
18
+
19
+ validates :address, presence: true,
20
+ uniqueness: true,
21
+ email: true
22
+
23
+ # TODO: move these in the relation scope?
24
+ current_address = arel_table[:address].eq(Identities::EmailVerification.arel_table[:recipient])
25
+ verified_address = Identities::EmailVerification.address_verification.spent.where(current_address)
26
+
27
+ def self.find_identity sign_in
28
+ identity = identity = find_by_address(sign_in[:email])
29
+ identity.authenticate(sign_in[:password]) if identity
30
+ end
31
+
32
+ def self.find_by_address address
33
+ where("lower(address) =?", address.downcase).first
34
+ end
35
+
36
+ def self.build_identity sign_up
37
+ identity = new(address: sign_up[:email],
38
+ password: sign_up[:password],
39
+ password_confirmation: sign_up[:password_confirmation])
40
+ sign_up.account.email = identity
41
+
42
+ identity
43
+ end
44
+
45
+ # Scope for all the email addresses that have been verified via email verifications
46
+ # or, implicitly, via password resets.
47
+ def self.verified
48
+ joins(:verifications).where(verified_address)
49
+ end
50
+
51
+ def self.unverified
52
+ joins(:verifications).where.not(verified_address)
53
+ end
54
+
55
+ def verified?
56
+ verifications.spent.where(recipient: address) ? true : false
57
+ end
58
+
59
+ # Starts the address change process by creating a new email verification token
60
+ def change_address(new_address)
61
+ @address_change_verification ||= verifications.create(type: 'AddressChangeVerification')
62
+ address_change_verification.recipient = new_address
63
+ end
64
+ alias_method :unverified_address=, :change_address
65
+
66
+ def unverified_address
67
+ @address_change_verification && @address_change_verification.recipient
68
+ end
69
+
70
+ def unverified_address_changed?
71
+ @address_change_verification ? true : false
72
+ end
73
+
74
+ # Returns a newly-created password reset token
75
+ def reset_password!
76
+ verifications.password_reset.create(recipient: address)
77
+ end
78
+
79
+ # Forces validation of the email address
80
+ def self.verify_address! token
81
+ verification = Identities::EmailVerification.address_verification.find_by(token: token)
82
+ raise ActiveRecord::RecordNotFound unless verification
83
+ verification.email.address = verification.recipient
84
+ verification.email.save!
85
+ end
86
+
87
+ end
88
+
89
+ # A Null email is an email identity-like object created when the address is not
90
+ # valid.
91
+ NullEmail = Struct.new(:address) do
92
+ def authenticate password
93
+ return nil
94
+ end
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,70 @@
1
+ module User::Identities
2
+
3
+ # An EmailVerification is a token that is sent to a specific recipient and
4
+ # it is used to perform a task while confirming email validation.
5
+ #
6
+ # Once used the token should be marked as spent, that will ensure that the
7
+ # token is fresh and hasn't been used before via validation.
8
+ #
9
+ # It is used by Identities::Email to verify an email address at signup,
10
+ # validate email address changes and reset the password.
11
+ class EmailVerification < ActiveRecord::Base
12
+ include TokenSegment
13
+ self.inheritance_column = nil
14
+
15
+ belongs_to :email
16
+
17
+ has_token :token, expires_in: 2.weeks, regenerate_on: :create do
18
+ "#{Time.now}-#{rand}-#{recipient}"
19
+ end
20
+
21
+ validates :recipient, presence: true,
22
+ email: true
23
+
24
+ validates_with UserPlane::FreshValidator, if: :spent_at_changed?
25
+ validates_each :spent_at, if: :spent_at_changed? do |record, attr, value|
26
+ record.errors.add(:spent_at, 'has already been spent') unless record.spent_at_was.nil?
27
+ end
28
+
29
+ def self.unspent
30
+ where(spent_at: nil)
31
+ end
32
+
33
+ def self.spent
34
+ where.not(spent_at: nil)
35
+ end
36
+
37
+ def self.address_verification
38
+ where(type: ['SignUpAddressVerification', 'AddressChangeVerification'])
39
+ end
40
+
41
+ def self.address_signup_verification
42
+ where(type: 'SignUpAddressVerification')
43
+ end
44
+ def self.address_change_verification
45
+ where(type: 'AddressChangeVerification')
46
+ end
47
+
48
+ def self.password_reset
49
+ where(type: 'PasswordReset')
50
+ end
51
+
52
+ def spend
53
+ self.spent_at = Time.now
54
+ end
55
+
56
+ def spend!
57
+ spend
58
+ save!
59
+ end
60
+
61
+ def unspent?
62
+ spent_at.nil? ? true : false
63
+ end
64
+
65
+ def spent?
66
+ !unspent?
67
+ end
68
+
69
+ end
70
+ end