@mcpher/gas-fakes 2.0.10 → 2.0.12

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/package.json CHANGED
@@ -24,6 +24,9 @@
24
24
  "unzipper": "^0.12.3",
25
25
  "zod": "^4.3.6"
26
26
  },
27
+ "overrides": {
28
+ "minimatch": "^10.2.1"
29
+ },
27
30
  "type": "module",
28
31
  "scripts": {
29
32
  "pub": "npm publish --access public",
@@ -33,7 +36,7 @@
33
36
  },
34
37
  "name": "@mcpher/gas-fakes",
35
38
  "author": "bruce mcpherson",
36
- "version": "2.0.10",
39
+ "version": "2.0.12",
37
40
  "license": "MIT",
38
41
  "main": "main.js",
39
42
  "description": "An implementation of the Google Workspace Apps Script runtime: Run native App Script Code on Node and Cloud Run",
@@ -56,7 +56,25 @@ const getHashedUserId = () =>
56
56
  const _getTokenInfo = async (client) => {
57
57
  const tokenResponse = await client.getAccessToken();
58
58
  const token = tokenResponse.token;
59
- const tokenInfo = await client.getTokenInfo(token);
59
+
60
+ let tokenInfo;
61
+ if (typeof client.getTokenInfo === 'function') {
62
+ tokenInfo = await client.getTokenInfo(token);
63
+ } else {
64
+ // Fallback for clients like AwsClient that don't have getTokenInfo
65
+ // Call the Token Info API directly
66
+ const response = await client.request({
67
+ url: `https://oauth2.googleapis.com/tokeninfo?access_token=${token}`,
68
+ method: 'GET'
69
+ });
70
+ tokenInfo = response.data;
71
+
72
+ // Ensure email is populated from subject if missing (WIF fallback)
73
+ if (!tokenInfo.email && process.env.GOOGLE_WORKSPACE_SUBJECT) {
74
+ tokenInfo.email = process.env.GOOGLE_WORKSPACE_SUBJECT;
75
+ }
76
+ }
77
+
60
78
  return {
61
79
  tokenInfo,
62
80
  token
@@ -112,9 +130,6 @@ const setAuth = async (scopes = [], mcpLoading = false) => {
112
130
  mayLog(`...discovered project ID: ${_projectId}`)
113
131
 
114
132
  // steering for auth type
115
- // 1. if AUTH_TYPE is DWD, use DWD
116
- // 2. if AUTH_TYPE is ADC, use ADC
117
- // 3. if AUTH_TYPE is not set, use DWD if saName is present, else ADC
118
133
  const saName = process.env.GOOGLE_SERVICE_ACCOUNT_NAME
119
134
  const authType = process.env.AUTH_TYPE?.toLowerCase()
120
135
  const useDwd = authType === 'dwd' || (authType !== 'adc' && saName)
@@ -134,19 +149,21 @@ const setAuth = async (scopes = [], mcpLoading = false) => {
134
149
  mayLog(`...attempting to use service account: ${targetPrincipal}`)
135
150
 
136
151
  /// _sourceClient is the identity of the person/thing running the code
137
- // we'll try to get the openid and email scopes for the source client too if they are in the manifest
138
152
  const sourceScopes = scopes.filter(s => s === 'openid' || s === 'https://www.googleapis.com/auth/userinfo.email')
139
153
  _sourceClient = await _auth.getClient(sourceScopes.length > 0 ? { scopes: sourceScopes } : {})
140
154
 
141
155
  // now to get who the real user is
142
156
  const { tokenInfo: userInfo } = await getSourceAccessTokenInfo()
143
- mayLog(`...user verified as: ${userInfo.email}`);
144
-
145
- // DWD Subject priority:
146
- // 1. GOOGLE_WORKSPACE_SUBJECT
147
- // 2. Caller identity (from getSourceAccessTokenInfo)
157
+
158
+ // AWS tokens might not have email info
148
159
  const saEmail = targetPrincipal
149
160
  const userEmail = process.env.GOOGLE_WORKSPACE_SUBJECT || userInfo.email
161
+
162
+ if (userEmail) {
163
+ mayLog(`...user verified as: ${userEmail}`);
164
+ } else {
165
+ mayLog(`...warning: user identity could not be verified from token, using fallback`);
166
+ }
150
167
 
151
168
  const dwdClient = new OAuth2Client()
152
169
  dwdClient._token = null
@@ -168,10 +185,7 @@ const setAuth = async (scopes = [], mcpLoading = false) => {
168
185
  scope: scopes.join(' ')
169
186
  }
170
187
 
171
- //mayLog(`[Auth] DWD Scopes: ${payload.scope}`)
172
-
173
188
  // Sign the JWT via IAM API
174
- // Note: The caller must have 'Service Account Token Creator' role on the target SA
175
189
  const signUrl = `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${saEmail}:signJwt`
176
190
  const signResponse = await _sourceClient.request({
177
191
  url: signUrl,
@@ -196,8 +210,6 @@ const setAuth = async (scopes = [], mcpLoading = false) => {
196
210
 
197
211
  if (!tokenResponse.ok) {
198
212
  const errorText = await tokenResponse.text()
199
- console.log ('... it looks like you forgot to enable domain-wide delegation for the service account')
200
- console.log ('... rerun gas-fakes auth and check the instructions about enabling domain-wide delegation')
201
213
  throw new Error(`Failed to exchange JWT for token: ${errorText}`)
202
214
  }
203
215
 
@@ -209,10 +221,9 @@ const setAuth = async (scopes = [], mcpLoading = false) => {
209
221
  return { token: this._token }
210
222
  }
211
223
 
212
- // override request to ensure we use our token but leverage the existing transporter
224
+ // override request
213
225
  const originalRequest = dwdClient.request.bind(dwdClient)
214
226
  dwdClient.request = async function (options) {
215
- // Ensure token is fresh
216
227
  await this.getAccessToken()
217
228
  return originalRequest(options)
218
229
  }
@@ -230,14 +241,18 @@ const setAuth = async (scopes = [], mcpLoading = false) => {
230
241
 
231
242
  mayLog(`...using Domain-Wide Delegation for user: ${userEmail}`)
232
243
 
233
- // check we can get an access token - this will trigger the signJwt flow
234
- const { tokenInfo } = await getAccessTokenInfo()
235
- mayLog(`...sa (acting as user) verified as: ${tokenInfo.email}`);
244
+ // check we can get an access token
245
+ const { tokenInfo: authedTokenInfo } = await getAccessTokenInfo()
246
+ if (authedTokenInfo.email) {
247
+ mayLog(`...sa (acting as user) verified as: ${authedTokenInfo.email}`);
248
+ } else {
249
+ mayLog(`...sa (acting as user) token acquired successfully`);
250
+ }
236
251
  }
237
252
 
238
253
 
239
254
  } catch (error) {
240
- mayLog(`...auth failed - check you are logged in with 'gcloud auth login' and have enabled workload identity: ${error}`)
255
+ mayLog(`...auth failed - check environment variables and workload identity: ${error}`)
241
256
  throw error
242
257
  }
243
258
  return getAuth()
@@ -256,15 +271,6 @@ const invalidateToken = () => {
256
271
  }
257
272
  }
258
273
  }
259
- /**
260
- * we'll be using adc credentials so no need for any special auth here
261
- * the idea here is to keep addign scopes to any auth so we have them all
262
- * @param {string[]} [scopes=[]] the required scopes will be added to existing scopes already asked for
263
- * @param {string} [keyFile=null]
264
- * @param {boolean} [mcpLoading=false] When the MCP server is loading, this value is true. By this, the invalid values can be hidden while the MCP server is loading. This is important for using Google Antigravity.
265
- * @returns {GoogleAuth.auth}
266
- */
267
-
268
274
 
269
275
  /**
270
276
  * if we're doing a fetch on drive API we need a special header
@@ -278,10 +284,6 @@ const googify = (options = {}) => {
278
284
  // if no authorization, we dont need this either
279
285
  if (!Reflect.has(headers, "Authorization")) return options;
280
286
 
281
- // we'll need the projectID for this
282
- // note - you must add the x-goog-user-project header, otherwise it'll use some nonexistent project
283
- // see https://cloud.google.com/docs/authentication/rest#set-billing-project
284
- // this has been syncified
285
287
  const projectId = getProjectId();
286
288
  return {
287
289
  ...options,
@@ -293,7 +295,6 @@ const googify = (options = {}) => {
293
295
  };
294
296
 
295
297
  /**
296
- * this would have been set up when manifest was imported
297
298
  * @returns {string} the project id
298
299
  */
299
300
  const getProjectId = () => {
@@ -318,7 +319,6 @@ const getAuth = () => {
318
319
  if (!hasAuth())
319
320
  throw new Error(`auth hasnt been intialized with setAuth yet`);
320
321
 
321
- // Simply return the client we've already prepared/patched
322
322
  return getAuthClient();
323
323
  };
324
324
 
@@ -326,17 +326,11 @@ const getAuth = () => {
326
326
 
327
327
  /**
328
328
  * why is this here ?
329
- * because when we syncit, we import auth for each method and it needs this
330
- * if it was somewhere else we'd need to import that too.
331
- * we can't serialize a return object
332
- * so we just select a few props from it
333
- * @param {SyncApiResponse} result
334
- * @returns
335
329
  */
336
330
  export const responseSyncify = (result) => {
337
331
  if (!result) {
338
332
  return {
339
- status: 503, // Service Unavailable, a good representation for a worker-level failure
333
+ status: 503,
340
334
  statusCode: 503,
341
335
  statusText: "Worker Error: No response object received from API call",
342
336
  error: {
@@ -357,10 +351,6 @@ export const responseSyncify = (result) => {
357
351
  };
358
352
  };
359
353
 
360
- /**
361
- * these are the ones that have been so far requested
362
- * @returns {Set}
363
- */
364
354
  const getAuthedScopes = () => _authScopes;
365
355
 
366
356
  export const Auth = {
@@ -80,7 +80,7 @@ export const sxInit = async ({ manifestPath, claspPath, settingsPath, cachePath,
80
80
  try {
81
81
  await mkdir(settingsDir, { recursive: true })
82
82
  syncLog(`...writing to ${settingsPath}`);
83
- writeFile(settingsPath, strSet, { flag: 'w' })
83
+ await writeFile(settingsPath, strSet, { flag: 'w' })
84
84
  } catch (err) {
85
85
  syncWarn(`...unable to write settings file (normal in readonly filesystem) - skipping ${settingsPath}: ${err}`)
86
86
  }