firebase-functions 6.6.0 → 7.0.0-rc.0
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/lib/_virtual/rolldown_runtime.js +34 -0
- package/lib/bin/firebase-functions.js +78 -103
- package/lib/common/app.js +35 -55
- package/lib/common/change.js +54 -75
- package/lib/common/config.js +41 -41
- package/lib/common/debug.js +23 -47
- package/lib/common/encoding.js +59 -82
- package/lib/common/onInit.js +26 -28
- package/lib/common/options.js +22 -42
- package/lib/common/params.d.ts +6 -6
- package/lib/common/params.js +0 -23
- package/lib/common/providers/database.js +270 -300
- package/lib/common/providers/firestore.js +66 -92
- package/lib/common/providers/https.d.ts +0 -1
- package/lib/common/providers/https.js +537 -539
- package/lib/common/providers/identity.js +393 -444
- package/lib/common/providers/tasks.js +64 -98
- package/lib/common/timezone.js +544 -542
- package/lib/common/trace.d.ts +0 -1
- package/lib/common/trace.js +63 -55
- package/lib/common/utilities/assertions.d.ts +11 -0
- package/lib/common/utilities/assertions.js +18 -0
- package/lib/common/utilities/encoder.js +20 -37
- package/lib/common/utilities/path-pattern.js +106 -132
- package/lib/common/utilities/path.js +28 -27
- package/lib/common/utilities/utils.js +23 -45
- package/lib/esm/_virtual/rolldown_runtime.mjs +16 -0
- package/lib/esm/bin/firebase-functions.mjs +91 -0
- package/lib/esm/common/app.mjs +39 -0
- package/lib/esm/common/change.mjs +57 -0
- package/lib/esm/common/config.mjs +45 -0
- package/lib/esm/common/debug.mjs +28 -0
- package/lib/esm/common/encoding.mjs +69 -0
- package/lib/esm/common/onInit.mjs +33 -0
- package/lib/esm/common/options.mjs +22 -0
- package/lib/esm/common/params.mjs +1 -0
- package/lib/esm/common/providers/database.mjs +269 -0
- package/lib/esm/common/providers/firestore.mjs +78 -0
- package/lib/esm/common/providers/https.mjs +573 -0
- package/lib/esm/common/providers/identity.mjs +428 -0
- package/lib/esm/common/providers/tasks.mjs +67 -0
- package/lib/esm/common/timezone.mjs +544 -0
- package/lib/esm/common/trace.mjs +73 -0
- package/lib/esm/common/utilities/assertions.mjs +17 -0
- package/lib/esm/common/utilities/encoder.mjs +21 -0
- package/lib/esm/common/utilities/path-pattern.mjs +116 -0
- package/lib/esm/common/utilities/path.mjs +35 -0
- package/lib/esm/common/utilities/utils.mjs +29 -0
- package/lib/esm/function-configuration.mjs +1 -0
- package/lib/esm/logger/common.mjs +23 -0
- package/lib/esm/logger/compat.mjs +25 -0
- package/lib/esm/logger/index.mjs +131 -0
- package/lib/esm/params/index.mjs +160 -0
- package/lib/esm/params/types.mjs +400 -0
- package/lib/esm/runtime/loader.mjs +132 -0
- package/lib/esm/runtime/manifest.mjs +134 -0
- package/lib/esm/types/global.d.mjs +1 -0
- package/lib/esm/v1/cloud-functions.mjs +206 -0
- package/lib/esm/v1/config.mjs +14 -0
- package/lib/esm/v1/function-builder.mjs +252 -0
- package/lib/esm/v1/function-configuration.mjs +72 -0
- package/lib/esm/v1/index.mjs +27 -0
- package/lib/esm/v1/providers/analytics.mjs +212 -0
- package/lib/esm/v1/providers/auth.mjs +156 -0
- package/lib/esm/v1/providers/database.mjs +243 -0
- package/lib/esm/v1/providers/firestore.mjs +131 -0
- package/lib/esm/v1/providers/https.mjs +82 -0
- package/lib/esm/v1/providers/pubsub.mjs +175 -0
- package/lib/esm/v1/providers/remoteConfig.mjs +64 -0
- package/lib/esm/v1/providers/storage.mjs +163 -0
- package/lib/esm/v1/providers/tasks.mjs +63 -0
- package/lib/esm/v1/providers/testLab.mjs +94 -0
- package/lib/esm/v2/core.mjs +4 -0
- package/lib/esm/v2/index.mjs +28 -0
- package/lib/esm/v2/options.mjs +102 -0
- package/lib/esm/v2/providers/alerts/alerts.mjs +85 -0
- package/lib/esm/v2/providers/alerts/appDistribution.mjs +75 -0
- package/lib/esm/v2/providers/alerts/billing.mjs +51 -0
- package/lib/esm/v2/providers/alerts/crashlytics.mjs +122 -0
- package/lib/esm/v2/providers/alerts/index.mjs +22 -0
- package/lib/esm/v2/providers/alerts/performance.mjs +66 -0
- package/lib/esm/v2/providers/database.mjs +197 -0
- package/lib/esm/v2/providers/dataconnect.mjs +130 -0
- package/lib/esm/v2/providers/eventarc.mjs +51 -0
- package/lib/esm/v2/providers/firestore.mjs +294 -0
- package/lib/esm/v2/providers/https.mjs +210 -0
- package/lib/esm/v2/providers/identity.mjs +103 -0
- package/lib/esm/v2/providers/pubsub.mjs +148 -0
- package/lib/esm/v2/providers/remoteConfig.mjs +52 -0
- package/lib/esm/v2/providers/scheduler.mjs +84 -0
- package/lib/esm/v2/providers/storage.mjs +155 -0
- package/lib/esm/v2/providers/tasks.mjs +65 -0
- package/lib/esm/v2/providers/testLab.mjs +53 -0
- package/lib/esm/v2/trace.mjs +20 -0
- package/lib/function-configuration.d.ts +0 -0
- package/lib/function-configuration.js +0 -0
- package/lib/logger/common.js +21 -41
- package/lib/logger/compat.js +18 -33
- package/lib/logger/index.js +119 -130
- package/lib/params/index.d.ts +4 -2
- package/lib/params/index.js +150 -144
- package/lib/params/types.js +389 -423
- package/lib/runtime/loader.js +114 -148
- package/lib/runtime/manifest.js +106 -126
- package/lib/types/global.d.js +0 -0
- package/lib/v1/cloud-functions.d.ts +2 -2
- package/lib/v1/cloud-functions.js +193 -241
- package/lib/v1/config.d.ts +4 -7
- package/lib/v1/config.js +13 -75
- package/lib/v1/function-builder.js +239 -368
- package/lib/v1/function-configuration.js +70 -63
- package/lib/v1/index.js +118 -73
- package/lib/v1/providers/analytics.js +188 -235
- package/lib/v1/providers/auth.d.ts +2 -1
- package/lib/v1/providers/auth.js +159 -164
- package/lib/v1/providers/database.js +237 -242
- package/lib/v1/providers/firestore.js +131 -130
- package/lib/v1/providers/https.d.ts +2 -1
- package/lib/v1/providers/https.js +79 -86
- package/lib/v1/providers/pubsub.js +175 -172
- package/lib/v1/providers/remoteConfig.js +64 -68
- package/lib/v1/providers/storage.js +161 -163
- package/lib/v1/providers/tasks.d.ts +1 -1
- package/lib/v1/providers/tasks.js +65 -80
- package/lib/v1/providers/testLab.js +94 -94
- package/lib/v2/core.d.ts +1 -1
- package/lib/v2/core.js +5 -32
- package/lib/v2/index.d.ts +6 -3
- package/lib/v2/index.js +123 -75
- package/lib/v2/options.js +88 -114
- package/lib/v2/providers/alerts/alerts.js +76 -95
- package/lib/v2/providers/alerts/appDistribution.js +73 -78
- package/lib/v2/providers/alerts/billing.js +49 -53
- package/lib/v2/providers/alerts/crashlytics.js +110 -102
- package/lib/v2/providers/alerts/index.js +56 -53
- package/lib/v2/providers/alerts/performance.js +64 -74
- package/lib/v2/providers/database.js +177 -180
- package/lib/v2/providers/dataconnect.d.ts +95 -0
- package/lib/v2/providers/dataconnect.js +137 -0
- package/lib/v2/providers/eventarc.js +55 -77
- package/lib/v2/providers/firestore.js +262 -260
- package/lib/v2/providers/https.d.ts +3 -2
- package/lib/v2/providers/https.js +210 -247
- package/lib/v2/providers/identity.d.ts +2 -1
- package/lib/v2/providers/identity.js +96 -105
- package/lib/v2/providers/pubsub.js +149 -167
- package/lib/v2/providers/remoteConfig.js +54 -63
- package/lib/v2/providers/scheduler.js +84 -96
- package/lib/v2/providers/storage.js +147 -162
- package/lib/v2/providers/tasks.d.ts +1 -1
- package/lib/v2/providers/tasks.js +68 -95
- package/lib/v2/providers/testLab.js +55 -64
- package/lib/v2/trace.js +18 -19
- package/package.json +290 -226
- package/protos/compiledFirestore.mjs +3512 -0
- package/protos/update.sh +28 -7
|
@@ -1,591 +1,589 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
// copies or substantial portions of the Software.
|
|
15
|
-
//
|
|
16
|
-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
// SOFTWARE.
|
|
23
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.onCallHandler = exports.checkAuthToken = exports.unsafeDecodeAppCheckToken = exports.unsafeDecodeIdToken = exports.unsafeDecodeToken = exports.decode = exports.encode = exports.isValidRequest = exports.HttpsError = exports.DEFAULT_HEARTBEAT_SECONDS = exports.ORIGINAL_AUTH_HEADER = exports.CALLABLE_AUTH_HEADER = void 0;
|
|
25
|
-
const cors = require("cors");
|
|
26
|
-
const logger = require("../../logger");
|
|
27
|
-
// TODO(inlined): Decide whether we want to un-version apps or whether we want a
|
|
28
|
-
// different strategy
|
|
29
|
-
const app_check_1 = require("firebase-admin/app-check");
|
|
30
|
-
const auth_1 = require("firebase-admin/auth");
|
|
31
|
-
const app_1 = require("../app");
|
|
32
|
-
const debug_1 = require("../debug");
|
|
1
|
+
const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.js');
|
|
2
|
+
const require_logger_index = require('../../logger/index.js');
|
|
3
|
+
const require_common_app = require('../app.js');
|
|
4
|
+
const require_common_debug = require('../debug.js');
|
|
5
|
+
let firebase_admin_auth = require("firebase-admin/auth");
|
|
6
|
+
firebase_admin_auth = require_rolldown_runtime.__toESM(firebase_admin_auth);
|
|
7
|
+
let cors = require("cors");
|
|
8
|
+
cors = require_rolldown_runtime.__toESM(cors);
|
|
9
|
+
let firebase_admin_app_check = require("firebase-admin/app-check");
|
|
10
|
+
firebase_admin_app_check = require_rolldown_runtime.__toESM(firebase_admin_app_check);
|
|
11
|
+
|
|
12
|
+
//#region src/common/providers/https.ts
|
|
33
13
|
const JWT_REGEX = /^[a-zA-Z0-9\-_=]+?\.[a-zA-Z0-9\-_=]+?\.([a-zA-Z0-9\-_=]+)?$/;
|
|
34
14
|
/** @internal */
|
|
35
|
-
|
|
15
|
+
const CALLABLE_AUTH_HEADER = "x-callable-context-auth";
|
|
36
16
|
/** @internal */
|
|
37
|
-
|
|
17
|
+
const ORIGINAL_AUTH_HEADER = "x-original-auth";
|
|
38
18
|
/** @internal */
|
|
39
|
-
|
|
19
|
+
const DEFAULT_HEARTBEAT_SECONDS = 30;
|
|
40
20
|
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
21
|
+
* Standard error codes and HTTP statuses for different ways a request can fail,
|
|
22
|
+
* as defined by:
|
|
23
|
+
* https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
|
|
24
|
+
*
|
|
25
|
+
* This map is used primarily to convert from a client error code string to
|
|
26
|
+
* to the HTTP format error code string and status, and make sure it's in the
|
|
27
|
+
* supported set.
|
|
28
|
+
*/
|
|
49
29
|
const errorCodeMap = {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
30
|
+
ok: {
|
|
31
|
+
canonicalName: "OK",
|
|
32
|
+
status: 200
|
|
33
|
+
},
|
|
34
|
+
cancelled: {
|
|
35
|
+
canonicalName: "CANCELLED",
|
|
36
|
+
status: 499
|
|
37
|
+
},
|
|
38
|
+
unknown: {
|
|
39
|
+
canonicalName: "UNKNOWN",
|
|
40
|
+
status: 500
|
|
41
|
+
},
|
|
42
|
+
"invalid-argument": {
|
|
43
|
+
canonicalName: "INVALID_ARGUMENT",
|
|
44
|
+
status: 400
|
|
45
|
+
},
|
|
46
|
+
"deadline-exceeded": {
|
|
47
|
+
canonicalName: "DEADLINE_EXCEEDED",
|
|
48
|
+
status: 504
|
|
49
|
+
},
|
|
50
|
+
"not-found": {
|
|
51
|
+
canonicalName: "NOT_FOUND",
|
|
52
|
+
status: 404
|
|
53
|
+
},
|
|
54
|
+
"already-exists": {
|
|
55
|
+
canonicalName: "ALREADY_EXISTS",
|
|
56
|
+
status: 409
|
|
57
|
+
},
|
|
58
|
+
"permission-denied": {
|
|
59
|
+
canonicalName: "PERMISSION_DENIED",
|
|
60
|
+
status: 403
|
|
61
|
+
},
|
|
62
|
+
unauthenticated: {
|
|
63
|
+
canonicalName: "UNAUTHENTICATED",
|
|
64
|
+
status: 401
|
|
65
|
+
},
|
|
66
|
+
"resource-exhausted": {
|
|
67
|
+
canonicalName: "RESOURCE_EXHAUSTED",
|
|
68
|
+
status: 429
|
|
69
|
+
},
|
|
70
|
+
"failed-precondition": {
|
|
71
|
+
canonicalName: "FAILED_PRECONDITION",
|
|
72
|
+
status: 400
|
|
73
|
+
},
|
|
74
|
+
aborted: {
|
|
75
|
+
canonicalName: "ABORTED",
|
|
76
|
+
status: 409
|
|
77
|
+
},
|
|
78
|
+
"out-of-range": {
|
|
79
|
+
canonicalName: "OUT_OF_RANGE",
|
|
80
|
+
status: 400
|
|
81
|
+
},
|
|
82
|
+
unimplemented: {
|
|
83
|
+
canonicalName: "UNIMPLEMENTED",
|
|
84
|
+
status: 501
|
|
85
|
+
},
|
|
86
|
+
internal: {
|
|
87
|
+
canonicalName: "INTERNAL",
|
|
88
|
+
status: 500
|
|
89
|
+
},
|
|
90
|
+
unavailable: {
|
|
91
|
+
canonicalName: "UNAVAILABLE",
|
|
92
|
+
status: 503
|
|
93
|
+
},
|
|
94
|
+
"data-loss": {
|
|
95
|
+
canonicalName: "DATA_LOSS",
|
|
96
|
+
status: 500
|
|
97
|
+
}
|
|
67
98
|
};
|
|
68
99
|
/**
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
exports.HttpsError = HttpsError;
|
|
100
|
+
* An explicit error that can be thrown from a handler to send an error to the
|
|
101
|
+
* client that called the function.
|
|
102
|
+
*/
|
|
103
|
+
var HttpsError = class extends Error {
|
|
104
|
+
constructor(code, message, details) {
|
|
105
|
+
super(message);
|
|
106
|
+
if (code in errorCodeMap === false) {
|
|
107
|
+
throw new Error(`Unknown error code: ${code}.`);
|
|
108
|
+
}
|
|
109
|
+
this.code = code;
|
|
110
|
+
this.details = details;
|
|
111
|
+
this.httpErrorCode = errorCodeMap[code];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Returns a JSON-serializable representation of this object.
|
|
115
|
+
*/
|
|
116
|
+
toJSON() {
|
|
117
|
+
const { details, httpErrorCode: { canonicalName: status }, message } = this;
|
|
118
|
+
return {
|
|
119
|
+
...details === undefined ? {} : { details },
|
|
120
|
+
message,
|
|
121
|
+
status
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
};
|
|
96
125
|
/** @hidden */
|
|
97
|
-
// Returns true if req is a properly formatted callable request.
|
|
98
126
|
function isValidRequest(req) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// Verify that the body does not have any extra fields.
|
|
127
|
-
const extraKeys = Object.keys(req.body).filter((field) => field !== "data");
|
|
128
|
-
if (extraKeys.length !== 0) {
|
|
129
|
-
logger.warn("Request body has extra fields: ", extraKeys.join(", "));
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
return true;
|
|
127
|
+
if (!req.body) {
|
|
128
|
+
require_logger_index.warn("Request is missing body.");
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
if (req.method !== "POST") {
|
|
132
|
+
require_logger_index.warn("Request has invalid method.", req.method);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
let contentType = (req.header("Content-Type") || "").toLowerCase();
|
|
136
|
+
const semiColon = contentType.indexOf(";");
|
|
137
|
+
if (semiColon >= 0) {
|
|
138
|
+
contentType = contentType.slice(0, semiColon).trim();
|
|
139
|
+
}
|
|
140
|
+
if (contentType !== "application/json") {
|
|
141
|
+
require_logger_index.warn("Request has incorrect Content-Type.", contentType);
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
if (typeof req.body.data === "undefined") {
|
|
145
|
+
require_logger_index.warn("Request body is missing data.", req.body);
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
const extraKeys = Object.keys(req.body).filter((field) => field !== "data");
|
|
149
|
+
if (extraKeys.length !== 0) {
|
|
150
|
+
require_logger_index.warn("Request body has extra fields: ", extraKeys.join(", "));
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
133
154
|
}
|
|
134
|
-
exports.isValidRequest = isValidRequest;
|
|
135
155
|
/** @hidden */
|
|
136
156
|
const LONG_TYPE = "type.googleapis.com/google.protobuf.Int64Value";
|
|
137
157
|
/** @hidden */
|
|
138
158
|
const UNSIGNED_LONG_TYPE = "type.googleapis.com/google.protobuf.UInt64Value";
|
|
139
159
|
/**
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
160
|
+
* Encodes arbitrary data in our special format for JSON.
|
|
161
|
+
* This is exposed only for testing.
|
|
162
|
+
*/
|
|
143
163
|
/** @hidden */
|
|
144
164
|
function encode(data) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return obj;
|
|
173
|
-
}
|
|
174
|
-
// If we got this far, the data is not encodable.
|
|
175
|
-
logger.error("Data cannot be encoded in JSON.", data);
|
|
176
|
-
throw new Error(`Data cannot be encoded in JSON: ${data}`);
|
|
165
|
+
if (data === null || typeof data === "undefined") {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
if (data instanceof Number) {
|
|
169
|
+
data = data.valueOf();
|
|
170
|
+
}
|
|
171
|
+
if (Number.isFinite(data)) {
|
|
172
|
+
return data;
|
|
173
|
+
}
|
|
174
|
+
if (typeof data === "boolean") {
|
|
175
|
+
return data;
|
|
176
|
+
}
|
|
177
|
+
if (typeof data === "string") {
|
|
178
|
+
return data;
|
|
179
|
+
}
|
|
180
|
+
if (Array.isArray(data)) {
|
|
181
|
+
return data.map(encode);
|
|
182
|
+
}
|
|
183
|
+
if (typeof data === "object" || typeof data === "function") {
|
|
184
|
+
const obj = {};
|
|
185
|
+
for (const [k, v] of Object.entries(data)) {
|
|
186
|
+
obj[k] = encode(v);
|
|
187
|
+
}
|
|
188
|
+
return obj;
|
|
189
|
+
}
|
|
190
|
+
require_logger_index.error("Data cannot be encoded in JSON.", data);
|
|
191
|
+
throw new Error(`Data cannot be encoded in JSON: ${data}`);
|
|
177
192
|
}
|
|
178
|
-
exports.encode = encode;
|
|
179
193
|
/**
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
194
|
+
* Decodes our special format for JSON into native types.
|
|
195
|
+
* This is exposed only for testing.
|
|
196
|
+
*/
|
|
183
197
|
/** @hidden */
|
|
184
198
|
function decode(data) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
return obj;
|
|
218
|
-
}
|
|
219
|
-
// Anything else is safe to return.
|
|
220
|
-
return data;
|
|
199
|
+
if (data === null) {
|
|
200
|
+
return data;
|
|
201
|
+
}
|
|
202
|
+
if (data["@type"]) {
|
|
203
|
+
switch (data["@type"]) {
|
|
204
|
+
case LONG_TYPE:
|
|
205
|
+
case UNSIGNED_LONG_TYPE: {
|
|
206
|
+
const value = parseFloat(data.value);
|
|
207
|
+
if (isNaN(value)) {
|
|
208
|
+
require_logger_index.error("Data cannot be decoded from JSON.", data);
|
|
209
|
+
throw new Error(`Data cannot be decoded from JSON: ${data}`);
|
|
210
|
+
}
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
default: {
|
|
214
|
+
require_logger_index.error("Data cannot be decoded from JSON.", data);
|
|
215
|
+
throw new Error(`Data cannot be decoded from JSON: ${data}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (Array.isArray(data)) {
|
|
220
|
+
return data.map(decode);
|
|
221
|
+
}
|
|
222
|
+
if (typeof data === "object") {
|
|
223
|
+
const obj = {};
|
|
224
|
+
for (const [k, v] of Object.entries(data)) {
|
|
225
|
+
obj[k] = decode(v);
|
|
226
|
+
}
|
|
227
|
+
return obj;
|
|
228
|
+
}
|
|
229
|
+
return data;
|
|
221
230
|
}
|
|
222
|
-
exports.decode = decode;
|
|
223
231
|
/** @internal */
|
|
224
232
|
function unsafeDecodeToken(token) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return payload;
|
|
233
|
+
if (!JWT_REGEX.test(token)) {
|
|
234
|
+
return {};
|
|
235
|
+
}
|
|
236
|
+
const components = token.split(".").map((s) => Buffer.from(s, "base64").toString());
|
|
237
|
+
let payload = components[1];
|
|
238
|
+
if (typeof payload === "string") {
|
|
239
|
+
try {
|
|
240
|
+
const obj = JSON.parse(payload);
|
|
241
|
+
if (typeof obj === "object") {
|
|
242
|
+
payload = obj;
|
|
243
|
+
}
|
|
244
|
+
} catch (_e) {}
|
|
245
|
+
}
|
|
246
|
+
return payload;
|
|
242
247
|
}
|
|
243
|
-
exports.unsafeDecodeToken = unsafeDecodeToken;
|
|
244
248
|
/**
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
249
|
+
* Decode, but not verify, a Auth ID token.
|
|
250
|
+
*
|
|
251
|
+
* Do not use in production. Token should always be verified using the Admin SDK.
|
|
252
|
+
*
|
|
253
|
+
* This is exposed only for testing.
|
|
254
|
+
*/
|
|
251
255
|
/** @internal */
|
|
252
256
|
function unsafeDecodeIdToken(token) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
257
|
+
const decoded = unsafeDecodeToken(token);
|
|
258
|
+
decoded.uid = decoded.sub;
|
|
259
|
+
return decoded;
|
|
256
260
|
}
|
|
257
|
-
exports.unsafeDecodeIdToken = unsafeDecodeIdToken;
|
|
258
261
|
/**
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
* Decode, but not verify, an App Check token.
|
|
263
|
+
*
|
|
264
|
+
* Do not use in production. Token should always be verified using the Admin SDK.
|
|
265
|
+
*
|
|
266
|
+
* This is exposed only for testing.
|
|
267
|
+
*/
|
|
265
268
|
/** @internal */
|
|
266
269
|
function unsafeDecodeAppCheckToken(token) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
const decoded = unsafeDecodeToken(token);
|
|
271
|
+
decoded.app_id = decoded.sub;
|
|
272
|
+
return decoded;
|
|
270
273
|
}
|
|
271
|
-
exports.unsafeDecodeAppCheckToken = unsafeDecodeAppCheckToken;
|
|
272
274
|
/**
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
* Check and verify tokens included in the requests. Once verified, tokens
|
|
276
|
+
* are injected into the callable context.
|
|
277
|
+
*
|
|
278
|
+
* @param {Request} req - Request sent to the Callable function.
|
|
279
|
+
* @param {CallableContext} ctx - Context to be sent to callable function handler.
|
|
280
|
+
* @returns {CallableTokenStatus} Status of the token verifications.
|
|
281
|
+
*/
|
|
280
282
|
/** @internal */
|
|
281
283
|
async function checkTokens(req, ctx, options) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
logger.debug("Callable request verification passed", logPayload);
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
logger.warn(`Callable request verification failed: ${errs.join(" ")}`, logPayload);
|
|
308
|
-
}
|
|
309
|
-
return verifications;
|
|
284
|
+
const verifications = {
|
|
285
|
+
app: "INVALID",
|
|
286
|
+
auth: "INVALID"
|
|
287
|
+
};
|
|
288
|
+
[verifications.auth, verifications.app] = await Promise.all([checkAuthToken(req, ctx), checkAppCheckToken(req, ctx, options)]);
|
|
289
|
+
const logPayload = {
|
|
290
|
+
verifications,
|
|
291
|
+
"logging.googleapis.com/labels": { "firebase-log-type": "callable-request-verification" }
|
|
292
|
+
};
|
|
293
|
+
const errs = [];
|
|
294
|
+
if (verifications.app === "INVALID") {
|
|
295
|
+
errs.push("AppCheck token was rejected.");
|
|
296
|
+
}
|
|
297
|
+
if (verifications.auth === "INVALID") {
|
|
298
|
+
errs.push("Auth token was rejected.");
|
|
299
|
+
}
|
|
300
|
+
if (errs.length === 0) {
|
|
301
|
+
require_logger_index.debug("Callable request verification passed", logPayload);
|
|
302
|
+
} else {
|
|
303
|
+
require_logger_index.warn(`Callable request verification failed: ${errs.join(" ")}`, logPayload);
|
|
304
|
+
}
|
|
305
|
+
return verifications;
|
|
310
306
|
}
|
|
311
307
|
/** @interanl */
|
|
312
308
|
async function checkAuthToken(req, ctx) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
return "INVALID";
|
|
340
|
-
}
|
|
309
|
+
const authorization = req.header("Authorization");
|
|
310
|
+
if (!authorization) {
|
|
311
|
+
return "MISSING";
|
|
312
|
+
}
|
|
313
|
+
const match = authorization.match(/^Bearer (.*)$/i);
|
|
314
|
+
if (!match) {
|
|
315
|
+
return "INVALID";
|
|
316
|
+
}
|
|
317
|
+
const idToken = match[1];
|
|
318
|
+
try {
|
|
319
|
+
let authToken;
|
|
320
|
+
if (require_common_debug.isDebugFeatureEnabled("skipTokenVerification")) {
|
|
321
|
+
authToken = unsafeDecodeIdToken(idToken);
|
|
322
|
+
} else {
|
|
323
|
+
authToken = await (0, firebase_admin_auth.getAuth)(require_common_app.getApp()).verifyIdToken(idToken);
|
|
324
|
+
}
|
|
325
|
+
ctx.auth = {
|
|
326
|
+
uid: authToken.uid,
|
|
327
|
+
token: authToken,
|
|
328
|
+
rawToken: idToken
|
|
329
|
+
};
|
|
330
|
+
return "VALID";
|
|
331
|
+
} catch (err) {
|
|
332
|
+
require_logger_index.warn("Failed to validate auth token.", err);
|
|
333
|
+
return "INVALID";
|
|
334
|
+
}
|
|
341
335
|
}
|
|
342
|
-
exports.checkAuthToken = checkAuthToken;
|
|
343
336
|
/** @internal */
|
|
344
337
|
async function checkAppCheckToken(req, ctx, options) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
return "INVALID";
|
|
384
|
-
}
|
|
338
|
+
const appCheckToken = req.header("X-Firebase-AppCheck");
|
|
339
|
+
if (!appCheckToken) {
|
|
340
|
+
return "MISSING";
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
let appCheckData;
|
|
344
|
+
if (require_common_debug.isDebugFeatureEnabled("skipTokenVerification")) {
|
|
345
|
+
const decodedToken = unsafeDecodeAppCheckToken(appCheckToken);
|
|
346
|
+
appCheckData = {
|
|
347
|
+
appId: decodedToken.app_id,
|
|
348
|
+
token: decodedToken
|
|
349
|
+
};
|
|
350
|
+
if (options.consumeAppCheckToken) {
|
|
351
|
+
appCheckData.alreadyConsumed = false;
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
const appCheck = (0, firebase_admin_app_check.getAppCheck)(require_common_app.getApp());
|
|
355
|
+
if (options.consumeAppCheckToken) {
|
|
356
|
+
if (appCheck.verifyToken?.length === 1) {
|
|
357
|
+
const errorMsg = "Unsupported version of the Admin SDK." + " App Check token will not be consumed." + " Please upgrade the firebase-admin to the latest version.";
|
|
358
|
+
require_logger_index.error(errorMsg);
|
|
359
|
+
throw new HttpsError("internal", "Internal Error");
|
|
360
|
+
}
|
|
361
|
+
appCheckData = await (0, firebase_admin_app_check.getAppCheck)(require_common_app.getApp()).verifyToken(appCheckToken, { consume: true });
|
|
362
|
+
} else {
|
|
363
|
+
appCheckData = await (0, firebase_admin_app_check.getAppCheck)(require_common_app.getApp()).verifyToken(appCheckToken);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
ctx.app = appCheckData;
|
|
367
|
+
return "VALID";
|
|
368
|
+
} catch (err) {
|
|
369
|
+
require_logger_index.warn("Failed to validate AppCheck token.", err);
|
|
370
|
+
if (err instanceof HttpsError) {
|
|
371
|
+
throw err;
|
|
372
|
+
}
|
|
373
|
+
return "INVALID";
|
|
374
|
+
}
|
|
385
375
|
}
|
|
386
376
|
/** @internal */
|
|
387
377
|
function onCallHandler(options, handler, version) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
378
|
+
const wrapped = wrapOnCallHandler(options, handler, version);
|
|
379
|
+
return (req, res) => {
|
|
380
|
+
return new Promise((resolve) => {
|
|
381
|
+
res.on("finish", resolve);
|
|
382
|
+
(0, cors.default)(options.cors)(req, res, () => {
|
|
383
|
+
resolve(wrapped(req, res));
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
};
|
|
397
387
|
}
|
|
398
|
-
exports.onCallHandler = onCallHandler;
|
|
399
388
|
function encodeSSE(data) {
|
|
400
|
-
|
|
389
|
+
return `data: ${JSON.stringify(data)}\n\n`;
|
|
401
390
|
}
|
|
402
391
|
/** @internal */
|
|
403
392
|
function wrapOnCallHandler(options, handler, version) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
else {
|
|
562
|
-
res.end();
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
catch (err) {
|
|
566
|
-
if (!abortController.signal.aborted) {
|
|
567
|
-
let httpErr = err;
|
|
568
|
-
if (!(err instanceof HttpsError)) {
|
|
569
|
-
// This doesn't count as an 'explicit' error.
|
|
570
|
-
logger.error("Unhandled error", err);
|
|
571
|
-
httpErr = new HttpsError("internal", "INTERNAL");
|
|
572
|
-
}
|
|
573
|
-
const { status } = httpErr.httpErrorCode;
|
|
574
|
-
const body = { error: httpErr.toJSON() };
|
|
575
|
-
if (version === "gcfv2" && req.header("accept") === "text/event-stream") {
|
|
576
|
-
res.write(encodeSSE(body));
|
|
577
|
-
res.end();
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
res.status(status).send(body);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
else {
|
|
584
|
-
res.end();
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
finally {
|
|
588
|
-
clearScheduledHeartbeat();
|
|
589
|
-
}
|
|
590
|
-
};
|
|
393
|
+
return async (req, res) => {
|
|
394
|
+
const abortController = new AbortController();
|
|
395
|
+
let heartbeatInterval = null;
|
|
396
|
+
const heartbeatSeconds = options.heartbeatSeconds === undefined ? DEFAULT_HEARTBEAT_SECONDS : options.heartbeatSeconds;
|
|
397
|
+
const clearScheduledHeartbeat = () => {
|
|
398
|
+
if (heartbeatInterval) {
|
|
399
|
+
clearTimeout(heartbeatInterval);
|
|
400
|
+
heartbeatInterval = null;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
const scheduleHeartbeat = () => {
|
|
404
|
+
clearScheduledHeartbeat();
|
|
405
|
+
if (!abortController.signal.aborted) {
|
|
406
|
+
heartbeatInterval = setTimeout(() => {
|
|
407
|
+
if (!abortController.signal.aborted) {
|
|
408
|
+
res.write(": ping\n\n");
|
|
409
|
+
scheduleHeartbeat();
|
|
410
|
+
}
|
|
411
|
+
}, heartbeatSeconds * 1e3);
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
res.on("close", () => {
|
|
415
|
+
clearScheduledHeartbeat();
|
|
416
|
+
abortController.abort();
|
|
417
|
+
});
|
|
418
|
+
try {
|
|
419
|
+
if (!isValidRequest(req)) {
|
|
420
|
+
require_logger_index.error("Invalid request, unable to process.");
|
|
421
|
+
throw new HttpsError("invalid-argument", "Bad Request");
|
|
422
|
+
}
|
|
423
|
+
const context = { rawRequest: req };
|
|
424
|
+
if (require_common_debug.isDebugFeatureEnabled("skipTokenVerification") && version === "gcfv1") {
|
|
425
|
+
const authContext = context.rawRequest.header(CALLABLE_AUTH_HEADER);
|
|
426
|
+
if (authContext) {
|
|
427
|
+
require_logger_index.debug("Callable functions auth override", {
|
|
428
|
+
key: CALLABLE_AUTH_HEADER,
|
|
429
|
+
value: authContext
|
|
430
|
+
});
|
|
431
|
+
context.auth = JSON.parse(decodeURIComponent(authContext));
|
|
432
|
+
delete context.rawRequest.headers[CALLABLE_AUTH_HEADER];
|
|
433
|
+
}
|
|
434
|
+
const originalAuth = context.rawRequest.header(ORIGINAL_AUTH_HEADER);
|
|
435
|
+
if (originalAuth) {
|
|
436
|
+
context.rawRequest.headers["authorization"] = originalAuth;
|
|
437
|
+
delete context.rawRequest.headers[ORIGINAL_AUTH_HEADER];
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const tokenStatus = await checkTokens(req, context, options);
|
|
441
|
+
if (tokenStatus.auth === "INVALID") {
|
|
442
|
+
throw new HttpsError("unauthenticated", "Unauthenticated");
|
|
443
|
+
}
|
|
444
|
+
if (tokenStatus.app === "INVALID") {
|
|
445
|
+
if (options.enforceAppCheck) {
|
|
446
|
+
throw new HttpsError("unauthenticated", "Unauthenticated");
|
|
447
|
+
} else {
|
|
448
|
+
require_logger_index.warn("Allowing request with invalid AppCheck token because enforcement is disabled");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (tokenStatus.app === "MISSING" && options.enforceAppCheck) {
|
|
452
|
+
throw new HttpsError("unauthenticated", "Unauthenticated");
|
|
453
|
+
}
|
|
454
|
+
const instanceId = req.header("Firebase-Instance-ID-Token");
|
|
455
|
+
if (instanceId) {
|
|
456
|
+
context.instanceIdToken = req.header("Firebase-Instance-ID-Token");
|
|
457
|
+
}
|
|
458
|
+
const acceptsStreaming = req.header("accept") === "text/event-stream";
|
|
459
|
+
if (acceptsStreaming && version === "gcfv1") {
|
|
460
|
+
throw new HttpsError("invalid-argument", "Unsupported Accept header 'text/event-stream'");
|
|
461
|
+
}
|
|
462
|
+
const data = decode(req.body.data);
|
|
463
|
+
if (options.authPolicy) {
|
|
464
|
+
const authorized = await options.authPolicy(context.auth ?? null, data);
|
|
465
|
+
if (!authorized) {
|
|
466
|
+
throw new HttpsError("permission-denied", "Permission Denied");
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
let result;
|
|
470
|
+
if (version === "gcfv1") {
|
|
471
|
+
result = await handler(data, context);
|
|
472
|
+
} else {
|
|
473
|
+
const arg = {
|
|
474
|
+
...context,
|
|
475
|
+
data,
|
|
476
|
+
acceptsStreaming
|
|
477
|
+
};
|
|
478
|
+
const responseProxy = {
|
|
479
|
+
sendChunk(chunk) {
|
|
480
|
+
if (!acceptsStreaming) {
|
|
481
|
+
return Promise.resolve(false);
|
|
482
|
+
}
|
|
483
|
+
if (abortController.signal.aborted) {
|
|
484
|
+
return Promise.resolve(false);
|
|
485
|
+
}
|
|
486
|
+
const formattedData = encodeSSE({ message: chunk });
|
|
487
|
+
let resolve;
|
|
488
|
+
let reject;
|
|
489
|
+
const p = new Promise((res$1, rej) => {
|
|
490
|
+
resolve = res$1;
|
|
491
|
+
reject = rej;
|
|
492
|
+
});
|
|
493
|
+
const wrote = res.write(formattedData, (error$1) => {
|
|
494
|
+
if (error$1) {
|
|
495
|
+
reject(error$1);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
resolve(wrote);
|
|
499
|
+
});
|
|
500
|
+
if (wrote && heartbeatInterval !== null && heartbeatSeconds > 0) {
|
|
501
|
+
scheduleHeartbeat();
|
|
502
|
+
}
|
|
503
|
+
return p;
|
|
504
|
+
},
|
|
505
|
+
signal: abortController.signal
|
|
506
|
+
};
|
|
507
|
+
if (acceptsStreaming) {
|
|
508
|
+
res.status(200);
|
|
509
|
+
if (heartbeatSeconds !== null && heartbeatSeconds > 0) {
|
|
510
|
+
scheduleHeartbeat();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
result = await handler(arg, responseProxy);
|
|
514
|
+
clearScheduledHeartbeat();
|
|
515
|
+
}
|
|
516
|
+
if (!abortController.signal.aborted) {
|
|
517
|
+
result = encode(result);
|
|
518
|
+
const responseBody = { result };
|
|
519
|
+
if (acceptsStreaming) {
|
|
520
|
+
res.write(encodeSSE(responseBody));
|
|
521
|
+
res.end();
|
|
522
|
+
} else {
|
|
523
|
+
res.status(200).send(responseBody);
|
|
524
|
+
}
|
|
525
|
+
} else {
|
|
526
|
+
res.end();
|
|
527
|
+
}
|
|
528
|
+
} catch (err) {
|
|
529
|
+
if (!abortController.signal.aborted) {
|
|
530
|
+
let httpErr = err;
|
|
531
|
+
if (!(err instanceof HttpsError)) {
|
|
532
|
+
require_logger_index.error("Unhandled error", err);
|
|
533
|
+
httpErr = new HttpsError("internal", "INTERNAL");
|
|
534
|
+
}
|
|
535
|
+
const { status } = httpErr.httpErrorCode;
|
|
536
|
+
const body = { error: httpErr.toJSON() };
|
|
537
|
+
if (version === "gcfv2" && req.header("accept") === "text/event-stream") {
|
|
538
|
+
res.write(encodeSSE(body));
|
|
539
|
+
res.end();
|
|
540
|
+
} else {
|
|
541
|
+
res.status(status).send(body);
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
res.end();
|
|
545
|
+
}
|
|
546
|
+
} finally {
|
|
547
|
+
clearScheduledHeartbeat();
|
|
548
|
+
}
|
|
549
|
+
};
|
|
591
550
|
}
|
|
551
|
+
/**
|
|
552
|
+
* Wraps an HTTP handler with a safety net for unhandled errors.
|
|
553
|
+
*
|
|
554
|
+
* This wrapper catches both synchronous errors and rejected Promises from `async` handlers.
|
|
555
|
+
* Without this, an unhandled error in an `async` handler would cause the request to hang
|
|
556
|
+
* until the platform timeout, as Express (v4) does not await handlers.
|
|
557
|
+
*
|
|
558
|
+
* It logs the error and returns a 500 Internal Server Error to the client if the response
|
|
559
|
+
* headers have not yet been sent.
|
|
560
|
+
*
|
|
561
|
+
* @internal
|
|
562
|
+
*/
|
|
563
|
+
function withErrorHandler(handler) {
|
|
564
|
+
return async (req, res) => {
|
|
565
|
+
try {
|
|
566
|
+
await handler(req, res);
|
|
567
|
+
} catch (err) {
|
|
568
|
+
require_logger_index.error("Unhandled error", err);
|
|
569
|
+
if (!res.headersSent) {
|
|
570
|
+
res.status(500).send("Internal Server Error");
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
//#endregion
|
|
577
|
+
exports.CALLABLE_AUTH_HEADER = CALLABLE_AUTH_HEADER;
|
|
578
|
+
exports.DEFAULT_HEARTBEAT_SECONDS = DEFAULT_HEARTBEAT_SECONDS;
|
|
579
|
+
exports.HttpsError = HttpsError;
|
|
580
|
+
exports.ORIGINAL_AUTH_HEADER = ORIGINAL_AUTH_HEADER;
|
|
581
|
+
exports.checkAuthToken = checkAuthToken;
|
|
582
|
+
exports.decode = decode;
|
|
583
|
+
exports.encode = encode;
|
|
584
|
+
exports.isValidRequest = isValidRequest;
|
|
585
|
+
exports.onCallHandler = onCallHandler;
|
|
586
|
+
exports.unsafeDecodeAppCheckToken = unsafeDecodeAppCheckToken;
|
|
587
|
+
exports.unsafeDecodeIdToken = unsafeDecodeIdToken;
|
|
588
|
+
exports.unsafeDecodeToken = unsafeDecodeToken;
|
|
589
|
+
exports.withErrorHandler = withErrorHandler;
|