我正在努力(拼命)用Devise和Omniauth设置我的Rails 4应用程序。
我目前的努力是努力让本教程适合我。
http://sourcey.com/rails-4-omniauth-using-devise-with-twitter-facebook-and-linkedin/
目前,我有:
的Gemfile:
gem 'devise', '3.4.1'
gem 'devise_zxcvbn'
gem 'omniauth'
gem 'omniauth-oauth2', '1.3.1'
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-linkedin-oauth2'
gem 'google-api-client', require: 'google/api_client'
# gem 'oath'
我的gem文件和教程之间的主要区别在于教程不包含omniauth-oauth2 gem。我已将此包含在内,因为它是谷歌的要求(教程不使用)。
Identity.rb:
class Identity < ActiveRecord::Base
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
end
我的devise.rb包含4个策略提供商中每个提供商的config.omniauth。
我的配置/环境文件包含邮件程序操作的电子邮件设置。
routes.rb中:
devise_for :users, #class_name: 'FormUser',
:controllers => {
# :registrations => "users/registrations",
# :omniauth_callbacks => "users/authentications"
:omniauth_callbacks => 'users/omniauth_callbacks'
}
match '/users/:id/finish_signup' => 'users#finish_signup', via: [:get, :patch], :as => :finish_signup
Omniauth回调控制器:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
@user = User.find_for_oauth(env["omniauth.auth"])
if @user.persisted?
sign_in_and_redirect @user, event: :authentication
set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
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
def after_sign_in_path_for(resource)
if resource.email_verified?
super resource
else
finish_signup_path(resource)
end
end
end
教程和我的代码之间的主要区别:我已经包含了谷歌策略
User.rb
class User < ActiveRecord::Base
TEMP_EMAIL_PREFIX = 'change@me'
TEMP_EMAIL_REGEX = /\Achange@me/
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable,
:confirmable, :lockable,
# :zxcvbnable,
:omniauthable, :omniauth_providers => [:facebook, :linkedin, :twitter, :google_oauth2 ]
mount_uploader :image, AvatarUploader
# --------------- associations
has_many :articles
has_many :authentications, :dependent => :delete_all
has_many :comments
belongs_to :organisation
has_one :profile
has_many :qualifications
has_many :identities
has_many :trl_assessments, as: :addressable
# has_and_belongs_to_many :projects
# --------------- scopes
# --------------- validations
# validates_presence_of :first_name, :last_name
validates_uniqueness_of :email
# per sourcey tutorial - how do i confirm email registrations are unique?
# this is generating an error about the options in the without function -- cant figure out the solution
validates_format_of :email, :without => TEMP_EMAIL_REGEX, on: :update
# --------------- class methods
# sourcey tutorial
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
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,
#username: auth.info.nickname || auth.uid,
password: Devise.friendly_token[0,20])
#
# 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
def full_name
[*first_name.capitalize, last_name.capitalize].join(" ")
end
def formal_name
[*self.profile.try(:title), first_name.capitalize, last_name.capitalize].join(" ")
end
#----------- user to be approved by admin (for now); later approved by approver
def disapprove
self.approved = false
end
def approved
self.approved = true
end
def active_for_authentication?
super && approved?
end
def inactive_message
if !approved?
:not_approved
else
super # Use whatever other message
end
end
def self.send_reset_password_instructions(attributes={})
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
if !recoverable.approved?
recoverable.errors[:base] << I18n.t("devise.failure.not_approved")
elsif recoverable.persisted?
recoverable.send_reset_password_instructions
end
recoverable
end
# --------------- callbacks
# after_create :add_default_role
# --------------- instance methods
# --------------- private methods
# def add_default_role
# add_role(:pending) if self.roles.blank?
# end
end
我的代码和教程之间的主要区别:
将名称更改为在名字和姓氏之间拆分(以适合我的架构),因此使用了auth.info而不是原始信息。
在本教程中,find_for_oauth方法会创建一个新用户,其电子邮件属性包含问号和电子邮件的替代方法?我删除了问号以及它右侧的所有内容,因为我无法通过此行中的错误&amp;我无法理解它想要做什么。
我尝试使用if email_is_verified方法处理确认异常,而不是仅仅跳过确认。
我将email_verified方法更改为:
self.email&amp;&amp; TEMP_EMAIL_REGEX = ~self.email
我不知道为什么会发生这种变化 - 我试图通过这些问题获得codementor.io的帮助,这是所做的更改之一(没有解释原因)。
用户控制器:
class UsersController < ApplicationController
before_action :set_user, only: [:index, :show, :edit, :update, :destroy]
def index
if params[:approved] == "false"
@users = User.find_all_by_approved(false)
else
@users = User.all
end
end
# GET /users/:id.:format
def show
# authorize! :read, @user
end
# GET /users/:id/edit
def edit
# authorize! :update, @user
end
# PATCH/PUT /users/:id.:format
def update
# authorize! :update, @user
respond_to do |format|
if @user.update(user_params)
sign_in(@user == current_user ? @user : current_user, :bypass => true)
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!
sign_in(@user, :bypass => true)
redirect_to @user, notice: 'Your profile was successfully updated.'
else
@show_errors = true
end
end
end
# DELETE /users/:id.:format
def destroy
# authorize! :delete, @user
@user.destroy
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])
end
def user_params
params.require(:user).permit(policy(@user).permitted_attributes)
# accessible = [ :first_name, :last_name, :email ] # 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
教程的主要变化:强权参数现在在权威用户策略中定义(完全按照上面的注释),尽管我已经尝试用控制器中的强参数来解决这个问题。它对当前的问题没有任何影响。
完成注册表单:
<%= 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">
<%= f.text_field :email, :autofocus => true, :value => '', class: 'form-control input-lg', placeholder: 'Example: email@me.com -- use your primary work address' %>
<p class="help-block">Please confirm your email address. No spam.</p>
</div>
</div>
<div class="actions">
<%= f.submit 'Continue', :class => 'btn btn-primary' %>
目前,当我尝试在twitter上注册时,收到此错误消息:
ActiveRecord::StatementInvalid in Users::OmniauthCallbacksController#twitter
PG::NotNullViolation: ERROR: null value in column "email" violates not-null constraint DETAIL: Failing row contains (19, null, null, null, $2a$10$aye71.1oEClA5vsy..8WzeknYl9wxi7W8VDEGlNbzakQQyJqe8i8q, null, null, null, 0, null, null, null, null,
4e25979ab053eb4a26a6ba451d9, null, 2015-12-14 01:00:08.431268, null, 0, null, null, 2015-12-14 01:00:08.428961, 2015-12-14 01:00:08.428961, null, f). : INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at", "confirmation_token", "confirmation_sent_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id"
我不知道这个错误意味着什么,或者我为什么会这样做。我认为这很可能与我的代码和上面提到的教程之间的差异有关,但是我无法看到它们中的哪一个导致了问题或者如何解决它。
当我使用linkedin尝试相同的操作时,我收到此错误:
NoMethodError (undefined method `approved?' for #<User:0x007fa90c867600>):
2015-12-14T01:02:10.472783+00:00 app[web.1]: app/models/user.rb:130:in `active_for_authentication?'
我的架构中有一个已批准的属性。我还添加了一个名为&#39; approved&#39;我的用户模型试图解决这个问题,但它没有工作。
任何人都可以看到任何可能有用的东西。我一直试图在3年的最佳时间内完成这项工作。我已经将$$$$转入代码导师,支付了各种顾问,并参加了我所在地区的所有会议,但我找不到任何可以提供帮助的人。
考虑到Moustafa关于linkedin问题的建议:
我从user.rb中注释掉了这些批准方法:
# def disapprove
# self.approved = false
# end
# def approved?
# self.approved = true
# end
# def active_for_authentication?
# super && approved?
# end
# def inactive_message
# if !approved?
# :not_approved
# else
# super # Use whatever other message
# end
# end
前两个是否试图解决这个问题?问题和后两个是因为,在本教程之后,我想允许管理员批准新注册的用户,然后才能登录。
无论如何,对这些进行评论并再次尝试,我得到了这个错误:
Started PATCH "/users/1/finish_signup" for 49.191.133.120 at 2015-12-14 08:18:49 +0000
2015-12-14T08:18:49.536922+00:00 app[web.1]:
2015-12-14T08:18:49.536927+00:00 app[web.1]: NameError (undefined local variable or method `user' for #<UsersController:0x007f7215732e48>):
2015-12-14T08:18:49.536929+00:00 app[web.1]: app/controllers/users_controller.rb:72:in `user_params'
2015-12-14T08:18:49.536930+00:00 app[web.1]: app/controllers/users_controller.rb:43:in `finish_signup'
2015-12-14T08:18:49.536941+00:00 app[web.1]:
我的用户控制器的第72行有:
accessible << [:approved] if user.admin
ATTEMPT 2:
我取消注释了最后两个已批准的方法(在设计教程中建议用于管理员批准新注册用户的方法)。当我保存并重新启动服务器时,主页甚至无法加载。日志说:
Completed 500 Internal Server Error in 21ms (ActiveRecord: 1.8ms)
2015-12-14T08:45:09.932008+00:00 app[web.1]:
2015-12-14T08:45:09.932010+00:00 app[web.1]: ActionView::Template::Error (undefined method `approved?' for #<User:0x007f6afe8a2878>):
2015-12-14T08:45:09.932012+00:00 app[web.1]: 12: <span class="deviselinks" style="padding-right:30px">
2015-12-14T08:45:09.932011+00:00 app[web.1]: 10: <div class="col-md-8>">
2015-12-14T08:45:09.932012+00:00 app[web.1]: 11: <ul style="text-align: right">
2015-12-14T08:45:09.932016+00:00 app[web.1]: app/models/user.rb:130:in `active_for_authentication?'
2015-12-14T08:45:09.932014+00:00 app[web.1]: 14: Hi <%= link_to(current_user.first_name.titlecase, profile_path(current_user)) %></span>
2015-12-14T08:45:09.943612+00:00 heroku[router]: at=info method=GET path="/" host=www.[].com request_id=a5646532-14a6-46ed-8dd7-6e4741688cf3 fwd="49.191.133.120" dyno=web.1 connect=1ms service=34ms status=500 bytes=1754
我不知道完整的堆栈跟踪意味着什么 - 是终端命令吗?
答案 0 :(得分:0)
以下是我的意见:
在以下代码中,如果未经过验证,我会抓住您离开email variable = nil
的问题。但如果电子邮件未经过验证,您就不会为电子邮件提供任何虚拟数据。然后你尝试创建一个email = nil
的用户,这会导致例外。
email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
email = auth.info.email if email_is_verified
user = User.where(:email => email).first if email
对于twitter案例:
verified
下存在extra.raw_info
密钥,这意味着任何尝试通过Twitter进行身份验证都会失败,因为即使电子邮件已经过验证。您查看的位置错误,email
nil
email = nil
,然后您尝试创建一个email
的用户帐户,该帐户会被设计阻止,因为user object
是必需的。在这里找到omniauth-twitter的链接,查看omniauth提供的哈希值。
关于linkedin的第二个问题,显然电子邮件已经过验证approved?
查找名为user model
的方法,而approved
方法中有一个名为@extends('layouts.main)
的方法完全不同的方法。