105 lines
6.1 KiB
OpenEdge ABL
105 lines
6.1 KiB
OpenEdge ABL
/**
|
|
* ─────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
* This web service class handle the custom respons which we send while authenticate user
|
|
* Insert the user refresh token into the configuration custom settings.
|
|
* Saved refresh token into installer User's org.
|
|
* Shopify API Documentation: https://shopify.dev/docs/admin-api/rest/reference
|
|
* Create Shopify Store: https://www.shopify.com/signup?itcat=identity&itterm=create-new-store
|
|
* Shopify API Release 2020: https://www.shopify.in/partners/blog/shopify-api-release-january-2020
|
|
* What is Person Account in Salesforce ? https://help.salesforce.com/articleView?id=account_person.htm&type=5
|
|
* ──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
* @author Sumit <sumit@hicShopify.com>
|
|
* ──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
*/
|
|
@RestResource(UrlMapping='/UpdateAccessToken/*')
|
|
global with sharing class UpdateAccessToken
|
|
{
|
|
/**
|
|
* Accepts an access token pushed in by the authenticator.
|
|
*
|
|
* WHAT WAS WRONG: this endpoint used to do
|
|
* listofConfiguration[0].HIC_ShopifySync__Access_Token__c = JSONResponseBody;
|
|
* i.e. it stored the ENTIRE RAW REQUEST BODY as the access token, unparsed and unvalidated, and never
|
|
* touched Refresh_Token__c. Two consequences:
|
|
*
|
|
* 1. Any JSON body (e.g. {"access_token":"..."}) was stored verbatim, so Access_Token__c held a JSON
|
|
* blob rather than a token. Every subsequent authenticated callout then failed, and because
|
|
* the field was non-blank, nothing detected it: RefreshTokenKeepAlive's String.isBlank() passed and
|
|
* ShopifyController.refreshTokenCheck handed the blob back as if the org were connected.
|
|
* 2. It updated the access token WITHOUT updating the refresh token. Under mandatory Refresh Token
|
|
* Rotation, if the caller obtained that access token via grant_type=refresh_token then the stored
|
|
* refresh token has ALREADY been consumed and is dead — so the org looks freshly authenticated
|
|
* while its refresh token is guaranteed to fail on next use. That is the "worked for days, then
|
|
* asks to re-initialize" signature.
|
|
*
|
|
* Now: the body is parsed, only a real token value is accepted, and if the caller supplies a rotated
|
|
* refresh token alongside it BOTH are written in one DML via the shared, compare-and-swap-protected
|
|
* persistRotatedToken. Failures are reported instead of swallowed.
|
|
*/
|
|
@HTTPPost
|
|
global static void updatetoken(){
|
|
Restrequest request = restContext.request;
|
|
Restresponse response = restContext.response;
|
|
String rawBody = (request != null && request.requestBody != null)
|
|
? request.requestBody.toString().trim()
|
|
: null;
|
|
|
|
if (String.isBlank(rawBody)) {
|
|
if (response != null) response.statusCode = 400;
|
|
return;
|
|
}
|
|
|
|
String accessToken;
|
|
String refreshToken;
|
|
try {
|
|
Map<String, Object> meta = (Map<String, Object>) JSON.deserializeUntyped(rawBody);
|
|
if (meta != null) {
|
|
// Accept the packaged field name and the plain OAuth name.
|
|
Object a = meta.containsKey('HIC_ShopifySync__Access_Token__c')
|
|
? meta.get('HIC_ShopifySync__Access_Token__c') : meta.get('access_token');
|
|
Object r = meta.containsKey('HIC_ShopifySync__Refresh_Token__c')
|
|
? meta.get('HIC_ShopifySync__Refresh_Token__c') : meta.get('refresh_token');
|
|
accessToken = (a == null) ? null : String.valueOf(a).trim();
|
|
refreshToken = (r == null) ? null : String.valueOf(r).trim();
|
|
}
|
|
} catch (Exception parseEx) {
|
|
// Not JSON. Treat the body as a bare token only if it cannot be a structured payload —
|
|
// never store something that merely looks like JSON/HTML.
|
|
if (!rawBody.startsWith('{') && !rawBody.startsWith('[') && !rawBody.startsWith('<')
|
|
&& !rawBody.contains(' ') && rawBody.length() >= 20) {
|
|
accessToken = rawBody;
|
|
}
|
|
}
|
|
|
|
if (String.isBlank(accessToken)
|
|
|| accessToken.equalsIgnoreCase('null') || accessToken.equalsIgnoreCase('undefined')) {
|
|
System.debug(LoggingLevel.ERROR,
|
|
'SME_TOKEN UpdateAccessToken: no usable access token in body — refusing to store it');
|
|
if (response != null) {
|
|
response.statusCode = 400;
|
|
response.addHeader('Content-Type', 'application/json');
|
|
response.responseBody = Blob.valueOf(
|
|
'{"error":"no_access_token","error_description":"body did not contain a usable access token"}');
|
|
}
|
|
return;
|
|
}
|
|
|
|
Map<String, Object> payload = new Map<String, Object>{
|
|
'HIC_ShopifySync__Access_Token__c' => accessToken
|
|
};
|
|
if (String.isNotBlank(refreshToken)) {
|
|
payload.put('HIC_ShopifySync__Refresh_Token__c', refreshToken);
|
|
}
|
|
|
|
Boolean stored = AuthenticateAllApexClass.persistRotatedToken(JSON.serialize(payload));
|
|
|
|
if (response != null) {
|
|
response.statusCode = stored ? 200 : 500;
|
|
response.addHeader('Content-Type', 'application/json');
|
|
response.responseBody = Blob.valueOf(stored
|
|
? '{"status":"ok"}'
|
|
: '{"error":"token_not_stored","error_description":"access token could not be persisted"}');
|
|
}
|
|
}
|
|
|
|
} |