@prmichaelsen/firebase-admin-sdk-v8 2.0.1 → 2.0.4

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.
Files changed (3) hide show
  1. package/dist/index.js +111 -20
  2. package/dist/index.mjs +111 -20
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -82,21 +82,113 @@ function getProjectId() {
82
82
  return projectId;
83
83
  }
84
84
 
85
+ // src/x509.ts
86
+ function decodeBase64(str) {
87
+ const binary = atob(str);
88
+ const bytes = new Uint8Array(binary.length);
89
+ for (let i = 0; i < binary.length; i++) {
90
+ bytes[i] = binary.charCodeAt(i);
91
+ }
92
+ return bytes;
93
+ }
94
+ function parseElement(bytes) {
95
+ let position = 0;
96
+ let tag = bytes[0] & 31;
97
+ position++;
98
+ if (tag === 31) {
99
+ tag = 0;
100
+ while (bytes[position] >= 128) {
101
+ tag = tag * 128 + bytes[position] - 128;
102
+ position++;
103
+ }
104
+ tag = tag * 128 + bytes[position] - 128;
105
+ position++;
106
+ }
107
+ let length = 0;
108
+ if (bytes[position] < 128) {
109
+ length = bytes[position];
110
+ position++;
111
+ } else if (bytes[position] === 128) {
112
+ length = 0;
113
+ while (bytes[position + length] !== 0 || bytes[position + length + 1] !== 0) {
114
+ if (length > bytes.byteLength) {
115
+ throw new TypeError("Invalid indefinite form length");
116
+ }
117
+ length++;
118
+ }
119
+ const byteLength2 = position + length + 2;
120
+ return {
121
+ byteLength: byteLength2,
122
+ contents: bytes.subarray(position, position + length),
123
+ raw: bytes.subarray(0, byteLength2)
124
+ };
125
+ } else {
126
+ const numberOfDigits = bytes[position] & 127;
127
+ position++;
128
+ length = 0;
129
+ for (let i = 0; i < numberOfDigits; i++) {
130
+ length = length * 256 + bytes[position];
131
+ position++;
132
+ }
133
+ }
134
+ const byteLength = position + length;
135
+ return {
136
+ byteLength,
137
+ contents: bytes.subarray(position, byteLength),
138
+ raw: bytes.subarray(0, byteLength)
139
+ };
140
+ }
141
+ function getElement(seq) {
142
+ const result = [];
143
+ let next = 0;
144
+ while (next < seq.length) {
145
+ const nextPart = parseElement(seq.subarray(next));
146
+ result.push(nextPart);
147
+ next += nextPart.byteLength;
148
+ }
149
+ return result;
150
+ }
151
+ async function spkiFromX509(buf) {
152
+ const tbsCertificate = getElement(
153
+ getElement(parseElement(buf).contents)[0].contents
154
+ );
155
+ const spki = tbsCertificate[tbsCertificate[0].raw[0] === 160 ? 6 : 5].raw;
156
+ return await crypto.subtle.importKey(
157
+ "spki",
158
+ spki,
159
+ {
160
+ name: "RSASSA-PKCS1-v1_5",
161
+ hash: "SHA-256"
162
+ },
163
+ true,
164
+ ["verify"]
165
+ );
166
+ }
167
+ async function importPublicKeyFromX509(pem) {
168
+ const base64 = pem.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g, "");
169
+ const raw = decodeBase64(base64);
170
+ return await spkiFromX509(raw);
171
+ }
172
+
85
173
  // src/auth.ts
86
174
  var publicKeysCache = null;
87
175
  var publicKeysCacheExpiry = 0;
