I'm showing the related codes of Devise timeoutable
User.rb
class User < ApplicationRecord
devise :database_authenticatable, :recoverable, :rememberable, :validatable, :confirmable, :timeoutable
.....
end
and /config/initializers/devise.rb
config.timeout_in = 1.day
It was worked perfectly, recently I have implemented Rails Action Cable for some background jobs as like /app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags "ActionCable", current_user.email
end
private
def find_verified_user
current_user = env['warden'].user
if current_user
current_user
else
reject_unauthorized_connection
end
end
end
end
the unsubscribe method
def unsubscribed
# Any cleanup needed when channel is unsubscribed
$redis.del "user_#{ current_user.id }_online"
ActionCable.server.broadcast 'online_indicator', userId: current_user.id, online: false
end
and after implemented this Rails automatically destroyed the session after sometimes inactive the user I mean logged out.
I don't know what is happening with this code.
Could anyone figure out what is this, please?
Thanks
Are you sure you don't have any disconnect method or is anything cleaning :current_user when channel is closed? I think the problem is channel being closed and that cleaning current_user
Related
I created a Rails app with Devise.
Each User should be able to reply to a form which populates the model Questionone.
However, I keep getting Couldn't find User without an ID
What I did:
rails generate scaffold Questionone first_question:text second_question:text user:references
In my User.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :questionones
end
In my Questionone.rb i have:
class Questionone < ApplicationRecord
belongs_to :user
end
In my questiones_controllers
before_action :set_questionone, only: [:show, :edit, :update, :destroy]
def new
#questionone = Questionone.new
#user = User.find(params[:user_id])
#If I use #user = current_user.id it works, but not sure if is right way
end
def create
#questionone = Questionone.new(questionone_params)
#questionone.user = User.find(params[:user_id])
#questionone.save
end
private
# Use callbacks to share common setup or constraints between actions.
def set_questionone
#questionone = Questionone.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def questionone_params
params.require(:questionone).permit(:user_id, :first_question, :second_question)
end
end
If I use a raise, it tells me that #user is nil
IF I USE #user = current_user.id it works, but I am not sure it is the right way to go
A bit of faulty logic there. Here is the answer:
def new
#questionone = Questionone.new
#user = current_user
end
def create
#questionone = Questionone.new(questionone_params)
#questionone.user = current_user
#questionone.save
end
Also, you are not memoizing the Questionone (? weird name :D ), you should do it like this:
def set_questionone
#questionone ||= Questionone.find(params[:id])
end
The longer answer:
I believe the user scope should be limited to the one that is currently login. If that's correct, then you are introducing a serious security problem in which anyone can create questions by pushing a user_id in the params.
Remove the user_id param entirely and use the current_user variable.
Before I was only using Omni auth + devise for a user to sign Up. Everything was working fine. Later I decided to use simple sign Up form of devise. I configured it and it was also working fine, but the problem arises when a user wants to use Omni auth to sign Up. Avatar is always set to nil even though facebook/Github is returning the link to the profile picture. I have tried this solution but, still is not working and I am aware of carrierwave remote location upload method. Gems I have used for Omni auth are omniauth-facebook and omniauth-github.
(1)
User.rb
Code:
class User < ApplicationRecord
include Storext.model
devise :database_authenticatable, :registerable, :omniauthable,
:recoverable, :rememberable, :trackable, :validatable
validates :first_name, :last_name, :email, :experience_level,
:goal_level, :theoretical_learner_level, presence: true
mount_uploader :avatar, AvatarUploader
# Override devise method for Oauth
def self.new_with_session(params, session)
if session['devise.user_attributes']
new(session['devise.user_attributes'].merge(session[:user_attributes])) do |user|
user.attributes = params
user.valid?
end
else
super
end
end
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid).to_hash).first_or_create do |user|
OauthUserGenerator.new(user: user, auth: auth).generate
end
end
# If sign in through Oauth, don't require password
def password_required?
super && provider.blank?
end
# Don't require update with password if Oauth
def update_with_password(params, *options)
if encrypted_password.blank?
update_attributes(params, *options)
else
super
end
end
end
(2)
oauth_user_generator.rb
Code:
class OauthUserGenerator
def initialize(user:, auth:)
#user = user
#auth = auth
end
def generate
#user.provider = #auth.provider
#user.uid = #auth.uid
#user.email = #auth.info.email
#user.password = Devise.friendly_token[0, 20]
#user.first_name = #auth.info.name.split[0]
#user.last_name = #auth.info.name.split[1]
#user.remote_avatar_url = #auth.info.image
end
end
(3)
omniauth_callbacks_controller.rb
Code:
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def omniauth_providers
process_oauth(request.env['omniauth.auth'].merge(session.fetch(:user_attributes, {})))
end
alias facebook omniauth_providers
alias github omniauth_providers
private
def process_oauth(omniauth_params)
user = User.from_omniauth(omniauth_params)
if user.persisted?
flash.notice = 'Signed in!'
sign_in_and_redirect user
else
session['devise.user_attributes'] = user.attributes
redirect_to new_user_email_registration_path
end
end
end
Thank You.
I had a similar problem a while ago.
Aparently, carrierwave does not allow a remote_url which redirects from a protocol to another. The url you get with auth.info.image is an http url which redirects to a https url. So in your OauthUserGeneratorclass, on your generate method. Try to do the following:
#user.remote_avatar_url = #auth.info.image.gsub('http', 'https')
That way the redirect will be from https -> https. That worked for me. Hope that's the same issue for you.
I am trying to configure token generation with devise in a grape api rails app. Since I have the current version of devise, token generation has been disabled. I am having several issues. First, is that when I submit a username and password to the sessions controller, it gives me an error that "ensure_authentication_token":
undefined method `ensure_authentication_token!' for #<User:0x007f880cca9090>
This is so strange because as you can see below, I have it defined in my user model and when I manually create Users in rails console, it works properly.
Is that a scope issue or why is that occurring?
User Model:
class User < ActiveRecord::Base
before_save :ensure_authentication_token
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.where(authentication_token: token).first
end
end
end
Sessions Grape API Controller:
module API
module V1
class Sessions < Grape::API
include API::V1::Defaults
resource :sessions do
params do
requires :email, type: String, desc: "Email"
requires :password, type: String, desc: "Password"
end
post do
email = params[:email]
password = params[:password]
if email.nil? or password.nil?
error!({error_code: 404, error_message: "Invalid Email or Password."},401)
return
end
user = User.where(email: email.downcase).first
if user.nil?
error!({error_code: 404, error_message: "Invalid Email or Password."},401)
return
end
if !user.valid_password?(password)
error!({error_code: 404, error_message: "Invalid Email or Password."},401)
return
else
user.ensure_authentication_token!
user.save
{status: 'ok', token: user.authentication_token}.to_json
end
end
end
end
end
end
The second problem is that when I follow this blog, it says that I need to add the following authentication check in my defaults.rb in the base api controller. When I add the "before do" section, I get access denied error even if I enter in the right credentials and it doesn't even go on to the rest of the sessions controller that I mentioned above.
before do
error!("401 Unauthorized, 401") unless authenticated
end
helpers do
def warden
env['warden']
end
def authenticated
return true if warden.authenticated?
params[:access_token] && #user = User.find_by_authentication_token(params[:access_token])
end
def current_user
warden.user || #user
end
end
Thanks for any help you can give!
EDIT: Phillip was absolutely correct that one of these issues was due to the bang versus non banged version of ensure_authentication_token. Removing the ! from the controller fixed that issue. The other problem was indeed from adding the "before do" loop I had.
This is so close to working, I can give and receive tokens in my api but when it connects to ember, it complains about a lack of a csrf token even though I have "protect_from_forgery with: :null_session" set in my application.rb
In your User model, you define a method called ensure_authentication_token.
In your Session controller, you call a method called ensure_authentication_token!.
These are not the same method: Why are exclamation marks used in Ruby methods?
This is preventing you from generating an authentication token, which probably explains the "401 Unauthorized, 401" error.
In my Rails app I have to models: Account & User. I'm using MongoID and Devise with Omniauth.
I'm having some issues with devise and warden about logging in users correctly with this configuration:
class Account
#code..
include Mongoid::Document
embeds_many :users
#code..
end
class User
#code..
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable
devise :omniauthable, omniauth_providers: [:twitter] #just an example..
include Mongoid::Document
embedded_in :account
#code..
end
And a pretty standard OmniauthCallbacksController:
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def twitter
callback_of('Twitter')
end
protected
def callback_of(provider)
#user = User.find_user_from(request.env["omniauth.auth"])
#user = User.new_with(request.env["omniauth.auth"]) if #user.blank?
if #user.persisted?
sign_in_and_redirect #user, event: :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, kind: provider) if is_navigational_format?
else
redirect_to new_user_registration_path, alert: "There was a problem with your #{provider} account. Please try again."
end
end
def after_sign_in_path_for(resource)
if resource.registered
account_path(resource.account)
else
new_account_path
end
end
end
That code works great, if the user is not registered an account document is created and an embedded user document is stored inside it. Then it's called sign_in_and_redirect #user, event: :authentication and the user is correctly signed in. Then there is a redirection to new_account_path where things become weird..
After redirection user is not logged in anymore, and therefore all devise helpers, like current_user are useless. I guess this is caused because users are embedded on accounts so Warden can't find the user correctly.
I found this Devise wiki's post, but seems to be outdated.
I'm not a devise/warden expert, so I'd need some help with this. Any help will be very appreciated.
Thanks!
My rails application uses devise to handle registration, authentication, etc. I'm using the confirmable module. The bug is this– when a user registers with email, Devise is sending two confirmation emails with different confirmation links. One link works, the other directs the user to an error page.
Devise spits out a message associated with the error: "Confirmation token is invalid" and takes the user to the Resend Confirmation Email page.
I'm hosting with heroku and using sendgrid to send the emails. update: The bug also occurs on localhost.
I have no idea where the root of this bug is, and this might be more code than what you need to see:
models/user.rb
...
devise :database_authenticatable, :registerable, :omniauthable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :authentication_keys => [:login]
...
## callbacks
after_create :account_created
# called after the account is first created
def account_created
# check if this activiy has already been created
if !self.activities.where(:kind => "created_account").blank?
puts "WARNING: user ##{self.id} already has a created account activity!"
return
end
# update points
self.points += 50
self.save
# create activity
act = self.activities.new
act.kind = "created_account"
act.created_at = self.created_at
act.save
end
...
def confirmation_required?
super && (self.standard_account? || self.email_changed)
end
...
controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def update
unless #user.last_sign_in_at.nil?
puts "--------------double checking whether password confirmation is required--"
## if the user has not signed in yet, we don't want to do this.
#user = User.find(current_user.id)
# uncomment if you want to require password for email change
email_changed = #user.email != params[:user][:email]
password_changed = !params[:user][:password].empty?
# uncomment if you want to require password for email change
# successfully_updated = if email_changed or password_changed
successfully_updated = if password_changed
params[:user].delete(:current_password) if params[:user][:current_password].blank?
#user.update_with_password(params[:user])
else
params[:user].delete(:current_password)
#user.update_without_password(params[:user])
end
if successfully_updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
if email_changed
flash[:blue] = "Your account has been updated! Check your email to confirm your new address. Until then, your email will remain unchanged."
else
flash[:blue] = "Account info has been updated!"
end
redirect_to edit_user_registration_path
else
render "edit"
end
end
end
end
controllers/omniauth_callbacks_controller
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_filter :verify_authenticity_token
def facebook
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
flash.notice = "Signed in!"
# if the oauth_token is expired or nil, update it...
if (DateTime.now > (user.oauth_expires_at || 99.years.ago) )
user.update_oauth_token(request.env["omniauth.auth"])
end
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
end
config/routes.rb
...
devise_for :users, controllers: {omniauth_callbacks: "omniauth_callbacks",
:registrations => "registrations"}
...
I'm happy to provide more information if needed. I'm also open to customizing/overriding the devise mailer behavior, but I don't know how to go about that.
Much thanks!
Solved!
I was able to override Devise::Mailer and force a stack trace to find out exactly what was causing duplicate emails. Devise::Mailer#confirmation_instructions was being called twice, and I found out that the problem was with my :after_create callback, shown below:
in models/user.rb...
after_create :account_created
# called after the account is first created
def account_created
...
# update points
self.points += 50
self.save
...
end
Calling self.save somehow caused the mailer to be triggered again. I solved the problem by changing when the points are added. I got rid of the after_create call and overrode the confirm! method in devise to look like this:
def confirm!
super
account_created
end
So now the user record doesn't get modified (adding points) until after confirmation. No more duplicate emails!
I originally went with Thomas Klemm's answer but I went back to look at this when I had some spare time to try and figure out what was happening as it didn't feel right.
I tracked the 'problem' down and noticed that it only happens when :confirmable is set in your devise (User) model and reconfirmable is enabled in the devise initializer - which in hindsight makes a lot of sense because essentially in the after_create we ARE changing the User model, although we aren't changing the email address - I suspect Devise may do this because the account isn't confirmed yet, but in any case it is easy to stop the second email just by calling self.skip_reconfirmation! in the after_create method.
I created a sample rails project with a couple of tests just to ensure the correct behaviour. Below are the key excerpts. If you have far too much time on your hands, you can see the project here: https://github.com/richhollis/devise-reconfirmable-test
app/models/User.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
after_create :add_attribute
private
def add_attribute
self.skip_reconfirmation!
self.update_attributes({ :status => 200 }, :without_protection => true)
end
end
initializers/devise.rb
# Use this hook to configure devise mailer, warden hooks and so forth.
# Many of these configuration options can be set straight in your model.
Devise.setup do |config|
..
..
# If true, requires any email changes to be confirmed (exactly the same way as
# initial account confirmation) to be applied. Requires additional unconfirmed_email
# db field (see migrations). Until confirmed new email is stored in
# unconfirmed email column, and copied to email column on successful confirmation.
config.reconfirmable = true
..
..
end
spec/models/user_spec.rb
require 'spec_helper'
describe User do
subject(:user) { User.create(:email => 'nobody#nobody.com', :password => 'abcdefghijk') }
it "should only send one email during creation" do
expect {
user
}.to change(ActionMailer::Base.deliveries, :count).by(1)
end
it "should set attribute in after_create as expected" do
user.status.should eq(200)
end
end
Running the rspec tests to ensure only one email is sent confirms the behaviour:
..
Finished in 0.87571 seconds 2 examples, 0 failures
Thanks for your great solution, Stephen! I've tried it and it works perfectly to hook into the confirm! method. However, in this case, the function is being called (as the name says) when the user clicks the confirmation link in the email he receives.
An alternative is to hook into the generate_confirmation_token method, so your method is called directly when the confirmation token is created and the email is sent.
# app/models/user.rb
def generate_confirmation_token
make_owner_an_account_member
super # includes a call to save(validate: false),
# so be sure to call whatever you like beforehand
end
def make_owner_an_account_member
self.account = owned_account if owned_account?
end
Relevant source of the confirmation module.