@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.
- package/dist/index.js +111 -20
- package/dist/index.mjs +111 -20
- 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
|
-
|
|
93
|
-
|
|
94
|
-
);
|
|
184
|
+
console.log(`[fetchPublicKeys] Fetching from: ${endpoint}`);
|
|
185
|
+
const response = await fetch(endpoint);
|
|
95
186
|
if (!response.ok) {
|
|
96
|
-
throw new Error(
|
|
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
|
-
|
|
184
|
-
|
|
264
|
+
let publicKeys = await fetchPublicKeys(payload.iss);
|
|
265
|
+
let publicKeyPem = publicKeys[header.kid];
|
|
185
266
|
if (!publicKeyPem) {
|
|
186
|
-
|
|
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
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
);
|
|
144
|
+
console.log(`[fetchPublicKeys] Fetching from: ${endpoint}`);
|
|
145
|
+
const response = await fetch(endpoint);
|
|
55
146
|
if (!response.ok) {
|
|
56
|
-
throw new Error(
|
|
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
|
-
|
|
144
|
-
|
|
224
|
+
let publicKeys = await fetchPublicKeys(payload.iss);
|
|
225
|
+
let publicKeyPem = publicKeys[header.kid];
|
|
145
226
|
if (!publicKeyPem) {
|
|
146
|
-
|
|
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
|
|
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