88
- async function fetchPublicKeys() {
176
+ async function fetchPublicKeys(issuer) {
177
+ let endpoint = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
178
+ if (issuer && issuer.includes("session.firebase.google.com")) {
179
+ endpoint = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys";
180
+ }
89
181
  if (publicKeysCache && Date.now() < publicKeysCacheExpiry) {
90
182
  return publicKeysCache;
91
183
  }
92
- const response = await fetch(
93
- "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"
94
- );
184
+ console.log(`[fetchPublicKeys] Fetching from: ${endpoint}`);
185
+ const response = await fetch(endpoint);
95
186
  if (!response.ok) {
96
- throw new Error("Failed to fetch Firebase public keys");
187
+ throw new Error(`Failed to fetch Firebase public keys from ${endpoint}`);
97
188
  }
98
189
  publicKeysCache = await response.json();
99
190
  publicKeysCacheExpiry = Date.now() + 36e5;
191
+ console.log(`[fetchPublicKeys] Fetched ${Object.keys(publicKeysCache || {}).length} keys`);
100
192
  return publicKeysCache;
101
193
  }
102
194
  function base64UrlDecode(str) {
@@ -115,17 +207,6 @@ function parseJWT(token) {
115
207
  const payload = JSON.parse(base64UrlDecode(parts[1]));
116
208
  return { header, payload };
117
209
  }
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
210
  async function verifySignature(token, publicKey) {
130
211
  const parts = token.split(".");
131
212
  const signedData = `${parts[0]}.${parts[1]}`;
@@ -180,12 +261,22 @@ async function verifyIdToken(idToken) {
180
261
  if (payload.sub.length > 128) {
181
262
  throw new Error("Subject too long");
182
263
  }
183
- const publicKeys = await fetchPublicKeys();
184
- const publicKeyPem = publicKeys[header.kid];
264
+ let publicKeys = await fetchPublicKeys(payload.iss);
265
+ let publicKeyPem = publicKeys[header.kid];
185
266
  if (!publicKeyPem) {
186
- throw new Error("Public key not found for kid: " + header.kid);
267
+ console.log(`[verifyIdToken] Key ${header.kid} not found in cache, refreshing keys...`);
268
+ publicKeysCache = null;
269
+ publicKeysCacheExpiry = 0;
270
+ publicKeys = await fetchPublicKeys(payload.iss);
271
+ publicKeyPem = publicKeys[header.kid];
272
+ if (!publicKeyPem) {
273
+ const availableKids = Object.keys(publicKeys).join(", ");
274
+ throw new Error(
275
+ `Public key not found for kid: ${header.kid}. Available kids: ${availableKids}. This might indicate the token is from a different Firebase project or was signed with a very old key.`
276
+ );
277
+ }
187
278
  }
188
- const publicKey = await importPublicKey(publicKeyPem);
279
+ const publicKey = await importPublicKeyFromX509(publicKeyPem);
189
280
  const isValid = await verifySignature(idToken, publicKey);
190
281
  if (!isValid) {
191
282
  throw new Error("Invalid token signature");
package/dist/index.mjs CHANGED
@@ -42,21 +42,113 @@ function getProjectId() {
42
42
  return projectId;
43
43
  }
44
44
 
45
+ // src/x509.ts
46
+ function decodeBase64(str) {
47
+ const binary = atob(str);
48
+ const bytes = new Uint8Array(binary.length);
49
+ for (let i = 0; i < binary.length; i++) {
50
+ bytes[i] = binary.charCodeAt(i);
51
+ }
52
+ return bytes;
53
+ }
54
+ function parseElement(bytes) {
55
+ let position = 0;
56
+ let tag = bytes[0] & 31;
57
+ position++;
58
+ if (tag === 31) {
59
+ tag = 0;
60
+ while (bytes[position] >= 128) {
61
+ tag = tag * 128 + bytes[position] - 128;
62
+ position++;
63
+ }
64
+ tag = tag * 128 + bytes[position] - 128;
65
+ position++;
66
+ }
67
+ let length = 0;
68
+ if (bytes[position] < 128) {
69
+ length = bytes[position];
70
+ position++;
71
+ } else if (bytes[position] === 128) {
72
+ length = 0;
73
+ while (bytes[position + length] !== 0 || bytes[position + length + 1] !== 0) {
74
+ if (length > bytes.byteLength) {
75
+ throw new TypeError("Invalid indefinite form length");
76
+ }
77
+ length++;
78
+ }
79
+ const byteLength2 = position + length + 2;
80
+ return {
81
+ byteLength: byteLength2,
82
+ contents: bytes.subarray(position, position + length),
83
+ raw: bytes.subarray(0, byteLength2)
84
+ };
85
+ } else {
86
+ const numberOfDigits = bytes[position] & 127;
87
+ position++;
88
+ length = 0;
89
+ for (let i = 0; i < numberOfDigits; i++) {
90
+ length = length * 256 + bytes[position];
91
+ position++;
92
+ }
93
+ }
94
+ const byteLength = position + length;
95
+ return {
96
+ byteLength,
97
+ contents: bytes.subarray(position, byteLength),
98
+ raw: bytes.subarray(0, byteLength)
99
+ };
100
+ }
101
+ function getElement(seq) {
102
+ const result = [];
103
+ let next = 0;
104
+ while (next < seq.length) {
105
+ const nextPart = parseElement(seq.subarray(next));
106
+ result.push(nextPart);
107
+ next += nextPart.byteLength;
108
+ }
109
+ return result;
110
+ }
111
+ async function spkiFromX509(buf) {
112
+ const tbsCertificate = getElement(
113
+ getElement(parseElement(buf).contents)[0].contents
114
+ );
115
+ const spki = tbsCertificate[tbsCertificate[0].raw[0] === 160 ? 6 : 5].raw;
116
+ return await crypto.subtle.importKey(
117
+ "spki",
118
+ spki,
119
+ {
120
+ name: "RSASSA-PKCS1-v1_5",
121
+ hash: "SHA-256"
122
+ },
123
+ true,
124
+ ["verify"]
125
+ );
126
+ }
127
+ async function importPublicKeyFromX509(pem) {
128
+ const base64 = pem.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g, "");
129
+ const raw = decodeBase64(base64);
130
+ return await spkiFromX509(raw);
131
+ }
132
+
45
133
  // src/auth.ts
46
134
  var publicKeysCache = null;
47
135
  var publicKeysCacheExpiry = 0;
48
- async function fetchPublicKeys() {
136
+ async function fetchPublicKeys(issuer) {
137
+ let endpoint = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
138
+ if (issuer && issuer.includes("session.firebase.google.com")) {
139
+ endpoint = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys";
140
+ }
49
141
  if (publicKeysCache && Date.now() < publicKeysCacheExpiry) {
50
142
  return publicKeysCache;
51
143
  }
52
- const response = await fetch(
53
- "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"
54
- );
144
+ console.log(`[fetchPublicKeys] Fetching from: ${endpoint}`);
145
+ const response = await fetch(endpoint);
55
146
  if (!response.ok) {
56
- throw new Error("Failed to fetch Firebase public keys");
147
+ throw new Error(`Failed to fetch Firebase public keys from ${endpoint}`);
57
148
  }
58
149
  publicKeysCache = await response.json();
59
150
  publicKeysCacheExpiry = Date.now() + 36e5;
151
+ console.log(`[fetchPublicKeys] Fetched ${Object.keys(publicKeysCache || {}).length} keys`);
60
152
  return publicKeysCache;
61
153
  }
62
154
  function base64UrlDecode(str) {
@@ -75,17 +167,6 @@ function parseJWT(token) {
75
167
  const payload = JSON.parse(base64UrlDecode(parts[1]));
76
168
  return { header, payload };
77
169
  }
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
170
  async function verifySignature(token, publicKey) {
90
171
  const parts = token.split(".");
91
172
  const signedData = `${parts[0]}.${parts[1]}`;
@@ -140,12 +221,22 @@ async function verifyIdToken(idToken) {
140
221
  if (payload.sub.length > 128) {
141
222
  throw new Error("Subject too long");
142
223
  }
143
- const publicKeys = await fetchPublicKeys();
144
- const publicKeyPem = publicKeys[header.kid];
224
+ let publicKeys = await fetchPublicKeys(payload.iss);
225
+ let publicKeyPem = publicKeys[header.kid];
145
226
  if (!publicKeyPem) {
146
- throw new Error("Public key not found for kid: " + header.kid);
227
+ console.log(`[verifyIdToken] Key ${header.kid} not found in cache, refreshing keys...`);
228
+ publicKeysCache = null;
229
+ publicKeysCacheExpiry = 0;
230
+ publicKeys = await fetchPublicKeys(payload.iss);
231
+ publicKeyPem = publicKeys[header.kid];
232
+ if (!publicKeyPem) {
233
+ const availableKids = Object.keys(publicKeys).join(", ");
234
+ throw new Error(
235
+ `Public key not found for kid: ${header.kid}. Available kids: ${availableKids}. This might indicate the token is from a different Firebase project or was signed with a very old key.`
236
+ );
237
+ }
147
238
  }
148
- const publicKey = await importPublicKey(publicKeyPem);
239
+ const publicKey = await importPublicKeyFromX509(publicKeyPem);
149
240
  const isValid = await verifySignature(idToken, publicKey);
150
241
  if (!isValid) {
151
242
  throw new Error("Invalid token signature");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/firebase-admin-sdk-v8",
3
- "version": "2.0.1",
3
+ "version": "2.0.4",
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",