@prmichaelsen/firebase-admin-sdk-v8 2.0.22 → 2.1.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/dist/index.d.mts +56 -1
- package/dist/index.d.ts +56 -1
- package/dist/index.js +143 -10
- package/dist/index.mjs +141 -10
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -195,6 +195,7 @@ interface BatchWriteResult {
|
|
|
195
195
|
interface SDKConfig {
|
|
196
196
|
serviceAccount?: ServiceAccount | string;
|
|
197
197
|
projectId?: string;
|
|
198
|
+
apiKey?: string;
|
|
198
199
|
}
|
|
199
200
|
/**
|
|
200
201
|
* Initialize the Firebase Admin SDK with configuration
|
|
@@ -250,6 +251,21 @@ declare function getProjectId(): string;
|
|
|
250
251
|
* ID token verification supporting both Firebase v9 and v10 token formats
|
|
251
252
|
*/
|
|
252
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Custom claims for custom tokens
|
|
256
|
+
*/
|
|
257
|
+
interface CustomClaims {
|
|
258
|
+
[key: string]: any;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Response from signInWithCustomToken
|
|
262
|
+
*/
|
|
263
|
+
interface CustomTokenSignInResponse {
|
|
264
|
+
idToken: string;
|
|
265
|
+
refreshToken: string;
|
|
266
|
+
expiresIn: string;
|
|
267
|
+
localId: string;
|
|
268
|
+
}
|
|
253
269
|
/**
|
|
254
270
|
* Verify a Firebase ID token
|
|
255
271
|
* Supports both Firebase v9 (securetoken) and v10 (session) token formats
|
|
@@ -280,6 +296,45 @@ declare function verifyIdToken(idToken: string): Promise<DecodedIdToken>;
|
|
|
280
296
|
* ```
|
|
281
297
|
*/
|
|
282
298
|
declare function getUserFromToken(idToken: string): Promise<UserInfo>;
|
|
299
|
+
/**
|
|
300
|
+
* Create a custom token for a user
|
|
301
|
+
*
|
|
302
|
+
* @param uid - User ID
|
|
303
|
+
* @param customClaims - Optional custom claims to include in token
|
|
304
|
+
* @returns Custom JWT token
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```typescript
|
|
308
|
+
* // Create token with custom claims
|
|
309
|
+
* const token = await createCustomToken('user123', {
|
|
310
|
+
* role: 'admin',
|
|
311
|
+
* premium: true,
|
|
312
|
+
* });
|
|
313
|
+
*
|
|
314
|
+
* // Use token on client to sign in
|
|
315
|
+
* await signInWithCustomToken(auth, token);
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
declare function createCustomToken(uid: string, customClaims?: CustomClaims): Promise<string>;
|
|
319
|
+
/**
|
|
320
|
+
* Exchange a custom token for an ID token and refresh token
|
|
321
|
+
*
|
|
322
|
+
* @param customToken - Custom JWT token created with createCustomToken
|
|
323
|
+
* @returns User credentials (idToken, refreshToken, expiresIn, localId)
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```typescript
|
|
327
|
+
* // Server-side: Create custom token
|
|
328
|
+
* const customToken = await createCustomToken('user123', { role: 'admin' });
|
|
329
|
+
*
|
|
330
|
+
* // Exchange for ID token
|
|
331
|
+
* const credentials = await signInWithCustomToken(customToken);
|
|
332
|
+
*
|
|
333
|
+
* // Use ID token for authenticated requests
|
|
334
|
+
* const user = await verifyIdToken(credentials.idToken);
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
declare function signInWithCustomToken(customToken: string): Promise<CustomTokenSignInResponse>;
|
|
283
338
|
/**
|
|
284
339
|
* Get Auth instance (for compatibility, but not used in new implementation)
|
|
285
340
|
* @deprecated Use verifyIdToken directly
|
|
@@ -705,4 +760,4 @@ declare function getAdminAccessToken(): Promise<string>;
|
|
|
705
760
|
*/
|
|
706
761
|
declare function clearTokenCache(): void;
|
|
707
762
|
|
|
708
|
-
export { type BatchWrite, type BatchWriteResult, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type QueryFilter, type QueryOptions, type QueryOrder, type ServiceAccount, type SetOptions, type SignedUrlOptions, type TokenResponse, type UpdateOptions, type UploadOptions, type UserInfo, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, deleteDocument, deleteFile, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserFromToken, initializeApp, listFiles, queryDocuments, setDocument, updateDocument, uploadFile, verifyIdToken };
|
|
763
|
+
export { type BatchWrite, type BatchWriteResult, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type QueryFilter, type QueryOptions, type QueryOrder, type ServiceAccount, type SetOptions, type SignedUrlOptions, type TokenResponse, type UpdateOptions, type UploadOptions, type UserInfo, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, createCustomToken, deleteDocument, deleteFile, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserFromToken, initializeApp, listFiles, queryDocuments, setDocument, signInWithCustomToken, updateDocument, uploadFile, verifyIdToken };
|
package/dist/index.d.ts
CHANGED
|
@@ -195,6 +195,7 @@ interface BatchWriteResult {
|
|
|
195
195
|
interface SDKConfig {
|
|
196
196
|
serviceAccount?: ServiceAccount | string;
|
|
197
197
|
projectId?: string;
|
|
198
|
+
apiKey?: string;
|
|
198
199
|
}
|
|
199
200
|
/**
|
|
200
201
|
* Initialize the Firebase Admin SDK with configuration
|
|
@@ -250,6 +251,21 @@ declare function getProjectId(): string;
|
|
|
250
251
|
* ID token verification supporting both Firebase v9 and v10 token formats
|
|
251
252
|
*/
|
|
252
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Custom claims for custom tokens
|
|
256
|
+
*/
|
|
257
|
+
interface CustomClaims {
|
|
258
|
+
[key: string]: any;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Response from signInWithCustomToken
|
|
262
|
+
*/
|
|
263
|
+
interface CustomTokenSignInResponse {
|
|
264
|
+
idToken: string;
|
|
265
|
+
refreshToken: string;
|
|
266
|
+
expiresIn: string;
|
|
267
|
+
localId: string;
|
|
268
|
+
}
|
|
253
269
|
/**
|
|
254
270
|
* Verify a Firebase ID token
|
|
255
271
|
* Supports both Firebase v9 (securetoken) and v10 (session) token formats
|
|
@@ -280,6 +296,45 @@ declare function verifyIdToken(idToken: string): Promise<DecodedIdToken>;
|
|
|
280
296
|
* ```
|
|
281
297
|
*/
|
|
282
298
|
declare function getUserFromToken(idToken: string): Promise<UserInfo>;
|
|
299
|
+
/**
|
|
300
|
+
* Create a custom token for a user
|
|
301
|
+
*
|
|
302
|
+
* @param uid - User ID
|
|
303
|
+
* @param customClaims - Optional custom claims to include in token
|
|
304
|
+
* @returns Custom JWT token
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```typescript
|
|
308
|
+
* // Create token with custom claims
|
|
309
|
+
* const token = await createCustomToken('user123', {
|
|
310
|
+
* role: 'admin',
|
|
311
|
+
* premium: true,
|
|
312
|
+
* });
|
|
313
|
+
*
|
|
314
|
+
* // Use token on client to sign in
|
|
315
|
+
* await signInWithCustomToken(auth, token);
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
declare function createCustomToken(uid: string, customClaims?: CustomClaims): Promise<string>;
|
|
319
|
+
/**
|
|
320
|
+
* Exchange a custom token for an ID token and refresh token
|
|
321
|
+
*
|
|
322
|
+
* @param customToken - Custom JWT token created with createCustomToken
|
|
323
|
+
* @returns User credentials (idToken, refreshToken, expiresIn, localId)
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```typescript
|
|
327
|
+
* // Server-side: Create custom token
|
|
328
|
+
* const customToken = await createCustomToken('user123', { role: 'admin' });
|
|
329
|
+
*
|
|
330
|
+
* // Exchange for ID token
|
|
331
|
+
* const credentials = await signInWithCustomToken(customToken);
|
|
332
|
+
*
|
|
333
|
+
* // Use ID token for authenticated requests
|
|
334
|
+
* const user = await verifyIdToken(credentials.idToken);
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
declare function signInWithCustomToken(customToken: string): Promise<CustomTokenSignInResponse>;
|
|
283
338
|
/**
|
|
284
339
|
* Get Auth instance (for compatibility, but not used in new implementation)
|
|
285
340
|
* @deprecated Use verifyIdToken directly
|
|
@@ -705,4 +760,4 @@ declare function getAdminAccessToken(): Promise<string>;
|
|
|
705
760
|
*/
|
|
706
761
|
declare function clearTokenCache(): void;
|
|
707
762
|
|
|
708
|
-
export { type BatchWrite, type BatchWriteResult, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type QueryFilter, type QueryOptions, type QueryOrder, type ServiceAccount, type SetOptions, type SignedUrlOptions, type TokenResponse, type UpdateOptions, type UploadOptions, type UserInfo, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, deleteDocument, deleteFile, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserFromToken, initializeApp, listFiles, queryDocuments, setDocument, updateDocument, uploadFile, verifyIdToken };
|
|
763
|
+
export { type BatchWrite, type BatchWriteResult, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type QueryFilter, type QueryOptions, type QueryOrder, type ServiceAccount, type SetOptions, type SignedUrlOptions, type TokenResponse, type UpdateOptions, type UploadOptions, type UserInfo, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, createCustomToken, deleteDocument, deleteFile, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserFromToken, initializeApp, listFiles, queryDocuments, setDocument, signInWithCustomToken, updateDocument, uploadFile, verifyIdToken };
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
batchWrite: () => batchWrite,
|
|
26
26
|
clearConfig: () => clearConfig,
|
|
27
27
|
clearTokenCache: () => clearTokenCache,
|
|
28
|
+
createCustomToken: () => createCustomToken,
|
|
28
29
|
deleteDocument: () => deleteDocument,
|
|
29
30
|
deleteFile: () => deleteFile,
|
|
30
31
|
downloadFile: () => downloadFile,
|
|
@@ -42,6 +43,7 @@ __export(index_exports, {
|
|
|
42
43
|
listFiles: () => listFiles,
|
|
43
44
|
queryDocuments: () => queryDocuments,
|
|
44
45
|
setDocument: () => setDocument,
|
|
46
|
+
signInWithCustomToken: () => signInWithCustomToken,
|
|
45
47
|
updateDocument: () => updateDocument,
|
|
46
48
|
uploadFile: () => uploadFile,
|
|
47
49
|
verifyIdToken: () => verifyIdToken
|
|
@@ -112,6 +114,20 @@ function getProjectId() {
|
|
|
112
114
|
"Firebase project ID not configured. Either call initializeApp({ projectId: ... }) or set FIREBASE_PROJECT_ID environment variable."
|
|
113
115
|
);
|
|
114
116
|
}
|
|
117
|
+
function getFirebaseApiKey() {
|
|
118
|
+
if (globalConfig.apiKey) {
|
|
119
|
+
return globalConfig.apiKey;
|
|
120
|
+
}
|
|
121
|
+
if (typeof process !== "undefined" && process.env) {
|
|
122
|
+
const apiKey = process.env.FIREBASE_API_KEY || process.env.PUBLIC_FIREBASE_API_KEY;
|
|
123
|
+
if (apiKey) {
|
|
124
|
+
return apiKey;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
throw new Error(
|
|
128
|
+
"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."
|
|
129
|
+
);
|
|
130
|
+
}
|
|
115
131
|
|
|
116
132
|
// src/x509.ts
|
|
117
133
|
function decodeBase64(str) {
|
|
@@ -332,6 +348,105 @@ async function getUserFromToken(idToken) {
|
|
|
332
348
|
photoURL: decodedToken.picture || null
|
|
333
349
|
};
|
|
334
350
|
}
|
|
351
|
+
function base64UrlEncode(str) {
|
|
352
|
+
return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
353
|
+
}
|
|
354
|
+
async function signWithPrivateKey(data, privateKey) {
|
|
355
|
+
const pemHeader = "-----BEGIN PRIVATE KEY-----";
|
|
356
|
+
const pemFooter = "-----END PRIVATE KEY-----";
|
|
357
|
+
const pemContents = privateKey.replace(pemHeader, "").replace(pemFooter, "").replace(/\s/g, "");
|
|
358
|
+
const binaryString = atob(pemContents);
|
|
359
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
360
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
361
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
362
|
+
}
|
|
363
|
+
const key = await crypto.subtle.importKey(
|
|
364
|
+
"pkcs8",
|
|
365
|
+
bytes,
|
|
366
|
+
{
|
|
367
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
368
|
+
hash: "SHA-256"
|
|
369
|
+
},
|
|
370
|
+
false,
|
|
371
|
+
["sign"]
|
|
372
|
+
);
|
|
373
|
+
const encoder = new TextEncoder();
|
|
374
|
+
const dataBytes = encoder.encode(data);
|
|
375
|
+
const signature = await crypto.subtle.sign(
|
|
376
|
+
"RSASSA-PKCS1-v1_5",
|
|
377
|
+
key,
|
|
378
|
+
dataBytes
|
|
379
|
+
);
|
|
380
|
+
return base64UrlEncode(String.fromCharCode(...new Uint8Array(signature)));
|
|
381
|
+
}
|
|
382
|
+
async function createCustomToken(uid, customClaims) {
|
|
383
|
+
const serviceAccount = getServiceAccount();
|
|
384
|
+
if (!uid || typeof uid !== "string") {
|
|
385
|
+
throw new Error("uid must be a non-empty string");
|
|
386
|
+
}
|
|
387
|
+
if (uid.length > 128) {
|
|
388
|
+
throw new Error("uid must be at most 128 characters");
|
|
389
|
+
}
|
|
390
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
391
|
+
const payload = {
|
|
392
|
+
iss: serviceAccount.client_email,
|
|
393
|
+
sub: serviceAccount.client_email,
|
|
394
|
+
aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
|
|
395
|
+
iat: now,
|
|
396
|
+
exp: now + 3600,
|
|
397
|
+
// 1 hour
|
|
398
|
+
uid
|
|
399
|
+
};
|
|
400
|
+
if (customClaims) {
|
|
401
|
+
payload.claims = customClaims;
|
|
402
|
+
}
|
|
403
|
+
const header = {
|
|
404
|
+
alg: "RS256",
|
|
405
|
+
typ: "JWT"
|
|
406
|
+
};
|
|
407
|
+
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
|
408
|
+
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
|
409
|
+
const unsignedToken = `${encodedHeader}.${encodedPayload}`;
|
|
410
|
+
const signature = await signWithPrivateKey(unsignedToken, serviceAccount.private_key);
|
|
411
|
+
return `${unsignedToken}.${signature}`;
|
|
412
|
+
}
|
|
413
|
+
async function signInWithCustomToken(customToken) {
|
|
414
|
+
if (!customToken || typeof customToken !== "string") {
|
|
415
|
+
throw new Error("customToken must be a non-empty string");
|
|
416
|
+
}
|
|
417
|
+
const apiKey = getFirebaseApiKey();
|
|
418
|
+
const url = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`;
|
|
419
|
+
const response = await fetch(url, {
|
|
420
|
+
method: "POST",
|
|
421
|
+
headers: {
|
|
422
|
+
"Content-Type": "application/json"
|
|
423
|
+
},
|
|
424
|
+
body: JSON.stringify({
|
|
425
|
+
token: customToken,
|
|
426
|
+
returnSecureToken: true
|
|
427
|
+
})
|
|
428
|
+
});
|
|
429
|
+
if (!response.ok) {
|
|
430
|
+
const errorText = await response.text();
|
|
431
|
+
let errorMessage = `Failed to sign in with custom token: ${response.status}`;
|
|
432
|
+
try {
|
|
433
|
+
const errorJson = JSON.parse(errorText);
|
|
434
|
+
if (errorJson.error && errorJson.error.message) {
|
|
435
|
+
errorMessage += ` - ${errorJson.error.message}`;
|
|
436
|
+
}
|
|
437
|
+
} catch {
|
|
438
|
+
errorMessage += ` - ${errorText}`;
|
|
439
|
+
}
|
|
440
|
+
throw new Error(errorMessage);
|
|
441
|
+
}
|
|
442
|
+
const result = await response.json();
|
|
443
|
+
return {
|
|
444
|
+
idToken: result.idToken,
|
|
445
|
+
refreshToken: result.refreshToken,
|
|
446
|
+
expiresIn: result.expiresIn,
|
|
447
|
+
localId: result.localId
|
|
448
|
+
};
|
|
449
|
+
}
|
|
335
450
|
function getAuth() {
|
|
336
451
|
return {
|
|
337
452
|
verifyIdToken
|
|
@@ -615,7 +730,7 @@ function removeFieldTransforms(data) {
|
|
|
615
730
|
}
|
|
616
731
|
|
|
617
732
|
// src/token-generation.ts
|
|
618
|
-
function
|
|
733
|
+
function base64UrlEncode2(str) {
|
|
619
734
|
return btoa(str).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
620
735
|
}
|
|
621
736
|
function base64UrlEncodeBuffer(buffer) {
|
|
@@ -636,8 +751,8 @@ async function createJWT(serviceAccount) {
|
|
|
636
751
|
exp: expiry,
|
|
637
752
|
scope: "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore https://www.googleapis.com/auth/firebase"
|
|
638
753
|
};
|
|
639
|
-
const encodedHeader =
|
|
640
|
-
const encodedPayload =
|
|
754
|
+
const encodedHeader = base64UrlEncode2(JSON.stringify(header));
|
|
755
|
+
const encodedPayload = base64UrlEncode2(JSON.stringify(payload));
|
|
641
756
|
const unsignedToken = `${encodedHeader}.${encodedPayload}`;
|
|
642
757
|
const pemContents = serviceAccount.private_key.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace(/\s/g, "");
|
|
643
758
|
const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
|
|
@@ -1019,6 +1134,10 @@ async function batchWrite(operations) {
|
|
|
1019
1134
|
var STORAGE_API_BASE = "https://storage.googleapis.com/storage/v1";
|
|
1020
1135
|
var UPLOAD_API_BASE = "https://storage.googleapis.com/upload/storage/v1";
|
|
1021
1136
|
function getDefaultBucket() {
|
|
1137
|
+
const customBucket = process.env.FIREBASE_STORAGE_BUCKET;
|
|
1138
|
+
if (customBucket) {
|
|
1139
|
+
return customBucket;
|
|
1140
|
+
}
|
|
1022
1141
|
const projectId = getProjectId();
|
|
1023
1142
|
return `${projectId}.appspot.com`;
|
|
1024
1143
|
}
|
|
@@ -1206,6 +1325,14 @@ async function fileExists(path) {
|
|
|
1206
1325
|
}
|
|
1207
1326
|
|
|
1208
1327
|
// src/storage/signed-urls.ts
|
|
1328
|
+
function getStorageBucket() {
|
|
1329
|
+
const customBucket = process.env.FIREBASE_STORAGE_BUCKET;
|
|
1330
|
+
if (customBucket) {
|
|
1331
|
+
return customBucket;
|
|
1332
|
+
}
|
|
1333
|
+
const projectId = getProjectId();
|
|
1334
|
+
return `${projectId}.appspot.com`;
|
|
1335
|
+
}
|
|
1209
1336
|
function getExpirationTimestamp(expires) {
|
|
1210
1337
|
if (expires instanceof Date) {
|
|
1211
1338
|
return Math.floor(expires.getTime() / 1e3);
|
|
@@ -1258,12 +1385,15 @@ async function signData(data, privateKey) {
|
|
|
1258
1385
|
}
|
|
1259
1386
|
async function generateSignedUrl(path, options) {
|
|
1260
1387
|
const serviceAccount = getServiceAccount();
|
|
1261
|
-
const
|
|
1262
|
-
const bucket = `${projectId}.appspot.com`;
|
|
1388
|
+
const bucket = getStorageBucket();
|
|
1263
1389
|
const method = actionToMethod(options.action);
|
|
1264
1390
|
const expiration = getExpirationTimestamp(options.expires);
|
|
1265
|
-
const
|
|
1266
|
-
const
|
|
1391
|
+
const now = /* @__PURE__ */ new Date();
|
|
1392
|
+
const timestamp = Math.floor(now.getTime() / 1e3);
|
|
1393
|
+
const isoString = now.toISOString();
|
|
1394
|
+
const datestamp = isoString.split("T")[0].replace(/-/g, "");
|
|
1395
|
+
const timeString = isoString.split("T")[1].replace(/[:.]/g, "").substring(0, 6);
|
|
1396
|
+
const dateTimeStamp = `${datestamp}T${timeString}Z`;
|
|
1267
1397
|
const credentialScope = `${datestamp}/auto/storage/goog4_request`;
|
|
1268
1398
|
const credential = `${serviceAccount.client_email}/${credentialScope}`;
|
|
1269
1399
|
const canonicalHeaders = `host:storage.googleapis.com
|
|
@@ -1272,7 +1402,7 @@ async function generateSignedUrl(path, options) {
|
|
|
1272
1402
|
const queryParams = {
|
|
1273
1403
|
"X-Goog-Algorithm": "GOOG4-RSA-SHA256",
|
|
1274
1404
|
"X-Goog-Credential": credential,
|
|
1275
|
-
"X-Goog-Date":
|
|
1405
|
+
"X-Goog-Date": dateTimeStamp,
|
|
1276
1406
|
"X-Goog-Expires": (expiration - timestamp).toString(),
|
|
1277
1407
|
"X-Goog-SignedHeaders": signedHeaders
|
|
1278
1408
|
};
|
|
@@ -1287,7 +1417,8 @@ async function generateSignedUrl(path, options) {
|
|
|
1287
1417
|
}
|
|
1288
1418
|
const sortedParams = Object.keys(queryParams).sort();
|
|
1289
1419
|
const canonicalQueryString = sortedParams.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`).join("&");
|
|
1290
|
-
const
|
|
1420
|
+
const encodedPath = path.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
1421
|
+
const canonicalUri = `/${bucket}/${encodedPath}`;
|
|
1291
1422
|
const canonicalRequest = [
|
|
1292
1423
|
method,
|
|
1293
1424
|
canonicalUri,
|
|
@@ -1299,7 +1430,7 @@ async function generateSignedUrl(path, options) {
|
|
|
1299
1430
|
const canonicalRequestHash = stringToHex(canonicalRequest);
|
|
1300
1431
|
const stringToSign = [
|
|
1301
1432
|
"GOOG4-RSA-SHA256",
|
|
1302
|
-
|
|
1433
|
+
dateTimeStamp,
|
|
1303
1434
|
credentialScope,
|
|
1304
1435
|
canonicalRequestHash
|
|
1305
1436
|
].join("\n");
|
|
@@ -1314,6 +1445,7 @@ async function generateSignedUrl(path, options) {
|
|
|
1314
1445
|
batchWrite,
|
|
1315
1446
|
clearConfig,
|
|
1316
1447
|
clearTokenCache,
|
|
1448
|
+
createCustomToken,
|
|
1317
1449
|
deleteDocument,
|
|
1318
1450
|
deleteFile,
|
|
1319
1451
|
downloadFile,
|
|
@@ -1331,6 +1463,7 @@ async function generateSignedUrl(path, options) {
|
|
|
1331
1463
|
listFiles,
|
|
1332
1464
|
queryDocuments,
|
|
1333
1465
|
setDocument,
|
|
1466
|
+
signInWithCustomToken,
|
|
1334
1467
|
updateDocument,
|
|
1335
1468
|
uploadFile,
|
|
1336
1469
|
verifyIdToken
|
package/dist/index.mjs
CHANGED
|
@@ -62,6 +62,20 @@ function getProjectId() {
|
|
|
62
62
|
"Firebase project ID not configured. Either call initializeApp({ projectId: ... }) or set FIREBASE_PROJECT_ID environment variable."
|
|
63
63
|
);
|
|
64
64
|
}
|
|
65
|
+
function getFirebaseApiKey() {
|
|
66
|
+
if (globalConfig.apiKey) {
|
|
67
|
+
return globalConfig.apiKey;
|
|
68
|
+
}
|
|
69
|
+
if (typeof process !== "undefined" && process.env) {
|
|
70
|
+
const apiKey = process.env.FIREBASE_API_KEY || process.env.PUBLIC_FIREBASE_API_KEY;
|
|
71
|
+
if (apiKey) {
|
|
72
|
+
return apiKey;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
throw new Error(
|
|
76
|
+
"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."
|
|
77
|
+
);
|
|
78
|
+
}
|
|
65
79
|
|
|
66
80
|
// src/x509.ts
|
|
67
81
|
function decodeBase64(str) {
|
|
@@ -282,6 +296,105 @@ async function getUserFromToken(idToken) {
|
|
|
282
296
|
photoURL: decodedToken.picture || null
|
|
283
297
|
};
|
|
284
298
|
}
|
|
299
|
+
function base64UrlEncode(str) {
|
|
300
|
+
return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
301
|
+
}
|
|
302
|
+
async function signWithPrivateKey(data, privateKey) {
|
|
303
|
+
const pemHeader = "-----BEGIN PRIVATE KEY-----";
|
|
304
|
+
const pemFooter = "-----END PRIVATE KEY-----";
|
|
305
|
+
const pemContents = privateKey.replace(pemHeader, "").replace(pemFooter, "").replace(/\s/g, "");
|
|
306
|
+
const binaryString = atob(pemContents);
|
|
307
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
308
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
309
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
310
|
+
}
|
|
311
|
+
const key = await crypto.subtle.importKey(
|
|
312
|
+
"pkcs8",
|
|
313
|
+
bytes,
|
|
314
|
+
{
|
|
315
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
316
|
+
hash: "SHA-256"
|
|
317
|
+
},
|
|
318
|
+
false,
|
|
319
|
+
["sign"]
|
|
320
|
+
);
|
|
321
|
+
const encoder = new TextEncoder();
|
|
322
|
+
const dataBytes = encoder.encode(data);
|
|
323
|
+
const signature = await crypto.subtle.sign(
|
|
324
|
+
"RSASSA-PKCS1-v1_5",
|
|
325
|
+
key,
|
|
326
|
+
dataBytes
|
|
327
|
+
);
|
|
328
|
+
return base64UrlEncode(String.fromCharCode(...new Uint8Array(signature)));
|
|
329
|
+
}
|
|
330
|
+
async function createCustomToken(uid, customClaims) {
|
|
331
|
+
const serviceAccount = getServiceAccount();
|
|
332
|
+
if (!uid || typeof uid !== "string") {
|
|
333
|
+
throw new Error("uid must be a non-empty string");
|
|
334
|
+
}
|
|
335
|
+
if (uid.length > 128) {
|
|
336
|
+
throw new Error("uid must be at most 128 characters");
|
|
337
|
+
}
|
|
338
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
339
|
+
const payload = {
|
|
340
|
+
iss: serviceAccount.client_email,
|
|
341
|
+
sub: serviceAccount.client_email,
|
|
342
|
+
aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
|
|
343
|
+
iat: now,
|
|
344
|
+
exp: now + 3600,
|
|
345
|
+
// 1 hour
|
|
346
|
+
uid
|
|
347
|
+
};
|
|
348
|
+
if (customClaims) {
|
|
349
|
+
payload.claims = customClaims;
|
|
350
|
+
}
|
|
351
|
+
const header = {
|
|
352
|
+
alg: "RS256",
|
|
353
|
+
typ: "JWT"
|
|
354
|
+
};
|
|
355
|
+
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
|
356
|
+
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
|
357
|
+
const unsignedToken = `${encodedHeader}.${encodedPayload}`;
|
|
358
|
+
const signature = await signWithPrivateKey(unsignedToken, serviceAccount.private_key);
|
|
359
|
+
return `${unsignedToken}.${signature}`;
|
|
360
|
+
}
|
|
361
|
+
async function signInWithCustomToken(customToken) {
|
|
362
|
+
if (!customToken || typeof customToken !== "string") {
|
|
363
|
+
throw new Error("customToken must be a non-empty string");
|
|
364
|
+
}
|
|
365
|
+
const apiKey = getFirebaseApiKey();
|
|
366
|
+
const url = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`;
|
|
367
|
+
const response = await fetch(url, {
|
|
368
|
+
method: "POST",
|
|
369
|
+
headers: {
|
|
370
|
+
"Content-Type": "application/json"
|
|
371
|
+
},
|
|
372
|
+
body: JSON.stringify({
|
|
373
|
+
token: customToken,
|
|
374
|
+
returnSecureToken: true
|
|
375
|
+
})
|
|
376
|
+
});
|
|
377
|
+
if (!response.ok) {
|
|
378
|
+
const errorText = await response.text();
|
|
379
|
+
let errorMessage = `Failed to sign in with custom token: ${response.status}`;
|
|
380
|
+
try {
|
|
381
|
+
const errorJson = JSON.parse(errorText);
|
|
382
|
+
if (errorJson.error && errorJson.error.message) {
|
|
383
|
+
errorMessage += ` - ${errorJson.error.message}`;
|
|
384
|
+
}
|
|
385
|
+
} catch {
|
|
386
|
+
errorMessage += ` - ${errorText}`;
|
|
387
|
+
}
|
|
388
|
+
throw new Error(errorMessage);
|
|
389
|
+
}
|
|
390
|
+
const result = await response.json();
|
|
391
|
+
return {
|
|
392
|
+
idToken: result.idToken,
|
|
393
|
+
refreshToken: result.refreshToken,
|
|
394
|
+
expiresIn: result.expiresIn,
|
|
395
|
+
localId: result.localId
|
|
396
|
+
};
|
|
397
|
+
}
|
|
285
398
|
function getAuth() {
|
|
286
399
|
return {
|
|
287
400
|
verifyIdToken
|
|
@@ -565,7 +678,7 @@ function removeFieldTransforms(data) {
|
|
|
565
678
|
}
|
|
566
679
|
|
|
567
680
|
// src/token-generation.ts
|
|
568
|
-
function
|
|
681
|
+
function base64UrlEncode2(str) {
|
|
569
682
|
return btoa(str).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
570
683
|
}
|
|
571
684
|
function base64UrlEncodeBuffer(buffer) {
|
|
@@ -586,8 +699,8 @@ async function createJWT(serviceAccount) {
|
|
|
586
699
|
exp: expiry,
|
|
587
700
|
scope: "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore https://www.googleapis.com/auth/firebase"
|
|
588
701
|
};
|
|
589
|
-
const encodedHeader =
|
|
590
|
-
const encodedPayload =
|
|
702
|
+
const encodedHeader = base64UrlEncode2(JSON.stringify(header));
|
|
703
|
+
const encodedPayload = base64UrlEncode2(JSON.stringify(payload));
|
|
591
704
|
const unsignedToken = `${encodedHeader}.${encodedPayload}`;
|
|
592
705
|
const pemContents = serviceAccount.private_key.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace(/\s/g, "");
|
|
593
706
|
const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
|
|
@@ -969,6 +1082,10 @@ async function batchWrite(operations) {
|
|
|
969
1082
|
var STORAGE_API_BASE = "https://storage.googleapis.com/storage/v1";
|
|
970
1083
|
var UPLOAD_API_BASE = "https://storage.googleapis.com/upload/storage/v1";
|
|
971
1084
|
function getDefaultBucket() {
|
|
1085
|
+
const customBucket = process.env.FIREBASE_STORAGE_BUCKET;
|
|
1086
|
+
if (customBucket) {
|
|
1087
|
+
return customBucket;
|
|
1088
|
+
}
|
|
972
1089
|
const projectId = getProjectId();
|
|
973
1090
|
return `${projectId}.appspot.com`;
|
|
974
1091
|
}
|
|
@@ -1156,6 +1273,14 @@ async function fileExists(path) {
|
|
|
1156
1273
|
}
|
|
1157
1274
|
|
|
1158
1275
|
// src/storage/signed-urls.ts
|
|
1276
|
+
function getStorageBucket() {
|
|
1277
|
+
const customBucket = process.env.FIREBASE_STORAGE_BUCKET;
|
|
1278
|
+
if (customBucket) {
|
|
1279
|
+
return customBucket;
|
|
1280
|
+
}
|
|
1281
|
+
const projectId = getProjectId();
|
|
1282
|
+
return `${projectId}.appspot.com`;
|
|
1283
|
+
}
|
|
1159
1284
|
function getExpirationTimestamp(expires) {
|
|
1160
1285
|
if (expires instanceof Date) {
|
|
1161
1286
|
return Math.floor(expires.getTime() / 1e3);
|
|
@@ -1208,12 +1333,15 @@ async function signData(data, privateKey) {
|
|
|
1208
1333
|
}
|
|
1209
1334
|
async function generateSignedUrl(path, options) {
|
|
1210
1335
|
const serviceAccount = getServiceAccount();
|
|
1211
|
-
const
|
|
1212
|
-
const bucket = `${projectId}.appspot.com`;
|
|
1336
|
+
const bucket = getStorageBucket();
|
|
1213
1337
|
const method = actionToMethod(options.action);
|
|
1214
1338
|
const expiration = getExpirationTimestamp(options.expires);
|
|
1215
|
-
const
|
|
1216
|
-
const
|
|
1339
|
+
const now = /* @__PURE__ */ new Date();
|
|
1340
|
+
const timestamp = Math.floor(now.getTime() / 1e3);
|
|
1341
|
+
const isoString = now.toISOString();
|
|
1342
|
+
const datestamp = isoString.split("T")[0].replace(/-/g, "");
|
|
1343
|
+
const timeString = isoString.split("T")[1].replace(/[:.]/g, "").substring(0, 6);
|
|
1344
|
+
const dateTimeStamp = `${datestamp}T${timeString}Z`;
|
|
1217
1345
|
const credentialScope = `${datestamp}/auto/storage/goog4_request`;
|
|
1218
1346
|
const credential = `${serviceAccount.client_email}/${credentialScope}`;
|
|
1219
1347
|
const canonicalHeaders = `host:storage.googleapis.com
|
|
@@ -1222,7 +1350,7 @@ async function generateSignedUrl(path, options) {
|
|
|
1222
1350
|
const queryParams = {
|
|
1223
1351
|
"X-Goog-Algorithm": "GOOG4-RSA-SHA256",
|
|
1224
1352
|
"X-Goog-Credential": credential,
|
|
1225
|
-
"X-Goog-Date":
|
|
1353
|
+
"X-Goog-Date": dateTimeStamp,
|
|
1226
1354
|
"X-Goog-Expires": (expiration - timestamp).toString(),
|
|
1227
1355
|
"X-Goog-SignedHeaders": signedHeaders
|
|
1228
1356
|
};
|
|
@@ -1237,7 +1365,8 @@ async function generateSignedUrl(path, options) {
|
|
|
1237
1365
|
}
|
|
1238
1366
|
const sortedParams = Object.keys(queryParams).sort();
|
|
1239
1367
|
const canonicalQueryString = sortedParams.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`).join("&");
|
|
1240
|
-
const
|
|
1368
|
+
const encodedPath = path.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
1369
|
+
const canonicalUri = `/${bucket}/${encodedPath}`;
|
|
1241
1370
|
const canonicalRequest = [
|
|
1242
1371
|
method,
|
|
1243
1372
|
canonicalUri,
|
|
@@ -1249,7 +1378,7 @@ async function generateSignedUrl(path, options) {
|
|
|
1249
1378
|
const canonicalRequestHash = stringToHex(canonicalRequest);
|
|
1250
1379
|
const stringToSign = [
|
|
1251
1380
|
"GOOG4-RSA-SHA256",
|
|
1252
|
-
|
|
1381
|
+
dateTimeStamp,
|
|
1253
1382
|
credentialScope,
|
|
1254
1383
|
canonicalRequestHash
|
|
1255
1384
|
].join("\n");
|
|
@@ -1263,6 +1392,7 @@ export {
|
|
|
1263
1392
|
batchWrite,
|
|
1264
1393
|
clearConfig,
|
|
1265
1394
|
clearTokenCache,
|
|
1395
|
+
createCustomToken,
|
|
1266
1396
|
deleteDocument,
|
|
1267
1397
|
deleteFile,
|
|
1268
1398
|
downloadFile,
|
|
@@ -1280,6 +1410,7 @@ export {
|
|
|
1280
1410
|
listFiles,
|
|
1281
1411
|
queryDocuments,
|
|
1282
1412
|
setDocument,
|
|
1413
|
+
signInWithCustomToken,
|
|
1283
1414
|
updateDocument,
|
|
1284
1415
|
uploadFile,
|
|
1285
1416
|
verifyIdToken
|
package/package.json
CHANGED