As I was investigating an approach for #16488, I stumbled upon two methods partially doing the same thing. This helps consolidate the logic and provides some guiding documentation.
87 lines
2.7 KiB
Ruby
87 lines
2.7 KiB
Ruby
module Api
|
|
module V0
|
|
class ApiController < ApplicationController
|
|
protect_from_forgery with: :exception, prepend: true
|
|
|
|
include ValidRequest
|
|
|
|
respond_to :json
|
|
|
|
rescue_from ActionController::ParameterMissing do |exc|
|
|
error_unprocessable_entity(exc.message)
|
|
end
|
|
|
|
rescue_from ActiveRecord::RecordInvalid do |exc|
|
|
error_unprocessable_entity(exc.message)
|
|
end
|
|
|
|
rescue_from ActiveRecord::RecordNotFound, with: :error_not_found
|
|
|
|
rescue_from Pundit::NotAuthorizedError, with: :error_unauthorized
|
|
|
|
protected
|
|
|
|
def error_unprocessable_entity(message)
|
|
render json: { error: message, status: 422 }, status: :unprocessable_entity
|
|
end
|
|
|
|
def error_unauthorized
|
|
render json: { error: "unauthorized", status: 401 }, status: :unauthorized
|
|
end
|
|
|
|
def error_not_found
|
|
render json: { error: "not found", status: 404 }, status: :not_found
|
|
end
|
|
|
|
def authenticate!
|
|
@user = authenticated_user
|
|
return error_unauthorized unless @user && !@user.suspended?
|
|
end
|
|
|
|
def authorize_super_admin
|
|
error_unauthorized unless @user.super_admin?
|
|
end
|
|
|
|
# Checks if the user is authenticated, sets @user to nil otherwise
|
|
#
|
|
# @return [User, NilClass]
|
|
def authenticate_with_api_key_or_current_user
|
|
@user = authenticate_with_api_key || current_user
|
|
end
|
|
|
|
# Checks if the user is authenticated, if not respond with an HTTP 401 Unauthorized
|
|
#
|
|
# @see {authenticate_with_api_key_or_current_user}
|
|
def authenticate_with_api_key_or_current_user!
|
|
# [@jeremyf] Note, I'm not relying on the other method setting the instance variable, but
|
|
# instead relying on the returned value. This insulates us from an implementation detail
|
|
# (namely should we use @user or current_user, which is a bit soupy in the API controller).
|
|
user = authenticate_with_api_key_or_current_user
|
|
error_unauthorized unless user
|
|
end
|
|
|
|
private
|
|
|
|
def authenticate_with_api_key
|
|
api_key = request.headers["api-key"]
|
|
return unless api_key
|
|
|
|
api_secret = ApiSecret.includes(:user).find_by(secret: api_key)
|
|
return unless api_secret
|
|
|
|
# guard against timing attacks
|
|
# see <https://www.slideshare.net/NickMalcolm/timing-attacks-and-ruby-on-rails>
|
|
secure_secret = ActiveSupport::SecurityUtils.secure_compare(api_secret.secret, api_key)
|
|
return api_secret.user if secure_secret
|
|
end
|
|
|
|
def authenticated_user
|
|
if request.headers["api-key"]
|
|
authenticate_with_api_key
|
|
elsif current_user
|
|
current_user
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|