I am using Devise in my Rails application and have overwritten the Devise Session Controller so I can update an attribute on my user model after the signin has succeeded. When, in the respond_with method, trying to do resource.update_attribute(:token_issued_at, Time.now.to_i) results in the following error:
PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_users_on_email"
DETAIL: Key (email)=(test#example.com) already exists.
I have tried setting the resource (user in this case) attribute token_issued_at value and then saving, however at that point it tells me the email is already taken.
resource.token_issued_at = Time.now.to_i
resource.save!
After turning validation off by calling resource.save(validate: false) it resorts back to the key violation error. Any help in solving this would be greatly appreciated!
class Users::SessionsController < Devise::SessionsController
def create
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource
end
private
def respond_with(resource, _opts = {})
resource.update_attribute(:token_issued_at, Time.now.to_i)
tokens = Jwt::Issuer.call(resource)
response.headers['authorization'] = "Bearer #{tokens[0]}"
render json: { refresh_token: tokens[1].crypted_token }, status: :ok
end
end
Somewhere along the line, my local databases got messed up. I was able to re-create this situation and was able to fix it by either of these two options.
Run this command in a rails console.
ActiveRecord::Base.connection.tables.each do |table_name|
ActiveRecord::Base.connection.reset_pk_sequence!(table_name)
end
OR, if your local data is newer and or not important.
rails db:drop db:create
Related
In my application I am trying to manually make devise sessions unusable by setting a session_validity_token.
How I do it:
The devise User model has a column named session_validity_token
I also have a SeesionHistory model which has the same column
In my devise initialization ...
Warden::Manager.after_set_user except: :fetch do |user, warden, opts|
user.update_attribute(:session_validity_token, Devise.friendly_token) if user.session_validity_token.nil?
warden.raw_session["validity_token"] = user.session_validity_token
end
Warden::Manager.after_fetch do |user, warden, opts|
unless user.session_histories.unblocked.where(session_validity_token: warden.raw_session["validity_token"]).exists?
warden.logout
end
end
... when a user signs in or up I set the validity_token of the stored Cookie to the Users session_validity_token. If the User doesn't have one yet (signup), I create a token.... when a URL gets fetched I check before authorizing the User if a unblocked session to that token exists. If not, the User gets logged out.
In the ApplicationController ...
def after_sign_in_path_for(resource_or_scope)
session = SessionHistory.create(user_id: current_user.id, session_validity_token: current_user.session_validity_token)
current_user.update_attribute(:session_validity_token, Devise.friendly_token)
request.env['omniauth.origin'] || stored_location_for(resource) || root_path
end
... after a User gets signed in, I create a SessionHistory Record and simply set it's session_validity_token to the Users token and then recreate the Users token.
Unfortunately I get the following error:
NoMethodError in Users::SessionsController#create
undefined method `session_validity_token' for nil:NilClass
Here is the SessionController#Create Action:
def create
if User.where("email = '#{params[:user][:login]}' or username = '#{params[:user][:login]}'").exists?
#user = User.find(User.where("email = '#{params[:user][:login]}' or username = '#{params[:user][:login]}'").first.id)
if #user.confirmed? || ((Time.now - #user.created_at).to_i / (24 * 60 * 60)) < 1
super
else
redirect_to new_user_confirmation_path(q: "unconfirmed")
end
else
flash[:alert] = "The email or username does not match any accounts"
redirect_to new_user_session_path
end
end
So I guess I did something wrong when handling the tokens with Warden ...
Please ask if you need additional Information.
You may have a namespace collision between two customizations named session_validity_token. This is not naturally in the Devise model (and is not in the source for devise--I checked that).
If that is the case, and you have power over the source, consider changing the name of one, or both of the session_validity_token symbols to clarify the specific usage and relieve the conflict.
Suppose I have a scenario where we have Users and each user can create their own Projects.
I'm trying to limit the Show action of my Rails controller to only allow admin or the owner of the project to be able to go through Show action.
The problem I am facing is, perhaps I'm misunderstanding on how to use Scopes in Pundit.
My Show action looks like this:
def show
project = policy_scope(Project).find_by({id: project_params[:id]})
if project
render json: project
else
render json: { error: "Not found" }, status: :not_found
end
end
My Pundit Scope class looks like this:
class Scope < Scope
def resolve
if #user.admin?
scope.all
else
# obviously, if non-matching user id, an ActiveRelation of
# empty array would be returned and subsequent find_by(...)
# would fail causing my controller's 'else' to execute
# returning 404 instead of 403
scope.where(user_id: #user.id)
end
end
end
In my Rails test, I am trying to assert that non-project owner should receive a 403 forbidden:
test "show project should return forbidden if non admin viewing other user's project" do
# "rex" here is not the owner of the project
get project_path(#project.id), headers: #rex_authorization_header
assert_response :forbidden
end
My test is failing. I am getting the error:
Failure:
ProjectsControllerTest#test_show_project_should_return_forbidden_if_non_admin_viewing_other_user's_project [/Users/zhang/App_Projects/LanceKit/Rails_Project/LanceKit/test/controllers/projects_controller_test.rb:40]:
Expected response to be a <403: forbidden>, but was a <404: Not Found>.
Expected: 403
Actual: 404
I don't quite feel like I'm using Pundit correctly.
Should I be using Pundit's authorize project instead of using policy_scope(Project)... for the Show action?
I was expecting the scope.where(...) to detect the incorrect user id and return some error saying 'you are not authorized to view this resource' rather than returning results.
From what my test results are indicating to me, using scope for show action is wrong.
My finding is telling me Pundit scope are only used for filtering a set of data to only return those that matches a condition, it does NOT check whether the current_user is the owner of the resource. Pundit scope does NOT raise a 403 Forbidden error.
In other words, using scoping only in the show action will lead to a semantic bug that's saying this project with id 3 does not exist in the database for example instead of saying you are not authorized to view this project because it belongs to a different user.
A summary for myself:
use policy_scope for index action
use authorize for show, create, update, delete
use authorize AND policy_scope if you're not resource owner and trying to access some funky plural resource route like
get "/user/1/projects" => "Project.index"
in case you want to check if user is say a "project manager" or "collaborator" who is allowed to view your project. In this case, you would probably need to modify your scope code with an extra elsif clause.
In relation to my above question, I modified my project to use authorize inside my show action:
def show
project = Project.find_by({id: project_params[:id]})
authorize project
if project
render json: project
else
render json: { error: "Not found" }, status: :not_found
end
end
This then raises the expected 403 Forbidden error that my tests is expecting and thus my test passes.
Pundits docs regarding scopes state that you can indeed use them for the show action:
def index
#posts = policy_scope(Post)
end
def show
#post = policy_scope(Post).find(params[:id])
end
Just using authorize may not be enough if a User (manually) opens a URL with a id param of an instance, that she should not be able to view.
To avoid a RecordNotFound error, I used the recommended NilClassPolicy:
class NilClassPolicy < ApplicationPolicy
class Scope < Scope
def resolve
raise Pundit::NotDefinedError, "Cannot scope NilClass"
end
end
def show?
false # Nobody can see nothing
end
end
Need to do ajax side-registration with devise. Created RegistrationsController:
class RegistrationsController < Devise::RegistrationsController
def create
if request.xhr?
build_resource(sign_up_params)
if resource.save
...
end
else
super
end
end
end
Noticed, that model does not validate confirmation of password, if I don't pass that field in XHR, or the field is empty, and Angular does not gather it before doing $http.post
Hacked it for a while in Angular, hardcoding initial $scope.reg = {'password_confirmation': ''} but wonder how do I fix this a normal way?
It's unlikely that your model's password confirmation is not validated once you've properly set up Devise. You'll know for sure if validation isn't being run if registration actually creates a new user. If it doesn't create a new user, then validation works fine.
The problem seems to lie in your create action. It looks like your if resource.save doesn't have an else block which is what is called when resource.save fails due to validation errors. Your create action should look like:
def create
# seems unnecessary to check if request is xhr
build_resource(sign_up_params)
if resource.save
render json: resource
else
render :json => { :errors => #model.errors.full_messages }, :status => 422
end
end
Then just have code in Angular that handles errors by notifying the user.
I'm working on a rails 4 app, and i have the following controller code
def index
#issue = Issue.find(1)
#sections = #issue.sections
#articles = #issue.articles
end
which breaks if the database is empty with the error: "Couldn't find Issue with id=1". What is the proper way to check for this in a way that if nothing is in the db it doesn't raise an error?
One method you can use is the exists? active record method, like so:
#issue = Issue.where(id: 1)
if #issue.exists?
# do something if it exists
else
# do something if it is missing
end
Side note: Since you're attempting to find by id, you don't necessarily need the .where portion; you can simply do: Issue.exists?(1).
exists? documentation on APIDoc
In most cases such exception is expected and recommenced. For example, you can rescue it with a custom 404 page.
Anyway, if you really don't want that, you can use find_by method which will output nil if nothing found
#issue = Issue.find_by(id: 1)
you can handle that exception in your controller
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
def record_not_found
flash[:alert] = "invalid information"
redirect_to root_url
end
or you can use a where clause
#issue = Issue.where(id: 1).first
now check for nil by
#issue.nil?
I'm trying to implement Authlogic in Rails 3 and have just been having headache after headache...I'm extremely new to rails, so please forgive me for not being an expert. I followed the railscast on the subject which has been really helpful, but as soon as i submit my create new user form via the actual website I get this:
undefined method `activated?'
app/controllers/users_controller.rb:37:in `create'
Any help would be SO appreciated...had a headache with this tonight...
Code from create method:
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = "Registration successful."
else
render :action => 'new'
end
end
If anyone else hits this issue - regenerate your user_session model and fill it with:
class UserSession < Authlogic::Session::Base
def to_key
new_record? ? nil : [ self.send(self.class.primary_key) ]
end
end
This fixed it for me...seems to be an error surrounding that model at the very least so take it back to basics!
The problem for me was the existence of a user_sessions table. If you created the UserSession model through a generator, you have a migration that creates that table.
Simply deleting the table (both in test and development databases) and the migration file solved the problem for me.
Cheers,
-- José