diff --git a/.ruby-version b/.ruby-version index b1b25a5f..530cdd91 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.2.2 +2.2.4 diff --git a/Gemfile b/Gemfile index f5443231..80f956f4 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source "https://rubygems.org" -ruby "2.2.2" +ruby "2.2.4" gem "rails", "4.2.3" gem "pg" @@ -28,6 +28,8 @@ gem "devise" gem "google-analytics-rails" gem "interactor" gem "kaminari" +gem "omniauth-facebook" +gem "omniauth-google-oauth2" gem "responders" gem "rollbar", "~> 0.10.3" gem "seedbank" diff --git a/Gemfile.lock b/Gemfile.lock index 3fd62acb..01ef885e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -20,7 +20,7 @@ GEM erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - active_link_to (1.0.2) + active_link_to (1.0.3) actionpack activejob (4.2.3) activesupport (= 4.2.3) @@ -38,73 +38,71 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - addressable (2.3.6) + addressable (2.3.8) arel (6.0.3) - ast (2.0.0) - astrolabe (1.3.0) - parser (>= 2.2.0.pre.3, < 3.0) - autoprefixer-rails (4.0.2.2) + ast (2.1.0) + astrolabe (1.3.1) + parser (~> 2.2) + autoprefixer-rails (6.0.3) execjs + json awesome_print (1.6.1) bcrypt (3.1.10) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) - brakeman (3.0.3) + brakeman (3.1.1) erubis (~> 2.6) fastercsv (~> 1.5) haml (>= 3.0, < 5.0) - highline (~> 1.6.20) + highline (~> 1.6) multi_json (~> 1.2) - ruby2ruby (~> 2.1.1) - ruby_parser (~> 3.6.2) + ruby2ruby (>= 2.1.1, < 2.3.0) + ruby_parser (~> 3.7.0) sass (~> 3.0) + slim (>= 1.3.6, < 4.0) terminal-table (~> 1.4) builder (3.2.2) - bullet (4.14.7) + bullet (4.14.9) activesupport (>= 3.0.0) uniform_notifier (~> 1.9.0) - bundler-audit (0.3.1) + bundler-audit (0.4.0) bundler (~> 1.2) thor (~> 0.18) - byebug (3.1.2) - columnize (~> 0.8) - debugger-linecache (~> 1.2) - capybara (2.4.4) + byebug (6.0.2) + capybara (2.5.0) mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) - capybara-webkit (1.3.1) - capybara (>= 2.0.2, < 2.5.0) + capybara-webkit (1.7.1) + capybara (>= 2.3.0, < 2.6.0) json choice (0.2.0) code_analyzer (0.4.5) sexp_processor - codeclimate-test-reporter (0.4.7) + codeclimate-test-reporter (0.4.8) simplecov (>= 0.7.1, < 1.0.0) coderay (1.1.0) - coffee-rails (4.0.1) + coffee-rails (4.1.0) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) - coffee-script (2.2.0) + coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.7.0) - coffeelint (1.9.1) + coffee-script-source (1.9.1.1) + coffeelint (1.11.0) coffee-script execjs json colored (1.2) - columnize (0.8.9) crack (0.4.2) safe_yaml (~> 1.0.0) - daemons (1.1.9) - database_cleaner (1.3.0) + daemons (1.2.3) + database_cleaner (1.5.0) debug_inspector (0.0.2) - debugger-linecache (1.2.0) - decent_exposure (2.3.1) - devise (3.4.1) + decent_exposure (2.3.2) + devise (3.5.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 3.2.6, < 5) @@ -113,15 +111,16 @@ GEM warden (~> 1.2.3) diff-lcs (1.2.5) docile (1.1.5) - dotenv (0.9.0) - dotenv-rails (0.9.0) - dotenv (= 0.9.0) + dotenv (2.0.2) + dotenv-rails (2.0.2) + dotenv (= 2.0.2) + railties (~> 4.0) email_spec (1.6.0) launchy (~> 2.1) mail (~> 2.2) erubis (2.7.0) - eventmachine (1.0.7) - execjs (2.2.2) + eventmachine (1.0.8) + execjs (2.6.0) factory_girl (4.5.0) activesupport (>= 3.0.0) factory_girl_rails (4.5.0) @@ -129,51 +128,54 @@ GEM railties (>= 3.0.0) faker (1.5.0) i18n (~> 0.5) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) fastercsv (1.5.5) - foreman (0.63.0) - dotenv (>= 0.7) - thor (>= 0.13.6) - formulaic (0.2.0) + foreman (0.78.0) + thor (~> 0.19.1) + formulaic (0.3.0) activesupport capybara i18n foundation-icons-sass-rails (3.0.0) railties (>= 3.1.1) sass-rails (>= 3.1.1) - foundation-rails (5.4.5.0) + foundation-rails (5.5.3.2) railties (>= 3.1.0) - sass (>= 3.2.0) - fuubar (2.0.0.rc1) - rspec (~> 3.0.rc1) + sass (>= 3.3.0, < 3.5) + fuubar (2.0.0) + rspec (~> 3.0) ruby-progressbar (~> 1.4) globalid (0.3.6) activesupport (>= 4.1.0) google-analytics-rails (0.0.6) - haml (4.0.6) + haml (4.0.7) tilt - highline (1.6.21) + hashie (3.4.2) + highline (1.7.8) i18n (0.7.0) interactor (3.1.0) - jasmine (2.3.0) + jasmine (2.3.1) jasmine-core (~> 2.3) phantomjs rack (>= 1.2.1) rake - jasmine-core (2.3.0) + jasmine-core (2.3.4) jasmine-jquery-rails (2.0.3) - jquery-rails (4.0.4) + jquery-rails (4.0.5) rails-dom-testing (~> 1.0) railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (1.8.3) + jwt (1.5.1) kaminari (0.16.3) actionpack (>= 3.0.0) activesupport (>= 3.0.0) - launchy (2.4.2) + launchy (2.4.3) addressable (~> 2.3) - letter_opener (1.2.0) + letter_opener (1.4.1) launchy (~> 2.2) - libv8 (3.16.14.7) + libv8 (3.16.14.11) loofah (2.0.3) nokogiri (>= 1.5.9) mail (2.6.3) @@ -181,27 +183,49 @@ GEM metamagic (3.1.7) rails (>= 3.0.0) method_source (0.8.2) - mime-types (2.6.1) - mini_portile (0.6.2) - minitest (5.8.0) - multi_json (1.11.0) - nokogiri (1.6.6.2) - mini_portile (~> 0.6.0) + mime-types (2.6.2) + mini_portile2 (2.0.0) + minitest (5.8.3) + multi_json (1.11.2) + multi_xml (0.5.5) + multipart-post (2.0.0) + nokogiri (1.6.7.2) + mini_portile2 (~> 2.0.0.rc2) + oauth2 (1.0.0) + faraday (>= 0.8, < 0.10) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (~> 1.2) + omniauth (1.2.2) + hashie (>= 1.2, < 4) + rack (~> 1.0) + omniauth-facebook (2.0.1) + omniauth-oauth2 (~> 1.2) + omniauth-google-oauth2 (0.2.8) + addressable (~> 2.3) + jwt (~> 1.0) + multi_json (~> 1.3) + omniauth (>= 1.1.1) + omniauth-oauth2 (>= 1.1.1) + omniauth-oauth2 (1.3.1) + oauth2 (~> 1.0) + omniauth (~> 1.2) orm_adapter (0.5.0) - parser (2.2.2.3) + parser (2.2.3.0) ast (>= 1.1, < 3.0) - pg (0.17.1) + pg (0.18.3) phantomjs (1.9.8.0) powerpack (0.1.1) - pry (0.9.12.6) - coderay (~> 1.0) - method_source (~> 0.8) + pry (0.10.2) + coderay (~> 1.1.0) + method_source (~> 0.8.1) slop (~> 3.4) - pry-rails (0.3.2) + pry-rails (0.3.4) pry (>= 0.9.10) - pundit (1.0.0) + pundit (1.0.1) activesupport (>= 3.0.0) - quiet_assets (1.0.2) + quiet_assets (1.1.0) railties (>= 3.1, < 5.0) rack (1.6.4) rack-canonical-host (0.1.0) @@ -226,14 +250,14 @@ GEM activesupport (>= 4.2.0.beta, < 5.0) nokogiri (~> 1.6.0) rails-deprecated_sanitizer (>= 1.0.1) - rails-erd (1.4.1) + rails-erd (1.4.3) activerecord (>= 3.2) activesupport (>= 3.2) choice (~> 0.2.0) ruby-graphviz (~> 1.2) rails-html-sanitizer (1.0.2) loofah (~> 2.0) - rails_12factor (0.0.2) + rails_12factor (0.0.3) rails_serve_static_assets rails_stdout_logging rails_best_practices (1.15.7) @@ -245,70 +269,71 @@ GEM json require_all ruby-progressbar - rails_serve_static_assets (0.0.2) - rails_stdout_logging (0.0.3) + rails_serve_static_assets (0.0.4) + rails_stdout_logging (0.0.4) railties (4.2.3) actionpack (= 4.2.3) activesupport (= 4.2.3) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (2.0.0) - rake (10.4.2) - ref (1.0.5) + rake (10.5.0) + ref (2.0.0) require_all (1.3.2) - responders (2.1.0) - railties (>= 4.2.0, < 5) + responders (2.1.1) + railties (>= 4.2.0, < 5.1) rollbar (0.10.14) multi_json (~> 1.5) - rspec (3.0.0) - rspec-core (~> 3.0.0) - rspec-expectations (~> 3.0.0) - rspec-mocks (~> 3.0.0) - rspec-core (3.0.2) - rspec-support (~> 3.0.0) - rspec-expectations (3.0.2) + rspec (3.3.0) + rspec-core (~> 3.3.0) + rspec-expectations (~> 3.3.0) + rspec-mocks (~> 3.3.0) + rspec-core (3.3.2) + rspec-support (~> 3.3.0) + rspec-expectations (3.3.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.0.0) - rspec-mocks (3.0.2) - rspec-support (~> 3.0.0) - rspec-rails (3.0.1) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.0.0) - rspec-expectations (~> 3.0.0) - rspec-mocks (~> 3.0.0) - rspec-support (~> 3.0.0) - rspec-support (3.0.2) - rubocop (0.31.0) + rspec-support (~> 3.3.0) + rspec-mocks (3.3.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.3.0) + rspec-rails (3.3.3) + actionpack (>= 3.0, < 4.3) + activesupport (>= 3.0, < 4.3) + railties (>= 3.0, < 4.3) + rspec-core (~> 3.3.0) + rspec-expectations (~> 3.3.0) + rspec-mocks (~> 3.3.0) + rspec-support (~> 3.3.0) + rspec-support (3.3.0) + rubocop (0.34.2) astrolabe (~> 1.3) - parser (>= 2.2.2.1, < 3.0) + parser (>= 2.2.2.5, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.4) ruby-graphviz (1.2.2) ruby-progressbar (1.7.5) - ruby2ruby (2.1.4) + ruby2ruby (2.2.0) ruby_parser (~> 3.1) sexp_processor (~> 4.0) - ruby_parser (3.6.6) + ruby_parser (3.7.1) sexp_processor (~> 4.1) - safe_yaml (1.0.1) - sass (3.4.13) - sass-rails (5.0.3) + safe_yaml (1.0.4) + sass (3.4.19) + sass-rails (5.0.4) railties (>= 4.0.0, < 5.0) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) - tilt (~> 1.1) - scss_lint (0.38.0) + tilt (>= 1.1, < 3) + scss_lint (0.42.2) rainbow (~> 2.0) - sass (~> 3.4.1) + sass (~> 3.4.15) seedbank (0.3.0) - sexp_processor (4.5.1) - shoulda-matchers (2.8.0) - activesupport (>= 3.0.0) - simple_form (3.1.0) + sexp_processor (4.6.0) + shoulda-matchers (3.0.0) + activesupport (>= 4.0.0) + simple_form (3.2.0) actionpack (~> 4.0) activemodel (~> 4.0) simplecov (0.10.0) @@ -316,53 +341,57 @@ GEM json (~> 1.8) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) - skim (0.9.3) + skim (0.10.0) coffee-script coffee-script-source (>= 1.2.0) - slim (~> 2.0.0) - sprockets - slim (2.0.3) - temple (~> 0.6.6) + slim (>= 3.0) + sprockets (>= 2, < 4) + slim (3.0.6) + temple (~> 0.7.3) tilt (>= 1.3.3, < 2.1) - slim-rails (0.2.1) - slim (>= 0.9.2) + slim-rails (3.0.1) + actionmailer (>= 3.1, < 5.0) + actionpack (>= 3.1, < 5.0) + activesupport (>= 3.1, < 5.0) + railties (>= 3.1, < 5.0) + slim (~> 3.0) slop (3.6.0) - spring (1.3.6) + spring (1.4.0) spring-commands-rspec (1.0.4) spring (>= 0.9.1) - sprockets (3.3.1) - rack (~> 1.0) - sprockets-rails (2.3.2) + sprockets (3.4.0) + rack (> 1, < 3) + sprockets-rails (2.3.3) actionpack (>= 3.0) activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) - temple (0.6.10) - terminal-table (1.4.5) - therubyracer (0.12.1) + temple (0.7.6) + terminal-table (1.5.2) + therubyracer (0.12.2) libv8 (~> 3.16.14.0) ref - thin (1.6.1) - daemons (>= 1.0.9) - eventmachine (>= 1.0.0) - rack (>= 1.0.0) + thin (1.6.4) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0, >= 1.0.4) + rack (~> 1.0) thor (0.19.1) thread_safe (0.3.5) - tilt (1.4.1) + tilt (2.0.1) tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (2.4.0) + uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) uniform_notifier (1.9.0) - warden (1.2.3) + warden (1.2.4) rack (>= 1.0) - web-console (2.1.3) + web-console (2.2.1) activemodel (>= 4.0) binding_of_caller (>= 0.7.2) railties (>= 4.0) sprockets-rails (>= 2.0, < 4.0) - webmock (1.17.3) - addressable (>= 2.2.7) + webmock (1.21.0) + addressable (>= 2.3.6) crack (>= 0.3.2) xpath (2.0.0) nokogiri (~> 1.3) @@ -404,6 +433,8 @@ DEPENDENCIES launchy letter_opener metamagic + omniauth-facebook + omniauth-google-oauth2 pg pry-rails pundit @@ -432,3 +463,6 @@ DEPENDENCIES uglifier (>= 1.3.0) web-console (~> 2.0) webmock + +BUNDLED WITH + 1.11.2 diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb new file mode 100644 index 00000000..bee67a14 --- /dev/null +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -0,0 +1,49 @@ +class OmniauthCallbacksController < Devise::OmniauthCallbacksController + SocialProfile::PROVIDERS.each do |provider| + define_method(provider.to_s) do + begin + current_user ? connect_social_profile : handle_sign_in + rescue AuthVerificationPolicy::OauthError => e + redirect_to root_path, flash: { notice: e.to_s } + end + end + end + + private + + def connect_social_profile + OauthConnectOrganizer.new(auth, current_user).call + redirect_to edit_user_registration_path + end + + def auth + request.env["omniauth.auth"] + end + + def handle_sign_in + user = auth_organizer.new(auth).user + sign_in_and_redirect user, event: :authentication + end + + def auth_organizer + auth_verified? ? VerifiedAuthOrganizer : UnverifiedAuthOrganizer + end + + def auth_verified? + AuthVerificationPolicy.new(auth).verified? + end + + def after_sign_in_path_for(resource) + if resource.confirmed? + edit_user_registration_path + else + session[:auth_verified?] = auth_verified? + resource.reset_password(new_password, new_password) + finish_signup_path(resource) + end + end + + def new_password + @new_password ||= Devise.friendly_token.first(8) + end +end diff --git a/app/controllers/social_profiles_controller.rb b/app/controllers/social_profiles_controller.rb new file mode 100644 index 00000000..eac940b2 --- /dev/null +++ b/app/controllers/social_profiles_controller.rb @@ -0,0 +1,21 @@ +class SocialProfilesController < ApplicationController + before_action :authenticate_user! + + expose(:social_profiles) { current_user.social_profiles } + expose(:social_profile) + + def destroy + if social_profile.destroy + flash[:notice] = t "flash.actions.destroy.notice", resource_name: resource_name + else + flash[:alert] = t "flash.actions.destroy.alert", resource_name: resource_name + end + redirect_to edit_user_registration_url + end + + private + + def resource_name + SocialProfile.model_name.human + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 00000000..14a7adbc --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,39 @@ +class UsersController < ApplicationController + before_action :authenticate_user!, only: :home + + expose(:user, attributes: :user_params) + + def home + end + + def finish_signup + return false unless request.patch? + + user.update_attributes(user_params) ? sign_in_user : render_errors + end + + private + + def user_params + params.require(:user).permit(%i(full_name email password)) + end + + def sign_in_user + confirm_user + sign_in(user, bypass: true) + redirect_to root_path, notice: "Welcome!" + end + + def render_errors + render :finish_signup + end + + def confirm_user + if session[:auth_verified?] + session[:auth_verified?] = nil + user.update_attribute(:confirmed_at, Time.zone.now) + else + user.send_confirmation_instructions + end + end +end diff --git a/app/helpers/omniauth_helper.rb b/app/helpers/omniauth_helper.rb new file mode 100644 index 00000000..88471a14 --- /dev/null +++ b/app/helpers/omniauth_helper.rb @@ -0,0 +1,5 @@ +module OmniauthHelper + def provider_name(provider) + t "active_record.attributes.social_profile.provider_name.#{provider}" + end +end diff --git a/app/interactors/connect_social_profile.rb b/app/interactors/connect_social_profile.rb new file mode 100644 index 00000000..2b92e7d8 --- /dev/null +++ b/app/interactors/connect_social_profile.rb @@ -0,0 +1,27 @@ +class ConnectSocialProfile + attr_reader :user, :auth + private :user, :auth + + def initialize(user, auth) + @user = user + @auth = auth + end + + def call + social_profile ? update_social_profile : create_social_profile + end + + private + + def social_profile + @social_profile ||= SocialProfile.from_omniauth(auth) + end + + def update_social_profile + social_profile.update_attribute(:user, user) + end + + def create_social_profile + user.social_profiles.create!(provider: auth.provider, uid: auth.uid) + end +end diff --git a/app/interactors/create_user_from_auth.rb b/app/interactors/create_user_from_auth.rb new file mode 100644 index 00000000..eae04f7c --- /dev/null +++ b/app/interactors/create_user_from_auth.rb @@ -0,0 +1,25 @@ +class CreateUserFromAuth + attr_reader :auth + private :auth + + def initialize(auth) + @auth = auth + end + + def call + user = User.new( + email: auth.info.email, + full_name: auth.info.name, + password: new_password, + password_confirmation: new_password + ) + user.skip_confirmation_notification! && user.save! + user + end + + private + + def new_password + @new_password ||= Devise.friendly_token.first(8) + end +end diff --git a/app/interactors/find_user_by_email_service.rb b/app/interactors/find_user_by_email_service.rb new file mode 100644 index 00000000..b32dc0f0 --- /dev/null +++ b/app/interactors/find_user_by_email_service.rb @@ -0,0 +1,25 @@ +class FindUserByEmailService + attr_reader :auth + private :auth + + def initialize(auth) + @auth = auth + end + + def call + return unless user + + create_social_profile + user + end + + private + + def user + @user ||= User.find_by(email: auth.info.email) + end + + def create_social_profile + user.social_profiles.create!(provider: auth.provider, uid: auth.uid) + end +end diff --git a/app/interactors/oauth_connect_organizer.rb b/app/interactors/oauth_connect_organizer.rb new file mode 100644 index 00000000..439fef80 --- /dev/null +++ b/app/interactors/oauth_connect_organizer.rb @@ -0,0 +1,37 @@ +class OauthConnectOrganizer + attr_reader :auth, :user + private :auth, :user + + def initialize(auth, user) + @auth = auth + @user = user + end + + def call + unless user.confirmed? + auth_verified? ? process_user_confirmation : fail_oauth + end + + connect_social_profile + end + + private + + def auth_verified? + AuthVerificationPolicy.new(auth).verified? + end + + def process_user_confirmation + user.confirm + user.send_reset_password_instructions + end + + def fail_oauth + fail AuthVerificationPolicy::OauthError, + "Please confirm your account before connecting your #{auth.provider} account." + end + + def connect_social_profile + ConnectSocialProfile.new(user, auth).call + end +end diff --git a/app/interactors/unverified_auth_organizer.rb b/app/interactors/unverified_auth_organizer.rb new file mode 100644 index 00000000..50d4e7cb --- /dev/null +++ b/app/interactors/unverified_auth_organizer.rb @@ -0,0 +1,34 @@ +class UnverifiedAuthOrganizer + attr_reader :auth + private :auth + + def initialize(auth) + @auth = auth + end + + def user + check_user_by_email! + + found_user.send_confirmation_instructions unless found_user.confirmed? + found_user + end + + private + + def check_user_by_email! + user_by_email = User.find_by(email: auth.info.email) + fail AuthVerificationPolicy::OauthError, "Please, connect your account from profile page." if user_by_email + end + + def found_user + user_found_by_uid || new_user + end + + def user_found_by_uid + @user_found_by_uid ||= SocialProfile.from_omniauth(auth).try(:user) + end + + def new_user + @new_user ||= CreateUserFromAuth.new(auth).call + end +end diff --git a/app/interactors/verified_auth_organizer.rb b/app/interactors/verified_auth_organizer.rb new file mode 100644 index 00000000..4bb6c0e8 --- /dev/null +++ b/app/interactors/verified_auth_organizer.rb @@ -0,0 +1,26 @@ +class VerifiedAuthOrganizer + attr_reader :auth + private :auth + + def initialize(auth) + @auth = auth + end + + def user + user_found_by_uid || user_found_by_email || new_user + end + + private + + def user_found_by_uid + SocialProfile.from_omniauth(auth).try(:user) + end + + def user_found_by_email + FindUserByEmailService.new(auth).call + end + + def new_user + CreateUserFromAuth.new(auth).call + end +end diff --git a/app/models/social_profile.rb b/app/models/social_profile.rb new file mode 100644 index 00000000..e1118f6b --- /dev/null +++ b/app/models/social_profile.rb @@ -0,0 +1,12 @@ +class SocialProfile < ActiveRecord::Base + PROVIDERS = OmniAuth.strategies.map { |s| s.to_s.demodulize.underscore }.drop(1) + + belongs_to :user + + validates :user, :provider, :uid, presence: true + validates :uid, uniqueness: { scope: :provider } + + def self.from_omniauth(auth) + find_by(provider: auth.provider, uid: auth.uid) + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 521dad51..7b84a228 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,9 +1,12 @@ class User < ActiveRecord::Base devise :database_authenticatable, :registerable, :confirmable, - :recoverable, :rememberable, :trackable, :validatable + :recoverable, :rememberable, :trackable, :validatable, + :omniauthable, omniauth_providers: SocialProfile::PROVIDERS validates :full_name, presence: true + has_many :social_profiles, dependent: :destroy + def to_s full_name end diff --git a/app/policies/auth_verification_policy.rb b/app/policies/auth_verification_policy.rb new file mode 100644 index 00000000..158152e7 --- /dev/null +++ b/app/policies/auth_verification_policy.rb @@ -0,0 +1,35 @@ +class AuthVerificationPolicy + class OauthError < StandardError + end + + attr_reader :auth + private :auth + + def initialize(auth) + @auth = auth + end + + def verified? + request_verification_for + rescue NoMethodError + fail_with_error + end + + private + + def request_verification_for + send(auth.provider) + end + + def fail_with_error + fail ArgumentError, I18n.t("omniauth.verification.not_implemented", kind: auth.provider) + end + + def facebook + auth.info.verified? || auth.extra.raw_info.verified? + end + + def google_oauth2 + auth.extra.raw_info.email_verified? + end +end diff --git a/app/views/social_profiles/_list.html.slim b/app/views/social_profiles/_list.html.slim new file mode 100644 index 00000000..69571677 --- /dev/null +++ b/app/views/social_profiles/_list.html.slim @@ -0,0 +1,14 @@ +- if social_profiles.any? + b Successfully authorized via: + ul.js-social-profiles + - social_profiles.each do |social_profile| + li = link_to "#{provider_name(social_profile.provider)} (#{social_profile.uid.truncate(9)}). Unauthorize?", + social_profile, + data: { confirm: "Are you sure you want to remove this social profile?" }, + method: :delete, + class: "js-unauthorize" + +b Add service to sign in with: +ul + - SocialProfile::PROVIDERS.each do |provider| + li = link_to provider_name(provider), user_omniauth_authorize_path(provider) diff --git a/app/views/users/finish_signup.html.slim b/app/views/users/finish_signup.html.slim new file mode 100644 index 00000000..7757819b --- /dev/null +++ b/app/views/users/finish_signup.html.slim @@ -0,0 +1,16 @@ +.row + h3 Finish Signup + + .medium-6.columns + = simple_form_for user, + as: 'user', + url: finish_signup_path(user), + html: { method: :patch } do |f| + + .form-inputs + = f.input :email, required: true, autofocus: true, placeholder: "Your email here" + + = f.input :password, required: true, hint: '', placeholder: "Your password here" + + .form-actions + = f.button :submit, "Finish Signup" diff --git a/app/views/users/registrations/edit.html.slim b/app/views/users/registrations/edit.html.slim index 0decc952..dcd25388 100644 --- a/app/views/users/registrations/edit.html.slim +++ b/app/views/users/registrations/edit.html.slim @@ -24,6 +24,8 @@ = f.button :submit, 'Update' .medium-6.columns.end + = render "social_profiles/list", social_profiles: current_user.social_profiles + h6 b Cancel my account p diff --git a/config.ru b/config.ru index 93fb28fa..193e5fed 100644 --- a/config.ru +++ b/config.ru @@ -1,4 +1,4 @@ # This file is used by Rack-based servers to start the application. -require ::File.expand_path("../config/environment", __FILE__) +require ::File.expand_path("../config/environment", __FILE__) run Rails.application diff --git a/config/environments/development.rb b/config/environments/development.rb index 53838903..2f14eb9a 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -44,5 +44,5 @@ # Application specific options # - config.slim_options = { pretty: true } + config.slim_options = { pretty: true } end diff --git a/config/environments/test.rb b/config/environments/test.rb index 88d6da92..e3f40aaa 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -13,7 +13,7 @@ config.eager_load = false # Configure static asset server for tests with Cache-Control for performance. - config.serve_static_files = true + config.serve_static_files = true config.static_cache_control = "public, max-age=3600" # Show full error reports and disable caching. diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index e5b9fb2c..0f57869c 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -230,6 +230,8 @@ # Add a new OmniAuth provider. Check the wiki for more information on setting # up on your models and hooks. # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + config.omniauth :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"] + config.omniauth :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_APP_SECRET"], info_fields: "email, name, verified" # ==> Warden configuration # If you want to use other strategies, that are not supported by Devise, or diff --git a/config/locales/models/social_profile.en.yml b/config/locales/models/social_profile.en.yml new file mode 100644 index 00000000..9a464125 --- /dev/null +++ b/config/locales/models/social_profile.en.yml @@ -0,0 +1,7 @@ +en: + active_record: + attributes: + social_profile: + provider_name: + google_oauth2: Google + facebook: Facebook diff --git a/config/locales/oauth.en.yml b/config/locales/oauth.en.yml new file mode 100644 index 00000000..9f69b348 --- /dev/null +++ b/config/locales/oauth.en.yml @@ -0,0 +1,5 @@ +en: + omniauth: + verification: + failure: Your %{kind} account can't be used to sign in. Please verify it via profile page. + not_implemented: Verification checking is not implemented for %{kind}. diff --git a/config/routes.rb b/config/routes.rb index 2053e363..cc204395 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,9 @@ Rails.application.routes.draw do - devise_for :users + devise_for :users, controllers: { omniauth_callbacks: "omniauth_callbacks" } resource :feedback, only: %i(new create) + resources :social_profiles, only: :destroy + match "/users/:id/finish_signup" => "users#finish_signup", via: %i(get patch), as: :finish_signup with_options controller: :pages do get :about diff --git a/db/migrate/20151208195800_create_social_profiles.rb b/db/migrate/20151208195800_create_social_profiles.rb new file mode 100644 index 00000000..1278f4d8 --- /dev/null +++ b/db/migrate/20151208195800_create_social_profiles.rb @@ -0,0 +1,9 @@ +class CreateSocialProfiles < ActiveRecord::Migration + def change + create_table :social_profiles do |t| + t.references :user, index: true + t.string :provider, index: true, null: false, default: "" + t.string :uid, index: true, null: false, default: "" + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 423f9aa7..a979f0b0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,29 +11,39 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20100713113845) do +ActiveRecord::Schema.define(version: 20151208195800) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "social_profiles", force: :cascade do |t| + t.integer "user_id" + t.string "provider", default: "", null: false + t.string "uid", default: "", null: false + end + + add_index "social_profiles", ["provider"], name: "index_social_profiles_on_provider", using: :btree + add_index "social_profiles", ["uid"], name: "index_social_profiles_on_uid", using: :btree + add_index "social_profiles", ["user_id"], name: "index_social_profiles_on_user_id", using: :btree + create_table "users", force: :cascade do |t| - t.string "email", limit: 255, default: "", null: false - t.string "encrypted_password", limit: 255, default: "", null: false - t.string "reset_password_token", limit: 255 + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.string "confirmation_token", limit: 255 + t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.string "unconfirmed_email", limit: 255 - t.integer "sign_in_count", default: 0 + t.string "unconfirmed_email" + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip", limit: 255 - t.string "last_sign_in_ip", limit: 255 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "full_name", limit: 255 + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.datetime "created_at" + t.datetime "updated_at" + t.string "full_name" end add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree diff --git a/spec/factories/feedbacks.rb b/spec/factories/feedbacks.rb index 5cc030b2..f3893e8f 100644 --- a/spec/factories/feedbacks.rb +++ b/spec/factories/feedbacks.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :feedback do email - name { Faker::Name.name } + name { Faker::Name.name } message { Faker::Lorem.paragraph } phone { Faker::PhoneNumber.phone_number } end diff --git a/spec/factories/social_profiles.rb b/spec/factories/social_profiles.rb new file mode 100644 index 00000000..483521b7 --- /dev/null +++ b/spec/factories/social_profiles.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :social_profile do + user + provider "facebook" + uid "123545" + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index b98724b1..fb58dd2a 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -17,4 +17,8 @@ user.update(confirmation_sent_at: 3.days.ago) end end + + trait :from_auth_hashie do + email "joe@bloggs.com" + end end diff --git a/spec/features/user/connect_social_account_spec.rb b/spec/features/user/connect_social_account_spec.rb new file mode 100644 index 00000000..23181359 --- /dev/null +++ b/spec/features/user/connect_social_account_spec.rb @@ -0,0 +1,59 @@ +require "rails_helper" + +feature "Connect social account" do + context "oauth confirmed" do + include_context :stub_omniauth + + before { click_connect_fb } + + context "user confirmed" do + let!(:user) { create(:user, :confirmed, :from_auth_hashie) } + + scenario "User connects social account" do + expect(page).to have_connected_account("Facebook") + end + end + + context "user not confirmed" do + let!(:user) { create(:user, :from_auth_hashie) } + + scenario "User have to confirm own account" do + expect(page).to have_connected_account("Facebook") + + open_email(user.email) + + expect(current_email).to have_subject("Confirmation instructions") + expect(current_email).to have_body_text(user.full_name) + end + end + end + + context "oauth not confirmed" do + include_context :stub_not_verified_omniauth + + before { click_connect_fb } + + context "user confirmed" do + let!(:user) { create(:user, :confirmed, :from_auth_hashie) } + + scenario "User connects social account" do + expect(page).to have_connected_account("Facebook") + end + end + + context "user not confirmed" do + let!(:user) { create(:user, :from_auth_hashie) } + + scenario "User sees alert" do + expect(page).to have_text("Please confirm your account before connecting your facebook account.") + expect(current_path).to eq(root_path) + end + end + end + + def click_connect_fb + login_as(user, scope: :user) + visit edit_user_registration_path(user) + click_link "Facebook" + end +end diff --git a/spec/features/visitor/sign_in_with_social_spec.rb b/spec/features/visitor/sign_in_with_social_spec.rb new file mode 100644 index 00000000..4209fc09 --- /dev/null +++ b/spec/features/visitor/sign_in_with_social_spec.rb @@ -0,0 +1,94 @@ +require "rails_helper" + +feature "Sign in with social account" do + context "when oauth confirmed" do + include_context :stub_omniauth + + context "when user found by uid" do + let!(:social_profile) { create(:social_profile, user: user) } + + before { click_sign_in_with_fb } + + context "when user confirmed" do + let!(:user) { create(:user, :confirmed, :from_auth_hashie) } + + it_behaves_like "success sign in" + end + + context "when user not confirmed" do + let!(:user) { create(:user, :from_auth_hashie) } + + it_behaves_like "finishing sign up" do + let(:name) { user.full_name } + let(:email) { "mailer@mail.com" } + let(:password) { "123456qwe" } + end + end + end + + context "when user found by email" do + context "when user confirmed" do + let!(:user) { create(:user, :confirmed, :from_auth_hashie) } + + before { click_sign_in_with_fb } + + it_behaves_like "success sign in" + end + + context "when user not confirmed" do + let!(:user) { create(:user, :from_auth_hashie) } + + it_behaves_like "finishing sign up" do + let(:name) { user.full_name } + let(:email) { "mailer@mail.com" } + let(:password) { "123456qwe" } + end + end + end + + context "when user not found" do + it_behaves_like "finishing sign up" do + let(:name) { "Joe Bloggs" } + let(:email) { "mailer@mail.com" } + let(:password) { "123456qwe" } + end + end + end + + context "when oauth not confirmed" do + include_context :stub_not_verified_omniauth + + context "when user found by uid" do + let!(:social_profile) { create(:social_profile, user: user) } + let!(:user) { create(:user, :confirmed) } + + before { click_sign_in_with_fb } + + it_behaves_like "success sign in" + end + + context "when user found by email" do + let!(:user) { create(:user, :confirmed, :from_auth_hashie) } + + before { click_sign_in_with_fb } + + scenario "User sees alert message" do + expect(page).to have_text("Please, connect your account from profile page.") + expect(current_path).to eq(root_path) + end + end + + context "when user not found" do + it_behaves_like "finishing sign up" do + let(:name) { "Joe Bloggs" } + let(:email) { "mailer@mail.com" } + let(:password) { "123456qwe" } + end + end + end + + def click_sign_in_with_fb + visit new_user_session_path + click_link "Sign in with Facebook" + end +end diff --git a/spec/interactors/connect_social_profile_spec.rb b/spec/interactors/connect_social_profile_spec.rb new file mode 100644 index 00000000..de5d56f9 --- /dev/null +++ b/spec/interactors/connect_social_profile_spec.rb @@ -0,0 +1,27 @@ +require "rails_helper" + +describe ConnectSocialProfile do + include_context :auth_hashie + + let(:user_1) { create(:user) } + let(:user_2) { create(:user) } + let(:service) { described_class.new(user_2, auth_hashie) } + + subject { service.call } + + context "when social_profile exists" do + let!(:social_profile) do + create(:social_profile, uid: auth_hashie.uid, provider: auth_hashie.provider, user: user_1) + end + + it "updates user record" do + expect { subject }.to change { social_profile.reload.user }.from(user_1).to(user_2) + end + end + + context "when social profile not exists" do + it "creates related social profile" do + expect { subject }.to change { user_2.social_profiles.count }.by(1) + end + end +end diff --git a/spec/interactors/create_user_from_auth_spec.rb b/spec/interactors/create_user_from_auth_spec.rb new file mode 100644 index 00000000..2ad97d3f --- /dev/null +++ b/spec/interactors/create_user_from_auth_spec.rb @@ -0,0 +1,18 @@ +require "rails_helper" + +describe CreateUserFromAuth do + include_context :auth_hashie + + let(:user) { User.last } + let(:service) { described_class.new(auth_hashie) } + let(:sent_emails) { ActionMailer::Base.deliveries.count } + + subject { service.call } + + it "creates new user from auth hash" do + expect { subject }.to change { User.count }.by(1) + expect(sent_emails).to eq(0) + expect(user.email).to eq(auth_hashie.info.email) + expect(user.full_name).to eq(auth_hashie.info.name) + end +end diff --git a/spec/interactors/find_user_by_email_service_spec.rb b/spec/interactors/find_user_by_email_service_spec.rb new file mode 100644 index 00000000..1523ef11 --- /dev/null +++ b/spec/interactors/find_user_by_email_service_spec.rb @@ -0,0 +1,22 @@ +require "rails_helper" + +describe FindUserByEmailService do + include_context :auth_hashie + + let(:service) { described_class.new(auth_hashie) } + + subject { service.call } + + context "when user not exists" do + it { is_expected.to be_nil } + end + + context "when user exists" do + let!(:user) { create(:user, :from_auth_hashie) } + + it "creates new social_profile" do + expect { subject }.to change { user.social_profiles.count }.by(1) + expect(subject).to eq(user) + end + end +end diff --git a/spec/interactors/oauth_connect_organizer_spec.rb b/spec/interactors/oauth_connect_organizer_spec.rb new file mode 100644 index 00000000..3d1aa1e2 --- /dev/null +++ b/spec/interactors/oauth_connect_organizer_spec.rb @@ -0,0 +1,47 @@ +require "rails_helper" + +describe OauthConnectOrganizer do + include_context :auth_hashie + + let(:service) { described_class.new(auth_hashie, user) } + + subject { service.call } + + context "when user confirmed" do + let(:user) { create(:user, :confirmed) } + + before do + allow(ConnectSocialProfile).to receive_message_chain(:new, :call) + service.call + end + + it "creates social profile" do + expect(ConnectSocialProfile).to have_received(:new).with(user, auth_hashie) + end + end + + context "when user not confirmed" do + let(:user) { create(:user) } + + context "when auth not verified" do + include_context :invalid_auth_hashie + + it "raises error" do + expect { subject }.to raise_error(AuthVerificationPolicy::OauthError) + end + end + + context "when auth verified" do + before do + allow(user).to receive(:confirm) + allow(user).to receive(:send_reset_password_instructions) + service.call + end + + it "confirms user" do + expect(user).to have_received(:confirm) + expect(user).to have_received(:send_reset_password_instructions) + end + end + end +end diff --git a/spec/interactors/unverified_auth_organizer_spec.rb b/spec/interactors/unverified_auth_organizer_spec.rb new file mode 100644 index 00000000..3ae4eaac --- /dev/null +++ b/spec/interactors/unverified_auth_organizer_spec.rb @@ -0,0 +1,50 @@ +require "rails_helper" + +describe UnverifiedAuthOrganizer do + include_context :auth_hashie + + let(:service) { described_class.new(auth_hashie) } + + subject { service.user } + + context "when user found by email" do + before { create(:user, :from_auth_hashie) } + + it "raises error" do + expect { subject }.to raise_error(AuthVerificationPolicy::OauthError) + end + end + + context "when user found by uid" do + let!(:social_profile) { create(:social_profile, provider: auth_hashie.provider, uid: auth_hashie.uid, user: user) } + + let(:emails) { ActionMailer::Base.deliveries } + + context "when user confirmed" do + let!(:user) { create(:user, :confirmed, :from_auth_hashie) } + + it "not sends confirmation notification" do + expect(emails).to be_empty + end + end + + context "when user not confirmed" do + let!(:user) { create(:user, :from_auth_hashie) } + let(:email) { emails.last } + + it "sends confirmation notification" do + expect(email.subject).to eq("Confirmation instructions") + expect(email.to).to eq([user.email]) + end + end + end + + context "when user not found" do + let(:user) { User.last } + + it "creates new user from auth_hashie" do + expect { subject }.to change { User.count }.by(1) + expect(subject).to eq(user) + end + end +end diff --git a/spec/interactors/verified_auth_organizer_spec.rb b/spec/interactors/verified_auth_organizer_spec.rb new file mode 100644 index 00000000..e498edc2 --- /dev/null +++ b/spec/interactors/verified_auth_organizer_spec.rb @@ -0,0 +1,32 @@ +require "rails_helper" + +describe VerifiedAuthOrganizer do + include_context :auth_hashie + + let(:service) { described_class.new(auth_hashie) } + + subject { service.user } + + context "when social profile exists" do + let!(:social_profile) { create(:social_profile, uid: auth_hashie.uid, provider: auth_hashie.provider) } + + it { is_expected.to eq(social_profile.user) } + end + + context "when social profile not exists" do + context "when user exists" do + let!(:user) { create(:user, :from_auth_hashie) } + + it "creates related social profile" do + expect { subject }.to change { user.social_profiles.count }.by(1) + expect(subject).to eq(user) + end + end + + context "when user not exists" do + it "creates new one" do + expect { subject }.to change { User.count }.by(1) + end + end + end +end diff --git a/spec/models/social_profile_spec.rb b/spec/models/social_profile_spec.rb new file mode 100644 index 00000000..c6acfec7 --- /dev/null +++ b/spec/models/social_profile_spec.rb @@ -0,0 +1,27 @@ +require "rails_helper" + +describe SocialProfile do + subject { create(:social_profile, uid: "abc123") } + + it { is_expected.to belong_to :user } + it { is_expected.to validate_presence_of :user } + it { is_expected.to validate_presence_of :uid } + it { is_expected.to validate_presence_of :provider } + it { is_expected.to validate_uniqueness_of(:uid).scoped_to(:provider) } + + describe ".from_omniauth" do + include_context :auth_hashie + + subject { described_class.from_omniauth(auth_hashie) } + + context "when record exists" do + let!(:social_profile) { create(:social_profile, uid: auth_hashie.uid, provider: auth_hashie.provider) } + + it { is_expected.to eq(social_profile) } + end + + context "when record not exists" do + it { is_expected.to be_nil } + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0d1f5ef0..5ca43167 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2,4 +2,5 @@ describe User do it { is_expected.to validate_presence_of :full_name } + it { is_expected.to have_many(:social_profiles).dependent(:destroy) } end diff --git a/spec/policies/auth_verification_policy_spec.rb b/spec/policies/auth_verification_policy_spec.rb new file mode 100644 index 00000000..fb4f6fdf --- /dev/null +++ b/spec/policies/auth_verification_policy_spec.rb @@ -0,0 +1,43 @@ +require "rails_helper" + +describe AuthVerificationPolicy do + let(:auth) { double(:omniauth, provider: provider) } + + describe ".verified?" do + subject { described_class.new(auth).verified? } + + context "when provider is Facebook" do + let(:provider) { "facebook" } + + before do + allow(auth).to receive_message_chain(:info, :verified?).and_return(true) + allow(auth).to receive_message_chain(:extra, :raw_info, :verified?).and_return(true) + end + + it "returns corresponding value" do + expect(subject).to eq(true) + end + end + + context "when provider is Google" do + let(:provider) { "google_oauth2" } + + before do + allow(auth).to receive_message_chain(:extra, :raw_info, :email_verified?).and_return(true) + end + + it "returns corresponding value" do + expect(subject).to eq(true) + end + end + + context "when provider is not in the case statement" do + let(:provider) { "another" } + + it "raises Exception" do + expect { subject } + .to raise_error(ArgumentError, I18n.t("omniauth.verification.not_implemented", kind: provider)) + end + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 7177eac9..4afb59c2 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -6,8 +6,16 @@ require "shoulda/matchers" Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } +OmniAuth.config.test_mode = true RSpec.configure do |config| config.use_transactional_fixtures = false config.infer_spec_type_from_file_location! end + +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end diff --git a/spec/support/matchers/have_connected_account.rb b/spec/support/matchers/have_connected_account.rb new file mode 100644 index 00000000..301f8685 --- /dev/null +++ b/spec/support/matchers/have_connected_account.rb @@ -0,0 +1,7 @@ +RSpec::Matchers.define :have_connected_account do |social_profile| + match do + within ".js-social-profiles" do + have_text(social_profile) + end + end +end diff --git a/spec/support/shared_examples/finishing_sign_up.rb b/spec/support/shared_examples/finishing_sign_up.rb new file mode 100644 index 00000000..368c3d98 --- /dev/null +++ b/spec/support/shared_examples/finishing_sign_up.rb @@ -0,0 +1,23 @@ +shared_examples_for "finishing sign up" do + before do + visit new_user_session_path + click_link "Sign in with Facebook" + end + + scenario "User finishing registration" do + expect(page).to have_text("Finish Signup") + + fill_in :user_email, with: email + fill_in :user_password, with: password + click_button "Finish Signup" + + open_email(email) + expect(current_email).to have_subject("Confirmation instructions") + expect(current_email).to have_body_text(name) + + visit_in_email("Confirm my account") + expect(page).to have_content("Your email address has been successfully confirmed") + expect(page).to have_text(name) + expect(page).to have_text(email) + end +end diff --git a/spec/support/shared_examples/omniauth_stub.rb b/spec/support/shared_examples/omniauth_stub.rb new file mode 100644 index 00000000..9ab8052f --- /dev/null +++ b/spec/support/shared_examples/omniauth_stub.rb @@ -0,0 +1,89 @@ +require "rails_helper" + +shared_context :auth_hashie do + let(:auth_hashie) do + OmniAuth::AuthHash.new( + provider: "facebook", + uid: "123545", + info: { + email: "joe@bloggs.com", + name: "Joe Bloggs", + verified: true + }, + extra: { + raw_info: { + email: "joe@bloggs.com", + name: "Joe Bloggs", + verified: true, + email_verified: true + } + } + ) + end +end + +shared_context :invalid_auth_hashie do + let(:auth_hashie) do + OmniAuth::AuthHash.new( + provider: "facebook", + uid: "123545", + info: { + email: "joe@bloggs.com", + name: "Joe Bloggs", + verified: false + }, + extra: { + raw_info: { + email: "joe@bloggs.com", + name: "Joe Bloggs", + verified: false, + email_verified: false + } + } + ) + end +end + +shared_context :stub_omniauth do + background do + OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new( + provider: "facebook", + uid: "123545", + info: { + email: "joe@bloggs.com", + name: "Joe Bloggs", + verified: true + }, + extra: { + raw_info: { + email: "joe@bloggs.com", + name: "Joe Bloggs", + verified: true, + email_verified: true + } + } + ) + end +end + +shared_context :stub_not_verified_omniauth do + background do + OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new( + provider: "facebook", + uid: "123545", + info: { + email: "joe@bloggs.com", + name: "Joe Bloggs", + verified: false + }, + extra: { + raw_info: { + email: "joe@bloggs.com", + name: "Joe Bloggs", + verified: false, + email_verified: false + } + } + ) + end +end diff --git a/spec/support/shared_examples/success_sign_in.rb b/spec/support/shared_examples/success_sign_in.rb new file mode 100644 index 00000000..a51f6609 --- /dev/null +++ b/spec/support/shared_examples/success_sign_in.rb @@ -0,0 +1,7 @@ +shared_examples_for "success sign in" do + scenario "User signs in" do + expect(page).to have_text(user.full_name) + expect(current_path).to eq(edit_user_registration_path) + expect(page).to have_connected_account("Facebook") + end +end