我正在尝试使用 Rails 4 制作应用程序。
我一直在尝试(3 年多)来弄清楚如何让 Devise 和 Omniauth 正常工作,以便用户可以将多个社交媒体帐户添加到他们的用户资料中。
我已经阅读了所有的设计和omniauth 文档。我能用这些文档得到的最好的结果是添加 1 个社交媒体帐户。那不是我想要的。
我已经尝试过这个站点点教程 sitepoint.com/rails-authentication-oauth-2-0-omniauth
我试过这个 willschenck 教程 http://willschenk.com/setting-up-devise-with-twitter-and-facebook-and-other-omniauth-schemes-without-email-addresses/
我试过这个 jorge.caballeromurillo 教程:http: //jorge.caballeromurillo.com/multiple-omniauth-providers-for-same-user-on-ruby-on-rails/
我也试过这个源教程:http: //sourcey.com/rails-4-omniauth-using-devise-with-twitter-facebook-and-linkedin/
我已经承诺在 SO 上提供数千分的赏金以寻求解决这个问题的帮助 - 但还没有弄清楚。在过去的 3 年里,我参加了我所在地区的每一次 Rails 聚会,并在 codementor 上浪费了 $$ 试图寻求帮助。自从最近一次令人沮丧的尝试准备再试一次以来,已经过去了足够长的时间。请帮忙。
这是我到目前为止所拥有的:
用户.rb
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable,
:confirmable, :lockable,
# :zxcvbnable,
:omniauthable, :omniauth_providers => [:facebook, :linkedin, :twitter, :google_oauth2 ]
has_many :identities, dependent: :destroy
def self.find_for_oauth(auth, signed_in_resource = nil)
# Get the identity and user if they exist
identity = Identity.find_for_oauth(auth)
# If a signed_in_resource is provided it always overrides the existing user
# to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date.
user = signed_in_resource ? signed_in_resource : identity.user
# p '11111'
# Create the user if needed
if user.nil?
# p 22222
# Get the existing user by email if the provider gives us a verified email.
# If no verified email was provided we assign a temporary email and ask the
# user to verify it on the next step via UsersController.finish_signup
email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
email = auth.info.email if email_is_verified # take out this if stmt for chin yi's solution
user = User.where(:email => email).first if email
# Create the user if it's a new registration
if user.nil?
# p 33333
user = User.new(
# at least one problem with this is that each provider uses different terms to desribe first name/last name/email. See notes on linkedin above
first_name: auth.info.first_name,
last_name: auth.info.last_name,
email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
#username: auth.info.nickname || auth.uid,
password: Devise.friendly_token[0,20])
# fallback for name fields - add nickname to user table
# debugger
# if email_is_verified
user.skip_confirmation!
# end
# user.skip_confirmation!
user.save!
end
end
# Associate the identity with the user if needed
if identity.user != user
identity.user = user
identity.save!
end
user
end
def email_verified?
self.email && TEMP_EMAIL_REGEX !~ self.email
end
身份.rb
belongs_to :user
validates_presence_of :uid, :provider
validates_uniqueness_of :uid, :scope => :provider
def self.find_for_oauth(auth)
find_or_create_by(uid: auth.uid, provider: auth.provider)
end
用户控制器:
class UsersController < ApplicationController
before_action :set_user, only: [ :show, :edit, :update, :finish_signup, :destroy]
def index
# if params[:approved] == "false"
# @users = User.find_all_by_approved(false)
# else
@users = User.all
authorize @users
# end
end
# GET /users/:id.:format
def show
# authorize! :read, @user
end
# GET /users/:id/edit
def edit
# authorize! :update, @user
authorize @user
end
# PATCH/PUT /users/:id.:format
def update
# authorize! :update, @user
respond_to do |format|
authorize @user
if @user.update(user_params)
sign_in(@user == current_user ? @user : current_user, :bypass => true)
# I'm trying to get the user matched to an organisation after the email address (in finish sign up) updates the user model.
UserOrganisationMapperService.call(@user)
format.html { redirect_to @user }#, notice: 'Your profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
# GET/PATCH /users/:id/finish_signup
def finish_signup
# authorize! :update, @user
if request.patch? && params[:user] #&& params[:user][:email]
if @user.update(user_params)
@user.skip_reconfirmation!
# @user.confirm!
sign_in(@user, :bypass => true)
redirect_to root_path#, notice: 'Your profile was successfully updated.'
# redirect_to [@user, @user.profile || @user.build_profile]
# sign_in_and_redirect(@user, :bypass => true)
else
@show_errors = true
end
end
end
# DELETE /users/:id.:format
def destroy
# authorize! :delete, @user
@user.destroy
authorize @user
respond_to do |format|
format.html { redirect_to root_url }
format.json { head :no_content }
end
end
private
def set_user
@user = User.find(params[:id])
authorize @user
end
def user_params
# params.require(:user).permit(policy(@user).permitted_attributes)
accessible = [ :first_name, :last_name, :email, :avatar, {role_ids: []} ] # extend with your own params
accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
# accessible << [:approved] if user.admin
params.require(:user).permit(accessible)
end
end
身份控制器
class IdentitiesController < ApplicationController
before_action :set_identity, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
# GET /identities
# GET /identities.json
def index
@identities = Identity.all
end
# GET /identities/1
# GET /identities/1.json
def show
end
# GET /identities/new
def new
@identity = Identity.new
end
# GET /identities/1/edit
def edit
end
# POST /identities
# POST /identities.json
def create
@identity = Identity.new(identity_params)
respond_to do |format|
if @identity.save
format.html { redirect_to @identity, notice: 'Identity was successfully created.' }
format.json { render :show, status: :created, location: @identity }
else
format.html { render :new }
format.json { render json: @identity.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /identities/1
# PATCH/PUT /identities/1.json
创建我也尝试过的替代方案
def create
auth = request.env['omniauth.auth']
# Find an identity here
@identity = Identity.find_with_omniauth(auth)
if @identity.nil?
# If no identity was found, create a brand new one here
@identity = Identity.create_with_omniauth(auth)
end
if signed_in?
if @identity.user == current_user
# User is signed in so they are trying to link an identity with their
# account. But we found the identity and the user associated with it
# is the current user. So the identity is already associated with
# this user. So let's display an error message.
redirect_to root_url, notice: "Already linked that account!"
else
# The identity is not associated with the current_user so lets
# associate the identity
@identity.user = current_user
@identity.save
redirect_to root_url, notice: "Successfully linked that account!"
end
else
if @identity.user.present?
# The identity we found had a user associated with it so let's
# just log them in here
self.current_user = @identity.user
redirect_to root_url, notice: "Signed in!"
else
# No user associated with the identity so we need to create a new one
redirect_to new_registration_path, notice: "Please finish registering"
end
end
end
def update
respond_to do |format|
if @identity.update(identity_params)
format.html { redirect_to @identity, notice: 'Identity was successfully updated.' }
format.json { render :show, status: :ok, location: @identity }
else
format.html { render :edit }
format.json { render json: @identity.errors, status: :unprocessable_entity }
end
end
end
# DELETE /identities/1
# DELETE /identities/1.json
def destroy
@identity.destroy
respond_to do |format|
format.html { redirect_to identities_url, notice: 'Identity was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_identity
@identity = Identity.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def identity_params
params[:identity]
end
end
注册控制器
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_permitted_parameters, if: :devise_controller?
def create
super do |resource|
UserOrganisationMapperService.call(resource)
end
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :password, :first_name, :last_name) }
end
private
end
omniauth 回调控制器
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
#sourcey tutorial ------------------
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
@user = User.find_for_oauth(env["omniauth.auth"], current_user)
if @user.persisted?
sign_in_and_redirect @user, event: :authentication
else
session["devise.#{provider}_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
}
end
[:twitter, :facebook, :linkedin, :google_oauth2].each do |provider|
provides_callback_for provider
end
end
用户/完成注册视图
<div class="container-fluid">
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<h1 class="formheader">Complete your registration</h1>
<%= form_for(current_user, :as => 'user', :url => finish_signup_path(current_user), :html => { role: 'form'}) do |f| %>
<% if @show_errors && current_user.errors.any? %>
<div id="error_explanation">
<% current_user.errors.full_messages.each do |msg| %>
<%= msg %><br>
<% end %>
</div>
<% end %>
<div class="form-group">
<!-- f.label :false -->
<div class="controls">
<% if current_user.first_name.blank? %>
<%= f.text_field :first_name, :value => '', class: 'form-control input-lg', placeholder: 'First name' %>
<p class="help-block">Hi there, what is your first name?.</p>
<% end %>
<% if current_user.last_name.blank? %>
<%= f.text_field :last_name, :value => '', class: 'form-control input-lg', placeholder: 'Last name (surname)' %>
<p class="help-block">Add your last name</p>
<% end %>
<% if !current_user.email_verified? %>
<%= f.text_field :email, :value => '', class: 'form-control input-lg', placeholder: 'Example: email@me.com -- use your primary work or university address' %>
<p class="help-block">Please confirm your email address. No spam.</p>
<% end %>
</div>
</div>
<div class="actions">
<%= f.submit 'Continue', :class => 'btn btn-primary' %>
</div>
<% end %>
</div>
</div>
</div>
用户/身份验证视图
<div class="container-fluid">
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<div class="table-responsive" style="margin-left:30px; margin-top:15px">
<table class="table table-bordered">
<tr>
<td><i class="fa fa-facebook"></i></td>
<td>
<% if @user.identities.map(&:provider).include?('facebook') %>
<span class="glyphicon glyphicon-ok"</span>
<% else %>
<%= link_to icon('Connect Facebook', id: 'facebookauth'), user_omniauth_authorize_path(:facebook) %>
<% end %>
</td>
</tr>
<tr>
<td><i class="fa fa-google"></i></td>
<td>
<% if @user.identities.map(&:provider).include?('googleauth') %>
<span class="glyphicon glyphicon-ok"</span>
<% else %>
<%= link_to icon('Connect Google', id: 'googleauth'), user_omniauth_authorize_path(:google_oauth2) %>
<% end %>
</td>
</tr>
<tr>
<td><i class="fa fa-linkedin"></i></td>
<td>
<% if @user.identities.map(&:provider).include?('linkedin') %>
<span class="glyphicon glyphicon-ok"</span>
<% else %>
<%= link_to icon('Connect Linkedin', id: 'linkedinauth'), user_omniauth_authorize_path(:linkedin) %>
<% end %>
</td>
</tr>
<tr>
<td><i class="fa fa-twitter"></i></td>
<td>
<% if @user.identities.map(&:provider).include?('twitter') %>
å <span class="glyphicon glyphicon-ok"</span>
<% else %>
<%= link_to icon('Connect Twitter', id: 'twitterauth'), user_omniauth_authorize_path(:twitter) %>
<% end %>
</td>
</tr>
<tr>
<td>Password</td>
<td>
<% if @user.encrypted_password.present? %>
<span class="glyphicon glyphicon-ok"</span>
<% else %>
<%= form_for(current_user, :as => 'user', :html => { role: 'form'}) do |f| %>
<% if @show_errors && current_user.errors.any? %>
<div id="error_explanation">
<% current_user.errors.full_messages.each do |msg| %>
<%= msg %><br>
<% end %>
</div>
<div class="form-group">
<div class="controls">
<%= f.input :password, hint: ("#{@minimum_password_length} characters minimum" if @validatable), :input_html => { class: 'estimate-password'} %>
</div>
</div>
<% end %>
<div class="actions">
<%= f.submit 'Continue', :class => 'btn btn-primary' %>
</div>
<% end %>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
路线
devise_for :users, #class_name: 'FormUser',
:controllers => {
:registrations => "users/registrations",
# :omniauth_callbacks => "users/authentications"
:omniauth_callbacks => 'users/omniauth_callbacks'
}
# PER SOURCEY TUTORIAL ----------
match '/users/:id/finish_signup' => 'users#finish_signup', via: [:get, :patch], :as => :finish_signup
这些都不起作用。我不知道如何插入它。我不确定是否应该将存储在我的身份表中的属性包含在控制器的允许参数中?
属性是:
t.integer "user_id"
t.string "provider"
t.string "accesstoken"
t.string "refreshtoken"
t.string "uid"
t.string "name"
t.string "email"
t.string "nickname"
t.string "image"
t.string "phone"
t.string "urls"
我已经完成了这项工作,因此用户只能使用一种方法进行身份验证。我不知道如何让这个工作。我已经尝试了所有能找到的资源来解决这个问题,但我被困住了。
我让这一切都与每个单独的社交插件和电子邮件一起工作,但我没有的是能够将身份添加到现有用户(在当前会话中),以便他们下次登录时可以使用任何可接受的身份。
任何人都可以帮忙吗?