@tern-secure/backend 1.2.0-canary.v20251127235234 → 1.2.0-canary.v20251202164451

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 +24 -598
  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 +1135 -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 +902 -394
  34. package/dist/auth/index.js.map +1 -1
  35. package/dist/auth/index.mjs +5 -3
  36. package/dist/chunk-34QENCWP.mjs +784 -0
  37. package/dist/chunk-34QENCWP.mjs.map +1 -0
  38. package/dist/{chunk-NXYWC6YO.mjs → chunk-TUYCJY35.mjs} +182 -6
  39. package/dist/chunk-TUYCJY35.mjs.map +1 -0
  40. package/dist/chunk-UCSJDX6Y.mjs +778 -0
  41. package/dist/chunk-UCSJDX6Y.mjs.map +1 -0
  42. package/dist/constants.d.ts +10 -1
  43. package/dist/constants.d.ts.map +1 -1
  44. package/dist/fireRestApi/endpoints/AppCheckApi.d.ts.map +1 -1
  45. package/dist/index.d.ts +4 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +1275 -856
  48. package/dist/index.js.map +1 -1
  49. package/dist/index.mjs +97 -137
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/jwt/crypto-signer.d.ts +21 -0
  52. package/dist/jwt/crypto-signer.d.ts.map +1 -0
  53. package/dist/jwt/index.d.ts +2 -1
  54. package/dist/jwt/index.d.ts.map +1 -1
  55. package/dist/jwt/index.js +119 -2
  56. package/dist/jwt/index.js.map +1 -1
  57. package/dist/jwt/index.mjs +7 -3
  58. package/dist/jwt/signJwt.d.ts +8 -2
  59. package/dist/jwt/signJwt.d.ts.map +1 -1
  60. package/dist/jwt/types.d.ts +6 -0
  61. package/dist/jwt/types.d.ts.map +1 -1
  62. package/dist/jwt/verifyJwt.d.ts +7 -1
  63. package/dist/jwt/verifyJwt.d.ts.map +1 -1
  64. package/dist/tokens/authstate.d.ts +2 -0
  65. package/dist/tokens/authstate.d.ts.map +1 -1
  66. package/dist/tokens/c-authenticateRequestProcessor.d.ts +2 -2
  67. package/dist/tokens/c-authenticateRequestProcessor.d.ts.map +1 -1
  68. package/dist/tokens/keys.d.ts.map +1 -1
  69. package/dist/tokens/request.d.ts.map +1 -1
  70. package/dist/tokens/types.d.ts +6 -4
  71. package/dist/tokens/types.d.ts.map +1 -1
  72. package/dist/utils/config.d.ts.map +1 -1
  73. package/dist/{auth/utils.d.ts → utils/fetcher.d.ts} +2 -1
  74. package/dist/utils/fetcher.d.ts.map +1 -0
  75. package/dist/utils/mapDecode.d.ts +2 -1
  76. package/dist/utils/mapDecode.d.ts.map +1 -1
  77. package/dist/utils/token-generator.d.ts +4 -0
  78. package/dist/utils/token-generator.d.ts.map +1 -0
  79. package/package.json +13 -3
  80. package/dist/auth/constants.d.ts +0 -6
  81. package/dist/auth/constants.d.ts.map +0 -1
  82. package/dist/auth/utils.d.ts.map +0 -1
  83. package/dist/chunk-DJLDUW7J.mjs +0 -414
  84. package/dist/chunk-DJLDUW7J.mjs.map +0 -1
  85. package/dist/chunk-GFH5CXQR.mjs +0 -71
  86. package/dist/chunk-GFH5CXQR.mjs.map +0 -1
  87. package/dist/chunk-NXYWC6YO.mjs.map +0 -1
  88. package/dist/chunk-WIVOBOZR.mjs +0 -86
  89. package/dist/chunk-WIVOBOZR.mjs.map +0 -1
  90. package/dist/utils/gemini_admin-init.d.ts +0 -10
  91. package/dist/utils/gemini_admin-init.d.ts.map +0 -1
