@tern-secure/backend 1.2.0-canary.v20251125170702 → 1.2.0-canary.v20251127221555
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/admin/index.js +3 -0
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +4 -2
- package/dist/admin/index.mjs.map +1 -1
- package/dist/auth/constants.d.ts +6 -0
- package/dist/auth/constants.d.ts.map +1 -0
- package/dist/auth/credential.d.ts +27 -0
- package/dist/auth/credential.d.ts.map +1 -0
- package/dist/auth/getauth.d.ts +1 -0
- package/dist/auth/getauth.d.ts.map +1 -1
- package/dist/auth/index.js +234 -28
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/index.mjs +3 -3
- package/dist/auth/utils.d.ts +3 -0
- package/dist/auth/utils.d.ts.map +1 -0
- package/dist/{chunk-MS6L7M3C.mjs → chunk-DJLDUW7J.mjs} +174 -12
- package/dist/chunk-DJLDUW7J.mjs.map +1 -0
- package/dist/{chunk-ASGV4MFO.mjs → chunk-GFH5CXQR.mjs} +2 -2
- package/dist/{chunk-DDUNOEIM.mjs → chunk-NXYWC6YO.mjs} +278 -116
- package/dist/chunk-NXYWC6YO.mjs.map +1 -0
- package/dist/{chunk-DFAJCSBJ.mjs → chunk-WIVOBOZR.mjs} +2 -1
- package/dist/chunk-WIVOBOZR.mjs.map +1 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/fireRestApi/createFireApi.d.ts +2 -1
- package/dist/fireRestApi/createFireApi.d.ts.map +1 -1
- package/dist/fireRestApi/endpoints/AppCheckApi.d.ts +23 -0
- package/dist/fireRestApi/endpoints/AppCheckApi.d.ts.map +1 -0
- package/dist/fireRestApi/endpoints/TokenApi.d.ts +3 -1
- package/dist/fireRestApi/endpoints/TokenApi.d.ts.map +1 -1
- package/dist/fireRestApi/endpoints/UserData.d.ts.map +1 -1
- package/dist/fireRestApi/endpoints/index.d.ts +1 -0
- package/dist/fireRestApi/endpoints/index.d.ts.map +1 -1
- package/dist/fireRestApi/request.d.ts.map +1 -1
- package/dist/index.js +390 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +159 -12
- package/dist/index.mjs.map +1 -1
- package/dist/jwt/index.d.ts +1 -0
- package/dist/jwt/index.d.ts.map +1 -1
- package/dist/jwt/index.js +51 -19
- package/dist/jwt/index.js.map +1 -1
- package/dist/jwt/index.mjs +8 -132
- package/dist/jwt/index.mjs.map +1 -1
- package/dist/jwt/signJwt.d.ts +8 -0
- package/dist/jwt/signJwt.d.ts.map +1 -1
- package/dist/jwt/verifyJwt.d.ts.map +1 -1
- package/dist/tokens/authstate.d.ts.map +1 -1
- package/dist/tokens/c-authenticateRequestProcessor.d.ts +1 -0
- package/dist/tokens/c-authenticateRequestProcessor.d.ts.map +1 -1
- package/dist/tokens/request.d.ts.map +1 -1
- package/dist/tokens/types.d.ts +2 -1
- package/dist/tokens/types.d.ts.map +1 -1
- package/dist/tokens/verify.d.ts +2 -2
- package/dist/tokens/verify.d.ts.map +1 -1
- package/dist/utils/admin-init.d.ts +1 -0
- package/dist/utils/admin-init.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-DDUNOEIM.mjs.map +0 -1
- package/dist/chunk-DFAJCSBJ.mjs.map +0 -1
- package/dist/chunk-MS6L7M3C.mjs.map +0 -1
- /package/dist/{chunk-ASGV4MFO.mjs.map → chunk-GFH5CXQR.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -78,6 +78,7 @@ var QueryParameters = {
|
|
|
78
78
|
};
|
|
79
79
|
var Headers2 = {
|
|
80
80
|
Accept: "accept",
|
|
81
|
+
AppCheckToken: "x-firebase-appcheck",
|
|
81
82
|
AuthMessage: "x-ternsecure-auth-message",
|
|
82
83
|
Authorization: "authorization",
|
|
83
84
|
AuthReason: "x-ternsecure-auth-reason",
|
|
@@ -371,6 +372,84 @@ var AbstractAPI = class {
|
|
|
371
372
|
}
|
|
372
373
|
};
|
|
373
374
|
|
|
375
|
+
// src/fireRestApi/endpoints/AppCheckApi.ts
|
|
376
|
+
function getSdkVersion() {
|
|
377
|
+
return "12.7.0";
|
|
378
|
+
}
|
|
379
|
+
var FIREBASE_APP_CHECK_CONFIG_HEADERS = {
|
|
380
|
+
"X-Firebase-Client": `fire-admin-node/${getSdkVersion()}`
|
|
381
|
+
};
|
|
382
|
+
var AppCheckApi = class extends AbstractAPI {
|
|
383
|
+
async exchangeCustomToken(params) {
|
|
384
|
+
const { projectId, appId, customToken, accessToken, limitedUse = false } = params;
|
|
385
|
+
if (!projectId || !appId) {
|
|
386
|
+
throw new Error("Project ID and App ID are required for App Check token exchange");
|
|
387
|
+
}
|
|
388
|
+
const endpoint = `https://firebaseappcheck.googleapis.com/v1beta/projects/${projectId}/apps/${appId}:exchangeCustomToken`;
|
|
389
|
+
const headers = {
|
|
390
|
+
"Content-Type": "application/json",
|
|
391
|
+
"Authorization": `Bearer ${accessToken}`
|
|
392
|
+
};
|
|
393
|
+
const body = {
|
|
394
|
+
customToken,
|
|
395
|
+
limitedUse
|
|
396
|
+
};
|
|
397
|
+
try {
|
|
398
|
+
const response = await fetch(endpoint, {
|
|
399
|
+
method: "POST",
|
|
400
|
+
headers,
|
|
401
|
+
body: JSON.stringify(body)
|
|
402
|
+
});
|
|
403
|
+
if (!response.ok) {
|
|
404
|
+
const errorText = await response.text();
|
|
405
|
+
throw new Error(`App Check token exchange failed: ${response.status} ${errorText}`);
|
|
406
|
+
}
|
|
407
|
+
const data = await response.json();
|
|
408
|
+
return {
|
|
409
|
+
token: data.token,
|
|
410
|
+
ttl: data.ttl
|
|
411
|
+
};
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.warn("[ternsecure - appcheck api]unexpected error:", error);
|
|
414
|
+
throw error;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
async exchangeDebugToken(params) {
|
|
418
|
+
const { projectId, appId, customToken, accessToken, limitedUse = false } = params;
|
|
419
|
+
if (!projectId || !appId) {
|
|
420
|
+
throw new Error("Project ID and App ID are required for App Check token exchange");
|
|
421
|
+
}
|
|
422
|
+
const endpoint = `https://firebaseappcheck.googleapis.com/v1beta/projects/${projectId}/apps/${appId}:exchangeDebugToken`;
|
|
423
|
+
const headers = {
|
|
424
|
+
...FIREBASE_APP_CHECK_CONFIG_HEADERS,
|
|
425
|
+
"Authorization": `Bearer ${accessToken}`
|
|
426
|
+
};
|
|
427
|
+
const body = {
|
|
428
|
+
customToken,
|
|
429
|
+
limitedUse
|
|
430
|
+
};
|
|
431
|
+
try {
|
|
432
|
+
const response = await fetch(endpoint, {
|
|
433
|
+
method: "POST",
|
|
434
|
+
headers,
|
|
435
|
+
body: JSON.stringify(body)
|
|
436
|
+
});
|
|
437
|
+
if (!response.ok) {
|
|
438
|
+
const errorText = await response.text();
|
|
439
|
+
throw new Error(`App Check token exchange failed: ${response.status} ${errorText}`);
|
|
440
|
+
}
|
|
441
|
+
const data = await response.json();
|
|
442
|
+
return {
|
|
443
|
+
token: data.token,
|
|
444
|
+
ttl: data.ttl
|
|
445
|
+
};
|
|
446
|
+
} catch (error) {
|
|
447
|
+
console.warn("[ternsecure - appcheck api]unexpected error:", error);
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
|
|
374
453
|
// src/fireRestApi/endpoints/EmailApi.ts
|
|
375
454
|
var EmailApi = class extends AbstractAPI {
|
|
376
455
|
async verifyEmailVerification(apiKey, params) {
|
|
@@ -488,11 +567,14 @@ var SignUpApi = class extends AbstractAPI {
|
|
|
488
567
|
var TokenApi = class extends AbstractAPI {
|
|
489
568
|
async refreshToken(apiKey, params) {
|
|
490
569
|
this.requireApiKey(apiKey);
|
|
491
|
-
const { refresh_token, request_origin, ...restParams } = params;
|
|
570
|
+
const { refresh_token, request_origin, app_check_token, ...restParams } = params;
|
|
492
571
|
const headers = {};
|
|
493
572
|
if (request_origin) {
|
|
494
573
|
headers["Referer"] = request_origin;
|
|
495
574
|
}
|
|
575
|
+
if (app_check_token) {
|
|
576
|
+
headers["X-Firebase-AppCheck"] = app_check_token;
|
|
577
|
+
}
|
|
496
578
|
const bodyParams = {
|
|
497
579
|
grant_type: "refresh_token",
|
|
498
580
|
refresh_token,
|
|
@@ -512,13 +594,23 @@ var TokenApi = class extends AbstractAPI {
|
|
|
512
594
|
if (options?.referer) {
|
|
513
595
|
headers["Referer"] = options.referer;
|
|
514
596
|
}
|
|
515
|
-
|
|
597
|
+
if (options?.appCheckToken) {
|
|
598
|
+
headers["X-Firebase-AppCheck"] = options.appCheckToken;
|
|
599
|
+
}
|
|
600
|
+
const response = await this.request({
|
|
516
601
|
endpoint: "signInWithCustomToken",
|
|
517
602
|
method: "POST",
|
|
518
603
|
apiKey,
|
|
519
604
|
bodyParams: params,
|
|
520
605
|
headerParams: headers
|
|
521
606
|
});
|
|
607
|
+
if (response.errors) {
|
|
608
|
+
throw new Error(response.errors[0].message);
|
|
609
|
+
}
|
|
610
|
+
if (!response.data) {
|
|
611
|
+
throw new Error("No data received from Firebase token exchange");
|
|
612
|
+
}
|
|
613
|
+
return response.data;
|
|
522
614
|
}
|
|
523
615
|
};
|
|
524
616
|
|
|
@@ -650,8 +742,18 @@ function createRequest(options) {
|
|
|
650
742
|
...body
|
|
651
743
|
});
|
|
652
744
|
}
|
|
653
|
-
const isJSONResponse = res?.headers && res.headers?.get(constants.Headers.ContentType)
|
|
654
|
-
|
|
745
|
+
const isJSONResponse = res?.headers && res.headers?.get(constants.Headers.ContentType)?.includes(constants.ContentTypes.Json);
|
|
746
|
+
let responseBody;
|
|
747
|
+
try {
|
|
748
|
+
const text = await res.text();
|
|
749
|
+
try {
|
|
750
|
+
responseBody = JSON.parse(text);
|
|
751
|
+
} catch {
|
|
752
|
+
responseBody = text;
|
|
753
|
+
}
|
|
754
|
+
} catch (e) {
|
|
755
|
+
responseBody = null;
|
|
756
|
+
}
|
|
655
757
|
if (!res.ok) {
|
|
656
758
|
return {
|
|
657
759
|
data: null,
|
|
@@ -732,6 +834,7 @@ function parseError(error) {
|
|
|
732
834
|
function createFireApi(options) {
|
|
733
835
|
const request = createRequest(options);
|
|
734
836
|
return {
|
|
837
|
+
appCheck: new AppCheckApi(request),
|
|
735
838
|
email: new EmailApi(request),
|
|
736
839
|
password: new PasswordApi(request),
|
|
737
840
|
signIn: new SignInApi(request),
|
|
@@ -1094,33 +1197,44 @@ async function verifySignature(jwt, key) {
|
|
|
1094
1197
|
}
|
|
1095
1198
|
}
|
|
1096
1199
|
function ternDecodeJwt(token) {
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1200
|
+
try {
|
|
1201
|
+
const header = (0, import_jose3.decodeProtectedHeader)(token);
|
|
1202
|
+
const payload = (0, import_jose3.decodeJwt)(token);
|
|
1203
|
+
const tokenParts = (token || "").toString().split(".");
|
|
1204
|
+
if (tokenParts.length !== 3) {
|
|
1205
|
+
return {
|
|
1206
|
+
errors: [
|
|
1207
|
+
new TokenVerificationError({
|
|
1208
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
1209
|
+
message: "Invalid JWT format"
|
|
1210
|
+
})
|
|
1211
|
+
]
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
const [rawHeader, rawPayload, rawSignature] = tokenParts;
|
|
1215
|
+
const signature = base64url.parse(rawSignature, { loose: true });
|
|
1216
|
+
const data = {
|
|
1217
|
+
header,
|
|
1218
|
+
payload,
|
|
1219
|
+
signature,
|
|
1220
|
+
raw: {
|
|
1221
|
+
header: rawHeader,
|
|
1222
|
+
payload: rawPayload,
|
|
1223
|
+
signature: rawSignature,
|
|
1224
|
+
text: token
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
return { data };
|
|
1228
|
+
} catch (error) {
|
|
1101
1229
|
return {
|
|
1102
1230
|
errors: [
|
|
1103
1231
|
new TokenVerificationError({
|
|
1104
1232
|
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
1105
|
-
message: "Invalid
|
|
1233
|
+
message: `${error.message || "Invalid Token or Protected Header formatting"} (Token length: ${token?.length}, First 10 chars: ${token?.substring(0, 10)}...)`
|
|
1106
1234
|
})
|
|
1107
1235
|
]
|
|
1108
1236
|
};
|
|
1109
1237
|
}
|
|
1110
|
-
const [rawHeader, rawPayload, rawSignature] = tokenParts;
|
|
1111
|
-
const signature = base64url.parse(rawSignature, { loose: true });
|
|
1112
|
-
const data = {
|
|
1113
|
-
header,
|
|
1114
|
-
payload,
|
|
1115
|
-
signature,
|
|
1116
|
-
raw: {
|
|
1117
|
-
header: rawHeader,
|
|
1118
|
-
payload: rawPayload,
|
|
1119
|
-
signature: rawSignature,
|
|
1120
|
-
text: token
|
|
1121
|
-
}
|
|
1122
|
-
};
|
|
1123
|
-
return { data };
|
|
1124
1238
|
}
|
|
1125
1239
|
async function verifyJwt(token, options) {
|
|
1126
1240
|
const { key } = options;
|
|
@@ -1278,6 +1392,143 @@ async function verifyToken(token, options) {
|
|
|
1278
1392
|
}
|
|
1279
1393
|
}
|
|
1280
1394
|
|
|
1395
|
+
// src/jwt/guardReturn.ts
|
|
1396
|
+
function createJwtGuard(decodedFn) {
|
|
1397
|
+
return (...args) => {
|
|
1398
|
+
const { data, errors } = decodedFn(...args);
|
|
1399
|
+
if (errors) {
|
|
1400
|
+
throw errors[0];
|
|
1401
|
+
}
|
|
1402
|
+
return data;
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// src/jwt/jwt.ts
|
|
1407
|
+
var import_jose4 = require("jose");
|
|
1408
|
+
|
|
1409
|
+
// src/jwt/signJwt.ts
|
|
1410
|
+
var import_jose5 = require("jose");
|
|
1411
|
+
var ALGORITHM_RS256 = "RS256";
|
|
1412
|
+
async function ternSignJwt(opts) {
|
|
1413
|
+
const { payload, privateKey, keyId } = opts;
|
|
1414
|
+
let key;
|
|
1415
|
+
try {
|
|
1416
|
+
key = await (0, import_jose5.importPKCS8)(privateKey, ALGORITHM_RS256);
|
|
1417
|
+
} catch (error) {
|
|
1418
|
+
throw new TokenVerificationError({
|
|
1419
|
+
message: `Failed to import private key: ${error.message}`,
|
|
1420
|
+
reason: TokenVerificationErrorReason.TokenInvalid
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
return new import_jose5.SignJWT(payload).setProtectedHeader({ alg: ALGORITHM_RS256, kid: keyId }).sign(key);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// src/jwt/index.ts
|
|
1427
|
+
var ternDecodeJwt2 = createJwtGuard(ternDecodeJwt);
|
|
1428
|
+
|
|
1429
|
+
// src/auth/constants.ts
|
|
1430
|
+
var TOKEN_EXPIRY_THRESHOLD_MILLIS = 5 * 60 * 1e3;
|
|
1431
|
+
var GOOGLE_TOKEN_AUDIENCE = "https://accounts.google.com/o/oauth2/token";
|
|
1432
|
+
var GOOGLE_AUTH_TOKEN_HOST = "accounts.google.com";
|
|
1433
|
+
var GOOGLE_AUTH_TOKEN_PATH = "/o/oauth2/token";
|
|
1434
|
+
var ONE_HOUR_IN_SECONDS = 60 * 60;
|
|
1435
|
+
|
|
1436
|
+
// src/auth/utils.ts
|
|
1437
|
+
async function getDetailFromResponse(response) {
|
|
1438
|
+
const json = await response.json();
|
|
1439
|
+
if (!json) {
|
|
1440
|
+
return "Missing error payload";
|
|
1441
|
+
}
|
|
1442
|
+
let detail = typeof json.error === "string" ? json.error : json.error?.message ?? "Missing error payload";
|
|
1443
|
+
if (json.error_description) {
|
|
1444
|
+
detail += " (" + json.error_description + ")";
|
|
1445
|
+
}
|
|
1446
|
+
return detail;
|
|
1447
|
+
}
|
|
1448
|
+
async function fetchJson(url, init) {
|
|
1449
|
+
return (await fetchAny(url, init)).json();
|
|
1450
|
+
}
|
|
1451
|
+
async function fetchAny(url, init) {
|
|
1452
|
+
const response = await fetch(url, init);
|
|
1453
|
+
if (!response.ok) {
|
|
1454
|
+
throw new Error(await getDetailFromResponse(response));
|
|
1455
|
+
}
|
|
1456
|
+
return response;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// src/auth/credential.ts
|
|
1460
|
+
var accessTokenCache = /* @__PURE__ */ new Map();
|
|
1461
|
+
async function requestAccessToken(urlString, init) {
|
|
1462
|
+
const json = await fetchJson(urlString, init);
|
|
1463
|
+
if (!json.access_token || !json.expires_in) {
|
|
1464
|
+
throw new Error("Invalid access token response");
|
|
1465
|
+
}
|
|
1466
|
+
return {
|
|
1467
|
+
accessToken: json.access_token,
|
|
1468
|
+
expirationTime: Date.now() + json.expires_in * 1e3
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
var ServiceAccountTokenManager = class {
|
|
1472
|
+
projectId;
|
|
1473
|
+
privateKey;
|
|
1474
|
+
clientEmail;
|
|
1475
|
+
constructor(serviceAccount) {
|
|
1476
|
+
this.projectId = serviceAccount.projectId;
|
|
1477
|
+
this.privateKey = serviceAccount.privateKey;
|
|
1478
|
+
this.clientEmail = serviceAccount.clientEmail;
|
|
1479
|
+
}
|
|
1480
|
+
fetchAccessToken = async (url) => {
|
|
1481
|
+
const token = await this.createJwt();
|
|
1482
|
+
const postData = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + token;
|
|
1483
|
+
return requestAccessToken(url, {
|
|
1484
|
+
method: "POST",
|
|
1485
|
+
headers: {
|
|
1486
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1487
|
+
Authorization: `Bearer ${token}`,
|
|
1488
|
+
Accept: "application/json"
|
|
1489
|
+
},
|
|
1490
|
+
body: postData
|
|
1491
|
+
});
|
|
1492
|
+
};
|
|
1493
|
+
fetchAndCacheAccessToken = async (url) => {
|
|
1494
|
+
const accessToken = await this.fetchAccessToken(url);
|
|
1495
|
+
accessTokenCache.set(this.projectId, accessToken);
|
|
1496
|
+
return accessToken;
|
|
1497
|
+
};
|
|
1498
|
+
getAccessToken = async (refresh) => {
|
|
1499
|
+
const url = `https://${GOOGLE_AUTH_TOKEN_HOST}${GOOGLE_AUTH_TOKEN_PATH}`;
|
|
1500
|
+
if (refresh) {
|
|
1501
|
+
return this.fetchAndCacheAccessToken(url);
|
|
1502
|
+
}
|
|
1503
|
+
const cachedResponse = accessTokenCache.get(this.projectId);
|
|
1504
|
+
if (!cachedResponse || cachedResponse.expirationTime - Date.now() <= TOKEN_EXPIRY_THRESHOLD_MILLIS) {
|
|
1505
|
+
return this.fetchAndCacheAccessToken(url);
|
|
1506
|
+
}
|
|
1507
|
+
return cachedResponse;
|
|
1508
|
+
};
|
|
1509
|
+
createJwt = async () => {
|
|
1510
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
1511
|
+
const payload = {
|
|
1512
|
+
aud: GOOGLE_TOKEN_AUDIENCE,
|
|
1513
|
+
iat,
|
|
1514
|
+
exp: iat + ONE_HOUR_IN_SECONDS,
|
|
1515
|
+
iss: this.clientEmail,
|
|
1516
|
+
sub: this.clientEmail,
|
|
1517
|
+
scope: [
|
|
1518
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
1519
|
+
"https://www.googleapis.com/auth/firebase.database",
|
|
1520
|
+
"https://www.googleapis.com/auth/firebase.messaging",
|
|
1521
|
+
"https://www.googleapis.com/auth/identitytoolkit",
|
|
1522
|
+
"https://www.googleapis.com/auth/userinfo.email"
|
|
1523
|
+
].join(" ")
|
|
1524
|
+
};
|
|
1525
|
+
return ternSignJwt({
|
|
1526
|
+
payload,
|
|
1527
|
+
privateKey: this.privateKey
|
|
1528
|
+
});
|
|
1529
|
+
};
|
|
1530
|
+
};
|
|
1531
|
+
|
|
1281
1532
|
// src/auth/getauth.ts
|
|
1282
1533
|
var API_KEY_ERROR = "API Key is required";
|
|
1283
1534
|
var NO_DATA_ERROR = "No token data received";
|
|
@@ -1292,9 +1543,17 @@ function parseFirebaseResponse(data) {
|
|
|
1292
1543
|
return data;
|
|
1293
1544
|
}
|
|
1294
1545
|
function getAuth(options) {
|
|
1295
|
-
const { apiKey } = options;
|
|
1546
|
+
const { apiKey, firebaseAdminConfig } = options;
|
|
1296
1547
|
const firebaseApiKey = options.firebaseConfig?.apiKey;
|
|
1297
1548
|
const effectiveApiKey = apiKey || firebaseApiKey;
|
|
1549
|
+
let credential = null;
|
|
1550
|
+
if (firebaseAdminConfig?.projectId && firebaseAdminConfig?.privateKey && firebaseAdminConfig?.clientEmail) {
|
|
1551
|
+
credential = new ServiceAccountTokenManager({
|
|
1552
|
+
projectId: firebaseAdminConfig.projectId,
|
|
1553
|
+
privateKey: firebaseAdminConfig.privateKey,
|
|
1554
|
+
clientEmail: firebaseAdminConfig.clientEmail
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1298
1557
|
async function getUserData(idToken, localId) {
|
|
1299
1558
|
if (!effectiveApiKey) {
|
|
1300
1559
|
throw new Error(API_KEY_ERROR);
|
|
@@ -1336,23 +1595,23 @@ function getAuth(options) {
|
|
|
1336
1595
|
if (!effectiveApiKey) {
|
|
1337
1596
|
throw new Error("API Key is required to create custom token");
|
|
1338
1597
|
}
|
|
1339
|
-
const
|
|
1598
|
+
const data = await options.apiClient?.tokens.exchangeCustomForIdAndRefreshTokens(
|
|
1340
1599
|
effectiveApiKey,
|
|
1341
1600
|
{
|
|
1342
1601
|
token: customToken,
|
|
1343
1602
|
returnSecureToken: true
|
|
1344
1603
|
},
|
|
1345
1604
|
{
|
|
1346
|
-
referer: opts.referer
|
|
1605
|
+
referer: opts.referer,
|
|
1606
|
+
appCheckToken: opts.appCheckToken
|
|
1347
1607
|
}
|
|
1348
1608
|
);
|
|
1349
|
-
if (!
|
|
1609
|
+
if (!data) {
|
|
1350
1610
|
throw new Error("No data received from Firebase token exchange");
|
|
1351
1611
|
}
|
|
1352
|
-
const parsedData = parseFirebaseResponse(response.data);
|
|
1353
1612
|
return {
|
|
1354
|
-
idToken:
|
|
1355
|
-
refreshToken:
|
|
1613
|
+
idToken: data.idToken,
|
|
1614
|
+
refreshToken: data.refreshToken
|
|
1356
1615
|
};
|
|
1357
1616
|
}
|
|
1358
1617
|
async function createCustomIdAndRefreshToken(idToken, opts) {
|
|
@@ -1366,7 +1625,8 @@ function getAuth(options) {
|
|
|
1366
1625
|
source_sign_in_provider: data.firebase.sign_in_provider
|
|
1367
1626
|
});
|
|
1368
1627
|
const idAndRefreshTokens = await customForIdAndRefreshToken(customToken, {
|
|
1369
|
-
referer: opts.referer
|
|
1628
|
+
referer: opts.referer,
|
|
1629
|
+
appCheckToken: opts.appCheckToken
|
|
1370
1630
|
});
|
|
1371
1631
|
const decodedCustomIdToken = await verifyToken(idAndRefreshTokens.idToken, options);
|
|
1372
1632
|
if (decodedCustomIdToken.errors) {
|
|
@@ -1378,11 +1638,60 @@ function getAuth(options) {
|
|
|
1378
1638
|
auth_time: decodedCustomIdToken.data.auth_time
|
|
1379
1639
|
};
|
|
1380
1640
|
}
|
|
1641
|
+
async function exchangeAppCheckToken(idToken) {
|
|
1642
|
+
if (!credential) {
|
|
1643
|
+
return {
|
|
1644
|
+
data: null,
|
|
1645
|
+
error: new Error(
|
|
1646
|
+
"Firebase Admin config must be provided to exchange App Check tokens."
|
|
1647
|
+
)
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
if (!effectiveApiKey) {
|
|
1651
|
+
return { data: null, error: new Error(API_KEY_ERROR) };
|
|
1652
|
+
}
|
|
1653
|
+
try {
|
|
1654
|
+
const decoded = await verifyToken(idToken, options);
|
|
1655
|
+
if (decoded.errors) {
|
|
1656
|
+
return { data: null, error: decoded.errors[0] };
|
|
1657
|
+
}
|
|
1658
|
+
const customToken = await createCustomToken(decoded.data.uid, {
|
|
1659
|
+
emailVerified: decoded.data.email_verified,
|
|
1660
|
+
source_sign_in_provider: decoded.data.firebase.sign_in_provider
|
|
1661
|
+
});
|
|
1662
|
+
const projectId = options.firebaseConfig?.projectId;
|
|
1663
|
+
const appId = options.firebaseConfig?.appId;
|
|
1664
|
+
if (!projectId || !appId) {
|
|
1665
|
+
return { data: null, error: new Error("Project ID and App ID are required for App Check") };
|
|
1666
|
+
}
|
|
1667
|
+
const { accessToken } = await credential.getAccessToken();
|
|
1668
|
+
const appCheckResponse = await options.apiClient?.appCheck.exchangeCustomToken({
|
|
1669
|
+
accessToken,
|
|
1670
|
+
projectId,
|
|
1671
|
+
appId,
|
|
1672
|
+
customToken,
|
|
1673
|
+
limitedUse: false
|
|
1674
|
+
});
|
|
1675
|
+
if (!appCheckResponse?.token) {
|
|
1676
|
+
return { data: null, error: new Error("Failed to exchange for App Check token") };
|
|
1677
|
+
}
|
|
1678
|
+
return {
|
|
1679
|
+
data: {
|
|
1680
|
+
token: appCheckResponse.token,
|
|
1681
|
+
ttl: appCheckResponse.ttl
|
|
1682
|
+
},
|
|
1683
|
+
error: null
|
|
1684
|
+
};
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
return { data: null, error };
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1381
1689
|
return {
|
|
1382
1690
|
getUserData,
|
|
1383
1691
|
customForIdAndRefreshToken,
|
|
1384
1692
|
createCustomIdAndRefreshToken,
|
|
1385
|
-
refreshExpiredIdToken
|
|
1693
|
+
refreshExpiredIdToken,
|
|
1694
|
+
exchangeAppCheckToken
|
|
1386
1695
|
};
|
|
1387
1696
|
}
|
|
1388
1697
|
|
|
@@ -1413,6 +1722,7 @@ var RequestProcessorContext = class {
|
|
|
1413
1722
|
this.userAgent = this.getHeader(constants.Headers.UserAgent);
|
|
1414
1723
|
this.secFetchDest = this.getHeader(constants.Headers.SecFetchDest);
|
|
1415
1724
|
this.accept = this.getHeader(constants.Headers.Accept);
|
|
1725
|
+
this.appCheckToken = this.getHeader(constants.Headers.AppCheckToken);
|
|
1416
1726
|
}
|
|
1417
1727
|
initCookieValues() {
|
|
1418
1728
|
const isProduction = process.env.NODE_ENV === "production";
|
|
@@ -1476,7 +1786,7 @@ function isRequestForRefresh(error, context, request) {
|
|
|
1476
1786
|
}
|
|
1477
1787
|
async function authenticateRequest(request, options) {
|
|
1478
1788
|
const context = createRequestProcessor(createTernSecureRequest(request), options);
|
|
1479
|
-
const { refreshTokenInCookie } = context;
|
|
1789
|
+
const { refreshTokenInCookie, appCheckToken } = context;
|
|
1480
1790
|
const { refreshExpiredIdToken } = getAuth(options);
|
|
1481
1791
|
function checkSessionTimeout(authTimeValue) {
|
|
1482
1792
|
const defaultMaxAgeSeconds = convertToSeconds("5 days");
|
|
@@ -1499,7 +1809,8 @@ async function authenticateRequest(request, options) {
|
|
|
1499
1809
|
};
|
|
1500
1810
|
}
|
|
1501
1811
|
return await refreshExpiredIdToken(refreshTokenInCookie, {
|
|
1502
|
-
referer: context.ternUrl.origin
|
|
1812
|
+
referer: context.ternUrl.origin,
|
|
1813
|
+
appCheckToken
|
|
1503
1814
|
});
|
|
1504
1815
|
}
|
|
1505
1816
|
async function handleRefresh() {
|
|
@@ -1601,7 +1912,24 @@ async function authenticateRequest(request, options) {
|
|
|
1601
1912
|
if (errors) {
|
|
1602
1913
|
throw errors[0];
|
|
1603
1914
|
}
|
|
1604
|
-
const
|
|
1915
|
+
const { exchangeAppCheckToken } = getAuth(options);
|
|
1916
|
+
let appCheckTokenValue;
|
|
1917
|
+
try {
|
|
1918
|
+
const idToken = context.idTokenInCookie || "";
|
|
1919
|
+
const appCheckResult = await exchangeAppCheckToken(idToken);
|
|
1920
|
+
console.log("[authenticateRequest] App Check exchange result:", appCheckResult);
|
|
1921
|
+
if (appCheckResult.data?.token) {
|
|
1922
|
+
appCheckTokenValue = appCheckResult.data.token;
|
|
1923
|
+
}
|
|
1924
|
+
} catch (error) {
|
|
1925
|
+
console.warn("App Check token exchange failed:", error);
|
|
1926
|
+
}
|
|
1927
|
+
const headers = new Headers();
|
|
1928
|
+
headers.set(
|
|
1929
|
+
constants.Headers.AppCheckToken,
|
|
1930
|
+
appCheckTokenValue || ""
|
|
1931
|
+
);
|
|
1932
|
+
const signedInRequestState = signedIn(context, data, headers, context.idTokenInCookie);
|
|
1605
1933
|
return signedInRequestState;
|
|
1606
1934
|
} catch (err) {
|
|
1607
1935
|
return handleError(err, "cookie");
|
|
@@ -1615,7 +1943,23 @@ async function authenticateRequest(request, options) {
|
|
|
1615
1943
|
if (errors) {
|
|
1616
1944
|
throw errors[0];
|
|
1617
1945
|
}
|
|
1618
|
-
const
|
|
1946
|
+
const { exchangeAppCheckToken } = getAuth(options);
|
|
1947
|
+
let appCheckTokenValue;
|
|
1948
|
+
try {
|
|
1949
|
+
const token = sessionTokenInHeader || "";
|
|
1950
|
+
const appCheckResult = await exchangeAppCheckToken(token);
|
|
1951
|
+
if (appCheckResult.data?.token) {
|
|
1952
|
+
appCheckTokenValue = appCheckResult.data.token;
|
|
1953
|
+
}
|
|
1954
|
+
} catch (error) {
|
|
1955
|
+
console.warn("App Check token exchange failed:", error);
|
|
1956
|
+
}
|
|
1957
|
+
const headers = new Headers();
|
|
1958
|
+
headers.set(
|
|
1959
|
+
constants.Headers.AppCheckToken,
|
|
1960
|
+
appCheckTokenValue || ""
|
|
1961
|
+
);
|
|
1962
|
+
const signedInRequestState = signedIn(context, data, headers, sessionTokenInHeader);
|
|
1619
1963
|
return signedInRequestState;
|
|
1620
1964
|
} catch (err) {
|
|
1621
1965
|
return handleError(err, "header");
|
|
@@ -1629,6 +1973,16 @@ async function authenticateRequest(request, options) {
|
|
|
1629
1973
|
if (isRequestForRefresh(err, context, request)) {
|
|
1630
1974
|
const { data, error } = await handleRefresh();
|
|
1631
1975
|
if (data) {
|
|
1976
|
+
const { exchangeAppCheckToken } = getAuth(options);
|
|
1977
|
+
let appCheckTokenValue;
|
|
1978
|
+
try {
|
|
1979
|
+
const appCheckResult = await exchangeAppCheckToken(data.token);
|
|
1980
|
+
if (appCheckResult.data?.token) {
|
|
1981
|
+
appCheckTokenValue = appCheckResult.data.token;
|
|
1982
|
+
}
|
|
1983
|
+
} catch (error2) {
|
|
1984
|
+
console.warn("App Check token exchange failed in error handler:", error2);
|
|
1985
|
+
}
|
|
1632
1986
|
return signedIn(context, data.decoded, data.headers, data.token);
|
|
1633
1987
|
}
|
|
1634
1988
|
if (error?.cause?.reason) {
|