@prmichaelsen/firebase-admin-sdk-v8 2.3.1 → 2.4.2
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/AGENT.md +117 -6
- package/CHANGELOG.md +14 -0
- package/README.md +21 -1
- package/dist/chunk-5X465GLA.mjs +161 -0
- package/dist/index.d.mts +55 -1
- package/dist/index.d.ts +55 -1
- package/dist/index.js +330 -125
- package/dist/index.mjs +178 -162
- package/dist/token-generation-5K7K6T6U.mjs +8 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __esm = (fn, res) => function __init() {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
};
|
|
6
9
|
var __export = (target, all) => {
|
|
7
10
|
for (var name in all)
|
|
8
11
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -17,45 +20,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
20
|
};
|
|
18
21
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
22
|
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
FieldValue: () => FieldValue,
|
|
24
|
-
addDocument: () => addDocument,
|
|
25
|
-
batchWrite: () => batchWrite,
|
|
26
|
-
clearConfig: () => clearConfig,
|
|
27
|
-
clearTokenCache: () => clearTokenCache,
|
|
28
|
-
countDocuments: () => countDocuments,
|
|
29
|
-
createCustomToken: () => createCustomToken,
|
|
30
|
-
deleteDocument: () => deleteDocument,
|
|
31
|
-
deleteFile: () => deleteFile,
|
|
32
|
-
downloadFile: () => downloadFile,
|
|
33
|
-
fileExists: () => fileExists,
|
|
34
|
-
generateSignedUrl: () => generateSignedUrl,
|
|
35
|
-
getAdminAccessToken: () => getAdminAccessToken,
|
|
36
|
-
getAuth: () => getAuth,
|
|
37
|
-
getConfig: () => getConfig,
|
|
38
|
-
getDocument: () => getDocument,
|
|
39
|
-
getFileMetadata: () => getFileMetadata,
|
|
40
|
-
getProjectId: () => getProjectId,
|
|
41
|
-
getServiceAccount: () => getServiceAccount,
|
|
42
|
-
getUserFromToken: () => getUserFromToken,
|
|
43
|
-
initializeApp: () => initializeApp,
|
|
44
|
-
iterateCollection: () => iterateCollection,
|
|
45
|
-
listDocuments: () => listDocuments,
|
|
46
|
-
listFiles: () => listFiles,
|
|
47
|
-
queryDocuments: () => queryDocuments,
|
|
48
|
-
setDocument: () => setDocument,
|
|
49
|
-
signInWithCustomToken: () => signInWithCustomToken,
|
|
50
|
-
updateDocument: () => updateDocument,
|
|
51
|
-
uploadFile: () => uploadFile,
|
|
52
|
-
uploadFileResumable: () => uploadFileResumable,
|
|
53
|
-
verifyIdToken: () => verifyIdToken
|
|
54
|
-
});
|
|
55
|
-
module.exports = __toCommonJS(index_exports);
|
|
56
|
-
|
|
57
23
|
// src/config.ts
|
|
58
|
-
var globalConfig = {};
|
|
59
24
|
function initializeApp(config) {
|
|
60
25
|
globalConfig = { ...config };
|
|
61
26
|
}
|
|
@@ -132,6 +97,149 @@ function getFirebaseApiKey() {
|
|
|
132
97
|
"Firebase API key not configured. Either call initializeApp({ apiKey: ... }) or set FIREBASE_API_KEY environment variable. Find your API key in Firebase Console > Project Settings > Web API Key."
|
|
133
98
|
);
|
|
134
99
|
}
|
|
100
|
+
var globalConfig;
|
|
101
|
+
var init_config = __esm({
|
|
102
|
+
"src/config.ts"() {
|
|
103
|
+
"use strict";
|
|
104
|
+
globalConfig = {};
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// src/service-account.ts
|
|
109
|
+
var init_service_account = __esm({
|
|
110
|
+
"src/service-account.ts"() {
|
|
111
|
+
"use strict";
|
|
112
|
+
init_config();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// src/token-generation.ts
|
|
117
|
+
var token_generation_exports = {};
|
|
118
|
+
__export(token_generation_exports, {
|
|
119
|
+
clearTokenCache: () => clearTokenCache,
|
|
120
|
+
getAdminAccessToken: () => getAdminAccessToken
|
|
121
|
+
});
|
|
122
|
+
function base64UrlEncode(str) {
|
|
123
|
+
return btoa(str).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
124
|
+
}
|
|
125
|
+
function base64UrlEncodeBuffer(buffer) {
|
|
126
|
+
return btoa(String.fromCharCode(...buffer)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
127
|
+
}
|
|
128
|
+
async function createJWT(serviceAccount) {
|
|
129
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
130
|
+
const expiry = now + 3600;
|
|
131
|
+
const header = {
|
|
132
|
+
alg: "RS256",
|
|
133
|
+
typ: "JWT"
|
|
134
|
+
};
|
|
135
|
+
const payload = {
|
|
136
|
+
iss: serviceAccount.client_email,
|
|
137
|
+
sub: serviceAccount.client_email,
|
|
138
|
+
aud: serviceAccount.token_uri,
|
|
139
|
+
iat: now,
|
|
140
|
+
exp: expiry,
|
|
141
|
+
scope: "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore https://www.googleapis.com/auth/firebase"
|
|
142
|
+
};
|
|
143
|
+
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
|
144
|
+
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
|
145
|
+
const unsignedToken = `${encodedHeader}.${encodedPayload}`;
|
|
146
|
+
const pemContents = serviceAccount.private_key.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace(/\s/g, "");
|
|
147
|
+
const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
|
|
148
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
149
|
+
"pkcs8",
|
|
150
|
+
binaryDer,
|
|
151
|
+
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
152
|
+
false,
|
|
153
|
+
["sign"]
|
|
154
|
+
);
|
|
155
|
+
const signature = await crypto.subtle.sign(
|
|
156
|
+
"RSASSA-PKCS1-v1_5",
|
|
157
|
+
cryptoKey,
|
|
158
|
+
new TextEncoder().encode(unsignedToken)
|
|
159
|
+
);
|
|
160
|
+
const encodedSignature = base64UrlEncodeBuffer(new Uint8Array(signature));
|
|
161
|
+
return `${unsignedToken}.${encodedSignature}`;
|
|
162
|
+
}
|
|
163
|
+
async function getAdminAccessToken() {
|
|
164
|
+
if (cachedAccessToken && Date.now() < tokenExpiry) {
|
|
165
|
+
return cachedAccessToken;
|
|
166
|
+
}
|
|
167
|
+
const serviceAccount = getServiceAccount();
|
|
168
|
+
const jwt = await createJWT(serviceAccount);
|
|
169
|
+
const response = await fetch(serviceAccount.token_uri, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
172
|
+
body: new URLSearchParams({
|
|
173
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
174
|
+
assertion: jwt
|
|
175
|
+
})
|
|
176
|
+
});
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
const errorText = await response.text();
|
|
179
|
+
throw new Error(`Failed to get access token: ${errorText}`);
|
|
180
|
+
}
|
|
181
|
+
const data = await response.json();
|
|
182
|
+
cachedAccessToken = data.access_token;
|
|
183
|
+
tokenExpiry = Date.now() + data.expires_in * 1e3 - 6e4;
|
|
184
|
+
return cachedAccessToken;
|
|
185
|
+
}
|
|
186
|
+
function clearTokenCache() {
|
|
187
|
+
cachedAccessToken = null;
|
|
188
|
+
tokenExpiry = 0;
|
|
189
|
+
}
|
|
190
|
+
var cachedAccessToken, tokenExpiry;
|
|
191
|
+
var init_token_generation = __esm({
|
|
192
|
+
"src/token-generation.ts"() {
|
|
193
|
+
"use strict";
|
|
194
|
+
init_service_account();
|
|
195
|
+
cachedAccessToken = null;
|
|
196
|
+
tokenExpiry = 0;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// src/index.ts
|
|
201
|
+
var index_exports = {};
|
|
202
|
+
__export(index_exports, {
|
|
203
|
+
FieldValue: () => FieldValue,
|
|
204
|
+
addDocument: () => addDocument,
|
|
205
|
+
batchWrite: () => batchWrite,
|
|
206
|
+
clearConfig: () => clearConfig,
|
|
207
|
+
clearTokenCache: () => clearTokenCache,
|
|
208
|
+
countDocuments: () => countDocuments,
|
|
209
|
+
createCustomToken: () => createCustomToken,
|
|
210
|
+
createSessionCookie: () => createSessionCookie,
|
|
211
|
+
deleteDocument: () => deleteDocument,
|
|
212
|
+
deleteFile: () => deleteFile,
|
|
213
|
+
downloadFile: () => downloadFile,
|
|
214
|
+
fileExists: () => fileExists,
|
|
215
|
+
generateSignedUrl: () => generateSignedUrl,
|
|
216
|
+
getAdminAccessToken: () => getAdminAccessToken,
|
|
217
|
+
getAuth: () => getAuth,
|
|
218
|
+
getConfig: () => getConfig,
|
|
219
|
+
getDocument: () => getDocument,
|
|
220
|
+
getFileMetadata: () => getFileMetadata,
|
|
221
|
+
getProjectId: () => getProjectId,
|
|
222
|
+
getServiceAccount: () => getServiceAccount,
|
|
223
|
+
getUserFromToken: () => getUserFromToken,
|
|
224
|
+
initializeApp: () => initializeApp,
|
|
225
|
+
iterateCollection: () => iterateCollection,
|
|
226
|
+
listDocuments: () => listDocuments,
|
|
227
|
+
listFiles: () => listFiles,
|
|
228
|
+
queryDocuments: () => queryDocuments,
|
|
229
|
+
setDocument: () => setDocument,
|
|
230
|
+
signInWithCustomToken: () => signInWithCustomToken,
|
|
231
|
+
updateDocument: () => updateDocument,
|
|
232
|
+
uploadFile: () => uploadFile,
|
|
233
|
+
uploadFileResumable: () => uploadFileResumable,
|
|
234
|
+
verifyIdToken: () => verifyIdToken,
|
|
235
|
+
verifySessionCookie: () => verifySessionCookie
|
|
236
|
+
});
|
|
237
|
+
module.exports = __toCommonJS(index_exports);
|
|
238
|
+
init_config();
|
|
239
|
+
|
|
240
|
+
// src/auth.ts
|
|
241
|
+
init_service_account();
|
|
242
|
+
init_config();
|
|
135
243
|
|
|
136
244
|
// src/x509.ts
|
|
137
245
|
function decodeBase64(str) {
|
|
@@ -222,23 +330,41 @@ async function importPublicKeyFromX509(pem) {
|
|
|
222
330
|
}
|
|
223
331
|
|
|
224
332
|
// src/auth.ts
|
|
225
|
-
var
|
|
226
|
-
var
|
|
333
|
+
var idTokenKeysCache = null;
|
|
334
|
+
var idTokenKeysCacheExpiry = 0;
|
|
335
|
+
var sessionKeysCache = null;
|
|
336
|
+
var sessionKeysCacheExpiry = 0;
|
|
227
337
|
async function fetchPublicKeys(issuer) {
|
|
228
|
-
|
|
229
|
-
|
|
338
|
+
const isSessionCookie = issuer && issuer.includes("session.firebase.google.com");
|
|
339
|
+
let endpoint;
|
|
340
|
+
let cache;
|
|
341
|
+
let cacheExpiry;
|
|
342
|
+
if (isSessionCookie) {
|
|
230
343
|
endpoint = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys";
|
|
344
|
+
cache = sessionKeysCache;
|
|
345
|
+
cacheExpiry = sessionKeysCacheExpiry;
|
|
346
|
+
} else {
|
|
347
|
+
endpoint = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
|
|
348
|
+
cache = idTokenKeysCache;
|
|
349
|
+
cacheExpiry = idTokenKeysCacheExpiry;
|
|
231
350
|
}
|
|
232
|
-
if (
|
|
233
|
-
return
|
|
351
|
+
if (cache && Date.now() < cacheExpiry) {
|
|
352
|
+
return cache;
|
|
234
353
|
}
|
|
235
354
|
const response = await fetch(endpoint);
|
|
236
355
|
if (!response.ok) {
|
|
237
356
|
throw new Error(`Failed to fetch Firebase public keys from ${endpoint}`);
|
|
238
357
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
358
|
+
const keys = await response.json();
|
|
359
|
+
const newExpiry = Date.now() + 36e5;
|
|
360
|
+
if (isSessionCookie) {
|
|
361
|
+
sessionKeysCache = keys;
|
|
362
|
+
sessionKeysCacheExpiry = newExpiry;
|
|
363
|
+
} else {
|
|
364
|
+
idTokenKeysCache = keys;
|
|
365
|
+
idTokenKeysCacheExpiry = newExpiry;
|
|
366
|
+
}
|
|
367
|
+
return keys;
|
|
242
368
|
}
|
|
243
369
|
function base64UrlDecode(str) {
|
|
244
370
|
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
@@ -313,8 +439,14 @@ async function verifyIdToken(idToken) {
|
|
|
313
439
|
let publicKeys = await fetchPublicKeys(payload.iss);
|
|
314
440
|
let publicKeyPem = publicKeys[header.kid];
|
|
315
441
|
if (!publicKeyPem) {
|
|
316
|
-
|
|
317
|
-
|
|
442
|
+
const isSessionCookie = payload.iss && payload.iss.includes("session.firebase.google.com");
|
|
443
|
+
if (isSessionCookie) {
|
|
444
|
+
sessionKeysCache = null;
|
|
445
|
+
sessionKeysCacheExpiry = 0;
|
|
446
|
+
} else {
|
|
447
|
+
idTokenKeysCache = null;
|
|
448
|
+
idTokenKeysCacheExpiry = 0;
|
|
449
|
+
}
|
|
318
450
|
publicKeys = await fetchPublicKeys(payload.iss);
|
|
319
451
|
publicKeyPem = publicKeys[header.kid];
|
|
320
452
|
if (!publicKeyPem) {
|
|
@@ -349,7 +481,7 @@ async function getUserFromToken(idToken) {
|
|
|
349
481
|
photoURL: decodedToken.picture || null
|
|
350
482
|
};
|
|
351
483
|
}
|
|
352
|
-
function
|
|
484
|
+
function base64UrlEncode2(str) {
|
|
353
485
|
return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
354
486
|
}
|
|
355
487
|
async function signWithPrivateKey(data, privateKey) {
|
|
@@ -378,7 +510,7 @@ async function signWithPrivateKey(data, privateKey) {
|
|
|
378
510
|
key,
|
|
379
511
|
dataBytes
|
|
380
512
|
);
|
|
381
|
-
return
|
|
513
|
+
return base64UrlEncode2(String.fromCharCode(...new Uint8Array(signature)));
|
|
382
514
|
}
|
|
383
515
|
async function createCustomToken(uid, customClaims) {
|
|
384
516
|
const serviceAccount = getServiceAccount();
|
|
@@ -405,8 +537,8 @@ async function createCustomToken(uid, customClaims) {
|
|
|
405
537
|
alg: "RS256",
|
|
406
538
|
typ: "JWT"
|
|
407
539
|
};
|
|
408
|
-
const encodedHeader =
|
|
409
|
-
const encodedPayload =
|
|
540
|
+
const encodedHeader = base64UrlEncode2(JSON.stringify(header));
|
|
541
|
+
const encodedPayload = base64UrlEncode2(JSON.stringify(payload));
|
|
410
542
|
const unsignedToken = `${encodedHeader}.${encodedPayload}`;
|
|
411
543
|
const signature = await signWithPrivateKey(unsignedToken, serviceAccount.private_key);
|
|
412
544
|
return `${unsignedToken}.${signature}`;
|
|
@@ -448,6 +580,136 @@ async function signInWithCustomToken(customToken) {
|
|
|
448
580
|
isNewUser: result.isNewUser
|
|
449
581
|
};
|
|
450
582
|
}
|
|
583
|
+
async function createSessionCookie(idToken, options) {
|
|
584
|
+
if (!idToken || typeof idToken !== "string") {
|
|
585
|
+
throw new Error("idToken must be a non-empty string");
|
|
586
|
+
}
|
|
587
|
+
if (!options.expiresIn || typeof options.expiresIn !== "number") {
|
|
588
|
+
throw new Error("expiresIn must be a number");
|
|
589
|
+
}
|
|
590
|
+
const MIN_DURATION = 5 * 60 * 1e3;
|
|
591
|
+
const MAX_DURATION = 14 * 24 * 60 * 60 * 1e3;
|
|
592
|
+
if (options.expiresIn < MIN_DURATION) {
|
|
593
|
+
throw new Error(`expiresIn must be at least ${MIN_DURATION}ms (5 minutes)`);
|
|
594
|
+
}
|
|
595
|
+
if (options.expiresIn > MAX_DURATION) {
|
|
596
|
+
throw new Error(`expiresIn must be at most ${MAX_DURATION}ms (14 days)`);
|
|
597
|
+
}
|
|
598
|
+
const { getAdminAccessToken: getAdminAccessToken2 } = await Promise.resolve().then(() => (init_token_generation(), token_generation_exports));
|
|
599
|
+
const accessToken = await getAdminAccessToken2();
|
|
600
|
+
const projectId = getProjectId();
|
|
601
|
+
const url = `https://identitytoolkit.googleapis.com/v1/projects/${projectId}:createSessionCookie`;
|
|
602
|
+
const validDurationSeconds = Math.floor(options.expiresIn / 1e3);
|
|
603
|
+
const response = await fetch(url, {
|
|
604
|
+
method: "POST",
|
|
605
|
+
headers: {
|
|
606
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
607
|
+
"Content-Type": "application/json"
|
|
608
|
+
},
|
|
609
|
+
body: JSON.stringify({
|
|
610
|
+
idToken,
|
|
611
|
+
validDuration: validDurationSeconds.toString()
|
|
612
|
+
// Must be string per API spec
|
|
613
|
+
})
|
|
614
|
+
});
|
|
615
|
+
if (!response.ok) {
|
|
616
|
+
const errorText = await response.text();
|
|
617
|
+
let errorMessage = `Failed to create session cookie: ${response.status}`;
|
|
618
|
+
try {
|
|
619
|
+
const errorJson = JSON.parse(errorText);
|
|
620
|
+
if (errorJson.error && errorJson.error.message) {
|
|
621
|
+
errorMessage += ` - ${errorJson.error.message}`;
|
|
622
|
+
}
|
|
623
|
+
} catch {
|
|
624
|
+
errorMessage += ` - ${errorText}`;
|
|
625
|
+
}
|
|
626
|
+
throw new Error(errorMessage);
|
|
627
|
+
}
|
|
628
|
+
const result = await response.json();
|
|
629
|
+
return result.sessionCookie;
|
|
630
|
+
}
|
|
631
|
+
async function verifySessionCookie(sessionCookie, checkRevoked = false) {
|
|
632
|
+
if (!sessionCookie || typeof sessionCookie !== "string") {
|
|
633
|
+
throw new Error("sessionCookie must be a non-empty string");
|
|
634
|
+
}
|
|
635
|
+
try {
|
|
636
|
+
const parts = sessionCookie.split(".");
|
|
637
|
+
if (parts.length !== 3) {
|
|
638
|
+
throw new Error("Invalid session cookie format");
|
|
639
|
+
}
|
|
640
|
+
const [headerB64, payloadB64] = parts;
|
|
641
|
+
const headerJson = atob(headerB64.replace(/-/g, "+").replace(/_/g, "/"));
|
|
642
|
+
const header = JSON.parse(headerJson);
|
|
643
|
+
const payloadJson = atob(payloadB64.replace(/-/g, "+").replace(/_/g, "/"));
|
|
644
|
+
const payload = JSON.parse(payloadJson);
|
|
645
|
+
const projectId = getProjectId();
|
|
646
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
647
|
+
if (!payload.exp || payload.exp < now) {
|
|
648
|
+
throw new Error("Session cookie has expired");
|
|
649
|
+
}
|
|
650
|
+
if (!payload.iat || payload.iat > now) {
|
|
651
|
+
throw new Error("Session cookie issued in the future");
|
|
652
|
+
}
|
|
653
|
+
if (payload.aud !== projectId) {
|
|
654
|
+
throw new Error(`Session cookie has incorrect audience. Expected ${projectId}, got ${payload.aud}`);
|
|
655
|
+
}
|
|
656
|
+
const expectedIssuer = `https://session.firebase.google.com/${projectId}`;
|
|
657
|
+
if (payload.iss !== expectedIssuer) {
|
|
658
|
+
throw new Error(`Session cookie has incorrect issuer. Expected ${expectedIssuer}, got ${payload.iss}`);
|
|
659
|
+
}
|
|
660
|
+
if (!payload.sub || typeof payload.sub !== "string" || payload.sub.length === 0) {
|
|
661
|
+
throw new Error("Session cookie has no subject (user ID)");
|
|
662
|
+
}
|
|
663
|
+
await verifySessionCookieSignature(sessionCookie, header, payload);
|
|
664
|
+
if (checkRevoked) {
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
uid: payload.sub,
|
|
668
|
+
aud: payload.aud,
|
|
669
|
+
auth_time: payload.auth_time,
|
|
670
|
+
exp: payload.exp,
|
|
671
|
+
iat: payload.iat,
|
|
672
|
+
iss: payload.iss,
|
|
673
|
+
sub: payload.sub,
|
|
674
|
+
email: payload.email,
|
|
675
|
+
email_verified: payload.email_verified,
|
|
676
|
+
firebase: payload.firebase,
|
|
677
|
+
...payload
|
|
678
|
+
};
|
|
679
|
+
} catch (error) {
|
|
680
|
+
throw new Error(`Failed to verify session cookie: ${error.message}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
async function verifySessionCookieSignature(jwt, header, payload) {
|
|
684
|
+
const keys = await fetchPublicKeys(payload.iss);
|
|
685
|
+
const kid = header.kid;
|
|
686
|
+
if (!kid || !keys[kid]) {
|
|
687
|
+
throw new Error("Session cookie has invalid key ID");
|
|
688
|
+
}
|
|
689
|
+
const publicKeyPem = keys[kid];
|
|
690
|
+
const publicKey = await importPublicKeyFromX509(publicKeyPem);
|
|
691
|
+
const [headerAndPayload, signature] = [
|
|
692
|
+
jwt.split(".").slice(0, 2).join("."),
|
|
693
|
+
jwt.split(".")[2]
|
|
694
|
+
];
|
|
695
|
+
const encoder = new TextEncoder();
|
|
696
|
+
const data = encoder.encode(headerAndPayload);
|
|
697
|
+
const signatureBase64 = signature.replace(/-/g, "+").replace(/_/g, "/");
|
|
698
|
+
const signatureBinary = atob(signatureBase64);
|
|
699
|
+
const signatureBytes = new Uint8Array(signatureBinary.length);
|
|
700
|
+
for (let i = 0; i < signatureBinary.length; i++) {
|
|
701
|
+
signatureBytes[i] = signatureBinary.charCodeAt(i);
|
|
702
|
+
}
|
|
703
|
+
const isValid = await crypto.subtle.verify(
|
|
704
|
+
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
705
|
+
publicKey,
|
|
706
|
+
signatureBytes,
|
|
707
|
+
data
|
|
708
|
+
);
|
|
709
|
+
if (!isValid) {
|
|
710
|
+
throw new Error("Session cookie signature verification failed");
|
|
711
|
+
}
|
|
712
|
+
}
|
|
451
713
|
function getAuth() {
|
|
452
714
|
return {
|
|
453
715
|
verifyIdToken
|
|
@@ -730,77 +992,9 @@ function removeFieldTransforms(data) {
|
|
|
730
992
|
return result;
|
|
731
993
|
}
|
|
732
994
|
|
|
733
|
-
// src/
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
}
|
|
737
|
-
function base64UrlEncodeBuffer(buffer) {
|
|
738
|
-
return btoa(String.fromCharCode(...buffer)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
739
|
-
}
|
|
740
|
-
async function createJWT(serviceAccount) {
|
|
741
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
742
|
-
const expiry = now + 3600;
|
|
743
|
-
const header = {
|
|
744
|
-
alg: "RS256",
|
|
745
|
-
typ: "JWT"
|
|
746
|
-
};
|
|
747
|
-
const payload = {
|
|
748
|
-
iss: serviceAccount.client_email,
|
|
749
|
-
sub: serviceAccount.client_email,
|
|
750
|
-
aud: serviceAccount.token_uri,
|
|
751
|
-
iat: now,
|
|
752
|
-
exp: expiry,
|
|
753
|
-
scope: "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore https://www.googleapis.com/auth/firebase"
|
|
754
|
-
};
|
|
755
|
-
const encodedHeader = base64UrlEncode2(JSON.stringify(header));
|
|
756
|
-
const encodedPayload = base64UrlEncode2(JSON.stringify(payload));
|
|
757
|
-
const unsignedToken = `${encodedHeader}.${encodedPayload}`;
|
|
758
|
-
const pemContents = serviceAccount.private_key.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace(/\s/g, "");
|
|
759
|
-
const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
|
|
760
|
-
const cryptoKey = await crypto.subtle.importKey(
|
|
761
|
-
"pkcs8",
|
|
762
|
-
binaryDer,
|
|
763
|
-
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
764
|
-
false,
|
|
765
|
-
["sign"]
|
|
766
|
-
);
|
|
767
|
-
const signature = await crypto.subtle.sign(
|
|
768
|
-
"RSASSA-PKCS1-v1_5",
|
|
769
|
-
cryptoKey,
|
|
770
|
-
new TextEncoder().encode(unsignedToken)
|
|
771
|
-
);
|
|
772
|
-
const encodedSignature = base64UrlEncodeBuffer(new Uint8Array(signature));
|
|
773
|
-
return `${unsignedToken}.${encodedSignature}`;
|
|
774
|
-
}
|
|
775
|
-
var cachedAccessToken = null;
|
|
776
|
-
var tokenExpiry = 0;
|
|
777
|
-
async function getAdminAccessToken() {
|
|
778
|
-
if (cachedAccessToken && Date.now() < tokenExpiry) {
|
|
779
|
-
return cachedAccessToken;
|
|
780
|
-
}
|
|
781
|
-
const serviceAccount = getServiceAccount();
|
|
782
|
-
const jwt = await createJWT(serviceAccount);
|
|
783
|
-
const response = await fetch(serviceAccount.token_uri, {
|
|
784
|
-
method: "POST",
|
|
785
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
786
|
-
body: new URLSearchParams({
|
|
787
|
-
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
788
|
-
assertion: jwt
|
|
789
|
-
})
|
|
790
|
-
});
|
|
791
|
-
if (!response.ok) {
|
|
792
|
-
const errorText = await response.text();
|
|
793
|
-
throw new Error(`Failed to get access token: ${errorText}`);
|
|
794
|
-
}
|
|
795
|
-
const data = await response.json();
|
|
796
|
-
cachedAccessToken = data.access_token;
|
|
797
|
-
tokenExpiry = Date.now() + data.expires_in * 1e3 - 6e4;
|
|
798
|
-
return cachedAccessToken;
|
|
799
|
-
}
|
|
800
|
-
function clearTokenCache() {
|
|
801
|
-
cachedAccessToken = null;
|
|
802
|
-
tokenExpiry = 0;
|
|
803
|
-
}
|
|
995
|
+
// src/firestore/operations.ts
|
|
996
|
+
init_token_generation();
|
|
997
|
+
init_service_account();
|
|
804
998
|
|
|
805
999
|
// src/firestore/path-validation.ts
|
|
806
1000
|
function validateCollectionPath(argumentName, collectionPath) {
|
|
@@ -1172,6 +1366,8 @@ async function countDocuments(collectionPath, options) {
|
|
|
1172
1366
|
}
|
|
1173
1367
|
|
|
1174
1368
|
// src/storage/client.ts
|
|
1369
|
+
init_token_generation();
|
|
1370
|
+
init_config();
|
|
1175
1371
|
var STORAGE_API_BASE = "https://storage.googleapis.com/storage/v1";
|
|
1176
1372
|
var UPLOAD_API_BASE = "https://storage.googleapis.com/upload/storage/v1";
|
|
1177
1373
|
function getDefaultBucket() {
|
|
@@ -1366,6 +1562,7 @@ async function fileExists(path) {
|
|
|
1366
1562
|
}
|
|
1367
1563
|
|
|
1368
1564
|
// src/storage/signed-urls.ts
|
|
1565
|
+
init_config();
|
|
1369
1566
|
function getStorageBucket() {
|
|
1370
1567
|
const customBucket = process.env.FIREBASE_STORAGE_BUCKET;
|
|
1371
1568
|
if (customBucket) {
|
|
@@ -1487,6 +1684,8 @@ UNSIGNED-PAYLOAD`;
|
|
|
1487
1684
|
}
|
|
1488
1685
|
|
|
1489
1686
|
// src/storage/resumable-upload.ts
|
|
1687
|
+
init_token_generation();
|
|
1688
|
+
init_config();
|
|
1490
1689
|
var UPLOAD_API_BASE2 = "https://storage.googleapis.com/upload/storage/v1";
|
|
1491
1690
|
function getDefaultBucket2() {
|
|
1492
1691
|
const customBucket = process.env.FIREBASE_STORAGE_BUCKET;
|
|
@@ -1683,6 +1882,10 @@ async function uploadFromStream(bucket, path, stream, contentType, chunkSize, op
|
|
|
1683
1882
|
reader.releaseLock();
|
|
1684
1883
|
}
|
|
1685
1884
|
}
|
|
1885
|
+
|
|
1886
|
+
// src/index.ts
|
|
1887
|
+
init_token_generation();
|
|
1888
|
+
init_service_account();
|
|
1686
1889
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1687
1890
|
0 && (module.exports = {
|
|
1688
1891
|
FieldValue,
|
|
@@ -1692,6 +1895,7 @@ async function uploadFromStream(bucket, path, stream, contentType, chunkSize, op
|
|
|
1692
1895
|
clearTokenCache,
|
|
1693
1896
|
countDocuments,
|
|
1694
1897
|
createCustomToken,
|
|
1898
|
+
createSessionCookie,
|
|
1695
1899
|
deleteDocument,
|
|
1696
1900
|
deleteFile,
|
|
1697
1901
|
downloadFile,
|
|
@@ -1715,5 +1919,6 @@ async function uploadFromStream(bucket, path, stream, contentType, chunkSize, op
|
|
|
1715
1919
|
updateDocument,
|
|
1716
1920
|
uploadFile,
|
|
1717
1921
|
uploadFileResumable,
|
|
1718
|
-
verifyIdToken
|
|
1922
|
+
verifyIdToken,
|
|
1923
|
+
verifySessionCookie
|
|
1719
1924
|
});
|