Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.2.2
2.2.3
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Add ability to sign in/sign up via Social networks. And connect/disconnect network with current account from the profile page.
- Update [uglifier](https://github.com/lautis/uglifier) gem up to 2.7.2
- Move Rack::CanonicalHost and Rack::Auth::Basic configuration to initializers
- Support [Heroku Review Apps](https://devcenter.heroku.com/articles/github-integration#review-apps)
- Update [rails](https://github.com/rails/rails) version up to 4.2.3
Expand Down
6 changes: 4 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
source "https://rubygems.org"

ruby "2.2.2"
ruby "2.2.3"

gem "rails", "4.2.3"
gem "pg"
Expand All @@ -14,7 +14,7 @@ gem "jquery-rails"
gem "sass-rails", "~> 5.0.0"
gem "skim"
gem "therubyracer", platforms: :ruby
gem "uglifier", ">= 1.3.0"
gem "uglifier"

# views
gem "active_link_to"
Expand All @@ -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"
Expand Down
35 changes: 33 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ 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)
Expand All @@ -151,6 +153,7 @@ GEM
google-analytics-rails (0.0.6)
haml (4.0.6)
tilt
hashie (3.4.2)
highline (1.6.21)
i18n (0.7.0)
interactor (3.1.0)
Expand All @@ -166,6 +169,7 @@ GEM
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)
Expand All @@ -185,8 +189,30 @@ GEM
mini_portile (0.6.2)
minitest (5.8.0)
multi_json (1.11.0)
multi_xml (0.5.5)
multipart-post (2.0.0)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
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)
ast (>= 1.1, < 3.0)
Expand Down Expand Up @@ -350,7 +376,7 @@ GEM
tilt (1.4.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)
Expand Down Expand Up @@ -404,6 +430,8 @@ DEPENDENCIES
launchy
letter_opener
metamagic
omniauth-facebook
omniauth-google-oauth2
pg
pry-rails
pundit
Expand All @@ -429,6 +457,9 @@ DEPENDENCIES
spring-commands-rspec
therubyracer
thin
uglifier (>= 1.3.0)
uglifier
web-console (~> 2.0)
webmock

BUNDLED WITH
1.10.6
42 changes: 42 additions & 0 deletions app/controllers/omniauth_callbacks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include OmniauthHelper

expose(:user) { OauthOrganizer.new(current_user, auth_hash).call }

SocialProfile::PROVIDERS.each do |provider|
define_method(provider.to_s) do
begin
handle_user
rescue OauthOrganizer::OauthError
handle_error
end
end
end

def after_sign_in_path_for(_resource)
edit_user_registration_path
end

private

def auth_hash
request.env["omniauth.auth"]
end

# rubocop:disable Metrics/AbcSize
def handle_user
if user.persisted?
sign_in_and_redirect user, event: :authentication
set_flash_message(:notice, :success, kind: "#{provider_name(auth_hash.provider)}") if is_navigational_format?
else
session[:omniauth] = auth_hash.except("extra")
redirect_to new_user_registration_url
end
end
# rubocop:enable Metrics/AbcSize

def handle_error
redirect_to new_user_session_path,
notice: t("omniauth.verification.failure", kind: provider_name(auth_hash.provider))
end
end
13 changes: 13 additions & 0 deletions app/controllers/registrations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class RegistrationsController < Devise::RegistrationsController
def create
super
session[:omniauth] = nil unless @user.new_record?
end

private

def build_resource(*args)
super
@user.apply_omniauth(session[:omniauth]) if session[:omniauth]
end
end
21 changes: 21 additions & 0 deletions app/controllers/social_profiles_controller.rb
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions app/helpers/omniauth_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module OmniauthHelper
def provider_name(provider)
t "active_record.attributes.social_profile.provider_name.#{provider}"
end
end
23 changes: 23 additions & 0 deletions app/interactors/connect_social_profile.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class ConnectSocialProfile
attr_reader :user, :auth
private :user, :auth

def initialize(user, auth)
@user = user
@auth = auth
end

def call
if social_profile
social_profile.update_attribute(:user, user)
else
user.apply_omniauth(auth) && user.save
end
end

private

def social_profile
@social_profile ||= SocialProfile.from_omniauth(auth)
end
end
22 changes: 22 additions & 0 deletions app/interactors/fetch_oauth_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class FetchOauthUser
attr_reader :auth
private :auth

def initialize(auth)
@auth = auth
end

def call
find_social_profile_user || find_user_by_email
end

private

def find_social_profile_user
SocialProfile.from_omniauth(auth).try(:user)
end

def find_user_by_email
User.find_by(email: auth["info"]["email"])
end
end
44 changes: 44 additions & 0 deletions app/interactors/oauth_organizer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
class OauthOrganizer
class OauthError < StandardError
end

attr_reader :auth, :current_user
private :auth, :current_user

def initialize(current_user, auth)
@current_user = current_user
@auth = auth
end

def call
user.present? ? connect_social_profile : fail_oauth
user
end

private

def user
@user ||= current_user || fetch_oauth_user || build_user
end

def fetch_oauth_user
FetchOauthUser.new(auth).call if auth_verified?
end

def auth_verified?
AuthVerificationPolicy.new(auth).verified?
end

def build_user
User.build_from_omniauth(auth) if auth_verified?
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaximLarionov what do you think, should I leave it as is, or change logic and replace if auth_verified? with if !user_found_by_email??

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I doubt that we should build something from unverified auth, so auth verification should be kept

end

def connect_social_profile
ConnectSocialProfile.new(user, auth).call
end

def fail_oauth
fail OauthError, "Sorry, but yours #{auth.provider.titleize} failed verification.
Seems like yours #{auth.provider.titleize} account hasn't been verified."
end
end
12 changes: 12 additions & 0 deletions app/models/social_profile.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class SocialProfile < ActiveRecord::Base
PROVIDERS = %i(facebook google_oauth2)

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
15 changes: 14 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
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

def full_name_with_email
"#{self[:full_name]} (#{email})"
end

def self.build_from_omniauth(auth)
new(email: auth["info"]["email"], full_name: auth["info"]["name"])
end

def apply_omniauth(auth)
self.email = auth["info"]["email"] if email.blank?
self.full_name = auth["info"]["name"] if full_name.blank?
social_profiles.build(provider: auth["provider"], uid: auth["uid"])
end
end
32 changes: 32 additions & 0 deletions app/policies/auth_verification_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class AuthVerificationPolicy
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
6 changes: 6 additions & 0 deletions app/views/application/_navigation_user.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ ul.right
- else
= active_link_to 'Sign in', new_user_session_path, active: :exclusive, wrap_tag: :li
= active_link_to 'Sign up', new_user_registration_path, active: :exclusive, wrap_tag: :li

- SocialProfile::PROVIDERS.each do |provider|
= active_link_to "Sign in with #{provider_name(provider)}",
user_omniauth_authorize_path(provider),
active: :exclusive,
wrap_tag: :li
14 changes: 14 additions & 0 deletions app/views/social_profiles/_list.html.slim
Original file line number Diff line number Diff line change
@@ -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)
Loading