auth details added

This commit is contained in:
Andy L WSL 2026-02-28 12:58:52 +00:00
parent cb896736a1
commit e845c68dc3
3 changed files with 99 additions and 4 deletions

View File

@ -0,0 +1,54 @@
# Google Cloud Authentication Architecture
While the [Google Cloud Setup Guide](GoogleCloudSetup.md) covers the step-by-step instructions for extracting your API credentials, this document outlines the architectural decisions, pitfalls, and lessons learned when authenticating from CyberChef's client-side environment to Google Cloud APIs.
## The CyberChef Context
CyberChef operates entirely within the browser. There is no middle-tier backend server acting as a proxy. This means that any REST request to a Cloud API originates directly from the user's browser via `fetch()` from a Web Worker.
This architecture has three major implications for authentication:
### 1. Browser-Side Secret Exposure
Any secret embedded in a CyberChef recipe URL or baked into an operation is visible to the browser, the network layer, and anyone who receives the recipe link.
* **API Keys**: An API Key is a static secret. If you use one, you **must** apply HTTP Referrer restrictions in the Google Cloud Console (as detailed in the Setup Guide) to ensure it can only be used from your specific CyberChef domain (e.g., `http://localhost:8080/`).
* **OAuth Tokens**: A temporary OAuth Token (from `gcloud auth print-access-token`) is generally preferred because it automatically expires (usually in 1 hour), mitigating the risk of credential leakage.
### 2. The Application Default Credentials (ADC) Quota Project Pitfall
When you use a short-lived OAuth token generated by an End User account (e.g., via `gcloud auth login`), the token identifies *you*, but Google occasionally needs to know which project to bill for the specific API usage.
If you attempt to call certain APIs (like `translate.googleapis.com`) using an End User OAuth Token, Google Cloud will throw a 403 error:
> *"Your application is authenticating by using local Application Default Credentials. The translate.googleapis.com API requires a quota project, which is not set by default."*
#### The Solution:
CyberChef cloud operations that accept an OAuth Token must implement an optional **Quota Project** input argument.
Under the hood, this argument must be injected into the `fetch()` request as the `x-goog-user-project` HTTP header:
```javascript
headers.set("Authorization", `Bearer ${authString}`);
if (quotaProject) {
headers.set("x-goog-user-project", quotaProject); // Injects the billing bound project
}
```
*Note: You configure your local `gcloud` environment's default quota project using `gcloud auth application-default set-quota-project <YOUR_PROJECT_ID>`.*
### 3. UI Argument Masking (The `toggleString` Pitfall)
To prevent your API Key or Token from being displayed in plain text within CyberChef's UI, Cloud operations should use the `toggleString` data type for the authentication argument. This renders the input cleanly and masks the text.
#### The Pitfall:
When converting a CyberChef argument from a standard `string` to a `toggleString`, the underlying payload structure changes. The Web Worker will no longer receive a raw string; it will receive an object:
```javascript
// A normal string returns: "AIzaSy..."
// A toggleString returns: { "string": "AIzaSy...", "option": "UTF8" }
```
If an operation's `run()` function expects a string and does not explicitly unpack `authStringObj.string`, the network request will silently fail, or the Web Worker will hang when attempting to serialize the object into an HTTP Header.
Always ensure the runtime safely parses masked keys:
```javascript
const authString = typeof authStringObj === "string" ? authStringObj : (authStringObj.string || "");
```
## Supported Methods Comparison
| Authentication Method | Security Profile | Setup Complexity | Best For |
| :--- | :--- | :--- | :--- |
| **API Key (Restricted)** | Moderate (Long-lived secret, but restricted by referrer) | Low | Permanent embedded recipes, public web hosting, and sharing recipes internally. |
| **OAuth Bearer Token** | High (Expires in 1 hr) | Medium (Requires `gcloud` CLI) | Local development, automated E2E testing, and one-off secure executions. |

View File

@ -51,6 +51,11 @@ class GoogleTranslate extends Operation {
"type": "toggleString",
"value": "",
"toggleValues": ["UTF8", "Latin1", "Base64", "Hex"]
},
{
"name": "Quota Project (ADC only)",
"type": "string",
"value": ""
}
];
}
@ -61,7 +66,7 @@ class GoogleTranslate extends Operation {
* @returns {string}
*/
async run(input, args) {
const [sourceLanguage, targetLanguage, authType, authStringObj] = args;
const [sourceLanguage, targetLanguage, authType, authStringObj, quotaProject] = args;
const authString = typeof authStringObj === "string" ? authStringObj : (authStringObj.string || "");
if (input.length === 0) return "";
@ -75,6 +80,9 @@ class GoogleTranslate extends Operation {
url += `?key=${encodeURIComponent(authString)}`;
} else if (authType === "OAuth Token") {
headers.set("Authorization", `Bearer ${authString}`);
if (quotaProject) {
headers.set("x-goog-user-project", quotaProject);
}
}
const body = JSON.stringify({

View File

@ -28,7 +28,8 @@ module.exports = {
"en",
"es",
"API Key",
{ option: "UTF8", string: "" }
{ option: "UTF8", string: "" },
""
]);
browser.waitForElementNotVisible("#snackbar-container", 6000);
@ -41,6 +42,37 @@ module.exports = {
});
},
"Google Translate: Successful OAuth Token Translation": function (browser) {
let testToken = process.env.CYBERCHEF_GCP_TEST_TOKEN;
if (!testToken || testToken === "YOUR_OAUTH_TOKEN_HERE") {
try {
testToken = require('child_process').execSync('gcloud auth print-access-token', { stdio: 'pipe', encoding: 'utf-8' }).trim();
} catch (e) {
console.log("No valid CYBERCHEF_GCP_TEST_TOKEN found and gcloud failed. Skipping live API test.");
return;
}
}
browserUtils.loadRecipe(browser, "Google Translate", "Hello", [
"en",
"es",
"OAuth Token",
{ option: "UTF8", string: testToken },
"cyberchefcloud"
]);
browser.waitForElementNotVisible("#snackbar-container", 6000);
browserUtils.bake(browser);
browser.pause(2000);
browser.saveScreenshot("tests/browser/output/success_oauth_debug.png");
browser.execute(function () {
return window.app.manager.output.outputEditorView.state.doc.toString();
}, [], function ({ value }) {
browser.assert.equal(value, "Hola");
});
},
"Google Translate: Successful API Key Translation": function (browser) {
const testKey = process.env.CYBERCHEF_GCP_TEST_KEY;
@ -53,13 +85,14 @@ module.exports = {
"en",
"es",
"API Key",
{ option: "UTF8", string: testKey }
{ option: "UTF8", string: testKey },
""
]);
browser.waitForElementNotVisible("#snackbar-container", 6000);
browserUtils.bake(browser);
browser.pause(2000);
browser.saveScreenshot("tests/browser/output/success_debug.png");
browser.saveScreenshot("tests/browser/output/success_apikey_debug.png");
browser.execute(function () {
return window.app.manager.output.outputEditorView.state.doc.toString();
}, [], function ({ value }) {