@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 +4 -1
- package/src/support/auth.js +37 -47
- package/src/support/sxauth.js +1 -1
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.
|
|
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",
|
package/src/support/auth.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
234
|
-
const { tokenInfo } = await getAccessTokenInfo()
|
|
235
|
-
|
|
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
|
|
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,
|
|
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 = {
|
package/src/support/sxauth.js
CHANGED
|
@@ -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
|
}
|