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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +30 -0
- data/app/assets/javascripts/user_plane/application.js +13 -0
- data/app/assets/stylesheets/user_plane/application.css +15 -0
- data/app/concerns/null_object_persistable.rb +62 -0
- data/app/controllers/user/confirm_email_addresses_controller.rb +17 -0
- data/app/controllers/user/details_controller.rb +48 -0
- data/app/controllers/user/invites_controller.rb +69 -0
- data/app/controllers/user/reset_passwords_controller.rb +50 -0
- data/app/controllers/user/sign_ins_controller.rb +53 -0
- data/app/controllers/user/sign_ups_controller.rb +47 -0
- data/app/controllers/user_plane/application_controller.rb +5 -0
- data/app/helpers/user_plane/application_helper.rb +4 -0
- data/app/mailers/user_plane/application_mailer.rb +8 -0
- data/app/mailers/user_plane/invite_mailer.rb +14 -0
- data/app/mailers/user_plane/verification_mailer.rb +25 -0
- data/app/models/session_manager.rb +100 -0
- data/app/models/user.rb +5 -0
- data/app/models/user/account.rb +26 -0
- data/app/models/user/confirm_email_address.rb +55 -0
- data/app/models/user/guest.rb +18 -0
- data/app/models/user/identities.rb +5 -0
- data/app/models/user/identities/email.rb +98 -0
- data/app/models/user/identities/email_verification.rb +70 -0
- data/app/models/user/identities/facebook.rb +5 -0
- data/app/models/user/identities/github.rb +5 -0
- data/app/models/user/identities/id_token.rb +7 -0
- data/app/models/user/identities/o_auth.rb +67 -0
- data/app/models/user/identities/o_auth_endpoint.rb +28 -0
- data/app/models/user/identities/twitter.rb +5 -0
- data/app/models/user/identity.rb +26 -0
- data/app/models/user/reset_password.rb +59 -0
- data/app/models/user/send_password_reset.rb +25 -0
- data/app/models/user/send_sign_up_invite.rb +27 -0
- data/app/models/user/sign_in.rb +42 -0
- data/app/models/user/sign_up.rb +47 -0
- data/app/models/user/sign_up_invites.rb +5 -0
- data/app/models/user/sign_up_invites/invite.rb +46 -0
- data/app/models/user/sign_up_invites/stack.rb +22 -0
- data/app/models/user/sign_up_with_invite.rb +45 -0
- data/app/models/user/suspension.rb +7 -0
- data/app/models/user/update_details.rb +103 -0
- data/app/views/layouts/user_plane/application.html.erb +14 -0
- data/app/views/user/details/edit.html.erb +37 -0
- data/app/views/user/invites/edit.html.erb +34 -0
- data/app/views/user/invites/new.html.erb +21 -0
- data/app/views/user/reset_passwords/edit.html.erb +26 -0
- data/app/views/user/reset_passwords/new.html.erb +21 -0
- data/app/views/user/sign_ins/new.html.erb +25 -0
- data/app/views/user/sign_ups/new.html.erb +33 -0
- data/app/views/user_plane/invite_mailer/invite.html.erb +2 -0
- data/app/views/user_plane/invite_mailer/invite.text.erb +3 -0
- data/app/views/user_plane/verification_mailer/address_verification.html.erb +2 -0
- data/app/views/user_plane/verification_mailer/address_verification.text.erb +4 -0
- data/app/views/user_plane/verification_mailer/password_reset.html.erb +2 -0
- data/app/views/user_plane/verification_mailer/password_reset.text.erb +4 -0
- data/config/initializers/inflections.rb +3 -0
- data/config/locales/en.yml +63 -0
- data/config/locales/it.yml +62 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20121128143404_create_user_accounts.rb +11 -0
- data/db/migrate/20121226202553_create_user_identities_o_auths.rb +12 -0
- data/db/migrate/20121226203032_create_user_identities_emails.rb +13 -0
- data/db/migrate/20121227144617_create_user_identities_email_verifications.rb +15 -0
- data/db/migrate/20130113120152_create_user_identities_id_tokens.rb +12 -0
- data/db/migrate/20141025230304_create_user_sign_up_invites_stacks.rb +10 -0
- data/db/migrate/20141025230500_create_user_sign_up_invites_invites.rb +13 -0
- data/db/migrate/20141026230208_create_user_suspensions.rb +13 -0
- data/lib/generators/user_plane/view/details_generator.rb +22 -0
- data/lib/generators/user_plane/view/helpers.rb +68 -0
- data/lib/generators/user_plane/view/invites_generator.rb +23 -0
- data/lib/generators/user_plane/view/reset_passwords_generator.rb +22 -0
- data/lib/generators/user_plane/view/sign_ins_generator.rb +18 -0
- data/lib/generators/user_plane/view/sign_ups_generator.rb +22 -0
- data/lib/generators/user_plane/views_generator.rb +32 -0
- data/lib/tasks/user_plane_tasks.rake +4 -0
- data/lib/user_plane.rb +43 -0
- data/lib/user_plane/command.rb +24 -0
- data/lib/user_plane/engine.rb +27 -0
- data/lib/user_plane/fresh_validator.rb +9 -0
- data/lib/user_plane/omniauth.rb +50 -0
- data/lib/user_plane/redirect_to_sign_in.rb +22 -0
- data/lib/user_plane/route_concerns.rb +167 -0
- data/lib/user_plane/session_manager_concern.rb +9 -0
- data/lib/user_plane/signed_in_constraint.rb +11 -0
- data/lib/user_plane/token_segment.rb +52 -0
- data/lib/user_plane/version.rb +3 -0
- data/spec/controllers/user/confirm_email_addresses_controller_spec.rb +5 -0
- data/spec/controllers/user/details_controller_spec.rb +5 -0
- data/spec/controllers/user/invites_controller_spec.rb +19 -0
- data/spec/controllers/user/reset_passwords_controller_spec.rb +5 -0
- data/spec/controllers/user/sign_ins_controller_spec.rb +34 -0
- data/spec/controllers/user/sign_ups_controller_spec.rb +5 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +7 -0
- data/spec/dummy/app/controllers/welcome_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +30 -0
- data/spec/dummy/app/views/welcome/index.html.erb +1 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +30 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +49 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +78 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/assets.rb +8 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/user_plane.rb +5 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +43 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/schema.rb +101 -0
- data/spec/dummy/log/development.log +0 -0
- data/spec/dummy/log/test.log +20185 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/-pOuxJZhYk_qXqMNKgm23KfvzyUW71NynNLlcNBOubE.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/3AV9ywHBH56Leqey5LeznxK9vu4HD8fF3zSTk4MiDJA.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/5fzR1G0D8ukHkPkLXsUu6rP6qV82aIdx3hugKkDy6nM.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/9bKtJ2lkHPqtboGfbyknZ1OyH4xYO-aml7U3qhv-3kk.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/HjDmE9SFP2wimdNHU8Nff9cm3vFZ5soO1iw7Jdlb6z8.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/J9j6OdatarYW7VzVCVttmGphOhJKL0QXasdheyrgsTE.cache +2 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/KqrOQSlg0Th0N3XXx-h4p5BVJCfN0D8rRLoA9VxvXrc.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/UCB1W65KwVU8ttOY8jnPRDp8HyyYYEjeTwwPD6R4qy8.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/YmMSaaBmIcNZWPVF9jXcGBi-kwEzMuxzwPT_Zrcj1Bo.cache +2 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/g3wU8ajFWb5ZLPvujEt5l9DesbFCiAwqjx1WQgwTtHA.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/i5pp88VHKoqlxQJdgmQd_lkgX1-4em_uHqNDjQ4nyHA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/jL74yXjxf8cb6Olkjbw1C28MH_HbZe221l8AI6WVeH0.cache +3 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/puh7X4rfS3eDN9oHTXoQdAgqxivonrwAAdYZ4UB3GIg.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/qxPWFIWnE6gOCY-SsdBJe7Cgm5D3YUwaEne78Y7XdRg.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/rM9s67WgzKMZ1bRhUdA0yhPZDlyRE5a1kmdt7cS6m4c.cache +3 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/rVoG8EHlrCOgY4ZkzOj64f0jiTcbteQ_SYNzq9RqY0I.cache +0 -0
- data/spec/fabricators/user_account_fabricator.rb +11 -0
- data/spec/fabricators/user_guest_fabricator.rb +2 -0
- data/spec/fabricators/user_identities_email_fabricator.rb +8 -0
- data/spec/fabricators/user_identities_email_verification_fabricator.rb +5 -0
- data/spec/fabricators/user_identities_id_token_fabricator.rb +2 -0
- data/spec/fabricators/user_sign_up_fabricator.rb +51 -0
- data/spec/fabricators/user_sign_up_invites_invite_fabricator.rb +3 -0
- data/spec/fabricators/user_sign_up_invites_stack_fabricator.rb +4 -0
- data/spec/fabricators/user_suspension_fabricator.rb +4 -0
- data/spec/fabricators/user_update_detail_fabricator.rb +2 -0
- data/spec/features/user_plane/user_plane_invites_spec.rb +31 -0
- data/spec/features/user_plane/user_plane_reset_passwords_spec.rb +31 -0
- data/spec/features/user_plane/user_plane_sign_ins_spec.rb +44 -0
- data/spec/features/user_plane/user_plane_signed_in_only_spec.rb +31 -0
- data/spec/features/user_plane/user_plane_update_details_spec.rb +43 -0
- data/spec/fixtures/user_plane/invite_mailer/invite +3 -0
- data/spec/fixtures/user_plane/verification_mailer/address_verification +3 -0
- data/spec/fixtures/user_plane/verification_mailer/password_reset +3 -0
- data/spec/lib/generators/views_generator_spec.rb +16 -0
- data/spec/lib/route_concerns_spec.rb +54 -0
- data/spec/mailers/previews/user_plane/invite_mailer_preview.rb +11 -0
- data/spec/mailers/previews/user_plane/verification_mailer_preview.rb +16 -0
- data/spec/mailers/user_plane/invite_mailer_spec.rb +25 -0
- data/spec/mailers/user_plane/verification_mailer_spec.rb +52 -0
- data/spec/models/session_manager_spec.rb +28 -0
- data/spec/models/user/account_spec.rb +26 -0
- data/spec/models/user/confirm_email_address_spec.rb +101 -0
- data/spec/models/user/guest_spec.rb +5 -0
- data/spec/models/user/identities/email_spec.rb +5 -0
- data/spec/models/user/identities/email_verification_spec.rb +42 -0
- data/spec/models/user/identities/facebook_spec.rb +5 -0
- data/spec/models/user/identities/github_spec.rb +5 -0
- data/spec/models/user/identities/id_token_spec.rb +5 -0
- data/spec/models/user/identities/o_auth_spec.rb +12 -0
- data/spec/models/user/identities/twitter_spec.rb +5 -0
- data/spec/models/user/reset_password_spec.rb +141 -0
- data/spec/models/user/send_password_reset_spec.rb +44 -0
- data/spec/models/user/send_sign_up_invite_spec.rb +30 -0
- data/spec/models/user/sign_in_spec.rb +31 -0
- data/spec/models/user/sign_up_invites/invite_spec.rb +13 -0
- data/spec/models/user/sign_up_invites/stack_spec.rb +21 -0
- data/spec/models/user/sign_up_spec.rb +58 -0
- data/spec/models/user/sign_up_with_invite_spec.rb +83 -0
- data/spec/models/user/suspension_spec.rb +5 -0
- data/spec/models/user/update_details_spec.rb +98 -0
- data/spec/routing/invites_spec.rb +49 -0
- data/spec/routing/reset_passwords_spec.rb +31 -0
- data/spec/routing/sign_ins_spec.rb +36 -0
- data/spec/routing/update_details_spec.rb +30 -0
- data/spec/shared_contexts/feature_helpers.rb +12 -0
- data/spec/shared_contexts/routing.rb +8 -0
- data/spec/shared_contexts/user.rb +67 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/fabrication.rb +7 -0
- data/spec/support/omniauth.rb +4 -0
- metadata +770 -0
@@ -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
|
+
|
data/app/models/user.rb
ADDED
@@ -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,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
|