@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 CHANGED
@@ -9,9 +9,10 @@ This library provides Firebase Admin SDK functionality for Cloudflare Workers an
9
9
 
10
10
  ## ✨ Features
11
11
 
12
- - ✅ **No Node.js Dependencies** - Pure Web APIs (crypto.subtle, fetch)
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 using firebase-auth-cloudflare-workers
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 using firebase-auth-cloudflare-workers
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 MemoryKeyStore = class {
90
- constructor() {
91
- this.cache = null;
92
- }
93
- async get() {
94
- if (!this.cache) {
95
- return null;
96
- }
97
- try {
98
- return JSON.parse(this.cache);
99
- } catch {
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
- async put(value, _expirationTtl) {
104
- this.cache = value;
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
- var keyStore = new MemoryKeyStore();
108
- function getAuth() {
109
- const projectId = getProjectId();
110
- return import_firebase_auth_cloudflare_workers.Auth.getOrInitialize(projectId, keyStore);
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 decodedToken = await auth.verifyIdToken(idToken);
119
- return decodedToken;
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(`Failed to verify ID token: ${error instanceof Error ? error.message : String(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 MemoryKeyStore = class {
50
- constructor() {
51
- this.cache = null;
52
- }
53
- async get() {
54
- if (!this.cache) {
55
- return null;
56
- }
57
- try {
58
- return JSON.parse(this.cache);
59
- } catch {
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
- async put(value, _expirationTtl) {
64
- this.cache = value;
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
- var keyStore = new MemoryKeyStore();
68
- function getAuth() {
69
- const projectId = getProjectId();
70
- return Auth.getOrInitialize(projectId, keyStore);
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 decodedToken = await auth.verifyIdToken(idToken);
79
- return decodedToken;
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(`Failed to verify ID token: ${error instanceof Error ? error.message : String(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": "1.1.2",
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",