Пользовательская аутентификация Rails 5 API: 'skip_before_action' на GET-маршрутах - PullRequest
0 голосов
/ 07 мая 2019

Я использовал эту серию в качестве отправной точки для бэкенда Rails для веб-сайта портфолио работ.Адаптация была в основном простой, и она делает то, что я хочу.Одна большая проблема заключается в том, что «index» и «show» (действия чтения) должны быть доступны без аутентификации, в то время как «create», «update» и «delete» (действия записи) должны требовать допустимого JWT.

Следуя подходу, используемому для исключения маршрутов регистрации и входа в систему из аутентификации, я попытался

skip_before_action :authorize_request, only: [:index, :show]

в соответствующем контроллере.Это, однако, приведет к аварийному завершению работы приложения с

NoMethodError (undefined method `works' for nil:NilClass):
app/controllers/works_controller.rb:10:in `index'

Хотя проблема кажется очевидной - если пропустить действие аутентификации, класс не будет создан, - по крайней мере для меня это не исправление.Может ли кто-нибудь помочь, пожалуйста?

Код для проекта здесь .

Контроллер приложений

class ApplicationController < ActionController::API
  include Response
  include ExceptionHandler

  # called before every action on controllers
  before_action :authorize_request
  attr_reader :current_user

  private

  # Check for valid request token and return user
  def authorize_request
    @current_user = (AuthorizeApiRequest.new(request.headers).call)[:user]
  end
end

Контроллер «Works»

class WorksController < ApplicationController

  #skip_before_action :authorize_request, only: [:index, :show]

  before_action :set_work, only: [:show, :update, :destroy]

  # GET /works
  def index
    @works = current_user.works
    json_response(@works)
  end

  # POST /works
  def create
    @work = current_user.works.create!(work_params)
    json_response(@work, :created)
  end

  # GET /works/:id
  def show
    json_response(@work)
  end

  # PUT /works/:id
  def update
    @work.update(work_params)
    head :no_content
  end

  # DELETE /works/:id
  def destroy
    @work.destroy
    head :no_content
  end

  private

  def work_params
    # whitelist params
    params.permit(:title, :nature, :role, :client, :timeframe, :description, :images, :url, :blog_post)
  end

  def set_work
    @work = Work.find(params[:id])
  end
end

Контроллер «Users»

class UsersController < ApplicationController

  skip_before_action :authorize_request, only: :create

  def create
    user = User.create!(user_params)
    auth_token = AuthenticateUser.new(user.username, user.password).call
    response = { message: Message.account_created, access_token: auth_token }
    json_response(response, :created)
  end

  def show
    json_response(username: current_user.username)
  end

  private

  def user_params
    params.permit(
      :username,
      :password,
      :password_confirmation
    )
  end
end

Контроллер «Authentication»

class AuthenticationController < ApplicationController

  skip_before_action :authorize_request, only: :authenticate

  # return auth token once user is authenticated
  def authenticate
    auth_token =
      AuthenticateUser.new(auth_params[:username], auth_params[:password]).call
    json_response(access_token: auth_token)
  end

  private

  def auth_params
    params.permit(:username, :password)
  end
end

Помощник AuthenticateUser

class AuthenticateUser
  def initialize(username, password)
    @username = username
    @password = password
  end

  # Service entry point
  def call
    JsonWebToken.encode(user_id: user.id) if user
  end

  private

  attr_reader :username, :password

  # verify user credentials
  def user
    user = User.find_by(username: username)
    return user if user && user.authenticate(password)
    # raise Authentication error if credentials are invalid
    raise(ExceptionHandler::AuthenticationError, Message.invalid_credentials)
  end
end

Помощник AuthorizeApiRequest

class AuthorizeApiRequest
  def initialize(headers = {})
    @headers = headers
  end

  # Service entry point - return valid user object
  def call
    {
      user: user
    }
  end

  private

  attr_reader :headers

  def user
    # check if user is in the database
    # memoize user object
    @user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
    # handle user not found
  rescue ActiveRecord::RecordNotFound => e
    # raise custom error
    raise(
      ExceptionHandler::InvalidToken,
      ("#{Message.invalid_token} #{e.message}")
    )
  end

  # decode authentication token
  def decoded_auth_token
    @decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
  end

  # check for token in `Authorization` header
  def http_auth_header
    if headers['Authorization'].present?
      return headers['Authorization'].split(' ').last
    end
      raise(ExceptionHandler::MissingToken, Message.missing_token)
  end
end

Помощник ExceptionHandler

module ExceptionHandler
  extend ActiveSupport::Concern

  # Define custom error subclasses - rescue catches `StandardErrors`
  class AuthenticationError < StandardError; end
  class MissingToken < StandardError; end
  class InvalidToken < StandardError; end

  included do
    # Define custom handlers
    rescue_from ActiveRecord::RecordInvalid, with: :four_twenty_two
    rescue_from ExceptionHandler::AuthenticationError, with: :unauthorized_request
    rescue_from ExceptionHandler::MissingToken, with: :four_twenty_two
    rescue_from ExceptionHandler::InvalidToken, with: :four_twenty_two

    rescue_from ActiveRecord::RecordNotFound do |e|
      json_response({ message: e.message }, :not_found)
    end
  end

  private

  # JSON response with message; Status code 422 - unprocessable entity
  def four_twenty_two(e)
    json_response({ message: e.message }, :unprocessable_entity)
  end

  # JSON response with message; Status code 401 - Unauthorized
  def unauthorized_request(e)
    json_response({ message: e.message }, :unauthorized)
  end
end

1 Ответ

1 голос
/ 07 мая 2019

Сообщение об ошибке гласит:

NoMethodError (undefined method `works' for nil:NilClass):
app/controllers/works_controller.rb:10:in `index'

Или, чтобы перевести это, в строке 10 файла works_controller.rb мы вызываем метод с именем works для nil, который выбрасываетошибка.

Если предположить, что строка 10 works_controller равна

@works = current_user.works

Тогда сообщение об ошибке говорит нам, что мы вызываем works для nil, т.е. у нас нет current_user.

Либо когда вы назначаете этот код, он работает неправильно, либо вы получаете доступ к этой части кода без входа в систему и не кодировали ее.В любом случае, переменная current_user возвращает ноль и не должна быть.

...