@tern-secure/backend 1.2.0-canary.v20251127235234 → 1.2.0-canary.v20251202164451
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +24 -598
- 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 +1135 -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 +902 -394
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/index.mjs +5 -3
- package/dist/chunk-34QENCWP.mjs +784 -0
- package/dist/chunk-34QENCWP.mjs.map +1 -0
- package/dist/{chunk-NXYWC6YO.mjs → chunk-TUYCJY35.mjs} +182 -6
- package/dist/chunk-TUYCJY35.mjs.map +1 -0
- package/dist/chunk-UCSJDX6Y.mjs +778 -0
- package/dist/chunk-UCSJDX6Y.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 +1275 -856
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +97 -137
- 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-GFH5CXQR.mjs +0 -71
- package/dist/chunk-GFH5CXQR.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
|
@@ -0,0 +1,1135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
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
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/app-check/index.ts
|
|
31
|
+
var app_check_exports = {};
|
|
32
|
+
__export(app_check_exports, {
|
|
33
|
+
AppCheck: () => AppCheck,
|
|
34
|
+
ServerAppCheckManager: () => ServerAppCheckManager,
|
|
35
|
+
getAppCheck: () => getAppCheck
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(app_check_exports);
|
|
38
|
+
|
|
39
|
+
// src/jwt/customJwt.ts
|
|
40
|
+
var import_jose = require("jose");
|
|
41
|
+
|
|
42
|
+
// src/jwt/verifyJwt.ts
|
|
43
|
+
var import_jose3 = require("jose");
|
|
44
|
+
|
|
45
|
+
// src/utils/errors.ts
|
|
46
|
+
var TokenVerificationErrorReason = {
|
|
47
|
+
TokenExpired: "token-expired",
|
|
48
|
+
TokenInvalid: "token-invalid",
|
|
49
|
+
TokenInvalidAlgorithm: "token-invalid-algorithm",
|
|
50
|
+
TokenInvalidAuthorizedParties: "token-invalid-authorized-parties",
|
|
51
|
+
TokenInvalidSignature: "token-invalid-signature",
|
|
52
|
+
TokenNotActiveYet: "token-not-active-yet",
|
|
53
|
+
TokenIatInTheFuture: "token-iat-in-the-future",
|
|
54
|
+
TokenVerificationFailed: "token-verification-failed",
|
|
55
|
+
InvalidSecretKey: "secret-key-invalid",
|
|
56
|
+
LocalJWKMissing: "jwk-local-missing",
|
|
57
|
+
RemoteJWKFailedToLoad: "jwk-remote-failed-to-load",
|
|
58
|
+
RemoteJWKInvalid: "jwk-remote-invalid",
|
|
59
|
+
RemoteJWKMissing: "jwk-remote-missing",
|
|
60
|
+
JWKFailedToResolve: "jwk-failed-to-resolve",
|
|
61
|
+
JWKKidMismatch: "jwk-kid-mismatch"
|
|
62
|
+
};
|
|
63
|
+
var TokenVerificationError = class _TokenVerificationError extends Error {
|
|
64
|
+
reason;
|
|
65
|
+
tokenCarrier;
|
|
66
|
+
constructor({
|
|
67
|
+
message,
|
|
68
|
+
reason
|
|
69
|
+
}) {
|
|
70
|
+
super(message);
|
|
71
|
+
Object.setPrototypeOf(this, _TokenVerificationError.prototype);
|
|
72
|
+
this.reason = reason;
|
|
73
|
+
this.message = message;
|
|
74
|
+
}
|
|
75
|
+
getFullMessage() {
|
|
76
|
+
return `${[this.message].filter((m) => m).join(" ")} (reason=${this.reason}, token-carrier=${this.tokenCarrier})`;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// src/utils/mapDecode.ts
|
|
81
|
+
function mapJwtPayloadToDecodedAppCheckToken(payload) {
|
|
82
|
+
const decodedAppCheckToken = payload;
|
|
83
|
+
decodedAppCheckToken.app_id = decodedAppCheckToken.sub;
|
|
84
|
+
return decodedAppCheckToken;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/utils/rfc4648.ts
|
|
88
|
+
var base64url = {
|
|
89
|
+
parse(string, opts) {
|
|
90
|
+
return parse(string, base64UrlEncoding, opts);
|
|
91
|
+
},
|
|
92
|
+
stringify(data, opts) {
|
|
93
|
+
return stringify(data, base64UrlEncoding, opts);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var base64UrlEncoding = {
|
|
97
|
+
chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
|
|
98
|
+
bits: 6
|
|
99
|
+
};
|
|
100
|
+
function parse(string, encoding, opts = {}) {
|
|
101
|
+
if (!encoding.codes) {
|
|
102
|
+
encoding.codes = {};
|
|
103
|
+
for (let i = 0; i < encoding.chars.length; ++i) {
|
|
104
|
+
encoding.codes[encoding.chars[i]] = i;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!opts.loose && string.length * encoding.bits & 7) {
|
|
108
|
+
throw new SyntaxError("Invalid padding");
|
|
109
|
+
}
|
|
110
|
+
let end = string.length;
|
|
111
|
+
while (string[end - 1] === "=") {
|
|
112
|
+
--end;
|
|
113
|
+
if (!opts.loose && !((string.length - end) * encoding.bits & 7)) {
|
|
114
|
+
throw new SyntaxError("Invalid padding");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const out = new (opts.out ?? Uint8Array)(end * encoding.bits / 8 | 0);
|
|
118
|
+
let bits = 0;
|
|
119
|
+
let buffer = 0;
|
|
120
|
+
let written = 0;
|
|
121
|
+
for (let i = 0; i < end; ++i) {
|
|
122
|
+
const value = encoding.codes[string[i]];
|
|
123
|
+
if (value === void 0) {
|
|
124
|
+
throw new SyntaxError("Invalid character " + string[i]);
|
|
125
|
+
}
|
|
126
|
+
buffer = buffer << encoding.bits | value;
|
|
127
|
+
bits += encoding.bits;
|
|
128
|
+
if (bits >= 8) {
|
|
129
|
+
bits -= 8;
|
|
130
|
+
out[written++] = 255 & buffer >> bits;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (bits >= encoding.bits || 255 & buffer << 8 - bits) {
|
|
134
|
+
throw new SyntaxError("Unexpected end of data");
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
function stringify(data, encoding, opts = {}) {
|
|
139
|
+
const { pad = true } = opts;
|
|
140
|
+
const mask = (1 << encoding.bits) - 1;
|
|
141
|
+
let out = "";
|
|
142
|
+
let bits = 0;
|
|
143
|
+
let buffer = 0;
|
|
144
|
+
for (let i = 0; i < data.length; ++i) {
|
|
145
|
+
buffer = buffer << 8 | 255 & data[i];
|
|
146
|
+
bits += 8;
|
|
147
|
+
while (bits > encoding.bits) {
|
|
148
|
+
bits -= encoding.bits;
|
|
149
|
+
out += encoding.chars[mask & buffer >> bits];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (bits) {
|
|
153
|
+
out += encoding.chars[mask & buffer << encoding.bits - bits];
|
|
154
|
+
}
|
|
155
|
+
if (pad) {
|
|
156
|
+
while (out.length * encoding.bits & 7) {
|
|
157
|
+
out += "=";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/jwt/cryptoKeys.ts
|
|
164
|
+
var import_jose2 = require("jose");
|
|
165
|
+
|
|
166
|
+
// src/jwt/algorithms.ts
|
|
167
|
+
var algToHash = {
|
|
168
|
+
RS256: "SHA-256",
|
|
169
|
+
RS384: "SHA-384",
|
|
170
|
+
RS512: "SHA-512"
|
|
171
|
+
};
|
|
172
|
+
var algs = Object.keys(algToHash);
|
|
173
|
+
|
|
174
|
+
// src/jwt/verifyContent.ts
|
|
175
|
+
var verifyHeaderKid = (kid) => {
|
|
176
|
+
if (typeof kid === "undefined") {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (typeof kid !== "string") {
|
|
180
|
+
throw new TokenVerificationError({
|
|
181
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
182
|
+
message: `Invalid JWT kid ${JSON.stringify(kid)}. Expected a string.`
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
var verifySubClaim = (sub) => {
|
|
187
|
+
if (typeof sub !== "string") {
|
|
188
|
+
throw new TokenVerificationError({
|
|
189
|
+
reason: TokenVerificationErrorReason.TokenVerificationFailed,
|
|
190
|
+
message: `Subject claim (sub) is required and must be a string. Received ${JSON.stringify(sub)}.`
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
var verifyExpirationClaim = (exp, clockSkewInMs) => {
|
|
195
|
+
if (typeof exp !== "number") {
|
|
196
|
+
throw new TokenVerificationError({
|
|
197
|
+
reason: TokenVerificationErrorReason.TokenVerificationFailed,
|
|
198
|
+
message: `Invalid JWT expiry date (exp) claim ${JSON.stringify(exp)}. Expected a number.`
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const currentDate = new Date(Date.now());
|
|
202
|
+
const expiryDate = /* @__PURE__ */ new Date(0);
|
|
203
|
+
expiryDate.setUTCSeconds(exp);
|
|
204
|
+
const expired = expiryDate.getTime() <= currentDate.getTime() - clockSkewInMs;
|
|
205
|
+
if (expired) {
|
|
206
|
+
throw new TokenVerificationError({
|
|
207
|
+
reason: TokenVerificationErrorReason.TokenExpired,
|
|
208
|
+
message: `JWT is expired. Expiry date: ${expiryDate.toUTCString()}, Current date: ${currentDate.toUTCString()}.`
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
var verifyIssuedAtClaim = (iat, clockSkewInMs) => {
|
|
213
|
+
if (typeof iat === "undefined") {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (typeof iat !== "number") {
|
|
217
|
+
throw new TokenVerificationError({
|
|
218
|
+
reason: TokenVerificationErrorReason.TokenVerificationFailed,
|
|
219
|
+
message: `Invalid JWT issued at date claim (iat) ${JSON.stringify(iat)}. Expected a number.`
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const currentDate = new Date(Date.now());
|
|
223
|
+
const issuedAtDate = /* @__PURE__ */ new Date(0);
|
|
224
|
+
issuedAtDate.setUTCSeconds(iat);
|
|
225
|
+
const postIssued = issuedAtDate.getTime() > currentDate.getTime() + clockSkewInMs;
|
|
226
|
+
if (postIssued) {
|
|
227
|
+
throw new TokenVerificationError({
|
|
228
|
+
reason: TokenVerificationErrorReason.TokenIatInTheFuture,
|
|
229
|
+
message: `JWT issued at date claim (iat) is in the future. Issued at date: ${issuedAtDate.toUTCString()}; Current date: ${currentDate.toUTCString()};`
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// src/jwt/verifyJwt.ts
|
|
235
|
+
var DEFAULT_CLOCK_SKEW_IN_MS = 5 * 1e3;
|
|
236
|
+
function ternDecodeJwt(token) {
|
|
237
|
+
try {
|
|
238
|
+
const header = (0, import_jose3.decodeProtectedHeader)(token);
|
|
239
|
+
const payload = (0, import_jose3.decodeJwt)(token);
|
|
240
|
+
const tokenParts = (token || "").toString().split(".");
|
|
241
|
+
if (tokenParts.length !== 3) {
|
|
242
|
+
return {
|
|
243
|
+
errors: [
|
|
244
|
+
new TokenVerificationError({
|
|
245
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
246
|
+
message: "Invalid JWT format"
|
|
247
|
+
})
|
|
248
|
+
]
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
const [rawHeader, rawPayload, rawSignature] = tokenParts;
|
|
252
|
+
const signature = base64url.parse(rawSignature, { loose: true });
|
|
253
|
+
const data = {
|
|
254
|
+
header,
|
|
255
|
+
payload,
|
|
256
|
+
signature,
|
|
257
|
+
raw: {
|
|
258
|
+
header: rawHeader,
|
|
259
|
+
payload: rawPayload,
|
|
260
|
+
signature: rawSignature,
|
|
261
|
+
text: token
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
return { data };
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return {
|
|
267
|
+
errors: [
|
|
268
|
+
new TokenVerificationError({
|
|
269
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
270
|
+
message: `${error.message || "Invalid Token or Protected Header formatting"} (Token length: ${token?.length}, First 10 chars: ${token?.substring(0, 10)}...)`
|
|
271
|
+
})
|
|
272
|
+
]
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async function verifyAppCheckSignature(jwt, getPublicKey2) {
|
|
277
|
+
const { header, raw } = jwt;
|
|
278
|
+
const joseAlgorithm = header.alg || "RS256";
|
|
279
|
+
try {
|
|
280
|
+
const key = await getPublicKey2();
|
|
281
|
+
const { payload } = await (0, import_jose3.jwtVerify)(raw.text, key);
|
|
282
|
+
return { data: payload };
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return {
|
|
285
|
+
errors: [
|
|
286
|
+
new TokenVerificationError({
|
|
287
|
+
reason: TokenVerificationErrorReason.TokenInvalidSignature,
|
|
288
|
+
message: error.message
|
|
289
|
+
})
|
|
290
|
+
]
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async function verifyAppCheckJwt(token, options) {
|
|
295
|
+
const { key: getPublicKey2 } = options;
|
|
296
|
+
const clockSkew = options.clockSkewInMs || DEFAULT_CLOCK_SKEW_IN_MS;
|
|
297
|
+
const { data: decoded, errors } = ternDecodeJwt(token);
|
|
298
|
+
if (errors) {
|
|
299
|
+
return { errors };
|
|
300
|
+
}
|
|
301
|
+
const { header, payload } = decoded;
|
|
302
|
+
try {
|
|
303
|
+
verifyHeaderKid(header.kid);
|
|
304
|
+
verifySubClaim(payload.sub);
|
|
305
|
+
verifyExpirationClaim(payload.exp, clockSkew);
|
|
306
|
+
verifyIssuedAtClaim(payload.iat, clockSkew);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
return { errors: [error] };
|
|
309
|
+
}
|
|
310
|
+
const { data: verifiedPayload, errors: signatureErrors } = await verifyAppCheckSignature(
|
|
311
|
+
decoded,
|
|
312
|
+
getPublicKey2
|
|
313
|
+
);
|
|
314
|
+
if (signatureErrors) {
|
|
315
|
+
return {
|
|
316
|
+
errors: [
|
|
317
|
+
new TokenVerificationError({
|
|
318
|
+
reason: TokenVerificationErrorReason.TokenInvalidSignature,
|
|
319
|
+
message: "Token signature verification failed."
|
|
320
|
+
})
|
|
321
|
+
]
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
const decodedAppCheckToken = mapJwtPayloadToDecodedAppCheckToken(verifiedPayload);
|
|
325
|
+
return { data: decodedAppCheckToken };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/constants.ts
|
|
329
|
+
var FIREBASE_APP_CHECK_AUDIENCE = "https://firebaseappcheck.googleapis.com/google.firebase.appcheck.v1.TokenExchangeService";
|
|
330
|
+
var MAX_CACHE_LAST_UPDATED_AT_SECONDS = 5 * 60;
|
|
331
|
+
var DEFAULT_CACHE_DURATION = 3600 * 1e3;
|
|
332
|
+
var TOKEN_EXPIRY_THRESHOLD_MILLIS = 5 * 60 * 1e3;
|
|
333
|
+
var GOOGLE_TOKEN_AUDIENCE = "https://accounts.google.com/o/oauth2/token";
|
|
334
|
+
var GOOGLE_AUTH_TOKEN_HOST = "accounts.google.com";
|
|
335
|
+
var GOOGLE_AUTH_TOKEN_PATH = "/o/oauth2/token";
|
|
336
|
+
var ONE_HOUR_IN_SECONDS = 60 * 60;
|
|
337
|
+
var ONE_MINUTE_IN_SECONDS = 60;
|
|
338
|
+
var ONE_MINUTE_IN_MILLIS = ONE_MINUTE_IN_SECONDS * 1e3;
|
|
339
|
+
var ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1e3;
|
|
340
|
+
var Attributes = {
|
|
341
|
+
AuthToken: "__ternsecureAuthToken",
|
|
342
|
+
AuthSignature: "__ternsecureAuthSignature",
|
|
343
|
+
AuthStatus: "__ternsecureAuthStatus",
|
|
344
|
+
AuthReason: "__ternsecureAuthReason",
|
|
345
|
+
AuthMessage: "__ternsecureAuthMessage",
|
|
346
|
+
TernSecureUrl: "__ternsecureUrl"
|
|
347
|
+
};
|
|
348
|
+
var Cookies = {
|
|
349
|
+
Session: "__session",
|
|
350
|
+
CsrfToken: "__terncf",
|
|
351
|
+
IdToken: "TernSecure_[DEFAULT]",
|
|
352
|
+
Refresh: "TernSecureID_[DEFAULT]",
|
|
353
|
+
Custom: "__custom",
|
|
354
|
+
TernAut: "tern_aut",
|
|
355
|
+
Handshake: "__ternsecure_handshake",
|
|
356
|
+
DevBrowser: "__ternsecure_db_jwt",
|
|
357
|
+
RedirectCount: "__ternsecure_redirect_count",
|
|
358
|
+
HandshakeNonce: "__ternsecure_handshake_nonce"
|
|
359
|
+
};
|
|
360
|
+
var QueryParameters = {
|
|
361
|
+
TernSynced: "__tern_synced",
|
|
362
|
+
SuffixedCookies: "suffixed_cookies",
|
|
363
|
+
TernRedirectUrl: "__tern_redirect_url",
|
|
364
|
+
// use the reference to Cookies to indicate that it's the same value
|
|
365
|
+
DevBrowser: Cookies.DevBrowser,
|
|
366
|
+
Handshake: Cookies.Handshake,
|
|
367
|
+
HandshakeHelp: "__tern_help",
|
|
368
|
+
LegacyDevBrowser: "__dev_session",
|
|
369
|
+
HandshakeReason: "__tern_hs_reason",
|
|
370
|
+
HandshakeNonce: Cookies.HandshakeNonce
|
|
371
|
+
};
|
|
372
|
+
var Headers2 = {
|
|
373
|
+
Accept: "accept",
|
|
374
|
+
AppCheckToken: "x-ternsecure-appcheck",
|
|
375
|
+
AuthMessage: "x-ternsecure-auth-message",
|
|
376
|
+
Authorization: "authorization",
|
|
377
|
+
AuthReason: "x-ternsecure-auth-reason",
|
|
378
|
+
AuthSignature: "x-ternsecure-auth-signature",
|
|
379
|
+
AuthStatus: "x-ternsecure-auth-status",
|
|
380
|
+
AuthToken: "x-ternsecure-auth-token",
|
|
381
|
+
CacheControl: "cache-control",
|
|
382
|
+
TernSecureRedirectTo: "x-ternsecure-redirect-to",
|
|
383
|
+
TernSecureRequestData: "x-ternsecure-request-data",
|
|
384
|
+
TernSecureUrl: "x-ternsecure-url",
|
|
385
|
+
CloudFrontForwardedProto: "cloudfront-forwarded-proto",
|
|
386
|
+
ContentType: "content-type",
|
|
387
|
+
ContentSecurityPolicy: "content-security-policy",
|
|
388
|
+
ContentSecurityPolicyReportOnly: "content-security-policy-report-only",
|
|
389
|
+
EnableDebug: "x-ternsecure-debug",
|
|
390
|
+
ForwardedHost: "x-forwarded-host",
|
|
391
|
+
ForwardedPort: "x-forwarded-port",
|
|
392
|
+
ForwardedProto: "x-forwarded-proto",
|
|
393
|
+
Host: "host",
|
|
394
|
+
Location: "location",
|
|
395
|
+
Nonce: "x-nonce",
|
|
396
|
+
Origin: "origin",
|
|
397
|
+
Referrer: "referer",
|
|
398
|
+
SecFetchDest: "sec-fetch-dest",
|
|
399
|
+
UserAgent: "user-agent",
|
|
400
|
+
ReportingEndpoints: "reporting-endpoints"
|
|
401
|
+
};
|
|
402
|
+
var ContentTypes = {
|
|
403
|
+
Json: "application/json"
|
|
404
|
+
};
|
|
405
|
+
var constants = {
|
|
406
|
+
Attributes,
|
|
407
|
+
Cookies,
|
|
408
|
+
Headers: Headers2,
|
|
409
|
+
ContentTypes,
|
|
410
|
+
QueryParameters
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// src/utils/config.ts
|
|
414
|
+
var loadAdminConfig = () => ({
|
|
415
|
+
projectId: process.env.FIREBASE_PROJECT_ID || "",
|
|
416
|
+
clientEmail: process.env.FIREBASE_CLIENT_EMAIL || "",
|
|
417
|
+
privateKey: process.env.FIREBASE_PRIVATE_KEY || ""
|
|
418
|
+
});
|
|
419
|
+
var validateAdminConfig = (config) => {
|
|
420
|
+
const requiredFields = [
|
|
421
|
+
"projectId",
|
|
422
|
+
"clientEmail",
|
|
423
|
+
"privateKey"
|
|
424
|
+
];
|
|
425
|
+
const errors = [];
|
|
426
|
+
requiredFields.forEach((field) => {
|
|
427
|
+
if (!config[field]) {
|
|
428
|
+
errors.push(`Missing required field: FIREBASE_${String(field).toUpperCase()}`);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
return {
|
|
432
|
+
isValid: errors.length === 0,
|
|
433
|
+
errors,
|
|
434
|
+
config
|
|
435
|
+
};
|
|
436
|
+
};
|
|
437
|
+
var initializeAdminConfig = () => {
|
|
438
|
+
const config = loadAdminConfig();
|
|
439
|
+
const validationResult = validateAdminConfig(config);
|
|
440
|
+
if (!validationResult.isValid) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`Firebase Admin configuration validation failed:
|
|
443
|
+
${validationResult.errors.join("\n")}`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
return config;
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// src/jwt/guardReturn.ts
|
|
450
|
+
function createJwtGuard(decodedFn) {
|
|
451
|
+
return (...args) => {
|
|
452
|
+
const { data, errors } = decodedFn(...args);
|
|
453
|
+
if (errors) {
|
|
454
|
+
throw errors[0];
|
|
455
|
+
}
|
|
456
|
+
return data;
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/jwt/jwt.ts
|
|
461
|
+
var import_jose4 = require("jose");
|
|
462
|
+
|
|
463
|
+
// src/jwt/signJwt.ts
|
|
464
|
+
var import_jose5 = require("jose");
|
|
465
|
+
|
|
466
|
+
// src/utils/fetcher.ts
|
|
467
|
+
async function getDetailFromResponse(response) {
|
|
468
|
+
const json = await response.json();
|
|
469
|
+
if (!json) {
|
|
470
|
+
return "Missing error payload";
|
|
471
|
+
}
|
|
472
|
+
let detail = typeof json.error === "string" ? json.error : json.error?.message ?? "Missing error payload";
|
|
473
|
+
if (json.error_description) {
|
|
474
|
+
detail += " (" + json.error_description + ")";
|
|
475
|
+
}
|
|
476
|
+
return detail;
|
|
477
|
+
}
|
|
478
|
+
async function fetchText(url, init) {
|
|
479
|
+
return (await fetchAny(url, init)).text();
|
|
480
|
+
}
|
|
481
|
+
async function fetchJson(url, init) {
|
|
482
|
+
return (await fetchAny(url, init)).json();
|
|
483
|
+
}
|
|
484
|
+
async function fetchAny(url, init) {
|
|
485
|
+
const response = await fetch(url, init);
|
|
486
|
+
if (!response.ok) {
|
|
487
|
+
throw new Error(await getDetailFromResponse(response));
|
|
488
|
+
}
|
|
489
|
+
return response;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/jwt/types.ts
|
|
493
|
+
var ALGORITHM_RS256 = "RS256";
|
|
494
|
+
|
|
495
|
+
// src/jwt/signJwt.ts
|
|
496
|
+
async function ternSignJwt(opts) {
|
|
497
|
+
const { payload, privateKey, keyId } = opts;
|
|
498
|
+
let key;
|
|
499
|
+
try {
|
|
500
|
+
key = await (0, import_jose5.importPKCS8)(privateKey, ALGORITHM_RS256);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
throw new TokenVerificationError({
|
|
503
|
+
message: `Failed to import private key: ${error.message}`,
|
|
504
|
+
reason: TokenVerificationErrorReason.TokenInvalid
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
return new import_jose5.SignJWT(payload).setProtectedHeader({ alg: ALGORITHM_RS256, kid: keyId }).sign(key);
|
|
508
|
+
}
|
|
509
|
+
function formatBase64(value) {
|
|
510
|
+
return value.replace(/\//g, "_").replace(/\+/g, "-").replace(/=+$/, "");
|
|
511
|
+
}
|
|
512
|
+
function encodeSegment(segment) {
|
|
513
|
+
const value = JSON.stringify(segment);
|
|
514
|
+
return formatBase64(import_jose5.base64url.encode(value));
|
|
515
|
+
}
|
|
516
|
+
async function ternSignBlob({
|
|
517
|
+
payload,
|
|
518
|
+
serviceAccountId,
|
|
519
|
+
accessToken
|
|
520
|
+
}) {
|
|
521
|
+
const url = `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${serviceAccountId}:signBlob`;
|
|
522
|
+
const header = {
|
|
523
|
+
alg: ALGORITHM_RS256,
|
|
524
|
+
typ: "JWT"
|
|
525
|
+
};
|
|
526
|
+
const token = `${encodeSegment(header)}.${encodeSegment(payload)}`;
|
|
527
|
+
const request = {
|
|
528
|
+
method: "POST",
|
|
529
|
+
headers: {
|
|
530
|
+
Authorization: `Bearer ${accessToken}`
|
|
531
|
+
},
|
|
532
|
+
body: JSON.stringify({ payload: import_jose5.base64url.encode(token) })
|
|
533
|
+
};
|
|
534
|
+
const response = await fetchAny(url, request);
|
|
535
|
+
const blob = await response.blob();
|
|
536
|
+
const key = await blob.text();
|
|
537
|
+
const { signedBlob } = JSON.parse(key);
|
|
538
|
+
return `${token}.${formatBase64(signedBlob)}`;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/jwt/crypto-signer.ts
|
|
542
|
+
var ServiceAccountSigner = class {
|
|
543
|
+
constructor(credential, tenantId) {
|
|
544
|
+
this.credential = credential;
|
|
545
|
+
this.tenantId = tenantId;
|
|
546
|
+
}
|
|
547
|
+
async getAccountId() {
|
|
548
|
+
return Promise.resolve(this.credential.clientEmail);
|
|
549
|
+
}
|
|
550
|
+
async sign(payload) {
|
|
551
|
+
if (this.tenantId) {
|
|
552
|
+
payload.tenant_id = this.tenantId;
|
|
553
|
+
}
|
|
554
|
+
return ternSignJwt({ payload, privateKey: this.credential.privateKey });
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
var IAMSigner = class {
|
|
558
|
+
algorithm = ALGORITHM_RS256;
|
|
559
|
+
credential;
|
|
560
|
+
tenantId;
|
|
561
|
+
serviceAccountId;
|
|
562
|
+
constructor(credential, tenantId, serviceAccountId) {
|
|
563
|
+
this.credential = credential;
|
|
564
|
+
this.tenantId = tenantId;
|
|
565
|
+
this.serviceAccountId = serviceAccountId;
|
|
566
|
+
}
|
|
567
|
+
async sign(payload) {
|
|
568
|
+
if (this.tenantId) {
|
|
569
|
+
payload.tenant_id = this.tenantId;
|
|
570
|
+
}
|
|
571
|
+
const serviceAccount = await this.getAccountId();
|
|
572
|
+
const accessToken = await this.credential.getAccessToken();
|
|
573
|
+
return ternSignBlob({
|
|
574
|
+
accessToken: accessToken.accessToken,
|
|
575
|
+
serviceAccountId: serviceAccount,
|
|
576
|
+
payload
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
async getAccountId() {
|
|
580
|
+
if (this.serviceAccountId) {
|
|
581
|
+
return this.serviceAccountId;
|
|
582
|
+
}
|
|
583
|
+
const token = await this.credential.getAccessToken();
|
|
584
|
+
const url = "http://metadata/computeMetadata/v1/instance/service-accounts/default/email";
|
|
585
|
+
const request = {
|
|
586
|
+
method: "GET",
|
|
587
|
+
headers: {
|
|
588
|
+
"Metadata-Flavor": "Google",
|
|
589
|
+
Authorization: `Bearer ${token.accessToken}`
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
return this.serviceAccountId = await fetchText(url, request);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
// src/jwt/index.ts
|
|
597
|
+
var ternDecodeJwt2 = createJwtGuard(ternDecodeJwt);
|
|
598
|
+
|
|
599
|
+
// src/auth/credential.ts
|
|
600
|
+
var accessTokenCache = /* @__PURE__ */ new Map();
|
|
601
|
+
async function requestAccessToken(urlString, init) {
|
|
602
|
+
const json = await fetchJson(urlString, init);
|
|
603
|
+
if (!json.access_token || !json.expires_in) {
|
|
604
|
+
throw new Error("Invalid access token response");
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
accessToken: json.access_token,
|
|
608
|
+
expirationTime: Date.now() + json.expires_in * 1e3
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
var ServiceAccountManager = class {
|
|
612
|
+
projectId;
|
|
613
|
+
privateKey;
|
|
614
|
+
clientEmail;
|
|
615
|
+
constructor(serviceAccount) {
|
|
616
|
+
this.projectId = serviceAccount.projectId;
|
|
617
|
+
this.privateKey = serviceAccount.privateKey;
|
|
618
|
+
this.clientEmail = serviceAccount.clientEmail;
|
|
619
|
+
}
|
|
620
|
+
fetchAccessToken = async (url) => {
|
|
621
|
+
const token = await this.createJwt();
|
|
622
|
+
const postData = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + token;
|
|
623
|
+
return requestAccessToken(url, {
|
|
624
|
+
method: "POST",
|
|
625
|
+
headers: {
|
|
626
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
627
|
+
Authorization: `Bearer ${token}`,
|
|
628
|
+
Accept: "application/json"
|
|
629
|
+
},
|
|
630
|
+
body: postData
|
|
631
|
+
});
|
|
632
|
+
};
|
|
633
|
+
fetchAndCacheAccessToken = async (url) => {
|
|
634
|
+
const accessToken = await this.fetchAccessToken(url);
|
|
635
|
+
accessTokenCache.set(this.projectId, accessToken);
|
|
636
|
+
return accessToken;
|
|
637
|
+
};
|
|
638
|
+
getAccessToken = async (refresh) => {
|
|
639
|
+
const url = `https://${GOOGLE_AUTH_TOKEN_HOST}${GOOGLE_AUTH_TOKEN_PATH}`;
|
|
640
|
+
if (refresh) {
|
|
641
|
+
return this.fetchAndCacheAccessToken(url);
|
|
642
|
+
}
|
|
643
|
+
const cachedResponse = accessTokenCache.get(this.projectId);
|
|
644
|
+
if (!cachedResponse || cachedResponse.expirationTime - Date.now() <= TOKEN_EXPIRY_THRESHOLD_MILLIS) {
|
|
645
|
+
return this.fetchAndCacheAccessToken(url);
|
|
646
|
+
}
|
|
647
|
+
return cachedResponse;
|
|
648
|
+
};
|
|
649
|
+
createJwt = async () => {
|
|
650
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
651
|
+
const payload = {
|
|
652
|
+
aud: GOOGLE_TOKEN_AUDIENCE,
|
|
653
|
+
iat,
|
|
654
|
+
exp: iat + ONE_HOUR_IN_SECONDS,
|
|
655
|
+
iss: this.clientEmail,
|
|
656
|
+
sub: this.clientEmail,
|
|
657
|
+
scope: [
|
|
658
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
659
|
+
"https://www.googleapis.com/auth/firebase.database",
|
|
660
|
+
"https://www.googleapis.com/auth/firebase.messaging",
|
|
661
|
+
"https://www.googleapis.com/auth/identitytoolkit",
|
|
662
|
+
"https://www.googleapis.com/auth/userinfo.email"
|
|
663
|
+
].join(" ")
|
|
664
|
+
};
|
|
665
|
+
return ternSignJwt({
|
|
666
|
+
payload,
|
|
667
|
+
privateKey: this.privateKey
|
|
668
|
+
});
|
|
669
|
+
};
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
// src/utils/token-generator.ts
|
|
673
|
+
function cryptoSignerFromCredential(credential, tenantId, serviceAccountId) {
|
|
674
|
+
if (credential instanceof ServiceAccountManager) {
|
|
675
|
+
return new ServiceAccountSigner(credential, tenantId);
|
|
676
|
+
}
|
|
677
|
+
return new IAMSigner(credential, tenantId, serviceAccountId);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// src/app-check/AppCheckApi.ts
|
|
681
|
+
function getSdkVersion() {
|
|
682
|
+
return "12.7.0";
|
|
683
|
+
}
|
|
684
|
+
var FIREBASE_APP_CHECK_CONFIG_HEADERS = {
|
|
685
|
+
"X-Firebase-Client": `fire-admin-node/${getSdkVersion()}`
|
|
686
|
+
};
|
|
687
|
+
var AppCheckApi = class {
|
|
688
|
+
constructor(credential) {
|
|
689
|
+
this.credential = credential;
|
|
690
|
+
}
|
|
691
|
+
async exchangeToken(params) {
|
|
692
|
+
const { projectId, appId, customToken, limitedUse = false } = params;
|
|
693
|
+
const token = await this.credential.getAccessToken(false);
|
|
694
|
+
if (!projectId || !appId) {
|
|
695
|
+
throw new Error("Project ID and App ID are required for App Check token exchange");
|
|
696
|
+
}
|
|
697
|
+
const endpoint = `https://firebaseappcheck.googleapis.com/v1/projects/${projectId}/apps/${appId}:exchangeCustomToken`;
|
|
698
|
+
const headers = {
|
|
699
|
+
"Content-Type": "application/json",
|
|
700
|
+
"Authorization": `Bearer ${token.accessToken}`
|
|
701
|
+
};
|
|
702
|
+
try {
|
|
703
|
+
const response = await fetch(endpoint, {
|
|
704
|
+
method: "POST",
|
|
705
|
+
headers,
|
|
706
|
+
body: JSON.stringify({ customToken, limitedUse })
|
|
707
|
+
});
|
|
708
|
+
if (!response.ok) {
|
|
709
|
+
const errorText = await response.text();
|
|
710
|
+
throw new Error(`App Check token exchange failed: ${response.status} ${errorText}`);
|
|
711
|
+
}
|
|
712
|
+
const data = await response.json();
|
|
713
|
+
return {
|
|
714
|
+
token: data.token,
|
|
715
|
+
ttl: data.ttl
|
|
716
|
+
};
|
|
717
|
+
} catch (error) {
|
|
718
|
+
console.warn("[ternsecure - appcheck api]unexpected error:", error);
|
|
719
|
+
throw error;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
async exchangeDebugToken(params) {
|
|
723
|
+
const { projectId, appId, customToken, accessToken, limitedUse = false } = params;
|
|
724
|
+
if (!projectId || !appId) {
|
|
725
|
+
throw new Error("Project ID and App ID are required for App Check token exchange");
|
|
726
|
+
}
|
|
727
|
+
const endpoint = `https://firebaseappcheck.googleapis.com/v1beta/projects/${projectId}/apps/${appId}:exchangeDebugToken`;
|
|
728
|
+
const headers = {
|
|
729
|
+
...FIREBASE_APP_CHECK_CONFIG_HEADERS,
|
|
730
|
+
"Authorization": `Bearer ${accessToken}`
|
|
731
|
+
};
|
|
732
|
+
const body = {
|
|
733
|
+
customToken,
|
|
734
|
+
limitedUse
|
|
735
|
+
};
|
|
736
|
+
try {
|
|
737
|
+
const response = await fetch(endpoint, {
|
|
738
|
+
method: "POST",
|
|
739
|
+
headers,
|
|
740
|
+
body: JSON.stringify(body)
|
|
741
|
+
});
|
|
742
|
+
if (!response.ok) {
|
|
743
|
+
const errorText = await response.text();
|
|
744
|
+
throw new Error(`App Check token exchange failed: ${response.status} ${errorText}`);
|
|
745
|
+
}
|
|
746
|
+
const data = await response.json();
|
|
747
|
+
return {
|
|
748
|
+
token: data.token,
|
|
749
|
+
ttl: data.ttl
|
|
750
|
+
};
|
|
751
|
+
} catch (error) {
|
|
752
|
+
console.warn("[ternsecure - appcheck api]unexpected error:", error);
|
|
753
|
+
throw error;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
// src/app-check/generator.ts
|
|
759
|
+
function transformMillisecondsToSecondsString(milliseconds) {
|
|
760
|
+
let duration;
|
|
761
|
+
const seconds = Math.floor(milliseconds / 1e3);
|
|
762
|
+
const nanos = Math.floor((milliseconds - seconds * 1e3) * 1e6);
|
|
763
|
+
if (nanos > 0) {
|
|
764
|
+
let nanoString = nanos.toString();
|
|
765
|
+
while (nanoString.length < 9) {
|
|
766
|
+
nanoString = "0" + nanoString;
|
|
767
|
+
}
|
|
768
|
+
duration = `${seconds}.${nanoString}s`;
|
|
769
|
+
} else {
|
|
770
|
+
duration = `${seconds}s`;
|
|
771
|
+
}
|
|
772
|
+
return duration;
|
|
773
|
+
}
|
|
774
|
+
var AppCheckTokenGenerator = class {
|
|
775
|
+
signer;
|
|
776
|
+
constructor(signer) {
|
|
777
|
+
this.signer = signer;
|
|
778
|
+
}
|
|
779
|
+
async createCustomToken(appId, options) {
|
|
780
|
+
if (!appId) {
|
|
781
|
+
throw new Error(
|
|
782
|
+
"appId is invalid"
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
let customOptions = {};
|
|
786
|
+
if (typeof options !== "undefined") {
|
|
787
|
+
customOptions = this.validateTokenOptions(options);
|
|
788
|
+
}
|
|
789
|
+
const account = await this.signer.getAccountId();
|
|
790
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
791
|
+
const body = {
|
|
792
|
+
iss: account,
|
|
793
|
+
sub: account,
|
|
794
|
+
app_id: appId,
|
|
795
|
+
aud: FIREBASE_APP_CHECK_AUDIENCE,
|
|
796
|
+
exp: iat + ONE_MINUTE_IN_SECONDS * 5,
|
|
797
|
+
iat,
|
|
798
|
+
...customOptions
|
|
799
|
+
};
|
|
800
|
+
return this.signer.sign(body);
|
|
801
|
+
}
|
|
802
|
+
validateTokenOptions(options) {
|
|
803
|
+
if (typeof options.ttlMillis !== "undefined") {
|
|
804
|
+
if (options.ttlMillis < ONE_MINUTE_IN_MILLIS * 30 || options.ttlMillis > ONE_DAY_IN_MILLIS * 7) {
|
|
805
|
+
throw new Error(
|
|
806
|
+
"ttlMillis must be a duration in milliseconds between 30 minutes and 7 days (inclusive)."
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
return { ttl: transformMillisecondsToSecondsString(options.ttlMillis) };
|
|
810
|
+
}
|
|
811
|
+
return {};
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
// src/app-check/serverAppCheck.ts
|
|
816
|
+
var import_redis = require("@upstash/redis");
|
|
817
|
+
|
|
818
|
+
// src/admin/sessionTernSecure.ts
|
|
819
|
+
var import_errors6 = require("@tern-secure/shared/errors");
|
|
820
|
+
|
|
821
|
+
// src/utils/admin-init.ts
|
|
822
|
+
var import_firebase_admin = __toESM(require("firebase-admin"));
|
|
823
|
+
var import_app_check2 = require("firebase-admin/app-check");
|
|
824
|
+
if (!import_firebase_admin.default.apps.length) {
|
|
825
|
+
try {
|
|
826
|
+
const config = initializeAdminConfig();
|
|
827
|
+
import_firebase_admin.default.initializeApp({
|
|
828
|
+
credential: import_firebase_admin.default.credential.cert({
|
|
829
|
+
...config,
|
|
830
|
+
privateKey: config.privateKey.replace(/\\n/g, "\n")
|
|
831
|
+
})
|
|
832
|
+
});
|
|
833
|
+
} catch (error) {
|
|
834
|
+
console.error("Firebase admin initialization error", error);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
var adminTernSecureAuth = import_firebase_admin.default.auth();
|
|
838
|
+
var adminTernSecureDb = import_firebase_admin.default.firestore();
|
|
839
|
+
var TernSecureTenantManager = import_firebase_admin.default.auth().tenantManager();
|
|
840
|
+
var appCheckAdmin = (0, import_app_check2.getAppCheck)();
|
|
841
|
+
|
|
842
|
+
// src/admin/sessionTernSecure.ts
|
|
843
|
+
var DEFAULT_COOKIE_CONFIG = {
|
|
844
|
+
DEFAULT_EXPIRES_IN_MS: 5 * 60 * 1e3,
|
|
845
|
+
// 5 minutes
|
|
846
|
+
DEFAULT_EXPIRES_IN_SECONDS: 5 * 60,
|
|
847
|
+
REVOKE_REFRESH_TOKENS_ON_SIGNOUT: true
|
|
848
|
+
};
|
|
849
|
+
var DEFAULT_COOKIE_OPTIONS = {
|
|
850
|
+
httpOnly: true,
|
|
851
|
+
secure: process.env.NODE_ENV === "production",
|
|
852
|
+
sameSite: "strict",
|
|
853
|
+
path: "/"
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
// src/admin/nextSessionTernSecure.ts
|
|
857
|
+
var import_cookie = require("@tern-secure/shared/cookie");
|
|
858
|
+
var import_errors7 = require("@tern-secure/shared/errors");
|
|
859
|
+
var import_headers = require("next/headers");
|
|
860
|
+
var SESSION_CONSTANTS = {
|
|
861
|
+
COOKIE_NAME: constants.Cookies.Session,
|
|
862
|
+
DEFAULT_EXPIRES_IN_MS: 60 * 60 * 24 * 5 * 1e3,
|
|
863
|
+
// 5 days
|
|
864
|
+
DEFAULT_EXPIRES_IN_SECONDS: 60 * 60 * 24 * 5,
|
|
865
|
+
REVOKE_REFRESH_TOKENS_ON_SIGNOUT: true
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
// src/tokens/ternSecureRequest.ts
|
|
869
|
+
var import_cookie2 = require("cookie");
|
|
870
|
+
|
|
871
|
+
// src/admin/user.ts
|
|
872
|
+
var import_errors8 = require("@tern-secure/shared/errors");
|
|
873
|
+
|
|
874
|
+
// src/app-check/serverAppCheck.ts
|
|
875
|
+
var ServerAppCheckManager = class _ServerAppCheckManager {
|
|
876
|
+
static instances = /* @__PURE__ */ new Map();
|
|
877
|
+
memoryCache = /* @__PURE__ */ new Map();
|
|
878
|
+
redisClient = null;
|
|
879
|
+
options;
|
|
880
|
+
pendingTokens = /* @__PURE__ */ new Map();
|
|
881
|
+
constructor(options) {
|
|
882
|
+
const defaultOptions = {
|
|
883
|
+
strategy: "memory",
|
|
884
|
+
ttlMillis: 36e5,
|
|
885
|
+
// 1 hour
|
|
886
|
+
refreshBufferMillis: 3e5,
|
|
887
|
+
// 5 minutes
|
|
888
|
+
keyPrefix: "appcheck:token:",
|
|
889
|
+
skipInMemoryFirst: false
|
|
890
|
+
};
|
|
891
|
+
this.options = { ...defaultOptions, ...options };
|
|
892
|
+
if (this.options.strategy === "redis" && this.options.redis) {
|
|
893
|
+
void this.initializeRedis(this.options.redis);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
initializeRedis = (config) => {
|
|
897
|
+
if (!config) {
|
|
898
|
+
throw new Error('[AppCheck] Redis configuration is required when strategy is "redis"');
|
|
899
|
+
}
|
|
900
|
+
try {
|
|
901
|
+
this.redisClient = new import_redis.Redis({
|
|
902
|
+
url: config.url,
|
|
903
|
+
token: config.token
|
|
904
|
+
});
|
|
905
|
+
console.info("[AppCheck] Redis client initialized for token caching");
|
|
906
|
+
} catch (error) {
|
|
907
|
+
console.error("[AppCheck] Failed to initialize Redis client:", error);
|
|
908
|
+
throw new Error('[AppCheck] Redis initialization failed. Install "@upstash/redis" package.');
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
static getInstance(options) {
|
|
912
|
+
const key = options?.strategy || "memory";
|
|
913
|
+
if (!_ServerAppCheckManager.instances.has(key)) {
|
|
914
|
+
_ServerAppCheckManager.instances.set(key, new _ServerAppCheckManager(options));
|
|
915
|
+
}
|
|
916
|
+
const instance = _ServerAppCheckManager.instances.get(key);
|
|
917
|
+
if (!instance) {
|
|
918
|
+
throw new Error("[AppCheck] Failed to get instance");
|
|
919
|
+
}
|
|
920
|
+
return instance;
|
|
921
|
+
}
|
|
922
|
+
buildCacheKey(appId) {
|
|
923
|
+
return `${this.options.keyPrefix}${appId}`;
|
|
924
|
+
}
|
|
925
|
+
getCachedToken = async (appId) => {
|
|
926
|
+
if (this.options.strategy === "memory") {
|
|
927
|
+
return this.memoryCache.get(appId) || null;
|
|
928
|
+
}
|
|
929
|
+
if (this.options.strategy === "redis") {
|
|
930
|
+
if (!this.options.skipInMemoryFirst) {
|
|
931
|
+
const memCached = this.memoryCache.get(appId);
|
|
932
|
+
if (memCached) {
|
|
933
|
+
return memCached;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
if (this.redisClient) {
|
|
937
|
+
try {
|
|
938
|
+
const key = this.buildCacheKey(appId);
|
|
939
|
+
const cached = await this.redisClient.get(key);
|
|
940
|
+
if (cached) {
|
|
941
|
+
const parsed = typeof cached === "string" ? JSON.parse(cached) : cached;
|
|
942
|
+
if (!this.options.skipInMemoryFirst) {
|
|
943
|
+
this.memoryCache.set(appId, parsed);
|
|
944
|
+
}
|
|
945
|
+
return parsed;
|
|
946
|
+
}
|
|
947
|
+
} catch (error) {
|
|
948
|
+
console.error("[AppCheck] Redis get error:", error);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return null;
|
|
953
|
+
};
|
|
954
|
+
setCachedToken = async (appId, token, expiresAt) => {
|
|
955
|
+
const cachedToken = { token, expiresAt };
|
|
956
|
+
this.memoryCache.set(appId, cachedToken);
|
|
957
|
+
if (this.options.strategy === "memory") {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (this.options.strategy === "redis" && this.redisClient) {
|
|
961
|
+
try {
|
|
962
|
+
const key = this.buildCacheKey(appId);
|
|
963
|
+
const ttl = expiresAt - Date.now();
|
|
964
|
+
await this.redisClient.set(key, JSON.stringify(cachedToken), {
|
|
965
|
+
px: ttl
|
|
966
|
+
// Expiry in milliseconds (lowercase for Upstash)
|
|
967
|
+
});
|
|
968
|
+
} catch (error) {
|
|
969
|
+
console.error("[AppCheck] Redis set error:", error);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
getOrGenerateToken = async (appId) => {
|
|
974
|
+
const cached = await this.getCachedToken(appId);
|
|
975
|
+
const now = Date.now();
|
|
976
|
+
if (cached && cached.expiresAt > now + this.options.refreshBufferMillis) {
|
|
977
|
+
return cached.token;
|
|
978
|
+
}
|
|
979
|
+
const pending = this.pendingTokens.get(appId);
|
|
980
|
+
if (pending) {
|
|
981
|
+
return pending;
|
|
982
|
+
}
|
|
983
|
+
const tokenPromise = this.generateAndCacheToken(appId);
|
|
984
|
+
this.pendingTokens.set(appId, tokenPromise);
|
|
985
|
+
try {
|
|
986
|
+
const token = await tokenPromise;
|
|
987
|
+
return token;
|
|
988
|
+
} finally {
|
|
989
|
+
this.pendingTokens.delete(appId);
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
/**
|
|
993
|
+
* Generate and cache a new token
|
|
994
|
+
*/
|
|
995
|
+
generateAndCacheToken = async (appId) => {
|
|
996
|
+
try {
|
|
997
|
+
const now = Date.now();
|
|
998
|
+
const appCheckToken = await appCheckAdmin.createToken(appId, {
|
|
999
|
+
ttlMillis: this.options.ttlMillis
|
|
1000
|
+
});
|
|
1001
|
+
const expiresAt = now + this.options.ttlMillis;
|
|
1002
|
+
await this.setCachedToken(appId, appCheckToken.token, expiresAt);
|
|
1003
|
+
return appCheckToken.token;
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
console.error("[AppCheck] Failed to generate token:", error);
|
|
1006
|
+
return null;
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
clearCache = async (appId) => {
|
|
1010
|
+
if (appId) {
|
|
1011
|
+
this.memoryCache.delete(appId);
|
|
1012
|
+
} else {
|
|
1013
|
+
this.memoryCache.clear();
|
|
1014
|
+
}
|
|
1015
|
+
if (this.options.strategy === "redis" && this.redisClient) {
|
|
1016
|
+
try {
|
|
1017
|
+
if (appId) {
|
|
1018
|
+
const key = this.buildCacheKey(appId);
|
|
1019
|
+
await this.redisClient.del(key);
|
|
1020
|
+
}
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
console.error("[AppCheck] Redis delete error:", error);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
getCacheStats() {
|
|
1027
|
+
const now = Date.now();
|
|
1028
|
+
const entries = Array.from(this.memoryCache.entries()).map(([appId, cached]) => ({
|
|
1029
|
+
appId,
|
|
1030
|
+
expiresIn: Math.max(0, cached.expiresAt - now)
|
|
1031
|
+
}));
|
|
1032
|
+
return {
|
|
1033
|
+
strategy: this.options.strategy,
|
|
1034
|
+
memorySize: this.memoryCache.size,
|
|
1035
|
+
entries
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Close Redis connection
|
|
1040
|
+
*/
|
|
1041
|
+
disconnect() {
|
|
1042
|
+
if (this.redisClient) {
|
|
1043
|
+
this.redisClient = null;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
// src/app-check/verifier.ts
|
|
1049
|
+
var import_jose6 = require("jose");
|
|
1050
|
+
var getPublicKey = async (header, keyURL) => {
|
|
1051
|
+
const jswksUrl = new URL(keyURL);
|
|
1052
|
+
const getKey = (0, import_jose6.createRemoteJWKSet)(jswksUrl);
|
|
1053
|
+
return getKey(header);
|
|
1054
|
+
};
|
|
1055
|
+
var verifyAppCheckToken = async (token, options) => {
|
|
1056
|
+
const { data: decodedResult, errors } = ternDecodeJwt(token);
|
|
1057
|
+
if (errors) {
|
|
1058
|
+
throw errors[0];
|
|
1059
|
+
}
|
|
1060
|
+
const { header } = decodedResult;
|
|
1061
|
+
const { kid } = header;
|
|
1062
|
+
if (!kid) {
|
|
1063
|
+
return {
|
|
1064
|
+
errors: [
|
|
1065
|
+
new TokenVerificationError({
|
|
1066
|
+
reason: TokenVerificationErrorReason.TokenInvalid,
|
|
1067
|
+
message: 'JWT "kid" header is missing.'
|
|
1068
|
+
})
|
|
1069
|
+
]
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
try {
|
|
1073
|
+
const getPublicKeyForToken = () => getPublicKey(header, options.keyURL || "");
|
|
1074
|
+
return await verifyAppCheckJwt(token, { ...options, key: getPublicKeyForToken });
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
if (error instanceof TokenVerificationError) {
|
|
1077
|
+
return { errors: [error] };
|
|
1078
|
+
}
|
|
1079
|
+
return {
|
|
1080
|
+
errors: [error]
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
var AppcheckTokenVerifier = class {
|
|
1085
|
+
constructor(credential) {
|
|
1086
|
+
this.credential = credential;
|
|
1087
|
+
}
|
|
1088
|
+
verifyToken = async (token, projectId, options) => {
|
|
1089
|
+
const { data, errors } = await verifyAppCheckToken(token, options);
|
|
1090
|
+
if (errors) {
|
|
1091
|
+
throw errors[0];
|
|
1092
|
+
}
|
|
1093
|
+
return data;
|
|
1094
|
+
};
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
// src/app-check/index.ts
|
|
1098
|
+
var JWKS_URL = "https://firebaseappcheck.googleapis.com/v1/jwks";
|
|
1099
|
+
var AppCheck = class {
|
|
1100
|
+
client;
|
|
1101
|
+
tokenGenerator;
|
|
1102
|
+
appCheckTokenVerifier;
|
|
1103
|
+
limitedUse;
|
|
1104
|
+
constructor(credential, tenantId, limitedUse) {
|
|
1105
|
+
this.client = new AppCheckApi(credential);
|
|
1106
|
+
this.tokenGenerator = new AppCheckTokenGenerator(
|
|
1107
|
+
cryptoSignerFromCredential(credential, tenantId)
|
|
1108
|
+
);
|
|
1109
|
+
this.appCheckTokenVerifier = new AppcheckTokenVerifier(credential);
|
|
1110
|
+
this.limitedUse = limitedUse;
|
|
1111
|
+
}
|
|
1112
|
+
createToken = (projectId, appId, options) => {
|
|
1113
|
+
return this.tokenGenerator.createCustomToken(appId, options).then((customToken) => {
|
|
1114
|
+
return this.client.exchangeToken({ customToken, projectId, appId });
|
|
1115
|
+
});
|
|
1116
|
+
};
|
|
1117
|
+
verifyToken = async (appCheckToken, projectId, options) => {
|
|
1118
|
+
return this.appCheckTokenVerifier.verifyToken(appCheckToken, projectId, { keyURL: JWKS_URL, ...options }).then((decodedToken) => {
|
|
1119
|
+
return {
|
|
1120
|
+
appId: decodedToken.app_id,
|
|
1121
|
+
token: decodedToken
|
|
1122
|
+
};
|
|
1123
|
+
});
|
|
1124
|
+
};
|
|
1125
|
+
};
|
|
1126
|
+
function getAppCheck(serviceAccount, tenantId, limitedUse) {
|
|
1127
|
+
return new AppCheck(new ServiceAccountManager(serviceAccount), tenantId, limitedUse);
|
|
1128
|
+
}
|
|
1129
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1130
|
+
0 && (module.exports = {
|
|
1131
|
+
AppCheck,
|
|
1132
|
+
ServerAppCheckManager,
|
|
1133
|
+
getAppCheck
|
|
1134
|
+
});
|
|
1135
|
+
//# sourceMappingURL=index.js.map
|