@@ -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/customJwt.ts
28
- var import_jose = require("jose");
29
- var CustomTokenError = class extends Error {
30
- constructor(message, code) {
31
- super(message);
32
- this.code = code;
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
- const expiresIn = 3600;
88
- const now = Math.floor(Date.now() / 1e3);
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 import_jose3 = require("jose");
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 import_jose2 = require("jose");
176
+ var import_jose = require("jose");
245
177
  async function importKey(key, algorithm) {
246
178
  if (typeof key === "object") {
247
- const result = await (0, import_jose2.importJWK)(key, algorithm);
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, import_jose2.importX509)(keyString, algorithm);
187
+ return await (0, import_jose.importX509)(keyString, algorithm);
256
188
  }
257
189
  if (keyString.includes("-----BEGIN PUBLIC KEY-----")) {
258
- return await (0, import_jose2.importSPKI)(keyString, algorithm);
190
+ return await (0, import_jose.importSPKI)(keyString, algorithm);
259
191
  }
260
192
  try {
261
- return await (0, import_jose2.importSPKI)(keyString, algorithm);
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, import_jose3.jwtVerify)(raw.text, publicKey);
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, import_jose3.decodeProtectedHeader)(token);
360
- const payload = (0, import_jose3.decodeJwt)(token);
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
- // src/constants.ts
429
- var GOOGLE_PUBLIC_KEYS_URL = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
430
- var MAX_CACHE_LAST_UPDATED_AT_SECONDS = 5 * 60;
431
- var DEFAULT_CACHE_DURATION = 3600 * 1e3;
432
- var CACHE_CONTROL_REGEX = /max-age=(\d+)/;
433
- var Cookies = {
434
- Session: "__session",
435
- CsrfToken: "__terncf",
436
- IdToken: "TernSecure_[DEFAULT]",
437
- Refresh: "TernSecureID_[DEFAULT]",
438
- Custom: "__custom",
439
- TernAut: "tern_aut",
440
- Handshake: "__ternsecure_handshake",
441
- DevBrowser: "__ternsecure_db_jwt",
442
- RedirectCount: "__ternsecure_redirect_count",
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
- // src/tokens/verify.ts
543
- async function verifyToken(token, options) {
544
- const { data: decodedResult, errors } = ternDecodeJwt(token);
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 } = decodedResult;
549
- const { kid } = header;
550
- if (!kid) {
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.TokenInvalid,
555
- message: 'JWT "kid" header is missing.'
401
+ reason: TokenVerificationErrorReason.TokenInvalidSignature,
402
+ message: "Token signature verification failed."
556
403
  })
557
404
  ]
558
405
  };
559
406
  }
