Skip to content

Commit

Permalink
add site_admin api jwt
Browse files Browse the repository at this point in the history
  • Loading branch information
celuchmarek committed Dec 11, 2023
1 parent 6dca425 commit b6899d6
Show file tree
Hide file tree
Showing 21 changed files with 192 additions and 68 deletions.
28 changes: 0 additions & 28 deletions app/controllers/api/admin/boxes_controller.rb

This file was deleted.

16 changes: 16 additions & 0 deletions app/controllers/api/site_admin/boxes_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class Api::SiteAdmin::BoxesController < Api::SiteAdminController

def create
@box = @tenant.boxes.new(box_params)
return if @box.save

render :error, status: :unprocessable_entity
log_api_call(:create_tenant_box_api_called)
end

private

def box_params
params.require(:box).permit(:id, :name, :short_name, :uri, :color, :api_connection_id)
end
end
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
class Api::Stats::TenantsController < ActionController::Base
include AuditableApiEvents
class Api::SiteAdmin::Stats::TenantsController < Api::SiteAdminController
before_action :set_tenant
rescue_from ActiveRecord::RecordNotFound, with: :save_exception
rescue_from ActionController::ParameterMissing, with: :save_exception

def users_count
@users_count = @tenant.users.count
render :error, status: :unprocessable_entity unless @tenant
puts "no exception"
end

def messages_per_period
Expand All @@ -25,18 +23,4 @@ def messages_count
@messages_count = Message.joins(thread: :box).where(box: { tenant_id: @tenant.id }).count
render :error, status: :unprocessable_entity unless @tenant
end

private

def set_tenant
@tenant = Tenant.find(params[:tenant_id])
end

def tenant_params
params.require(:tenant).permit(:name, { admin: [:name, :email] })
end

def save_exception(exception)
@exception = exception
end
end
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
class Api::Admin::TenantsController < ActionController::Base
include AuditableApiEvents
class Api::SiteAdmin::TenantsController < Api::SiteAdminController
before_action :set_tenant, only: %i[destroy]
rescue_from ActiveRecord::RecordNotFound, with: :save_exception
rescue_from ActionController::ParameterMissing, with: :save_exception

def create
@tenant, @admin, @group_membership = Tenant.create_with_admin(tenant_params)
Expand All @@ -19,15 +16,7 @@ def destroy

private

def set_tenant
@tenant = Tenant.find(params[:id])
end

def tenant_params
params.require(:tenant).permit(:name, { admin: [:name, :email] })
end

def save_exception(exception)
@exception = exception
end
end
7 changes: 7 additions & 0 deletions app/controllers/api/site_admin_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Api::SiteAdminController < ApiController
private

def authenticate_user
ApiEnvironment.site_admin_token_authenticator.verify_token(authenticity_token)
end
end
2 changes: 2 additions & 0 deletions app/controllers/api/tenant/empty_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Api::Tenant::EmptyController < Api::TenantController
end
7 changes: 7 additions & 0 deletions app/controllers/api/tenant_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Api::TenantController < ApiController
private

def authenticate_user
ApiEnvironment.tenant_token_authenticator.verify_token(authenticity_token)
end
end
89 changes: 89 additions & 0 deletions app/controllers/api_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
class ApiController < ActionController::API
include AuditableApiEvents

before_action :authenticate_user
around_action :wrap_in_request_logger

rescue_from JWT::DecodeError do |error|
if error.message == 'Nil JSON web token'
render_bad_request(:no_credentials)
else
key = error.message == 'obo' ? :obo : :credentials
render_unauthorized(key)
end
end

rescue_from RestClient::Exceptions::Timeout, with: :render_request_timeout
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
rescue_from ActionController::ParameterMissing, with: :render_unprocessable_entity

private

def authenticity_token
(ActionController::HttpAuthentication::Token.token_and_options(request)&.first || params[:token])&.squish.presence
end

def set_tenant
@tenant = Tenant.find(params.require(:id))
end


def raise_with_resource_details(error = $!, resource, id, **options)
error.resource = [resource, options.merge(id: id)] if error.respond_to?(:resource=)
raise error
end

def log_request(error = nil)
# TODO save the log somewhere
end

def wrap_in_request_logger
yield
rescue Error => error
log_request(error) and raise(error)
else
log_request
end


