ApplicationController
that defines methods to authorize requests based on JWT, also defines Error handling
class ApplicationController < ActionController::API
before_action :authenticate
before_action :set_current_user_refs
before_action :authorize
class NotAuthorized < StandardError
end
rescue_from NotAuthorized, with: :not_authorized
class InsufficientParams < StandardError
end
rescue_from InsufficientParams, with: :insufficient_params
class InternalServerError < StandardError
end
rescue_from InternalServerError, with: :internal_server_error
def ping
render json: { res: "pong" }
end
def not_found
render json: { error: "not found" }, status: 404
end
def authenticate
render json: { error: "unauthorized" }, status: 401 unless valid_id_token?
end
def valid_id_token?
return false unless auth_header_present?
verify_id_token(get_id_token)
end
private
def auth_header_present?
!!request.env.fetch("HTTP_AUTHORIZATION", "").scan(/Bearer/).flatten.first
end
def get_id_token
request.env["HTTP_AUTHORIZATION"].scan(/Bearer (.*)$/).flatten.last
end
def verify_id_token(id_token)
begin
cognito_jwks = JSON.parse(File.read(Rails.root.join('config/jwks.json')))
# cognito_jwks = {"keys"=>[{"alg"=>"RS256", "e"=>"AQAB", "kid"=>"6YPwROJiWaClmWi7Q4okTGIgJS78p35ls7Y3eNMq1gg=", "kty"=>"RSA", "n"=>"tOLjgUMsT29AavKD0LoKtRIxvtIH9_KhrgNUh5NLYGqRxBPyCsLio7gqiCo52WZ1rbnGfDkcbjEr6UfVkAJ0Ym1XaSyt-t0DGHjO_MTn44HE6uD95690I8xP02r2IQrgbUl9aUGjg1KCRf6vcPr-0DtxUN3pKQcw55ODG3O56imCQvQfw9Q66l8nGKnd3cqjwsSeG2BXRMQOEHPDjVdocTyK37-xwhiJNWtnauPuUa2_XVLKia9PRqjRkdysc68Oh_N7HD-JkfyflIqnNkRsWQYYOEs3BRGis_ib-nr75uKHrCQR9hG6CmvlG6V07u0NGtkCjyaCuzCJo-KYim2tOQ", "use"=>"sig"}, {"alg"=>"RS256", "e"=>"AQAB", "kid"=>"o6GR+dnhKzcl2mreFN9OE2+sMxRitKnKCe0pGIOTFeI=", "kty"=>"RSA", "n"=>"zKvF0uq5xj4VpXoKy76jxsf_up2xHbyaniH-EvjFSuPP6mxpH3GiOFnKftOxxdp6MiEgMysYY5K5nVBq0uczUqp_OCVOCgXId2_CT1iop8Y2trpzAL8BG8MC7TpfbqajgPpOlUOrW2m8jCo3eYNDp1sOiI1JDDNDFYb03UXlKMJ6ZzABPPbB57lXBOHxqqDhfhSq6aebRXDHHfrt6hi4SeCK78UNq7rgpIoWIlfNPuKVq4aYOShs5hgRuwTrcZ32MkNfnJzNWmzm7_GfWEPoUFXBOCItZwX7HENUybD8QkdjpbDieyecyUp5EXgtM5jDoCMp_Y5no93cEhKYKxKlbw", "use"=>"sig"}]}
id_token_header = id_token.split('.')[0]
decoded_id_token_header = JSON.parse(Base64.decode64(id_token_header))
kid = decoded_id_token_header['kid']
# Find its corresponding jwk by kid
jwk = cognito_jwks["keys"].detect { |jwk| jwk["kid"] == kid }
jwk = JSON::JWK.new(jwk)
valid_jwt = JSON::JWT.decode(id_token, jwk)
groups = valid_jwt['cognito:groups']
if groups.include?('admin') or groups.include?('superadmin')
@current_user = {
id: valid_jwt[:sub],
iat: valid_jwt[:iat],
exp: valid_jwt[:exp],
group: valid_jwt['cognito:groups'][0]
# group: 'admin'
}
return true
else
@current_user = nil
return false
end
rescue JSON::JWS::VerificationFailed
@current_user = nil
return false
rescue JSON::JWT::InvalidFormat
@current_user = nil
return false
end
false
end
def set_current_user_refs
if @current_user[:group] == 'superadmin'
@current_user[:organization_id] = UsersOrganization.where(id: @current_user[:id]).pluck(:organization_id).first
raise NotAuthorized unless @current_user[:organization_id].present?
@current_user[:organization_unit_ids] = OrganizationUnit.where(organization_id: @current_user[:organization_id]).pluck(:id)
elsif @current_user[:group] == 'admin'
@current_user[:organization_unit_ids] = UsersOrganizationUnit.where(id: @current_user[:id]).pluck(:organization_unit_id)
raise NotAuthorized unless @current_user[:organization_unit_ids].count > 0
@current_user[:organization_id] = OrganizationUnit.where(id: @current_user[:organization_unit_ids][0]).pluck(:organization_id).first
end
end
def authorize
if params[:organization_id].present?
raise NotAuthorized unless @current_user[:organization_id] == params[:organization_id]
end
if params[:organization_unit_id].present?
raise NotAuthorized unless @current_user[:organization_unit_ids].include?(params[:organization_unit_id])
end
end
def not_authorized(e)
render json: { error: true, errorMsg: 'not authorized' }, status: :unauthorized
end
def insufficient_params(e)
render json: { error: true, errorMsg: 'insufficient params' }, status: :bad_request
end
def not_found(e)
render json: { error: true, errorMsg: 'not found' }, status: :not_found
end
def internal_server_error(e)
render json: { error: 'internal server error' }, status: :internal_server_error
end
end