@tern-secure/backend 1.2.0-canary.v20251108045933 → 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.d.ts +1 -0
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +40 -0
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +40 -2
- package/dist/admin/index.mjs.map +1 -1
- package/dist/admin/user.d.ts +16 -0
- package/dist/admin/user.d.ts.map +1 -0
- 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 +4 -2
- package/dist/fireRestApi/createFireApi.d.ts.map +1 -1
- package/dist/fireRestApi/endpointUrl.d.ts +2 -1
- package/dist/fireRestApi/endpointUrl.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/SignInApi.d.ts +11 -0
- package/dist/fireRestApi/endpoints/SignInApi.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 +2 -0
- package/dist/fireRestApi/endpoints/index.d.ts.map +1 -1
- package/dist/fireRestApi/request.d.ts.map +1 -1
- package/dist/fireRestApi/resources/EmailAddress.d.ts +7 -0
- package/dist/fireRestApi/resources/EmailAddress.d.ts.map +1 -0
- package/dist/fireRestApi/resources/JSON.d.ts +4 -0
- package/dist/fireRestApi/resources/JSON.d.ts.map +1 -1
- package/dist/index.js +421 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +190 -19
- 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) {
|
|
@@ -424,6 +503,30 @@ var PasswordApi = class extends AbstractAPI {
|
|
|
424
503
|
}
|
|
425
504
|
};
|
|
426
505
|
|
|
506
|
+
// src/fireRestApi/endpoints/SignInApi.ts
|
|
507
|
+
var SignInApi = class extends AbstractAPI {
|
|
508
|
+
async resetPasswordEmail(apiKey, params) {
|
|
509
|
+
try {
|
|
510
|
+
this.requireApiKey(apiKey);
|
|
511
|
+
const { ...restParams } = params;
|
|
512
|
+
const response = await this.request({
|
|
513
|
+
endpoint: "sendOobCode",
|
|
514
|
+
method: "POST",
|
|
515
|
+
apiKey,
|
|
516
|
+
bodyParams: restParams
|
|
517
|
+
});
|
|
518
|
+
if (response.errors) {
|
|
519
|
+
const errorMessage = response.errors[0]?.message || "Failed to send reset password email";
|
|
520
|
+
throw new Error(errorMessage);
|
|
521
|
+
}
|
|
522
|
+
return response.data;
|
|
523
|
+
} catch (error) {
|
|
524
|
+
const contextualMessage = `Failed to send reset password email: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
525
|
+
throw new Error(contextualMessage);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
427
530
|
// src/fireRestApi/endpoints/SignInTokenApi.ts
|
|
428
531
|
var SignInTokenApi = class extends AbstractAPI {
|
|
429
532
|
async createCustomToken(apiKey, params) {
|
|
@@ -464,11 +567,14 @@ var SignUpApi = class extends AbstractAPI {
|
|
|
464
567
|
var TokenApi = class extends AbstractAPI {
|
|
465
568
|
async refreshToken(apiKey, params) {
|
|
466
569
|
this.requireApiKey(apiKey);
|
|
467
|
-
const { refresh_token, request_origin, ...restParams } = params;
|
|
570
|
+
const { refresh_token, request_origin, app_check_token, ...restParams } = params;
|
|
468
571
|
const headers = {};
|
|
469
572
|
if (request_origin) {
|
|
470
573
|
headers["Referer"] = request_origin;
|
|
471
574
|
}
|
|
575
|
+
if (app_check_token) {
|
|
576
|
+
headers["X-Firebase-AppCheck"] = app_check_token;
|
|
577
|
+
}
|
|
472
578
|
const bodyParams = {
|
|
473
579
|
grant_type: "refresh_token",
|
|
474
580
|
refresh_token,
|
|
@@ -488,13 +594,23 @@ var TokenApi = class extends AbstractAPI {
|
|
|
488
594
|
if (options?.referer) {
|
|
489
595
|
headers["Referer"] = options.referer;
|
|
490
596
|
}
|
|
491
|
-
|
|
597
|
+
if (options?.appCheckToken) {
|
|
598
|
+
headers["X-Firebase-AppCheck"] = options.appCheckToken;
|
|
599
|
+
}
|
|
600
|
+
const response = await this.request({
|
|
492
601
|
endpoint: "signInWithCustomToken",
|
|
493
602
|
method: "POST",
|
|
494
603
|
apiKey,
|
|
495
604
|
bodyParams: params,
|
|
496
605
|
headerParams: headers
|
|
497
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;
|
|
498
614
|
}
|
|
499
615
|
};
|
|
500
616
|
|
|
@@ -556,6 +672,9 @@ var signInWithPassword = (apiKey) => {
|
|
|
556
672
|
var signUpEndpoint = (apiKey) => {
|
|
557
673
|
return `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${apiKey}`;
|
|
558
674
|
};
|
|
675
|
+
var sendOobCode = (apiKey) => {
|
|
676
|
+
return `https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode?key=${apiKey}`;
|
|
677
|
+
};
|
|
559
678
|
var getCustomTokenEndpoint = (apiKey) => {
|
|
560
679
|
if (useEmulator() && FIREBASE_AUTH_EMULATOR_HOST) {
|
|
561
680
|
let protocol = "http://";
|
|
@@ -566,9 +685,6 @@ var getCustomTokenEndpoint = (apiKey) => {
|
|
|
566
685
|
}
|
|
567
686
|
return `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`;
|
|
568
687
|
};
|
|
569
|
-
var passwordResetEndpoint = (apiKey) => {
|
|
570
|
-
return `https://identitytoolkit.googleapis.com/v1/accounts:resetPassword?key=${apiKey}`;
|
|
571
|
-
};
|
|
572
688
|
|
|
573
689
|
// src/fireRestApi/request.ts
|
|
574
690
|
var FIREBASE_ENDPOINT_MAP = {
|
|
@@ -576,8 +692,8 @@ var FIREBASE_ENDPOINT_MAP = {
|
|
|
576
692
|
signInWithPassword,
|
|
577
693
|
signUp: signUpEndpoint,
|
|
578
694
|
signInWithCustomToken: getCustomTokenEndpoint,
|
|
579
|
-
passwordReset:
|
|
580
|
-
sendOobCode
|
|
695
|
+
passwordReset: sendOobCode,
|
|
696
|
+
sendOobCode,
|
|
581
697
|
lookup: lookupEndpoint
|
|
582
698
|
};
|
|
583
699
|
function createRequest(options) {
|
|
@@ -626,8 +742,18 @@ function createRequest(options) {
|
|
|
626
742
|
...body
|
|
627
743
|
});
|
|
628
744
|
}
|
|
629
|
-
const isJSONResponse = res?.headers && res.headers?.get(constants.Headers.ContentType)
|
|
630
|
-
|
|
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
|
+
}
|
|
631
757
|
if (!res.ok) {
|
|
632
758
|
return {
|
|
633
759
|
data: null,
|
|
@@ -708,9 +834,11 @@ function parseError(error) {
|
|
|
708
834
|
function createFireApi(options) {
|
|
709
835
|
const request = createRequest(options);
|
|
710
836
|
return {
|
|
837
|
+
appCheck: new AppCheckApi(request),
|
|
711
838
|
email: new EmailApi(request),
|
|
712
839
|
password: new PasswordApi(request),
|
|
713
|
-
signIn: new
|
|
840
|
+
signIn: new SignInApi(request),
|
|
841
|
+
signInToken: new SignInTokenApi(request),
|
|
714
842
|
signUp: new SignUpApi(request),
|
|
715
843
|
tokens: new TokenApi(request),
|
|
716
844
|
userData: new UserData(request)
|
|
@@ -1069,33 +1197,44 @@ async function verifySignature(jwt, key) {
|
|
|
1069
1197
|
}
|
|
1070
1198
|
}
|
|
1071
1199
|
function ternDecodeJwt(token) {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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) {
|
|
1076
1229
|
return {
|
|
1077
1230
|
errors: [
|
|
1078
1231
|
new TokenVerificationError({
|
|
1079
1232
|
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
1080
|
-
message: "Invalid
|
|
1233
|
+
message: `${error.message || "Invalid Token or Protected Header formatting"} (Token length: ${token?.length}, First 10 chars: ${token?.substring(0, 10)}...)`
|
|
1081
1234
|
})
|
|
1082
1235
|
]
|
|
1083
1236
|
};
|
|
1084
1237
|
}
|
|
1085
|
-
const [rawHeader, rawPayload, rawSignature] = tokenParts;
|
|
1086
|
-
const signature = base64url.parse(rawSignature, { loose: true });
|
|
1087
|
-
const data = {
|
|
1088
|
-
header,
|
|
1089
|
-
payload,
|
|
1090
|
-
signature,
|
|
1091
|
-
raw: {
|
|
1092
|
-
header: rawHeader,
|
|
1093
|
-
payload: rawPayload,
|
|
1094
|
-
signature: rawSignature,
|
|
1095
|
-
text: token
|
|
1096
|
-
}
|
|
1097
|
-
};
|
|
1098
|
-
return { data };
|
|
1099
1238
|
}
|
|
1100
1239
|
async function verifyJwt(token, options) {
|
|
1101
1240
|
const { key } = options;
|
|
@@ -1253,6 +1392,143 @@ async function verifyToken(token, options) {
|
|
|
1253
1392
|
}
|
|
1254
1393
|
}
|
|
1255
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
|
+
|
|
1256
1532
|
// src/auth/getauth.ts
|
|
1257
1533
|
var API_KEY_ERROR = "API Key is required";
|
|
1258
1534
|
var NO_DATA_ERROR = "No token data received";
|
|
@@ -1267,9 +1543,17 @@ function parseFirebaseResponse(data) {
|
|
|
1267
1543
|
return data;
|
|
1268
1544
|
}
|
|
1269
1545
|
function getAuth(options) {
|
|
1270
|
-
const { apiKey } = options;
|
|
1546
|
+
const { apiKey, firebaseAdminConfig } = options;
|
|
1271
1547
|
const firebaseApiKey = options.firebaseConfig?.apiKey;
|
|
1272
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
|
+
}
|
|
1273
1557
|
async function getUserData(idToken, localId) {
|
|
1274
1558
|
if (!effectiveApiKey) {
|
|
1275
1559
|
throw new Error(API_KEY_ERROR);
|
|
@@ -1311,23 +1595,23 @@ function getAuth(options) {
|
|
|
1311
1595
|
if (!effectiveApiKey) {
|
|
1312
1596
|
throw new Error("API Key is required to create custom token");
|
|
1313
1597
|
}
|
|
1314
|
-
const
|
|
1598
|
+
const data = await options.apiClient?.tokens.exchangeCustomForIdAndRefreshTokens(
|
|
1315
1599
|
effectiveApiKey,
|
|
1316
1600
|
{
|
|
1317
1601
|
token: customToken,
|
|
1318
1602
|
returnSecureToken: true
|
|
1319
1603
|
},
|
|
1320
1604
|
{
|
|
1321
|
-
referer: opts.referer
|
|
1605
|
+
referer: opts.referer,
|
|
1606
|
+
appCheckToken: opts.appCheckToken
|
|
1322
1607
|
}
|
|
1323
1608
|
);
|
|
1324
|
-
if (!
|
|
1609
|
+
if (!data) {
|
|
1325
1610
|
throw new Error("No data received from Firebase token exchange");
|
|
1326
1611
|
}
|
|
1327
|
-
const parsedData = parseFirebaseResponse(response.data);
|
|
1328
1612
|
return {
|
|
1329
|
-
idToken:
|
|
1330
|
-
refreshToken:
|
|
1613
|
+
idToken: data.idToken,
|
|
1614
|
+
refreshToken: data.refreshToken
|
|
1331
1615
|
};
|
|
1332
1616
|
}
|
|
1333
1617
|
async function createCustomIdAndRefreshToken(idToken, opts) {
|
|
@@ -1341,7 +1625,8 @@ function getAuth(options) {
|
|
|
1341
1625
|
source_sign_in_provider: data.firebase.sign_in_provider
|
|
1342
1626
|
});
|
|
1343
1627
|
const idAndRefreshTokens = await customForIdAndRefreshToken(customToken, {
|
|
1344
|
-
referer: opts.referer
|
|
1628
|
+
referer: opts.referer,
|
|
1629
|
+
appCheckToken: opts.appCheckToken
|
|
1345
1630
|
});
|
|
1346
1631
|
const decodedCustomIdToken = await verifyToken(idAndRefreshTokens.idToken, options);
|
|
1347
1632
|
if (decodedCustomIdToken.errors) {
|
|
@@ -1353,11 +1638,60 @@ function getAuth(options) {
|
|
|
1353
1638
|
auth_time: decodedCustomIdToken.data.auth_time
|
|
1354
1639
|
};
|
|
1355
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
|
+
}
|
|
1356
1689
|
return {
|
|
1357
1690
|
getUserData,
|
|
1358
1691
|
customForIdAndRefreshToken,
|
|
1359
1692
|
createCustomIdAndRefreshToken,
|
|
1360
|
-
refreshExpiredIdToken
|
|
1693
|
+
refreshExpiredIdToken,
|
|
1694
|
+
exchangeAppCheckToken
|
|
1361
1695
|
};
|
|
1362
1696
|
}
|
|
1363
1697
|
|
|
@@ -1388,6 +1722,7 @@ var RequestProcessorContext = class {
|
|
|
1388
1722
|
this.userAgent = this.getHeader(constants.Headers.UserAgent);
|
|
1389
1723
|
this.secFetchDest = this.getHeader(constants.Headers.SecFetchDest);
|
|
1390
1724
|
this.accept = this.getHeader(constants.Headers.Accept);
|
|
1725
|
+
this.appCheckToken = this.getHeader(constants.Headers.AppCheckToken);
|
|
1391
1726
|
}
|
|
1392
1727
|
initCookieValues() {
|
|
1393
1728
|
const isProduction = process.env.NODE_ENV === "production";
|
|
@@ -1451,14 +1786,13 @@ function isRequestForRefresh(error, context, request) {
|
|
|
1451
1786
|
}
|
|
1452
1787
|
async function authenticateRequest(request, options) {
|
|
1453
1788
|
const context = createRequestProcessor(createTernSecureRequest(request), options);
|
|
1454
|
-
const { refreshTokenInCookie } = context;
|
|
1789
|
+
const { refreshTokenInCookie, appCheckToken } = context;
|
|
1455
1790
|
const { refreshExpiredIdToken } = getAuth(options);
|
|
1456
1791
|
function checkSessionTimeout(authTimeValue) {
|
|
1457
1792
|
const defaultMaxAgeSeconds = convertToSeconds("5 days");
|
|
1458
1793
|
const REAUTH_PERIOD_SECONDS = context.session?.maxAge ? convertToSeconds(context.session.maxAge) : defaultMaxAgeSeconds;
|
|
1459
1794
|
const currentTime = Math.floor(Date.now() / 1e3);
|
|
1460
1795
|
const authAge = currentTime - authTimeValue;
|
|
1461
|
-
console.log("Current time:", currentTime, "Auth age:", authAge, "Reauth period (s):", REAUTH_PERIOD_SECONDS);
|
|
1462
1796
|
if (authTimeValue > 0 && authAge > REAUTH_PERIOD_SECONDS) {
|
|
1463
1797
|
return signedOut(context, AuthErrorReason.AuthTimeout, "Authentication expired");
|
|
1464
1798
|
}
|
|
@@ -1475,7 +1809,8 @@ async function authenticateRequest(request, options) {
|
|
|
1475
1809
|
};
|
|
1476
1810
|
}
|
|
1477
1811
|
return await refreshExpiredIdToken(refreshTokenInCookie, {
|
|
1478
|
-
referer: context.ternUrl.origin
|
|
1812
|
+
referer: context.ternUrl.origin,
|
|
1813
|
+
appCheckToken
|
|
1479
1814
|
});
|
|
1480
1815
|
}
|
|
1481
1816
|
async function handleRefresh() {
|
|
@@ -1577,7 +1912,24 @@ async function authenticateRequest(request, options) {
|
|
|
1577
1912
|
if (errors) {
|
|
1578
1913
|
throw errors[0];
|
|
1579
1914
|
}
|
|
1580
|
-
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);
|
|
1581
1933
|
return signedInRequestState;
|
|
1582
1934
|
} catch (err) {
|
|
1583
1935
|
return handleError(err, "cookie");
|
|
@@ -1591,7 +1943,23 @@ async function authenticateRequest(request, options) {
|
|
|
1591
1943
|
if (errors) {
|
|
1592
1944
|
throw errors[0];
|
|
1593
1945
|
}
|
|
1594
|
-
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);
|
|
1595
1963
|
return signedInRequestState;
|
|
1596
1964
|
} catch (err) {
|
|
1597
1965
|
return handleError(err, "header");
|
|
@@ -1605,6 +1973,16 @@ async function authenticateRequest(request, options) {
|
|
|
1605
1973
|
if (isRequestForRefresh(err, context, request)) {
|
|
1606
1974
|
const { data, error } = await handleRefresh();
|
|
1607
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
|
+
}
|
|
1608
1986
|
return signedIn(context, data.decoded, data.headers, data.token);
|
|
1609
1987
|
}
|
|
1610
1988
|
if (error?.cause?.reason) {
|