Class: Pinterest::OauthService
- Inherits:
-
Object
- Object
- Pinterest::OauthService
- Defined in:
- app/services/pinterest/oauth_service.rb
Overview
Handles Pinterest OAuth 2.0 token exchange and refresh for WarmlyYours'
Pinterest integration: the advertiser (Marketing API) campaign sync that
drives PinterestCampaignSyncWorker, plus organic content management —
creating and updating boards and pins on the brand account.
System-level credential (account_id: nil) — one shared token for the
WarmlyYours Pinterest account, not a per-employee login.
Replaces the manual dev-portal "Generate token" advertiser_access_token,
which expired roughly every 30 days with no refresh path and produced the
recurring campaign-sync 401s (AppSignal #5228 / #5225).
Flow
- Crm::PinterestOauthController#authorize redirects an admin to
#authorization_url. - Pinterest redirects back to
/pinterest/oauth/callback?code=…. - #exchange_code! swaps the code for an access token + a continuous
refresh token, stored in OauthCredential. - Access tokens last 30 days; PinterestTokenRefreshWorker refreshes
well before expiry. The refresh token lasts 60 days and is reissued on
every refresh ("continuous"), so the credential renews indefinitely
with no further human action.
Defined Under Namespace
Classes: TokenRefreshError
Constant Summary collapse
- PROVIDER =
OAuth provider key stored on OauthCredential#provider.
'pinterest'- AUTHORIZE_URL =
Endpoint the admin's browser is redirected to for consent.
'https://www.pinterest.com/oauth/'- SCOPES =
Scopes requested during the OAuth consent flow, sent comma-separated.
Beyond the campaign sync's read-onlyads:read, this token also manages
WarmlyYours' organic Pinterest presence — creating/updating boards and
pins (including secret ones) and reading account analytics.Widening this list only affects new authorizations: an existing token
keeps its granted scope until the OAuth flow is re-run (the refresh
worker never expands scope). After changing it, reconnect via
/admin/oauth_credentials. %w[ ads:read user_accounts:read boards:read boards:read_secret boards:write boards:write_secret pins:read pins:read_secret pins:write pins:write_secret ].join(',')
Instance Method Summary collapse
-
#access_token! ⇒ String
Return a valid access token.
-
#authorization_url(state:) ⇒ String
Pinterest consent URL to redirect the admin to for one-time authorization.
-
#connected? ⇒ Boolean
Connected when either an OAuth credential is stored or a static advertiser_access_token is configured for this environment.
-
#connection_status ⇒ Hash
DB-only connection status (no HTTP) — safe to call on every page load.
-
#disconnect! ⇒ Object
Remove the stored credential.
-
#exchange_code!(code) ⇒ OauthCredential
Exchange the authorization code for tokens and persist them.
-
#healthy? ⇒ Boolean
Full health check — resolves a token (OAuth lazy-refresh or the static fallback) and verifies it with a live API call.
-
#initialize(account: nil) ⇒ OauthService
constructor
A new instance of OauthService.
-
#refresh! ⇒ OauthCredential
Exchange the stored refresh token for a fresh access + refresh token pair.
Constructor Details
#initialize(account: nil) ⇒ OauthService
Returns a new instance of OauthService.
57 58 59 60 61 |
# File 'app/services/pinterest/oauth_service.rb', line 57 def initialize(account: nil) @account = account @client_id = Heatwave::Configuration.fetch(:pinterest, :app_id) @client_secret = Heatwave::Configuration.fetch(:pinterest, :app_secret_key) end |
Instance Method Details
#access_token! ⇒ String
Return a valid access token. Prefers the OAuth credential (refreshing
lazily when expired); otherwise falls back to the environment's static
advertiser_access_token — used in dev/staging, and in production until
the OAuth flow has been connected.
115 116 117 118 119 120 121 |
# File 'app/services/pinterest/oauth_service.rb', line 115 def access_token! credential = OauthCredential.for(PROVIDER, account: @account) return static_token! unless credential refresh! if credential.token_expired? credential.reload.access_token end |
#authorization_url(state:) ⇒ String
Pinterest consent URL to redirect the admin to for one-time authorization.
67 68 69 70 71 72 73 74 75 76 |
# File 'app/services/pinterest/oauth_service.rb', line 67 def (state:) query = { client_id: @client_id, redirect_uri: redirect_uri, response_type: 'code', scope: SCOPES, state: state }.to_query "#{AUTHORIZE_URL}?#{query}" end |
#connected? ⇒ Boolean
Connected when either an OAuth credential is stored or a static
advertiser_access_token is configured for this environment.
127 128 129 |
# File 'app/services/pinterest/oauth_service.rb', line 127 def connected? OauthCredential.for(PROVIDER, account: @account).present? || static_token.present? end |
#connection_status ⇒ Hash
DB-only connection status (no HTTP) — safe to call on every page load.
135 136 137 138 139 140 141 142 143 144 |
# File 'app/services/pinterest/oauth_service.rb', line 135 def connection_status credential = OauthCredential.for(PROVIDER, account: @account) if credential { connected: true, healthy: credential.token_fresh?, credential: credential, via: :oauth } elsif static_token.present? { connected: true, healthy: true, credential: nil, via: :static } else { connected: false, healthy: false, credential: nil, via: nil } end end |
#disconnect! ⇒ Object
Remove the stored credential.
147 148 149 |
# File 'app/services/pinterest/oauth_service.rb', line 147 def disconnect! OauthCredential.destroy_by(provider: PROVIDER, account_id: @account&.id) end |
#exchange_code!(code) ⇒ OauthCredential
Exchange the authorization code for tokens and persist them.
83 84 85 86 87 88 89 90 91 92 93 |
# File 'app/services/pinterest/oauth_service.rb', line 83 def exchange_code!(code) response = token_request( grant_type: 'authorization_code', code: code, redirect_uri: redirect_uri, # Request a continuous (reissued-on-refresh) refresh token. Ignored # by apps created on/after 2025-09-25, which get it automatically. continuous_refresh: true ) persist_tokens!(response) end |
#healthy? ⇒ Boolean
Full health check — resolves a token (OAuth lazy-refresh or the static
fallback) and verifies it with a live API call. Makes HTTP requests;
use on admin pages, not hot paths.
156 157 158 159 160 |
# File 'app/services/pinterest/oauth_service.rb', line 156 def healthy? verify_api_access(access_token!) rescue TokenRefreshError, StandardError false end |
#refresh! ⇒ OauthCredential
Exchange the stored refresh token for a fresh access + refresh token pair.
99 100 101 102 103 104 105 106 |
# File 'app/services/pinterest/oauth_service.rb', line 99 def refresh! credential = OauthCredential.for(PROVIDER, account: @account) raise TokenRefreshError, 'No Pinterest credential found' unless credential raise TokenRefreshError, 'No refresh token available' if credential.refresh_token.blank? response = token_request(grant_type: 'refresh_token', refresh_token: credential.refresh_token) persist_tokens!(response, credential: credential) end |