Skip to content

Commit

Permalink
POC(api_key): cache api key and organization
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-pochet committed Feb 6, 2025
1 parent ae937fc commit d262623
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 8 deletions.
6 changes: 4 additions & 2 deletions app/controllers/api/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ class BaseController < ApplicationController
def authenticate
return unauthorized_error unless auth_token

@current_api_key = ApiKey.find_by(value: auth_token)
result = ApiKeys::FetchService.call(auth_token)
return unauthorized_error if result.failure?

@current_api_key = result.api_key
return unauthorized_error unless current_api_key

@current_organization = current_api_key.organization
@current_organization = result.organization
true
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/v1/subscriptions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def render_subscription(subscription)
render(
json: ::V1::SubscriptionSerializer.new(
subscription,
root_name: 'subscription',
root_name: "subscription",
includes: %i[plan]
)
)
Expand Down
6 changes: 5 additions & 1 deletion app/models/api_key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ApiKey < ApplicationRecord

default_scope { active }

scope :active, -> { where('expires_at IS NULL OR expires_at > ?', Time.current) }
scope :active, -> { where("expires_at IS NULL OR expires_at > ?", Time.current) }
scope :non_expiring, -> { where(expires_at: nil) }

def permit?(resource, mode)
Expand All @@ -38,6 +38,10 @@ def self.default_permissions
RESOURCES.index_with { MODES.dup }
end

def expired?(time = Time.current)
expires_at.present? && expires_at < time
end

private

def permissions_keys_compliance
Expand Down
1 change: 1 addition & 0 deletions app/services/api_keys/destroy_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def call
api_key.touch(:expires_at) # rubocop:disable Rails/SkipsModelValidations

ApiKeyMailer.with(api_key:).destroyed.deliver_later
Rails.cache.delete("api_key/#{api_key.value}")

result.api_key = api_key
result
Expand Down
54 changes: 54 additions & 0 deletions app/services/api_keys/fetch_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

module ApiKeys
class FetchService < BaseService
Result = BaseResult[:api_key, :organization]

def initialize(auth_token)
@auth_token = auth_token
super
end

def call
api_key_json = Rails.cache.read("api_key/#{auth_token}")
if api_key_json
data = JSON.parse(api_key_json)
api_key = ApiKey.instantiate(data["api_key"].slice(*ApiKey.column_names))

unless api_key.expired?
result.organization = Organization.instantiate(data["organization"].slice(*Organization.column_names))
result.api_key = api_key
return result
end
end

api_key = ApiKey.includes(:organization).find_by(value: auth_token)
write_to_cache(api_key) if api_key

result.api_key = api_key
result.organization = api_key&.organization
result
end

private

attr_reader :auth_token

def write_to_cache(api_key)
expiration = if api_key.expires_at
(api_key.expires_at - Time.current).to_i.seconds
else
1.hour
end

Rails.cache.write(
"api_key/#{auth_token}",
{
organization: api_key.organization.attributes,
api_key: api_key.attributes
}.to_json,
expires_in: expiration
)
end
end
end
1 change: 1 addition & 0 deletions app/services/api_keys/rotate_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def call
api_key.update!(expires_at:)
end

Rails.cache.delete("api_key/#{api_key.value}")
ApiKeyMailer.with(api_key:).rotated.deliver_later

result.api_key = new_api_key
Expand Down
1 change: 1 addition & 0 deletions app/services/api_keys/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def call
end

api_key.update!(params.slice(:name, :permissions))
Rails.cache.delete("api_key/#{api_key.value}")

result.api_key = api_key
result
Expand Down
16 changes: 12 additions & 4 deletions app/services/organizations/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

module Organizations
class UpdateService < BaseService
Result = BaseResult[:organization]

def initialize(organization:, params:)
@organization = organization
@params = params
Expand Down Expand Up @@ -56,6 +58,8 @@ def call
assign_premium_attributes
handle_base64_logo if params.key?(:logo)

expire_api_keys_cache

organization.save!

result.organization = organization
Expand All @@ -80,24 +84,24 @@ def assign_premium_attributes
def handle_base64_logo
return if params[:logo].blank?

base64_data = params[:logo].split(',')
base64_data = params[:logo].split(",")
data = base64_data.second
decoded_base_64_data = Base64.decode64(data)

# NOTE: data:image/png;base64, should give image/png content_type
content_type = base64_data.first.split(';').first.split(':').second
content_type = base64_data.first.split(";").first.split(":").second

organization.logo.attach(
io: StringIO.new(decoded_base_64_data),
filename: 'logo',
filename: "logo",
content_type:
)
end

def handle_eu_tax_management(eu_tax_management)
trying_to_enable_eu_tax_management = params[:eu_tax_management] && !organization.eu_tax_management
if !organization.eu_vat_eligible? && trying_to_enable_eu_tax_management
result.single_validation_failure!(error_code: 'org_must_be_in_eu', field: :eu_tax_management)
result.single_validation_failure!(error_code: "org_must_be_in_eu", field: :eu_tax_management)
.raise_if_error!
end

Expand All @@ -106,5 +110,9 @@ def handle_eu_tax_management(eu_tax_management)

organization.eu_tax_management = eu_tax_management
end

def expire_api_keys_cache
organization.api_keys.each { Rails.cache.delete("api_key/#{_1.value}") }
end
end
end

0 comments on commit d262623

Please sign in to comment.