I'm using Authlogic with my Rails app and I can't figure out why it sometimes won't find a user based on the perishable token. It usually works, but sometimes it won't. I used this code so that users could be verified before being able to log in. However, for example right now, I'm looking at an example where the user is not being found by the perishable token even though, when I look in the DB for the user, the perishable token matches the one for the user.
The process is this:
in users_controller #create
#user.deliver_verification_instructions!(#subdomain)
in user model:
def deliver_verification_instructions!(subdomain)
reset_perishable_token!
Notifier.verification_instructions(self, subdomain).deliver!
end
in my mailer (Notifier.rb)
# email on new user registration to verify user
def verification_instructions(user,subdomain)
#user = user
#subdomain = subdomain
#url = "http://#{#subdomain.name}.foobar.com/user_verifications/#{#user.perishable_token}"
sent_on Time.now
mail(:to => "#{user.first_name} <#{user.email}>",
:subject => "Email Verification",
:from => 'Foo <info#foobar.com>') do |format|
format.text
format.html
end
end
in the email view:
Please click the following link to verify your email address:
<%= #url %>
in User_verifications controller
class UserVerificationsController < ApplicationController
before_filter :load_user_using_perishable_token
def show
#subdomain = Subdomain.find_by_user_name(current_subdomain)
if #user
#user.verify!
flash[:notice] = "Thank you for verifying your account. You may now login."
end
redirect_to home_path
end
private
def load_user_using_perishable_token
#user = User.find_using_perishable_token(params[:id])
flash[:notice] = "Unable to find your account." unless #user
end
end
The code sometimes returns "Unable to find your account." which means it's not finding the perishable token even though the token in the url in the email matches what I see in the database.
When I look at my logs on Heroku all I see it:
2013-01-11 00:10:01+00:00 app web.1 - - Started GET "/user_verifications/lTpNRnjDw4WbyAMUyw6" for 10.253.207.217/ip-10-253-207-217.eu-west-1.compute.internal at 2013-01-11 00:10:01 +0000
2013-01-11 00:10:02+00:00 heroku router - - at=info method=GET path=/user_verifications/lTpNRnjDw4WbyAMUyw6 host=mvfd.foobar.com fwd=10.253.207.217/ip-10-253-207-217.eu-west-1.compute.internal dyno=web.1 queue=0 wait=0ms connect=10ms service=1325ms status=302 bytes=96
2013-01-11 00:10:02+00:00 app web.1 - - cache: [GET /user_verifications/lTpNRnjDw4WbyAMUyw6] miss
2013-01-11 00:10:02+00:00 app web.1 - - Processing by UserVerificationsController#show as HTML
2013-01-11 00:10:02+00:00 app web.1 - - Parameters: {"id"=>"lTpNRnjDw4WbyAMUyw6"}
2013-01-11 00:10:02+00:00 app web.1 - - Redirected to http://mvfd.foobar.com/home
2013-01-11 00:10:02+00:00 app web.1 - - Completed 302 Found in 1008ms
Thanks for any assistance you can provide!
Authlogic expires perishable tokens after 10 minutes by default (for security) so it is likely that some users are following the password reset link after it has expired.
You can change this setting in your User model:
class User < ActiveRecord::Base
acts_as_authentic do |config|
config.perishable_token_valid_for = 1.hour
end
end
https://github.com/binarylogic/authlogic/blob/master/lib/authlogic/acts_as_authentic/perishable_token.rb#L15-L25
OR, pass a different expiration value in to the find_using_perishable_token method in your controller:
#user = User.find_using_perishable_token(params[:id], 1.hour)
https://github.com/binarylogic/authlogic/blob/master/lib/authlogic/acts_as_authentic/perishable_token.rb#L55-L63
Make sure to consider the risk of increasing the expiration value depending on the security requirements of your application.
Related
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!
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
I'm using the Cloudmailin addon to receive e-mail from my Heroku app. However, Cloudmailin has not been able to deliver - or, rather, it gets 500 from Heroku every time (so the address is correct).
The error in Heroku logs is
Started POST "/incoming_mails" for 109.107.35.53 at 2013-02-27 08:54:22 +0000
2013-02-27T08:54:23+00:00 app[web.1]: Entering the controller! Controlling the e-mail!
2013-02-27T08:54:23+00:00 app[web.1]:
2013-02-27T08:54:23+00:00 app[web.1]: NoMethodError (undefined method `[]' for nil:NilClass):
2013-02-27T08:54:23+00:00 app[web.1]: app/controllers/incoming_mails_controller.rb:7:in `create'
My routing is correct; the "Entering the controller! Controlling the e-mail!" comes from the puts at the beginning of the class, so the class definitely gets entered.
# routes.rb
post '/incoming_mails' => 'incoming_mails#create'
The file itself looks like this:
# /app/controllers/incoming_mails_controller.rb
class IncomingMailsController < ApplicationController
skip_before_filter :verify_authenticity_token
def create
puts "Entering the controller! Controlling the e-mail!"
Rails.logger.info params[:headers][:subject]
Rails.logger.info params[:plain]
Rails.logger.info params[:html]
if User.all.map(&:email).include? params[:envelope][:from] # check if user is registered
#thought = Thought.new
#thought.body = params[:plain].split("\n").first
#thought.user = User.where(:email => params[:envelope][:from])
#thought.date = DateTime.now
if #thought.save
render :text => 'Success', :status => 200
else
render :text => 'Internal failure', :status => 501
end
else
render :text => 'Unknown user', :status => 404 # 404 would reject the mail
end
end
end
User and Thought are database resources used elsewhere without a problem. The saving procedure is the same that works in scaffolding-generated Thought controller. The params and Rails.logger logic I copied from a Cloudmailin Rails 3 example.
I'm really confused - where am I going wrong? I'd really appreciate any pointers.
It turns out, this is why you shouldn't code while sleep-deprived. The problem was simple: there is no such thing as params[:envelope][:from], there is only params[:from]). My assumption that :from would be a sub-element of :envelope was probably formed by looking at the pattern in the second "Cloudmailin in Rails on Heroku" example, where a code used to log subject is Rails.logger.log params[:envelope][:subject].
I realized this was the error after reading the API documentation for 'original' Cloudmailin format. It was exceptionally silly of me not to have found / looked for this resource in the first place.
After fixing this, the code still didn't work, because User.where(:email => params[:from]) only returned a Relation object, while User object was expected. The error in Heroku logs was the following:
ActiveRecord::AssociationTypeMismatch (User(#29025160) expected,
got ActiveRecord::Relation(#12334440)):
Since there can only be one user with some e-mail, the fix User.where(:email => params[:from]).first has no side-effects and results in correct behavior.
I have currently set my app so that on successful sign in the app redirects the user to their profile at localhost:3000/users/id however if I am the first user id => 1 and type users/2 I have full access to this profile. I have been trying to find how to stop this using devise. I'm pretty new to rails so I'm sure I'm missing something simple, I have used the before_filter :authenticate_user! but this is obviously just checking if a user is signed in, but doesn't limit access to other users' profiles. I have read a bit on CanCan but this seems a bit overkill for what I am trying to achieve. Any pointers much appreciated.
users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate_user!
before_filter :user_authorization
def index
#users = User.all
end
def show
#user = User.find(current_user[:id])
end
private
def user_authorization
redirect_to(root_url) unless current_user.id == params[:id]
end
end
This is being reported from the server:
Started GET "/users/2" for 127.0.0.1 at 2012-06-24 13:00:38 +0200
Processing by UsersController#show as HTML
Parameters: {"id"=>"2"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 2]]
Redirected to http://localhost:3000/
Filter chain halted as :user_authorization rendered or redirected
Completed 302 Found in 20ms (ActiveRecord: 0.8ms)
In your controller:
class UsersController < ApplicationController
before_filter :validate_user, :only => :show
def show
#user = User.find(params[:id]
end
def validate_user
redirect_to courses_path unless current_user.id.to_s == params[:id]
end
end
Notice the current_user.id.to_s since current_user.id is an integer and params[:id] is a string.
In general, I'd say there are two approaches to solving this kind of problem:
Rolling your own code and implementing checks in your controllers (or potentially in your model classes), and
using a gem that enforces rules for you.
If you want to role-your-own, the simplest way would be to simply put checks in your controller that makes sure they get redirected if they try to look at a profile that isn't theirs. One way to do this using a before filter is this, though you'd want to adapt it for the behavior that makes sense for your app.
before_filter :validate_user
def validate_user
redirect_to home_path unless current_user and current_user.id == params[:id]
end
If you want to use a gem, then I'd recommend cancan as you've mentioned or another gem called Acts as Tenant. I've seen it used for similar things. But if all you want is to lock down the user profile, adding code to the controller probably works fine.
And voilĂ :
before_filter :user_authorization
private
def user_authorization
redirect_to(root_url) unless current_user.id == params[:id]
end
current_user is an helper that contains current logged user.
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