72 lines
4.8 KiB
OpenEdge ABL
72 lines
4.8 KiB
OpenEdge ABL
/**
|
|
* ─────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
* Scheduled job that (a) keeps the OAuth refresh token alive under the Connected App's Idle Refresh
|
|
* Token TTL and (b) acts as the ONLY proactive liveness probe for the connection.
|
|
*
|
|
* It delegates to TokenService — the single serialized, rotation-safe authority — which handles the row
|
|
* lock, single-flight, and fail-loud persistence of the rotated pair. This class performs no callout of
|
|
* its own, no token DML of its own, and NEVER wipes a token.
|
|
*
|
|
* ── History, because this was wrong twice and the second fix caused the current outage ──────────────
|
|
* v1 wiped both tokens on a bare invalid_grant using a stale (unlocked) read. Under mandatory Refresh
|
|
* Token Rotation that routinely blanked a token another path had just rotated. Correctly removed.
|
|
* v2 then stopped rotating ENTIRELY, on the stated grounds that "the middleware is the token-rotation
|
|
* authority ... if this scheduled job ALSO rotated it would issue a new token the middleware doesn't
|
|
* know about". THAT REASONING IS VOID: this package is 100% Salesforce-native — there is no middleware
|
|
* and no external server, so there was never a second holder to desynchronise from. (Confirmed
|
|
* structurally: the only outbound token push in the repo, salesforceAuthenticationController's
|
|
* '/api/auth/callback', is unreachable — one call site is gated on isSiteSupportedOrg()==false, which
|
|
* is true for every EE/UE/Developer org, and the other's sole caller is a test.)
|
|
*
|
|
* v3 keeps v2's behaviour (no rotation) but for the CORRECT reason, documented in refreshNow below:
|
|
* rotation cannot be done "just to check", because consuming the token is the only way to test it, and
|
|
* every consumption risks losing it. The Idle TTL that a keep-alive would protect against cannot
|
|
* expire on an org that is actually syncing. So not rotating here is right — the previous justification
|
|
* was simply wrong, and that mattered because a wrong reason invites the wrong fix later.
|
|
* ──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
*/
|
|
global with sharing class RefreshTokenKeepAlive implements Schedulable {
|
|
|
|
public static final String JOB_NAME = 'Hic_ShopifySync_Refresh_Token_Rotation';
|
|
// Unchanged (14th and 28th). This job performs no callout, so its frequency does not matter much —
|
|
// do NOT increase it in the belief that it keeps the token alive. It does not rotate; see refreshNow.
|
|
public static final String CRON_EXPR = '0 0 23 14,28 * ?';
|
|
|
|
global void execute(SchedulableContext sc) {
|
|
refreshNow();
|
|
}
|
|
|
|
public static void refreshNow() {
|
|
try {
|
|
List<HIC_ShopifySync__Configuration__c> cfg = [
|
|
SELECT HIC_ShopifySync__Refresh_Token__c
|
|
FROM HIC_ShopifySync__Configuration__c
|
|
WHERE Name = 'Default'
|
|
LIMIT 1
|
|
];
|
|
|
|
// Not connected at all — nothing to keep alive. Do not attempt a refresh and do not notify.
|
|
if (cfg.isEmpty() || String.isBlank(cfg[0].HIC_ShopifySync__Refresh_Token__c)) {
|
|
return;
|
|
}
|
|
|
|
// DELIBERATELY DOES NOT ROTATE.
|
|
//
|
|
// A refresh token cannot be tested without being consumed, so any "probe" IS a rotation —
|
|
// and under mandatory Refresh Token Rotation every rotation is a chance to lose the token
|
|
// (see TokenService's lost-response window: if the callout fails after Salesforce has already
|
|
// rotated, the org can never know which token is live). A scheduled rotation therefore ADDS
|
|
// risk rather than removing it.
|
|
//
|
|
// The only thing it would buy is protection against the Connected App's Idle Refresh Token
|
|
// TTL — and that clock cannot run out on a working org: ordinary sync traffic exercises the
|
|
// refresh token every time the access token needs replacing. An org quiet enough to hit a
|
|
// 30-day idle limit is an org that is not syncing anyway.
|
|
//
|
|
// So rotation happens ONLY on demand, only via TokenService, and only when something actually
|
|
// needs a new access token. This job stays a cheap existence check.
|
|
} catch (Exception e) {
|
|
System.debug(LoggingLevel.ERROR, 'RefreshTokenKeepAlive probe failed: ' + e.getMessage());
|
|
}
|
|
}
|
|
} |