@tern-secure/backend 1.2.0-canary.v20251127221555 → 1.2.0-canary.v20251202162458
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/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/types.d.ts +42 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/admin/index.d.ts +1 -1
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +8 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +10 -70
- package/dist/admin/index.mjs.map +1 -1
- package/dist/app-check/AppCheckApi.d.ts +14 -0
- package/dist/app-check/AppCheckApi.d.ts.map +1 -0
- package/dist/app-check/generator.d.ts +9 -0
- package/dist/app-check/generator.d.ts.map +1 -0
- package/dist/app-check/index.d.ts +18 -0
- package/dist/app-check/index.d.ts.map +1 -0
- package/dist/app-check/index.js +1052 -0
- package/dist/app-check/index.js.map +1 -0
- package/dist/app-check/index.mjs +13 -0
- package/dist/app-check/index.mjs.map +1 -0
- package/dist/app-check/serverAppCheck.d.ts +33 -0
- package/dist/app-check/serverAppCheck.d.ts.map +1 -0
- package/dist/app-check/types.d.ts +21 -0
- package/dist/app-check/types.d.ts.map +1 -0
- package/dist/app-check/verifier.d.ts +16 -0
- package/dist/app-check/verifier.d.ts.map +1 -0
- package/dist/auth/credential.d.ts +5 -5
- package/dist/auth/credential.d.ts.map +1 -1
- package/dist/auth/getauth.d.ts +2 -1
- package/dist/auth/getauth.d.ts.map +1 -1
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +819 -394
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/index.mjs +5 -3
- package/dist/chunk-3OGMNIOJ.mjs +174 -0
- package/dist/chunk-3OGMNIOJ.mjs.map +1 -0
- package/dist/{chunk-GFH5CXQR.mjs → chunk-AW5OXT7N.mjs} +2 -2
- package/dist/chunk-IEJQ7F4A.mjs +778 -0
- package/dist/chunk-IEJQ7F4A.mjs.map +1 -0
- package/dist/{chunk-NXYWC6YO.mjs → chunk-TUYCJY35.mjs} +182 -6
- package/dist/chunk-TUYCJY35.mjs.map +1 -0
- package/dist/constants.d.ts +10 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/fireRestApi/endpoints/AppCheckApi.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1570 -1183
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +97 -135
- package/dist/index.mjs.map +1 -1
- package/dist/jwt/crypto-signer.d.ts +21 -0
- package/dist/jwt/crypto-signer.d.ts.map +1 -0
- package/dist/jwt/index.d.ts +2 -1
- package/dist/jwt/index.d.ts.map +1 -1
- package/dist/jwt/index.js +119 -2
- package/dist/jwt/index.js.map +1 -1
- package/dist/jwt/index.mjs +7 -3
- package/dist/jwt/signJwt.d.ts +8 -2
- package/dist/jwt/signJwt.d.ts.map +1 -1
- package/dist/jwt/types.d.ts +6 -0
- package/dist/jwt/types.d.ts.map +1 -1
- package/dist/jwt/verifyJwt.d.ts +7 -1
- package/dist/jwt/verifyJwt.d.ts.map +1 -1
- package/dist/tokens/authstate.d.ts +2 -0
- package/dist/tokens/authstate.d.ts.map +1 -1
- package/dist/tokens/c-authenticateRequestProcessor.d.ts +2 -2
- package/dist/tokens/c-authenticateRequestProcessor.d.ts.map +1 -1
- package/dist/tokens/keys.d.ts.map +1 -1
- package/dist/tokens/request.d.ts.map +1 -1
- package/dist/tokens/types.d.ts +6 -4
- package/dist/tokens/types.d.ts.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/{auth/utils.d.ts → utils/fetcher.d.ts} +2 -1
- package/dist/utils/fetcher.d.ts.map +1 -0
- package/dist/utils/mapDecode.d.ts +2 -1
- package/dist/utils/mapDecode.d.ts.map +1 -1
- package/dist/utils/token-generator.d.ts +4 -0
- package/dist/utils/token-generator.d.ts.map +1 -0
- package/package.json +13 -3
- package/dist/auth/constants.d.ts +0 -6
- package/dist/auth/constants.d.ts.map +0 -1
- package/dist/auth/utils.d.ts.map +0 -1
- package/dist/chunk-DJLDUW7J.mjs +0 -414
- package/dist/chunk-DJLDUW7J.mjs.map +0 -1
- package/dist/chunk-NXYWC6YO.mjs.map +0 -1
- package/dist/chunk-WIVOBOZR.mjs +0 -86
- package/dist/chunk-WIVOBOZR.mjs.map +0 -1
- package/dist/utils/gemini_admin-init.d.ts +0 -10
- package/dist/utils/gemini_admin-init.d.ts.map +0 -1
- /package/dist/{chunk-GFH5CXQR.mjs.map → chunk-AW5OXT7N.mjs.map} +0 -0
package/dist/auth/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,112 +17,37 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/auth/index.ts
|
|
21
31
|
var auth_exports = {};
|
|
22
32
|
__export(auth_exports, {
|
|
33
|
+
ServiceAccountManager: () => ServiceAccountManager,
|
|
23
34
|
getAuth: () => getAuth
|
|
24
35
|
});
|
|
25
36
|
module.exports = __toCommonJS(auth_exports);
|
|
26
37
|
|
|
27
|
-
// src/jwt/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this.name = "CustomTokenError";
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
var RESERVED_CLAIMS = [
|
|
37
|
-
"acr",
|
|
38
|
-
"amr",
|
|
39
|
-
"at_hash",
|
|
40
|
-
"aud",
|
|
41
|
-
"auth_time",
|
|
42
|
-
"azp",
|
|
43
|
-
"cnf",
|
|
44
|
-
"c_hash",
|
|
45
|
-
"exp",
|
|
46
|
-
"firebase",
|
|
47
|
-
"iat",
|
|
48
|
-
"iss",
|
|
49
|
-
"jti",
|
|
50
|
-
"nbf",
|
|
51
|
-
"nonce",
|
|
52
|
-
"sub"
|
|
53
|
-
];
|
|
54
|
-
async function createCustomTokenJwt(uid, developerClaims) {
|
|
55
|
-
try {
|
|
56
|
-
const privateKey = process.env.FIREBASE_PRIVATE_KEY;
|
|
57
|
-
const clientEmail = process.env.FIREBASE_CLIENT_EMAIL;
|
|
58
|
-
if (!privateKey || !clientEmail) {
|
|
59
|
-
return {
|
|
60
|
-
errors: [
|
|
61
|
-
new CustomTokenError(
|
|
62
|
-
"Missing FIREBASE_PRIVATE_KEY or FIREBASE_CLIENT_EMAIL environment variables",
|
|
63
|
-
"MISSING_ENV_VARS"
|
|
64
|
-
)
|
|
65
|
-
]
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
if (!uid || typeof uid !== "string") {
|
|
69
|
-
return {
|
|
70
|
-
errors: [new CustomTokenError("uid must be a non-empty string", "INVALID_UID")]
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
if (uid.length > 128) {
|
|
74
|
-
return {
|
|
75
|
-
errors: [new CustomTokenError("uid must not exceed 128 characters", "UID_TOO_LONG")]
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
if (developerClaims) {
|
|
79
|
-
for (const claim of Object.keys(developerClaims)) {
|
|
80
|
-
if (RESERVED_CLAIMS.includes(claim)) {
|
|
81
|
-
return {
|
|
82
|
-
errors: [new CustomTokenError(`Custom claim '${claim}' is reserved`, "RESERVED_CLAIM")]
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
}
|
|
38
|
+
// src/jwt/guardReturn.ts
|
|
39
|
+
function createJwtGuard(decodedFn) {
|
|
40
|
+
return (...args) => {
|
|
41
|
+
const { data, errors } = decodedFn(...args);
|
|
42
|
+
if (errors) {
|
|
43
|
+
throw errors[0];
|
|
86
44
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const parsedPrivateKey = await (0, import_jose.importPKCS8)(privateKey.replace(/\\n/g, "\n"), "RS256");
|
|
90
|
-
const payload = {
|
|
91
|
-
iss: clientEmail,
|
|
92
|
-
sub: clientEmail,
|
|
93
|
-
aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
|
|
94
|
-
iat: now,
|
|
95
|
-
exp: now + expiresIn,
|
|
96
|
-
uid,
|
|
97
|
-
...developerClaims
|
|
98
|
-
};
|
|
99
|
-
const jwt = await new import_jose.SignJWT(payload).setProtectedHeader({ alg: "RS256", typ: "JWT" }).setIssuedAt(now).setExpirationTime(now + expiresIn).setIssuer(clientEmail).setSubject(clientEmail).setAudience(
|
|
100
|
-
"https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
|
|
101
|
-
).sign(parsedPrivateKey);
|
|
102
|
-
return {
|
|
103
|
-
data: jwt
|
|
104
|
-
};
|
|
105
|
-
} catch (error) {
|
|
106
|
-
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
107
|
-
return {
|
|
108
|
-
errors: [
|
|
109
|
-
new CustomTokenError(`Failed to create custom token: ${message}`, "TOKEN_CREATION_FAILED")
|
|
110
|
-
]
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
async function createCustomToken(uid, developerClaims) {
|
|
115
|
-
const { data, errors } = await createCustomTokenJwt(uid, developerClaims);
|
|
116
|
-
if (errors) {
|
|
117
|
-
throw errors[0];
|
|
118
|
-
}
|
|
119
|
-
return data;
|
|
45
|
+
return data;
|
|
46
|
+
};
|
|
120
47
|
}
|
|
121
48
|
|
|
122
49
|
// src/jwt/verifyJwt.ts
|
|
123
|
-
var
|
|
50
|
+
var import_jose2 = require("jose");
|
|
124
51
|
|
|
125
52
|
// src/utils/errors.ts
|
|
126
53
|
var TokenVerificationErrorReason = {
|
|
@@ -163,6 +90,11 @@ function mapJwtPayloadToDecodedIdToken(payload) {
|
|
|
163
90
|
decodedIdToken.uid = decodedIdToken.sub;
|
|
164
91
|
return decodedIdToken;
|
|
165
92
|
}
|
|
93
|
+
function mapJwtPayloadToDecodedAppCheckToken(payload) {
|
|
94
|
+
const decodedAppCheckToken = payload;
|
|
95
|
+
decodedAppCheckToken.app_id = decodedAppCheckToken.sub;
|
|
96
|
+
return decodedAppCheckToken;
|
|
97
|
+
}
|
|
166
98
|
|
|
167
99
|
// src/utils/rfc4648.ts
|
|
168
100
|
var base64url = {
|
|
@@ -241,10 +173,10 @@ function stringify(data, encoding, opts = {}) {
|
|
|
241
173
|
}
|
|
242
174
|
|
|
243
175
|
// src/jwt/cryptoKeys.ts
|
|
244
|
-
var
|
|
176
|
+
var import_jose = require("jose");
|
|
245
177
|
async function importKey(key, algorithm) {
|
|
246
178
|
if (typeof key === "object") {
|
|
247
|
-
const result = await (0,
|
|
179
|
+
const result = await (0, import_jose.importJWK)(key, algorithm);
|
|
248
180
|
if (result instanceof Uint8Array) {
|
|
249
181
|
throw new Error("Unexpected Uint8Array result from JWK import");
|
|
250
182
|
}
|
|
@@ -252,13 +184,13 @@ async function importKey(key, algorithm) {
|
|
|
252
184
|
}
|
|
253
185
|
const keyString = key.trim();
|
|
254
186
|
if (keyString.includes("-----BEGIN CERTIFICATE-----")) {
|
|
255
|
-
return await (0,
|
|
187
|
+
return await (0, import_jose.importX509)(keyString, algorithm);
|
|
256
188
|
}
|
|
257
189
|
if (keyString.includes("-----BEGIN PUBLIC KEY-----")) {
|
|
258
|
-
return await (0,
|
|
190
|
+
return await (0, import_jose.importSPKI)(keyString, algorithm);
|
|
259
191
|
}
|
|
260
192
|
try {
|
|
261
|
-
return await (0,
|
|
193
|
+
return await (0, import_jose.importSPKI)(keyString, algorithm);
|
|
262
194
|
} catch (error) {
|
|
263
195
|
throw new Error(
|
|
264
196
|
`Unsupported key format. Supported formats: X.509 certificate (PEM), SPKI (PEM), JWK (JSON object or string). Error: ${error}`
|
|
@@ -341,7 +273,7 @@ async function verifySignature(jwt, key) {
|
|
|
341
273
|
const joseAlgorithm = header.alg || "RS256";
|
|
342
274
|
try {
|
|
343
275
|
const publicKey = await importKey(key, joseAlgorithm);
|
|
344
|
-
const { payload } = await (0,
|
|
276
|
+
const { payload } = await (0, import_jose2.jwtVerify)(raw.text, publicKey);
|
|
345
277
|
return { data: payload };
|
|
346
278
|
} catch (error) {
|
|
347
279
|
return {
|
|
@@ -356,8 +288,8 @@ async function verifySignature(jwt, key) {
|
|
|
356
288
|
}
|
|
357
289
|
function ternDecodeJwt(token) {
|
|
358
290
|
try {
|
|
359
|
-
const header = (0,
|
|
360
|
-
const payload = (0,
|
|
291
|
+
const header = (0, import_jose2.decodeProtectedHeader)(token);
|
|
292
|
+
const payload = (0, import_jose2.decodeJwt)(token);
|
|
361
293
|
const tokenParts = (token || "").toString().split(".");
|
|
362
294
|
if (tokenParts.length !== 3) {
|
|
363
295
|
return {
|
|
@@ -424,179 +356,189 @@ async function verifyJwt(token, options) {
|
|
|
424
356
|
const decodedIdToken = mapJwtPayloadToDecodedIdToken(verifiedPayload);
|
|
425
357
|
return { data: decodedIdToken };
|
|
426
358
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
HandshakeNonce: "__ternsecure_handshake_nonce"
|
|
444
|
-
};
|
|
445
|
-
var QueryParameters = {
|
|
446
|
-
TernSynced: "__tern_synced",
|
|
447
|
-
SuffixedCookies: "suffixed_cookies",
|
|
448
|
-
TernRedirectUrl: "__tern_redirect_url",
|
|
449
|
-
// use the reference to Cookies to indicate that it's the same value
|
|
450
|
-
DevBrowser: Cookies.DevBrowser,
|
|
451
|
-
Handshake: Cookies.Handshake,
|
|
452
|
-
HandshakeHelp: "__tern_help",
|
|
453
|
-
LegacyDevBrowser: "__dev_session",
|
|
454
|
-
HandshakeReason: "__tern_hs_reason",
|
|
455
|
-
HandshakeNonce: Cookies.HandshakeNonce
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
// src/tokens/keys.ts
|
|
459
|
-
var cache = {};
|
|
460
|
-
var lastUpdatedAt = 0;
|
|
461
|
-
var googleExpiresAt = 0;
|
|
462
|
-
function getFromCache(kid) {
|
|
463
|
-
return cache[kid];
|
|
464
|
-
}
|
|
465
|
-
function getCacheValues() {
|
|
466
|
-
return Object.values(cache);
|
|
467
|
-
}
|
|
468
|
-
function setInCache(kid, certificate, shouldExpire = true) {
|
|
469
|
-
cache[kid] = certificate;
|
|
470
|
-
lastUpdatedAt = shouldExpire ? Date.now() : -1;
|
|
471
|
-
}
|
|
472
|
-
async function fetchPublicKeys(keyUrl) {
|
|
473
|
-
const url = new URL(keyUrl);
|
|
474
|
-
const response = await fetch(url);
|
|
475
|
-
if (!response.ok) {
|
|
476
|
-
throw new TokenVerificationError({
|
|
477
|
-
message: `Error loading public keys from ${url.href} with code=${response.status} `,
|
|
478
|
-
reason: TokenVerificationErrorReason.TokenInvalid
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
const data = await response.json();
|
|
482
|
-
const expiresAt = getExpiresAt(response);
|
|
483
|
-
return {
|
|
484
|
-
keys: data,
|
|
485
|
-
expiresAt
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
async function loadJWKFromRemote({
|
|
489
|
-
keyURL = GOOGLE_PUBLIC_KEYS_URL,
|
|
490
|
-
skipJwksCache,
|
|
491
|
-
kid
|
|
492
|
-
}) {
|
|
493
|
-
if (skipJwksCache || isCacheExpired() || !getFromCache(kid)) {
|
|
494
|
-
const { keys, expiresAt } = await fetchPublicKeys(keyURL);
|
|
495
|
-
if (!keys || Object.keys(keys).length === 0) {
|
|
496
|
-
throw new TokenVerificationError({
|
|
497
|
-
message: `The JWKS endpoint ${keyURL} returned no keys`,
|
|
498
|
-
reason: TokenVerificationErrorReason.RemoteJWKFailedToLoad
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
googleExpiresAt = expiresAt;
|
|
502
|
-
Object.entries(keys).forEach(([keyId, cert2]) => {
|
|
503
|
-
setInCache(keyId, cert2);
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
const cert = getFromCache(kid);
|
|
507
|
-
if (!cert) {
|
|
508
|
-
getCacheValues();
|
|
509
|
-
const availableKids = Object.keys(cache).sort().join(", ");
|
|
510
|
-
throw new TokenVerificationError({
|
|
511
|
-
message: `No public key found for kid "${kid}". Available kids: [${availableKids}]`,
|
|
512
|
-
reason: TokenVerificationErrorReason.TokenInvalid
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
return cert;
|
|
516
|
-
}
|
|
517
|
-
function isCacheExpired() {
|
|
518
|
-
const now = Date.now();
|
|
519
|
-
if (lastUpdatedAt === -1) {
|
|
520
|
-
return false;
|
|
521
|
-
}
|
|
522
|
-
const cacheAge = now - lastUpdatedAt;
|
|
523
|
-
const maxCacheAge = MAX_CACHE_LAST_UPDATED_AT_SECONDS * 1e3;
|
|
524
|
-
const localCacheExpired = cacheAge >= maxCacheAge;
|
|
525
|
-
const googleCacheExpired = now >= googleExpiresAt;
|
|
526
|
-
const isExpired = localCacheExpired || googleCacheExpired;
|
|
527
|
-
if (isExpired) {
|
|
528
|
-
cache = {};
|
|
529
|
-
}
|
|
530
|
-
return isExpired;
|
|
531
|
-
}
|
|
532
|
-
function getExpiresAt(res) {
|
|
533
|
-
const cacheControlHeader = res.headers.get("cache-control");
|
|
534
|
-
if (!cacheControlHeader) {
|
|
535
|
-
return Date.now() + DEFAULT_CACHE_DURATION;
|
|
359
|
+
async function verifyAppCheckSignature(jwt, getPublicKey2) {
|
|
360
|
+
const { header, raw } = jwt;
|
|
361
|
+
const joseAlgorithm = header.alg || "RS256";
|
|
362
|
+
try {
|
|
363
|
+
const key = await getPublicKey2();
|
|
364
|
+
const { payload } = await (0, import_jose2.jwtVerify)(raw.text, key);
|
|
365
|
+
return { data: payload };
|
|
366
|
+
} catch (error) {
|
|
367
|
+
return {
|
|
368
|
+
errors: [
|
|
369
|
+
new TokenVerificationError({
|
|
370
|
+
reason: TokenVerificationErrorReason.TokenInvalidSignature,
|
|
371
|
+
message: error.message
|
|
372
|
+
})
|
|
373
|
+
]
|
|
374
|
+
};
|
|
536
375
|
}
|
|
537
|
-
const maxAgeMatch = cacheControlHeader.match(CACHE_CONTROL_REGEX);
|
|
538
|
-
const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1], 10) : DEFAULT_CACHE_DURATION / 1e3;
|
|
539
|
-
return Date.now() + maxAge * 1e3;
|
|
540
376
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
const { data:
|
|
377
|
+
async function verifyAppCheckJwt(token, options) {
|
|
378
|
+
const { key: getPublicKey2 } = options;
|
|
379
|
+
const clockSkew = options.clockSkewInMs || DEFAULT_CLOCK_SKEW_IN_MS;
|
|
380
|
+
const { data: decoded, errors } = ternDecodeJwt(token);
|
|
545
381
|
if (errors) {
|
|
546
382
|
return { errors };
|
|
547
383
|
}
|
|
548
|
-
const { header } =
|
|
549
|
-
|
|
550
|
-
|
|
384
|
+
const { header, payload } = decoded;
|
|
385
|
+
try {
|
|
386
|
+
verifyHeaderKid(header.kid);
|
|
387
|
+
verifySubClaim(payload.sub);
|
|
388
|
+
verifyExpirationClaim(payload.exp, clockSkew);
|
|
389
|
+
verifyIssuedAtClaim(payload.iat, clockSkew);
|
|
390
|
+
} catch (error) {
|
|
391
|
+
return { errors: [error] };
|
|
392
|
+
}
|
|
393
|
+
const { data: verifiedPayload, errors: signatureErrors } = await verifyAppCheckSignature(
|
|
394
|
+
decoded,
|
|
395
|
+
getPublicKey2
|
|
396
|
+
);
|
|
397
|
+
if (signatureErrors) {
|
|
551
398
|
return {
|
|
552
399
|
errors: [
|
|
553
400
|
new TokenVerificationError({
|
|
554
|
-
reason: TokenVerificationErrorReason.
|
|
555
|
-
message:
|
|
401
|
+
reason: TokenVerificationErrorReason.TokenInvalidSignature,
|
|
402
|
+
message: "Token signature verification failed."
|
|
556
403
|
})
|
|
557
404
|
]
|
|
558
405
|
};
|
|
559
406
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
407
|
+
const decodedAppCheckToken = mapJwtPayloadToDecodedAppCheckToken(verifiedPayload);
|
|
408
|
+
return { data: decodedAppCheckToken };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/jwt/jwt.ts
|
|
412
|
+
var import_jose3 = require("jose");
|
|
413
|
+
|
|
414
|
+
// src/jwt/customJwt.ts
|
|
415
|
+
var import_jose4 = require("jose");
|
|
416
|
+
var CustomTokenError = class extends Error {
|
|
417
|
+
constructor(message, code) {
|
|
418
|
+
super(message);
|
|
419
|
+
this.code = code;
|
|
420
|
+
this.name = "CustomTokenError";
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
var RESERVED_CLAIMS = [
|
|
424
|
+
"acr",
|
|
425
|
+
"amr",
|
|
426
|
+
"at_hash",
|
|
427
|
+
"aud",
|
|
428
|
+
"auth_time",
|
|
429
|
+
"azp",
|
|
430
|
+
"cnf",
|
|
431
|
+
"c_hash",
|
|
432
|
+
"exp",
|
|
433
|
+
"firebase",
|
|
434
|
+
"iat",
|
|
435
|
+
"iss",
|
|
436
|
+
"jti",
|
|
437
|
+
"nbf",
|
|
438
|
+
"nonce",
|
|
439
|
+
"sub"
|
|
440
|
+
];
|
|
441
|
+
async function createCustomTokenJwt(uid, developerClaims) {
|
|
442
|
+
try {
|
|
443
|
+
const privateKey = process.env.FIREBASE_PRIVATE_KEY;
|
|
444
|
+
const clientEmail = process.env.FIREBASE_CLIENT_EMAIL;
|
|
445
|
+
if (!privateKey || !clientEmail) {
|
|
446
|
+
return {
|
|
447
|
+
errors: [
|
|
448
|
+
new CustomTokenError(
|
|
449
|
+
"Missing FIREBASE_PRIVATE_KEY or FIREBASE_CLIENT_EMAIL environment variables",
|
|
450
|
+
"MISSING_ENV_VARS"
|
|
451
|
+
)
|
|
569
452
|
]
|
|
570
453
|
};
|
|
571
454
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
455
|
+
if (!uid || typeof uid !== "string") {
|
|
456
|
+
return {
|
|
457
|
+
errors: [new CustomTokenError("uid must be a non-empty string", "INVALID_UID")]
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
if (uid.length > 128) {
|
|
461
|
+
return {
|
|
462
|
+
errors: [new CustomTokenError("uid must not exceed 128 characters", "UID_TOO_LONG")]
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
if (developerClaims) {
|
|
466
|
+
for (const claim of Object.keys(developerClaims)) {
|
|
467
|
+
if (RESERVED_CLAIMS.includes(claim)) {
|
|
468
|
+
return {
|
|
469
|
+
errors: [new CustomTokenError(`Custom claim '${claim}' is reserved`, "RESERVED_CLAIM")]
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
576
473
|
}
|
|
474
|
+
const expiresIn = 3600;
|
|
475
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
476
|
+
const parsedPrivateKey = await (0, import_jose4.importPKCS8)(privateKey.replace(/\\n/g, "\n"), "RS256");
|
|
477
|
+
const payload = {
|
|
478
|
+
iss: clientEmail,
|
|
479
|
+
sub: clientEmail,
|
|
480
|
+
aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
|
|
481
|
+
iat: now,
|
|
482
|
+
exp: now + expiresIn,
|
|
483
|
+
uid,
|
|
484
|
+
...developerClaims
|
|
485
|
+
};
|
|
486
|
+
const jwt = await new import_jose4.SignJWT(payload).setProtectedHeader({ alg: "RS256", typ: "JWT" }).setIssuedAt(now).setExpirationTime(now + expiresIn).setIssuer(clientEmail).setSubject(clientEmail).setAudience(
|
|
487
|
+
"https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
|
|
488
|
+
).sign(parsedPrivateKey);
|
|
577
489
|
return {
|
|
578
|
-
|
|
490
|
+
data: jwt
|
|
491
|
+
};
|
|
492
|
+
} catch (error) {
|
|
493
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
494
|
+
return {
|
|
495
|
+
errors: [
|
|
496
|
+
new CustomTokenError(`Failed to create custom token: ${message}`, "TOKEN_CREATION_FAILED")
|
|
497
|
+
]
|
|
579
498
|
};
|
|
580
499
|
}
|
|
581
500
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
throw errors[0];
|
|
589
|
-
}
|
|
590
|
-
return data;
|
|
591
|
-
};
|
|
501
|
+
async function createCustomToken(uid, developerClaims) {
|
|
502
|
+
const { data, errors } = await createCustomTokenJwt(uid, developerClaims);
|
|
503
|
+
if (errors) {
|
|
504
|
+
throw errors[0];
|
|
505
|
+
}
|
|
506
|
+
return data;
|
|
592
507
|
}
|
|
593
508
|
|
|
594
|
-
// src/jwt/jwt.ts
|
|
595
|
-
var import_jose4 = require("jose");
|
|
596
|
-
|
|
597
509
|
// src/jwt/signJwt.ts
|
|
598
510
|
var import_jose5 = require("jose");
|
|
511
|
+
|
|
512
|
+
// src/utils/fetcher.ts
|
|
513
|
+
async function getDetailFromResponse(response) {
|
|
514
|
+
const json = await response.json();
|
|
515
|
+
if (!json) {
|
|
516
|
+
return "Missing error payload";
|
|
517
|
+
}
|
|
518
|
+
let detail = typeof json.error === "string" ? json.error : json.error?.message ?? "Missing error payload";
|
|
519
|
+
if (json.error_description) {
|
|
520
|
+
detail += " (" + json.error_description + ")";
|
|
521
|
+
}
|
|
522
|
+
return detail;
|
|
523
|
+
}
|
|
524
|
+
async function fetchText(url, init) {
|
|
525
|
+
return (await fetchAny(url, init)).text();
|
|
526
|
+
}
|
|
527
|
+
async function fetchJson(url, init) {
|
|
528
|
+
return (await fetchAny(url, init)).json();
|
|
529
|
+
}
|
|
530
|
+
async function fetchAny(url, init) {
|
|
531
|
+
const response = await fetch(url, init);
|
|
532
|
+
if (!response.ok) {
|
|
533
|
+
throw new Error(await getDetailFromResponse(response));
|
|
534
|
+
}
|
|
535
|
+
return response;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/jwt/types.ts
|
|
599
539
|
var ALGORITHM_RS256 = "RS256";
|
|
540
|
+
|
|
541
|
+
// src/jwt/signJwt.ts
|
|
600
542
|
async function ternSignJwt(opts) {
|
|
601
543
|
const { payload, privateKey, keyId } = opts;
|
|
602
544
|
let key;
|
|
@@ -604,118 +546,553 @@ async function ternSignJwt(opts) {
|
|
|
604
546
|
key = await (0, import_jose5.importPKCS8)(privateKey, ALGORITHM_RS256);
|
|
605
547
|
} catch (error) {
|
|
606
548
|
throw new TokenVerificationError({
|
|
607
|
-
message: `Failed to import private key: ${error.message}`,
|
|
549
|
+
message: `Failed to import private key: ${error.message}`,
|
|
550
|
+
reason: TokenVerificationErrorReason.TokenInvalid
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
return new import_jose5.SignJWT(payload).setProtectedHeader({ alg: ALGORITHM_RS256, kid: keyId }).sign(key);
|
|
554
|
+
}
|
|
555
|
+
function formatBase64(value) {
|
|
556
|
+
return value.replace(/\//g, "_").replace(/\+/g, "-").replace(/=+$/, "");
|
|
557
|
+
}
|
|
558
|
+
function encodeSegment(segment) {
|
|
559
|
+
const value = JSON.stringify(segment);
|
|
560
|
+
return formatBase64(import_jose5.base64url.encode(value));
|
|
561
|
+
}
|
|
562
|
+
async function ternSignBlob({
|
|
563
|
+
payload,
|
|
564
|
+
serviceAccountId,
|
|
565
|
+
accessToken
|
|
566
|
+
}) {
|
|
567
|
+
const url = `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${serviceAccountId}:signBlob`;
|
|
568
|
+
const header = {
|
|
569
|
+
alg: ALGORITHM_RS256,
|
|
570
|
+
typ: "JWT"
|
|
571
|
+
};
|
|
572
|
+
const token = `${encodeSegment(header)}.${encodeSegment(payload)}`;
|
|
573
|
+
const request = {
|
|
574
|
+
method: "POST",
|
|
575
|
+
headers: {
|
|
576
|
+
Authorization: `Bearer ${accessToken}`
|
|
577
|
+
},
|
|
578
|
+
body: JSON.stringify({ payload: import_jose5.base64url.encode(token) })
|
|
579
|
+
};
|
|
580
|
+
const response = await fetchAny(url, request);
|
|
581
|
+
const blob = await response.blob();
|
|
582
|
+
const key = await blob.text();
|
|
583
|
+
const { signedBlob } = JSON.parse(key);
|
|
584
|
+
return `${token}.${formatBase64(signedBlob)}`;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/jwt/crypto-signer.ts
|
|
588
|
+
var ServiceAccountSigner = class {
|
|
589
|
+
constructor(credential, tenantId) {
|
|
590
|
+
this.credential = credential;
|
|
591
|
+
this.tenantId = tenantId;
|
|
592
|
+
}
|
|
593
|
+
async getAccountId() {
|
|
594
|
+
return Promise.resolve(this.credential.clientEmail);
|
|
595
|
+
}
|
|
596
|
+
async sign(payload) {
|
|
597
|
+
if (this.tenantId) {
|
|
598
|
+
payload.tenant_id = this.tenantId;
|
|
599
|
+
}
|
|
600
|
+
return ternSignJwt({ payload, privateKey: this.credential.privateKey });
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
var IAMSigner = class {
|
|
604
|
+
algorithm = ALGORITHM_RS256;
|
|
605
|
+
credential;
|
|
606
|
+
tenantId;
|
|
607
|
+
serviceAccountId;
|
|
608
|
+
constructor(credential, tenantId, serviceAccountId) {
|
|
609
|
+
this.credential = credential;
|
|
610
|
+
this.tenantId = tenantId;
|
|
611
|
+
this.serviceAccountId = serviceAccountId;
|
|
612
|
+
}
|
|
613
|
+
async sign(payload) {
|
|
614
|
+
if (this.tenantId) {
|
|
615
|
+
payload.tenant_id = this.tenantId;
|
|
616
|
+
}
|
|
617
|
+
const serviceAccount = await this.getAccountId();
|
|
618
|
+
const accessToken = await this.credential.getAccessToken();
|
|
619
|
+
return ternSignBlob({
|
|
620
|
+
accessToken: accessToken.accessToken,
|
|
621
|
+
serviceAccountId: serviceAccount,
|
|
622
|
+
payload
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
async getAccountId() {
|
|
626
|
+
if (this.serviceAccountId) {
|
|
627
|
+
return this.serviceAccountId;
|
|
628
|
+
}
|
|
629
|
+
const token = await this.credential.getAccessToken();
|
|
630
|
+
const url = "http://metadata/computeMetadata/v1/instance/service-accounts/default/email";
|
|
631
|
+
const request = {
|
|
632
|
+
method: "GET",
|
|
633
|
+
headers: {
|
|
634
|
+
"Metadata-Flavor": "Google",
|
|
635
|
+
Authorization: `Bearer ${token.accessToken}`
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
return this.serviceAccountId = await fetchText(url, request);
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// src/jwt/index.ts
|
|
643
|
+
var ternDecodeJwt2 = createJwtGuard(ternDecodeJwt);
|
|
644
|
+
|
|
645
|
+
// src/utils/token-generator.ts
|
|
646
|
+
function cryptoSignerFromCredential(credential, tenantId, serviceAccountId) {
|
|
647
|
+
if (credential instanceof ServiceAccountManager) {
|
|
648
|
+
return new ServiceAccountSigner(credential, tenantId);
|
|
649
|
+
}
|
|
650
|
+
return new IAMSigner(credential, tenantId, serviceAccountId);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// src/app-check/AppCheckApi.ts
|
|
654
|
+
function getSdkVersion() {
|
|
655
|
+
return "12.7.0";
|
|
656
|
+
}
|
|
657
|
+
var FIREBASE_APP_CHECK_CONFIG_HEADERS = {
|
|
658
|
+
"X-Firebase-Client": `fire-admin-node/${getSdkVersion()}`
|
|
659
|
+
};
|
|
660
|
+
var AppCheckApi = class {
|
|
661
|
+
constructor(credential) {
|
|
662
|
+
this.credential = credential;
|
|
663
|
+
}
|
|
664
|
+
async exchangeToken(params) {
|
|
665
|
+
const { projectId, appId, customToken, limitedUse = false } = params;
|
|
666
|
+
const token = await this.credential.getAccessToken(false);
|
|
667
|
+
if (!projectId || !appId) {
|
|
668
|
+
throw new Error("Project ID and App ID are required for App Check token exchange");
|
|
669
|
+
}
|
|
670
|
+
const endpoint = `https://firebaseappcheck.googleapis.com/v1/projects/${projectId}/apps/${appId}:exchangeCustomToken`;
|
|
671
|
+
const headers = {
|
|
672
|
+
"Content-Type": "application/json",
|
|
673
|
+
"Authorization": `Bearer ${token.accessToken}`
|
|
674
|
+
};
|
|
675
|
+
try {
|
|
676
|
+
const response = await fetch(endpoint, {
|
|
677
|
+
method: "POST",
|
|
678
|
+
headers,
|
|
679
|
+
body: JSON.stringify({ customToken, limitedUse })
|
|
680
|
+
});
|
|
681
|
+
if (!response.ok) {
|
|
682
|
+
const errorText = await response.text();
|
|
683
|
+
throw new Error(`App Check token exchange failed: ${response.status} ${errorText}`);
|
|
684
|
+
}
|
|
685
|
+
const data = await response.json();
|
|
686
|
+
return {
|
|
687
|
+
token: data.token,
|
|
688
|
+
ttl: data.ttl
|
|
689
|
+
};
|
|
690
|
+
} catch (error) {
|
|
691
|
+
console.warn("[ternsecure - appcheck api]unexpected error:", error);
|
|
692
|
+
throw error;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
async exchangeDebugToken(params) {
|
|
696
|
+
const { projectId, appId, customToken, accessToken, limitedUse = false } = params;
|
|
697
|
+
if (!projectId || !appId) {
|
|
698
|
+
throw new Error("Project ID and App ID are required for App Check token exchange");
|
|
699
|
+
}
|
|
700
|
+
const endpoint = `https://firebaseappcheck.googleapis.com/v1beta/projects/${projectId}/apps/${appId}:exchangeDebugToken`;
|
|
701
|
+
const headers = {
|
|
702
|
+
...FIREBASE_APP_CHECK_CONFIG_HEADERS,
|
|
703
|
+
"Authorization": `Bearer ${accessToken}`
|
|
704
|
+
};
|
|
705
|
+
const body = {
|
|
706
|
+
customToken,
|
|
707
|
+
limitedUse
|
|
708
|
+
};
|
|
709
|
+
try {
|
|
710
|
+
const response = await fetch(endpoint, {
|
|
711
|
+
method: "POST",
|
|
712
|
+
headers,
|
|
713
|
+
body: JSON.stringify(body)
|
|
714
|
+
});
|
|
715
|
+
if (!response.ok) {
|
|
716
|
+
const errorText = await response.text();
|
|
717
|
+
throw new Error(`App Check token exchange failed: ${response.status} ${errorText}`);
|
|
718
|
+
}
|
|
719
|
+
const data = await response.json();
|
|
720
|
+
return {
|
|
721
|
+
token: data.token,
|
|
722
|
+
ttl: data.ttl
|
|
723
|
+
};
|
|
724
|
+
} catch (error) {
|
|
725
|
+
console.warn("[ternsecure - appcheck api]unexpected error:", error);
|
|
726
|
+
throw error;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
// src/constants.ts
|
|
732
|
+
var GOOGLE_PUBLIC_KEYS_URL = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
|
|
733
|
+
var FIREBASE_APP_CHECK_AUDIENCE = "https://firebaseappcheck.googleapis.com/google.firebase.appcheck.v1.TokenExchangeService";
|
|
734
|
+
var MAX_CACHE_LAST_UPDATED_AT_SECONDS = 5 * 60;
|
|
735
|
+
var DEFAULT_CACHE_DURATION = 3600 * 1e3;
|
|
736
|
+
var CACHE_CONTROL_REGEX = /max-age=(\d+)/;
|
|
737
|
+
var TOKEN_EXPIRY_THRESHOLD_MILLIS = 5 * 60 * 1e3;
|
|
738
|
+
var GOOGLE_TOKEN_AUDIENCE = "https://accounts.google.com/o/oauth2/token";
|
|
739
|
+
var GOOGLE_AUTH_TOKEN_HOST = "accounts.google.com";
|
|
740
|
+
var GOOGLE_AUTH_TOKEN_PATH = "/o/oauth2/token";
|
|
741
|
+
var ONE_HOUR_IN_SECONDS = 60 * 60;
|
|
742
|
+
var ONE_MINUTE_IN_SECONDS = 60;
|
|
743
|
+
var ONE_MINUTE_IN_MILLIS = ONE_MINUTE_IN_SECONDS * 1e3;
|
|
744
|
+
var ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1e3;
|
|
745
|
+
var Cookies = {
|
|
746
|
+
Session: "__session",
|
|
747
|
+
CsrfToken: "__terncf",
|
|
748
|
+
IdToken: "TernSecure_[DEFAULT]",
|
|
749
|
+
Refresh: "TernSecureID_[DEFAULT]",
|
|
750
|
+
Custom: "__custom",
|
|
751
|
+
TernAut: "tern_aut",
|
|
752
|
+
Handshake: "__ternsecure_handshake",
|
|
753
|
+
DevBrowser: "__ternsecure_db_jwt",
|
|
754
|
+
RedirectCount: "__ternsecure_redirect_count",
|
|
755
|
+
HandshakeNonce: "__ternsecure_handshake_nonce"
|
|
756
|
+
};
|
|
757
|
+
var QueryParameters = {
|
|
758
|
+
TernSynced: "__tern_synced",
|
|
759
|
+
SuffixedCookies: "suffixed_cookies",
|
|
760
|
+
TernRedirectUrl: "__tern_redirect_url",
|
|
761
|
+
// use the reference to Cookies to indicate that it's the same value
|
|
762
|
+
DevBrowser: Cookies.DevBrowser,
|
|
763
|
+
Handshake: Cookies.Handshake,
|
|
764
|
+
HandshakeHelp: "__tern_help",
|
|
765
|
+
LegacyDevBrowser: "__dev_session",
|
|
766
|
+
HandshakeReason: "__tern_hs_reason",
|
|
767
|
+
HandshakeNonce: Cookies.HandshakeNonce
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
// src/app-check/generator.ts
|
|
771
|
+
function transformMillisecondsToSecondsString(milliseconds) {
|
|
772
|
+
let duration;
|
|
773
|
+
const seconds = Math.floor(milliseconds / 1e3);
|
|
774
|
+
const nanos = Math.floor((milliseconds - seconds * 1e3) * 1e6);
|
|
775
|
+
if (nanos > 0) {
|
|
776
|
+
let nanoString = nanos.toString();
|
|
777
|
+
while (nanoString.length < 9) {
|
|
778
|
+
nanoString = "0" + nanoString;
|
|
779
|
+
}
|
|
780
|
+
duration = `${seconds}.${nanoString}s`;
|
|
781
|
+
} else {
|
|
782
|
+
duration = `${seconds}s`;
|
|
783
|
+
}
|
|
784
|
+
return duration;
|
|
785
|
+
}
|
|
786
|
+
var AppCheckTokenGenerator = class {
|
|
787
|
+
signer;
|
|
788
|
+
constructor(signer) {
|
|
789
|
+
this.signer = signer;
|
|
790
|
+
}
|
|
791
|
+
async createCustomToken(appId, options) {
|
|
792
|
+
if (!appId) {
|
|
793
|
+
throw new Error(
|
|
794
|
+
"appId is invalid"
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
let customOptions = {};
|
|
798
|
+
if (typeof options !== "undefined") {
|
|
799
|
+
customOptions = this.validateTokenOptions(options);
|
|
800
|
+
}
|
|
801
|
+
const account = await this.signer.getAccountId();
|
|
802
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
803
|
+
const body = {
|
|
804
|
+
iss: account,
|
|
805
|
+
sub: account,
|
|
806
|
+
app_id: appId,
|
|
807
|
+
aud: FIREBASE_APP_CHECK_AUDIENCE,
|
|
808
|
+
exp: iat + ONE_MINUTE_IN_SECONDS * 5,
|
|
809
|
+
iat,
|
|
810
|
+
...customOptions
|
|
811
|
+
};
|
|
812
|
+
return this.signer.sign(body);
|
|
813
|
+
}
|
|
814
|
+
validateTokenOptions(options) {
|
|
815
|
+
if (typeof options.ttlMillis !== "undefined") {
|
|
816
|
+
if (options.ttlMillis < ONE_MINUTE_IN_MILLIS * 30 || options.ttlMillis > ONE_DAY_IN_MILLIS * 7) {
|
|
817
|
+
throw new Error(
|
|
818
|
+
"ttlMillis must be a duration in milliseconds between 30 minutes and 7 days (inclusive)."
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
return { ttl: transformMillisecondsToSecondsString(options.ttlMillis) };
|
|
822
|
+
}
|
|
823
|
+
return {};
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
// src/app-check/serverAppCheck.ts
|
|
828
|
+
var import_redis = require("@upstash/redis");
|
|
829
|
+
|
|
830
|
+
// src/utils/admin-init.ts
|
|
831
|
+
var import_firebase_admin = __toESM(require("firebase-admin"));
|
|
832
|
+
var import_app_check = require("firebase-admin/app-check");
|
|
833
|
+
|
|
834
|
+
// src/utils/config.ts
|
|
835
|
+
var loadAdminConfig = () => ({
|
|
836
|
+
projectId: process.env.FIREBASE_PROJECT_ID || "",
|
|
837
|
+
clientEmail: process.env.FIREBASE_CLIENT_EMAIL || "",
|
|
838
|
+
privateKey: process.env.FIREBASE_PRIVATE_KEY || ""
|
|
839
|
+
});
|
|
840
|
+
var validateAdminConfig = (config) => {
|
|
841
|
+
const requiredFields = [
|
|
842
|
+
"projectId",
|
|
843
|
+
"clientEmail",
|
|
844
|
+
"privateKey"
|
|
845
|
+
];
|
|
846
|
+
const errors = [];
|
|
847
|
+
requiredFields.forEach((field) => {
|
|
848
|
+
if (!config[field]) {
|
|
849
|
+
errors.push(`Missing required field: FIREBASE_${String(field).toUpperCase()}`);
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
return {
|
|
853
|
+
isValid: errors.length === 0,
|
|
854
|
+
errors,
|
|
855
|
+
config
|
|
856
|
+
};
|
|
857
|
+
};
|
|
858
|
+
var initializeAdminConfig = () => {
|
|
859
|
+
const config = loadAdminConfig();
|
|
860
|
+
const validationResult = validateAdminConfig(config);
|
|
861
|
+
if (!validationResult.isValid) {
|
|
862
|
+
throw new Error(
|
|
863
|
+
`Firebase Admin configuration validation failed:
|
|
864
|
+
${validationResult.errors.join("\n")}`
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
return config;
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
// src/utils/admin-init.ts
|
|
871
|
+
if (!import_firebase_admin.default.apps.length) {
|
|
872
|
+
try {
|
|
873
|
+
const config = initializeAdminConfig();
|
|
874
|
+
import_firebase_admin.default.initializeApp({
|
|
875
|
+
credential: import_firebase_admin.default.credential.cert({
|
|
876
|
+
...config,
|
|
877
|
+
privateKey: config.privateKey.replace(/\\n/g, "\n")
|
|
878
|
+
})
|
|
879
|
+
});
|
|
880
|
+
} catch (error) {
|
|
881
|
+
console.error("Firebase admin initialization error", error);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
var adminTernSecureAuth = import_firebase_admin.default.auth();
|
|
885
|
+
var adminTernSecureDb = import_firebase_admin.default.firestore();
|
|
886
|
+
var TernSecureTenantManager = import_firebase_admin.default.auth().tenantManager();
|
|
887
|
+
var appCheckAdmin = (0, import_app_check.getAppCheck)();
|
|
888
|
+
|
|
889
|
+
// src/app-check/verifier.ts
|
|
890
|
+
var import_jose6 = require("jose");
|
|
891
|
+
var getPublicKey = async (header, keyURL) => {
|
|
892
|
+
const jswksUrl = new URL(keyURL);
|
|
893
|
+
const getKey = (0, import_jose6.createRemoteJWKSet)(jswksUrl);
|
|
894
|
+
return getKey(header);
|
|
895
|
+
};
|
|
896
|
+
var verifyAppCheckToken = async (token, options) => {
|
|
897
|
+
const { data: decodedResult, errors } = ternDecodeJwt(token);
|
|
898
|
+
if (errors) {
|
|
899
|
+
throw errors[0];
|
|
900
|
+
}
|
|
901
|
+
const { header } = decodedResult;
|
|
902
|
+
const { kid } = header;
|
|
903
|
+
if (!kid) {
|
|
904
|
+
return {
|
|
905
|
+
errors: [
|
|
906
|
+
new TokenVerificationError({
|
|
907
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
908
|
+
message: 'JWT "kid" header is missing.'
|
|
909
|
+
})
|
|
910
|
+
]
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
const getPublicKeyForToken = () => getPublicKey(header, options.keyURL || "");
|
|
915
|
+
return await verifyAppCheckJwt(token, { ...options, key: getPublicKeyForToken });
|
|
916
|
+
} catch (error) {
|
|
917
|
+
if (error instanceof TokenVerificationError) {
|
|
918
|
+
return { errors: [error] };
|
|
919
|
+
}
|
|
920
|
+
return {
|
|
921
|
+
errors: [error]
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
var AppcheckTokenVerifier = class {
|
|
926
|
+
constructor(credential) {
|
|
927
|
+
this.credential = credential;
|
|
928
|
+
}
|
|
929
|
+
verifyToken = async (token, projectId, options) => {
|
|
930
|
+
const { data, errors } = await verifyAppCheckToken(token, options);
|
|
931
|
+
if (errors) {
|
|
932
|
+
throw errors[0];
|
|
933
|
+
}
|
|
934
|
+
return data;
|
|
935
|
+
};
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
// src/app-check/index.ts
|
|
939
|
+
var JWKS_URL = "https://firebaseappcheck.googleapis.com/v1/jwks";
|
|
940
|
+
var AppCheck = class {
|
|
941
|
+
client;
|
|
942
|
+
tokenGenerator;
|
|
943
|
+
appCheckTokenVerifier;
|
|
944
|
+
limitedUse;
|
|
945
|
+
constructor(credential, tenantId, limitedUse) {
|
|
946
|
+
this.client = new AppCheckApi(credential);
|
|
947
|
+
this.tokenGenerator = new AppCheckTokenGenerator(
|
|
948
|
+
cryptoSignerFromCredential(credential, tenantId)
|
|
949
|
+
);
|
|
950
|
+
this.appCheckTokenVerifier = new AppcheckTokenVerifier(credential);
|
|
951
|
+
this.limitedUse = limitedUse;
|
|
952
|
+
}
|
|
953
|
+
createToken = (projectId, appId, options) => {
|
|
954
|
+
return this.tokenGenerator.createCustomToken(appId, options).then((customToken) => {
|
|
955
|
+
return this.client.exchangeToken({ customToken, projectId, appId });
|
|
956
|
+
});
|
|
957
|
+
};
|
|
958
|
+
verifyToken = async (appCheckToken, projectId, options) => {
|
|
959
|
+
return this.appCheckTokenVerifier.verifyToken(appCheckToken, projectId, { keyURL: JWKS_URL, ...options }).then((decodedToken) => {
|
|
960
|
+
return {
|
|
961
|
+
appId: decodedToken.app_id,
|
|
962
|
+
token: decodedToken
|
|
963
|
+
};
|
|
964
|
+
});
|
|
965
|
+
};
|
|
966
|
+
};
|
|
967
|
+
function getAppCheck2(serviceAccount, tenantId, limitedUse) {
|
|
968
|
+
return new AppCheck(new ServiceAccountManager(serviceAccount), tenantId, limitedUse);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// src/tokens/keys.ts
|
|
972
|
+
var cache = {};
|
|
973
|
+
var lastUpdatedAt = 0;
|
|
974
|
+
var googleExpiresAt = 0;
|
|
975
|
+
function getFromCache(kid) {
|
|
976
|
+
return cache[kid];
|
|
977
|
+
}
|
|
978
|
+
function getCacheValues() {
|
|
979
|
+
return Object.values(cache);
|
|
980
|
+
}
|
|
981
|
+
function setInCache(kid, certificate, shouldExpire = true) {
|
|
982
|
+
cache[kid] = certificate;
|
|
983
|
+
lastUpdatedAt = shouldExpire ? Date.now() : -1;
|
|
984
|
+
}
|
|
985
|
+
async function fetchPublicKeys(keyUrl) {
|
|
986
|
+
const url = new URL(keyUrl);
|
|
987
|
+
const response = await fetch(url);
|
|
988
|
+
if (!response.ok) {
|
|
989
|
+
throw new TokenVerificationError({
|
|
990
|
+
message: `Error loading public keys from ${url.href} with code=${response.status} `,
|
|
991
|
+
reason: TokenVerificationErrorReason.TokenInvalid
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
const data = await response.json();
|
|
995
|
+
const expiresAt = getExpiresAt(response);
|
|
996
|
+
return {
|
|
997
|
+
keys: data,
|
|
998
|
+
expiresAt
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
async function loadJWKFromRemote({
|
|
1002
|
+
keyURL,
|
|
1003
|
+
skipJwksCache,
|
|
1004
|
+
kid
|
|
1005
|
+
}) {
|
|
1006
|
+
const finalKeyURL = keyURL || GOOGLE_PUBLIC_KEYS_URL;
|
|
1007
|
+
if (skipJwksCache || isCacheExpired() || !getFromCache(kid)) {
|
|
1008
|
+
const { keys, expiresAt } = await fetchPublicKeys(finalKeyURL);
|
|
1009
|
+
if (!keys || Object.keys(keys).length === 0) {
|
|
1010
|
+
throw new TokenVerificationError({
|
|
1011
|
+
message: `The JWKS endpoint ${finalKeyURL} returned no keys`,
|
|
1012
|
+
reason: TokenVerificationErrorReason.RemoteJWKFailedToLoad
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
googleExpiresAt = expiresAt;
|
|
1016
|
+
Object.entries(keys).forEach(([keyId, cert2]) => {
|
|
1017
|
+
setInCache(keyId, cert2);
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
const cert = getFromCache(kid);
|
|
1021
|
+
if (!cert) {
|
|
1022
|
+
getCacheValues();
|
|
1023
|
+
const availableKids = Object.keys(cache).sort().join(", ");
|
|
1024
|
+
throw new TokenVerificationError({
|
|
1025
|
+
message: `No public key found for kid "${kid}". Available kids: [${availableKids}]`,
|
|
608
1026
|
reason: TokenVerificationErrorReason.TokenInvalid
|
|
609
1027
|
});
|
|
610
1028
|
}
|
|
611
|
-
return
|
|
1029
|
+
return cert;
|
|
612
1030
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// src/auth/constants.ts
|
|
618
|
-
var TOKEN_EXPIRY_THRESHOLD_MILLIS = 5 * 60 * 1e3;
|
|
619
|
-
var GOOGLE_TOKEN_AUDIENCE = "https://accounts.google.com/o/oauth2/token";
|
|
620
|
-
var GOOGLE_AUTH_TOKEN_HOST = "accounts.google.com";
|
|
621
|
-
var GOOGLE_AUTH_TOKEN_PATH = "/o/oauth2/token";
|
|
622
|
-
var ONE_HOUR_IN_SECONDS = 60 * 60;
|
|
623
|
-
|
|
624
|
-
// src/auth/utils.ts
|
|
625
|
-
async function getDetailFromResponse(response) {
|
|
626
|
-
const json = await response.json();
|
|
627
|
-
if (!json) {
|
|
628
|
-
return "Missing error payload";
|
|
1031
|
+
function isCacheExpired() {
|
|
1032
|
+
const now = Date.now();
|
|
1033
|
+
if (lastUpdatedAt === -1) {
|
|
1034
|
+
return false;
|
|
629
1035
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1036
|
+
const cacheAge = now - lastUpdatedAt;
|
|
1037
|
+
const maxCacheAge = MAX_CACHE_LAST_UPDATED_AT_SECONDS * 1e3;
|
|
1038
|
+
const localCacheExpired = cacheAge >= maxCacheAge;
|
|
1039
|
+
const googleCacheExpired = now >= googleExpiresAt;
|
|
1040
|
+
const isExpired = localCacheExpired || googleCacheExpired;
|
|
1041
|
+
if (isExpired) {
|
|
1042
|
+
cache = {};
|
|
633
1043
|
}
|
|
634
|
-
return
|
|
635
|
-
}
|
|
636
|
-
async function fetchJson(url, init) {
|
|
637
|
-
return (await fetchAny(url, init)).json();
|
|
1044
|
+
return isExpired;
|
|
638
1045
|
}
|
|
639
|
-
|
|
640
|
-
const
|
|
641
|
-
if (!
|
|
642
|
-
|
|
1046
|
+
function getExpiresAt(res) {
|
|
1047
|
+
const cacheControlHeader = res.headers.get("cache-control");
|
|
1048
|
+
if (!cacheControlHeader) {
|
|
1049
|
+
return Date.now() + DEFAULT_CACHE_DURATION;
|
|
643
1050
|
}
|
|
644
|
-
|
|
1051
|
+
const maxAgeMatch = cacheControlHeader.match(CACHE_CONTROL_REGEX);
|
|
1052
|
+
const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1], 10) : DEFAULT_CACHE_DURATION / 1e3;
|
|
1053
|
+
return Date.now() + maxAge * 1e3;
|
|
645
1054
|
}
|
|
646
1055
|
|
|
647
|
-
// src/
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
throw new Error("Invalid access token response");
|
|
1056
|
+
// src/tokens/verify.ts
|
|
1057
|
+
async function verifyToken(token, options) {
|
|
1058
|
+
const { data: decodedResult, errors } = ternDecodeJwt(token);
|
|
1059
|
+
if (errors) {
|
|
1060
|
+
return { errors };
|
|
653
1061
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
this.privateKey = serviceAccount.privateKey;
|
|
666
|
-
this.clientEmail = serviceAccount.clientEmail;
|
|
1062
|
+
const { header } = decodedResult;
|
|
1063
|
+
const { kid } = header;
|
|
1064
|
+
if (!kid) {
|
|
1065
|
+
return {
|
|
1066
|
+
errors: [
|
|
1067
|
+
new TokenVerificationError({
|
|
1068
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
1069
|
+
message: 'JWT "kid" header is missing.'
|
|
1070
|
+
})
|
|
1071
|
+
]
|
|
1072
|
+
};
|
|
667
1073
|
}
|
|
668
|
-
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
});
|
|
680
|
-
};
|
|
681
|
-
fetchAndCacheAccessToken = async (url) => {
|
|
682
|
-
const accessToken = await this.fetchAccessToken(url);
|
|
683
|
-
accessTokenCache.set(this.projectId, accessToken);
|
|
684
|
-
return accessToken;
|
|
685
|
-
};
|
|
686
|
-
getAccessToken = async (refresh) => {
|
|
687
|
-
const url = `https://${GOOGLE_AUTH_TOKEN_HOST}${GOOGLE_AUTH_TOKEN_PATH}`;
|
|
688
|
-
if (refresh) {
|
|
689
|
-
return this.fetchAndCacheAccessToken(url);
|
|
1074
|
+
try {
|
|
1075
|
+
const key = options.jwtKey || await loadJWKFromRemote({ ...options, kid });
|
|
1076
|
+
if (!key) {
|
|
1077
|
+
return {
|
|
1078
|
+
errors: [
|
|
1079
|
+
new TokenVerificationError({
|
|
1080
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
1081
|
+
message: `No public key found for kid "${kid}".`
|
|
1082
|
+
})
|
|
1083
|
+
]
|
|
1084
|
+
};
|
|
690
1085
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
1086
|
+
return await verifyJwt(token, { ...options, key });
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
if (error instanceof TokenVerificationError) {
|
|
1089
|
+
return { errors: [error] };
|
|
694
1090
|
}
|
|
695
|
-
return
|
|
696
|
-
|
|
697
|
-
createJwt = async () => {
|
|
698
|
-
const iat = Math.floor(Date.now() / 1e3);
|
|
699
|
-
const payload = {
|
|
700
|
-
aud: GOOGLE_TOKEN_AUDIENCE,
|
|
701
|
-
iat,
|
|
702
|
-
exp: iat + ONE_HOUR_IN_SECONDS,
|
|
703
|
-
iss: this.clientEmail,
|
|
704
|
-
sub: this.clientEmail,
|
|
705
|
-
scope: [
|
|
706
|
-
"https://www.googleapis.com/auth/cloud-platform",
|
|
707
|
-
"https://www.googleapis.com/auth/firebase.database",
|
|
708
|
-
"https://www.googleapis.com/auth/firebase.messaging",
|
|
709
|
-
"https://www.googleapis.com/auth/identitytoolkit",
|
|
710
|
-
"https://www.googleapis.com/auth/userinfo.email"
|
|
711
|
-
].join(" ")
|
|
1091
|
+
return {
|
|
1092
|
+
errors: [error]
|
|
712
1093
|
};
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
privateKey: this.privateKey
|
|
716
|
-
});
|
|
717
|
-
};
|
|
718
|
-
};
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
719
1096
|
|
|
720
1097
|
// src/auth/getauth.ts
|
|
721
1098
|
var API_KEY_ERROR = "API Key is required";
|
|
@@ -731,17 +1108,8 @@ function parseFirebaseResponse(data) {
|
|
|
731
1108
|
return data;
|
|
732
1109
|
}
|
|
733
1110
|
function getAuth(options) {
|
|
734
|
-
const { apiKey
|
|
735
|
-
const
|
|
736
|
-
const effectiveApiKey = apiKey || firebaseApiKey;
|
|
737
|
-
let credential = null;
|
|
738
|
-
if (firebaseAdminConfig?.projectId && firebaseAdminConfig?.privateKey && firebaseAdminConfig?.clientEmail) {
|
|
739
|
-
credential = new ServiceAccountTokenManager({
|
|
740
|
-
projectId: firebaseAdminConfig.projectId,
|
|
741
|
-
privateKey: firebaseAdminConfig.privateKey,
|
|
742
|
-
clientEmail: firebaseAdminConfig.clientEmail
|
|
743
|
-
});
|
|
744
|
-
}
|
|
1111
|
+
const { apiKey } = options;
|
|
1112
|
+
const effectiveApiKey = apiKey || process.env.NEXT_PUBLIC_FIREBASE_API_KEY;
|
|
745
1113
|
async function getUserData(idToken, localId) {
|
|
746
1114
|
if (!effectiveApiKey) {
|
|
747
1115
|
throw new Error(API_KEY_ERROR);
|
|
@@ -826,43 +1194,12 @@ function getAuth(options) {
|
|
|
826
1194
|
auth_time: decodedCustomIdToken.data.auth_time
|
|
827
1195
|
};
|
|
828
1196
|
}
|
|
829
|
-
async function
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
error: new Error(
|
|
834
|
-
"Firebase Admin config must be provided to exchange App Check tokens."
|
|
835
|
-
)
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
if (!effectiveApiKey) {
|
|
839
|
-
return { data: null, error: new Error(API_KEY_ERROR) };
|
|
840
|
-
}
|
|
1197
|
+
async function createAppCheckToken() {
|
|
1198
|
+
const adminConfig = loadAdminConfig();
|
|
1199
|
+
const appId = process.env.NEXT_PUBLIC_FIREBASE_APP_ID || "";
|
|
1200
|
+
const appCheck = getAppCheck2(adminConfig, options.tenantId);
|
|
841
1201
|
try {
|
|
842
|
-
const
|
|
843
|
-
if (decoded.errors) {
|
|
844
|
-
return { data: null, error: decoded.errors[0] };
|
|
845
|
-
}
|
|
846
|
-
const customToken = await createCustomToken(decoded.data.uid, {
|
|
847
|
-
emailVerified: decoded.data.email_verified,
|
|
848
|
-
source_sign_in_provider: decoded.data.firebase.sign_in_provider
|
|
849
|
-
});
|
|
850
|
-
const projectId = options.firebaseConfig?.projectId;
|
|
851
|
-
const appId = options.firebaseConfig?.appId;
|
|
852
|
-
if (!projectId || !appId) {
|
|
853
|
-
return { data: null, error: new Error("Project ID and App ID are required for App Check") };
|
|
854
|
-
}
|
|
855
|
-
const { accessToken } = await credential.getAccessToken();
|
|
856
|
-
const appCheckResponse = await options.apiClient?.appCheck.exchangeCustomToken({
|
|
857
|
-
accessToken,
|
|
858
|
-
projectId,
|
|
859
|
-
appId,
|
|
860
|
-
customToken,
|
|
861
|
-
limitedUse: false
|
|
862
|
-
});
|
|
863
|
-
if (!appCheckResponse?.token) {
|
|
864
|
-
return { data: null, error: new Error("Failed to exchange for App Check token") };
|
|
865
|
-
}
|
|
1202
|
+
const appCheckResponse = await appCheck.createToken(adminConfig.projectId, appId);
|
|
866
1203
|
return {
|
|
867
1204
|
data: {
|
|
868
1205
|
token: appCheckResponse.token,
|
|
@@ -874,16 +1211,104 @@ function getAuth(options) {
|
|
|
874
1211
|
return { data: null, error };
|
|
875
1212
|
}
|
|
876
1213
|
}
|
|
1214
|
+
async function verifyAppCheckToken2(token) {
|
|
1215
|
+
const adminConfig = loadAdminConfig();
|
|
1216
|
+
const appCheck = getAppCheck2(adminConfig, options.tenantId);
|
|
1217
|
+
try {
|
|
1218
|
+
const decodedToken = await appCheck.verifyToken(token, adminConfig.projectId, {});
|
|
1219
|
+
return {
|
|
1220
|
+
data: decodedToken,
|
|
1221
|
+
error: null
|
|
1222
|
+
};
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
return { data: null, error };
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
877
1227
|
return {
|
|
878
1228
|
getUserData,
|
|
879
1229
|
customForIdAndRefreshToken,
|
|
880
1230
|
createCustomIdAndRefreshToken,
|
|
881
1231
|
refreshExpiredIdToken,
|
|
882
|
-
|
|
1232
|
+
createAppCheckToken,
|
|
1233
|
+
verifyAppCheckToken: verifyAppCheckToken2
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// src/auth/credential.ts
|
|
1238
|
+
var accessTokenCache = /* @__PURE__ */ new Map();
|
|
1239
|
+
async function requestAccessToken(urlString, init) {
|
|
1240
|
+
const json = await fetchJson(urlString, init);
|
|
1241
|
+
if (!json.access_token || !json.expires_in) {
|
|
1242
|
+
throw new Error("Invalid access token response");
|
|
1243
|
+
}
|
|
1244
|
+
return {
|
|
1245
|
+
accessToken: json.access_token,
|
|
1246
|
+
expirationTime: Date.now() + json.expires_in * 1e3
|
|
883
1247
|
};
|
|
884
1248
|
}
|
|
1249
|
+
var ServiceAccountManager = class {
|
|
1250
|
+
projectId;
|
|
1251
|
+
privateKey;
|
|
1252
|
+
clientEmail;
|
|
1253
|
+
constructor(serviceAccount) {
|
|
1254
|
+
this.projectId = serviceAccount.projectId;
|
|
1255
|
+
this.privateKey = serviceAccount.privateKey;
|
|
1256
|
+
this.clientEmail = serviceAccount.clientEmail;
|
|
1257
|
+
}
|
|
1258
|
+
fetchAccessToken = async (url) => {
|
|
1259
|
+
const token = await this.createJwt();
|
|
1260
|
+
const postData = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + token;
|
|
1261
|
+
return requestAccessToken(url, {
|
|
1262
|
+
method: "POST",
|
|
1263
|
+
headers: {
|
|
1264
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1265
|
+
Authorization: `Bearer ${token}`,
|
|
1266
|
+
Accept: "application/json"
|
|
1267
|
+
},
|
|
1268
|
+
body: postData
|
|
1269
|
+
});
|
|
1270
|
+
};
|
|
1271
|
+
fetchAndCacheAccessToken = async (url) => {
|
|
1272
|
+
const accessToken = await this.fetchAccessToken(url);
|
|
1273
|
+
accessTokenCache.set(this.projectId, accessToken);
|
|
1274
|
+
return accessToken;
|
|
1275
|
+
};
|
|
1276
|
+
getAccessToken = async (refresh) => {
|
|
1277
|
+
const url = `https://${GOOGLE_AUTH_TOKEN_HOST}${GOOGLE_AUTH_TOKEN_PATH}`;
|
|
1278
|
+
if (refresh) {
|
|
1279
|
+
return this.fetchAndCacheAccessToken(url);
|
|
1280
|
+
}
|
|
1281
|
+
const cachedResponse = accessTokenCache.get(this.projectId);
|
|
1282
|
+
if (!cachedResponse || cachedResponse.expirationTime - Date.now() <= TOKEN_EXPIRY_THRESHOLD_MILLIS) {
|
|
1283
|
+
return this.fetchAndCacheAccessToken(url);
|
|
1284
|
+
}
|
|
1285
|
+
return cachedResponse;
|
|
1286
|
+
};
|
|
1287
|
+
createJwt = async () => {
|
|
1288
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
1289
|
+
const payload = {
|
|
1290
|
+
aud: GOOGLE_TOKEN_AUDIENCE,
|
|
1291
|
+
iat,
|
|
1292
|
+
exp: iat + ONE_HOUR_IN_SECONDS,
|
|
1293
|
+
iss: this.clientEmail,
|
|
1294
|
+
sub: this.clientEmail,
|
|
1295
|
+
scope: [
|
|
1296
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
1297
|
+
"https://www.googleapis.com/auth/firebase.database",
|
|
1298
|
+
"https://www.googleapis.com/auth/firebase.messaging",
|
|
1299
|
+
"https://www.googleapis.com/auth/identitytoolkit",
|
|
1300
|
+
"https://www.googleapis.com/auth/userinfo.email"
|
|
1301
|
+
].join(" ")
|
|
1302
|
+
};
|
|
1303
|
+
return ternSignJwt({
|
|
1304
|
+
payload,
|
|
1305
|
+
privateKey: this.privateKey
|
|
1306
|
+
});
|
|
1307
|
+
};
|
|
1308
|
+
};
|
|
885
1309
|
// Annotate the CommonJS export names for ESM import in node:
|
|
886
1310
|
0 && (module.exports = {
|
|
1311
|
+
ServiceAccountManager,
|
|
887
1312
|
getAuth
|
|
888
1313
|
});
|
|
889
1314
|
//# sourceMappingURL=index.js.map
|