Skip to content

Instantly share code, notes, and snippets.

@wegorich
Last active December 23, 2015 02:59
Show Gist options
  • Save wegorich/6570841 to your computer and use it in GitHub Desktop.
Save wegorich/6570841 to your computer and use it in GitHub Desktop.
multioauth authorization
#app/models/user/authorization.rb
class User::Authorization < ActiveRecord::Base
belongs_to :user
attr_accessible :primary, :provider, :secret, :token, :uid, :data, :user_id
validates_uniqueness_of :uid, scope: [:provider]
# validates_uniqueness_of :primary, scope: :user_id
hstorable(
{
property: lambda {|v| v.to_s}
}, :data
)
def fio
"#{first_name} #{middle_name} #{last_name}".squish
end
def self.primary
where(primary: true).first
end
def social_profile_url
if %w(facebook google_oauth2).include? provider
data["link"]
else
"http://vk.com/#{data["screen_name"]}"
end
end
end
#lib/authorization_builder.rb
class AuthorizationBuilder
attr_reader :omnihash, :user, :authorization
attr_accessor :auth_attribures
def initialize omnihash, user = nil
@omnihash = omnihash
@user = user
@auth_attribures = {}
@authorization = {}
omniauth_unfold
end
def create_authorization
@authorization = User::Authorization.create auth_attribures
end
def self.set_with_omniauth! omniauth_token, user
authorization = User::Authorization.find_by_token(omniauth_token)
if authorization
user.authorizations << authorization
user.email = authorization.email if user.email.blank?
user.username = authorization.username if user.username.blank?
user.build_profile do |profile|
profile.fio = authorization.fio
profile.first_name = authorization.first_name
profile.last_name = authorization.last_name
profile.middle_name = authorization.middle_name
profile.birth_date = authorization.birth_date
profile.gender = authorization.gender
end
end
end
private
def omniauth_unfold
auth_attribures[:uid] = omnihash['uid']
auth_attribures[:provider] = omnihash['provider']
auth_attribures[:data] = get_extra_data
get_omniauth_credentials
auth_attribures[:primary] = true
connect_to_user if user.present?
end
def get_extra_data
data = omnihash['info'].merge(omnihash['extra']['raw_info']).delete_if do |key, value|
value.is_a?(Hashie::Mash) or value.is_a?(Array)
end
data[:username] = data[:nickname]
data
end
def get_omniauth_credentials
if omnihash['credentials'].present?
auth_attribures[:token] = omnihash['credentials']['token']
auth_attribures[:secret] = omnihash['credentials']['secret']
end
end
def connect_to_user
auth_attribures[:primary] = false if user.authorizations.blank?
auth_attribures[:user_id] = user.id
end
end
#app/controllers/authorizations_controller.rb
require 'authorization_builder'
class AuthorizationsController < ApplicationController
respond_to :html, :js
def create
unless omniauth
logger.debug "no omniauth"
flash[:alert] = t(:provider_connection_failed)
redirect_to root_path
else
if user_signed_in?
add_authorization
elsif try_sign_in_by_email
sign_in_and_redirect(:user, @current_user)
else
if authorization_exists?
sign_in_with_omniauth
else
sign_up_with_omniauth
end
end
end
end
def auth_failure
redirect_to '/users/sign_in', :alert => params[:message]
end
private
def omniauth
request.env["omniauth.auth"]
end
def add_authorization
auth_builder = AuthorizationBuilder.new(omniauth, current_user)
auth_builder.create_authorization
redirect_to root_path
end
def authorization_exists?
@authorization = User::Authorization.where(
:provider => omniauth['provider'], :uid => omniauth['uid'].to_s
).first
end
def sign_in_with_omniauth
flash[:notice] = t(:signed_in_successfully)
if @authorization.user
sign_in_and_redirect(:user, @authorization.user)
else
session[:omniauth_token] = @authorization.token
redirect_to new_user_registration_url(with_provider: true)
end
end
def sign_up_with_omniauth
auth_builder = AuthorizationBuilder.new(omniauth)
auth_builder.create_authorization
session[:omniauth_token] = auth_builder.authorization.token
redirect_to new_user_registration_url(with_provider: true)
end
def try_sign_in_by_email
logger.debug "try_sign_in_by_email info: #{omniauth.info}"
if omniauth.info.email && (try_user = User.find_by_email(omniauth.info.email)) && try_user.confirmed_at
case omniauth['provider']
when 'google_oauth2'
@current_user = try_user if omniauth.extra.raw_info.verified_email
when 'facebook'
@current_user = try_user if omniauth.info.verified
end
if @current_user && (auth = authorization_exists?) && auth.user_id == nil
auth.update_attribute :user_id, @current_user.id
end
end
@current_user
end
end
class CreateUserAuthorizations < ActiveRecord::Migration
def change
create_table :user_authorizations do |t|
t.string :provider
t.string :uid
t.string :token
t.string :secret
t.hstore :data
t.belongs_to :user
t.boolean :primary, default: false
t.timestamps
end
add_index :user_authorizations, :user_id
end
end
gem 'pg'
gem 'activerecord-postgres-hstore', '~> 0.7'
#lib/hstorable.rb
require 'active_record'
module Hstorable
# Usage
#
# hstorable ({
# boolean_key: lambda {|v| v == "true" ? true : false },
# string_key: lambda {|v| v.to_s},
# integer_key: lambda {|v| v.to_i},
# float_key: lambda {|v| v.to_f},
# array_key: lambda {|v| v[1..-2].split(',').collect!{|n| n.to_i} }
#
# or
#
# key: { block: lambda{..}, default: val}
#
# }, column_name)
#
def hstorable (keys = {}, column = :data)
serialize column, ActiveRecord::Coders::Hstore
keys.each do |key, args|
key = key.to_s
if args.is_a? Hash
block, default = args[:block], args[:default]
else
block, default = args, nil
end
attr_accessible key
define_method(key) do
data = self.send(column)
value = data && data[key]
value ? block.call(value) : default
end
define_method("#{key}=") do |value|
self.send("#{column.to_s}=", (send(column) || {}).merge(key => value))
end
end
end
end
ActiveRecord::Base.send :extend, Hstorable
#config/initializers/omniauth.rb
#require 'openid/store/filesystem'
Rails.application.config.middleware.use OmniAuth::Builder do
Secrets::secret['omniauth'].each do |service, definition|
params = {}
definition['scope'] and params[:scope] = definition['scope']
provider service.to_sym, definition['key'], definition['secret'], params
end
end
#app/controllers/registrations_controller.rb
require 'authorization_builder'
class RegistrationsController < Devise::RegistrationsController
respond_to :html, :js
def success
respond_with(resource) do |format|
format.html { render :layout => !request.xhr? }
end
end
private
def build_resource(*args)
super
if session[:omniauth_token] and params[:with_provider] == "true"
AuthorizationBuilder.set_with_omniauth! session[:omniauth_token], @user
@authorization = @user.authorizations.first
@user.valid?
end
end
protected
def after_sign_up_path_for resource
session[:just_signed_up] = true
super(resource)
end
end
#config/routes.rb
match '/auth/:provider/callback' => 'authorizations#create'
resources :authorizations, :only => [:index, :create, :destroy]
match '/auth/failure' => 'authorizations#auth_failure'
devise_for :users, :controllers => {registrations: 'registrations'}
CREATE TABLE user_authorizations (
id integer NOT NULL,
provider character varying(255),
uid character varying(255),
token character varying(255),
secret character varying(255),
data hstore,
user_id integer,
"primary" boolean DEFAULT false,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);
#config/initializers/secrets.rb
module Secrets
def self.secret
@secrets ||= YAML::load( File::open(File::expand_path("../../#{keys_file}", __FILE__)))
end
def self.defined_providers
secret['omniauth'].each { |provider, pair|}.map { |provider| provider[0].to_sym }
end
private
def self.keys_file
'secrets.yml'
end
end
#config/secrets.yml
omniauth:
provider:
key:
secret:
class SetupHstore < ActiveRecord::Migration
def self.up
execute "CREATE EXTENSION IF NOT EXISTS hstore"
end
def self.down
execute "DROP EXTENSION IF EXISTS hstore"
end
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :authorizations,
class_name: "User::Authorization",
dependent: :destroy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment