@tern-secure/backend 1.2.0-canary.v20251127221555 → 1.2.0-canary.v20251202162458
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/types.d.ts +42 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/admin/index.d.ts +1 -1
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +8 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +10 -70
- package/dist/admin/index.mjs.map +1 -1
- package/dist/app-check/AppCheckApi.d.ts +14 -0
- package/dist/app-check/AppCheckApi.d.ts.map +1 -0
- package/dist/app-check/generator.d.ts +9 -0
- package/dist/app-check/generator.d.ts.map +1 -0
- package/dist/app-check/index.d.ts +18 -0
- package/dist/app-check/index.d.ts.map +1 -0
- package/dist/app-check/index.js +1052 -0
- package/dist/app-check/index.js.map +1 -0
- package/dist/app-check/index.mjs +13 -0
- package/dist/app-check/index.mjs.map +1 -0
- package/dist/app-check/serverAppCheck.d.ts +33 -0
- package/dist/app-check/serverAppCheck.d.ts.map +1 -0
- package/dist/app-check/types.d.ts +21 -0
- package/dist/app-check/types.d.ts.map +1 -0
- package/dist/app-check/verifier.d.ts +16 -0
- package/dist/app-check/verifier.d.ts.map +1 -0
- package/dist/auth/credential.d.ts +5 -5
- package/dist/auth/credential.d.ts.map +1 -1
- package/dist/auth/getauth.d.ts +2 -1
- package/dist/auth/getauth.d.ts.map +1 -1
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +819 -394
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/index.mjs +5 -3
- package/dist/chunk-3OGMNIOJ.mjs +174 -0
- package/dist/chunk-3OGMNIOJ.mjs.map +1 -0
- package/dist/{chunk-GFH5CXQR.mjs → chunk-AW5OXT7N.mjs} +2 -2
- package/dist/chunk-IEJQ7F4A.mjs +778 -0
- package/dist/chunk-IEJQ7F4A.mjs.map +1 -0
- package/dist/{chunk-NXYWC6YO.mjs → chunk-TUYCJY35.mjs} +182 -6
- package/dist/chunk-TUYCJY35.mjs.map +1 -0
- package/dist/constants.d.ts +10 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/fireRestApi/endpoints/AppCheckApi.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1570 -1183
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +97 -135
- package/dist/index.mjs.map +1 -1
- package/dist/jwt/crypto-signer.d.ts +21 -0
- package/dist/jwt/crypto-signer.d.ts.map +1 -0
- package/dist/jwt/index.d.ts +2 -1
- package/dist/jwt/index.d.ts.map +1 -1
- package/dist/jwt/index.js +119 -2
- package/dist/jwt/index.js.map +1 -1
- package/dist/jwt/index.mjs +7 -3
- package/dist/jwt/signJwt.d.ts +8 -2
- package/dist/jwt/signJwt.d.ts.map +1 -1
- package/dist/jwt/types.d.ts +6 -0
- package/dist/jwt/types.d.ts.map +1 -1
- package/dist/jwt/verifyJwt.d.ts +7 -1
- package/dist/jwt/verifyJwt.d.ts.map +1 -1
- package/dist/tokens/authstate.d.ts +2 -0
- package/dist/tokens/authstate.d.ts.map +1 -1
- package/dist/tokens/c-authenticateRequestProcessor.d.ts +2 -2
- package/dist/tokens/c-authenticateRequestProcessor.d.ts.map +1 -1
- package/dist/tokens/keys.d.ts.map +1 -1
- package/dist/tokens/request.d.ts.map +1 -1
- package/dist/tokens/types.d.ts +6 -4
- package/dist/tokens/types.d.ts.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/{auth/utils.d.ts → utils/fetcher.d.ts} +2 -1
- package/dist/utils/fetcher.d.ts.map +1 -0
- package/dist/utils/mapDecode.d.ts +2 -1
- package/dist/utils/mapDecode.d.ts.map +1 -1
- package/dist/utils/token-generator.d.ts +4 -0
- package/dist/utils/token-generator.d.ts.map +1 -0
- package/package.json +13 -3
- package/dist/auth/constants.d.ts +0 -6
- package/dist/auth/constants.d.ts.map +0 -1
- package/dist/auth/utils.d.ts.map +0 -1
- package/dist/chunk-DJLDUW7J.mjs +0 -414
- package/dist/chunk-DJLDUW7J.mjs.map +0 -1
- package/dist/chunk-NXYWC6YO.mjs.map +0 -1
- package/dist/chunk-WIVOBOZR.mjs +0 -86
- package/dist/chunk-WIVOBOZR.mjs.map +0 -1
- package/dist/utils/gemini_admin-init.d.ts +0 -10
- package/dist/utils/gemini_admin-init.d.ts.map +0 -1
- /package/dist/{chunk-GFH5CXQR.mjs.map → chunk-AW5OXT7N.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ 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/index.ts
|
|
@@ -28,6 +38,7 @@ __export(index_exports, {
|
|
|
28
38
|
createAdapter: () => createAdapter,
|
|
29
39
|
createBackendInstanceClient: () => createBackendInstanceClient,
|
|
30
40
|
createRedirect: () => createRedirect,
|
|
41
|
+
createRequestProcessor: () => createRequestProcessor,
|
|
31
42
|
createTernSecureRequest: () => createTernSecureRequest,
|
|
32
43
|
disableDebugLogging: () => disableDebugLogging,
|
|
33
44
|
enableDebugLogging: () => enableDebugLogging,
|
|
@@ -35,15 +46,25 @@ __export(index_exports, {
|
|
|
35
46
|
signedIn: () => signedIn,
|
|
36
47
|
signedInAuthObject: () => signedInAuthObject,
|
|
37
48
|
signedOutAuthObject: () => signedOutAuthObject,
|
|
38
|
-
validateCheckRevokedOptions: () => validateCheckRevokedOptions
|
|
49
|
+
validateCheckRevokedOptions: () => validateCheckRevokedOptions,
|
|
50
|
+
verifyToken: () => verifyToken
|
|
39
51
|
});
|
|
40
52
|
module.exports = __toCommonJS(index_exports);
|
|
41
53
|
|
|
42
54
|
// src/constants.ts
|
|
43
55
|
var GOOGLE_PUBLIC_KEYS_URL = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
|
|
56
|
+
var FIREBASE_APP_CHECK_AUDIENCE = "https://firebaseappcheck.googleapis.com/google.firebase.appcheck.v1.TokenExchangeService";
|
|
44
57
|
var MAX_CACHE_LAST_UPDATED_AT_SECONDS = 5 * 60;
|
|
45
58
|
var DEFAULT_CACHE_DURATION = 3600 * 1e3;
|
|
46
59
|
var CACHE_CONTROL_REGEX = /max-age=(\d+)/;
|
|
60
|
+
var TOKEN_EXPIRY_THRESHOLD_MILLIS = 5 * 60 * 1e3;
|
|
61
|
+
var GOOGLE_TOKEN_AUDIENCE = "https://accounts.google.com/o/oauth2/token";
|
|
62
|
+
var GOOGLE_AUTH_TOKEN_HOST = "accounts.google.com";
|
|
63
|
+
var GOOGLE_AUTH_TOKEN_PATH = "/o/oauth2/token";
|
|
64
|
+
var ONE_HOUR_IN_SECONDS = 60 * 60;
|
|
65
|
+
var ONE_MINUTE_IN_SECONDS = 60;
|
|
66
|
+
var ONE_MINUTE_IN_MILLIS = ONE_MINUTE_IN_SECONDS * 1e3;
|
|
67
|
+
var ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1e3;
|
|
47
68
|
var Attributes = {
|
|
48
69
|
AuthToken: "__ternsecureAuthToken",
|
|
49
70
|
AuthSignature: "__ternsecureAuthSignature",
|
|
@@ -78,7 +99,7 @@ var QueryParameters = {
|
|
|
78
99
|
};
|
|
79
100
|
var Headers2 = {
|
|
80
101
|
Accept: "accept",
|
|
81
|
-
AppCheckToken: "x-
|
|
102
|
+
AppCheckToken: "x-ternsecure-appcheck",
|
|
82
103
|
AuthMessage: "x-ternsecure-auth-message",
|
|
83
104
|
Authorization: "authorization",
|
|
84
105
|
AuthReason: "x-ternsecure-auth-reason",
|
|
@@ -238,12 +259,93 @@ var createTernSecureRequest = (...args) => {
|
|
|
238
259
|
return args[0] instanceof TernSecureRequest ? args[0] : new TernSecureRequest(...args);
|
|
239
260
|
};
|
|
240
261
|
|
|
262
|
+
// src/tokens/c-authenticateRequestProcessor.ts
|
|
263
|
+
var RequestProcessorContext = class {
|
|
264
|
+
constructor(ternSecureRequest, options) {
|
|
265
|
+
this.ternSecureRequest = ternSecureRequest;
|
|
266
|
+
this.options = options;
|
|
267
|
+
this.initHeaderValues();
|
|
268
|
+
this.initCookieValues();
|
|
269
|
+
this.initHandshakeValues();
|
|
270
|
+
this.initUrlValues();
|
|
271
|
+
Object.assign(this, options);
|
|
272
|
+
this.ternUrl = this.ternSecureRequest.ternUrl;
|
|
273
|
+
}
|
|
274
|
+
get request() {
|
|
275
|
+
return this.ternSecureRequest;
|
|
276
|
+
}
|
|
277
|
+
initHeaderValues() {
|
|
278
|
+
this.sessionTokenInHeader = this.parseAuthorizationHeader(
|
|
279
|
+
this.getHeader(constants.Headers.Authorization)
|
|
280
|
+
);
|
|
281
|
+
this.origin = this.getHeader(constants.Headers.Origin);
|
|
282
|
+
this.host = this.getHeader(constants.Headers.Host);
|
|
283
|
+
this.forwardedHost = this.getHeader(constants.Headers.ForwardedHost);
|
|
284
|
+
this.forwardedProto = this.getHeader(constants.Headers.CloudFrontForwardedProto) || this.getHeader(constants.Headers.ForwardedProto);
|
|
285
|
+
this.referrer = this.getHeader(constants.Headers.Referrer);
|
|
286
|
+
this.userAgent = this.getHeader(constants.Headers.UserAgent);
|
|
287
|
+
this.secFetchDest = this.getHeader(constants.Headers.SecFetchDest);
|
|
288
|
+
this.accept = this.getHeader(constants.Headers.Accept);
|
|
289
|
+
this.appCheckToken = this.getHeader(constants.Headers.AppCheckToken);
|
|
290
|
+
}
|
|
291
|
+
initCookieValues() {
|
|
292
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
293
|
+
const defaultPrefix = isProduction ? "__HOST-" : "__dev_";
|
|
294
|
+
this.sessionTokenInCookie = this.getCookie(constants.Cookies.Session);
|
|
295
|
+
this.idTokenInCookie = this.getCookie(`${defaultPrefix}${constants.Cookies.IdToken}`);
|
|
296
|
+
this.refreshTokenInCookie = this.getCookie(`${defaultPrefix}${constants.Cookies.Refresh}`);
|
|
297
|
+
this.csrfTokenInCookie = this.getCookie(constants.Cookies.CsrfToken);
|
|
298
|
+
this.customTokenInCookie = this.getCookie(constants.Cookies.Custom);
|
|
299
|
+
this.ternAuth = Number.parseInt(this.getCookie(constants.Cookies.TernAut) || "0", 10);
|
|
300
|
+
}
|
|
301
|
+
initHandshakeValues() {
|
|
302
|
+
this.handshakeToken = this.getQueryParam(constants.QueryParameters.Handshake) || this.getCookie(constants.Cookies.Handshake);
|
|
303
|
+
this.handshakeNonce = this.getQueryParam(constants.QueryParameters.HandshakeNonce) || this.getCookie(constants.Cookies.HandshakeNonce);
|
|
304
|
+
}
|
|
305
|
+
initUrlValues() {
|
|
306
|
+
this.method = this.ternSecureRequest.method;
|
|
307
|
+
this.pathSegments = this.ternSecureRequest.ternUrl.pathname.split("/").filter(Boolean);
|
|
308
|
+
this.endpoint = this.pathSegments[2];
|
|
309
|
+
this.subEndpoint = this.pathSegments[3];
|
|
310
|
+
}
|
|
311
|
+
getQueryParam(name) {
|
|
312
|
+
return this.ternSecureRequest.ternUrl.searchParams.get(name);
|
|
313
|
+
}
|
|
314
|
+
getHeader(name) {
|
|
315
|
+
return this.ternSecureRequest.headers.get(name) || void 0;
|
|
316
|
+
}
|
|
317
|
+
getCookie(name) {
|
|
318
|
+
return this.ternSecureRequest.cookies.get(name) || void 0;
|
|
319
|
+
}
|
|
320
|
+
parseAuthorizationHeader(authorizationHeader) {
|
|
321
|
+
if (!authorizationHeader) {
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
const [scheme, token] = authorizationHeader.split(" ", 2);
|
|
325
|
+
if (!token) {
|
|
326
|
+
return scheme;
|
|
327
|
+
}
|
|
328
|
+
if (scheme === "Bearer") {
|
|
329
|
+
return token;
|
|
330
|
+
}
|
|
331
|
+
return void 0;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
var createRequestProcessor = (ternSecureRequest, options) => {
|
|
335
|
+
return new RequestProcessorContext(ternSecureRequest, options);
|
|
336
|
+
};
|
|
337
|
+
|
|
241
338
|
// src/utils/mapDecode.ts
|
|
242
339
|
function mapJwtPayloadToDecodedIdToken(payload) {
|
|
243
340
|
const decodedIdToken = payload;
|
|
244
341
|
decodedIdToken.uid = decodedIdToken.sub;
|
|
245
342
|
return decodedIdToken;
|
|
246
343
|
}
|
|
344
|
+
function mapJwtPayloadToDecodedAppCheckToken(payload) {
|
|
345
|
+
const decodedAppCheckToken = payload;
|
|
346
|
+
decodedAppCheckToken.app_id = decodedAppCheckToken.sub;
|
|
347
|
+
return decodedAppCheckToken;
|
|
348
|
+
}
|
|
247
349
|
|
|
248
350
|
// src/tokens/authstate.ts
|
|
249
351
|
var AuthStatus = {
|
|
@@ -333,6 +435,7 @@ function signedOut(authCtx, reason, message = "", headers = new Headers()) {
|
|
|
333
435
|
isSignedIn: false,
|
|
334
436
|
auth: () => signedOutAuthObject(),
|
|
335
437
|
token: null,
|
|
438
|
+
appCheckToken: authCtx.appCheckToken,
|
|
336
439
|
headers
|
|
337
440
|
});
|
|
338
441
|
}
|
|
@@ -356,1059 +459,1143 @@ var decorateHeaders = (requestState) => {
|
|
|
356
459
|
} catch {
|
|
357
460
|
}
|
|
358
461
|
}
|
|
462
|
+
if (requestState.appCheckToken) {
|
|
463
|
+
try {
|
|
464
|
+
headers.set(constants.Headers.AppCheckToken, requestState.appCheckToken);
|
|
465
|
+
} catch {
|
|
466
|
+
}
|
|
467
|
+
}
|
|
359
468
|
requestState.headers = headers;
|
|
360
469
|
return requestState;
|
|
361
470
|
};
|
|
362
471
|
|
|
363
|
-
// src/
|
|
364
|
-
var
|
|
365
|
-
|
|
366
|
-
|
|
472
|
+
// src/jwt/verifyJwt.ts
|
|
473
|
+
var import_jose2 = require("jose");
|
|
474
|
+
|
|
475
|
+
// src/utils/errors.ts
|
|
476
|
+
var RefreshTokenErrorReason = {
|
|
477
|
+
NonEligibleNoCookie: "non-eligible-no-refresh-cookie",
|
|
478
|
+
NonEligibleNonGet: "non-eligible-non-get",
|
|
479
|
+
InvalidSessionToken: "invalid-session-token",
|
|
480
|
+
MissingApiClient: "missing-api-client",
|
|
481
|
+
MissingIdToken: "missing-id-token",
|
|
482
|
+
MissingSessionToken: "missing-session-token",
|
|
483
|
+
MissingRefreshToken: "missing-refresh-token",
|
|
484
|
+
ExpiredIdTokenDecodeFailed: "expired-id-token-decode-failed",
|
|
485
|
+
ExpiredSessionTokenDecodeFailed: "expired-session-token-decode-failed",
|
|
486
|
+
FetchError: "fetch-error"
|
|
487
|
+
};
|
|
488
|
+
var TokenVerificationErrorReason = {
|
|
489
|
+
TokenExpired: "token-expired",
|
|
490
|
+
TokenInvalid: "token-invalid",
|
|
491
|
+
TokenInvalidAlgorithm: "token-invalid-algorithm",
|
|
492
|
+
TokenInvalidAuthorizedParties: "token-invalid-authorized-parties",
|
|
493
|
+
TokenInvalidSignature: "token-invalid-signature",
|
|
494
|
+
TokenNotActiveYet: "token-not-active-yet",
|
|
495
|
+
TokenIatInTheFuture: "token-iat-in-the-future",
|
|
496
|
+
TokenVerificationFailed: "token-verification-failed",
|
|
497
|
+
InvalidSecretKey: "secret-key-invalid",
|
|
498
|
+
LocalJWKMissing: "jwk-local-missing",
|
|
499
|
+
RemoteJWKFailedToLoad: "jwk-remote-failed-to-load",
|
|
500
|
+
RemoteJWKInvalid: "jwk-remote-invalid",
|
|
501
|
+
RemoteJWKMissing: "jwk-remote-missing",
|
|
502
|
+
JWKFailedToResolve: "jwk-failed-to-resolve",
|
|
503
|
+
JWKKidMismatch: "jwk-kid-mismatch"
|
|
504
|
+
};
|
|
505
|
+
var TokenVerificationError = class _TokenVerificationError extends Error {
|
|
506
|
+
reason;
|
|
507
|
+
tokenCarrier;
|
|
508
|
+
constructor({
|
|
509
|
+
message,
|
|
510
|
+
reason
|
|
511
|
+
}) {
|
|
512
|
+
super(message);
|
|
513
|
+
Object.setPrototypeOf(this, _TokenVerificationError.prototype);
|
|
514
|
+
this.reason = reason;
|
|
515
|
+
this.message = message;
|
|
367
516
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
throw new Error("A valid API key is required.");
|
|
371
|
-
}
|
|
517
|
+
getFullMessage() {
|
|
518
|
+
return `${[this.message].filter((m) => m).join(" ")} (reason=${this.reason}, token-carrier=${this.tokenCarrier})`;
|
|
372
519
|
}
|
|
373
520
|
};
|
|
374
521
|
|
|
375
|
-
// src/
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
522
|
+
// src/utils/rfc4648.ts
|
|
523
|
+
var base64url = {
|
|
524
|
+
parse(string, opts) {
|
|
525
|
+
return parse2(string, base64UrlEncoding, opts);
|
|
526
|
+
},
|
|
527
|
+
stringify(data, opts) {
|
|
528
|
+
return stringify(data, base64UrlEncoding, opts);
|
|
529
|
+
}
|
|
381
530
|
};
|
|
382
|
-
var
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
531
|
+
var base64UrlEncoding = {
|
|
532
|
+
chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
|
|
533
|
+
bits: 6
|
|
534
|
+
};
|
|
535
|
+
function parse2(string, encoding, opts = {}) {
|
|
536
|
+
if (!encoding.codes) {
|
|
537
|
+
encoding.codes = {};
|
|
538
|
+
for (let i = 0; i < encoding.chars.length; ++i) {
|
|
539
|
+
encoding.codes[encoding.chars[i]] = i;
|
|
387
540
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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;
|
|
541
|
+
}
|
|
542
|
+
if (!opts.loose && string.length * encoding.bits & 7) {
|
|
543
|
+
throw new SyntaxError("Invalid padding");
|
|
544
|
+
}
|
|
545
|
+
let end = string.length;
|
|
546
|
+
while (string[end - 1] === "=") {
|
|
547
|
+
--end;
|
|
548
|
+
if (!opts.loose && !((string.length - end) * encoding.bits & 7)) {
|
|
549
|
+
throw new SyntaxError("Invalid padding");
|
|
415
550
|
}
|
|
416
551
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
552
|
+
const out = new (opts.out ?? Uint8Array)(end * encoding.bits / 8 | 0);
|
|
553
|
+
let bits = 0;
|
|
554
|
+
let buffer = 0;
|
|
555
|
+
let written = 0;
|
|
556
|
+
for (let i = 0; i < end; ++i) {
|
|
557
|
+
const value = encoding.codes[string[i]];
|
|
558
|
+
if (value === void 0) {
|
|
559
|
+
throw new SyntaxError("Invalid character " + string[i]);
|
|
421
560
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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;
|
|
561
|
+
buffer = buffer << encoding.bits | value;
|
|
562
|
+
bits += encoding.bits;
|
|
563
|
+
if (bits >= 8) {
|
|
564
|
+
bits -= 8;
|
|
565
|
+
out[written++] = 255 & buffer >> bits;
|
|
449
566
|
}
|
|
450
567
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
// src/fireRestApi/endpoints/EmailApi.ts
|
|
454
|
-
var EmailApi = class extends AbstractAPI {
|
|
455
|
-
async verifyEmailVerification(apiKey, params) {
|
|
456
|
-
this.requireApiKey(apiKey);
|
|
457
|
-
const { ...restParams } = params;
|
|
458
|
-
return this.request({
|
|
459
|
-
endpoint: "sendOobCode",
|
|
460
|
-
method: "POST",
|
|
461
|
-
bodyParams: restParams
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
async confirmEmailVerification(apiKey, params) {
|
|
465
|
-
this.requireApiKey(apiKey);
|
|
466
|
-
const { ...restParams } = params;
|
|
467
|
-
return this.request({
|
|
468
|
-
endpoint: "sendOobCode",
|
|
469
|
-
method: "POST",
|
|
470
|
-
bodyParams: restParams
|
|
471
|
-
});
|
|
568
|
+
if (bits >= encoding.bits || 255 & buffer << 8 - bits) {
|
|
569
|
+
throw new SyntaxError("Unexpected end of data");
|
|
472
570
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
571
|
+
return out;
|
|
572
|
+
}
|
|
573
|
+
function stringify(data, encoding, opts = {}) {
|
|
574
|
+
const { pad = true } = opts;
|
|
575
|
+
const mask = (1 << encoding.bits) - 1;
|
|
576
|
+
let out = "";
|
|
577
|
+
let bits = 0;
|
|
578
|
+
let buffer = 0;
|
|
579
|
+
for (let i = 0; i < data.length; ++i) {
|
|
580
|
+
buffer = buffer << 8 | 255 & data[i];
|
|
581
|
+
bits += 8;
|
|
582
|
+
while (bits > encoding.bits) {
|
|
583
|
+
bits -= encoding.bits;
|
|
584
|
+
out += encoding.chars[mask & buffer >> bits];
|
|
585
|
+
}
|
|
485
586
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const { ...restParams } = params;
|
|
489
|
-
return this.request({
|
|
490
|
-
endpoint: "passwordReset",
|
|
491
|
-
method: "POST",
|
|
492
|
-
bodyParams: restParams
|
|
493
|
-
});
|
|
587
|
+
if (bits) {
|
|
588
|
+
out += encoding.chars[mask & buffer << encoding.bits - bits];
|
|
494
589
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
endpoint: "passwordReset",
|
|
500
|
-
method: "POST",
|
|
501
|
-
bodyParams: restParams
|
|
502
|
-
});
|
|
590
|
+
if (pad) {
|
|
591
|
+
while (out.length * encoding.bits & 7) {
|
|
592
|
+
out += "=";
|
|
593
|
+
}
|
|
503
594
|
}
|
|
504
|
-
|
|
595
|
+
return out;
|
|
596
|
+
}
|
|
505
597
|
|
|
506
|
-
// src/
|
|
507
|
-
var
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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);
|
|
598
|
+
// src/jwt/cryptoKeys.ts
|
|
599
|
+
var import_jose = require("jose");
|
|
600
|
+
async function importKey(key, algorithm) {
|
|
601
|
+
if (typeof key === "object") {
|
|
602
|
+
const result = await (0, import_jose.importJWK)(key, algorithm);
|
|
603
|
+
if (result instanceof Uint8Array) {
|
|
604
|
+
throw new Error("Unexpected Uint8Array result from JWK import");
|
|
526
605
|
}
|
|
606
|
+
return result;
|
|
607
|
+
}
|
|
608
|
+
const keyString = key.trim();
|
|
609
|
+
if (keyString.includes("-----BEGIN CERTIFICATE-----")) {
|
|
610
|
+
return await (0, import_jose.importX509)(keyString, algorithm);
|
|
611
|
+
}
|
|
612
|
+
if (keyString.includes("-----BEGIN PUBLIC KEY-----")) {
|
|
613
|
+
return await (0, import_jose.importSPKI)(keyString, algorithm);
|
|
614
|
+
}
|
|
615
|
+
try {
|
|
616
|
+
return await (0, import_jose.importSPKI)(keyString, algorithm);
|
|
617
|
+
} catch (error) {
|
|
618
|
+
throw new Error(
|
|
619
|
+
`Unsupported key format. Supported formats: X.509 certificate (PEM), SPKI (PEM), JWK (JSON object or string). Error: ${error}`
|
|
620
|
+
);
|
|
527
621
|
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/jwt/algorithms.ts
|
|
625
|
+
var algToHash = {
|
|
626
|
+
RS256: "SHA-256",
|
|
627
|
+
RS384: "SHA-384",
|
|
628
|
+
RS512: "SHA-512"
|
|
528
629
|
};
|
|
630
|
+
var algs = Object.keys(algToHash);
|
|
529
631
|
|
|
530
|
-
// src/
|
|
531
|
-
var
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
});
|
|
541
|
-
if (response.errors) {
|
|
542
|
-
const errorMessage = response.errors[0]?.message || "Failed to create custom token";
|
|
543
|
-
throw new Error(errorMessage);
|
|
544
|
-
}
|
|
545
|
-
return response.data;
|
|
546
|
-
} catch (error) {
|
|
547
|
-
const contextualMessage = `Failed to create custom token: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
548
|
-
throw new Error(contextualMessage);
|
|
549
|
-
}
|
|
632
|
+
// src/jwt/verifyContent.ts
|
|
633
|
+
var verifyHeaderKid = (kid) => {
|
|
634
|
+
if (typeof kid === "undefined") {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (typeof kid !== "string") {
|
|
638
|
+
throw new TokenVerificationError({
|
|
639
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
640
|
+
message: `Invalid JWT kid ${JSON.stringify(kid)}. Expected a string.`
|
|
641
|
+
});
|
|
550
642
|
}
|
|
551
643
|
};
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const { ...restParams } = params;
|
|
558
|
-
return this.request({
|
|
559
|
-
endpoint: "signUp",
|
|
560
|
-
method: "POST",
|
|
561
|
-
bodyParams: restParams
|
|
644
|
+
var verifySubClaim = (sub) => {
|
|
645
|
+
if (typeof sub !== "string") {
|
|
646
|
+
throw new TokenVerificationError({
|
|
647
|
+
reason: TokenVerificationErrorReason.TokenVerificationFailed,
|
|
648
|
+
message: `Subject claim (sub) is required and must be a string. Received ${JSON.stringify(sub)}.`
|
|
562
649
|
});
|
|
563
650
|
}
|
|
564
651
|
};
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
const { refresh_token, request_origin, app_check_token, ...restParams } = params;
|
|
571
|
-
const headers = {};
|
|
572
|
-
if (request_origin) {
|
|
573
|
-
headers["Referer"] = request_origin;
|
|
574
|
-
}
|
|
575
|
-
if (app_check_token) {
|
|
576
|
-
headers["X-Firebase-AppCheck"] = app_check_token;
|
|
577
|
-
}
|
|
578
|
-
const bodyParams = {
|
|
579
|
-
grant_type: "refresh_token",
|
|
580
|
-
refresh_token,
|
|
581
|
-
...restParams
|
|
582
|
-
};
|
|
583
|
-
return this.request({
|
|
584
|
-
endpoint: "refreshToken",
|
|
585
|
-
method: "POST",
|
|
586
|
-
apiKey,
|
|
587
|
-
bodyParams,
|
|
588
|
-
headerParams: headers
|
|
652
|
+
var verifyExpirationClaim = (exp, clockSkewInMs) => {
|
|
653
|
+
if (typeof exp !== "number") {
|
|
654
|
+
throw new TokenVerificationError({
|
|
655
|
+
reason: TokenVerificationErrorReason.TokenVerificationFailed,
|
|
656
|
+
message: `Invalid JWT expiry date (exp) claim ${JSON.stringify(exp)}. Expected a number.`
|
|
589
657
|
});
|
|
590
658
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
600
|
-
const response = await this.request({
|
|
601
|
-
endpoint: "signInWithCustomToken",
|
|
602
|
-
method: "POST",
|
|
603
|
-
apiKey,
|
|
604
|
-
bodyParams: params,
|
|
605
|
-
headerParams: headers
|
|
659
|
+
const currentDate = new Date(Date.now());
|
|
660
|
+
const expiryDate = /* @__PURE__ */ new Date(0);
|
|
661
|
+
expiryDate.setUTCSeconds(exp);
|
|
662
|
+
const expired = expiryDate.getTime() <= currentDate.getTime() - clockSkewInMs;
|
|
663
|
+
if (expired) {
|
|
664
|
+
throw new TokenVerificationError({
|
|
665
|
+
reason: TokenVerificationErrorReason.TokenExpired,
|
|
666
|
+
message: `JWT is expired. Expiry date: ${expiryDate.toUTCString()}, Current date: ${currentDate.toUTCString()}.`
|
|
606
667
|
});
|
|
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;
|
|
614
668
|
}
|
|
615
669
|
};
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
670
|
+
var verifyIssuedAtClaim = (iat, clockSkewInMs) => {
|
|
671
|
+
if (typeof iat === "undefined") {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (typeof iat !== "number") {
|
|
675
|
+
throw new TokenVerificationError({
|
|
676
|
+
reason: TokenVerificationErrorReason.TokenVerificationFailed,
|
|
677
|
+
message: `Invalid JWT issued at date claim (iat) ${JSON.stringify(iat)}. Expected a number.`
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
const currentDate = new Date(Date.now());
|
|
681
|
+
const issuedAtDate = /* @__PURE__ */ new Date(0);
|
|
682
|
+
issuedAtDate.setUTCSeconds(iat);
|
|
683
|
+
const postIssued = issuedAtDate.getTime() > currentDate.getTime() + clockSkewInMs;
|
|
684
|
+
if (postIssued) {
|
|
685
|
+
throw new TokenVerificationError({
|
|
686
|
+
reason: TokenVerificationErrorReason.TokenIatInTheFuture,
|
|
687
|
+
message: `JWT issued at date claim (iat) is in the future. Issued at date: ${issuedAtDate.toUTCString()}; Current date: ${currentDate.toUTCString()};`
|
|
632
688
|
});
|
|
633
689
|
}
|
|
634
690
|
};
|
|
635
691
|
|
|
636
|
-
// src/
|
|
637
|
-
var
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
function emulatorHost() {
|
|
655
|
-
if (typeof process === "undefined") return void 0;
|
|
656
|
-
return FIREBASE_AUTH_EMULATOR_HOST;
|
|
657
|
-
}
|
|
658
|
-
function useEmulator() {
|
|
659
|
-
return !!emulatorHost();
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// src/fireRestApi/endpointUrl.ts
|
|
663
|
-
var lookupEndpoint = (apiKey) => {
|
|
664
|
-
return `https://identitytoolkit.googleapis.com/v1/accounts:lookup?key=${apiKey}`;
|
|
665
|
-
};
|
|
666
|
-
var getRefreshTokenEndpoint = (apiKey) => {
|
|
667
|
-
return `https://securetoken.googleapis.com/v1/token?key=${apiKey}`;
|
|
668
|
-
};
|
|
669
|
-
var signInWithPassword = (apiKey) => {
|
|
670
|
-
return `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${apiKey}`;
|
|
671
|
-
};
|
|
672
|
-
var signUpEndpoint = (apiKey) => {
|
|
673
|
-
return `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${apiKey}`;
|
|
674
|
-
};
|
|
675
|
-
var sendOobCode = (apiKey) => {
|
|
676
|
-
return `https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode?key=${apiKey}`;
|
|
677
|
-
};
|
|
678
|
-
var getCustomTokenEndpoint = (apiKey) => {
|
|
679
|
-
if (useEmulator() && FIREBASE_AUTH_EMULATOR_HOST) {
|
|
680
|
-
let protocol = "http://";
|
|
681
|
-
if (FIREBASE_AUTH_EMULATOR_HOST.startsWith("http://")) {
|
|
682
|
-
protocol = "";
|
|
683
|
-
}
|
|
684
|
-
return `${protocol}${FIREBASE_AUTH_EMULATOR_HOST}/identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`;
|
|
692
|
+
// src/jwt/verifyJwt.ts
|
|
693
|
+
var DEFAULT_CLOCK_SKEW_IN_MS = 5 * 1e3;
|
|
694
|
+
async function verifySignature(jwt, key) {
|
|
695
|
+
const { header, raw } = jwt;
|
|
696
|
+
const joseAlgorithm = header.alg || "RS256";
|
|
697
|
+
try {
|
|
698
|
+
const publicKey = await importKey(key, joseAlgorithm);
|
|
699
|
+
const { payload } = await (0, import_jose2.jwtVerify)(raw.text, publicKey);
|
|
700
|
+
return { data: payload };
|
|
701
|
+
} catch (error) {
|
|
702
|
+
return {
|
|
703
|
+
errors: [
|
|
704
|
+
new TokenVerificationError({
|
|
705
|
+
reason: TokenVerificationErrorReason.TokenInvalidSignature,
|
|
706
|
+
message: error.message
|
|
707
|
+
})
|
|
708
|
+
]
|
|
709
|
+
};
|
|
685
710
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
signUp: signUpEndpoint,
|
|
694
|
-
signInWithCustomToken: getCustomTokenEndpoint,
|
|
695
|
-
passwordReset: sendOobCode,
|
|
696
|
-
sendOobCode,
|
|
697
|
-
lookup: lookupEndpoint
|
|
698
|
-
};
|
|
699
|
-
function createRequest(options) {
|
|
700
|
-
const requestFn = async (requestOptions) => {
|
|
701
|
-
const { endpoint, method, apiKey, queryParams, headerParams, bodyParams, formData } = requestOptions;
|
|
702
|
-
if (!apiKey) {
|
|
711
|
+
}
|
|
712
|
+
function ternDecodeJwt(token) {
|
|
713
|
+
try {
|
|
714
|
+
const header = (0, import_jose2.decodeProtectedHeader)(token);
|
|
715
|
+
const payload = (0, import_jose2.decodeJwt)(token);
|
|
716
|
+
const tokenParts = (token || "").toString().split(".");
|
|
717
|
+
if (tokenParts.length !== 3) {
|
|
703
718
|
return {
|
|
704
|
-
data: null,
|
|
705
719
|
errors: [
|
|
706
|
-
{
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
code: "400"
|
|
711
|
-
}
|
|
720
|
+
new TokenVerificationError({
|
|
721
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
722
|
+
message: "Invalid JWT format"
|
|
723
|
+
})
|
|
712
724
|
]
|
|
713
725
|
};
|
|
714
726
|
}
|
|
715
|
-
const
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
};
|
|
727
|
-
let res;
|
|
728
|
-
try {
|
|
729
|
-
if (formData) {
|
|
730
|
-
res = await runtime.fetch(finalUrl.href, {
|
|
731
|
-
method,
|
|
732
|
-
headers,
|
|
733
|
-
body: formData
|
|
734
|
-
});
|
|
735
|
-
} else {
|
|
736
|
-
headers["Content-Type"] = "application/json";
|
|
737
|
-
const hasBody = method !== "GET" && bodyParams && Object.keys(bodyParams).length > 0;
|
|
738
|
-
const body = hasBody ? { body: JSON.stringify(bodyParams) } : null;
|
|
739
|
-
res = await runtime.fetch(finalUrl.href, {
|
|
740
|
-
method,
|
|
741
|
-
headers,
|
|
742
|
-
...body
|
|
743
|
-
});
|
|
744
|
-
}
|
|
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
|
-
}
|
|
757
|
-
if (!res.ok) {
|
|
758
|
-
return {
|
|
759
|
-
data: null,
|
|
760
|
-
errors: parseErrors(responseBody),
|
|
761
|
-
status: res?.status,
|
|
762
|
-
statusText: res?.statusText
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
|
-
return {
|
|
766
|
-
data: responseBody,
|
|
767
|
-
errors: null
|
|
768
|
-
};
|
|
769
|
-
} catch (error) {
|
|
770
|
-
if (error instanceof Error) {
|
|
771
|
-
return {
|
|
772
|
-
data: null,
|
|
773
|
-
errors: [
|
|
774
|
-
{
|
|
775
|
-
domain: "none",
|
|
776
|
-
reason: "request_failed",
|
|
777
|
-
message: error.message || "An unexpected error occurred",
|
|
778
|
-
code: "500"
|
|
779
|
-
}
|
|
780
|
-
]
|
|
781
|
-
};
|
|
727
|
+
const [rawHeader, rawPayload, rawSignature] = tokenParts;
|
|
728
|
+
const signature = base64url.parse(rawSignature, { loose: true });
|
|
729
|
+
const data = {
|
|
730
|
+
header,
|
|
731
|
+
payload,
|
|
732
|
+
signature,
|
|
733
|
+
raw: {
|
|
734
|
+
header: rawHeader,
|
|
735
|
+
payload: rawPayload,
|
|
736
|
+
signature: rawSignature,
|
|
737
|
+
text: token
|
|
782
738
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
739
|
+
};
|
|
740
|
+
return { data };
|
|
741
|
+
} catch (error) {
|
|
742
|
+
return {
|
|
743
|
+
errors: [
|
|
744
|
+
new TokenVerificationError({
|
|
745
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
746
|
+
message: `${error.message || "Invalid Token or Protected Header formatting"} (Token length: ${token?.length}, First 10 chars: ${token?.substring(0, 10)}...)`
|
|
747
|
+
})
|
|
748
|
+
]
|
|
749
|
+
};
|
|
750
|
+
}
|
|
792
751
|
}
|
|
793
|
-
function
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
return [];
|
|
800
|
-
}
|
|
752
|
+
async function verifyJwt(token, options) {
|
|
753
|
+
const { key } = options;
|
|
754
|
+
const clockSkew = options.clockSkewInMs || DEFAULT_CLOCK_SKEW_IN_MS;
|
|
755
|
+
const { data: decoded, errors } = ternDecodeJwt(token);
|
|
756
|
+
if (errors) {
|
|
757
|
+
return { errors };
|
|
801
758
|
}
|
|
802
|
-
|
|
803
|
-
|
|
759
|
+
const { header, payload } = decoded;
|
|
760
|
+
try {
|
|
761
|
+
verifyHeaderKid(header.kid);
|
|
762
|
+
verifySubClaim(payload.sub);
|
|
763
|
+
verifyExpirationClaim(payload.exp, clockSkew);
|
|
764
|
+
verifyIssuedAtClaim(payload.iat, clockSkew);
|
|
765
|
+
} catch (error) {
|
|
766
|
+
return { errors: [error] };
|
|
804
767
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
768
|
+
const { data: verifiedPayload, errors: signatureErrors } = await verifySignature(decoded, key);
|
|
769
|
+
if (signatureErrors) {
|
|
770
|
+
return {
|
|
771
|
+
errors: [
|
|
772
|
+
new TokenVerificationError({
|
|
773
|
+
reason: TokenVerificationErrorReason.TokenInvalidSignature,
|
|
774
|
+
message: "Token signature verification failed."
|
|
775
|
+
})
|
|
776
|
+
]
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
const decodedIdToken = mapJwtPayloadToDecodedIdToken(verifiedPayload);
|
|
780
|
+
return { data: decodedIdToken };
|
|
781
|
+
}
|
|
782
|
+
async function verifyAppCheckSignature(jwt, getPublicKey2) {
|
|
783
|
+
const { header, raw } = jwt;
|
|
784
|
+
const joseAlgorithm = header.alg || "RS256";
|
|
785
|
+
try {
|
|
786
|
+
const key = await getPublicKey2();
|
|
787
|
+
const { payload } = await (0, import_jose2.jwtVerify)(raw.text, key);
|
|
788
|
+
return { data: payload };
|
|
789
|
+
} catch (error) {
|
|
790
|
+
return {
|
|
791
|
+
errors: [
|
|
792
|
+
new TokenVerificationError({
|
|
793
|
+
reason: TokenVerificationErrorReason.TokenInvalidSignature,
|
|
794
|
+
message: error.message
|
|
795
|
+
})
|
|
796
|
+
]
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
async function verifyAppCheckJwt(token, options) {
|
|
801
|
+
const { key: getPublicKey2 } = options;
|
|
802
|
+
const clockSkew = options.clockSkewInMs || DEFAULT_CLOCK_SKEW_IN_MS;
|
|
803
|
+
const { data: decoded, errors } = ternDecodeJwt(token);
|
|
804
|
+
if (errors) {
|
|
805
|
+
return { errors };
|
|
806
|
+
}
|
|
807
|
+
const { header, payload } = decoded;
|
|
808
|
+
try {
|
|
809
|
+
verifyHeaderKid(header.kid);
|
|
810
|
+
verifySubClaim(payload.sub);
|
|
811
|
+
verifyExpirationClaim(payload.exp, clockSkew);
|
|
812
|
+
verifyIssuedAtClaim(payload.iat, clockSkew);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
return { errors: [error] };
|
|
815
|
+
}
|
|
816
|
+
const { data: verifiedPayload, errors: signatureErrors } = await verifyAppCheckSignature(
|
|
817
|
+
decoded,
|
|
818
|
+
getPublicKey2
|
|
819
|
+
);
|
|
820
|
+
if (signatureErrors) {
|
|
821
|
+
return {
|
|
822
|
+
errors: [
|
|
823
|
+
new TokenVerificationError({
|
|
824
|
+
reason: TokenVerificationErrorReason.TokenInvalidSignature,
|
|
825
|
+
message: "Token signature verification failed."
|
|
826
|
+
})
|
|
827
|
+
]
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
const decodedAppCheckToken = mapJwtPayloadToDecodedAppCheckToken(verifiedPayload);
|
|
831
|
+
return { data: decodedAppCheckToken };
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// src/tokens/keys.ts
|
|
835
|
+
var cache = {};
|
|
836
|
+
var lastUpdatedAt = 0;
|
|
837
|
+
var googleExpiresAt = 0;
|
|
838
|
+
function getFromCache(kid) {
|
|
839
|
+
return cache[kid];
|
|
840
|
+
}
|
|
841
|
+
function getCacheValues() {
|
|
842
|
+
return Object.values(cache);
|
|
843
|
+
}
|
|
844
|
+
function setInCache(kid, certificate, shouldExpire = true) {
|
|
845
|
+
cache[kid] = certificate;
|
|
846
|
+
lastUpdatedAt = shouldExpire ? Date.now() : -1;
|
|
847
|
+
}
|
|
848
|
+
async function fetchPublicKeys(keyUrl) {
|
|
849
|
+
const url = new URL(keyUrl);
|
|
850
|
+
const response = await fetch(url);
|
|
851
|
+
if (!response.ok) {
|
|
852
|
+
throw new TokenVerificationError({
|
|
853
|
+
message: `Error loading public keys from ${url.href} with code=${response.status} `,
|
|
854
|
+
reason: TokenVerificationErrorReason.TokenInvalid
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
const data = await response.json();
|
|
858
|
+
const expiresAt = getExpiresAt(response);
|
|
859
|
+
return {
|
|
860
|
+
keys: data,
|
|
861
|
+
expiresAt
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
async function loadJWKFromRemote({
|
|
865
|
+
keyURL,
|
|
866
|
+
skipJwksCache,
|
|
867
|
+
kid
|
|
868
|
+
}) {
|
|
869
|
+
const finalKeyURL = keyURL || GOOGLE_PUBLIC_KEYS_URL;
|
|
870
|
+
if (skipJwksCache || isCacheExpired() || !getFromCache(kid)) {
|
|
871
|
+
const { keys, expiresAt } = await fetchPublicKeys(finalKeyURL);
|
|
872
|
+
if (!keys || Object.keys(keys).length === 0) {
|
|
873
|
+
throw new TokenVerificationError({
|
|
874
|
+
message: `The JWKS endpoint ${finalKeyURL} returned no keys`,
|
|
875
|
+
reason: TokenVerificationErrorReason.RemoteJWKFailedToLoad
|
|
876
|
+
});
|
|
814
877
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
878
|
+
googleExpiresAt = expiresAt;
|
|
879
|
+
Object.entries(keys).forEach(([keyId, cert2]) => {
|
|
880
|
+
setInCache(keyId, cert2);
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
const cert = getFromCache(kid);
|
|
884
|
+
if (!cert) {
|
|
885
|
+
getCacheValues();
|
|
886
|
+
const availableKids = Object.keys(cache).sort().join(", ");
|
|
887
|
+
throw new TokenVerificationError({
|
|
888
|
+
message: `No public key found for kid "${kid}". Available kids: [${availableKids}]`,
|
|
889
|
+
reason: TokenVerificationErrorReason.TokenInvalid
|
|
890
|
+
});
|
|
821
891
|
}
|
|
822
|
-
return
|
|
823
|
-
}
|
|
824
|
-
function parseError(error) {
|
|
825
|
-
return {
|
|
826
|
-
domain: error.domain,
|
|
827
|
-
reason: error.reason,
|
|
828
|
-
message: error.message,
|
|
829
|
-
code: error.code
|
|
830
|
-
};
|
|
892
|
+
return cert;
|
|
831
893
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
894
|
+
function isCacheExpired() {
|
|
895
|
+
const now = Date.now();
|
|
896
|
+
if (lastUpdatedAt === -1) {
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
const cacheAge = now - lastUpdatedAt;
|
|
900
|
+
const maxCacheAge = MAX_CACHE_LAST_UPDATED_AT_SECONDS * 1e3;
|
|
901
|
+
const localCacheExpired = cacheAge >= maxCacheAge;
|
|
902
|
+
const googleCacheExpired = now >= googleExpiresAt;
|
|
903
|
+
const isExpired = localCacheExpired || googleCacheExpired;
|
|
904
|
+
if (isExpired) {
|
|
905
|
+
cache = {};
|
|
906
|
+
}
|
|
907
|
+
return isExpired;
|
|
846
908
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
return {
|
|
856
|
-
...defaultOptions,
|
|
857
|
-
...userOptions
|
|
858
|
-
};
|
|
909
|
+
function getExpiresAt(res) {
|
|
910
|
+
const cacheControlHeader = res.headers.get("cache-control");
|
|
911
|
+
if (!cacheControlHeader) {
|
|
912
|
+
return Date.now() + DEFAULT_CACHE_DURATION;
|
|
913
|
+
}
|
|
914
|
+
const maxAgeMatch = cacheControlHeader.match(CACHE_CONTROL_REGEX);
|
|
915
|
+
const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1], 10) : DEFAULT_CACHE_DURATION / 1e3;
|
|
916
|
+
return Date.now() + maxAge * 1e3;
|
|
859
917
|
}
|
|
860
918
|
|
|
861
|
-
// src/tokens/
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
919
|
+
// src/tokens/verify.ts
|
|
920
|
+
async function verifyToken(token, options) {
|
|
921
|
+
const { data: decodedResult, errors } = ternDecodeJwt(token);
|
|
922
|
+
if (errors) {
|
|
923
|
+
return { errors };
|
|
924
|
+
}
|
|
925
|
+
const { header } = decodedResult;
|
|
926
|
+
const { kid } = header;
|
|
927
|
+
if (!kid) {
|
|
928
|
+
return {
|
|
929
|
+
errors: [
|
|
930
|
+
new TokenVerificationError({
|
|
931
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
932
|
+
message: 'JWT "kid" header is missing.'
|
|
933
|
+
})
|
|
934
|
+
]
|
|
935
|
+
};
|
|
871
936
|
}
|
|
872
|
-
};
|
|
873
|
-
var RESERVED_CLAIMS = [
|
|
874
|
-
"acr",
|
|
875
|
-
"amr",
|
|
876
|
-
"at_hash",
|
|
877
|
-
"aud",
|
|
878
|
-
"auth_time",
|
|
879
|
-
"azp",
|
|
880
|
-
"cnf",
|
|
881
|
-
"c_hash",
|
|
882
|
-
"exp",
|
|
883
|
-
"firebase",
|
|
884
|
-
"iat",
|
|
885
|
-
"iss",
|
|
886
|
-
"jti",
|
|
887
|
-
"nbf",
|
|
888
|
-
"nonce",
|
|
889
|
-
"sub"
|
|
890
|
-
];
|
|
891
|
-
async function createCustomTokenJwt(uid, developerClaims) {
|
|
892
937
|
try {
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
if (!privateKey || !clientEmail) {
|
|
938
|
+
const key = options.jwtKey || await loadJWKFromRemote({ ...options, kid });
|
|
939
|
+
if (!key) {
|
|
896
940
|
return {
|
|
897
941
|
errors: [
|
|
898
|
-
new
|
|
899
|
-
|
|
900
|
-
"
|
|
901
|
-
)
|
|
942
|
+
new TokenVerificationError({
|
|
943
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
944
|
+
message: `No public key found for kid "${kid}".`
|
|
945
|
+
})
|
|
902
946
|
]
|
|
903
947
|
};
|
|
904
948
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
};
|
|
949
|
+
return await verifyJwt(token, { ...options, key });
|
|
950
|
+
} catch (error) {
|
|
951
|
+
if (error instanceof TokenVerificationError) {
|
|
952
|
+
return { errors: [error] };
|
|
909
953
|
}
|
|
910
|
-
|
|
954
|
+
return {
|
|
955
|
+
errors: [error]
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// src/fireRestApi/endpoints/AbstractApi.ts
|
|
961
|
+
var AbstractAPI = class {
|
|
962
|
+
constructor(request) {
|
|
963
|
+
this.request = request;
|
|
964
|
+
}
|
|
965
|
+
requireApiKey(apiKey) {
|
|
966
|
+
if (!apiKey) {
|
|
967
|
+
throw new Error("A valid API key is required.");
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
// src/fireRestApi/endpoints/AppCheckApi.ts
|
|
973
|
+
function getSdkVersion() {
|
|
974
|
+
return "12.7.0";
|
|
975
|
+
}
|
|
976
|
+
var FIREBASE_APP_CHECK_CONFIG_HEADERS = {
|
|
977
|
+
"X-Firebase-Client": `fire-admin-node/${getSdkVersion()}`
|
|
978
|
+
};
|
|
979
|
+
var AppCheckApi = class extends AbstractAPI {
|
|
980
|
+
async exchangeCustomToken(params) {
|
|
981
|
+
const { projectId, appId, customToken, accessToken, limitedUse = false } = params;
|
|
982
|
+
if (!projectId || !appId) {
|
|
983
|
+
throw new Error("Project ID and App ID are required for App Check token exchange");
|
|
984
|
+
}
|
|
985
|
+
const endpoint = `https://firebaseappcheck.googleapis.com/v1beta/projects/${projectId}/apps/${appId}:exchangeCustomToken`;
|
|
986
|
+
const headers = {
|
|
987
|
+
"Content-Type": "application/json",
|
|
988
|
+
"Authorization": `Bearer ${accessToken}`
|
|
989
|
+
};
|
|
990
|
+
try {
|
|
991
|
+
const response = await fetch(endpoint, {
|
|
992
|
+
method: "POST",
|
|
993
|
+
headers,
|
|
994
|
+
body: JSON.stringify({ customToken, limitedUse })
|
|
995
|
+
});
|
|
996
|
+
if (!response.ok) {
|
|
997
|
+
const errorText = await response.text();
|
|
998
|
+
throw new Error(`App Check token exchange failed: ${response.status} ${errorText}`);
|
|
999
|
+
}
|
|
1000
|
+
const data = await response.json();
|
|
911
1001
|
return {
|
|
912
|
-
|
|
1002
|
+
token: data.token,
|
|
1003
|
+
ttl: data.ttl
|
|
913
1004
|
};
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
console.warn("[ternsecure - appcheck api]unexpected error:", error);
|
|
1007
|
+
throw error;
|
|
914
1008
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
}
|
|
1009
|
+
}
|
|
1010
|
+
async exchangeDebugToken(params) {
|
|
1011
|
+
const { projectId, appId, customToken, accessToken, limitedUse = false } = params;
|
|
1012
|
+
if (!projectId || !appId) {
|
|
1013
|
+
throw new Error("Project ID and App ID are required for App Check token exchange");
|
|
923
1014
|
}
|
|
924
|
-
const
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
iss: clientEmail,
|
|
929
|
-
sub: clientEmail,
|
|
930
|
-
aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
|
|
931
|
-
iat: now,
|
|
932
|
-
exp: now + expiresIn,
|
|
933
|
-
uid,
|
|
934
|
-
...developerClaims
|
|
935
|
-
};
|
|
936
|
-
const jwt = await new import_jose.SignJWT(payload).setProtectedHeader({ alg: "RS256", typ: "JWT" }).setIssuedAt(now).setExpirationTime(now + expiresIn).setIssuer(clientEmail).setSubject(clientEmail).setAudience(
|
|
937
|
-
"https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
|
|
938
|
-
).sign(parsedPrivateKey);
|
|
939
|
-
return {
|
|
940
|
-
data: jwt
|
|
1015
|
+
const endpoint = `https://firebaseappcheck.googleapis.com/v1beta/projects/${projectId}/apps/${appId}:exchangeDebugToken`;
|
|
1016
|
+
const headers = {
|
|
1017
|
+
...FIREBASE_APP_CHECK_CONFIG_HEADERS,
|
|
1018
|
+
"Authorization": `Bearer ${accessToken}`
|
|
941
1019
|
};
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
errors: [
|
|
946
|
-
new CustomTokenError(`Failed to create custom token: ${message}`, "TOKEN_CREATION_FAILED")
|
|
947
|
-
]
|
|
1020
|
+
const body = {
|
|
1021
|
+
customToken,
|
|
1022
|
+
limitedUse
|
|
948
1023
|
};
|
|
1024
|
+
try {
|
|
1025
|
+
const response = await fetch(endpoint, {
|
|
1026
|
+
method: "POST",
|
|
1027
|
+
headers,
|
|
1028
|
+
body: JSON.stringify(body)
|
|
1029
|
+
});
|
|
1030
|
+
if (!response.ok) {
|
|
1031
|
+
const errorText = await response.text();
|
|
1032
|
+
throw new Error(`App Check token exchange failed: ${response.status} ${errorText}`);
|
|
1033
|
+
}
|
|
1034
|
+
const data = await response.json();
|
|
1035
|
+
return {
|
|
1036
|
+
token: data.token,
|
|
1037
|
+
ttl: data.ttl
|
|
1038
|
+
};
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
console.warn("[ternsecure - appcheck api]unexpected error:", error);
|
|
1041
|
+
throw error;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
// src/fireRestApi/endpoints/EmailApi.ts
|
|
1047
|
+
var EmailApi = class extends AbstractAPI {
|
|
1048
|
+
async verifyEmailVerification(apiKey, params) {
|
|
1049
|
+
this.requireApiKey(apiKey);
|
|
1050
|
+
const { ...restParams } = params;
|
|
1051
|
+
return this.request({
|
|
1052
|
+
endpoint: "sendOobCode",
|
|
1053
|
+
method: "POST",
|
|
1054
|
+
bodyParams: restParams
|
|
1055
|
+
});
|
|
949
1056
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1057
|
+
async confirmEmailVerification(apiKey, params) {
|
|
1058
|
+
this.requireApiKey(apiKey);
|
|
1059
|
+
const { ...restParams } = params;
|
|
1060
|
+
return this.request({
|
|
1061
|
+
endpoint: "sendOobCode",
|
|
1062
|
+
method: "POST",
|
|
1063
|
+
bodyParams: restParams
|
|
1064
|
+
});
|
|
955
1065
|
}
|
|
956
|
-
return data;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
// src/jwt/verifyJwt.ts
|
|
960
|
-
var import_jose3 = require("jose");
|
|
961
|
-
|
|
962
|
-
// src/utils/errors.ts
|
|
963
|
-
var RefreshTokenErrorReason = {
|
|
964
|
-
NonEligibleNoCookie: "non-eligible-no-refresh-cookie",
|
|
965
|
-
NonEligibleNonGet: "non-eligible-non-get",
|
|
966
|
-
InvalidSessionToken: "invalid-session-token",
|
|
967
|
-
MissingApiClient: "missing-api-client",
|
|
968
|
-
MissingIdToken: "missing-id-token",
|
|
969
|
-
MissingSessionToken: "missing-session-token",
|
|
970
|
-
MissingRefreshToken: "missing-refresh-token",
|
|
971
|
-
ExpiredIdTokenDecodeFailed: "expired-id-token-decode-failed",
|
|
972
|
-
ExpiredSessionTokenDecodeFailed: "expired-session-token-decode-failed",
|
|
973
|
-
FetchError: "fetch-error"
|
|
974
|
-
};
|
|
975
|
-
var TokenVerificationErrorReason = {
|
|
976
|
-
TokenExpired: "token-expired",
|
|
977
|
-
TokenInvalid: "token-invalid",
|
|
978
|
-
TokenInvalidAlgorithm: "token-invalid-algorithm",
|
|
979
|
-
TokenInvalidAuthorizedParties: "token-invalid-authorized-parties",
|
|
980
|
-
TokenInvalidSignature: "token-invalid-signature",
|
|
981
|
-
TokenNotActiveYet: "token-not-active-yet",
|
|
982
|
-
TokenIatInTheFuture: "token-iat-in-the-future",
|
|
983
|
-
TokenVerificationFailed: "token-verification-failed",
|
|
984
|
-
InvalidSecretKey: "secret-key-invalid",
|
|
985
|
-
LocalJWKMissing: "jwk-local-missing",
|
|
986
|
-
RemoteJWKFailedToLoad: "jwk-remote-failed-to-load",
|
|
987
|
-
RemoteJWKInvalid: "jwk-remote-invalid",
|
|
988
|
-
RemoteJWKMissing: "jwk-remote-missing",
|
|
989
|
-
JWKFailedToResolve: "jwk-failed-to-resolve",
|
|
990
|
-
JWKKidMismatch: "jwk-kid-mismatch"
|
|
991
1066
|
};
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1067
|
+
|
|
1068
|
+
// src/fireRestApi/endpoints/PasswordApi.ts
|
|
1069
|
+
var PasswordApi = class extends AbstractAPI {
|
|
1070
|
+
async verifyPasswordResetCode(apiKey, params) {
|
|
1071
|
+
this.requireApiKey(apiKey);
|
|
1072
|
+
const { ...restParams } = params;
|
|
1073
|
+
return this.request({
|
|
1074
|
+
endpoint: "passwordReset",
|
|
1075
|
+
method: "POST",
|
|
1076
|
+
bodyParams: restParams
|
|
1077
|
+
});
|
|
1003
1078
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1079
|
+
async confirmPasswordReset(apiKey, params) {
|
|
1080
|
+
this.requireApiKey(apiKey);
|
|
1081
|
+
const { ...restParams } = params;
|
|
1082
|
+
return this.request({
|
|
1083
|
+
endpoint: "passwordReset",
|
|
1084
|
+
method: "POST",
|
|
1085
|
+
bodyParams: restParams
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
async changePassword(apiKey, params) {
|
|
1089
|
+
this.requireApiKey(apiKey);
|
|
1090
|
+
const { ...restParams } = params;
|
|
1091
|
+
return this.request({
|
|
1092
|
+
endpoint: "passwordReset",
|
|
1093
|
+
method: "POST",
|
|
1094
|
+
bodyParams: restParams
|
|
1095
|
+
});
|
|
1006
1096
|
}
|
|
1007
1097
|
};
|
|
1008
1098
|
|
|
1009
|
-
// src/
|
|
1010
|
-
var
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1099
|
+
// src/fireRestApi/endpoints/SignInApi.ts
|
|
1100
|
+
var SignInApi = class extends AbstractAPI {
|
|
1101
|
+
async resetPasswordEmail(apiKey, params) {
|
|
1102
|
+
try {
|
|
1103
|
+
this.requireApiKey(apiKey);
|
|
1104
|
+
const { ...restParams } = params;
|
|
1105
|
+
const response = await this.request({
|
|
1106
|
+
endpoint: "sendOobCode",
|
|
1107
|
+
method: "POST",
|
|
1108
|
+
apiKey,
|
|
1109
|
+
bodyParams: restParams
|
|
1110
|
+
});
|
|
1111
|
+
if (response.errors) {
|
|
1112
|
+
const errorMessage = response.errors[0]?.message || "Failed to send reset password email";
|
|
1113
|
+
throw new Error(errorMessage);
|
|
1114
|
+
}
|
|
1115
|
+
return response.data;
|
|
1116
|
+
} catch (error) {
|
|
1117
|
+
const contextualMessage = `Failed to send reset password email: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1118
|
+
throw new Error(contextualMessage);
|
|
1119
|
+
}
|
|
1016
1120
|
}
|
|
1017
1121
|
};
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1122
|
+
|
|
1123
|
+
// src/fireRestApi/endpoints/SignInTokenApi.ts
|
|
1124
|
+
var SignInTokenApi = class extends AbstractAPI {
|
|
1125
|
+
async createCustomToken(apiKey, params) {
|
|
1126
|
+
try {
|
|
1127
|
+
this.requireApiKey(apiKey);
|
|
1128
|
+
const { ...restParams } = params;
|
|
1129
|
+
const response = await this.request({
|
|
1130
|
+
endpoint: "signInWithCustomToken",
|
|
1131
|
+
method: "POST",
|
|
1132
|
+
bodyParams: restParams
|
|
1133
|
+
});
|
|
1134
|
+
if (response.errors) {
|
|
1135
|
+
const errorMessage = response.errors[0]?.message || "Failed to create custom token";
|
|
1136
|
+
throw new Error(errorMessage);
|
|
1137
|
+
}
|
|
1138
|
+
return response.data;
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
const contextualMessage = `Failed to create custom token: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1141
|
+
throw new Error(contextualMessage);
|
|
1027
1142
|
}
|
|
1028
1143
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
// src/fireRestApi/endpoints/SignUpApi.ts
|
|
1147
|
+
var SignUpApi = class extends AbstractAPI {
|
|
1148
|
+
async createCustomToken(apiKey, params) {
|
|
1149
|
+
this.requireApiKey(apiKey);
|
|
1150
|
+
const { ...restParams } = params;
|
|
1151
|
+
return this.request({
|
|
1152
|
+
endpoint: "signUp",
|
|
1153
|
+
method: "POST",
|
|
1154
|
+
bodyParams: restParams
|
|
1155
|
+
});
|
|
1038
1156
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1159
|
+
// src/fireRestApi/endpoints/TokenApi.ts
|
|
1160
|
+
var TokenApi = class extends AbstractAPI {
|
|
1161
|
+
async refreshToken(apiKey, params) {
|
|
1162
|
+
this.requireApiKey(apiKey);
|
|
1163
|
+
const { refresh_token, request_origin, app_check_token, ...restParams } = params;
|
|
1164
|
+
const headers = {};
|
|
1165
|
+
if (request_origin) {
|
|
1166
|
+
headers["Referer"] = request_origin;
|
|
1047
1167
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
if (bits >= 8) {
|
|
1051
|
-
bits -= 8;
|
|
1052
|
-
out[written++] = 255 & buffer >> bits;
|
|
1168
|
+
if (app_check_token) {
|
|
1169
|
+
headers["X-Firebase-AppCheck"] = app_check_token;
|
|
1053
1170
|
}
|
|
1171
|
+
const bodyParams = {
|
|
1172
|
+
grant_type: "refresh_token",
|
|
1173
|
+
refresh_token,
|
|
1174
|
+
...restParams
|
|
1175
|
+
};
|
|
1176
|
+
return this.request({
|
|
1177
|
+
endpoint: "refreshToken",
|
|
1178
|
+
method: "POST",
|
|
1179
|
+
apiKey,
|
|
1180
|
+
bodyParams,
|
|
1181
|
+
headerParams: headers
|
|
1182
|
+
});
|
|
1054
1183
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
function stringify(data, encoding, opts = {}) {
|
|
1061
|
-
const { pad = true } = opts;
|
|
1062
|
-
const mask = (1 << encoding.bits) - 1;
|
|
1063
|
-
let out = "";
|
|
1064
|
-
let bits = 0;
|
|
1065
|
-
let buffer = 0;
|
|
1066
|
-
for (let i = 0; i < data.length; ++i) {
|
|
1067
|
-
buffer = buffer << 8 | 255 & data[i];
|
|
1068
|
-
bits += 8;
|
|
1069
|
-
while (bits > encoding.bits) {
|
|
1070
|
-
bits -= encoding.bits;
|
|
1071
|
-
out += encoding.chars[mask & buffer >> bits];
|
|
1184
|
+
async exchangeCustomForIdAndRefreshTokens(apiKey, params, options) {
|
|
1185
|
+
this.requireApiKey(apiKey);
|
|
1186
|
+
const headers = {};
|
|
1187
|
+
if (options?.referer) {
|
|
1188
|
+
headers["Referer"] = options.referer;
|
|
1072
1189
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
out += encoding.chars[mask & buffer << encoding.bits - bits];
|
|
1076
|
-
}
|
|
1077
|
-
if (pad) {
|
|
1078
|
-
while (out.length * encoding.bits & 7) {
|
|
1079
|
-
out += "=";
|
|
1190
|
+
if (options?.appCheckToken) {
|
|
1191
|
+
headers["X-Firebase-AppCheck"] = options.appCheckToken;
|
|
1080
1192
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
if (result instanceof Uint8Array) {
|
|
1091
|
-
throw new Error("Unexpected Uint8Array result from JWK import");
|
|
1193
|
+
const response = await this.request({
|
|
1194
|
+
endpoint: "signInWithCustomToken",
|
|
1195
|
+
method: "POST",
|
|
1196
|
+
apiKey,
|
|
1197
|
+
bodyParams: params,
|
|
1198
|
+
headerParams: headers
|
|
1199
|
+
});
|
|
1200
|
+
if (response.errors) {
|
|
1201
|
+
throw new Error(response.errors[0].message);
|
|
1092
1202
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
return await (0, import_jose2.importX509)(keyString, algorithm);
|
|
1098
|
-
}
|
|
1099
|
-
if (keyString.includes("-----BEGIN PUBLIC KEY-----")) {
|
|
1100
|
-
return await (0, import_jose2.importSPKI)(keyString, algorithm);
|
|
1101
|
-
}
|
|
1102
|
-
try {
|
|
1103
|
-
return await (0, import_jose2.importSPKI)(keyString, algorithm);
|
|
1104
|
-
} catch (error) {
|
|
1105
|
-
throw new Error(
|
|
1106
|
-
`Unsupported key format. Supported formats: X.509 certificate (PEM), SPKI (PEM), JWK (JSON object or string). Error: ${error}`
|
|
1107
|
-
);
|
|
1203
|
+
if (!response.data) {
|
|
1204
|
+
throw new Error("No data received from Firebase token exchange");
|
|
1205
|
+
}
|
|
1206
|
+
return response.data;
|
|
1108
1207
|
}
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
// src/jwt/algorithms.ts
|
|
1112
|
-
var algToHash = {
|
|
1113
|
-
RS256: "SHA-256",
|
|
1114
|
-
RS384: "SHA-384",
|
|
1115
|
-
RS512: "SHA-512"
|
|
1116
1208
|
};
|
|
1117
|
-
var algs = Object.keys(algToHash);
|
|
1118
1209
|
|
|
1119
|
-
// src/
|
|
1120
|
-
var
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1210
|
+
// src/fireRestApi/endpoints/UserData.ts
|
|
1211
|
+
var UserData = class extends AbstractAPI {
|
|
1212
|
+
async getUserData(apiKey, params, options) {
|
|
1213
|
+
this.requireApiKey(apiKey);
|
|
1214
|
+
const { ...restParams } = params;
|
|
1215
|
+
const headers = {};
|
|
1216
|
+
if (options?.referer) {
|
|
1217
|
+
headers["Referer"] = options.referer;
|
|
1218
|
+
}
|
|
1219
|
+
return this.request({
|
|
1220
|
+
endpoint: "lookup",
|
|
1221
|
+
method: "POST",
|
|
1222
|
+
apiKey,
|
|
1223
|
+
bodyParams: restParams,
|
|
1224
|
+
headerParams: headers
|
|
1128
1225
|
});
|
|
1129
1226
|
}
|
|
1130
1227
|
};
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1228
|
+
|
|
1229
|
+
// src/runtime.ts
|
|
1230
|
+
var import_crypto = require("#crypto");
|
|
1231
|
+
var globalFetch = fetch.bind(globalThis);
|
|
1232
|
+
var runtime = {
|
|
1233
|
+
crypto: import_crypto.webcrypto,
|
|
1234
|
+
get fetch() {
|
|
1235
|
+
return process.env.NODE_ENV === "test" ? fetch : globalFetch;
|
|
1236
|
+
},
|
|
1237
|
+
AbortController: globalThis.AbortController,
|
|
1238
|
+
Blob: globalThis.Blob,
|
|
1239
|
+
FormData: globalThis.FormData,
|
|
1240
|
+
Headers: globalThis.Headers,
|
|
1241
|
+
Request: globalThis.Request,
|
|
1242
|
+
Response: globalThis.Response
|
|
1138
1243
|
};
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
message: `JWT is expired. Expiry date: ${expiryDate.toUTCString()}, Current date: ${currentDate.toUTCString()}.`
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1244
|
+
|
|
1245
|
+
// src/fireRestApi/emulator.ts
|
|
1246
|
+
var FIREBASE_AUTH_EMULATOR_HOST = process.env.FIREBASE_AUTH_EMULATOR_HOST;
|
|
1247
|
+
function emulatorHost() {
|
|
1248
|
+
if (typeof process === "undefined") return void 0;
|
|
1249
|
+
return FIREBASE_AUTH_EMULATOR_HOST;
|
|
1250
|
+
}
|
|
1251
|
+
function useEmulator() {
|
|
1252
|
+
return !!emulatorHost();
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// src/fireRestApi/endpointUrl.ts
|
|
1256
|
+
var lookupEndpoint = (apiKey) => {
|
|
1257
|
+
return `https://identitytoolkit.googleapis.com/v1/accounts:lookup?key=${apiKey}`;
|
|
1156
1258
|
};
|
|
1157
|
-
var
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
}
|
|
1259
|
+
var getRefreshTokenEndpoint = (apiKey) => {
|
|
1260
|
+
return `https://securetoken.googleapis.com/v1/token?key=${apiKey}`;
|
|
1261
|
+
};
|
|
1262
|
+
var signInWithPassword = (apiKey) => {
|
|
1263
|
+
return `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${apiKey}`;
|
|
1264
|
+
};
|
|
1265
|
+
var signUpEndpoint = (apiKey) => {
|
|
1266
|
+
return `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${apiKey}`;
|
|
1267
|
+
};
|
|
1268
|
+
var sendOobCode = (apiKey) => {
|
|
1269
|
+
return `https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode?key=${apiKey}`;
|
|
1270
|
+
};
|
|
1271
|
+
var getCustomTokenEndpoint = (apiKey) => {
|
|
1272
|
+
if (useEmulator() && FIREBASE_AUTH_EMULATOR_HOST) {
|
|
1273
|
+
let protocol = "http://";
|
|
1274
|
+
if (FIREBASE_AUTH_EMULATOR_HOST.startsWith("http://")) {
|
|
1275
|
+
protocol = "";
|
|
1276
|
+
}
|
|
1277
|
+
return `${protocol}${FIREBASE_AUTH_EMULATOR_HOST}/identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`;
|
|
1176
1278
|
}
|
|
1279
|
+
return `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`;
|
|
1177
1280
|
};
|
|
1178
1281
|
|
|
1179
|
-
// src/
|
|
1180
|
-
var
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1282
|
+
// src/fireRestApi/request.ts
|
|
1283
|
+
var FIREBASE_ENDPOINT_MAP = {
|
|
1284
|
+
refreshToken: getRefreshTokenEndpoint,
|
|
1285
|
+
signInWithPassword,
|
|
1286
|
+
signUp: signUpEndpoint,
|
|
1287
|
+
signInWithCustomToken: getCustomTokenEndpoint,
|
|
1288
|
+
passwordReset: sendOobCode,
|
|
1289
|
+
sendOobCode,
|
|
1290
|
+
lookup: lookupEndpoint
|
|
1291
|
+
};
|
|
1292
|
+
function createRequest(options) {
|
|
1293
|
+
const requestFn = async (requestOptions) => {
|
|
1294
|
+
const { endpoint, method, apiKey, queryParams, headerParams, bodyParams, formData } = requestOptions;
|
|
1295
|
+
if (!apiKey) {
|
|
1296
|
+
return {
|
|
1297
|
+
data: null,
|
|
1298
|
+
errors: [
|
|
1299
|
+
{
|
|
1300
|
+
domain: "none",
|
|
1301
|
+
reason: "invalid_parameter",
|
|
1302
|
+
message: "Firebase API key is required",
|
|
1303
|
+
code: "400"
|
|
1304
|
+
}
|
|
1305
|
+
]
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
const endpointUrl = FIREBASE_ENDPOINT_MAP[endpoint](apiKey);
|
|
1309
|
+
const finalUrl = new URL(endpointUrl);
|
|
1310
|
+
if (queryParams) {
|
|
1311
|
+
Object.entries(queryParams).forEach(([key, value]) => {
|
|
1312
|
+
if (value) {
|
|
1313
|
+
[value].flat().forEach((v) => finalUrl.searchParams.append(key, v));
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
const headers = {
|
|
1318
|
+
...headerParams
|
|
1196
1319
|
};
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1320
|
+
let res;
|
|
1321
|
+
try {
|
|
1322
|
+
if (formData) {
|
|
1323
|
+
res = await runtime.fetch(finalUrl.href, {
|
|
1324
|
+
method,
|
|
1325
|
+
headers,
|
|
1326
|
+
body: formData
|
|
1327
|
+
});
|
|
1328
|
+
} else {
|
|
1329
|
+
headers["Content-Type"] = "application/json";
|
|
1330
|
+
const hasBody = method !== "GET" && bodyParams && Object.keys(bodyParams).length > 0;
|
|
1331
|
+
const body = hasBody ? { body: JSON.stringify(bodyParams) } : null;
|
|
1332
|
+
res = await runtime.fetch(finalUrl.href, {
|
|
1333
|
+
method,
|
|
1334
|
+
headers,
|
|
1335
|
+
...body
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
const isJSONResponse = res?.headers && res.headers?.get(constants.Headers.ContentType)?.includes(constants.ContentTypes.Json);
|
|
1339
|
+
let responseBody;
|
|
1340
|
+
try {
|
|
1341
|
+
const text = await res.text();
|
|
1342
|
+
try {
|
|
1343
|
+
responseBody = JSON.parse(text);
|
|
1344
|
+
} catch {
|
|
1345
|
+
responseBody = text;
|
|
1346
|
+
}
|
|
1347
|
+
} catch (e) {
|
|
1348
|
+
responseBody = null;
|
|
1349
|
+
}
|
|
1350
|
+
if (!res.ok) {
|
|
1351
|
+
return {
|
|
1352
|
+
data: null,
|
|
1353
|
+
errors: parseErrors(responseBody),
|
|
1354
|
+
status: res?.status,
|
|
1355
|
+
statusText: res?.statusText
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
return {
|
|
1359
|
+
data: responseBody,
|
|
1360
|
+
errors: null
|
|
1361
|
+
};
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
if (error instanceof Error) {
|
|
1364
|
+
return {
|
|
1365
|
+
data: null,
|
|
1366
|
+
errors: [
|
|
1367
|
+
{
|
|
1368
|
+
domain: "none",
|
|
1369
|
+
reason: "request_failed",
|
|
1370
|
+
message: error.message || "An unexpected error occurred",
|
|
1371
|
+
code: "500"
|
|
1372
|
+
}
|
|
1373
|
+
]
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1205
1376
|
return {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
})
|
|
1211
|
-
]
|
|
1377
|
+
data: null,
|
|
1378
|
+
errors: parseErrors(error),
|
|
1379
|
+
status: res?.status,
|
|
1380
|
+
statusText: res?.statusText
|
|
1212
1381
|
};
|
|
1213
1382
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
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) {
|
|
1229
|
-
return {
|
|
1230
|
-
errors: [
|
|
1231
|
-
new TokenVerificationError({
|
|
1232
|
-
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
1233
|
-
message: `${error.message || "Invalid Token or Protected Header formatting"} (Token length: ${token?.length}, First 10 chars: ${token?.substring(0, 10)}...)`
|
|
1234
|
-
})
|
|
1235
|
-
]
|
|
1236
|
-
};
|
|
1237
|
-
}
|
|
1383
|
+
};
|
|
1384
|
+
return requestFn;
|
|
1238
1385
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1386
|
+
function parseErrors(data) {
|
|
1387
|
+
let parsedData = data;
|
|
1388
|
+
if (typeof data === "string") {
|
|
1389
|
+
try {
|
|
1390
|
+
parsedData = JSON.parse(data);
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
return [];
|
|
1393
|
+
}
|
|
1245
1394
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
verifyHeaderKid(header.kid);
|
|
1249
|
-
verifySubClaim(payload.sub);
|
|
1250
|
-
verifyExpirationClaim(payload.exp, clockSkew);
|
|
1251
|
-
verifyIssuedAtClaim(payload.iat, clockSkew);
|
|
1252
|
-
} catch (error) {
|
|
1253
|
-
return { errors: [error] };
|
|
1395
|
+
if (!parsedData || typeof parsedData !== "object") {
|
|
1396
|
+
return [];
|
|
1254
1397
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
errors
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1398
|
+
if ("error" in parsedData && typeof parsedData.error === "object" && parsedData.error !== null) {
|
|
1399
|
+
const errorObj = parsedData.error;
|
|
1400
|
+
if ("errors" in errorObj && Array.isArray(errorObj.errors) && errorObj.errors.length > 0) {
|
|
1401
|
+
return errorObj.errors.map((err) => parseError({
|
|
1402
|
+
code: errorObj.code || "unknown_error",
|
|
1403
|
+
message: err.message || "Unknown error",
|
|
1404
|
+
domain: err.domain,
|
|
1405
|
+
reason: err.reason
|
|
1406
|
+
}));
|
|
1407
|
+
}
|
|
1408
|
+
return [parseError({
|
|
1409
|
+
code: errorObj.code?.toString() || "unknown_error",
|
|
1410
|
+
message: errorObj.message || "Unknown error",
|
|
1411
|
+
domain: errorObj.domain || "unknown",
|
|
1412
|
+
reason: errorObj.reason || errorObj.code?.toString() || "unknown_error"
|
|
1413
|
+
})];
|
|
1265
1414
|
}
|
|
1266
|
-
|
|
1267
|
-
return { data: decodedIdToken };
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
// src/tokens/keys.ts
|
|
1271
|
-
var cache = {};
|
|
1272
|
-
var lastUpdatedAt = 0;
|
|
1273
|
-
var googleExpiresAt = 0;
|
|
1274
|
-
function getFromCache(kid) {
|
|
1275
|
-
return cache[kid];
|
|
1415
|
+
return [];
|
|
1276
1416
|
}
|
|
1277
|
-
function
|
|
1278
|
-
return
|
|
1417
|
+
function parseError(error) {
|
|
1418
|
+
return {
|
|
1419
|
+
domain: error.domain,
|
|
1420
|
+
reason: error.reason,
|
|
1421
|
+
message: error.message,
|
|
1422
|
+
code: error.code
|
|
1423
|
+
};
|
|
1279
1424
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1425
|
+
|
|
1426
|
+
// src/fireRestApi/createFireApi.ts
|
|
1427
|
+
function createFireApi(options) {
|
|
1428
|
+
const request = createRequest(options);
|
|
1429
|
+
return {
|
|
1430
|
+
appCheck: new AppCheckApi(request),
|
|
1431
|
+
email: new EmailApi(request),
|
|
1432
|
+
password: new PasswordApi(request),
|
|
1433
|
+
signIn: new SignInApi(request),
|
|
1434
|
+
signInToken: new SignInTokenApi(request),
|
|
1435
|
+
signUp: new SignUpApi(request),
|
|
1436
|
+
tokens: new TokenApi(request),
|
|
1437
|
+
userData: new UserData(request)
|
|
1438
|
+
};
|
|
1283
1439
|
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
}
|
|
1293
|
-
const data = await response.json();
|
|
1294
|
-
const expiresAt = getExpiresAt(response);
|
|
1440
|
+
|
|
1441
|
+
// src/utils/options.ts
|
|
1442
|
+
var defaultOptions = {
|
|
1443
|
+
apiKey: void 0,
|
|
1444
|
+
apiUrl: void 0,
|
|
1445
|
+
apiVersion: void 0
|
|
1446
|
+
};
|
|
1447
|
+
function mergePreDefinedOptions(userOptions = {}) {
|
|
1295
1448
|
return {
|
|
1296
|
-
|
|
1297
|
-
|
|
1449
|
+
...defaultOptions,
|
|
1450
|
+
...userOptions
|
|
1298
1451
|
};
|
|
1299
1452
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
reason: TokenVerificationErrorReason.RemoteJWKFailedToLoad
|
|
1311
|
-
});
|
|
1453
|
+
|
|
1454
|
+
// src/tokens/request.ts
|
|
1455
|
+
var import_ms = require("@tern-secure/shared/ms");
|
|
1456
|
+
|
|
1457
|
+
// src/jwt/guardReturn.ts
|
|
1458
|
+
function createJwtGuard(decodedFn) {
|
|
1459
|
+
return (...args) => {
|
|
1460
|
+
const { data, errors } = decodedFn(...args);
|
|
1461
|
+
if (errors) {
|
|
1462
|
+
throw errors[0];
|
|
1312
1463
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
setInCache(keyId, cert2);
|
|
1316
|
-
});
|
|
1317
|
-
}
|
|
1318
|
-
const cert = getFromCache(kid);
|
|
1319
|
-
if (!cert) {
|
|
1320
|
-
getCacheValues();
|
|
1321
|
-
const availableKids = Object.keys(cache).sort().join(", ");
|
|
1322
|
-
throw new TokenVerificationError({
|
|
1323
|
-
message: `No public key found for kid "${kid}". Available kids: [${availableKids}]`,
|
|
1324
|
-
reason: TokenVerificationErrorReason.TokenInvalid
|
|
1325
|
-
});
|
|
1326
|
-
}
|
|
1327
|
-
return cert;
|
|
1328
|
-
}
|
|
1329
|
-
function isCacheExpired() {
|
|
1330
|
-
const now = Date.now();
|
|
1331
|
-
if (lastUpdatedAt === -1) {
|
|
1332
|
-
return false;
|
|
1333
|
-
}
|
|
1334
|
-
const cacheAge = now - lastUpdatedAt;
|
|
1335
|
-
const maxCacheAge = MAX_CACHE_LAST_UPDATED_AT_SECONDS * 1e3;
|
|
1336
|
-
const localCacheExpired = cacheAge >= maxCacheAge;
|
|
1337
|
-
const googleCacheExpired = now >= googleExpiresAt;
|
|
1338
|
-
const isExpired = localCacheExpired || googleCacheExpired;
|
|
1339
|
-
if (isExpired) {
|
|
1340
|
-
cache = {};
|
|
1341
|
-
}
|
|
1342
|
-
return isExpired;
|
|
1343
|
-
}
|
|
1344
|
-
function getExpiresAt(res) {
|
|
1345
|
-
const cacheControlHeader = res.headers.get("cache-control");
|
|
1346
|
-
if (!cacheControlHeader) {
|
|
1347
|
-
return Date.now() + DEFAULT_CACHE_DURATION;
|
|
1348
|
-
}
|
|
1349
|
-
const maxAgeMatch = cacheControlHeader.match(CACHE_CONTROL_REGEX);
|
|
1350
|
-
const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1], 10) : DEFAULT_CACHE_DURATION / 1e3;
|
|
1351
|
-
return Date.now() + maxAge * 1e3;
|
|
1464
|
+
return data;
|
|
1465
|
+
};
|
|
1352
1466
|
}
|
|
1353
1467
|
|
|
1354
|
-
// src/
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
errors: [
|
|
1365
|
-
new TokenVerificationError({
|
|
1366
|
-
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
1367
|
-
message: 'JWT "kid" header is missing.'
|
|
1368
|
-
})
|
|
1369
|
-
]
|
|
1370
|
-
};
|
|
1468
|
+
// src/jwt/jwt.ts
|
|
1469
|
+
var import_jose3 = require("jose");
|
|
1470
|
+
|
|
1471
|
+
// src/jwt/customJwt.ts
|
|
1472
|
+
var import_jose4 = require("jose");
|
|
1473
|
+
var CustomTokenError = class extends Error {
|
|
1474
|
+
constructor(message, code) {
|
|
1475
|
+
super(message);
|
|
1476
|
+
this.code = code;
|
|
1477
|
+
this.name = "CustomTokenError";
|
|
1371
1478
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1479
|
+
};
|
|
1480
|
+
var RESERVED_CLAIMS = [
|
|
1481
|
+
"acr",
|
|
1482
|
+
"amr",
|
|
1483
|
+
"at_hash",
|
|
1484
|
+
"aud",
|
|
1485
|
+
"auth_time",
|
|
1486
|
+
"azp",
|
|
1487
|
+
"cnf",
|
|
1488
|
+
"c_hash",
|
|
1489
|
+
"exp",
|
|
1490
|
+
"firebase",
|
|
1491
|
+
"iat",
|
|
1492
|
+
"iss",
|
|
1493
|
+
"jti",
|
|
1494
|
+
"nbf",
|
|
1495
|
+
"nonce",
|
|
1496
|
+
"sub"
|
|
1497
|
+
];
|
|
1498
|
+
async function createCustomTokenJwt(uid, developerClaims) {
|
|
1499
|
+
try {
|
|
1500
|
+
const privateKey = process.env.FIREBASE_PRIVATE_KEY;
|
|
1501
|
+
const clientEmail = process.env.FIREBASE_CLIENT_EMAIL;
|
|
1502
|
+
if (!privateKey || !clientEmail) {
|
|
1375
1503
|
return {
|
|
1376
1504
|
errors: [
|
|
1377
|
-
new
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1505
|
+
new CustomTokenError(
|
|
1506
|
+
"Missing FIREBASE_PRIVATE_KEY or FIREBASE_CLIENT_EMAIL environment variables",
|
|
1507
|
+
"MISSING_ENV_VARS"
|
|
1508
|
+
)
|
|
1381
1509
|
]
|
|
1382
1510
|
};
|
|
1383
1511
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1512
|
+
if (!uid || typeof uid !== "string") {
|
|
1513
|
+
return {
|
|
1514
|
+
errors: [new CustomTokenError("uid must be a non-empty string", "INVALID_UID")]
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
if (uid.length > 128) {
|
|
1518
|
+
return {
|
|
1519
|
+
errors: [new CustomTokenError("uid must not exceed 128 characters", "UID_TOO_LONG")]
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
if (developerClaims) {
|
|
1523
|
+
for (const claim of Object.keys(developerClaims)) {
|
|
1524
|
+
if (RESERVED_CLAIMS.includes(claim)) {
|
|
1525
|
+
return {
|
|
1526
|
+
errors: [new CustomTokenError(`Custom claim '${claim}' is reserved`, "RESERVED_CLAIM")]
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1388
1530
|
}
|
|
1531
|
+
const expiresIn = 3600;
|
|
1532
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1533
|
+
const parsedPrivateKey = await (0, import_jose4.importPKCS8)(privateKey.replace(/\\n/g, "\n"), "RS256");
|
|
1534
|
+
const payload = {
|
|
1535
|
+
iss: clientEmail,
|
|
1536
|
+
sub: clientEmail,
|
|
1537
|
+
aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
|
|
1538
|
+
iat: now,
|
|
1539
|
+
exp: now + expiresIn,
|
|
1540
|
+
uid,
|
|
1541
|
+
...developerClaims
|
|
1542
|
+
};
|
|
1543
|
+
const jwt = await new import_jose4.SignJWT(payload).setProtectedHeader({ alg: "RS256", typ: "JWT" }).setIssuedAt(now).setExpirationTime(now + expiresIn).setIssuer(clientEmail).setSubject(clientEmail).setAudience(
|
|
1544
|
+
"https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
|
|
1545
|
+
).sign(parsedPrivateKey);
|
|
1389
1546
|
return {
|
|
1390
|
-
|
|
1547
|
+
data: jwt
|
|
1548
|
+
};
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
1551
|
+
return {
|
|
1552
|
+
errors: [
|
|
1553
|
+
new CustomTokenError(`Failed to create custom token: ${message}`, "TOKEN_CREATION_FAILED")
|
|
1554
|
+
]
|
|
1391
1555
|
};
|
|
1392
1556
|
}
|
|
1393
1557
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
throw errors[0];
|
|
1401
|
-
}
|
|
1402
|
-
return data;
|
|
1403
|
-
};
|
|
1558
|
+
async function createCustomToken(uid, developerClaims) {
|
|
1559
|
+
const { data, errors } = await createCustomTokenJwt(uid, developerClaims);
|
|
1560
|
+
if (errors) {
|
|
1561
|
+
throw errors[0];
|
|
1562
|
+
}
|
|
1563
|
+
return data;
|
|
1404
1564
|
}
|
|
1405
1565
|
|
|
1406
|
-
// src/jwt/jwt.ts
|
|
1407
|
-
var import_jose4 = require("jose");
|
|
1408
|
-
|
|
1409
1566
|
// src/jwt/signJwt.ts
|
|
1410
1567
|
var import_jose5 = require("jose");
|
|
1568
|
+
|
|
1569
|
+
// src/utils/fetcher.ts
|
|
1570
|
+
async function getDetailFromResponse(response) {
|
|
1571
|
+
const json = await response.json();
|
|
1572
|
+
if (!json) {
|
|
1573
|
+
return "Missing error payload";
|
|
1574
|
+
}
|
|
1575
|
+
let detail = typeof json.error === "string" ? json.error : json.error?.message ?? "Missing error payload";
|
|
1576
|
+
if (json.error_description) {
|
|
1577
|
+
detail += " (" + json.error_description + ")";
|
|
1578
|
+
}
|
|
1579
|
+
return detail;
|
|
1580
|
+
}
|
|
1581
|
+
async function fetchText(url, init) {
|
|
1582
|
+
return (await fetchAny(url, init)).text();
|
|
1583
|
+
}
|
|
1584
|
+
async function fetchJson(url, init) {
|
|
1585
|
+
return (await fetchAny(url, init)).json();
|
|
1586
|
+
}
|
|
1587
|
+
async function fetchAny(url, init) {
|
|
1588
|
+
const response = await fetch(url, init);
|
|
1589
|
+
if (!response.ok) {
|
|
1590
|
+
throw new Error(await getDetailFromResponse(response));
|
|
1591
|
+
}
|
|
1592
|
+
return response;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
// src/jwt/types.ts
|
|
1411
1596
|
var ALGORITHM_RS256 = "RS256";
|
|
1597
|
+
|
|
1598
|
+
// src/jwt/signJwt.ts
|
|
1412
1599
|
async function ternSignJwt(opts) {
|
|
1413
1600
|
const { payload, privateKey, keyId } = opts;
|
|
1414
1601
|
let key;
|
|
@@ -1422,112 +1609,382 @@ async function ternSignJwt(opts) {
|
|
|
1422
1609
|
}
|
|
1423
1610
|
return new import_jose5.SignJWT(payload).setProtectedHeader({ alg: ALGORITHM_RS256, kid: keyId }).sign(key);
|
|
1424
1611
|
}
|
|
1612
|
+
function formatBase64(value) {
|
|
1613
|
+
return value.replace(/\//g, "_").replace(/\+/g, "-").replace(/=+$/, "");
|
|
1614
|
+
}
|
|
1615
|
+
function encodeSegment(segment) {
|
|
1616
|
+
const value = JSON.stringify(segment);
|
|
1617
|
+
return formatBase64(import_jose5.base64url.encode(value));
|
|
1618
|
+
}
|
|
1619
|
+
async function ternSignBlob({
|
|
1620
|
+
payload,
|
|
1621
|
+
serviceAccountId,
|
|
1622
|
+
accessToken
|
|
1623
|
+
}) {
|
|
1624
|
+
const url = `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${serviceAccountId}:signBlob`;
|
|
1625
|
+
const header = {
|
|
1626
|
+
alg: ALGORITHM_RS256,
|
|
1627
|
+
typ: "JWT"
|
|
1628
|
+
};
|
|
1629
|
+
const token = `${encodeSegment(header)}.${encodeSegment(payload)}`;
|
|
1630
|
+
const request = {
|
|
1631
|
+
method: "POST",
|
|
1632
|
+
headers: {
|
|
1633
|
+
Authorization: `Bearer ${accessToken}`
|
|
1634
|
+
},
|
|
1635
|
+
body: JSON.stringify({ payload: import_jose5.base64url.encode(token) })
|
|
1636
|
+
};
|
|
1637
|
+
const response = await fetchAny(url, request);
|
|
1638
|
+
const blob = await response.blob();
|
|
1639
|
+
const key = await blob.text();
|
|
1640
|
+
const { signedBlob } = JSON.parse(key);
|
|
1641
|
+
return `${token}.${formatBase64(signedBlob)}`;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// src/jwt/crypto-signer.ts
|
|
1645
|
+
var ServiceAccountSigner = class {
|
|
1646
|
+
constructor(credential, tenantId) {
|
|
1647
|
+
this.credential = credential;
|
|
1648
|
+
this.tenantId = tenantId;
|
|
1649
|
+
}
|
|
1650
|
+
async getAccountId() {
|
|
1651
|
+
return Promise.resolve(this.credential.clientEmail);
|
|
1652
|
+
}
|
|
1653
|
+
async sign(payload) {
|
|
1654
|
+
if (this.tenantId) {
|
|
1655
|
+
payload.tenant_id = this.tenantId;
|
|
1656
|
+
}
|
|
1657
|
+
return ternSignJwt({ payload, privateKey: this.credential.privateKey });
|
|
1658
|
+
}
|
|
1659
|
+
};
|
|
1660
|
+
var IAMSigner = class {
|
|
1661
|
+
algorithm = ALGORITHM_RS256;
|
|
1662
|
+
credential;
|
|
1663
|
+
tenantId;
|
|
1664
|
+
serviceAccountId;
|
|
1665
|
+
constructor(credential, tenantId, serviceAccountId) {
|
|
1666
|
+
this.credential = credential;
|
|
1667
|
+
this.tenantId = tenantId;
|
|
1668
|
+
this.serviceAccountId = serviceAccountId;
|
|
1669
|
+
}
|
|
1670
|
+
async sign(payload) {
|
|
1671
|
+
if (this.tenantId) {
|
|
1672
|
+
payload.tenant_id = this.tenantId;
|
|
1673
|
+
}
|
|
1674
|
+
const serviceAccount = await this.getAccountId();
|
|
1675
|
+
const accessToken = await this.credential.getAccessToken();
|
|
1676
|
+
return ternSignBlob({
|
|
1677
|
+
accessToken: accessToken.accessToken,
|
|
1678
|
+
serviceAccountId: serviceAccount,
|
|
1679
|
+
payload
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
async getAccountId() {
|
|
1683
|
+
if (this.serviceAccountId) {
|
|
1684
|
+
return this.serviceAccountId;
|
|
1685
|
+
}
|
|
1686
|
+
const token = await this.credential.getAccessToken();
|
|
1687
|
+
const url = "http://metadata/computeMetadata/v1/instance/service-accounts/default/email";
|
|
1688
|
+
const request = {
|
|
1689
|
+
method: "GET",
|
|
1690
|
+
headers: {
|
|
1691
|
+
"Metadata-Flavor": "Google",
|
|
1692
|
+
Authorization: `Bearer ${token.accessToken}`
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
return this.serviceAccountId = await fetchText(url, request);
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1425
1698
|
|
|
1426
1699
|
// src/jwt/index.ts
|
|
1427
1700
|
var ternDecodeJwt2 = createJwtGuard(ternDecodeJwt);
|
|
1428
1701
|
|
|
1429
|
-
// src/
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1702
|
+
// src/utils/token-generator.ts
|
|
1703
|
+
function cryptoSignerFromCredential(credential, tenantId, serviceAccountId) {
|
|
1704
|
+
if (credential instanceof ServiceAccountManager) {
|
|
1705
|
+
return new ServiceAccountSigner(credential, tenantId);
|
|
1706
|
+
}
|
|
1707
|
+
return new IAMSigner(credential, tenantId, serviceAccountId);
|
|
1708
|
+
}
|
|
1435
1709
|
|
|
1436
|
-
// src/
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1710
|
+
// src/app-check/AppCheckApi.ts
|
|
1711
|
+
function getSdkVersion2() {
|
|
1712
|
+
return "12.7.0";
|
|
1713
|
+
}
|
|
1714
|
+
var FIREBASE_APP_CHECK_CONFIG_HEADERS2 = {
|
|
1715
|
+
"X-Firebase-Client": `fire-admin-node/${getSdkVersion2()}`
|
|
1716
|
+
};
|
|
1717
|
+
var AppCheckApi2 = class {
|
|
1718
|
+
constructor(credential) {
|
|
1719
|
+
this.credential = credential;
|
|
1441
1720
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1721
|
+
async exchangeToken(params) {
|
|
1722
|
+
const { projectId, appId, customToken, limitedUse = false } = params;
|
|
1723
|
+
const token = await this.credential.getAccessToken(false);
|
|
1724
|
+
if (!projectId || !appId) {
|
|
1725
|
+
throw new Error("Project ID and App ID are required for App Check token exchange");
|
|
1726
|
+
}
|
|
1727
|
+
const endpoint = `https://firebaseappcheck.googleapis.com/v1/projects/${projectId}/apps/${appId}:exchangeCustomToken`;
|
|
1728
|
+
const headers = {
|
|
1729
|
+
"Content-Type": "application/json",
|
|
1730
|
+
"Authorization": `Bearer ${token.accessToken}`
|
|
1731
|
+
};
|
|
1732
|
+
try {
|
|
1733
|
+
const response = await fetch(endpoint, {
|
|
1734
|
+
method: "POST",
|
|
1735
|
+
headers,
|
|
1736
|
+
body: JSON.stringify({ customToken, limitedUse })
|
|
1737
|
+
});
|
|
1738
|
+
if (!response.ok) {
|
|
1739
|
+
const errorText = await response.text();
|
|
1740
|
+
throw new Error(`App Check token exchange failed: ${response.status} ${errorText}`);
|
|
1741
|
+
}
|
|
1742
|
+
const data = await response.json();
|
|
1743
|
+
return {
|
|
1744
|
+
token: data.token,
|
|
1745
|
+
ttl: data.ttl
|
|
1746
|
+
};
|
|
1747
|
+
} catch (error) {
|
|
1748
|
+
console.warn("[ternsecure - appcheck api]unexpected error:", error);
|
|
1749
|
+
throw error;
|
|
1750
|
+
}
|
|
1445
1751
|
}
|
|
1446
|
-
|
|
1752
|
+
async exchangeDebugToken(params) {
|
|
1753
|
+
const { projectId, appId, customToken, accessToken, limitedUse = false } = params;
|
|
1754
|
+
if (!projectId || !appId) {
|
|
1755
|
+
throw new Error("Project ID and App ID are required for App Check token exchange");
|
|
1756
|
+
}
|
|
1757
|
+
const endpoint = `https://firebaseappcheck.googleapis.com/v1beta/projects/${projectId}/apps/${appId}:exchangeDebugToken`;
|
|
1758
|
+
const headers = {
|
|
1759
|
+
...FIREBASE_APP_CHECK_CONFIG_HEADERS2,
|
|
1760
|
+
"Authorization": `Bearer ${accessToken}`
|
|
1761
|
+
};
|
|
1762
|
+
const body = {
|
|
1763
|
+
customToken,
|
|
1764
|
+
limitedUse
|
|
1765
|
+
};
|
|
1766
|
+
try {
|
|
1767
|
+
const response = await fetch(endpoint, {
|
|
1768
|
+
method: "POST",
|
|
1769
|
+
headers,
|
|
1770
|
+
body: JSON.stringify(body)
|
|
1771
|
+
});
|
|
1772
|
+
if (!response.ok) {
|
|
1773
|
+
const errorText = await response.text();
|
|
1774
|
+
throw new Error(`App Check token exchange failed: ${response.status} ${errorText}`);
|
|
1775
|
+
}
|
|
1776
|
+
const data = await response.json();
|
|
1777
|
+
return {
|
|
1778
|
+
token: data.token,
|
|
1779
|
+
ttl: data.ttl
|
|
1780
|
+
};
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
console.warn("[ternsecure - appcheck api]unexpected error:", error);
|
|
1783
|
+
throw error;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
};
|
|
1787
|
+
|
|
1788
|
+
// src/app-check/generator.ts
|
|
1789
|
+
function transformMillisecondsToSecondsString(milliseconds) {
|
|
1790
|
+
let duration;
|
|
1791
|
+
const seconds = Math.floor(milliseconds / 1e3);
|
|
1792
|
+
const nanos = Math.floor((milliseconds - seconds * 1e3) * 1e6);
|
|
1793
|
+
if (nanos > 0) {
|
|
1794
|
+
let nanoString = nanos.toString();
|
|
1795
|
+
while (nanoString.length < 9) {
|
|
1796
|
+
nanoString = "0" + nanoString;
|
|
1797
|
+
}
|
|
1798
|
+
duration = `${seconds}.${nanoString}s`;
|
|
1799
|
+
} else {
|
|
1800
|
+
duration = `${seconds}s`;
|
|
1801
|
+
}
|
|
1802
|
+
return duration;
|
|
1447
1803
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1804
|
+
var AppCheckTokenGenerator = class {
|
|
1805
|
+
signer;
|
|
1806
|
+
constructor(signer) {
|
|
1807
|
+
this.signer = signer;
|
|
1808
|
+
}
|
|
1809
|
+
async createCustomToken(appId, options) {
|
|
1810
|
+
if (!appId) {
|
|
1811
|
+
throw new Error(
|
|
1812
|
+
"appId is invalid"
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1815
|
+
let customOptions = {};
|
|
1816
|
+
if (typeof options !== "undefined") {
|
|
1817
|
+
customOptions = this.validateTokenOptions(options);
|
|
1818
|
+
}
|
|
1819
|
+
const account = await this.signer.getAccountId();
|
|
1820
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
1821
|
+
const body = {
|
|
1822
|
+
iss: account,
|
|
1823
|
+
sub: account,
|
|
1824
|
+
app_id: appId,
|
|
1825
|
+
aud: FIREBASE_APP_CHECK_AUDIENCE,
|
|
1826
|
+
exp: iat + ONE_MINUTE_IN_SECONDS * 5,
|
|
1827
|
+
iat,
|
|
1828
|
+
...customOptions
|
|
1829
|
+
};
|
|
1830
|
+
return this.signer.sign(body);
|
|
1831
|
+
}
|
|
1832
|
+
validateTokenOptions(options) {
|
|
1833
|
+
if (typeof options.ttlMillis !== "undefined") {
|
|
1834
|
+
if (options.ttlMillis < ONE_MINUTE_IN_MILLIS * 30 || options.ttlMillis > ONE_DAY_IN_MILLIS * 7) {
|
|
1835
|
+
throw new Error(
|
|
1836
|
+
"ttlMillis must be a duration in milliseconds between 30 minutes and 7 days (inclusive)."
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
return { ttl: transformMillisecondsToSecondsString(options.ttlMillis) };
|
|
1840
|
+
}
|
|
1841
|
+
return {};
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
|
|
1845
|
+
// src/app-check/serverAppCheck.ts
|
|
1846
|
+
var import_redis = require("@upstash/redis");
|
|
1847
|
+
|
|
1848
|
+
// src/utils/admin-init.ts
|
|
1849
|
+
var import_firebase_admin = __toESM(require("firebase-admin"));
|
|
1850
|
+
var import_app_check = require("firebase-admin/app-check");
|
|
1851
|
+
|
|
1852
|
+
// src/utils/config.ts
|
|
1853
|
+
var loadAdminConfig = () => ({
|
|
1854
|
+
projectId: process.env.FIREBASE_PROJECT_ID || "",
|
|
1855
|
+
clientEmail: process.env.FIREBASE_CLIENT_EMAIL || "",
|
|
1856
|
+
privateKey: process.env.FIREBASE_PRIVATE_KEY || ""
|
|
1857
|
+
});
|
|
1858
|
+
var validateAdminConfig = (config) => {
|
|
1859
|
+
const requiredFields = [
|
|
1860
|
+
"projectId",
|
|
1861
|
+
"clientEmail",
|
|
1862
|
+
"privateKey"
|
|
1863
|
+
];
|
|
1864
|
+
const errors = [];
|
|
1865
|
+
requiredFields.forEach((field) => {
|
|
1866
|
+
if (!config[field]) {
|
|
1867
|
+
errors.push(`Missing required field: FIREBASE_${String(field).toUpperCase()}`);
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
return {
|
|
1871
|
+
isValid: errors.length === 0,
|
|
1872
|
+
errors,
|
|
1873
|
+
config
|
|
1874
|
+
};
|
|
1875
|
+
};
|
|
1876
|
+
var initializeAdminConfig = () => {
|
|
1877
|
+
const config = loadAdminConfig();
|
|
1878
|
+
const validationResult = validateAdminConfig(config);
|
|
1879
|
+
if (!validationResult.isValid) {
|
|
1880
|
+
throw new Error(
|
|
1881
|
+
`Firebase Admin configuration validation failed:
|
|
1882
|
+
${validationResult.errors.join("\n")}`
|
|
1883
|
+
);
|
|
1884
|
+
}
|
|
1885
|
+
return config;
|
|
1886
|
+
};
|
|
1887
|
+
|
|
1888
|
+
// src/utils/admin-init.ts
|
|
1889
|
+
if (!import_firebase_admin.default.apps.length) {
|
|
1890
|
+
try {
|
|
1891
|
+
const config = initializeAdminConfig();
|
|
1892
|
+
import_firebase_admin.default.initializeApp({
|
|
1893
|
+
credential: import_firebase_admin.default.credential.cert({
|
|
1894
|
+
...config,
|
|
1895
|
+
privateKey: config.privateKey.replace(/\\n/g, "\n")
|
|
1896
|
+
})
|
|
1897
|
+
});
|
|
1898
|
+
} catch (error) {
|
|
1899
|
+
console.error("Firebase admin initialization error", error);
|
|
1900
|
+
}
|
|
1450
1901
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1902
|
+
var adminTernSecureAuth = import_firebase_admin.default.auth();
|
|
1903
|
+
var adminTernSecureDb = import_firebase_admin.default.firestore();
|
|
1904
|
+
var TernSecureTenantManager = import_firebase_admin.default.auth().tenantManager();
|
|
1905
|
+
var appCheckAdmin = (0, import_app_check.getAppCheck)();
|
|
1906
|
+
|
|
1907
|
+
// src/app-check/verifier.ts
|
|
1908
|
+
var import_jose6 = require("jose");
|
|
1909
|
+
var getPublicKey = async (header, keyURL) => {
|
|
1910
|
+
const jswksUrl = new URL(keyURL);
|
|
1911
|
+
const getKey = (0, import_jose6.createRemoteJWKSet)(jswksUrl);
|
|
1912
|
+
return getKey(header);
|
|
1913
|
+
};
|
|
1914
|
+
var verifyAppCheckToken = async (token, options) => {
|
|
1915
|
+
const { data: decodedResult, errors } = ternDecodeJwt(token);
|
|
1916
|
+
if (errors) {
|
|
1917
|
+
throw errors[0];
|
|
1918
|
+
}
|
|
1919
|
+
const { header } = decodedResult;
|
|
1920
|
+
const { kid } = header;
|
|
1921
|
+
if (!kid) {
|
|
1922
|
+
return {
|
|
1923
|
+
errors: [
|
|
1924
|
+
new TokenVerificationError({
|
|
1925
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
1926
|
+
message: 'JWT "kid" header is missing.'
|
|
1927
|
+
})
|
|
1928
|
+
]
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
try {
|
|
1932
|
+
const getPublicKeyForToken = () => getPublicKey(header, options.keyURL || "");
|
|
1933
|
+
return await verifyAppCheckJwt(token, { ...options, key: getPublicKeyForToken });
|
|
1934
|
+
} catch (error) {
|
|
1935
|
+
if (error instanceof TokenVerificationError) {
|
|
1936
|
+
return { errors: [error] };
|
|
1937
|
+
}
|
|
1938
|
+
return {
|
|
1939
|
+
errors: [error]
|
|
1940
|
+
};
|
|
1455
1941
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
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");
|
|
1942
|
+
};
|
|
1943
|
+
var AppcheckTokenVerifier = class {
|
|
1944
|
+
constructor(credential) {
|
|
1945
|
+
this.credential = credential;
|
|
1465
1946
|
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1947
|
+
verifyToken = async (token, projectId, options) => {
|
|
1948
|
+
const { data, errors } = await verifyAppCheckToken(token, options);
|
|
1949
|
+
if (errors) {
|
|
1950
|
+
throw errors[0];
|
|
1951
|
+
}
|
|
1952
|
+
return data;
|
|
1469
1953
|
};
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1954
|
+
};
|
|
1955
|
+
|
|
1956
|
+
// src/app-check/index.ts
|
|
1957
|
+
var JWKS_URL = "https://firebaseappcheck.googleapis.com/v1/jwks";
|
|
1958
|
+
var AppCheck = class {
|
|
1959
|
+
client;
|
|
1960
|
+
tokenGenerator;
|
|
1961
|
+
appCheckTokenVerifier;
|
|
1962
|
+
limitedUse;
|
|
1963
|
+
constructor(credential, tenantId, limitedUse) {
|
|
1964
|
+
this.client = new AppCheckApi2(credential);
|
|
1965
|
+
this.tokenGenerator = new AppCheckTokenGenerator(
|
|
1966
|
+
cryptoSignerFromCredential(credential, tenantId)
|
|
1967
|
+
);
|
|
1968
|
+
this.appCheckTokenVerifier = new AppcheckTokenVerifier(credential);
|
|
1969
|
+
this.limitedUse = limitedUse;
|
|
1479
1970
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
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
|
|
1971
|
+
createToken = (projectId, appId, options) => {
|
|
1972
|
+
return this.tokenGenerator.createCustomToken(appId, options).then((customToken) => {
|
|
1973
|
+
return this.client.exchangeToken({ customToken, projectId, appId });
|
|
1491
1974
|
});
|
|
1492
1975
|
};
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
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
|
|
1976
|
+
verifyToken = async (appCheckToken, projectId, options) => {
|
|
1977
|
+
return this.appCheckTokenVerifier.verifyToken(appCheckToken, projectId, { keyURL: JWKS_URL, ...options }).then((decodedToken) => {
|
|
1978
|
+
return {
|
|
1979
|
+
appId: decodedToken.app_id,
|
|
1980
|
+
token: decodedToken
|
|
1981
|
+
};
|
|
1528
1982
|
});
|
|
1529
1983
|
};
|
|
1530
1984
|
};
|
|
1985
|
+
function getAppCheck2(serviceAccount, tenantId, limitedUse) {
|
|
1986
|
+
return new AppCheck(new ServiceAccountManager(serviceAccount), tenantId, limitedUse);
|
|
1987
|
+
}
|
|
1531
1988
|
|
|
1532
1989
|
// src/auth/getauth.ts
|
|
1533
1990
|
var API_KEY_ERROR = "API Key is required";
|
|
@@ -1543,17 +2000,8 @@ function parseFirebaseResponse(data) {
|
|
|
1543
2000
|
return data;
|
|
1544
2001
|
}
|
|
1545
2002
|
function getAuth(options) {
|
|
1546
|
-
const { apiKey
|
|
1547
|
-
const
|
|
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
|
-
}
|
|
2003
|
+
const { apiKey } = options;
|
|
2004
|
+
const effectiveApiKey = apiKey || process.env.NEXT_PUBLIC_FIREBASE_API_KEY;
|
|
1557
2005
|
async function getUserData(idToken, localId) {
|
|
1558
2006
|
if (!effectiveApiKey) {
|
|
1559
2007
|
throw new Error(API_KEY_ERROR);
|
|
@@ -1638,43 +2086,12 @@ function getAuth(options) {
|
|
|
1638
2086
|
auth_time: decodedCustomIdToken.data.auth_time
|
|
1639
2087
|
};
|
|
1640
2088
|
}
|
|
1641
|
-
async function
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
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
|
-
}
|
|
2089
|
+
async function createAppCheckToken() {
|
|
2090
|
+
const adminConfig = loadAdminConfig();
|
|
2091
|
+
const appId = process.env.NEXT_PUBLIC_FIREBASE_APP_ID || "";
|
|
2092
|
+
const appCheck = getAppCheck2(adminConfig, options.tenantId);
|
|
1653
2093
|
try {
|
|
1654
|
-
const
|
|
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
|
-
}
|
|
2094
|
+
const appCheckResponse = await appCheck.createToken(adminConfig.projectId, appId);
|
|
1678
2095
|
return {
|
|
1679
2096
|
data: {
|
|
1680
2097
|
token: appCheckResponse.token,
|
|
@@ -1686,89 +2103,100 @@ function getAuth(options) {
|
|
|
1686
2103
|
return { data: null, error };
|
|
1687
2104
|
}
|
|
1688
2105
|
}
|
|
2106
|
+
async function verifyAppCheckToken2(token) {
|
|
2107
|
+
const adminConfig = loadAdminConfig();
|
|
2108
|
+
const appCheck = getAppCheck2(adminConfig, options.tenantId);
|
|
2109
|
+
try {
|
|
2110
|
+
const decodedToken = await appCheck.verifyToken(token, adminConfig.projectId, {});
|
|
2111
|
+
return {
|
|
2112
|
+
data: decodedToken,
|
|
2113
|
+
error: null
|
|
2114
|
+
};
|
|
2115
|
+
} catch (error) {
|
|
2116
|
+
return { data: null, error };
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
1689
2119
|
return {
|
|
1690
2120
|
getUserData,
|
|
1691
2121
|
customForIdAndRefreshToken,
|
|
1692
2122
|
createCustomIdAndRefreshToken,
|
|
1693
2123
|
refreshExpiredIdToken,
|
|
1694
|
-
|
|
2124
|
+
createAppCheckToken,
|
|
2125
|
+
verifyAppCheckToken: verifyAppCheckToken2
|
|
1695
2126
|
};
|
|
1696
2127
|
}
|
|
1697
2128
|
|
|
1698
|
-
// src/
|
|
1699
|
-
var
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
this.initCookieValues();
|
|
1705
|
-
this.initHandshakeValues();
|
|
1706
|
-
this.initUrlValues();
|
|
1707
|
-
Object.assign(this, options);
|
|
1708
|
-
this.ternUrl = this.ternSecureRequest.ternUrl;
|
|
1709
|
-
}
|
|
1710
|
-
get request() {
|
|
1711
|
-
return this.ternSecureRequest;
|
|
1712
|
-
}
|
|
1713
|
-
initHeaderValues() {
|
|
1714
|
-
this.sessionTokenInHeader = this.parseAuthorizationHeader(
|
|
1715
|
-
this.getHeader(constants.Headers.Authorization)
|
|
1716
|
-
);
|
|
1717
|
-
this.origin = this.getHeader(constants.Headers.Origin);
|
|
1718
|
-
this.host = this.getHeader(constants.Headers.Host);
|
|
1719
|
-
this.forwardedHost = this.getHeader(constants.Headers.ForwardedHost);
|
|
1720
|
-
this.forwardedProto = this.getHeader(constants.Headers.CloudFrontForwardedProto) || this.getHeader(constants.Headers.ForwardedProto);
|
|
1721
|
-
this.referrer = this.getHeader(constants.Headers.Referrer);
|
|
1722
|
-
this.userAgent = this.getHeader(constants.Headers.UserAgent);
|
|
1723
|
-
this.secFetchDest = this.getHeader(constants.Headers.SecFetchDest);
|
|
1724
|
-
this.accept = this.getHeader(constants.Headers.Accept);
|
|
1725
|
-
this.appCheckToken = this.getHeader(constants.Headers.AppCheckToken);
|
|
1726
|
-
}
|
|
1727
|
-
initCookieValues() {
|
|
1728
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
1729
|
-
const defaultPrefix = isProduction ? "__HOST-" : "__dev_";
|
|
1730
|
-
this.sessionTokenInCookie = this.getCookie(constants.Cookies.Session);
|
|
1731
|
-
this.idTokenInCookie = this.getCookie(`${defaultPrefix}${constants.Cookies.IdToken}`);
|
|
1732
|
-
this.refreshTokenInCookie = this.getCookie(`${defaultPrefix}${constants.Cookies.Refresh}`);
|
|
1733
|
-
this.csrfTokenInCookie = this.getCookie(constants.Cookies.CsrfToken);
|
|
1734
|
-
this.customTokenInCookie = this.getCookie(constants.Cookies.Custom);
|
|
1735
|
-
this.ternAuth = Number.parseInt(this.getCookie(constants.Cookies.TernAut) || "0", 10);
|
|
1736
|
-
}
|
|
1737
|
-
initHandshakeValues() {
|
|
1738
|
-
this.handshakeToken = this.getQueryParam(constants.QueryParameters.Handshake) || this.getCookie(constants.Cookies.Handshake);
|
|
1739
|
-
this.handshakeNonce = this.getQueryParam(constants.QueryParameters.HandshakeNonce) || this.getCookie(constants.Cookies.HandshakeNonce);
|
|
1740
|
-
}
|
|
1741
|
-
initUrlValues() {
|
|
1742
|
-
this.method = this.ternSecureRequest.method;
|
|
1743
|
-
this.pathSegments = this.ternSecureRequest.ternUrl.pathname.split("/").filter(Boolean);
|
|
1744
|
-
this.endpoint = this.pathSegments[2];
|
|
1745
|
-
this.subEndpoint = this.pathSegments[3];
|
|
1746
|
-
}
|
|
1747
|
-
getQueryParam(name) {
|
|
1748
|
-
return this.ternSecureRequest.ternUrl.searchParams.get(name);
|
|
1749
|
-
}
|
|
1750
|
-
getHeader(name) {
|
|
1751
|
-
return this.ternSecureRequest.headers.get(name) || void 0;
|
|
2129
|
+
// src/auth/credential.ts
|
|
2130
|
+
var accessTokenCache = /* @__PURE__ */ new Map();
|
|
2131
|
+
async function requestAccessToken(urlString, init) {
|
|
2132
|
+
const json = await fetchJson(urlString, init);
|
|
2133
|
+
if (!json.access_token || !json.expires_in) {
|
|
2134
|
+
throw new Error("Invalid access token response");
|
|
1752
2135
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
2136
|
+
return {
|
|
2137
|
+
accessToken: json.access_token,
|
|
2138
|
+
expirationTime: Date.now() + json.expires_in * 1e3
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
var ServiceAccountManager = class {
|
|
2142
|
+
projectId;
|
|
2143
|
+
privateKey;
|
|
2144
|
+
clientEmail;
|
|
2145
|
+
constructor(serviceAccount) {
|
|
2146
|
+
this.projectId = serviceAccount.projectId;
|
|
2147
|
+
this.privateKey = serviceAccount.privateKey;
|
|
2148
|
+
this.clientEmail = serviceAccount.clientEmail;
|
|
1755
2149
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
2150
|
+
fetchAccessToken = async (url) => {
|
|
2151
|
+
const token = await this.createJwt();
|
|
2152
|
+
const postData = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + token;
|
|
2153
|
+
return requestAccessToken(url, {
|
|
2154
|
+
method: "POST",
|
|
2155
|
+
headers: {
|
|
2156
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
2157
|
+
Authorization: `Bearer ${token}`,
|
|
2158
|
+
Accept: "application/json"
|
|
2159
|
+
},
|
|
2160
|
+
body: postData
|
|
2161
|
+
});
|
|
2162
|
+
};
|
|
2163
|
+
fetchAndCacheAccessToken = async (url) => {
|
|
2164
|
+
const accessToken = await this.fetchAccessToken(url);
|
|
2165
|
+
accessTokenCache.set(this.projectId, accessToken);
|
|
2166
|
+
return accessToken;
|
|
2167
|
+
};
|
|
2168
|
+
getAccessToken = async (refresh) => {
|
|
2169
|
+
const url = `https://${GOOGLE_AUTH_TOKEN_HOST}${GOOGLE_AUTH_TOKEN_PATH}`;
|
|
2170
|
+
if (refresh) {
|
|
2171
|
+
return this.fetchAndCacheAccessToken(url);
|
|
1763
2172
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
2173
|
+
const cachedResponse = accessTokenCache.get(this.projectId);
|
|
2174
|
+
if (!cachedResponse || cachedResponse.expirationTime - Date.now() <= TOKEN_EXPIRY_THRESHOLD_MILLIS) {
|
|
2175
|
+
return this.fetchAndCacheAccessToken(url);
|
|
1766
2176
|
}
|
|
1767
|
-
return
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
2177
|
+
return cachedResponse;
|
|
2178
|
+
};
|
|
2179
|
+
createJwt = async () => {
|
|
2180
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
2181
|
+
const payload = {
|
|
2182
|
+
aud: GOOGLE_TOKEN_AUDIENCE,
|
|
2183
|
+
iat,
|
|
2184
|
+
exp: iat + ONE_HOUR_IN_SECONDS,
|
|
2185
|
+
iss: this.clientEmail,
|
|
2186
|
+
sub: this.clientEmail,
|
|
2187
|
+
scope: [
|
|
2188
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
2189
|
+
"https://www.googleapis.com/auth/firebase.database",
|
|
2190
|
+
"https://www.googleapis.com/auth/firebase.messaging",
|
|
2191
|
+
"https://www.googleapis.com/auth/identitytoolkit",
|
|
2192
|
+
"https://www.googleapis.com/auth/userinfo.email"
|
|
2193
|
+
].join(" ")
|
|
2194
|
+
};
|
|
2195
|
+
return ternSignJwt({
|
|
2196
|
+
payload,
|
|
2197
|
+
privateKey: this.privateKey
|
|
2198
|
+
});
|
|
2199
|
+
};
|
|
1772
2200
|
};
|
|
1773
2201
|
|
|
1774
2202
|
// src/tokens/cookie.ts
|
|
@@ -1786,7 +2214,7 @@ function isRequestForRefresh(error, context, request) {
|
|
|
1786
2214
|
}
|
|
1787
2215
|
async function authenticateRequest(request, options) {
|
|
1788
2216
|
const context = createRequestProcessor(createTernSecureRequest(request), options);
|
|
1789
|
-
const { refreshTokenInCookie
|
|
2217
|
+
const { refreshTokenInCookie } = context;
|
|
1790
2218
|
const { refreshExpiredIdToken } = getAuth(options);
|
|
1791
2219
|
function checkSessionTimeout(authTimeValue) {
|
|
1792
2220
|
const defaultMaxAgeSeconds = convertToSeconds("5 days");
|
|
@@ -1809,8 +2237,7 @@ async function authenticateRequest(request, options) {
|
|
|
1809
2237
|
};
|
|
1810
2238
|
}
|
|
1811
2239
|
return await refreshExpiredIdToken(refreshTokenInCookie, {
|
|
1812
|
-
referer: context.ternUrl.origin
|
|
1813
|
-
appCheckToken
|
|
2240
|
+
referer: context.ternUrl.origin
|
|
1814
2241
|
});
|
|
1815
2242
|
}
|
|
1816
2243
|
async function handleRefresh() {
|
|
@@ -1912,24 +2339,7 @@ async function authenticateRequest(request, options) {
|
|
|
1912
2339
|
if (errors) {
|
|
1913
2340
|
throw errors[0];
|
|
1914
2341
|
}
|
|
1915
|
-
const
|
|
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);
|
|
2342
|
+
const signedInRequestState = signedIn(context, data, void 0, context.idTokenInCookie);
|
|
1933
2343
|
return signedInRequestState;
|
|
1934
2344
|
} catch (err) {
|
|
1935
2345
|
return handleError(err, "cookie");
|
|
@@ -1943,23 +2353,7 @@ async function authenticateRequest(request, options) {
|
|
|
1943
2353
|
if (errors) {
|
|
1944
2354
|
throw errors[0];
|
|
1945
2355
|
}
|
|
1946
|
-
const
|
|
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);
|
|
2356
|
+
const signedInRequestState = signedIn(context, data, void 0, sessionTokenInHeader);
|
|
1963
2357
|
return signedInRequestState;
|
|
1964
2358
|
} catch (err) {
|
|
1965
2359
|
return handleError(err, "header");
|
|
@@ -1973,17 +2367,8 @@ async function authenticateRequest(request, options) {
|
|
|
1973
2367
|
if (isRequestForRefresh(err, context, request)) {
|
|
1974
2368
|
const { data, error } = await handleRefresh();
|
|
1975
2369
|
if (data) {
|
|
1976
|
-
const
|
|
1977
|
-
|
|
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
|
-
}
|
|
1986
|
-
return signedIn(context, data.decoded, data.headers, data.token);
|
|
2370
|
+
const signedInState = signedIn(context, data.decoded, data.headers, data.token);
|
|
2371
|
+
return signedInState;
|
|
1987
2372
|
}
|
|
1988
2373
|
if (error?.cause?.reason) {
|
|
1989
2374
|
refreshError = error.cause.reason;
|
|
@@ -2161,7 +2546,7 @@ var PostgresAdapter = class {
|
|
|
2161
2546
|
};
|
|
2162
2547
|
|
|
2163
2548
|
// src/adapters/RedisAdapter.ts
|
|
2164
|
-
var
|
|
2549
|
+
var import_redis2 = require("@upstash/redis");
|
|
2165
2550
|
var TTLCache = class {
|
|
2166
2551
|
cache = /* @__PURE__ */ new Map();
|
|
2167
2552
|
defaultTTL;
|
|
@@ -2218,7 +2603,7 @@ var RedisAdapter = class {
|
|
|
2218
2603
|
cache;
|
|
2219
2604
|
keyPrefix;
|
|
2220
2605
|
constructor(config) {
|
|
2221
|
-
this.redis = new
|
|
2606
|
+
this.redis = new import_redis2.Redis({
|
|
2222
2607
|
url: config.url,
|
|
2223
2608
|
token: config.token
|
|
2224
2609
|
});
|
|
@@ -2300,6 +2685,7 @@ function validateCheckRevokedOptions(options) {
|
|
|
2300
2685
|
createAdapter,
|
|
2301
2686
|
createBackendInstanceClient,
|
|
2302
2687
|
createRedirect,
|
|
2688
|
+
createRequestProcessor,
|
|
2303
2689
|
createTernSecureRequest,
|
|
2304
2690
|
disableDebugLogging,
|
|
2305
2691
|
enableDebugLogging,
|
|
@@ -2307,6 +2693,7 @@ function validateCheckRevokedOptions(options) {
|
|
|
2307
2693
|
signedIn,
|
|
2308
2694
|
signedInAuthObject,
|
|
2309
2695
|
signedOutAuthObject,
|
|
2310
|
-
validateCheckRevokedOptions
|
|
2696
|
+
validateCheckRevokedOptions,
|
|
2697
|
+
verifyToken
|
|
2311
2698
|
});
|
|
2312
2699
|
//# sourceMappingURL=index.js.map
|