560
- try {
561
- const key = options.jwtKey || await loadJWKFromRemote({ ...options, kid });
562
- if (!key) {
563
- return {
564
- errors: [
565
- new TokenVerificationError({
566
- reason: TokenVerificationErrorReason.TokenInvalid,
567
- message: `No public key found for kid "${kid}".`
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
- return await verifyJwt(token, { ...options, key });
573
- } catch (error) {
574
- if (error instanceof TokenVerificationError) {
575
- return { errors: [error] };
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
+ };
576
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
+ }
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
- errors: [error]
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
- // src/jwt/guardReturn.ts
584
- function createJwtGuard(decodedFn) {
585
- return (...args) => {
586
- const { data, errors } = decodedFn(...args);
587
- if (errors) {
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,636 @@ 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 Attributes = {
746
+ AuthToken: "__ternsecureAuthToken",
747
+ AuthSignature: "__ternsecureAuthSignature",
748
+ AuthStatus: "__ternsecureAuthStatus",
749
+ AuthReason: "__ternsecureAuthReason",
750
+ AuthMessage: "__ternsecureAuthMessage",
751
+ TernSecureUrl: "__ternsecureUrl"
752
+ };
753
+ var Cookies = {
754
+ Session: "__session",
755
+ CsrfToken: "__terncf",
756
+ IdToken: "TernSecure_[DEFAULT]",
757
+ Refresh: "TernSecureID_[DEFAULT]",
758
+ Custom: "__custom",
759
+ TernAut: "tern_aut",
760
+ Handshake: "__ternsecure_handshake",
761
+ DevBrowser: "__ternsecure_db_jwt",
762
+ RedirectCount: "__ternsecure_redirect_count",
763
+ HandshakeNonce: "__ternsecure_handshake_nonce"
764
+ };
765
+ var QueryParameters = {
766
+ TernSynced: "__tern_synced",
767
+ SuffixedCookies: "suffixed_cookies",
768
+ TernRedirectUrl: "__tern_redirect_url",
769
+ // use the reference to Cookies to indicate that it's the same value
770
+ DevBrowser: Cookies.DevBrowser,
771
+ Handshake: Cookies.Handshake,
772
+ HandshakeHelp: "__tern_help",
773
+ LegacyDevBrowser: "__dev_session",
774
+ HandshakeReason: "__tern_hs_reason",
775
+ HandshakeNonce: Cookies.HandshakeNonce
776
+ };
777
+ var Headers2 = {
778
+ Accept: "accept",
779
+ AppCheckToken: "x-ternsecure-appcheck",
780
+ AuthMessage: "x-ternsecure-auth-message",
781
+ Authorization: "authorization",
782
+ AuthReason: "x-ternsecure-auth-reason",
783
+ AuthSignature: "x-ternsecure-auth-signature",
784
+ AuthStatus: "x-ternsecure-auth-status",
785
+ AuthToken: "x-ternsecure-auth-token",
786
+ CacheControl: "cache-control",
787
+ TernSecureRedirectTo: "x-ternsecure-redirect-to",
788
+ TernSecureRequestData: "x-ternsecure-request-data",
789
+ TernSecureUrl: "x-ternsecure-url",
790
+ CloudFrontForwardedProto: "cloudfront-forwarded-proto",
791
+ ContentType: "content-type",
792
+ ContentSecurityPolicy: "content-security-policy",
793
+ ContentSecurityPolicyReportOnly: "content-security-policy-report-only",
794
+ EnableDebug: "x-ternsecure-debug",
795
+ ForwardedHost: "x-forwarded-host",
796
+ ForwardedPort: "x-forwarded-port",
797
+ ForwardedProto: "x-forwarded-proto",
798
+ Host: "host",
799
+ Location: "location",
800
+ Nonce: "x-nonce",
801
+ Origin: "origin",
802
+ Referrer: "referer",
803
+ SecFetchDest: "sec-fetch-dest",
804
+ UserAgent: "user-agent",
805
+ ReportingEndpoints: "reporting-endpoints"
806
+ };
807
+ var ContentTypes = {
808
+ Json: "application/json"
809
+ };
810
+ var constants = {
811
+ Attributes,
812
+ Cookies,
813
+ Headers: Headers2,
814
+ ContentTypes,
815
+ QueryParameters
816
+ };
817
+
818
+ // src/app-check/generator.ts
819
+ function transformMillisecondsToSecondsString(milliseconds) {
820
+ let duration;
821
+ const seconds = Math.floor(milliseconds / 1e3);
822
+ const nanos = Math.floor((milliseconds - seconds * 1e3) * 1e6);
823
+ if (nanos > 0) {
824
+ let nanoString = nanos.toString();
825
+ while (nanoString.length < 9) {
826
+ nanoString = "0" + nanoString;
827
+ }
828
+ duration = `${seconds}.${nanoString}s`;
829
+ } else {
830
+ duration = `${seconds}s`;
831
+ }
832
+ return duration;
833
+ }
834
+ var AppCheckTokenGenerator = class {
835
+ signer;
836
+ constructor(signer) {
837
+ this.signer = signer;
838
+ }
839
+ async createCustomToken(appId, options) {
840
+ if (!appId) {
841
+ throw new Error(
842
+ "appId is invalid"
843
+ );
844
+ }
845
+ let customOptions = {};
846
+ if (typeof options !== "undefined") {
847
+ customOptions = this.validateTokenOptions(options);
848
+ }
849
+ const account = await this.signer.getAccountId();
850
+ const iat = Math.floor(Date.now() / 1e3);
851
+ const body = {
852
+ iss: account,
853
+ sub: account,
854
+ app_id: appId,
855
+ aud: FIREBASE_APP_CHECK_AUDIENCE,
856
+ exp: iat + ONE_MINUTE_IN_SECONDS * 5,
857
+ iat,
858
+ ...customOptions
859
+ };
860
+ return this.signer.sign(body);
861
+ }
862
+ validateTokenOptions(options) {
863
+ if (typeof options.ttlMillis !== "undefined") {
864
+ if (options.ttlMillis < ONE_MINUTE_IN_MILLIS * 30 || options.ttlMillis > ONE_DAY_IN_MILLIS * 7) {
865
+ throw new Error(
866
+ "ttlMillis must be a duration in milliseconds between 30 minutes and 7 days (inclusive)."
867
+ );
868
+ }
869
+ return { ttl: transformMillisecondsToSecondsString(options.ttlMillis) };
870
+ }
871
+ return {};
872
+ }
873
+ };
874
+
875
+ // src/app-check/serverAppCheck.ts
876
+ var import_redis = require("@upstash/redis");
877
+
878
+ // src/admin/sessionTernSecure.ts
879
+ var import_errors4 = require("@tern-secure/shared/errors");
880
+
881
+ // src/utils/admin-init.ts
882
+ var import_firebase_admin = __toESM(require("firebase-admin"));
883
+ var import_app_check = require("firebase-admin/app-check");
884
+
885
+ // src/utils/config.ts
886
+ var loadAdminConfig = () => ({
887
+ projectId: process.env.FIREBASE_PROJECT_ID || "",
888
+ clientEmail: process.env.FIREBASE_CLIENT_EMAIL || "",
889
+ privateKey: process.env.FIREBASE_PRIVATE_KEY || ""
890
+ });
891
+ var validateAdminConfig = (config) => {
892
+ const requiredFields = [
893
+ "projectId",
894
+ "clientEmail",
895
+ "privateKey"
896
+ ];
897
+ const errors = [];
898
+ requiredFields.forEach((field) => {
899
+ if (!config[field]) {
900
+ errors.push(`Missing required field: FIREBASE_${String(field).toUpperCase()}`);
901
+ }
902
+ });
903
+ return {
904
+ isValid: errors.length === 0,
905
+ errors,
906
+ config
907
+ };
908
+ };
909
+ var initializeAdminConfig = () => {
910
+ const config = loadAdminConfig();
911
+ const validationResult = validateAdminConfig(config);
912
+ if (!validationResult.isValid) {
913
+ throw new Error(
914
+ `Firebase Admin configuration validation failed:
915
+ ${validationResult.errors.join("\n")}`
916
+ );
917
+ }
918
+ return config;
919
+ };
920
+
921
+ // src/utils/admin-init.ts
922
+ if (!import_firebase_admin.default.apps.length) {
923
+ try {
924
+ const config = initializeAdminConfig();
925
+ import_firebase_admin.default.initializeApp({
926
+ credential: import_firebase_admin.default.credential.cert({
927
+ ...config,
928
+ privateKey: config.privateKey.replace(/\\n/g, "\n")
929
+ })
930
+ });
931
+ } catch (error) {
932
+ console.error("Firebase admin initialization error", error);
933
+ }
934
+ }
935
+ var adminTernSecureAuth = import_firebase_admin.default.auth();
936
+ var adminTernSecureDb = import_firebase_admin.default.firestore();
937
+ var TernSecureTenantManager = import_firebase_admin.default.auth().tenantManager();
938
+ var appCheckAdmin = (0, import_app_check.getAppCheck)();
939
+
940
+ // src/admin/sessionTernSecure.ts
941
+ var DEFAULT_COOKIE_CONFIG = {
942
+ DEFAULT_EXPIRES_IN_MS: 5 * 60 * 1e3,
943
+ // 5 minutes
944
+ DEFAULT_EXPIRES_IN_SECONDS: 5 * 60,
945
+ REVOKE_REFRESH_TOKENS_ON_SIGNOUT: true
946
+ };
947
+ var DEFAULT_COOKIE_OPTIONS = {
948
+ httpOnly: true,
949
+ secure: process.env.NODE_ENV === "production",
950
+ sameSite: "strict",
951
+ path: "/"
952
+ };
953
+
954
+ // src/admin/nextSessionTernSecure.ts
955
+ var import_cookie = require("@tern-secure/shared/cookie");
956
+ var import_errors5 = require("@tern-secure/shared/errors");
957
+ var import_headers = require("next/headers");
958
+ var SESSION_CONSTANTS = {
959
+ COOKIE_NAME: constants.Cookies.Session,
960
+ DEFAULT_EXPIRES_IN_MS: 60 * 60 * 24 * 5 * 1e3,
961
+ // 5 days
962
+ DEFAULT_EXPIRES_IN_SECONDS: 60 * 60 * 24 * 5,
963
+ REVOKE_REFRESH_TOKENS_ON_SIGNOUT: true
964
+ };
965
+
966
+ // src/tokens/ternSecureRequest.ts
967
+ var import_cookie2 = require("cookie");
968
+
969
+ // src/admin/user.ts
970
+ var import_errors6 = require("@tern-secure/shared/errors");
971
+
972
+ // src/app-check/verifier.ts
973
+ var import_jose6 = require("jose");
974
+ var getPublicKey = async (header, keyURL) => {
975
+ const jswksUrl = new URL(keyURL);
976
+ const getKey = (0, import_jose6.createRemoteJWKSet)(jswksUrl);
977
+ return getKey(header);
978
+ };
979
+ var verifyAppCheckToken = async (token, options) => {
980
+ const { data: decodedResult, errors } = ternDecodeJwt(token);
981
+ if (errors) {
982
+ throw errors[0];
983
+ }
984
+ const { header } = decodedResult;
985
+ const { kid } = header;
986
+ if (!kid) {
987
+ return {
988
+ errors: [
989
+ new TokenVerificationError({
990
+ reason: TokenVerificationErrorReason.TokenInvalid,
991
+ message: 'JWT "kid" header is missing.'
992
+ })
993
+ ]
994
+ };
995
+ }
996
+ try {
997
+ const getPublicKeyForToken = () => getPublicKey(header, options.keyURL || "");
998
+ return await verifyAppCheckJwt(token, { ...options, key: getPublicKeyForToken });
999
+ } catch (error) {
1000
+ if (error instanceof TokenVerificationError) {
1001
+ return { errors: [error] };
1002
+ }
1003
+ return {
1004
+ errors: [error]
1005
+ };
1006
+ }
1007
+ };
1008
+ var AppcheckTokenVerifier = class {
1009
+ constructor(credential) {
1010
+ this.credential = credential;
1011
+ }
1012
+ verifyToken = async (token, projectId, options) => {
1013
+ const { data, errors } = await verifyAppCheckToken(token, options);
1014
+ if (errors) {
1015
+ throw errors[0];
1016
+ }
1017
+ return data;
1018
+ };
1019
+ };
1020
+
1021
+ // src/app-check/index.ts
1022
+ var JWKS_URL = "https://firebaseappcheck.googleapis.com/v1/jwks";
1023
+ var AppCheck = class {
1024
+ client;
1025
+ tokenGenerator;
1026
+ appCheckTokenVerifier;
1027
+ limitedUse;
1028
+ constructor(credential, tenantId, limitedUse) {
1029
+ this.client = new AppCheckApi(credential);
1030
+ this.tokenGenerator = new AppCheckTokenGenerator(
1031
+ cryptoSignerFromCredential(credential, tenantId)
1032
+ );
1033
+ this.appCheckTokenVerifier = new AppcheckTokenVerifier(credential);
1034
+ this.limitedUse = limitedUse;
1035
+ }
1036
+ createToken = (projectId, appId, options) => {
1037
+ return this.tokenGenerator.createCustomToken(appId, options).then((customToken) => {
1038
+ return this.client.exchangeToken({ customToken, projectId, appId });
1039
+ });
1040
+ };
1041
+ verifyToken = async (appCheckToken, projectId, options) => {
1042
+ return this.appCheckTokenVerifier.verifyToken(appCheckToken, projectId, { keyURL: JWKS_URL, ...options }).then((decodedToken) => {
1043
+ return {
1044
+ appId: decodedToken.app_id,
1045
+ token: decodedToken
1046
+ };
1047
+ });
1048
+ };
1049
+ };
1050
+ function getAppCheck2(serviceAccount, tenantId, limitedUse) {
1051
+ return new AppCheck(new ServiceAccountManager(serviceAccount), tenantId, limitedUse);
1052
+ }
1053
+
1054
+ // src/tokens/keys.ts
1055
+ var cache = {};
1056
+ var lastUpdatedAt = 0;
1057
+ var googleExpiresAt = 0;
1058
+ function getFromCache(kid) {
1059
+ return cache[kid];
1060
+ }
1061
+ function getCacheValues() {
1062
+ return Object.values(cache);
1063
+ }
1064
+ function setInCache(kid, certificate, shouldExpire = true) {
1065
+ cache[kid] = certificate;
1066
+ lastUpdatedAt = shouldExpire ? Date.now() : -1;
1067
+ }
1068
+ async function fetchPublicKeys(keyUrl) {
1069
+ const url = new URL(keyUrl);
1070
+ const response = await fetch(url);
1071
+ if (!response.ok) {
1072
+ throw new TokenVerificationError({
1073
+ message: `Error loading public keys from ${url.href} with code=${response.status} `,
1074
+ reason: TokenVerificationErrorReason.TokenInvalid
1075
+ });
1076
+ }
1077
+ const data = await response.json();
1078
+ const expiresAt = getExpiresAt(response);
1079
+ return {
1080
+ keys: data,
1081
+ expiresAt
1082
+ };
1083
+ }
1084
+ async function loadJWKFromRemote({
1085
+ keyURL,
1086
+ skipJwksCache,
1087
+ kid
1088
+ }) {
1089
+ const finalKeyURL = keyURL || GOOGLE_PUBLIC_KEYS_URL;
1090
+ if (skipJwksCache || isCacheExpired() || !getFromCache(kid)) {
1091
+ const { keys, expiresAt } = await fetchPublicKeys(finalKeyURL);
1092
+ if (!keys || Object.keys(keys).length === 0) {
1093
+ throw new TokenVerificationError({
1094
+ message: `The JWKS endpoint ${finalKeyURL} returned no keys`,
1095
+ reason: TokenVerificationErrorReason.RemoteJWKFailedToLoad
1096
+ });
1097
+ }
1098
+ googleExpiresAt = expiresAt;
1099
+ Object.entries(keys).forEach(([keyId, cert2]) => {
1100
+ setInCache(keyId, cert2);
1101
+ });
1102
+ }
1103
+ const cert = getFromCache(kid);
1104
+ if (!cert) {
1105
+ getCacheValues();
1106
+ const availableKids = Object.keys(cache).sort().join(", ");
1107
+ throw new TokenVerificationError({
1108
+ message: `No public key found for kid "${kid}". Available kids: [${availableKids}]`,
608
1109
  reason: TokenVerificationErrorReason.TokenInvalid
609
1110
  });
610
1111
  }
611
- return new import_jose5.SignJWT(payload).setProtectedHeader({ alg: ALGORITHM_RS256, kid: keyId }).sign(key);
1112
+ return cert;
612
1113
  }
613
-
614
- // src/jwt/index.ts
615
- var ternDecodeJwt2 = createJwtGuard(ternDecodeJwt);
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";
1114
+ function isCacheExpired() {
1115
+ const now = Date.now();
1116
+ if (lastUpdatedAt === -1) {
1117
+ return false;
629
1118
  }
630
- let detail = typeof json.error === "string" ? json.error : json.error?.message ?? "Missing error payload";
631
- if (json.error_description) {
632
- detail += " (" + json.error_description + ")";
1119
+ const cacheAge = now - lastUpdatedAt;
1120
+ const maxCacheAge = MAX_CACHE_LAST_UPDATED_AT_SECONDS * 1e3;
1121
+ const localCacheExpired = cacheAge >= maxCacheAge;
1122
+ const googleCacheExpired = now >= googleExpiresAt;
1123
+ const isExpired = localCacheExpired || googleCacheExpired;
1124
+ if (isExpired) {
1125
+ cache = {};
633
1126
  }
634
- return detail;
635
- }
636
- async function fetchJson(url, init) {
637
- return (await fetchAny(url, init)).json();
1127
+ return isExpired;
638
1128
  }
639
- async function fetchAny(url, init) {
640
- const response = await fetch(url, init);
641
- if (!response.ok) {
642
- throw new Error(await getDetailFromResponse(response));
1129
+ function getExpiresAt(res) {
1130
+ const cacheControlHeader = res.headers.get("cache-control");
1131
+ if (!cacheControlHeader) {
1132
+ return Date.now() + DEFAULT_CACHE_DURATION;
643
1133
  }
644
- return response;
1134
+ const maxAgeMatch = cacheControlHeader.match(CACHE_CONTROL_REGEX);
1135
+ const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1], 10) : DEFAULT_CACHE_DURATION / 1e3;
1136
+ return Date.now() + maxAge * 1e3;
645
1137
  }
646
1138
 
647
- // src/auth/credential.ts
648
- var accessTokenCache = /* @__PURE__ */ new Map();
649
- async function requestAccessToken(urlString, init) {
650
- const json = await fetchJson(urlString, init);
651
- if (!json.access_token || !json.expires_in) {
652
- throw new Error("Invalid access token response");
1139
+ // src/tokens/verify.ts
1140
+ async function verifyToken(token, options) {
1141
+ const { data: decodedResult, errors } = ternDecodeJwt(token);
1142
+ if (errors) {
1143
+ return { errors };
653
1144
  }
654
- return {
655
- accessToken: json.access_token,
656
- expirationTime: Date.now() + json.expires_in * 1e3
657
- };
658
- }
659
- var ServiceAccountTokenManager = class {
660
- projectId;
661
- privateKey;
662
- clientEmail;
663
- constructor(serviceAccount) {
664
- this.projectId = serviceAccount.projectId;
665
- this.privateKey = serviceAccount.privateKey;
666
- this.clientEmail = serviceAccount.clientEmail;
1145
+ const { header } = decodedResult;
1146
+ const { kid } = header;
1147
+ if (!kid) {
1148
+ return {
1149
+ errors: [
1150
+ new TokenVerificationError({
1151
+ reason: TokenVerificationErrorReason.TokenInvalid,
1152
+ message: 'JWT "kid" header is missing.'
1153
+ })
1154
+ ]
1155
+ };
667
1156
  }
668
- fetchAccessToken = async (url) => {
669
- const token = await this.createJwt();
670
- const postData = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + token;
671
- return requestAccessToken(url, {
672
- method: "POST",
673
- headers: {
674
- "Content-Type": "application/x-www-form-urlencoded",
675
- Authorization: `Bearer ${token}`,
676
- Accept: "application/json"
677
- },
678
- body: postData
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);
1157
+ try {
1158
+ const key = options.jwtKey || await loadJWKFromRemote({ ...options, kid });
1159
+ if (!key) {
1160
+ return {
1161
+ errors: [
1162
+ new TokenVerificationError({
1163
+ reason: TokenVerificationErrorReason.TokenInvalid,
1164
+ message: `No public key found for kid "${kid}".`
1165
+ })
1166
+ ]
1167
+ };
690
1168
  }
691
- const cachedResponse = accessTokenCache.get(this.projectId);
692
- if (!cachedResponse || cachedResponse.expirationTime - Date.now() <= TOKEN_EXPIRY_THRESHOLD_MILLIS) {
693
- return this.fetchAndCacheAccessToken(url);
1169
+ return await verifyJwt(token, { ...options, key });
1170
+ } catch (error) {
1171
+ if (error instanceof TokenVerificationError) {
1172
+ return { errors: [error] };
694
1173
  }
695
- return cachedResponse;
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(" ")
1174
+ return {
1175
+ errors: [error]
712
1176
  };
713
- return ternSignJwt({
714
- payload,
715
- privateKey: this.privateKey
716
- });
717
- };
718
- };
1177
+ }
1178
+ }
719
1179
 
720
1180
  // src/auth/getauth.ts
721
1181
  var API_KEY_ERROR = "API Key is required";
@@ -731,17 +1191,8 @@ function parseFirebaseResponse(data) {
731
1191
  return data;
732
1192
  }
733
1193
  function getAuth(options) {
734
- const { apiKey, firebaseAdminConfig } = options;
735
- const firebaseApiKey = options.firebaseConfig?.apiKey;
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
- }
1194
+ const { apiKey } = options;
1195
+ const effectiveApiKey = apiKey || process.env.NEXT_PUBLIC_FIREBASE_API_KEY;
745
1196
  async function getUserData(idToken, localId) {
746
1197
  if (!effectiveApiKey) {
747
1198
  throw new Error(API_KEY_ERROR);
@@ -826,43 +1277,12 @@ function getAuth(options) {
826
1277
  auth_time: decodedCustomIdToken.data.auth_time
827
1278
  };
828
1279
  }
829
- async function exchangeAppCheckToken(idToken) {
830
- if (!credential) {
831
- return {
832
- data: null,
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
- }
1280
+ async function createAppCheckToken() {
1281
+ const adminConfig = loadAdminConfig();
1282
+ const appId = process.env.NEXT_PUBLIC_FIREBASE_APP_ID || "";
1283
+ const appCheck = getAppCheck2(adminConfig, options.tenantId);
841
1284
  try {
842
- const decoded = await verifyToken(idToken, options);
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
- }
1285
+ const appCheckResponse = await appCheck.createToken(adminConfig.projectId, appId);
866
1286
  return {
867
1287
  data: {
868
1288
  token: appCheckResponse.token,
@@ -874,16 +1294,104 @@ function getAuth(options) {
874
1294
  return { data: null, error };
875
1295
  }
876
1296
  }
1297
+ async function verifyAppCheckToken2(token) {
1298
+ const adminConfig = loadAdminConfig();
1299
+ const appCheck = getAppCheck2(adminConfig, options.tenantId);
1300
+ try {
1301
+ const decodedToken = await appCheck.verifyToken(token, adminConfig.projectId, {});
1302
+ return {
1303
+ data: decodedToken,
1304
+ error: null
1305
+ };
1306
+ } catch (error) {
1307
+ return { data: null, error };
1308
+ }
1309
+ }
877
1310
  return {
878
1311
  getUserData,
879
1312
  customForIdAndRefreshToken,
880
1313
  createCustomIdAndRefreshToken,
881
1314
  refreshExpiredIdToken,
882
- exchangeAppCheckToken
1315
+ createAppCheckToken,
1316
+ verifyAppCheckToken: verifyAppCheckToken2
1317
+ };
1318
+ }
1319
+
1320
+ // src/auth/credential.ts
1321
+ var accessTokenCache = /* @__PURE__ */ new Map();
1322
+ async function requestAccessToken(urlString, init) {
1323
+ const json = await fetchJson(urlString, init);
1324
+ if (!json.access_token || !json.expires_in) {
1325
+ throw new Error("Invalid access token response");
1326
+ }
1327
+ return {
1328
+ accessToken: json.access_token,
1329
+ expirationTime: Date.now() + json.expires_in * 1e3
883
1330
  };
884
1331
  }
1332
+ var ServiceAccountManager = class {
1333
+ projectId;
1334
+ privateKey;
1335
+ clientEmail;
1336
+ constructor(serviceAccount) {
1337
+ this.projectId = serviceAccount.projectId;
1338
+ this.privateKey = serviceAccount.privateKey;
1339
+ this.clientEmail = serviceAccount.clientEmail;
1340
+ }
1341
+ fetchAccessToken = async (url) => {
1342
+ const token = await this.createJwt();
1343
+ const postData = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + token;
1344
+ return requestAccessToken(url, {
1345
+ method: "POST",
1346
+ headers: {
1347
+ "Content-Type": "application/x-www-form-urlencoded",
1348
+ Authorization: `Bearer ${token}`,
1349
+ Accept: "application/json"
1350
+ },
1351
+ body: postData
1352
+ });
1353
+ };
1354
+ fetchAndCacheAccessToken = async (url) => {
1355
+ const accessToken = await this.fetchAccessToken(url);
1356
+ accessTokenCache.set(this.projectId, accessToken);
1357
+ return accessToken;
1358
+ };
1359
+ getAccessToken = async (refresh) => {
1360
+ const url = `https://${GOOGLE_AUTH_TOKEN_HOST}${GOOGLE_AUTH_TOKEN_PATH}`;
1361
+ if (refresh) {
1362
+ return this.fetchAndCacheAccessToken(url);
1363
+ }
1364
+ const cachedResponse = accessTokenCache.get(this.projectId);
1365
+ if (!cachedResponse || cachedResponse.expirationTime - Date.now() <= TOKEN_EXPIRY_THRESHOLD_MILLIS) {
1366
+ return this.fetchAndCacheAccessToken(url);
1367
+ }
1368
+ return cachedResponse;
1369
+ };
1370
+ createJwt = async () => {
1371
+ const iat = Math.floor(Date.now() / 1e3);
1372
+ const payload = {
1373
+ aud: GOOGLE_TOKEN_AUDIENCE,
1374
+ iat,
1375
+ exp: iat + ONE_HOUR_IN_SECONDS,
1376
+ iss: this.clientEmail,
1377
+ sub: this.clientEmail,
1378
+ scope: [
1379
+ "https://www.googleapis.com/auth/cloud-platform",
1380
+ "https://www.googleapis.com/auth/firebase.database",
1381
+ "https://www.googleapis.com/auth/firebase.messaging",
1382
+ "https://www.googleapis.com/auth/identitytoolkit",
1383
+ "https://www.googleapis.com/auth/userinfo.email"
1384
+ ].join(" ")
1385
+ };
1386
+ return ternSignJwt({
1387
+ payload,
1388
+ privateKey: this.privateKey
1389
+ });
1390
+ };
1391
+ };
885
1392
  // Annotate the CommonJS export names for ESM import in node:
886
1393
  0 && (module.exports = {
1394
+ ServiceAccountManager,
887
1395
  getAuth
888
1396
  });
889
1397
  //# sourceMappingURL=index.js.map