SyncMadeEasy/force-app/main/default/classes/PkceUtil.cls

57 lines
2.9 KiB
OpenEdge ABL

/**
* ─────────────────────────────────────────────────────────────────────────────────────────────────┐
* PKCE (RFC 7636) helper for Connected Apps that have "Require Proof Key for Code Exchange" enabled.
* Generates the code_verifier / code_challenge pair, and packs/unpacks the verifier inside the
* OAuth `state` parameter so it survives the round-trip through the browser and back to Apex
* (works across orgs for the publisher-Site flow, and across browser tabs for the LWC flow).
* ──────────────────────────────────────────────────────────────────────────────────────────────────
*/
global with sharing class PkceUtil {
private static final String STATE_DELIMITER = '__pkce__';
global static String generateCodeVerifier() {
Blob random = Crypto.generateAesKey(256);
return base64UrlEncode(random);
}
global static String generateCodeChallenge(String codeVerifier) {
if (String.isBlank(codeVerifier)) return '';
Blob hash = Crypto.generateDigest('SHA-256', Blob.valueOf(codeVerifier));
return base64UrlEncode(hash);
}
global static String embedVerifierInState(String existingState, String verifier) {
String safeState = existingState == null ? '' : existingState;
return safeState + STATE_DELIMITER + (verifier == null ? '' : verifier);
}
global static String extractVerifier(String stateParam) {
if (String.isBlank(stateParam) || !stateParam.contains(STATE_DELIMITER)) return '';
Integer i = stateParam.indexOf(STATE_DELIMITER);
return stateParam.substring(i + STATE_DELIMITER.length());
}
global static String extractOriginalState(String stateParam) {
if (String.isBlank(stateParam) || !stateParam.contains(STATE_DELIMITER)) return stateParam;
return stateParam.substring(0, stateParam.indexOf(STATE_DELIMITER));
}
@AuraEnabled
global static Map<String, String> issuePkceForState(String existingState) {
String verifier = generateCodeVerifier();
String challenge = generateCodeChallenge(verifier);
String state = embedVerifierInState(existingState, verifier);
Map<String, String> result = new Map<String, String>();
result.put('codeChallenge', challenge);
result.put('state', state);
return result;
}
private static String base64UrlEncode(Blob input) {
return EncodingUtil.base64Encode(input)
.replace('+', '-')
.replace('/', '_')
.replace('=', '');
}
}