Skip to content

Instantly share code, notes, and snippets.

@gaieges
Last active March 23, 2026 21:43
Show Gist options
  • Select an option

  • Save gaieges/b1a6f9f2b495124aae0d364b6038b4d4 to your computer and use it in GitHub Desktop.

Select an option

Save gaieges/b1a6f9f2b495124aae0d364b6038b4d4 to your computer and use it in GitHub Desktop.
Revoke Active Suitecloud M2m credentials via devtools script
// this is a simple script that gets pasted into the console of the M2M certificate management page
// that will to revoke all certificates that are for "SuiteCloud Development Integration" and have a revoke button available.
// It collects the certificateIds, then makes a POST request to revoke each one sequentially,
// and finally prints out the results to console.log.
//
// Instructions:
// - go to the m2m page: /app/oauth2/clientcredentials/setup.nl
// - open up browser dev tools (chrome is ideal)
// - update this script to enable / disable dryRun mode
// - paste this entire script into the console and hit enter
const targetAppName = "SuiteCloud Development Integration";
const results = [];
// Set to false to actually perform the revocations, true will just simulate and log the actions without making network requests
const dryRun = true;
// Phase 1: Fetch all client credentials and select the ones we want to revoke.
// This avoids any reliance on the page’s DOM / infinite scroll.
const getCredentialsToRevoke = async () => {
const listUrl = `${window.location.origin}/app/oauth2/clientcredentials/dataservice.nl?action=getClientCredentials`;
const response = await fetch(listUrl, {
method: "GET",
headers: {
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText} ${text}`);
}
const payload = await response.json();
if (payload?.status !== "ok") {
throw new Error(
`Unexpected payload status from getClientCredentials: ${JSON.stringify(
payload,
)}`,
);
}
const rows = Array.isArray(payload?.data) ? payload.data : [];
return (
rows
.filter((row) => row?.application?.name === targetAppName)
// already revoked = no-op
.filter((row) => row?.revoked === false)
// ensure we have the data we need to revoke
.filter(
(row) => Boolean(row?.application?.id) && Boolean(row?.certificateId),
)
.map((row) => ({
appId: row.application.id,
certificateId: row.certificateId,
applicationName: row.application.name,
entityName: row?.entity?.name,
roleName: row?.role?.name,
validUntil: row?.validUntil,
}))
);
};
// Phase 2: Perform fetch requests for each collected certificateId
const buildRevokeBody = ({ appId, certificateId }) =>
`action=revokeClientCredentials&appid=${encodeURIComponent(
String(appId),
)}&certificateId=${encodeURIComponent(String(certificateId))}`;
const revokeCertificatesSequentially = async (credentialsToRevoke) => {
const revokeUrl = `${window.location.origin}/app/oauth2/clientcredentials/dataservice.nl`;
const revokeHeaders = {
"Content-Type": "application/x-www-form-urlencoded",
};
for (const credential of credentialsToRevoke) {
const { appId, certificateId } = credential;
const body = buildRevokeBody(credential);
try {
const response = await fetch(revokeUrl, {
method: "POST",
headers: revokeHeaders,
body: body,
});
if (response.ok) {
const responseText = await response.text(); // Or .json() if the response is JSON
results.push({
appId,
certificateId,
status: "success",
response: responseText,
});
console.log(
`Successfully revoked certificateId=${certificateId} appid=${appId}: ${responseText}`,
);
} else {
const errorText = await response.text();
results.push({
appId,
certificateId,
status: "failed",
error: `HTTP error! Status: ${response.status} - ${errorText}`,
});
console.error(
`Failed to revoke certificateId=${certificateId} appid=${appId}: HTTP error! Status: ${response.status} - ${errorText}`,
);
}
} catch (error) {
results.push({
appId,
certificateId,
status: "failed",
error: `Network error: ${error.message}`,
});
console.error(
`Failed to revoke certificateId=${certificateId} appid=${appId}: Network error - ${error.message}`,
);
}
}
};
// We need to define an async IIFE (Immediately Invoked Function Expression) to use await in the global scope
(async () => {
const credentialsToRevoke = await getCredentialsToRevoke();
if (dryRun) {
console.log(
`[dryRun] Would revoke ${credentialsToRevoke.length} certificate(s):`,
credentialsToRevoke,
);
} else {
await revokeCertificatesSequentially(credentialsToRevoke);
// After all fetches are complete, assign the results to the top-level data variable
// This will be the return value of the executeJavaScript function
console.log("All operations complete. Results:", results);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment