@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.
Files changed (91) hide show
  1. package/dist/adapters/index.d.ts +1 -1
  2. package/dist/adapters/index.d.ts.map +1 -1
  3. package/dist/adapters/types.d.ts +42 -0
  4. package/dist/adapters/types.d.ts.map +1 -1
  5. package/dist/admin/index.d.ts +1 -1
  6. package/dist/admin/index.d.ts.map +1 -1
  7. package/dist/admin/index.js +8 -1
  8. package/dist/admin/index.js.map +1 -1
  9. package/dist/admin/index.mjs +10 -70
  10. package/dist/admin/index.mjs.map +1 -1
  11. package/dist/app-check/AppCheckApi.d.ts +14 -0
  12. package/dist/app-check/AppCheckApi.d.ts.map +1 -0
  13. package/dist/app-check/generator.d.ts +9 -0
  14. package/dist/app-check/generator.d.ts.map +1 -0
  15. package/dist/app-check/index.d.ts +18 -0
  16. package/dist/app-check/index.d.ts.map +1 -0
  17. package/dist/app-check/index.js +1052 -0
  18. package/dist/app-check/index.js.map +1 -0
  19. package/dist/app-check/index.mjs +13 -0
  20. package/dist/app-check/index.mjs.map +1 -0
  21. package/dist/app-check/serverAppCheck.d.ts +33 -0
  22. package/dist/app-check/serverAppCheck.d.ts.map +1 -0
  23. package/dist/app-check/types.d.ts +21 -0
  24. package/dist/app-check/types.d.ts.map +1 -0
  25. package/dist/app-check/verifier.d.ts +16 -0
  26. package/dist/app-check/verifier.d.ts.map +1 -0
  27. package/dist/auth/credential.d.ts +5 -5
  28. package/dist/auth/credential.d.ts.map +1 -1
  29. package/dist/auth/getauth.d.ts +2 -1
  30. package/dist/auth/getauth.d.ts.map +1 -1
  31. package/dist/auth/index.d.ts +2 -0
  32. package/dist/auth/index.d.ts.map +1 -1
  33. package/dist/auth/index.js +819 -394
  34. package/dist/auth/index.js.map +1 -1
  35. package/dist/auth/index.mjs +5 -3
  36. package/dist/chunk-3OGMNIOJ.mjs +174 -0
  37. package/dist/chunk-3OGMNIOJ.mjs.map +1 -0
  38. package/dist/{chunk-GFH5CXQR.mjs → chunk-AW5OXT7N.mjs} +2 -2
  39. package/dist/chunk-IEJQ7F4A.mjs +778 -0
  40. package/dist/chunk-IEJQ7F4A.mjs.map +1 -0
  41. package/dist/{chunk-NXYWC6YO.mjs → chunk-TUYCJY35.mjs} +182 -6
  42. package/dist/chunk-TUYCJY35.mjs.map +1 -0
  43. package/dist/constants.d.ts +10 -1
  44. package/dist/constants.d.ts.map +1 -1
  45. package/dist/fireRestApi/endpoints/AppCheckApi.d.ts.map +1 -1
  46. package/dist/index.d.ts +4 -1
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +1570 -1183
  49. package/dist/index.js.map +1 -1
  50. package/dist/index.mjs +97 -135
  51. package/dist/index.mjs.map +1 -1
  52. package/dist/jwt/crypto-signer.d.ts +21 -0
  53. package/dist/jwt/crypto-signer.d.ts.map +1 -0
  54. package/dist/jwt/index.d.ts +2 -1
  55. package/dist/jwt/index.d.ts.map +1 -1
  56. package/dist/jwt/index.js +119 -2
  57. package/dist/jwt/index.js.map +1 -1
  58. package/dist/jwt/index.mjs +7 -3
  59. package/dist/jwt/signJwt.d.ts +8 -2
  60. package/dist/jwt/signJwt.d.ts.map +1 -1
  61. package/dist/jwt/types.d.ts +6 -0
  62. package/dist/jwt/types.d.ts.map +1 -1
  63. package/dist/jwt/verifyJwt.d.ts +7 -1
  64. package/dist/jwt/verifyJwt.d.ts.map +1 -1
  65. package/dist/tokens/authstate.d.ts +2 -0
  66. package/dist/tokens/authstate.d.ts.map +1 -1
  67. package/dist/tokens/c-authenticateRequestProcessor.d.ts +2 -2
  68. package/dist/tokens/c-authenticateRequestProcessor.d.ts.map +1 -1
  69. package/dist/tokens/keys.d.ts.map +1 -1
  70. package/dist/tokens/request.d.ts.map +1 -1
  71. package/dist/tokens/types.d.ts +6 -4
  72. package/dist/tokens/types.d.ts.map +1 -1
  73. package/dist/utils/config.d.ts.map +1 -1
  74. package/dist/{auth/utils.d.ts → utils/fetcher.d.ts} +2 -1
  75. package/dist/utils/fetcher.d.ts.map +1 -0
  76. package/dist/utils/mapDecode.d.ts +2 -1
  77. package/dist/utils/mapDecode.d.ts.map +1 -1
  78. package/dist/utils/token-generator.d.ts +4 -0
  79. package/dist/utils/token-generator.d.ts.map +1 -0
  80. package/package.json +13 -3
  81. package/dist/auth/constants.d.ts +0 -6
  82. package/dist/auth/constants.d.ts.map +0 -1
  83. package/dist/auth/utils.d.ts.map +0 -1
  84. package/dist/chunk-DJLDUW7J.mjs +0 -414
  85. package/dist/chunk-DJLDUW7J.mjs.map +0 -1
  86. package/dist/chunk-NXYWC6YO.mjs.map +0 -1
  87. package/dist/chunk-WIVOBOZR.mjs +0 -86
  88. package/dist/chunk-WIVOBOZR.mjs.map +0 -1
  89. package/dist/utils/gemini_admin-init.d.ts +0 -10
  90. package/dist/utils/gemini_admin-init.d.ts.map +0 -1
  91. /package/dist/{chunk-GFH5CXQR.mjs.map → chunk-AW5OXT7N.mjs.map} +0 -0