def render_bad_request(key, **options)
render status: :bad_request, json: { message: "Bad request" }
end

def render_unauthorized(key = :credentials)
self.headers['WWW-Authenticate'] = 'Token realm="API"'
render status: :unauthorized, json: { message: "Unauthorized" }
end

def render_unpermitted_param(**options)
render status: :unprocessable_entity, json: { message: "Unprocessable entity" }
end

def render_forbidden_no_key
render status: :forbidden, json: { message: "Forbidden" }
end

def render_forbidden(key, **options)
render status: :forbidden, json: { message: "Forbidden" }
end

def render_not_found(key, **options)
render status: :not_found, json: { message: "Not found" }
end

def render_request_timeout
render status: :request_timeout, json: { message: "request timeout" }
end

def render_too_many_requests
render status: :too_many_requests, json: { message: "Too many requests" }
end

def render_internal_server_error
render status: :internal_server_error, json: { message: "Internal server error" }
end

def render_service_unavailable_error
render status: :service_unavailable, json: { message: "Service unavailable" }
end
end
23 changes: 23 additions & 0 deletions app/lib/api_environment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module ApiEnvironment
extend self

def tenant_token_authenticator
@tenant_token_authenticator ||= ApiTokenAuthenticator.new(
public_key_reader: API_TENANT_PUBLIC_KEY_READER,
return_handler: API_TENANT_BY_IDENTITY_FINDER,
)
end

def site_admin_token_authenticator
@site_admin_token_authenticator ||= ApiTokenAuthenticator.new(
public_key_reader: API_SITE_ADMIN_PUBLIC_KEY_READER,
return_handler: -> (sub) { 0 },
)
end


API_TENANT_PUBLIC_KEY_READER = -> (sub) { OpenSSL::PKey::RSA.new(API_TENANT_BY_IDENTITY_FINDER.call(sub).api_token_public_key) }
API_TENANT_BY_IDENTITY_FINDER = -> (sub) { Tenant.feature_enable(:api).find_by!(tenant_id: sub) }

API_SITE_ADMIN_PUBLIC_KEY_READER = -> (sub) { OpenSSL::PKey::RSA.new(ENV.fetch('SITE_ADMIN_API_PUBLIC_KEY')) }
end
30 changes: 30 additions & 0 deletions app/lib/api_token_authenticator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class ApiTokenAuthenticator
MAX_EXP_IN = 120.minutes
JTI_PATTERN = /\A[0-9a-z\-_]{32,256}\z/i

def initialize(public_key_reader:, return_handler:)
@public_key_reader = public_key_reader
@return_handler = return_handler
end

def verify_token(token)
options = {
algorithm: 'RS256',
verify_jti: -> (jti) { jti =~ JTI_PATTERN },
}

key_finder = -> (_, payload) do
@public_key_reader.call(payload['sub'])
rescue
raise JWT::InvalidSubError
end

payload, _ = JWT.decode(token, nil, true, options, &key_finder)
sub, exp, jti = payload['sub'], payload['exp'], payload['jti']

raise JWT::ExpiredSignature unless exp.is_a?(Integer)
raise JWT::InvalidPayload if exp > (Time.now + MAX_EXP_IN).to_i

@return_handler.call(sub)
end
end
2 changes: 1 addition & 1 deletion app/models/tenant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Tenant < ApplicationRecord

validates_presence_of :name

AVAILABLE_FEATURE_FLAGS = [:audit_log]
AVAILABLE_FEATURE_FLAGS = [:audit_log, :api]

def draft_tag!
draft_tag || raise(ActiveRecord::RecordNotFound.new("`DraftTag` not found in tenant: #{self.id}"))
Expand Down
23 changes: 14 additions & 9 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,22 @@
end

namespace :api do
namespace :admin do
resources :tenants do
resources :boxes
namespace :site_admin do
namespace :stats do
resources :tenants, only: [] do
member do
get :users_count
get :messages_per_period
get :messages_count
end
end
end

resources :tenants
resources :boxes
end
namespace :stats do
resources :tenants, only: [] do
get :users_count
get :messages_per_period
get :messages_count
end

namespace :tenant do
end
end

Expand Down

0 comments on commit b6899d6

Please sign in to comment.