I have the following namespaces ApiController
class Api::ApiController < ApplicationController
skip_before_action :verify_authenticity_token,
if: Proc.new { |c| c.request.content_type == 'application/json' }
before_action :authenticate
attr_reader :current_user
private
def authenticate
#current_user = AuthorizeApiRequest.call(request.headers).result
render json: { error: 'Not Authorized' }, status: 401 unless #current_user
end
end
On AuthorizeApiRequest.call, Rails complains that:
uninitialized constant Api::ApiController::AuthorizeApiRequest
My AuthorizeApiRequest class is defined under app/commands:
class AuthorizeApiRequest
prepend SimpleCommand
def initialize(headers = {})
#headers = headers
end
def call
user
end
private
attr_reader :headers
def user
#user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
#user || errors.add(:token, 'Invalid token') && nil
end
def decoded_auth_token
#decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
end
def http_auth_header
if headers['Authorization'].present?
return headers['Authorization'].split(' ').last
else
errors.add(:token, 'Missing token')
end
nil
end
end
So it seems to not allow me to call AuthorizeApiRequest.call without added namespace to front. How to fix?
Your app/commands folder doesn't seem to be loaded into Rails at boot.
You need to include your app/commands in your autoload paths for this to work or require the file manually in your controller.
See: https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#autoload-paths
Related
I'm building the token authentication for the rails side of the project project. which uses devise and JWT gems. I need to write a method(in session controller) to destroy the user session. Does anyone know how to go about doing this? in the front end the token is held in sessions when the user is logged in.
class SessionsController < Devise::SessionsController
# protect_from_forgery with: :null_session, if: ->{request.format.json?}
# skip_before_action :verify_authenticity_token
def create
user = User.find_by_email(params[:email])
if user && user.valid_password?(params[:password])
#current_user = user
else
render json: { errors: { 'email or password' => ['is invalid'] } }, status: :unprocessable_entity
end
end
def destroy
# stuck here
end
end
here's the application controller too
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
respond_to :json
before_action :underscore_params!
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :authenticate_user
private
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
end
def authenticate_user
if request.headers['Authorization'].present?
authenticate_or_request_with_http_token do |token|
begin
jwt_payload = JWT.decode(token, Rails.application.secrets.secret_key_base).first
#current_user_id = jwt_payload['id']
rescue JWT::ExpiredSignature, JWT::VerificationError, JWT::DecodeError
head :unauthorized
end
end
end
end
def underscore_params!
params.deep_transform_keys!(&:underscore)
end
def authenticate_user!(options = {})
head :unauthorized unless signed_in?
end
def current_user
#current_user ||= super || User.find(#current_user_id)
end
def signed_in?
#current_user_id.present?
end
end
I have this code in my ApplicationController
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
def authenticate!
# unless current_user
if current_user
current_user
else
render json: { 'error' => {'message' => 'Invalid access token', 'code' => 301 } }
return
end
end
def current_user
return #current_user if #current_user.present?
user = User.find_by(access_token: params.delete(:token))
if user.present?
#current_user = user
else
false
end
end
end
and I authenticate a user with
class Api::V1::RegisterController < ApplicationController
layout nil
skip_before_action :verify_authenticity_token
def get_user
authenticate!
render json: {'hello' => 'hi'}
end
end
it throws me an error of Double Render.
how can I render an invalid access token message if user's access token is not present in my database and return user details if present?
EDIT1: I tried the code provided by #andrew21
class ApplicationController < ActionController::Base
class UnauthorizedAccess < StandardError; end
rescue_from UnauthroizedAccess, with: render_invalid_access
protect_from_forgery with: :exception
def authenticate!
raise UnauthorizedAccess, 'invalid access token' unless current_user
end
def render_invalid_access
render json: { 'error' => {'message' => 'Invalid access token', 'code' => 301 } }
end
end
but I get an error.
undefined local variable or method `render_invalid_access' for ApplicationController:Class
why don't you raise an error on invalid access, then rescue the error and render the appropriate response. e.g.:
class ApplicationController < ActionController::Base
class UnauthorizedAccess < StandardError; end
protect_from_forgery with: :exception
rescue_from UnauthorizedAccess, with: :render_invalid_access
def authenticate!
raise UnauthorizedAccess, 'invalid access token' unless current_user
end
def render_invalid_access
render json: { 'error' => {'message' => 'Invalid access token', 'code' => 301 } }
end
end
I am trying to build a "router" for users that fires on login. I am trying to redirect users by role.
I have the following:
require "#{Rails.root}/lib/client/user_router"
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
add_flash_types :info
def after_sign_in_path_for(user)
UserRouter.new(user)
end
end
and in /lib/user_router.rb
class UserRouter
include Rails.application.routes.url_helpers
def initialize(user)
#user = user
route_user_by_role
end
def route_user_by_role
if #user.is_pt_master?
pt_master_root_url
elsif #user.is_traveler?
traveler_root_url
elsif #user.client_user_role?
route_client_by_role
else
root_url
end
end
def route_client_by_role
if #user.is_super_admin?
for_super_admin
else
for_other_admin
end
end
def for_super_admin
if #user.client_account.blank?
edit_client_account_url
else
client_root_url
end
end
def for_other_admin
if #user.is_first_sign_in? || #user.client_user_info.blank?
edit_client_user_info_url(#user)
else
client_root_url
end
end
end
I am using _url because if I use _path I get a .to_model error, but with _url I am getting Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true
I have already set default host in config/environments to be localhost:3000. Any help with how to do this would be greatly appreciated.
Add default_url_options to UserRouter:
class UserRouter
include Rails.application.routes.url_helpers
def self.default_url_options
{host: 'localhost:3000'}
end
def initialize(user)
#user = user
route_user_by_role
end
def route_user_by_role
if #user.is_pt_master?
pt_master_root_url
elsif #user.is_traveler?
traveler_root_url
elsif #user.client_user_role?
route_client_by_role <= breaks here with undefined method error
else
root_url
end
end
def route_client_by_role
if #user.is_super_admin?
for_super_admin
else
for_other_admin
end
end
def for_super_admin
if #user.client_account.blank?
edit_client_account_url
else
client_root_url
end
end
def for_other_admin
if #user.is_first_sign_in? || #user.client_user_info.blank?
edit_client_user_info_url(#user)
else
client_root_url
end
end
end
So I wasn't able to do it as Class in /lib/ like I wanted to, but pulling it out into a module as a Controller concern allowed me to keep the logic separate from the ApplicationController like I wanted to, its not ideal, but better than stuffing it all into App Cntrl.
class ApplicationController < ActionController::Base
include UserRouter
...
def after_sign_in_path_for(user)
initialize_user_router(user)
end
...
end
module UserRouter
def initialize_user_router(user)
#user = user
direct_user_by_role
end
def direct_user_by_role
if #user.is_pt_master?
pt_master_root_path
elsif #user.is_traveler?
direct_traveler
elsif #user.client_user_role?
direct_client_by_role
else
root_path
end
end
def direct_traveler
...
traveler_root_path
end
def direct_client_by_role
if #user.is_super_admin?
direct_super_admins
else
direct_other_admins
end
end
def direct_super_admins
if #user.client_account.blank?
edit_client_account_path
else
client_root_path
end
end
def direct_other_admins
if #user.first_sign_in? || #user.client_user_info.blank?
edit_client_user_info_path(#user)
else
client_root_path
end
end
end
I have two layouts Admin and Domain. And I don't need any extra configuration in Admin layout. but if user tries to access Domain layout they must be in their valid domain.
This means that, I need to customize all of my Domain policy to include both current_user as well as current_domain. I found this can be done with UserContext and pundit_user... so here is what I have done:
application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def pundit_user
UserContext.new(current_user, current_domain)
end
def after_sign_out_path_for(resource)
root_path
end
def current_domain
#current_domain ||= Domain.where(name: requested_domain).first
end
helper_method :current_domain
private
def requested_domain
return request.env["SERVER_NAME"]
end
def user_not_authorized
# reset_session
flash[:alert] = "You are not authorized to perform this action"
redirect_to(request.referrer || root_path)
end
end
Note that, when I access Admin layout, current_domain will be nil and if I visit any routes of Domain layout, then current_domain will set to currently accessing domain.
user_context.rb
class UserContext
attr_reader :current_user, :current_domain
def initialize(current_user, current_domain)
#current_user = current_user
#current_domain = current_domain
end
end
PROBLEM
Suppose I have this policy:
user_policy.rb
class UserPolicy < ApplicationPolicy
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def index?
binding.pry # debugging
current_user.admin? ||
current_user.domain == current_domain
end
private
def current_user
# return user.is_a?(User) ? user : user.current_user
user.current_user
end
def current_domain
# return user.is_a?(User) ? nil : user.current_domain
user.current_domain
end
end
when application runs current_user and current_domain must available in UserPolicy as per documentation(https://github.com/elabs/pundit#additional-context).
But I am getting
undefined method `current_user' for #<User:0x007fcefbc2b150>
That means, still I have user object in it, not user.current_user and user.current_domain
Please let me know, if you need further description. What am I missing here?
It was my own dumb mistake.
PROBLEM
I had a before_filter call in domain/base_controller.rb something like:
class Domain::BaseController < ApplicationController
before_action :authenticate_user!
before_action :domain_exists?
before_action :verify_domain!
private
def verify_domain!
# PROBLEM: this line was updating pundit_user again to user object
raise Pundit::NotAuthorizedError unless DomainConsolePolicy.new(current_user, current_domain).authorized?
end
def domain_exists?
if current_domain.blank?
redirect_to root_path, alert: 'Domain that you provided is not valid or is permanently removed!'
end
end
end
SOLUTION:
I have used headless policy for this because now I have both current_user and current_domain set with pundit_user in application_controller
domain/base_controller.rb
class Domain::BaseController < ApplicationController
before_action :authenticate_user!
before_action :domain_exists?
before_action :verify_domain!
private
def verify_domain!
# SOLUTION
authorize :domain_console, :has_access?
end
def domain_exists?
if current_domain.blank?
redirect_to root_path, alert: 'Domain that you provided is not valid or is permanently removed!'
end
end
end
policy/domain_console_policy.rb
class DomainConsolePolicy < Struct.new(:user, :domain_console)
def has_access?
user.current_user.admin? ||
user.current_user.domain_id == user.current_domain.id
end
end
Thanks
I am not able to solve this syntax error I have on permission.rb. It says it needs a extra "end" but when I do add it Safari is unable to load the page. I have tried several different methods on both files, none seem to work. Any ideas?
Error:
SyntaxError in UsersController#new
/Users/lexi87/dating/app/controllers/application_controller.rb:20: syntax error, unexpected keyword_end, expecting end-of-input
Rails.root: /Users/lexi87/dating
Application Trace | Framework Trace | Full Trace
app/controllers/users_controller.rb:1:in `<top (required)>'
permission.rb (without the extra 'end'):
class Permission < Struct.new(:user)
def allow?(controller, action)
if user.nil?
controller == "galleries" && action.in?(%w[index show])
elsif user.admin?
true
else
controller == "galleries" && action != "destroy"
end
end
application_controller:
class ApplicationController < ActionController::Base
protect_from_forgery
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def current_permission
#current_permission || ::Permission.new(current_user)
end
end
def authorize
if !current_permission.allow?(params[:controller], params[:action])
redirect_to root_url, alert: "Not authorized."
end
end
end
UPDATE
Here's my users_controller:
class UsersController < ApplicationController
before_filter :authorize
def new
#user = User.new
end
def profile
#profile = User.profile
end
def create
#user = User.new(params[:user])
if #user.save
UserMailer.registration_confirmation(#user).deliver
session[:user_id] = #user.id
redirect_to root_url, notice: "Thank you for signing up!"
else
render "new"
end
end
def show
#user = User.find(params[:id])
end
def edit
#user = User.find(params[:id])
end
def index
#users = User.all
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted."
redirect_to users_url
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Account updated"
redirect_to #user
authorize! :update, #user
else
render 'edit'
end
end
end
It looks like you need another end in permission.rb and you need to move one in application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def current_permission
#current_permission || ::Permission.new(current_user)
end
end # this shouldn't be here
def authorize
if !current_permission.allow?(params[:controller], params[:action])
redirect_to root_url, alert: "Not authorized."
end
end
# it should be here
corrections
1.
You definetely need the end at the end of the permission.rb
2.
you do not need and end to end the private section of ApplicationController. Everything below the private keyword is considered 'private'. So you need to move the "authorize" method.
solution
So the complete code is (with moving one method into "public"):
permission.rb:
class Permission < Struct.new(:user)
def allow?(controller, action)
if user.nil?
controller == "galleries" && action.in?(%w[index show])
elsif user.admin?
true
else
controller == "galleries" && action != "destroy"
end
end
end
application_controller:
class ApplicationController < ActionController::Base
protect_from_forgery
def authorize
if !current_permission.allow?(params[:controller], params[:action])
redirect_to root_url, alert: "Not authorized."
end
end
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def current_permission
#current_permission || ::Permission.new(current_user)
end
end
You don't have to close the private keyword.
The end after
def current_permission
is not necessary !