firebase-functions 3.16.0 → 3.18.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/bin/firebase-functions.d.ts +2 -0
- package/lib/bin/firebase-functions.js +48 -0
- package/lib/cloud-functions.d.ts +15 -8
- package/lib/cloud-functions.js +63 -2
- package/lib/common/encoding.js +6 -0
- package/lib/common/providers/https.d.ts +49 -10
- package/lib/common/providers/https.js +114 -57
- package/lib/common/providers/identity.d.ts +29 -0
- package/lib/common/providers/identity.js +96 -0
- package/lib/function-builder.d.ts +7 -1
- package/lib/function-builder.js +15 -0
- package/lib/function-configuration.d.ts +1 -0
- package/lib/handler-builder.d.ts +19 -15
- package/lib/handler-builder.js +32 -15
- package/lib/providers/auth.d.ts +2 -21
- package/lib/providers/auth.js +5 -66
- package/lib/providers/database.js +2 -1
- package/lib/providers/https.d.ts +44 -2
- package/lib/providers/https.js +61 -1
- package/lib/runtime/loader.d.ts +1 -0
- package/lib/runtime/loader.js +101 -0
- package/lib/runtime/manifest.d.ts +59 -0
- package/lib/runtime/manifest.js +2 -0
- package/lib/v2/core.d.ts +10 -3
- package/lib/v2/index.d.ts +2 -1
- package/lib/v2/index.js +3 -1
- package/lib/v2/options.d.ts +1 -1
- package/lib/v2/options.js +47 -9
- package/lib/v2/params/types.d.ts +2 -1
- package/lib/v2/params/types.js +2 -0
- package/lib/v2/providers/alerts/alerts.d.ts +36 -0
- package/lib/v2/providers/alerts/alerts.js +72 -0
- package/lib/v2/providers/alerts/appDistribution.d.ts +35 -0
- package/lib/v2/providers/alerts/appDistribution.js +39 -0
- package/lib/v2/providers/alerts/billing.d.ts +38 -0
- package/lib/v2/providers/alerts/billing.js +30 -0
- package/lib/v2/providers/alerts/crashlytics.d.ts +123 -0
- package/lib/v2/providers/alerts/crashlytics.js +74 -0
- package/lib/v2/providers/alerts/index.d.ts +5 -0
- package/lib/v2/providers/alerts/index.js +20 -0
- package/lib/v2/providers/https.d.ts +23 -3
- package/lib/v2/providers/https.js +71 -2
- package/lib/v2/providers/pubsub.d.ts +1 -1
- package/lib/v2/providers/pubsub.js +19 -4
- package/lib/v2/providers/storage.d.ts +1 -1
- package/lib/v2/providers/storage.js +32 -5
- package/package.json +27 -4
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const express = require("express");
|
|
5
|
+
const loader_1 = require("../runtime/loader");
|
|
6
|
+
function printUsageAndExit() {
|
|
7
|
+
console.error(`
|
|
8
|
+
Usage: firebase-functions [functionsDir]
|
|
9
|
+
|
|
10
|
+
Arguments:
|
|
11
|
+
- functionsDir: Directory containing source code for Firebase Functions.
|
|
12
|
+
`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
let functionsDir = '.';
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
if (args.length > 1) {
|
|
18
|
+
if (args[0] === '-h' || args[0] === '--help') {
|
|
19
|
+
printUsageAndExit();
|
|
20
|
+
}
|
|
21
|
+
functionsDir = args[0];
|
|
22
|
+
}
|
|
23
|
+
let server;
|
|
24
|
+
const app = express();
|
|
25
|
+
async function handleQuitquitquit(req, res) {
|
|
26
|
+
res.send('ok');
|
|
27
|
+
server.close(() => console.log('shutdown requested via /__/quitquitquit'));
|
|
28
|
+
}
|
|
29
|
+
app.get('/__/quitquitquit', handleQuitquitquit);
|
|
30
|
+
app.post('/__/quitquitquit', handleQuitquitquit);
|
|
31
|
+
app.get('/__/stack.yaml', async (req, res) => {
|
|
32
|
+
try {
|
|
33
|
+
const stack = await (0, loader_1.loadStack)(functionsDir);
|
|
34
|
+
res.setHeader('content-type', 'text/yaml');
|
|
35
|
+
res.send(JSON.stringify(stack));
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
res
|
|
39
|
+
.status(400)
|
|
40
|
+
.send(`Failed to generate manifest from function source: ${e}`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
let port = 8080;
|
|
44
|
+
if (process.env.STACK_CONTROL_API_PORT) {
|
|
45
|
+
port = Number.parseInt(process.env.STACK_CONTROL_API_PORT);
|
|
46
|
+
}
|
|
47
|
+
console.log('Serving at port', port);
|
|
48
|
+
server = app.listen(port);
|
package/lib/cloud-functions.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Request, Response } from 'express';
|
|
|
2
2
|
import { DeploymentOptions, FailurePolicy, Schedule } from './function-configuration';
|
|
3
3
|
export { Request, Response };
|
|
4
4
|
import { Duration } from './common/encoding';
|
|
5
|
+
import { ManifestEndpoint, ManifestRequiredAPI } from './runtime/manifest';
|
|
5
6
|
/**
|
|
6
7
|
* @hidden
|
|
7
8
|
*
|
|
@@ -198,8 +199,17 @@ export interface TriggerAnnotated {
|
|
|
198
199
|
vpcConnectorEgressSettings?: string;
|
|
199
200
|
serviceAccountEmail?: string;
|
|
200
201
|
ingressSettings?: string;
|
|
202
|
+
secrets?: string[];
|
|
201
203
|
};
|
|
202
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* @hidden
|
|
207
|
+
* EndpointAnnotated is used to generate the manifest that conforms to the container contract.
|
|
208
|
+
*/
|
|
209
|
+
export interface EndpointAnnotated {
|
|
210
|
+
__endpoint: ManifestEndpoint;
|
|
211
|
+
__requiredAPIs?: ManifestRequiredAPI[];
|
|
212
|
+
}
|
|
203
213
|
/**
|
|
204
214
|
* A Runnable has a `run` method which directly invokes the user-defined
|
|
205
215
|
* function - useful for unit testing.
|
|
@@ -216,7 +226,7 @@ export interface Runnable<T> {
|
|
|
216
226
|
* [`Response`](https://expressjs.com/en/api.html#res) objects as its only
|
|
217
227
|
* arguments.
|
|
218
228
|
*/
|
|
219
|
-
export declare type HttpsFunction = TriggerAnnotated & ((req: Request, resp: Response) => void | Promise<void>);
|
|
229
|
+
export declare type HttpsFunction = TriggerAnnotated & EndpointAnnotated & ((req: Request, resp: Response) => void | Promise<void>);
|
|
220
230
|
/**
|
|
221
231
|
* The Cloud Function type for all non-HTTPS triggers. This should be exported
|
|
222
232
|
* from your JavaScript file to define a Cloud Function.
|
|
@@ -224,7 +234,7 @@ export declare type HttpsFunction = TriggerAnnotated & ((req: Request, resp: Res
|
|
|
224
234
|
* This type is a special JavaScript function which takes a templated
|
|
225
235
|
* `Event` object as its only argument.
|
|
226
236
|
*/
|
|
227
|
-
export declare type CloudFunction<T> = Runnable<T> & TriggerAnnotated & ((input: any, context?: any) => PromiseLike<any> | any);
|
|
237
|
+
export declare type CloudFunction<T> = Runnable<T> & TriggerAnnotated & EndpointAnnotated & ((input: any, context?: any) => PromiseLike<any> | any);
|
|
228
238
|
/** @hidden */
|
|
229
239
|
export interface MakeCloudFunctionArgs<EventData> {
|
|
230
240
|
after?: (raw: Event) => void;
|
|
@@ -233,13 +243,9 @@ export interface MakeCloudFunctionArgs<EventData> {
|
|
|
233
243
|
dataConstructor?: (raw: Event) => EventData;
|
|
234
244
|
eventType: string;
|
|
235
245
|
handler?: (data: EventData, context: EventContext) => PromiseLike<any> | any;
|
|
236
|
-
labels?:
|
|
237
|
-
[key: string]: any;
|
|
238
|
-
};
|
|
246
|
+
labels?: Record<string, string>;
|
|
239
247
|
legacyEventType?: string;
|
|
240
|
-
options?:
|
|
241
|
-
[key: string]: any;
|
|
242
|
-
};
|
|
248
|
+
options?: DeploymentOptions;
|
|
243
249
|
provider: string;
|
|
244
250
|
service: string;
|
|
245
251
|
triggerResource: () => string;
|
|
@@ -248,3 +254,4 @@ export interface MakeCloudFunctionArgs<EventData> {
|
|
|
248
254
|
export declare function makeCloudFunction<EventData>({ after, before, contextOnlyHandler, dataConstructor, eventType, handler, labels, legacyEventType, options, provider, service, triggerResource, }: MakeCloudFunctionArgs<EventData>): CloudFunction<EventData>;
|
|
249
255
|
/** @hidden */
|
|
250
256
|
export declare function optionsToTrigger(options: DeploymentOptions): any;
|
|
257
|
+
export declare function optionsToEndpoint(options: DeploymentOptions): ManifestEndpoint;
|
package/lib/cloud-functions.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
// SOFTWARE.
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.optionsToTrigger = exports.makeCloudFunction = exports.Change = void 0;
|
|
24
|
+
exports.optionsToEndpoint = exports.optionsToTrigger = exports.makeCloudFunction = exports.Change = void 0;
|
|
25
25
|
const _ = require("lodash");
|
|
26
26
|
const function_configuration_1 = require("./function-configuration");
|
|
27
27
|
const logger_1 = require("./logger");
|
|
@@ -165,6 +165,42 @@ function makeCloudFunction({ after = () => { }, before = () => { }, contextOnlyH
|
|
|
165
165
|
return trigger;
|
|
166
166
|
},
|
|
167
167
|
});
|
|
168
|
+
Object.defineProperty(cloudFunction, '__endpoint', {
|
|
169
|
+
get: () => {
|
|
170
|
+
if (triggerResource() == null) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const endpoint = {
|
|
174
|
+
platform: 'gcfv1',
|
|
175
|
+
...optionsToEndpoint(options),
|
|
176
|
+
};
|
|
177
|
+
if (options.schedule) {
|
|
178
|
+
endpoint.scheduleTrigger = options.schedule;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
endpoint.eventTrigger = {
|
|
182
|
+
eventType: legacyEventType || provider + '.' + eventType,
|
|
183
|
+
eventFilters: {
|
|
184
|
+
resource: triggerResource(),
|
|
185
|
+
},
|
|
186
|
+
retry: !!options.failurePolicy,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// Note: We intentionally don't make use of labels args here.
|
|
190
|
+
// labels is used to pass SDK-defined labels to the trigger, which isn't
|
|
191
|
+
// something we will do in the container contract world.
|
|
192
|
+
endpoint.labels = { ...endpoint.labels };
|
|
193
|
+
return endpoint;
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
if (options.schedule) {
|
|
197
|
+
cloudFunction.__requiredAPIs = [
|
|
198
|
+
{
|
|
199
|
+
api: 'cloudscheduler.googleapis.com',
|
|
200
|
+
reason: 'Needed for scheduled functions.',
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
}
|
|
168
204
|
cloudFunction.run = handler || contextOnlyHandler;
|
|
169
205
|
return cloudFunction;
|
|
170
206
|
}
|
|
@@ -216,7 +252,7 @@ function _detectAuthType(event) {
|
|
|
216
252
|
/** @hidden */
|
|
217
253
|
function optionsToTrigger(options) {
|
|
218
254
|
const trigger = {};
|
|
219
|
-
(0, encoding_1.copyIfPresent)(trigger, options, 'regions', 'schedule', 'minInstances', 'maxInstances', 'ingressSettings', 'vpcConnectorEgressSettings', 'vpcConnector', 'labels');
|
|
255
|
+
(0, encoding_1.copyIfPresent)(trigger, options, 'regions', 'schedule', 'minInstances', 'maxInstances', 'ingressSettings', 'vpcConnectorEgressSettings', 'vpcConnector', 'labels', 'secrets');
|
|
220
256
|
(0, encoding_1.convertIfPresent)(trigger, options, 'failurePolicy', 'failurePolicy', (policy) => {
|
|
221
257
|
if (policy === false) {
|
|
222
258
|
return undefined;
|
|
@@ -245,3 +281,28 @@ function optionsToTrigger(options) {
|
|
|
245
281
|
return trigger;
|
|
246
282
|
}
|
|
247
283
|
exports.optionsToTrigger = optionsToTrigger;
|
|
284
|
+
function optionsToEndpoint(options) {
|
|
285
|
+
const endpoint = {};
|
|
286
|
+
(0, encoding_1.copyIfPresent)(endpoint, options, 'minInstances', 'maxInstances', 'ingressSettings', 'labels', 'timeoutSeconds');
|
|
287
|
+
(0, encoding_1.convertIfPresent)(endpoint, options, 'region', 'regions');
|
|
288
|
+
(0, encoding_1.convertIfPresent)(endpoint, options, 'serviceAccountEmail', 'serviceAccount', (sa) => sa);
|
|
289
|
+
(0, encoding_1.convertIfPresent)(endpoint, options, 'secretEnvironmentVariables', 'secrets', (secrets) => secrets.map((secret) => ({ secret, key: secret })));
|
|
290
|
+
if (options === null || options === void 0 ? void 0 : options.vpcConnector) {
|
|
291
|
+
endpoint.vpc = { connector: options.vpcConnector };
|
|
292
|
+
(0, encoding_1.convertIfPresent)(endpoint.vpc, options, 'egressSettings', 'vpcConnectorEgressSettings');
|
|
293
|
+
}
|
|
294
|
+
(0, encoding_1.convertIfPresent)(endpoint, options, 'availableMemoryMb', 'memory', (mem) => {
|
|
295
|
+
const memoryLookup = {
|
|
296
|
+
'128MB': 128,
|
|
297
|
+
'256MB': 256,
|
|
298
|
+
'512MB': 512,
|
|
299
|
+
'1GB': 1024,
|
|
300
|
+
'2GB': 2048,
|
|
301
|
+
'4GB': 4096,
|
|
302
|
+
'8GB': 8192,
|
|
303
|
+
};
|
|
304
|
+
return memoryLookup[mem];
|
|
305
|
+
});
|
|
306
|
+
return endpoint;
|
|
307
|
+
}
|
|
308
|
+
exports.optionsToEndpoint = optionsToEndpoint;
|
package/lib/common/encoding.js
CHANGED
|
@@ -13,6 +13,9 @@ exports.durationFromSeconds = durationFromSeconds;
|
|
|
13
13
|
* in A and B, but cannot verify that both Src and Dest have the same type for the same field.
|
|
14
14
|
*/
|
|
15
15
|
function copyIfPresent(dest, src, ...fields) {
|
|
16
|
+
if (!src) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
16
19
|
for (const field of fields) {
|
|
17
20
|
if (!Object.prototype.hasOwnProperty.call(src, field)) {
|
|
18
21
|
continue;
|
|
@@ -24,6 +27,9 @@ exports.copyIfPresent = copyIfPresent;
|
|
|
24
27
|
function convertIfPresent(dest, src, destField, srcField, converter = (from) => {
|
|
25
28
|
return from;
|
|
26
29
|
}) {
|
|
30
|
+
if (!src) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
27
33
|
if (!Object.prototype.hasOwnProperty.call(src, srcField)) {
|
|
28
34
|
return;
|
|
29
35
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import * as cors from 'cors';
|
|
3
2
|
import * as express from 'express';
|
|
4
3
|
import * as firebase from 'firebase-admin';
|
|
5
4
|
/** @hidden */
|
|
@@ -108,6 +107,55 @@ export interface CallableRequest<T = any> {
|
|
|
108
107
|
*/
|
|
109
108
|
rawRequest: Request;
|
|
110
109
|
}
|
|
110
|
+
/** How a task should be retried in the event of a non-2xx return. */
|
|
111
|
+
export interface TaskRetryConfig {
|
|
112
|
+
/**
|
|
113
|
+
* Maximum number of times a request should be attempted.
|
|
114
|
+
* If left unspecified, will default to 3.
|
|
115
|
+
*/
|
|
116
|
+
maxAttempts?: number;
|
|
117
|
+
/**
|
|
118
|
+
* The maximum amount of time to wait between attempts.
|
|
119
|
+
* If left unspecified will default to 1hr.
|
|
120
|
+
*/
|
|
121
|
+
maxBackoffSeconds?: number;
|
|
122
|
+
/**
|
|
123
|
+
* The maximum number of times to double the backoff between
|
|
124
|
+
* retries. If left unspecified will default to 16.
|
|
125
|
+
*/
|
|
126
|
+
maxDoublings?: number;
|
|
127
|
+
/**
|
|
128
|
+
* The minimum time to wait between attempts. If left unspecified
|
|
129
|
+
* will default to 100ms.
|
|
130
|
+
*/
|
|
131
|
+
minBackoffSeconds?: number;
|
|
132
|
+
}
|
|
133
|
+
/** How congestion control should be applied to the function. */
|
|
134
|
+
export interface TaskRateLimits {
|
|
135
|
+
maxBurstSize?: number;
|
|
136
|
+
maxConcurrentDispatches?: number;
|
|
137
|
+
maxDispatchesPerSecond?: number;
|
|
138
|
+
}
|
|
139
|
+
/** Metadata about a call to a Task Queue function. */
|
|
140
|
+
export interface TaskContext {
|
|
141
|
+
/**
|
|
142
|
+
* The result of decoding and verifying an ODIC token.
|
|
143
|
+
*/
|
|
144
|
+
auth?: AuthData;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The request used to call a Task Queue function.
|
|
148
|
+
*/
|
|
149
|
+
export interface TaskRequest<T = any> {
|
|
150
|
+
/**
|
|
151
|
+
* The parameters used by a client when calling this function.
|
|
152
|
+
*/
|
|
153
|
+
data: T;
|
|
154
|
+
/**
|
|
155
|
+
* The result of decoding and verifying an ODIC token.
|
|
156
|
+
*/
|
|
157
|
+
auth?: AuthData;
|
|
158
|
+
}
|
|
111
159
|
/**
|
|
112
160
|
* The set of Firebase Functions status codes. The codes are the same at the
|
|
113
161
|
* ones exposed by gRPC here:
|
|
@@ -196,13 +244,4 @@ export declare function encode(data: any): any;
|
|
|
196
244
|
*/
|
|
197
245
|
/** @hidden */
|
|
198
246
|
export declare function decode(data: any): any;
|
|
199
|
-
declare type v1Handler = (data: any, context: CallableContext) => any | Promise<any>;
|
|
200
|
-
declare type v2Handler<Req, Res> = (request: CallableRequest<Req>) => Res;
|
|
201
|
-
/** @hidden **/
|
|
202
|
-
export interface CallableOptions {
|
|
203
|
-
cors: cors.CorsOptions;
|
|
204
|
-
allowInvalidAppCheckToken?: boolean;
|
|
205
|
-
}
|
|
206
|
-
/** @hidden */
|
|
207
|
-
export declare function onCallHandler<Req = any, Res = any>(options: CallableOptions, handler: v1Handler | v2Handler<Req, Res>): (req: Request, res: express.Response) => Promise<void>;
|
|
208
247
|
export {};
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
// SOFTWARE.
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.onCallHandler = exports.unsafeDecodeAppCheckToken = exports.unsafeDecodeIdToken = exports.decode = exports.encode = exports.HttpsError = void 0;
|
|
24
|
+
exports.onDispatchHandler = exports.onCallHandler = exports.unsafeDecodeAppCheckToken = exports.unsafeDecodeIdToken = exports.decode = exports.encode = exports.HttpsError = void 0;
|
|
25
25
|
const cors = require("cors");
|
|
26
26
|
const logger = require("../../logger");
|
|
27
27
|
// TODO(inlined): Decide whether we want to un-version apps or whether we want a
|
|
@@ -263,64 +263,20 @@ exports.unsafeDecodeAppCheckToken = unsafeDecodeAppCheckToken;
|
|
|
263
263
|
* @param {CallableContext} ctx - Context to be sent to callable function handler.
|
|
264
264
|
* @return {CallableTokenStatus} Status of the token verifications.
|
|
265
265
|
*/
|
|
266
|
-
/** @
|
|
266
|
+
/** @internal */
|
|
267
267
|
async function checkTokens(req, ctx) {
|
|
268
268
|
const verifications = {
|
|
269
|
-
app: '
|
|
270
|
-
auth: '
|
|
269
|
+
app: 'INVALID',
|
|
270
|
+
auth: 'INVALID',
|
|
271
271
|
};
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
let appCheckData;
|
|
281
|
-
if (skipTokenVerify) {
|
|
282
|
-
const decodedToken = unsafeDecodeAppCheckToken(appCheck);
|
|
283
|
-
appCheckData = { appId: decodedToken.app_id, token: decodedToken };
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
appCheckData = await (0, apps_1.apps)()
|
|
287
|
-
.admin.appCheck()
|
|
288
|
-
.verifyToken(appCheck);
|
|
289
|
-
}
|
|
290
|
-
ctx.app = appCheckData;
|
|
291
|
-
verifications.app = 'VALID';
|
|
292
|
-
}
|
|
293
|
-
catch (err) {
|
|
294
|
-
logger.warn('Failed to validate AppCheck token.', err);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
const authorization = req.header('Authorization');
|
|
298
|
-
if (authorization) {
|
|
299
|
-
verifications.auth = 'INVALID';
|
|
300
|
-
const match = authorization.match(/^Bearer (.*)$/);
|
|
301
|
-
if (match) {
|
|
302
|
-
const idToken = match[1];
|
|
303
|
-
try {
|
|
304
|
-
let authToken;
|
|
305
|
-
if (skipTokenVerify) {
|
|
306
|
-
authToken = unsafeDecodeIdToken(idToken);
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
authToken = await (0, apps_1.apps)()
|
|
310
|
-
.admin.auth()
|
|
311
|
-
.verifyIdToken(idToken);
|
|
312
|
-
}
|
|
313
|
-
verifications.auth = 'VALID';
|
|
314
|
-
ctx.auth = {
|
|
315
|
-
uid: authToken.uid,
|
|
316
|
-
token: authToken,
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
catch (err) {
|
|
320
|
-
logger.warn('Failed to validate auth token.', err);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
272
|
+
await Promise.all([
|
|
273
|
+
Promise.resolve().then(async () => {
|
|
274
|
+
verifications.auth = await checkAuthToken(req, ctx);
|
|
275
|
+
}),
|
|
276
|
+
Promise.resolve().then(async () => {
|
|
277
|
+
verifications.app = await checkAppCheckToken(req, ctx);
|
|
278
|
+
}),
|
|
279
|
+
]);
|
|
324
280
|
const logPayload = {
|
|
325
281
|
verifications,
|
|
326
282
|
'logging.googleapis.com/labels': {
|
|
@@ -342,7 +298,66 @@ async function checkTokens(req, ctx) {
|
|
|
342
298
|
}
|
|
343
299
|
return verifications;
|
|
344
300
|
}
|
|
345
|
-
/** @
|
|
301
|
+
/** @interanl */
|
|
302
|
+
async function checkAuthToken(req, ctx) {
|
|
303
|
+
const authorization = req.header('Authorization');
|
|
304
|
+
if (!authorization) {
|
|
305
|
+
return 'MISSING';
|
|
306
|
+
}
|
|
307
|
+
const match = authorization.match(/^Bearer (.*)$/);
|
|
308
|
+
if (match) {
|
|
309
|
+
const idToken = match[1];
|
|
310
|
+
try {
|
|
311
|
+
let authToken;
|
|
312
|
+
if ((0, debug_1.isDebugFeatureEnabled)('skipTokenVerification')) {
|
|
313
|
+
authToken = unsafeDecodeIdToken(idToken);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
authToken = await (0, apps_1.apps)()
|
|
317
|
+
.admin.auth()
|
|
318
|
+
.verifyIdToken(idToken);
|
|
319
|
+
}
|
|
320
|
+
ctx.auth = {
|
|
321
|
+
uid: authToken.uid,
|
|
322
|
+
token: authToken,
|
|
323
|
+
};
|
|
324
|
+
return 'VALID';
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
logger.warn('Failed to validate auth token.', err);
|
|
328
|
+
return 'INVALID';
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/** @internal */
|
|
333
|
+
async function checkAppCheckToken(req, ctx) {
|
|
334
|
+
const appCheck = req.header('X-Firebase-AppCheck');
|
|
335
|
+
if (!appCheck) {
|
|
336
|
+
return 'MISSING';
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
if (!(0, apps_1.apps)().admin.appCheck) {
|
|
340
|
+
throw new Error('Cannot validate AppCheck token. Please update Firebase Admin SDK to >= v9.8.0');
|
|
341
|
+
}
|
|
342
|
+
let appCheckData;
|
|
343
|
+
if ((0, debug_1.isDebugFeatureEnabled)('skipTokenVerification')) {
|
|
344
|
+
const decodedToken = unsafeDecodeAppCheckToken(appCheck);
|
|
345
|
+
appCheckData = { appId: decodedToken.app_id, token: decodedToken };
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
appCheckData = await (0, apps_1.apps)()
|
|
349
|
+
.admin.appCheck()
|
|
350
|
+
.verifyToken(appCheck);
|
|
351
|
+
}
|
|
352
|
+
ctx.app = appCheckData;
|
|
353
|
+
return 'VALID';
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
logger.warn('Failed to validate AppCheck token.', err);
|
|
357
|
+
return 'INVALID';
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/** @internal */
|
|
346
361
|
function onCallHandler(options, handler) {
|
|
347
362
|
const wrapped = wrapOnCallHandler(options, handler);
|
|
348
363
|
return (req, res) => {
|
|
@@ -411,3 +426,45 @@ function wrapOnCallHandler(options, handler) {
|
|
|
411
426
|
}
|
|
412
427
|
};
|
|
413
428
|
}
|
|
429
|
+
/** @internal */
|
|
430
|
+
function onDispatchHandler(handler) {
|
|
431
|
+
return async (req, res) => {
|
|
432
|
+
try {
|
|
433
|
+
if (!isValidRequest(req)) {
|
|
434
|
+
logger.error('Invalid request, unable to process.');
|
|
435
|
+
throw new HttpsError('invalid-argument', 'Bad Request');
|
|
436
|
+
}
|
|
437
|
+
const context = {};
|
|
438
|
+
const status = await checkAuthToken(req, context);
|
|
439
|
+
// Note: this should never happen since task queue functions are guarded by IAM.
|
|
440
|
+
if (status === 'INVALID') {
|
|
441
|
+
throw new HttpsError('unauthenticated', 'Unauthenticated');
|
|
442
|
+
}
|
|
443
|
+
const data = decode(req.body.data);
|
|
444
|
+
if (handler.length === 2) {
|
|
445
|
+
await handler(data, context);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
const arg = {
|
|
449
|
+
...context,
|
|
450
|
+
data,
|
|
451
|
+
};
|
|
452
|
+
// For some reason the type system isn't picking up that the handler
|
|
453
|
+
// is a one argument function.
|
|
454
|
+
await handler(arg);
|
|
455
|
+
}
|
|
456
|
+
res.status(204).end();
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
if (!(err instanceof HttpsError)) {
|
|
460
|
+
// This doesn't count as an 'explicit' error.
|
|
461
|
+
logger.error('Unhandled error', err);
|
|
462
|
+
err = new HttpsError('internal', 'INTERNAL');
|
|
463
|
+
}
|
|
464
|
+
const { status } = err.httpErrorCode;
|
|
465
|
+
const body = { error: err.toJSON() };
|
|
466
|
+
res.status(status).send(body);
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
exports.onDispatchHandler = onDispatchHandler;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as firebase from 'firebase-admin';
|
|
2
|
+
/**
|
|
3
|
+
* The UserRecord passed to Cloud Functions is the same UserRecord that is returned by the Firebase Admin
|
|
4
|
+
* SDK.
|
|
5
|
+
*/
|
|
6
|
+
export declare type UserRecord = firebase.auth.UserRecord;
|
|
7
|
+
/**
|
|
8
|
+
* UserInfo that is part of the UserRecord
|
|
9
|
+
*/
|
|
10
|
+
export declare type UserInfo = firebase.auth.UserInfo;
|
|
11
|
+
/**
|
|
12
|
+
* Helper class to create the user metadata in a UserRecord object
|
|
13
|
+
*/
|
|
14
|
+
export declare class UserRecordMetadata implements firebase.auth.UserMetadata {
|
|
15
|
+
creationTime: string;
|
|
16
|
+
lastSignInTime: string;
|
|
17
|
+
constructor(creationTime: string, lastSignInTime: string);
|
|
18
|
+
/** Returns a plain JavaScript object with the properties of UserRecordMetadata. */
|
|
19
|
+
toJSON(): {
|
|
20
|
+
creationTime: string;
|
|
21
|
+
lastSignInTime: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Helper function that creates a UserRecord Class from data sent over the wire.
|
|
26
|
+
* @param wireData data sent over the wire
|
|
27
|
+
* @returns an instance of UserRecord with correct toJSON functions
|
|
28
|
+
*/
|
|
29
|
+
export declare function userRecordConstructor(wireData: Object): UserRecord;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The MIT License (MIT)
|
|
3
|
+
//
|
|
4
|
+
// Copyright (c) 2022 Firebase
|
|
5
|
+
//
|
|
6
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
// in the Software without restriction, including without limitation the rights
|
|
9
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
// furnished to do so, subject to the following conditions:
|
|
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.userRecordConstructor = exports.UserRecordMetadata = void 0;
|
|
25
|
+
const _ = require("lodash");
|
|
26
|
+
/**
|
|
27
|
+
* Helper class to create the user metadata in a UserRecord object
|
|
28
|
+
*/
|
|
29
|
+
class UserRecordMetadata {
|
|
30
|
+
constructor(creationTime, lastSignInTime) {
|
|
31
|
+
this.creationTime = creationTime;
|
|
32
|
+
this.lastSignInTime = lastSignInTime;
|
|
33
|
+
}
|
|
34
|
+
/** Returns a plain JavaScript object with the properties of UserRecordMetadata. */
|
|
35
|
+
toJSON() {
|
|
36
|
+
return {
|
|
37
|
+
creationTime: this.creationTime,
|
|
38
|
+
lastSignInTime: this.lastSignInTime,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.UserRecordMetadata = UserRecordMetadata;
|
|
43
|
+
/**
|
|
44
|
+
* Helper function that creates a UserRecord Class from data sent over the wire.
|
|
45
|
+
* @param wireData data sent over the wire
|
|
46
|
+
* @returns an instance of UserRecord with correct toJSON functions
|
|
47
|
+
*/
|
|
48
|
+
function userRecordConstructor(wireData) {
|
|
49
|
+
// Falsey values from the wire format proto get lost when converted to JSON, this adds them back.
|
|
50
|
+
const falseyValues = {
|
|
51
|
+
email: null,
|
|
52
|
+
emailVerified: false,
|
|
53
|
+
displayName: null,
|
|
54
|
+
photoURL: null,
|
|
55
|
+
phoneNumber: null,
|
|
56
|
+
disabled: false,
|
|
57
|
+
providerData: [],
|
|
58
|
+
customClaims: {},
|
|
59
|
+
passwordSalt: null,
|
|
60
|
+
passwordHash: null,
|
|
61
|
+
tokensValidAfterTime: null,
|
|
62
|
+
};
|
|
63
|
+
const record = _.assign({}, falseyValues, wireData);
|
|
64
|
+
const meta = _.get(record, 'metadata');
|
|
65
|
+
if (meta) {
|
|
66
|
+
_.set(record, 'metadata', new UserRecordMetadata(meta.createdAt || meta.creationTime, meta.lastSignedInAt || meta.lastSignInTime));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
_.set(record, 'metadata', new UserRecordMetadata(null, null));
|
|
70
|
+
}
|
|
71
|
+
_.forEach(record.providerData, (entry) => {
|
|
72
|
+
_.set(entry, 'toJSON', () => {
|
|
73
|
+
return entry;
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
_.set(record, 'toJSON', () => {
|
|
77
|
+
const json = _.pick(record, [
|
|
78
|
+
'uid',
|
|
79
|
+
'email',
|
|
80
|
+
'emailVerified',
|
|
81
|
+
'displayName',
|
|
82
|
+
'photoURL',
|
|
83
|
+
'phoneNumber',
|
|
84
|
+
'disabled',
|
|
85
|
+
'passwordHash',
|
|
86
|
+
'passwordSalt',
|
|
87
|
+
'tokensValidAfterTime',
|
|
88
|
+
]);
|
|
89
|
+
json.metadata = _.get(record, 'metadata').toJSON();
|
|
90
|
+
json.customClaims = _.cloneDeep(record.customClaims);
|
|
91
|
+
json.providerData = _.map(record.providerData, (entry) => entry.toJSON());
|
|
92
|
+
return json;
|
|
93
|
+
});
|
|
94
|
+
return record;
|
|
95
|
+
}
|
|
96
|
+
exports.userRecordConstructor = userRecordConstructor;
|
|
@@ -77,7 +77,13 @@ export declare class FunctionBuilder {
|
|
|
77
77
|
* Declares a callable method for clients to call using a Firebase SDK.
|
|
78
78
|
* @param handler A method that takes a data and context and returns a value.
|
|
79
79
|
*/
|
|
80
|
-
onCall: (handler: (data: any, context: https.CallableContext) => any | Promise<any>) => import("./cloud-functions").TriggerAnnotated & ((req: express.Request<import("express-serve-static-core").ParamsDictionary>, resp: express.Response<any>) => void | Promise<void>) & import("./cloud-functions").Runnable<any>;
|
|
80
|
+
onCall: (handler: (data: any, context: https.CallableContext) => any | Promise<any>) => import("./cloud-functions").TriggerAnnotated & import("./cloud-functions").EndpointAnnotated & ((req: express.Request<import("express-serve-static-core").ParamsDictionary>, resp: express.Response<any>) => void | Promise<void>) & import("./cloud-functions").Runnable<any>;
|
|
81
|
+
/**
|
|
82
|
+
* Declares a task queue function for clients to call using a Firebase Admin SDK.
|
|
83
|
+
* @param options Configurations for the task queue function.
|
|
84
|
+
*/
|
|
85
|
+
/** @hidden */
|
|
86
|
+
taskQueue: (options?: https.TaskQueueOptions) => https.TaskQueueBuilder;
|
|
81
87
|
};
|
|
82
88
|
get database(): {
|
|
83
89
|
/**
|
package/lib/function-builder.js
CHANGED
|
@@ -125,6 +125,13 @@ function assertRuntimeOptionsValid(runtimeOptions) {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
+
if (runtimeOptions.secrets !== undefined) {
|
|
129
|
+
const invalidSecrets = runtimeOptions.secrets.filter((s) => !/^[A-Za-z\d\-_]+$/.test(s));
|
|
130
|
+
if (invalidSecrets.length > 0) {
|
|
131
|
+
throw new Error(`Invalid secrets: ${invalidSecrets.join(',')}. ` +
|
|
132
|
+
'Secret must be configured using the resource id (e.g. API_KEY)');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
128
135
|
return true;
|
|
129
136
|
}
|
|
130
137
|
/**
|
|
@@ -231,6 +238,14 @@ class FunctionBuilder {
|
|
|
231
238
|
* @param handler A method that takes a data and context and returns a value.
|
|
232
239
|
*/
|
|
233
240
|
onCall: (handler) => https._onCallWithOptions(handler, this.options),
|
|
241
|
+
/**
|
|
242
|
+
* Declares a task queue function for clients to call using a Firebase Admin SDK.
|
|
243
|
+
* @param options Configurations for the task queue function.
|
|
244
|
+
*/
|
|
245
|
+
/** @hidden */
|
|
246
|
+
taskQueue: (options) => {
|
|
247
|
+
return new https.TaskQueueBuilder(options, this.options);
|
|
248
|
+
},
|
|
234
249
|
};
|
|
235
250
|
}
|
|
236
251
|
get database() {
|
|
@@ -99,6 +99,7 @@ export interface RuntimeOptions {
|
|
|
99
99
|
*/
|
|
100
100
|
invoker?: 'public' | 'private' | string | string[];
|
|
101
101
|
allowInvalidAppCheckToken?: boolean;
|
|
102
|
+
secrets?: string[];
|
|
102
103
|
}
|
|
103
104
|
export interface DeploymentOptions extends RuntimeOptions {
|
|
104
105
|
regions?: Array<typeof SUPPORTED_REGIONS[number] | string>;
|