@prmichaelsen/firebase-admin-sdk-v8 1.1.2 → 2.0.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 +3 -2
- package/dist/index.d.mts +7 -9
- package/dist/index.d.ts +7 -9
- package/dist/index.js +111 -27
- package/dist/index.mjs +111 -27
- package/package.json +2 -4
package/README.md
CHANGED
|
@@ -9,9 +9,10 @@ This library provides Firebase Admin SDK functionality for Cloudflare Workers an
|
|
|
9
9
|
|
|
10
10
|
## ✨ Features
|
|
11
11
|
|
|
12
|
-
- ✅ **
|
|
12
|
+
- ✅ **Zero Dependencies** - No external dependencies, pure Web APIs (crypto.subtle, fetch)
|
|
13
13
|
- ✅ **JWT Token Generation** - Service account authentication
|
|
14
|
-
- ✅ **ID Token Verification** - Verify Firebase ID tokens
|
|
14
|
+
- ✅ **ID Token Verification** - Verify Firebase ID tokens (supports v9 and v10 formats)
|
|
15
|
+
- ✅ **Firebase v10 Compatible** - Supports both old and new token issuer formats
|
|
15
16
|
- ✅ **Firestore REST API** - Full CRUD operations via REST
|
|
16
17
|
- ✅ **Field Value Operations** - increment, arrayUnion, arrayRemove, serverTimestamp, delete
|
|
17
18
|
- ✅ **Advanced Queries** - where, orderBy, limit, offset, cursor pagination
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { Auth } from 'firebase-auth-cloudflare-workers';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Firebase Admin SDK v8 - Type Definitions
|
|
5
3
|
*/
|
|
@@ -179,17 +177,12 @@ interface BatchWriteResult {
|
|
|
179
177
|
|
|
180
178
|
/**
|
|
181
179
|
* Firebase Admin SDK v8 - Authentication
|
|
182
|
-
* ID token verification
|
|
180
|
+
* ID token verification supporting both Firebase v9 and v10 token formats
|
|
183
181
|
*/
|
|
184
182
|
|
|
185
|
-
/**
|
|
186
|
-
* Get or initialize Firebase Auth instance
|
|
187
|
-
* @returns {Auth} Firebase Auth instance
|
|
188
|
-
* @throws {Error} If PUBLIC_FIREBASE_PROJECT_ID is not set
|
|
189
|
-
*/
|
|
190
|
-
declare function getAuth(): Auth;
|
|
191
183
|
/**
|
|
192
184
|
* Verify a Firebase ID token
|
|
185
|
+
* Supports both Firebase v9 (securetoken) and v10 (session) token formats
|
|
193
186
|
*
|
|
194
187
|
* @param {string} idToken - Firebase ID token to verify
|
|
195
188
|
* @returns {Promise<DecodedIdToken>} Decoded and verified token
|
|
@@ -217,6 +210,11 @@ declare function verifyIdToken(idToken: string): Promise<DecodedIdToken>;
|
|
|
217
210
|
* ```
|
|
218
211
|
*/
|
|
219
212
|
declare function getUserFromToken(idToken: string): Promise<UserInfo>;
|
|
213
|
+
/**
|
|
214
|
+
* Get Auth instance (for compatibility, but not used in new implementation)
|
|
215
|
+
* @deprecated Use verifyIdToken directly
|
|
216
|
+
*/
|
|
217
|
+
declare function getAuth(): any;
|
|
220
218
|
|
|
221
219
|
/**
|
|
222
220
|
* Firebase Admin SDK v8 - Firestore REST API
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { Auth } from 'firebase-auth-cloudflare-workers';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Firebase Admin SDK v8 - Type Definitions
|
|
5
3
|
*/
|
|
@@ -179,17 +177,12 @@ interface BatchWriteResult {
|
|
|
179
177
|
|
|
180
178
|
/**
|
|
181
179
|
* Firebase Admin SDK v8 - Authentication
|
|
182
|
-
* ID token verification
|
|
180
|
+
* ID token verification supporting both Firebase v9 and v10 token formats
|
|
183
181
|
*/
|
|
184
182
|
|
|
185
|
-
/**
|
|
186
|
-
* Get or initialize Firebase Auth instance
|
|
187
|
-
* @returns {Auth} Firebase Auth instance
|
|
188
|
-
* @throws {Error} If PUBLIC_FIREBASE_PROJECT_ID is not set
|
|
189
|
-
*/
|
|
190
|
-
declare function getAuth(): Auth;
|
|
191
183
|
/**
|
|
192
184
|
* Verify a Firebase ID token
|
|
185
|
+
* Supports both Firebase v9 (securetoken) and v10 (session) token formats
|
|
193
186
|
*
|
|
194
187
|
* @param {string} idToken - Firebase ID token to verify
|
|
195
188
|
* @returns {Promise<DecodedIdToken>} Decoded and verified token
|
|
@@ -217,6 +210,11 @@ declare function verifyIdToken(idToken: string): Promise<DecodedIdToken>;
|
|
|
217
210
|
* ```
|
|
218
211
|
*/
|
|
219
212
|
declare function getUserFromToken(idToken: string): Promise<UserInfo>;
|
|
213
|
+
/**
|
|
214
|
+
* Get Auth instance (for compatibility, but not used in new implementation)
|
|
215
|
+
* @deprecated Use verifyIdToken directly
|
|
216
|
+
*/
|
|
217
|
+
declare function getAuth(): any;
|
|
220
218
|
|
|
221
219
|
/**
|
|
222
220
|
* Firebase Admin SDK v8 - Firestore REST API
|
package/dist/index.js
CHANGED
|
@@ -38,9 +38,6 @@ __export(index_exports, {
|
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(index_exports);
|
|
40
40
|
|
|
41
|
-
// src/auth.ts
|
|
42
|
-
var import_firebase_auth_cloudflare_workers = require("firebase-auth-cloudflare-workers");
|
|
43
|
-
|
|
44
41
|
// src/service-account.ts
|
|
45
42
|
function getServiceAccount() {
|
|
46
43
|
const key = process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY;
|
|
@@ -86,39 +83,121 @@ function getProjectId() {
|
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
// src/auth.ts
|
|
89
|
-
var
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
86
|
+
var publicKeysCache = null;
|
|
87
|
+
var publicKeysCacheExpiry = 0;
|
|
88
|
+
async function fetchPublicKeys() {
|
|
89
|
+
if (publicKeysCache && Date.now() < publicKeysCacheExpiry) {
|
|
90
|
+
return publicKeysCache;
|
|
91
|
+
}
|
|
92
|
+
const response = await fetch(
|
|
93
|
+
"https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"
|
|
94
|
+
);
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new Error("Failed to fetch Firebase public keys");
|
|
102
97
|
}
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
publicKeysCache = await response.json();
|
|
99
|
+
publicKeysCacheExpiry = Date.now() + 36e5;
|
|
100
|
+
return publicKeysCache;
|
|
101
|
+
}
|
|
102
|
+
function base64UrlDecode(str) {
|
|
103
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
104
|
+
while (base64.length % 4) {
|
|
105
|
+
base64 += "=";
|
|
105
106
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
function
|
|
109
|
-
const
|
|
110
|
-
|
|
107
|
+
return atob(base64);
|
|
108
|
+
}
|
|
109
|
+
function parseJWT(token) {
|
|
110
|
+
const parts = token.split(".");
|
|
111
|
+
if (parts.length !== 3) {
|
|
112
|
+
throw new Error("Invalid JWT format");
|
|
113
|
+
}
|
|
114
|
+
const header = JSON.parse(base64UrlDecode(parts[0]));
|
|
115
|
+
const payload = JSON.parse(base64UrlDecode(parts[1]));
|
|
116
|
+
return { header, payload };
|
|
117
|
+
}
|
|
118
|
+
async function importPublicKey(pem) {
|
|
119
|
+
const pemContents = pem.replace("-----BEGIN CERTIFICATE-----", "").replace("-----END CERTIFICATE-----", "").replace(/\s/g, "");
|
|
120
|
+
const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
|
|
121
|
+
return await crypto.subtle.importKey(
|
|
122
|
+
"spki",
|
|
123
|
+
binaryDer,
|
|
124
|
+
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
125
|
+
false,
|
|
126
|
+
["verify"]
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
async function verifySignature(token, publicKey) {
|
|
130
|
+
const parts = token.split(".");
|
|
131
|
+
const signedData = `${parts[0]}.${parts[1]}`;
|
|
132
|
+
const signature = Uint8Array.from(
|
|
133
|
+
base64UrlDecode(parts[2]),
|
|
134
|
+
(c) => c.charCodeAt(0)
|
|
135
|
+
);
|
|
136
|
+
return await crypto.subtle.verify(
|
|
137
|
+
"RSASSA-PKCS1-v1_5",
|
|
138
|
+
publicKey,
|
|
139
|
+
signature,
|
|
140
|
+
new TextEncoder().encode(signedData)
|
|
141
|
+
);
|
|
111
142
|
}
|
|
112
143
|
async function verifyIdToken(idToken) {
|
|
113
144
|
if (!idToken) {
|
|
114
145
|
throw new Error("ID token is required");
|
|
115
146
|
}
|
|
116
|
-
const auth = getAuth();
|
|
117
147
|
try {
|
|
118
|
-
const
|
|
119
|
-
|
|
148
|
+
const { header, payload } = parseJWT(idToken);
|
|
149
|
+
const projectId = getProjectId();
|
|
150
|
+
if (header.alg !== "RS256") {
|
|
151
|
+
throw new Error("Invalid algorithm. Expected RS256");
|
|
152
|
+
}
|
|
153
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
154
|
+
if (!payload.exp || payload.exp < now) {
|
|
155
|
+
throw new Error("Token has expired");
|
|
156
|
+
}
|
|
157
|
+
if (!payload.iat || payload.iat > now) {
|
|
158
|
+
throw new Error("Token issued in the future");
|
|
159
|
+
}
|
|
160
|
+
if (!payload.auth_time || payload.auth_time > now) {
|
|
161
|
+
throw new Error("Auth time is in the future");
|
|
162
|
+
}
|
|
163
|
+
if (payload.aud !== projectId) {
|
|
164
|
+
throw new Error(`Invalid audience. Expected ${projectId}, got ${payload.aud}`);
|
|
165
|
+
}
|
|
166
|
+
const validIssuers = [
|
|
167
|
+
`https://securetoken.google.com/${projectId}`,
|
|
168
|
+
// Firebase v9 and earlier
|
|
169
|
+
`https://session.firebase.google.com/${projectId}`
|
|
170
|
+
// Firebase v10+
|
|
171
|
+
];
|
|
172
|
+
if (!validIssuers.includes(payload.iss)) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Invalid issuer. Expected one of: ${validIssuers.join(", ")}, got ${payload.iss}`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (!payload.sub || typeof payload.sub !== "string" || payload.sub.length === 0) {
|
|
178
|
+
throw new Error("Invalid subject");
|
|
179
|
+
}
|
|
180
|
+
if (payload.sub.length > 128) {
|
|
181
|
+
throw new Error("Subject too long");
|
|
182
|
+
}
|
|
183
|
+
const publicKeys = await fetchPublicKeys();
|
|
184
|
+
const publicKeyPem = publicKeys[header.kid];
|
|
185
|
+
if (!publicKeyPem) {
|
|
186
|
+
throw new Error("Public key not found for kid: " + header.kid);
|
|
187
|
+
}
|
|
188
|
+
const publicKey = await importPublicKey(publicKeyPem);
|
|
189
|
+
const isValid = await verifySignature(idToken, publicKey);
|
|
190
|
+
if (!isValid) {
|
|
191
|
+
throw new Error("Invalid token signature");
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
...payload,
|
|
195
|
+
uid: payload.sub
|
|
196
|
+
};
|
|
120
197
|
} catch (error) {
|
|
121
|
-
throw new Error(
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Failed to verify ID token: ${error instanceof Error ? error.message : String(error)}`
|
|
200
|
+
);
|
|
122
201
|
}
|
|
123
202
|
}
|
|
124
203
|
async function getUserFromToken(idToken) {
|
|
@@ -131,6 +210,11 @@ async function getUserFromToken(idToken) {
|
|
|
131
210
|
photoURL: decodedToken.picture || null
|
|
132
211
|
};
|
|
133
212
|
}
|
|
213
|
+
function getAuth() {
|
|
214
|
+
return {
|
|
215
|
+
verifyIdToken
|
|
216
|
+
};
|
|
217
|
+
}
|
|
134
218
|
|
|
135
219
|
// src/token-generation.ts
|
|
136
220
|
function base64UrlEncode(str) {
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// src/auth.ts
|
|
2
|
-
import { Auth } from "firebase-auth-cloudflare-workers";
|
|
3
|
-
|
|
4
1
|
// src/service-account.ts
|
|
5
2
|
function getServiceAccount() {
|
|
6
3
|
const key = process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY;
|
|
@@ -46,39 +43,121 @@ function getProjectId() {
|
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
// src/auth.ts
|
|
49
|
-
var
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
46
|
+
var publicKeysCache = null;
|
|
47
|
+
var publicKeysCacheExpiry = 0;
|
|
48
|
+
async function fetchPublicKeys() {
|
|
49
|
+
if (publicKeysCache && Date.now() < publicKeysCacheExpiry) {
|
|
50
|
+
return publicKeysCache;
|
|
51
|
+
}
|
|
52
|
+
const response = await fetch(
|
|
53
|
+
"https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"
|
|
54
|
+
);
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new Error("Failed to fetch Firebase public keys");
|
|
62
57
|
}
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
publicKeysCache = await response.json();
|
|
59
|
+
publicKeysCacheExpiry = Date.now() + 36e5;
|
|
60
|
+
return publicKeysCache;
|
|
61
|
+
}
|
|
62
|
+
function base64UrlDecode(str) {
|
|
63
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
64
|
+
while (base64.length % 4) {
|
|
65
|
+
base64 += "=";
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
function
|
|
69
|
-
const
|
|
70
|
-
|
|
67
|
+
return atob(base64);
|
|
68
|
+
}
|
|
69
|
+
function parseJWT(token) {
|
|
70
|
+
const parts = token.split(".");
|
|
71
|
+
if (parts.length !== 3) {
|
|
72
|
+
throw new Error("Invalid JWT format");
|
|
73
|
+
}
|
|
74
|
+
const header = JSON.parse(base64UrlDecode(parts[0]));
|
|
75
|
+
const payload = JSON.parse(base64UrlDecode(parts[1]));
|
|
76
|
+
return { header, payload };
|
|
77
|
+
}
|
|
78
|
+
async function importPublicKey(pem) {
|
|
79
|
+
const pemContents = pem.replace("-----BEGIN CERTIFICATE-----", "").replace("-----END CERTIFICATE-----", "").replace(/\s/g, "");
|
|
80
|
+
const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
|
|
81
|
+
return await crypto.subtle.importKey(
|
|
82
|
+
"spki",
|
|
83
|
+
binaryDer,
|
|
84
|
+
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
85
|
+
false,
|
|
86
|
+
["verify"]
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
async function verifySignature(token, publicKey) {
|
|
90
|
+
const parts = token.split(".");
|
|
91
|
+
const signedData = `${parts[0]}.${parts[1]}`;
|
|
92
|
+
const signature = Uint8Array.from(
|
|
93
|
+
base64UrlDecode(parts[2]),
|
|
94
|
+
(c) => c.charCodeAt(0)
|
|
95
|
+
);
|
|
96
|
+
return await crypto.subtle.verify(
|
|
97
|
+
"RSASSA-PKCS1-v1_5",
|
|
98
|
+
publicKey,
|
|
99
|
+
signature,
|
|
100
|
+
new TextEncoder().encode(signedData)
|
|
101
|
+
);
|
|
71
102
|
}
|
|
72
103
|
async function verifyIdToken(idToken) {
|
|
73
104
|
if (!idToken) {
|
|
74
105
|
throw new Error("ID token is required");
|
|
75
106
|
}
|
|
76
|
-
const auth = getAuth();
|
|
77
107
|
try {
|
|
78
|
-
const
|
|
79
|
-
|
|
108
|
+
const { header, payload } = parseJWT(idToken);
|
|
109
|
+
const projectId = getProjectId();
|
|
110
|
+
if (header.alg !== "RS256") {
|
|
111
|
+
throw new Error("Invalid algorithm. Expected RS256");
|
|
112
|
+
}
|
|
113
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
114
|
+
if (!payload.exp || payload.exp < now) {
|
|
115
|
+
throw new Error("Token has expired");
|
|
116
|
+
}
|
|
117
|
+
if (!payload.iat || payload.iat > now) {
|
|
118
|
+
throw new Error("Token issued in the future");
|
|
119
|
+
}
|
|
120
|
+
if (!payload.auth_time || payload.auth_time > now) {
|
|
121
|
+
throw new Error("Auth time is in the future");
|
|
122
|
+
}
|
|
123
|
+
if (payload.aud !== projectId) {
|
|
124
|
+
throw new Error(`Invalid audience. Expected ${projectId}, got ${payload.aud}`);
|
|
125
|
+
}
|
|
126
|
+
const validIssuers = [
|
|
127
|
+
`https://securetoken.google.com/${projectId}`,
|
|
128
|
+
// Firebase v9 and earlier
|
|
129
|
+
`https://session.firebase.google.com/${projectId}`
|
|
130
|
+
// Firebase v10+
|
|
131
|
+
];
|
|
132
|
+
if (!validIssuers.includes(payload.iss)) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Invalid issuer. Expected one of: ${validIssuers.join(", ")}, got ${payload.iss}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (!payload.sub || typeof payload.sub !== "string" || payload.sub.length === 0) {
|
|
138
|
+
throw new Error("Invalid subject");
|
|
139
|
+
}
|
|
140
|
+
if (payload.sub.length > 128) {
|
|
141
|
+
throw new Error("Subject too long");
|
|
142
|
+
}
|
|
143
|
+
const publicKeys = await fetchPublicKeys();
|
|
144
|
+
const publicKeyPem = publicKeys[header.kid];
|
|
145
|
+
if (!publicKeyPem) {
|
|
146
|
+
throw new Error("Public key not found for kid: " + header.kid);
|
|
147
|
+
}
|
|
148
|
+
const publicKey = await importPublicKey(publicKeyPem);
|
|
149
|
+
const isValid = await verifySignature(idToken, publicKey);
|
|
150
|
+
if (!isValid) {
|
|
151
|
+
throw new Error("Invalid token signature");
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
...payload,
|
|
155
|
+
uid: payload.sub
|
|
156
|
+
};
|
|
80
157
|
} catch (error) {
|
|
81
|
-
throw new Error(
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Failed to verify ID token: ${error instanceof Error ? error.message : String(error)}`
|
|
160
|
+
);
|
|
82
161
|
}
|
|
83
162
|
}
|
|
84
163
|
async function getUserFromToken(idToken) {
|
|
@@ -91,6 +170,11 @@ async function getUserFromToken(idToken) {
|
|
|
91
170
|
photoURL: decodedToken.picture || null
|
|
92
171
|
};
|
|
93
172
|
}
|
|
173
|
+
function getAuth() {
|
|
174
|
+
return {
|
|
175
|
+
verifyIdToken
|
|
176
|
+
};
|
|
177
|
+
}
|
|
94
178
|
|
|
95
179
|
// src/token-generation.ts
|
|
96
180
|
function base64UrlEncode(str) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prmichaelsen/firebase-admin-sdk-v8",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Firebase Admin SDK for Cloudflare Workers and edge runtimes using REST APIs",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -37,9 +37,7 @@
|
|
|
37
37
|
],
|
|
38
38
|
"author": "",
|
|
39
39
|
"license": "MIT",
|
|
40
|
-
"dependencies": {
|
|
41
|
-
"firebase-auth-cloudflare-workers": "^2.0.6"
|
|
42
|
-
},
|
|
40
|
+
"dependencies": {},
|
|
43
41
|
"devDependencies": {
|
|
44
42
|
"@types/node": "^20.11.0",
|
|
45
43
|
"tsup": "^8.0.1",
|