Class: Retailer::CallbackTokenService

Inherits:
Object
  • Object
show all
Defined in:
app/services/retailer/callback_token_service.rb

Overview

Generates and validates time-limited tokens for Oxylabs callback authentication.
Tokens are embedded in the callback URL to prevent unauthorized submissions.

Tokens are signed with ActiveSupport::MessageVerifier (Rails primitive)
under the namespaced verifier :oxylabs_callback and the explicit purpose
tag 'oxylabs_callback/v1' — defense in depth against a token signed for
a different feature being replayed against this webhook.

No legacy decoder: this service previously used JWT.encode / JWT.decode,
but those tokens cross neither verifier (different signature format). At
deploy time, any Oxylabs job already in flight will lose its callback
(the price-check itself completes on Oxylabs' side; we just never ingest
the WebhookLog). Recovery is to re-trigger the affected price-check from
the CatalogItem.

Uses API_HOSTNAME_WITHOUT_PORT constant for environment-aware URLs:

  • Production: api.warmlyyours.com
  • Staging: api.warmlyyours.ws
  • Development: Uses dev tunnel (api-hostname.warmlyyours.dev)

Examples:

Generate a callback URL with token

Retailer::CallbackTokenService.callback_url(catalog_item_id: 123)

Validate a token

Retailer::CallbackTokenService.valid_token?(params[:token])

Constant Summary collapse

TOKEN_EXPIRY =

Token expiry.

24.hours
PURPOSE =

Verifier purpose tag.

'oxylabs_callback/v1'

Class Method Summary collapse

Class Method Details

.callback_url(catalog_item_id: nil) ⇒ String

Generate a callback URL for production/staging.
Uses API_HOSTNAME_WITHOUT_PORT constant for environment-aware URLs.

Parameters:

  • catalog_item_id (Integer, nil) (defaults to: nil)

    Optional catalog item ID to embed in URL

Returns:

  • (String)

    Complete callback URL



63
64
65
66
67
68
# File 'app/services/retailer/callback_token_service.rb', line 63

def callback_url(catalog_item_id: nil)
  token = generate_token
  url = "https://#{API_HOSTNAME_WITHOUT_PORT}/webhooks/v1/oxylabs?token=#{CGI.escape(token)}"
  url += "&catalog_item_id=#{catalog_item_id}" if catalog_item_id
  url
end

.dev_callback_url(catalog_item_id: nil) ⇒ String

Generate a dev callback URL for testing via Cloudflare tunnel.
Uses hostname-specific subdomain: api-hostname.warmlyyours.dev

Parameters:

  • catalog_item_id (Integer, nil) (defaults to: nil)

    Optional catalog item ID to embed in URL

Returns:

  • (String)

    Complete dev callback URL



74
75
76
77
78
79
80
# File 'app/services/retailer/callback_token_service.rb', line 74

def dev_callback_url(catalog_item_id: nil)
  hostname = `hostname -s`.strip.downcase
  token = generate_token
  url = "https://api-#{hostname}.warmlyyours.dev/webhooks/v1/oxylabs?token=#{CGI.escape(token)}"
  url += "&catalog_item_id=#{catalog_item_id}" if catalog_item_id
  url
end

.generate_tokenString

Generate a verifier-signed token for callback authentication.

Returns:

  • (String)

    signed token



38
39
40
41
# File 'app/services/retailer/callback_token_service.rb', line 38

def generate_token
  payload = { 'iat' => Time.current.to_i }
  verifier.generate(payload, expires_in: TOKEN_EXPIRY, purpose: PURPOSE)
end

.valid_token?(token) ⇒ Boolean

Check if a token is valid.

Parameters:

  • token (String)

    The token to check

Returns:

  • (Boolean)


55
56
57
# File 'app/services/retailer/callback_token_service.rb', line 55

def valid_token?(token)
  validate_token(token).present?
end

.validate_token(token) ⇒ Hash?

Validate and decode a token.

Parameters:

  • token (String)

    The token to validate

Returns:

  • (Hash, nil)

    The decoded payload (string-keyed) if valid, nil otherwise



46
47
48
49
50
# File 'app/services/retailer/callback_token_service.rb', line 46

def validate_token(token)
  return nil if token.blank?

  verifier.verified(token, purpose: PURPOSE)
end