@mcpher/gas-fakes 2.2.7 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/package.json +2 -2
- package/src/cli/lib-manager.js +3 -1
- package/src/cli/setup.js +38 -6
- package/src/services/scriptapp/app.js +3 -1
- package/src/support/auth.js +24 -1
- package/src/support/sxauth.js +18 -0
package/README.md
CHANGED
|
@@ -19,8 +19,10 @@ For a complete guide on how to set up your local environment for authentication
|
|
|
19
19
|
Collaborators should fork the repo and use the local versions of these files - see collaborators info.
|
|
20
20
|
|
|
21
21
|
### Use exactly the same code as in Apps Script
|
|
22
|
+
## Usage
|
|
23
|
+
Just as on Apps Script, everything is executed synchronously so you don't need to bother with handling Promises/async/await. Just write normal Apps Script code. Usually you would have an associated App Script project if that's your eventual target. Just like Apps Script, you need a manifest file (appsscript.json) so you can be sure that the correct scopes are authorized and asked for.
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
> **Note on `appsscript.json`:** For full fidelity with live Apps Script, you should have a manifest available. However, as a workaround for "pure" `gas-fakes` projects where there is no intention to run in live Apps Script, if `appsscript.json` is missing or has no scopes, the runtime will automatically emulate one using the `DEFAULT_SCOPES` and `EXTRA_SCOPES` defined in your `.env` file, and will default the script's `timeZone` to `America/New_York` (or `GF_TIMEZONE`).
|
|
24
26
|
|
|
25
27
|
# gas-fakes-cli
|
|
26
28
|
|
|
@@ -168,11 +170,12 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
|
|
|
168
170
|
## Read more docs
|
|
169
171
|
|
|
170
172
|
- [gas fakes intro video](https://youtu.be/oEjpIrkYpEM)
|
|
171
|
-
- [getting started](GETTING_STARTED.md) - how to handle authentication for
|
|
173
|
+
- [getting started](GETTING_STARTED.md) - how to handle authentication for Workspace scopes.
|
|
172
174
|
- [readme](README.md)
|
|
173
175
|
- [gas fakes cli](gas-fakes-cli.md)
|
|
174
176
|
- [ksuite as a back end](ksuite_poc.md)
|
|
175
177
|
- [msgraph as a back end](msgraph.md)
|
|
178
|
+
- [gas-fakes in serverless containers](https://docs.google.com/presentation/d/1JlXF9T--DD4ERHopyP3WyAMhjRCxxHblgCP5ynxaJ3k/edit?usp=sharing)
|
|
176
179
|
- [apps script - a lingua franca for workspace platforms](https://ramblings.mcpher.com/apps-script-a-lingua-franca/)
|
|
177
180
|
- [Apps Script: A ‘Lingua Franca’ for the Multi-Cloud Era](https://ramblings.mcpher.com/apps-script-with-ksuite/)
|
|
178
181
|
- [running gas-fakes on google cloud run](https://github.com/brucemcpherson/gas-fakes-containers)
|
|
@@ -191,17 +194,17 @@ As I mentioned earlier, to take this further, I'm going to need a lot of help to
|
|
|
191
194
|
- [oddities](oddities.md) - a collection of oddities uncovered during this project
|
|
192
195
|
- [named colors](named-colors.md)
|
|
193
196
|
- [sandbox](sandbox.md)
|
|
194
|
-
- [senstive scopes](
|
|
197
|
+
- [senstive scopes](workspace_scopes.md)
|
|
195
198
|
- [using apps script libraries with gas-fakes](libraries.md)
|
|
196
199
|
- [how libhandler works](libhandler.md)
|
|
197
200
|
- [article:using apps script libraries with gas-fakes](https://ramblings.mcpher.com/how-to-use-apps-script-libraries-directly-from-node/)
|
|
198
201
|
- [named range identity](named-range-identity.md)
|
|
199
|
-
- [
|
|
202
|
+
- [Workspace scopes with local authentication](workspace_scopes.md)
|
|
200
203
|
- [push test pull](pull-test-push.md)
|
|
201
204
|
- [sharing cache and properties between gas-fakes and live apps script](https://ramblings.mcpher.com/sharing-cache-and-properties-between-gas-fakes-and-live-apps-script/)
|
|
202
205
|
- [gas-fakes-cli now has built in mcp server and gemini extension](https://ramblings.mcpher.com/gas-fakes-cli-now-has-built-in-mcp-server-and-gemini-extension/)
|
|
203
206
|
- [gas-fakes CLI: Run apps script code directly from your terminal](https://ramblings.mcpher.com/gas-fakes-cli-run-apps-script-code-directly-from-your-terminal/)
|
|
204
|
-
- [How to allow access to
|
|
207
|
+
- [How to allow access to Workspace scopes with Application Default Credentials](https://ramblings.mcpher.com/how-to-allow-access-to-sensitive-scopes-with-application-default-credentials/)
|
|
205
208
|
- [Supercharge Your Google Apps Script Caching with GasFlexCache](https://ramblings.mcpher.com/supercharge-your-google-apps-script-caching-with-gasflexcache/)
|
|
206
209
|
- [Fake-Sandbox for Google Apps Script: Granular controls.](https://ramblings.mcpher.com/fake-sandbox-for-google-apps-script-granular-controls/)
|
|
207
210
|
- [A Fake-Sandbox for Google Apps Script: Securely Executing Code Generated by Gemini CLI](https://ramblings.mcpher.com/gas-fakes-sandbox/)
|
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"archiver": "^7.0.1",
|
|
14
14
|
"commander": "^14.0.3",
|
|
15
15
|
"dotenv": "^17.3.1",
|
|
16
|
-
"fast-xml-parser": "^5.5.
|
|
16
|
+
"fast-xml-parser": "^5.5.9",
|
|
17
17
|
"get-stream": "^9.0.1",
|
|
18
18
|
"google-auth-library": "^10.6.2",
|
|
19
19
|
"googleapis": "^171.4.0",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"name": "@mcpher/gas-fakes",
|
|
41
41
|
"author": "bruce mcpherson",
|
|
42
|
-
"version": "2.
|
|
42
|
+
"version": "2.3.0",
|
|
43
43
|
"license": "MIT",
|
|
44
44
|
"main": "main.js",
|
|
45
45
|
"description": "An implementation of the Google Workspace Apps Script runtime: Run native App Script Code on Node and Cloud Run",
|
package/src/cli/lib-manager.js
CHANGED
|
@@ -6,8 +6,10 @@ import { checkForGcloudCli, spawnCommand } from "./utils.js";
|
|
|
6
6
|
async function getAccessToken(pattern) {
|
|
7
7
|
if (pattern == 1) {
|
|
8
8
|
// Authorization pattern 1
|
|
9
|
+
// We use cloud-platform as it provides sufficient access for Drive API fetching
|
|
10
|
+
// while avoiding the 'well-known client ID' block that targets Workspace scopes like drive.readonly.
|
|
9
11
|
const auth = await Auth.setAuth(
|
|
10
|
-
["https://www.googleapis.com/auth/
|
|
12
|
+
["https://www.googleapis.com/auth/cloud-platform"],
|
|
11
13
|
true
|
|
12
14
|
);
|
|
13
15
|
auth.cachedCredential = null;
|
package/src/cli/setup.js
CHANGED
|
@@ -283,12 +283,28 @@ export async function initializeConfiguration(options = {}) {
|
|
|
283
283
|
"https://www.googleapis.com/auth/userinfo.email",
|
|
284
284
|
"openid",
|
|
285
285
|
"https://www.googleapis.com/auth/cloud-platform",
|
|
286
|
-
"https://www.googleapis.com/auth/drive.readonly",
|
|
287
286
|
];
|
|
288
287
|
responses.DEFAULT_SCOPES = DEFAULT_SCOPES_VALUES.join(",");
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
288
|
+
|
|
289
|
+
let extraScopes = manifestScopes.filter(s => !DEFAULT_SCOPES_VALUES.includes(s));
|
|
290
|
+
|
|
291
|
+
// Restricted scopes that trigger blocks for the default 'well-known' ADC client ID
|
|
292
|
+
const restrictedMatch = (s) =>
|
|
293
|
+
s.includes("auth/drive") ||
|
|
294
|
+
s.includes("auth/spreadsheets") ||
|
|
295
|
+
s.includes("auth/documents") ||
|
|
296
|
+
s.includes("auth/forms") ||
|
|
297
|
+
s.includes("auth/presentations") ||
|
|
298
|
+
s.includes("auth/script.external_request");
|
|
299
|
+
|
|
300
|
+
if (responses.AUTH_TYPE === "adc" && !responses.CLIENT_CREDENTIAL_FILE) {
|
|
301
|
+
const toSkip = extraScopes.filter(restrictedMatch);
|
|
302
|
+
if (toSkip.length > 0) {
|
|
303
|
+
console.log(`\n\x1b[1;33mWarning: ADC requested with Workspace scopes (${toSkip.map(s => s.split("/").pop()).join(", ")}). Google now blocks these scopes when using the default gcloud CLI client ID. You MUST provide a custom OAuth client credential file.\x1b[0m`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
responses.EXTRA_SCOPES = extraScopes.join(",");
|
|
292
308
|
|
|
293
309
|
const googleQuestions = [
|
|
294
310
|
{
|
|
@@ -306,7 +322,7 @@ export async function initializeConfiguration(options = {}) {
|
|
|
306
322
|
{
|
|
307
323
|
type: responses.AUTH_TYPE === "adc" ? "text" : null,
|
|
308
324
|
name: "CLIENT_CREDENTIAL_FILE",
|
|
309
|
-
message: "Enter path to OAuth client credentials JSON (optional, required for
|
|
325
|
+
message: "Enter path to OAuth client credentials JSON (optional, required for Workspace scopes with ADC)",
|
|
310
326
|
initial: existingConfig.CLIENT_CREDENTIAL_FILE || "",
|
|
311
327
|
}
|
|
312
328
|
];
|
|
@@ -663,10 +679,26 @@ export async function authenticateUser(options = {}) {
|
|
|
663
679
|
...(EXTRA_SCOPES || "").split(","),
|
|
664
680
|
])).filter(s => s).join(",");
|
|
665
681
|
|
|
666
|
-
|
|
682
|
+
let adcScopes = AUTH_TYPE === "dwd"
|
|
667
683
|
? Array.from(new Set((DEFAULT_SCOPES || "").split(","))).filter(s => s).join(",")
|
|
668
684
|
: scopes;
|
|
669
685
|
|
|
686
|
+
const hasClientId = CLIENT_CREDENTIAL_FILE && fs.existsSync(path.resolve(process.cwd(), CLIENT_CREDENTIAL_FILE));
|
|
687
|
+
if (AUTH_TYPE !== "dwd" && !hasClientId) {
|
|
688
|
+
const scopeList = adcScopes.split(",");
|
|
689
|
+
const workspaceScopes = scopeList.filter(s =>
|
|
690
|
+
s.includes("auth/drive") ||
|
|
691
|
+
s.includes("auth/spreadsheets") ||
|
|
692
|
+
s.includes("auth/documents") ||
|
|
693
|
+
s.includes("auth/forms") ||
|
|
694
|
+
s.includes("auth/presentations")
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
if (workspaceScopes.length > 0) {
|
|
698
|
+
console.warn(`\n\x1b[1;33mWarning: Workspace scopes (${workspaceScopes.map(s => s.split("/").pop()).join(", ")}) requested with ADC and no CLIENT_CREDENTIAL_FILE. This is expected to be blocked by Google.\x1b[0m`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
670
702
|
console.log(`...requesting scopes: ${adcScopes}`);
|
|
671
703
|
|
|
672
704
|
const driveAccessFlag = "--enable-gdrive-access";
|
|
@@ -114,7 +114,9 @@ const checkScopesMatch = (required) => {
|
|
|
114
114
|
"https://www.googleapis.com/auth/script.external_request",
|
|
115
115
|
"https://www.googleapis.com/auth/documents",
|
|
116
116
|
"https://www.googleapis.com/auth/presentations",
|
|
117
|
-
"https://www.googleapis.com/auth/forms"
|
|
117
|
+
"https://www.googleapis.com/auth/forms",
|
|
118
|
+
"https://www.googleapis.com/auth/drive",
|
|
119
|
+
"https://www.googleapis.com/auth/spreadsheets"
|
|
118
120
|
]
|
|
119
121
|
const hasIgnore = ignores.some(i => i.replace(/\/$/, "") === ns)
|
|
120
122
|
if (hasIgnore) {
|
package/src/support/auth.js
CHANGED
|
@@ -167,6 +167,22 @@ const setAuth = async (scopes = [], mcpLoading = false) => {
|
|
|
167
167
|
|
|
168
168
|
if (!useDwd) {
|
|
169
169
|
id.authMethod = 'adc'
|
|
170
|
+
|
|
171
|
+
const workspaceScopes = scopes.filter(s =>
|
|
172
|
+
s.includes('auth/drive') ||
|
|
173
|
+
s.includes('auth/spreadsheets') ||
|
|
174
|
+
s.includes('auth/documents') ||
|
|
175
|
+
s.includes('auth/forms') ||
|
|
176
|
+
s.includes('auth/presentations')
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
const creds = await id.auth.getCredentials();
|
|
180
|
+
const isDefaultClient = creds && creds.client_id === '764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com';
|
|
181
|
+
|
|
182
|
+
if (isDefaultClient && workspaceScopes.length > 0) {
|
|
183
|
+
throw new Error(`ADC error: Google no longer allows the use of the default gcloud client ID for regular Workspace scopes (${workspaceScopes.map(s => s.split('/').pop()).join(', ')}). You must use a custom client credential file. See https://docs.cloud.google.com/docs/authentication/troubleshoot-adc#access_blocked_when_using_scopes`);
|
|
184
|
+
}
|
|
185
|
+
|
|
170
186
|
id.authClient = await id.auth.getClient({ scopes })
|
|
171
187
|
id.sourceClient = id.authClient
|
|
172
188
|
} else {
|
|
@@ -175,7 +191,14 @@ const setAuth = async (scopes = [], mcpLoading = false) => {
|
|
|
175
191
|
id.authMethod = 'dwd'
|
|
176
192
|
const targetPrincipal = `${saName}@${id.projectId}.iam.gserviceaccount.com`
|
|
177
193
|
|
|
178
|
-
|
|
194
|
+
// For DWD source client, we only need identity and enough scope to sign JWT
|
|
195
|
+
// cloud-platform is sufficient for IAM signJwt and avoid Drive-related blocks
|
|
196
|
+
const sourceScopes = scopes.filter(s =>
|
|
197
|
+
s === 'openid' ||
|
|
198
|
+
s === 'https://www.googleapis.com/auth/userinfo.email' ||
|
|
199
|
+
s === 'https://www.googleapis.com/auth/cloud-platform'
|
|
200
|
+
)
|
|
201
|
+
|
|
179
202
|
id.sourceClient = await id.auth.getClient(sourceScopes.length > 0 ? { scopes: sourceScopes } : {})
|
|
180
203
|
|
|
181
204
|
const { tokenInfo: userInfo } = await _getTokenInfo(id.sourceClient);
|
package/src/support/sxauth.js
CHANGED
|
@@ -52,6 +52,24 @@ export const sxInit = async ({ manifestPath, claspPath, settingsPath, cachePath,
|
|
|
52
52
|
getIfExists(claspFile)
|
|
53
53
|
])
|
|
54
54
|
|
|
55
|
+
// Emulate manifest scopes from .env if missing or empty
|
|
56
|
+
if (!manifest.oauthScopes || manifest.oauthScopes.length === 0) {
|
|
57
|
+
const envScopes = Array.from(new Set([
|
|
58
|
+
...(process.env.DEFAULT_SCOPES || "").split(","),
|
|
59
|
+
...(process.env.EXTRA_SCOPES || "").split(",")
|
|
60
|
+
])).map(s => s.trim()).filter(s => s);
|
|
61
|
+
|
|
62
|
+
if (envScopes.length > 0) {
|
|
63
|
+
manifest.oauthScopes = envScopes;
|
|
64
|
+
if (!manifest.timeZone) {
|
|
65
|
+
manifest.timeZone = process.env.GF_TIMEZONE || "America/New_York";
|
|
66
|
+
}
|
|
67
|
+
if (!_loggedSummary) {
|
|
68
|
+
syncLog(`...appsscript.json missing or missing scopes. Emulating manifest using scopes from .env file`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
55
73
|
const settings = {
|
|
56
74
|
manifest: manifestFile,
|
|
57
75
|
clasp: claspFile,
|