@@ -0,0 +1,778 @@
1
+ import {
2
+ CACHE_CONTROL_REGEX,
3
+ DEFAULT_CACHE_DURATION,
4
+ FIREBASE_APP_CHECK_AUDIENCE,
5
+ GOOGLE_AUTH_TOKEN_HOST,
6
+ GOOGLE_AUTH_TOKEN_PATH,
7
+ GOOGLE_PUBLIC_KEYS_URL,
8
+ GOOGLE_TOKEN_AUDIENCE,
9
+ MAX_CACHE_LAST_UPDATED_AT_SECONDS,
10
+ ONE_DAY_IN_MILLIS,
11
+ ONE_HOUR_IN_SECONDS,
12
+ ONE_MINUTE_IN_MILLIS,
13
+ ONE_MINUTE_IN_SECONDS,
14
+ TOKEN_EXPIRY_THRESHOLD_MILLIS,
15
+ appCheckAdmin,
16
+ loadAdminConfig
17
+ } from "./chunk-3OGMNIOJ.mjs";
18
+ import {
19
+ IAMSigner,
20
+ ServiceAccountSigner,
21
+ TokenVerificationError,
22
+ TokenVerificationErrorReason,
23
+ createCustomToken,
24
+ fetchJson,
25
+ ternDecodeJwt,
26
+ ternSignJwt,
27
+ verifyAppCheckJwt,
28
+ verifyJwt
29
+ } from "./chunk-TUYCJY35.mjs";
30
+
31
+ // src/tokens/keys.ts
32
+ var cache = {};
33
+ var lastUpdatedAt = 0;
34
+ var googleExpiresAt = 0;
35
+ function getFromCache(kid) {
36
+ return cache[kid];
37
+ }
38
+ function getCacheValues() {
39
+ return Object.values(cache);
40
+ }
41
+ function setInCache(kid, certificate, shouldExpire = true) {
42
+ cache[kid] = certificate;
43
+ lastUpdatedAt = shouldExpire ? Date.now() : -1;
44
+ }
45
+ async function fetchPublicKeys(keyUrl) {
46
+ const url = new URL(keyUrl);
47
+ const response = await fetch(url);
48
+ if (!response.ok) {
49
+ throw new TokenVerificationError({
50
+ message: `Error loading public keys from ${url.href} with code=${response.status} `,
51
+ reason: TokenVerificationErrorReason.TokenInvalid
52
+ });
53
+ }
54
+ const data = await response.json();
55
+ const expiresAt = getExpiresAt(response);
56
+ return {
57
+ keys: data,
58
+ expiresAt
59
+ };
60
+ }
61
+ async function loadJWKFromRemote({
62
+ keyURL,
63
+ skipJwksCache,
64
+ kid
65
+ }) {
66
+ const finalKeyURL = keyURL || GOOGLE_PUBLIC_KEYS_URL;
67
+ if (skipJwksCache || isCacheExpired() || !getFromCache(kid)) {
68
+ const { keys, expiresAt } = await fetchPublicKeys(finalKeyURL);
69
+ if (!keys || Object.keys(keys).length === 0) {
70
+ throw new TokenVerificationError({
71
+ message: `The JWKS endpoint ${finalKeyURL} returned no keys`,
72
+ reason: TokenVerificationErrorReason.RemoteJWKFailedToLoad
73
+ });
74
+ }
75
+ googleExpiresAt = expiresAt;
76
+ Object.entries(keys).forEach(([keyId, cert2]) => {
77
+ setInCache(keyId, cert2);
78
+ });
79
+ }
80
+ const cert = getFromCache(kid);
81
+ if (!cert) {
82
+ getCacheValues();
83
+ const availableKids = Object.keys(cache).sort().join(", ");
84
+ throw new TokenVerificationError({
85
+ message: `No public key found for kid "${kid}". Available kids: [${availableKids}]`,
86
+ reason: TokenVerificationErrorReason.TokenInvalid
87
+ });
88
+ }
89
+ return cert;
90
+ }
91
+ function isCacheExpired() {
92
+ const now = Date.now();
93
+ if (lastUpdatedAt === -1) {
94
+ return false;
95
+ }
96
+ const cacheAge = now - lastUpdatedAt;
97
+ const maxCacheAge = MAX_CACHE_LAST_UPDATED_AT_SECONDS * 1e3;
98
+ const localCacheExpired = cacheAge >= maxCacheAge;
99
+ const googleCacheExpired = now >= googleExpiresAt;
100
+ const isExpired = localCacheExpired || googleCacheExpired;
101
+ if (isExpired) {
102
+ cache = {};
103
+ }
104
+ return isExpired;
105
+ }
106
+ function getExpiresAt(res) {
107
+ const cacheControlHeader = res.headers.get("cache-control");
108
+ if (!cacheControlHeader) {
109
+ return Date.now() + DEFAULT_CACHE_DURATION;
110
+ }
111
+ const maxAgeMatch = cacheControlHeader.match(CACHE_CONTROL_REGEX);
112
+ const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1], 10) : DEFAULT_CACHE_DURATION / 1e3;
113
+ return Date.now() + maxAge * 1e3;
114
+ }
115
+
116
+ // src/tokens/verify.ts
117
+ async function verifyToken(token, options) {
118
+ const { data: decodedResult, errors } = ternDecodeJwt(token);
119
+ if (errors) {
120
+ return { errors };
121
+ }
122
+ const { header } = decodedResult;
123
+ const { kid } = header;
124
+ if (!kid) {
125
+ return {
126
+ errors: [
127
+ new TokenVerificationError({
128
+ reason: TokenVerificationErrorReason.TokenInvalid,
129
+ message: 'JWT "kid" header is missing.'
130
+ })
131
+ ]
132
+ };
133
+ }
134
+ try {
135
+ const key = options.jwtKey || await loadJWKFromRemote({ ...options, kid });
136
+ if (!key) {
137
+ return {
138
+ errors: [
139
+ new TokenVerificationError({
140
+ reason: TokenVerificationErrorReason.TokenInvalid,
141
+ message: `No public key found for kid "${kid}".`
142
+ })
143
+ ]
144
+ };
145
+ }
146
+ return await verifyJwt(token, { ...options, key });
147
+ } catch (error) {
148
+ if (error instanceof TokenVerificationError) {
149
+ return { errors: [error] };
150
+ }
151
+ return {
152
+ errors: [error]
153
+ };
154
+ }
155
+ }
156
+
157
+ // src/auth/getauth.ts
158
+ var API_KEY_ERROR = "API Key is required";
159
+ var NO_DATA_ERROR = "No token data received";
160
+ function parseFirebaseResponse(data) {
161
+ if (typeof data === "string") {
162
+ try {
163
+ return JSON.parse(data);
164
+ } catch (error) {
165
+ throw new Error(`Failed to parse Firebase response: ${error}`);
166
+ }
167
+ }
168
+ return data;
169
+ }
170
+ function getAuth(options) {
171
+ const { apiKey } = options;
172
+ const effectiveApiKey = apiKey || process.env.NEXT_PUBLIC_FIREBASE_API_KEY;
173
+ async function getUserData(idToken, localId) {
174
+ if (!effectiveApiKey) {
175
+ throw new Error(API_KEY_ERROR);
176
+ }
177
+ const response = await options.apiClient?.userData.getUserData(effectiveApiKey, {
178
+ idToken,
179
+ localId
180
+ });
181
+ if (!response?.data) {
182
+ throw new Error(NO_DATA_ERROR);
183
+ }
184
+ const parsedData = parseFirebaseResponse(response.data);
185
+ return parsedData;
186
+ }
187
+ async function refreshExpiredIdToken(refreshToken, opts) {
188
+ if (!effectiveApiKey) {
189
+ return { data: null, error: new Error(API_KEY_ERROR) };
190
+ }
191
+ const response = await options.apiClient?.tokens.refreshToken(effectiveApiKey, {
192
+ refresh_token: refreshToken,
193
+ request_origin: opts.referer
194
+ });
195
+ if (!response?.data) {
196
+ return {
197
+ data: null,
198
+ error: new Error(NO_DATA_ERROR)
199
+ };
200
+ }
201
+ const parsedData = parseFirebaseResponse(response.data);
202
+ return {
203
+ data: {
204
+ idToken: parsedData.id_token,
205
+ refreshToken: parsedData.refresh_token
206
+ },
207
+ error: null
208
+ };
209
+ }
210
+ async function customForIdAndRefreshToken(customToken, opts) {
211
+ if (!effectiveApiKey) {
212
+ throw new Error("API Key is required to create custom token");
213
+ }
214
+ const data = await options.apiClient?.tokens.exchangeCustomForIdAndRefreshTokens(
215
+ effectiveApiKey,
216
+ {
217
+ token: customToken,
218
+ returnSecureToken: true
219
+ },
220
+ {
221
+ referer: opts.referer,
222
+ appCheckToken: opts.appCheckToken
223
+ }
224
+ );
225
+ if (!data) {
226
+ throw new Error("No data received from Firebase token exchange");
227
+ }
228
+ return {
229
+ idToken: data.idToken,
230
+ refreshToken: data.refreshToken
231
+ };
232
+ }
233
+ async function createCustomIdAndRefreshToken(idToken, opts) {
234
+ const decoded = await verifyToken(idToken, options);
235
+ const { data, errors } = decoded;
236
+ if (errors) {
237
+ throw errors[0];
238
+ }
239
+ const customToken = await createCustomToken(data.uid, {
240
+ emailVerified: data.email_verified,
241
+ source_sign_in_provider: data.firebase.sign_in_provider
242
+ });
243
+ const idAndRefreshTokens = await customForIdAndRefreshToken(customToken, {
244
+ referer: opts.referer,
245
+ appCheckToken: opts.appCheckToken
246
+ });
247
+ const decodedCustomIdToken = await verifyToken(idAndRefreshTokens.idToken, options);
248
+ if (decodedCustomIdToken.errors) {
249
+ throw decodedCustomIdToken.errors[0];
250
+ }
251
+ return {
252
+ ...idAndRefreshTokens,
253
+ customToken,
254
+ auth_time: decodedCustomIdToken.data.auth_time
255
+ };
256
+ }
257
+ async function createAppCheckToken() {
258
+ const adminConfig = loadAdminConfig();
259
+ const appId = process.env.NEXT_PUBLIC_FIREBASE_APP_ID || "";
260
+ const appCheck = getAppCheck(adminConfig, options.tenantId);
261
+ try {
262
+ const appCheckResponse = await appCheck.createToken(adminConfig.projectId, appId);
263
+ return {
264
+ data: {
265
+ token: appCheckResponse.token,
266
+ ttl: appCheckResponse.ttl
267
+ },
268
+ error: null
269
+ };
270
+ } catch (error) {
271
+ return { data: null, error };
272
+ }
273
+ }
274
+ async function verifyAppCheckToken2(token) {
275
+ const adminConfig = loadAdminConfig();
276
+ const appCheck = getAppCheck(adminConfig, options.tenantId);
277
+ try {
278
+ const decodedToken = await appCheck.verifyToken(token, adminConfig.projectId, {});
279
+ return {
280
+ data: decodedToken,
281
+ error: null
282
+ };
283
+ } catch (error) {
284
+ return { data: null, error };
285
+ }
286
+ }
287
+ return {
288
+ getUserData,
289
+ customForIdAndRefreshToken,
290
+ createCustomIdAndRefreshToken,
291
+ refreshExpiredIdToken,
292
+ createAppCheckToken,
293
+ verifyAppCheckToken: verifyAppCheckToken2
294
+ };
295
+ }
296
+
297
+ // src/auth/credential.ts
298
+ var accessTokenCache = /* @__PURE__ */ new Map();
299
+ async function requestAccessToken(urlString, init) {
300
+ const json = await fetchJson(urlString, init);
301
+ if (!json.access_token || !json.expires_in) {
302
+ throw new Error("Invalid access token response");
303
+ }
304
+ return {
305
+ accessToken: json.access_token,
306
+ expirationTime: Date.now() + json.expires_in * 1e3
307
+ };
308
+ }
309
+ var ServiceAccountManager = class {
310
+ projectId;
311
+ privateKey;
312
+ clientEmail;
313
+ constructor(serviceAccount) {
314
+ this.projectId = serviceAccount.projectId;
315
+ this.privateKey = serviceAccount.privateKey;
316
+ this.clientEmail = serviceAccount.clientEmail;
317
+ }
318
+ fetchAccessToken = async (url) => {
319
+ const token = await this.createJwt();
320
+ const postData = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + token;
321
+ return requestAccessToken(url, {
322
+ method: "POST",
323
+ headers: {
324
+ "Content-Type": "application/x-www-form-urlencoded",
325
+ Authorization: `Bearer ${token}`,
326
+ Accept: "application/json"
327
+ },
328
+ body: postData
329
+ });
330
+ };
331
+ fetchAndCacheAccessToken = async (url) => {
332
+ const accessToken = await this.fetchAccessToken(url);
333
+ accessTokenCache.set(this.projectId, accessToken);
334
+ return accessToken;
335
+ };
336
+ getAccessToken = async (refresh) => {
337
+ const url = `https://${GOOGLE_AUTH_TOKEN_HOST}${GOOGLE_AUTH_TOKEN_PATH}`;
338
+ if (refresh) {
339
+ return this.fetchAndCacheAccessToken(url);
340
+ }
341
+ const cachedResponse = accessTokenCache.get(this.projectId);
342
+ if (!cachedResponse || cachedResponse.expirationTime - Date.now() <= TOKEN_EXPIRY_THRESHOLD_MILLIS) {
343
+ return this.fetchAndCacheAccessToken(url);
344
+ }
345
+ return cachedResponse;
346
+ };
347
+ createJwt = async () => {
348
+ const iat = Math.floor(Date.now() / 1e3);
349
+ const payload = {
350
+ aud: GOOGLE_TOKEN_AUDIENCE,
351
+ iat,
352
+ exp: iat + ONE_HOUR_IN_SECONDS,
353
+ iss: this.clientEmail,
354
+ sub: this.clientEmail,
355
+ scope: [
356
+ "https://www.googleapis.com/auth/cloud-platform",
357
+ "https://www.googleapis.com/auth/firebase.database",
358
+ "https://www.googleapis.com/auth/firebase.messaging",
359
+ "https://www.googleapis.com/auth/identitytoolkit",
360
+ "https://www.googleapis.com/auth/userinfo.email"
361
+ ].join(" ")
362
+ };
363
+ return ternSignJwt({
364
+ payload,
365
+ privateKey: this.privateKey
366
+ });
367
+ };
368
+ };
369
+
370
+ // src/utils/token-generator.ts
371
+ function cryptoSignerFromCredential(credential, tenantId, serviceAccountId) {
372
+ if (credential instanceof ServiceAccountManager) {
373
+ return new ServiceAccountSigner(credential, tenantId);
374
+ }
375
+ return new IAMSigner(credential, tenantId, serviceAccountId);
376
+ }
377
+
378
+ // src/app-check/AppCheckApi.ts
379
+ function getSdkVersion() {
380
+ return "12.7.0";
381
+ }
382
+ var FIREBASE_APP_CHECK_CONFIG_HEADERS = {
383
+ "X-Firebase-Client": `fire-admin-node/${getSdkVersion()}`
384
+ };
385
+ var AppCheckApi = class {
386
+ constructor(credential) {
387
+ this.credential = credential;
388
+ }
389
+ async exchangeToken(params) {
390
+ const { projectId, appId, customToken, limitedUse = false } = params;
391
+ const token = await this.credential.getAccessToken(false);
392
+ if (!projectId || !appId) {
393
+ throw new Error("Project ID and App ID are required for App Check token exchange");
394
+ }
395
+ const endpoint = `https://firebaseappcheck.googleapis.com/v1/projects/${projectId}/apps/${appId}:exchangeCustomToken`;
396
+ const headers = {
397
+ "Content-Type": "application/json",
398
+ "Authorization": `Bearer ${token.accessToken}`
399
+ };
400
+ try {
401
+ const response = await fetch(endpoint, {
402
+ method: "POST",
403
+ headers,
404
+ body: JSON.stringify({ customToken, limitedUse })
405
+ });
406
+ if (!response.ok) {
407
+ const errorText = await response.text();
408
+ throw new Error(`App Check token exchange failed: ${response.status} ${errorText}`);
409
+ }
410
+ const data = await response.json();
411
+ return {
412
+ token: data.token,
413
+ ttl: data.ttl
414
+ };
415
+ } catch (error) {
416
+ console.warn("[ternsecure - appcheck api]unexpected error:", error);
417
+ throw error;
418
+ }
419
+ }
420
+ async exchangeDebugToken(params) {
421
+ const { projectId, appId, customToken, accessToken, limitedUse = false } = params;
422
+ if (!projectId || !appId) {
423
+ throw new Error("Project ID and App ID are required for App Check token exchange");
424
+ }
425
+ const endpoint = `https://firebaseappcheck.googleapis.com/v1beta/projects/${projectId}/apps/${appId}:exchangeDebugToken`;
426
+ const headers = {
427
+ ...FIREBASE_APP_CHECK_CONFIG_HEADERS,
428
+ "Authorization": `Bearer ${accessToken}`
429
+ };
430
+ const body = {
431
+ customToken,
432
+ limitedUse
433
+ };
434
+ try {
435
+ const response = await fetch(endpoint, {
436
+ method: "POST",
437
+ headers,
438
+ body: JSON.stringify(body)
439
+ });
440
+ if (!response.ok) {
441
+ const errorText = await response.text();
442
+ throw new Error(`App Check token exchange failed: ${response.status} ${errorText}`);
443
+ }
444
+ const data = await response.json();
445
+ return {
446
+ token: data.token,
447
+ ttl: data.ttl
448
+ };
449
+ } catch (error) {
450
+ console.warn("[ternsecure - appcheck api]unexpected error:", error);
451
+ throw error;
452
+ }
453
+ }
454
+ };
455
+
456
+ // src/app-check/generator.ts
457
+ function transformMillisecondsToSecondsString(milliseconds) {
458
+ let duration;
459
+ const seconds = Math.floor(milliseconds / 1e3);
460
+ const nanos = Math.floor((milliseconds - seconds * 1e3) * 1e6);
461
+ if (nanos > 0) {
462
+ let nanoString = nanos.toString();
463
+ while (nanoString.length < 9) {
464
+ nanoString = "0" + nanoString;
465
+ }
466
+ duration = `${seconds}.${nanoString}s`;
467
+ } else {
468
+ duration = `${seconds}s`;
469
+ }
470
+ return duration;
471
+ }
472
+ var AppCheckTokenGenerator = class {
473
+ signer;
474
+ constructor(signer) {
475
+ this.signer = signer;
476
+ }
477
+ async createCustomToken(appId, options) {
478
+ if (!appId) {
479
+ throw new Error(
480
+ "appId is invalid"
481
+ );
482
+ }
483
+ let customOptions = {};
484
+ if (typeof options !== "undefined") {
485
+ customOptions = this.validateTokenOptions(options);
486
+ }
487
+ const account = await this.signer.getAccountId();
488
+ const iat = Math.floor(Date.now() / 1e3);
489
+ const body = {
490
+ iss: account,
491
+ sub: account,
492
+ app_id: appId,
493
+ aud: FIREBASE_APP_CHECK_AUDIENCE,
494
+ exp: iat + ONE_MINUTE_IN_SECONDS * 5,
495
+ iat,
496
+ ...customOptions
497
+ };
498
+ return this.signer.sign(body);
499
+ }
500
+ validateTokenOptions(options) {
501
+ if (typeof options.ttlMillis !== "undefined") {
502
+ if (options.ttlMillis < ONE_MINUTE_IN_MILLIS * 30 || options.ttlMillis > ONE_DAY_IN_MILLIS * 7) {
503
+ throw new Error(
504
+ "ttlMillis must be a duration in milliseconds between 30 minutes and 7 days (inclusive)."
505
+ );
506
+ }
507
+ return { ttl: transformMillisecondsToSecondsString(options.ttlMillis) };
508
+ }
509
+ return {};
510
+ }
511
+ };
512
+
513
+ // src/app-check/serverAppCheck.ts
514
+ import { Redis } from "@upstash/redis";
515
+ var ServerAppCheckManager = class _ServerAppCheckManager {
516
+ static instances = /* @__PURE__ */ new Map();
517
+ memoryCache = /* @__PURE__ */ new Map();
518
+ redisClient = null;
519
+ options;
520
+ pendingTokens = /* @__PURE__ */ new Map();
521
+ constructor(options) {
522
+ const defaultOptions = {
523
+ strategy: "memory",
524
+ ttlMillis: 36e5,
525
+ // 1 hour
526
+ refreshBufferMillis: 3e5,
527
+ // 5 minutes
528
+ keyPrefix: "appcheck:token:",
529
+ skipInMemoryFirst: false
530
+ };
531
+ this.options = { ...defaultOptions, ...options };
532
+ if (this.options.strategy === "redis" && this.options.redis) {
533
+ void this.initializeRedis(this.options.redis);
534
+ }
535
+ }
536
+ initializeRedis = (config) => {
537
+ if (!config) {
538
+ throw new Error('[AppCheck] Redis configuration is required when strategy is "redis"');
539
+ }
540
+ try {
541
+ this.redisClient = new Redis({
542
+ url: config.url,
543
+ token: config.token
544
+ });
545
+ console.info("[AppCheck] Redis client initialized for token caching");
546
+ } catch (error) {
547
+ console.error("[AppCheck] Failed to initialize Redis client:", error);
548
+ throw new Error('[AppCheck] Redis initialization failed. Install "@upstash/redis" package.');
549
+ }
550
+ };
551
+ static getInstance(options) {
552
+ const key = options?.strategy || "memory";
553
+ if (!_ServerAppCheckManager.instances.has(key)) {
554
+ _ServerAppCheckManager.instances.set(key, new _ServerAppCheckManager(options));
555
+ }
556
+ const instance = _ServerAppCheckManager.instances.get(key);
557
+ if (!instance) {
558
+ throw new Error("[AppCheck] Failed to get instance");
559
+ }
560
+ return instance;
561
+ }
562
+ buildCacheKey(appId) {
563
+ return `${this.options.keyPrefix}${appId}`;
564
+ }
565
+ getCachedToken = async (appId) => {
566
+ if (this.options.strategy === "memory") {
567
+ return this.memoryCache.get(appId) || null;
568
+ }
569
+ if (this.options.strategy === "redis") {
570
+ if (!this.options.skipInMemoryFirst) {
571
+ const memCached = this.memoryCache.get(appId);
572
+ if (memCached) {
573
+ return memCached;
574
+ }
575
+ }
576
+ if (this.redisClient) {
577
+ try {
578
+ const key = this.buildCacheKey(appId);
579
+ const cached = await this.redisClient.get(key);
580
+ if (cached) {
581
+ const parsed = typeof cached === "string" ? JSON.parse(cached) : cached;
582
+ if (!this.options.skipInMemoryFirst) {
583
+ this.memoryCache.set(appId, parsed);
584
+ }
585
+ return parsed;
586
+ }
587
+ } catch (error) {
588
+ console.error("[AppCheck] Redis get error:", error);
589
+ }
590
+ }
591
+ }
592
+ return null;
593
+ };
594
+ setCachedToken = async (appId, token, expiresAt) => {
595
+ const cachedToken = { token, expiresAt };
596
+ this.memoryCache.set(appId, cachedToken);
597
+ if (this.options.strategy === "memory") {
598
+ return;
599
+ }
600
+ if (this.options.strategy === "redis" && this.redisClient) {
601
+ try {
602
+ const key = this.buildCacheKey(appId);
603
+ const ttl = expiresAt - Date.now();
604
+ await this.redisClient.set(key, JSON.stringify(cachedToken), {
605
+ px: ttl
606
+ // Expiry in milliseconds (lowercase for Upstash)
607
+ });
608
+ } catch (error) {
609
+ console.error("[AppCheck] Redis set error:", error);
610
+ }
611
+ }
612
+ };
613
+ getOrGenerateToken = async (appId) => {
614
+ const cached = await this.getCachedToken(appId);
615
+ const now = Date.now();
616
+ if (cached && cached.expiresAt > now + this.options.refreshBufferMillis) {
617
+ return cached.token;
618
+ }
619
+ const pending = this.pendingTokens.get(appId);
620
+ if (pending) {
621
+ return pending;
622
+ }
623
+ const tokenPromise = this.generateAndCacheToken(appId);
624
+ this.pendingTokens.set(appId, tokenPromise);
625
+ try {
626
+ const token = await tokenPromise;
627
+ return token;
628
+ } finally {
629
+ this.pendingTokens.delete(appId);
630
+ }
631
+ };
632
+ /**
633
+ * Generate and cache a new token
634
+ */
635
+ generateAndCacheToken = async (appId) => {
636
+ try {
637
+ const now = Date.now();
638
+ const appCheckToken = await appCheckAdmin.createToken(appId, {
639
+ ttlMillis: this.options.ttlMillis
640
+ });
641
+ const expiresAt = now + this.options.ttlMillis;
642
+ await this.setCachedToken(appId, appCheckToken.token, expiresAt);
643
+ return appCheckToken.token;
644
+ } catch (error) {
645
+ console.error("[AppCheck] Failed to generate token:", error);
646
+ return null;
647
+ }
648
+ };
649
+ clearCache = async (appId) => {
650
+ if (appId) {
651
+ this.memoryCache.delete(appId);
652
+ } else {
653
+ this.memoryCache.clear();
654
+ }
655
+ if (this.options.strategy === "redis" && this.redisClient) {
656
+ try {
657
+ if (appId) {
658
+ const key = this.buildCacheKey(appId);
659
+ await this.redisClient.del(key);
660
+ }
661
+ } catch (error) {
662
+ console.error("[AppCheck] Redis delete error:", error);
663
+ }
664
+ }
665
+ };
666
+ getCacheStats() {
667
+ const now = Date.now();
668
+ const entries = Array.from(this.memoryCache.entries()).map(([appId, cached]) => ({
669
+ appId,
670
+ expiresIn: Math.max(0, cached.expiresAt - now)
671
+ }));
672
+ return {
673
+ strategy: this.options.strategy,
674
+ memorySize: this.memoryCache.size,
675
+ entries
676
+ };
677
+ }
678
+ /**
679
+ * Close Redis connection
680
+ */
681
+ disconnect() {
682
+ if (this.redisClient) {
683
+ this.redisClient = null;
684
+ }
685
+ }
686
+ };
687
+
688
+ // src/app-check/verifier.ts
689
+ import { createRemoteJWKSet } from "jose";
690
+ var getPublicKey = async (header, keyURL) => {
691
+ const jswksUrl = new URL(keyURL);
692
+ const getKey = createRemoteJWKSet(jswksUrl);
693
+ return getKey(header);
694
+ };
695
+ var verifyAppCheckToken = async (token, options) => {
696
+ const { data: decodedResult, errors } = ternDecodeJwt(token);
697
+ if (errors) {
698
+ throw errors[0];
699
+ }
700
+ const { header } = decodedResult;
701
+ const { kid } = header;
702
+ if (!kid) {
703
+ return {
704
+ errors: [
705
+ new TokenVerificationError({
706
+ reason: TokenVerificationErrorReason.TokenInvalid,
707
+ message: 'JWT "kid" header is missing.'
708
+ })
709
+ ]
710
+ };
711
+ }
712
+ try {
713
+ const getPublicKeyForToken = () => getPublicKey(header, options.keyURL || "");
714
+ return await verifyAppCheckJwt(token, { ...options, key: getPublicKeyForToken });
715
+ } catch (error) {
716
+ if (error instanceof TokenVerificationError) {
717
+ return { errors: [error] };
718
+ }
719
+ return {
720
+ errors: [error]
721
+ };
722
+ }
723
+ };
724
+ var AppcheckTokenVerifier = class {
725
+ constructor(credential) {
726
+ this.credential = credential;
727
+ }
728
+ verifyToken = async (token, projectId, options) => {
729
+ const { data, errors } = await verifyAppCheckToken(token, options);
730
+ if (errors) {
731
+ throw errors[0];
732
+ }
733
+ return data;
734
+ };
735
+ };
736
+
737
+ // src/app-check/index.ts
738
+ var JWKS_URL = "https://firebaseappcheck.googleapis.com/v1/jwks";
739
+ var AppCheck = class {
740
+ client;
741
+ tokenGenerator;
742
+ appCheckTokenVerifier;
743
+ limitedUse;
744
+ constructor(credential, tenantId, limitedUse) {
745
+ this.client = new AppCheckApi(credential);
746
+ this.tokenGenerator = new AppCheckTokenGenerator(
747
+ cryptoSignerFromCredential(credential, tenantId)
748
+ );
749
+ this.appCheckTokenVerifier = new AppcheckTokenVerifier(credential);
750
+ this.limitedUse = limitedUse;
751
+ }
752
+ createToken = (projectId, appId, options) => {
753
+ return this.tokenGenerator.createCustomToken(appId, options).then((customToken) => {
754
+ return this.client.exchangeToken({ customToken, projectId, appId });
755
+ });
756
+ };
757
+ verifyToken = async (appCheckToken, projectId, options) => {
758
+ return this.appCheckTokenVerifier.verifyToken(appCheckToken, projectId, { keyURL: JWKS_URL, ...options }).then((decodedToken) => {
759
+ return {
760
+ appId: decodedToken.app_id,
761
+ token: decodedToken
762
+ };
763
+ });
764
+ };
765
+ };
766
+ function getAppCheck(serviceAccount, tenantId, limitedUse) {
767
+ return new AppCheck(new ServiceAccountManager(serviceAccount), tenantId, limitedUse);
768
+ }
769
+
770
+ export {
771
+ verifyToken,
772
+ ServerAppCheckManager,
773
+ AppCheck,
774
+ getAppCheck,
775
+ getAuth,
776
+ ServiceAccountManager
777
+ };
778
+ //# sourceMappingURL=chunk-IEJQ7F4A.mjs.map