Rails Omniauth-github (422) The change you wanted was rejected - ruby-on-rails-3

I have had this solution for Omniauth & Github implemented and working fine but sometime in the last few months it stopped working.
The error I'm getting when I try to login is: (422) The change you wanted was rejected.
Specifically in the Heroku logs I'm seeing:
ActiveRecord::RecordInvalid (Validation failed: Password can't be blank):
app/models/user.rb:18:in `create_from_omniauth'
app/models/user.rb:14:in `from_omniauth'
app/controllers/sessions_controller.rb:4:in `create'
Do I need to save the credentials when I create the user?
My user model:
def self.from_omniauth(auth)
where(auth.slice("provider", "uid")).first || create_from_omniauth(auth)
end
def self.create_from_omniauth(auth)
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["nickname"]
user.email = auth["info"]["email"]
user.image = auth["info"]["image"]
end
end
Sessions controller:
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url, notice: "Signed in!"
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: "Signed out!"
end
end

Facebook's omniauth error "the change you wanted was rejected"
might appear because of your validations set in the model. I had to refactor my validation for users having one unique email, which wasn't working when a user would try to facebook login with the same email.
Look at your logs. heroku logs -t

It looks like you're either validating presence of the password field in your User model or using has_secure_password, which does that under the covers.
If you're doing that validation yourself, you can just add a clause like :if => :password_changed? to the validation.
If you're using has_secure_password, it depends which version of Rails you're using. Any version with these two changes (I believe only Rails 4) support passing a validations: false option to has_secure_password. Otherwise, there's not really a good solution, other than maybe setting a random dummy password when you create the user then letting them change it immediately.

I had this issue when the time on my gitlab server was out of sync, i restarted ntpd, which corrected the time on the server and the problem was resolved

Related

Intermittent OmniAuth errors authenticating against Google

When trying to authenticate using Omniauth, it works flawlessly in my development environment (Mac OSX Mavericks), but fails most of the time in production (on Heroku). The errors vary between "Invalid Credentials" and "Connection failed". In all cases, I'm already logged in to my Google account. I may get one or another of these errors anywhere between 4 and 8 times before the process succeeds.
Has anyone see this and can you shed some light on why this might be happening?
Gem: oa_openid (0.3.2)
config/routes.rb:
...
resource :admin_session, only: %w(show create destroy)
match '/auth/googleapps/callback' => 'admin_sessions#create'
...
config/omniauth.rb:
require 'openid/store/filesystem'
Rails.application.config.middleware.use OmniAuth::Strategies::GoogleApps,
OpenID::Store::Filesystem.new('./tmp'),
name: 'googleapps', domain: 'booktrakr.com'
admin_sessions_controller:
class AdminSessionsController < ApplicationController
# GET /admin_sessions
def show
redirect_to "/auth/googleapps?origin=#{params[:origin] || request.fullpath}" and return unless is_admin?
#session = authenticated_admin
end
# POST /admin_sessions
def create
authinfo = request.env['omniauth.auth']
uid = authinfo['uid']
unless uid =~ %r(^https?://(groundbreakingsoftware|booktrakr).com/openid)
raise "Bad hacker, no cookie"
end
self.authenticated_admin = authinfo
redirect_to request.env['omniauth.origin'], notice: 'Session was successfully created.'
end
# DELETE /admin_sessions
def destroy
self.authenticated_admin = nil
redirect_to root_url
end
end
It appears that switching to OAuth2 (https://github.com/zquestz/omniauth-google-oauth2) resolved the problem, at least at first blush. Thanks, #Ashitaka!

Why won't Devise allow unconfirmed users to login even when allow_unconfirmed_access_for is set?

We have an existing user base and are adding email confirmation. Confirmation is optional but will allow additional features. Users are not required to confirm. I've added the confirmable module and ran migrations. Confirmation works as advertised.
But, users cannot log in since they are not confirmed. All current users have nil confirmation values, which is what we want (users can go back and confirm their email at any time). I've followed all the Devise wiki articles and set allow_unconfirmed_access_for in the initializer:
config.allow_unconfirmed_access_for = 10.years
I've also tried setting it in our user model as well:
devise :confirmable, allow_unconfirmed_access_for: 10.years
I've also tried using other values (1.year, 500.days, etc.)
My SessionsController, which does not differ much from Devise's method (here on github)
class Users::SessionsController < Devise::SessionsController
respond_to :json
def new
redirect_to "/#login"
end
def create
resource = warden.authenticate(auth_options)
if !resource
render json: {error: "Invalid email or password" }, status: 401 and return
end
sign_in(resource_name, resource)
render "sign_in", formats: [:json], locals: { object: resource }
end
end
Devise's the response:
{"error": "You have to confirm your account before continuing."}
Devise 2.1.2 with Rails 3.2.9.
The Devise team have released a version (2.2.4) that supports nil as a valid value for allow_unconfirmed_access_for, meaning no limit. Issue: https://github.com/plataformatec/devise/issues/2275
You can now do:
config.allow_unconfirmed_access_for = nil
I simply needed to do this in my User model, instead of using allow_unconfirmed_access_for:
protected
def confirmation_required?
false
end
I've got the same issue: after turning on devise confirmations previously created accounts are unable to login.
The reason is here:
def confirmation_period_valid?
self.class.allow_unconfirmed_access_for.nil? || (confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago)
end
Old accounts have confirmation_sent_at set to nil, that's why they are unable to log in.
One solution is to force confirmation_sent_at like that:
update users set confirmation_sent_at=created_at where confirmation_sent_at is NULL;
You can do it manually, or create a migration.

Devise: "def create" in users_controller.rb not working?

I've been pulling my hair out trying to get anything working with "def create" and "def update" in the users_controller.rb for Devise.
For instance, I've tried this:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = "Test Save"
else
flash[:notice] = "Test Error"
end
end
end
I've used this code along with the appropriate code to show flash notices in the views section. However nothing is shown when I either submit a blank form, an incomplete form, or a complete form. The user registration will still go through on a complete form, but it does not follow anything I put in "def create". I've tried other ways of testing this aside from flash notices, such as sending to a different page, etc. I get no response. The same thing for "def update", it doesn't seem to even use that code.
I'm completely dumbfounded on this one, any ideas?
If i understand your question correctly, you should be overwriting the devise controller.
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def new
super
end
def create
# add custom create logic here
end
def update
super
end
end
You can see what the default devise controllers are doing here:
https://github.com/plataformatec/devise/tree/master/app/controllers/devise
If you just want to edit the flash message, looking at the link above shows that devise uses a method called set_flash_message
# Sets the flash message with :key, using I18n. By default you are able
# to setup your messages using specific resource scope, and if no one is
# found we look to default scope.
# Example (i18n locale file):
#
# en:
# devise:
# registrations:
# signed_up: 'Welcome! You have signed up successfully.'
So you can just edit your devise.en.yml file with the correct text and voila!
Note: If you do overwrite the controller don't forget to also add
# app/config/routes.rb
devise_for :users, :controllers => {:registrations => "registrations"}
How about this instead?
if #user.save
redirect_to #user, notice: 'User was successfully created.'
else
render action: 'new'
end
You are setting the flash, but no redirection and no rendering. I'm wondering if you are getting a blank page, or a 200 with no body.
This will redirect to the show action, setting a flash notice if successful and render the new form with the #user.errors showing why it failed.
If you are using devise, you could use the Registrations Controller to create a new account, you shouldn't need to create a new one. If you create a new one, there might be a conflict in the routes with registrations#create and users#create both pointing to POST /users

Devise and user profiles

I have set up a test applications and have setup devise to take care of the authentication, additionally I have set up a component where they are sent to a create profile page after registration which works well.
The problem I have is when a logged in user goes to edit they're profile it is easy for then to change the query string and access another users data -
http://localhost:3000/profiles/1/edit
the question i have is how do I lock this down to the current user so that can only edit they're data?
Robbie
I would go for a before_filter.
# in profiles controller
class ProfilesController < ApplicationController
before_filter :find_profile
before_filter :check_if_authorized
def find_profile
#profile = Profile.find(params[:id])
end
def check_if_authorized
render :status => 404 and return unless current_user == #profile.user
end
end
Assumptions:
devise model is named User
user has one profile
you're already checking if a user is logged in
You can use token authentication along with session for more precise and secure authentication.
Add devise :token_authenticatable to the model User
This will create an authentication token into the field authentication_token field of users table every time a user is created.
Then go for a before_filter :verify_auth_token
def verify_auth_token
if current_user.authentication_token == params[:auth_token]
return true
else
return false
end
end
Also the edit request should be http:///profiles/1/edit?auth_token=12wqaasaeaad

How to use 'Has_secure_password', when trying to refactor?

I am trying to refactor the railstutorial authorization mechanism.
My version of rails is 3.2.0 and I am using ruby 1.9.3-p0 and postrgresql 9.1.
So far, my tests are passing when it comes to a failed attempt to sigin but the successfull sign in fails.(The reason is that I have to refactor the old signin mechanism)
Here is my session helpers sign_in function:
def sign_in(employee)
cookies.permanent.signed[:remember_token] = [employee.id, employee.salt]
self.current_employee = employee
end.
One problem I see immediately with the sign_in function is that has_secure_password already takes care of the encryption and salt etc ... my thinking was that maybe I should use password_digest instead of employee.salt, but that failed as well.
I would like to have my cookies expire after two hours. I found this option at api.rubyonrails.org under cookies.
cookies[:key] = {
value => "employee.id, employee.salt",
expires => 2.hours.from.now
}
Another question I have has to do with the fact that has_secure_password already has an authenticate method so that means that I do not have to use the authenticate definition defined in the employee model,(user model) in the rails tutorial, but when I comment it out I get a flag reading:
NoMethodError: undefined method 'authenticate'
Here is my session controllers create action:
def create
employee = Employee.authenticate(params[:session][:email],
params[:session][:password])
if employee.nil?
flash.now[:error] = "Invalid email/password combination."
#title = "Sign in"
render 'new'
else
sign_in employee
redirect_back_or employee
end
end
It seems the Employee.authenticate is a problem.
So I essentially have three question and they are as follows:
In the rails tutorial we go through a fairly lengthly process of encrypting and applying salt etc to the employees password. Since has_secure_password has this already taken care of, what variable would I pass to my functions or arguments that would capture the encrypted password?
The next question has to do with the expiration of the cookie, and how I would use that in the sign_in function?
Lastly, how do I use the authenticate method so that rails recognizes it as a genuine method?
Just for the record, I have searched through railsguide, api.rubyonrails.org and other questions asked on SO that are similar to this one. Of course this merely points up my lack of understanding of the principles, but I am learning and do take direction well.
Thanks for any thoughts, suggestions and or resources you might share with me.
Update
I re-read the api on has_secure_password and authenticate takes only one argument, namely an unencrypted password ... so I have something to work with.
I still need any help or thoughts or suggestions that you might offer ... thanks.
update
I found this article that deals with session timeouts:
http://madkingsmusings.blogspot.com/2011/05/session-timeouts-on-rails.html
I am still working to see if I can get it to work for me, but it is tailored for the railstutorial.
As for the other questions, Michael Hartl is busy pushing out the second edition of Ruby on Rails tutorial and in that edition he will be dealing with has_secure_password.
The new version of the railstutorial is available.
For the first and last question question... You'll find the authentication extremely simple.
In the User model:
has_secure_password
In the SessionController:
def create
user = User.find_by_email(params[:session][:email])
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_back_or user
else
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
In the SessionsHelper:
def sign_in(user)
cookies[:remember_token] = user.remember_token
current_user = user
end
It should have been obvious but I didn't even think about looking for the code on github. Maybe someone else will appreciate the link.
Here is Hartl's 2nd edition Sample_App source on github