firebase-functions 3.19.0 → 3.21.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.
Files changed (46) hide show
  1. package/lib/cloud-functions.d.ts +8 -0
  2. package/lib/cloud-functions.js +4 -7
  3. package/lib/common/providers/https.d.ts +20 -49
  4. package/lib/common/providers/https.js +7 -45
  5. package/lib/common/providers/identity.d.ts +193 -4
  6. package/lib/common/providers/identity.js +402 -28
  7. package/lib/common/providers/tasks.d.ts +58 -0
  8. package/lib/common/providers/tasks.js +77 -0
  9. package/lib/function-builder.d.ts +5 -2
  10. package/lib/function-builder.js +7 -2
  11. package/lib/handler-builder.d.ts +13 -2
  12. package/lib/handler-builder.js +17 -4
  13. package/lib/index.d.ts +2 -1
  14. package/lib/index.js +3 -1
  15. package/lib/providers/auth.d.ts +19 -6
  16. package/lib/providers/auth.js +60 -8
  17. package/lib/providers/https.d.ts +2 -44
  18. package/lib/providers/https.js +1 -49
  19. package/lib/providers/tasks.d.ts +46 -0
  20. package/lib/providers/tasks.js +75 -0
  21. package/lib/runtime/loader.js +9 -7
  22. package/lib/runtime/manifest.d.ts +8 -10
  23. package/lib/runtime/manifest.js +21 -0
  24. package/lib/v2/core.d.ts +3 -21
  25. package/lib/v2/index.d.ts +4 -1
  26. package/lib/v2/index.js +7 -1
  27. package/lib/v2/options.d.ts +13 -2
  28. package/lib/v2/options.js +22 -30
  29. package/lib/v2/providers/alerts/alerts.d.ts +15 -7
  30. package/lib/v2/providers/alerts/alerts.js +4 -10
  31. package/lib/v2/providers/alerts/appDistribution.d.ts +11 -11
  32. package/lib/v2/providers/alerts/billing.d.ts +17 -11
  33. package/lib/v2/providers/alerts/crashlytics.d.ts +62 -29
  34. package/lib/v2/providers/eventarc.d.ts +32 -0
  35. package/lib/v2/providers/eventarc.js +65 -0
  36. package/lib/v2/providers/https.d.ts +8 -23
  37. package/lib/v2/providers/https.js +3 -48
  38. package/lib/v2/providers/identity.d.ts +22 -0
  39. package/lib/v2/providers/identity.js +73 -0
  40. package/lib/v2/providers/pubsub.d.ts +3 -3
  41. package/lib/v2/providers/pubsub.js +2 -5
  42. package/lib/v2/providers/storage.d.ts +17 -13
  43. package/lib/v2/providers/storage.js +5 -4
  44. package/lib/v2/providers/tasks.d.ts +22 -0
  45. package/lib/v2/providers/tasks.js +88 -0
  46. package/package.json +42 -15
@@ -21,8 +21,34 @@
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.userRecordConstructor = exports.UserRecordMetadata = void 0;
25
- const _ = require("lodash");
24
+ exports.wrapHandler = exports.getUpdateMask = exports.validateAuthResponse = exports.parseAuthEventContext = exports.parseAuthUserRecord = exports.parseMultiFactor = exports.parseDate = exports.parseProviderData = exports.parseMetadata = exports.isValidRequest = exports.userRecordConstructor = exports.UserRecordMetadata = exports.HttpsError = void 0;
25
+ const __1 = require("../..");
26
+ const apps_1 = require("../../apps");
27
+ const debug_1 = require("../debug");
28
+ const https_1 = require("./https");
29
+ Object.defineProperty(exports, "HttpsError", { enumerable: true, get: function () { return https_1.HttpsError; } });
30
+ const DISALLOWED_CUSTOM_CLAIMS = [
31
+ 'acr',
32
+ 'amr',
33
+ 'at_hash',
34
+ 'aud',
35
+ 'auth_time',
36
+ 'azp',
37
+ 'cnf',
38
+ 'c_hash',
39
+ 'exp',
40
+ 'iat',
41
+ 'iss',
42
+ 'jti',
43
+ 'nbf',
44
+ 'nonce',
45
+ 'firebase',
46
+ ];
47
+ const CLAIMS_MAX_PAYLOAD_SIZE = 1000;
48
+ const EVENT_MAPPING = {
49
+ beforeCreate: 'providers/cloud.auth/eventTypes/user.beforeCreate',
50
+ beforeSignIn: 'providers/cloud.auth/eventTypes/user.beforeSignIn',
51
+ };
26
52
  /**
27
53
  * Helper class to create the user metadata in a UserRecord object
28
54
  */
@@ -60,37 +86,385 @@ function userRecordConstructor(wireData) {
60
86
  passwordHash: null,
61
87
  tokensValidAfterTime: null,
62
88
  };
63
- const record = _.assign({}, falseyValues, wireData);
64
- const meta = _.get(record, 'metadata');
89
+ const record = { ...falseyValues, ...wireData };
90
+ const meta = record.metadata;
65
91
  if (meta) {
66
- _.set(record, 'metadata', new UserRecordMetadata(meta.createdAt || meta.creationTime, meta.lastSignedInAt || meta.lastSignInTime));
92
+ record.metadata = new UserRecordMetadata(meta.createdAt || meta.creationTime, meta.lastSignedInAt || meta.lastSignInTime);
67
93
  }
68
94
  else {
69
- _.set(record, 'metadata', new UserRecordMetadata(null, null));
95
+ record.metadata = new UserRecordMetadata(null, null);
70
96
  }
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());
97
+ record.toJSON = () => {
98
+ const { uid, email, emailVerified, displayName, photoURL, phoneNumber, disabled, passwordHash, passwordSalt, tokensValidAfterTime, } = record;
99
+ const json = {
100
+ uid,
101
+ email,
102
+ emailVerified,
103
+ displayName,
104
+ photoURL,
105
+ phoneNumber,
106
+ disabled,
107
+ passwordHash,
108
+ passwordSalt,
109
+ tokensValidAfterTime,
110
+ };
111
+ json.metadata = record.metadata.toJSON();
112
+ json.customClaims = JSON.parse(JSON.stringify(record.customClaims));
113
+ json.providerData = record.providerData.map((entry) => entry.toJSON());
92
114
  return json;
93
- });
115
+ };
94
116
  return record;
95
117
  }
96
118
  exports.userRecordConstructor = userRecordConstructor;
119
+ /**
120
+ * Checks for a valid identity platform web request, otherwise throws an HttpsError
121
+ * @internal
122
+ */
123
+ function isValidRequest(req) {
124
+ var _a, _b;
125
+ if (req.method !== 'POST') {
126
+ __1.logger.warn(`Request has invalid method "${req.method}".`);
127
+ return false;
128
+ }
129
+ const contentType = (req.header('Content-Type') || '').toLowerCase();
130
+ if (!contentType.includes('application/json')) {
131
+ __1.logger.warn('Request has invalid header Content-Type.');
132
+ return false;
133
+ }
134
+ if (!((_b = (_a = req.body) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.jwt)) {
135
+ __1.logger.warn('Request has an invalid body.');
136
+ return false;
137
+ }
138
+ return true;
139
+ }
140
+ exports.isValidRequest = isValidRequest;
141
+ /**
142
+ * Decode, but not verify, an Auth Blocking token.
143
+ *
144
+ * Do not use in production. Token should always be verified using the Admin SDK.
145
+ *
146
+ * This is exposed only for testing.
147
+ */
148
+ function unsafeDecodeAuthBlockingToken(token) {
149
+ const decoded = (0, https_1.unsafeDecodeToken)(token);
150
+ decoded.uid = decoded.sub;
151
+ return decoded;
152
+ }
153
+ /**
154
+ * Helper function to parse the decoded metadata object into a UserMetaData object
155
+ * @internal
156
+ */
157
+ function parseMetadata(metadata) {
158
+ const creationTime = (metadata === null || metadata === void 0 ? void 0 : metadata.creation_time)
159
+ ? new Date(metadata.creation_time * 1000).toUTCString()
160
+ : null;
161
+ const lastSignInTime = (metadata === null || metadata === void 0 ? void 0 : metadata.last_sign_in_time)
162
+ ? new Date(metadata.last_sign_in_time * 1000).toUTCString()
163
+ : null;
164
+ return {
165
+ creationTime,
166
+ lastSignInTime,
167
+ };
168
+ }
169
+ exports.parseMetadata = parseMetadata;
170
+ /**
171
+ * Helper function to parse the decoded user info array into an AuthUserInfo array
172
+ * @internal
173
+ */
174
+ function parseProviderData(providerData) {
175
+ const providers = [];
176
+ for (const provider of providerData) {
177
+ providers.push({
178
+ uid: provider.uid,
179
+ displayName: provider.display_name,
180
+ email: provider.email,
181
+ photoURL: provider.photo_url,
182
+ providerId: provider.provider_id,
183
+ phoneNumber: provider.phone_number,
184
+ });
185
+ }
186
+ return providers;
187
+ }
188
+ exports.parseProviderData = parseProviderData;
189
+ /**
190
+ * Helper function to parse the date into a UTC string
191
+ * @internal
192
+ */
193
+ function parseDate(tokensValidAfterTime) {
194
+ if (!tokensValidAfterTime) {
195
+ return null;
196
+ }
197
+ tokensValidAfterTime = tokensValidAfterTime * 1000;
198
+ try {
199
+ const date = new Date(tokensValidAfterTime);
200
+ if (!isNaN(date.getTime())) {
201
+ return date.toUTCString();
202
+ }
203
+ }
204
+ catch (_a) { }
205
+ return null;
206
+ }
207
+ exports.parseDate = parseDate;
208
+ /**
209
+ * Helper function to parse the decoded enrolled factors into a valid MultiFactorSettings
210
+ * @internal
211
+ */
212
+ function parseMultiFactor(multiFactor) {
213
+ if (!multiFactor) {
214
+ return null;
215
+ }
216
+ const parsedEnrolledFactors = [];
217
+ for (const factor of multiFactor.enrolled_factors || []) {
218
+ if (!factor.uid) {
219
+ throw new https_1.HttpsError('internal', 'INTERNAL ASSERT FAILED: Invalid multi-factor info response');
220
+ }
221
+ const enrollmentTime = factor.enrollment_time
222
+ ? new Date(factor.enrollment_time).toUTCString()
223
+ : null;
224
+ parsedEnrolledFactors.push({
225
+ uid: factor.uid,
226
+ factorId: factor.phone_number
227
+ ? factor.factor_id || 'phone'
228
+ : factor.factor_id,
229
+ displayName: factor.display_name,
230
+ enrollmentTime,
231
+ phoneNumber: factor.phone_number,
232
+ });
233
+ }
234
+ if (parsedEnrolledFactors.length > 0) {
235
+ return {
236
+ enrolledFactors: parsedEnrolledFactors,
237
+ };
238
+ }
239
+ return null;
240
+ }
241
+ exports.parseMultiFactor = parseMultiFactor;
242
+ /**
243
+ * Parses the decoded user record into a valid UserRecord for use in the handler
244
+ * @internal
245
+ */
246
+ function parseAuthUserRecord(decodedJWTUserRecord) {
247
+ if (!decodedJWTUserRecord.uid) {
248
+ throw new https_1.HttpsError('internal', 'INTERNAL ASSERT FAILED: Invalid user response');
249
+ }
250
+ const disabled = decodedJWTUserRecord.disabled || false;
251
+ const metadata = parseMetadata(decodedJWTUserRecord.metadata);
252
+ const providerData = parseProviderData(decodedJWTUserRecord.provider_data);
253
+ const tokensValidAfterTime = parseDate(decodedJWTUserRecord.tokens_valid_after_time);
254
+ const multiFactor = parseMultiFactor(decodedJWTUserRecord.multi_factor);
255
+ return {
256
+ uid: decodedJWTUserRecord.uid,
257
+ email: decodedJWTUserRecord.email,
258
+ emailVerified: decodedJWTUserRecord.email_verified,
259
+ displayName: decodedJWTUserRecord.display_name,
260
+ photoURL: decodedJWTUserRecord.photo_url,
261
+ phoneNumber: decodedJWTUserRecord.phone_number,
262
+ disabled,
263
+ metadata,
264
+ providerData,
265
+ passwordHash: decodedJWTUserRecord.password_hash,
266
+ passwordSalt: decodedJWTUserRecord.password_salt,
267
+ customClaims: decodedJWTUserRecord.custom_claims,
268
+ tenantId: decodedJWTUserRecord.tenant_id,
269
+ tokensValidAfterTime,
270
+ multiFactor,
271
+ };
272
+ }
273
+ exports.parseAuthUserRecord = parseAuthUserRecord;
274
+ /** Helper to get the AdditionalUserInfo from the decoded jwt */
275
+ function parseAdditionalUserInfo(decodedJWT) {
276
+ let profile, username;
277
+ if (decodedJWT.raw_user_info) {
278
+ try {
279
+ profile = JSON.parse(decodedJWT.raw_user_info);
280
+ }
281
+ catch (err) {
282
+ __1.logger.debug(`Parse Error: ${err.message}`);
283
+ }
284
+ }
285
+ if (profile) {
286
+ if (decodedJWT.sign_in_method === 'github.com') {
287
+ username = profile.login;
288
+ }
289
+ if (decodedJWT.sign_in_method === 'twitter.com') {
290
+ username = profile.screen_name;
291
+ }
292
+ }
293
+ return {
294
+ providerId: decodedJWT.sign_in_method === 'emailLink'
295
+ ? 'password'
296
+ : decodedJWT.sign_in_method,
297
+ profile,
298
+ username,
299
+ isNewUser: decodedJWT.event_type === 'beforeCreate' ? true : false,
300
+ };
301
+ }
302
+ /** Helper to get the Credential from the decoded jwt */
303
+ function parseAuthCredential(decodedJWT, time) {
304
+ if (!decodedJWT.sign_in_attributes &&
305
+ !decodedJWT.oauth_id_token &&
306
+ !decodedJWT.oauth_access_token &&
307
+ !decodedJWT.oauth_refresh_token) {
308
+ return null;
309
+ }
310
+ return {
311
+ claims: decodedJWT.sign_in_attributes,
312
+ idToken: decodedJWT.oauth_id_token,
313
+ accessToken: decodedJWT.oauth_access_token,
314
+ refreshToken: decodedJWT.oauth_refresh_token,
315
+ expirationTime: decodedJWT.oauth_expires_in
316
+ ? new Date(time + decodedJWT.oauth_expires_in * 1000).toUTCString()
317
+ : undefined,
318
+ secret: decodedJWT.oauth_token_secret,
319
+ providerId: decodedJWT.sign_in_method === 'emailLink'
320
+ ? 'password'
321
+ : decodedJWT.sign_in_method,
322
+ signInMethod: decodedJWT.sign_in_method,
323
+ };
324
+ }
325
+ /**
326
+ * Parses the decoded jwt into a valid AuthEventContext for use in the handler
327
+ * @internal
328
+ */
329
+ function parseAuthEventContext(decodedJWT, projectId, time = new Date().getTime()) {
330
+ const eventType = (EVENT_MAPPING[decodedJWT.event_type] || decodedJWT.event_type) +
331
+ (decodedJWT.sign_in_method ? `:${decodedJWT.sign_in_method}` : '');
332
+ return {
333
+ locale: decodedJWT.locale,
334
+ ipAddress: decodedJWT.ip_address,
335
+ userAgent: decodedJWT.user_agent,
336
+ eventId: decodedJWT.event_id,
337
+ eventType,
338
+ authType: !!decodedJWT.user_record ? 'USER' : 'UNAUTHENTICATED',
339
+ resource: {
340
+ // TODO(colerogers): figure out the correct service
341
+ service: 'identitytoolkit.googleapis.com',
342
+ name: !!decodedJWT.tenant_id
343
+ ? `projects/${projectId}/tenants/${decodedJWT.tenant_id}`
344
+ : `projects/${projectId}`,
345
+ },
346
+ timestamp: new Date(decodedJWT.iat * 1000).toUTCString(),
347
+ additionalUserInfo: parseAdditionalUserInfo(decodedJWT),
348
+ credential: parseAuthCredential(decodedJWT, time),
349
+ params: {},
350
+ };
351
+ }
352
+ exports.parseAuthEventContext = parseAuthEventContext;
353
+ /**
354
+ * Checks the handler response for invalid customClaims & sessionClaims objects
355
+ * @internal
356
+ */
357
+ function validateAuthResponse(eventType, authRequest) {
358
+ if (!authRequest) {
359
+ authRequest = {};
360
+ }
361
+ if (authRequest.customClaims) {
362
+ const invalidClaims = DISALLOWED_CUSTOM_CLAIMS.filter((claim) => authRequest.customClaims.hasOwnProperty(claim));
363
+ if (invalidClaims.length > 0) {
364
+ throw new https_1.HttpsError('invalid-argument', `The customClaims claims "${invalidClaims.join(',')}" are reserved and cannot be specified.`);
365
+ }
366
+ if (JSON.stringify(authRequest.customClaims).length > CLAIMS_MAX_PAYLOAD_SIZE) {
367
+ throw new https_1.HttpsError('invalid-argument', `The customClaims payload should not exceed ${CLAIMS_MAX_PAYLOAD_SIZE} characters.`);
368
+ }
369
+ }
370
+ if (eventType === 'beforeSignIn' &&
371
+ authRequest.sessionClaims) {
372
+ const invalidClaims = DISALLOWED_CUSTOM_CLAIMS.filter((claim) => authRequest.sessionClaims.hasOwnProperty(claim));
373
+ if (invalidClaims.length > 0) {
374
+ throw new https_1.HttpsError('invalid-argument', `The sessionClaims claims "${invalidClaims.join(',')}" are reserved and cannot be specified.`);
375
+ }
376
+ if (JSON.stringify(authRequest.sessionClaims)
377
+ .length > CLAIMS_MAX_PAYLOAD_SIZE) {
378
+ throw new https_1.HttpsError('invalid-argument', `The sessionClaims payload should not exceed ${CLAIMS_MAX_PAYLOAD_SIZE} characters.`);
379
+ }
380
+ const combinedClaims = {
381
+ ...authRequest.customClaims,
382
+ ...authRequest.sessionClaims,
383
+ };
384
+ if (JSON.stringify(combinedClaims).length > CLAIMS_MAX_PAYLOAD_SIZE) {
385
+ throw new https_1.HttpsError('invalid-argument', `The customClaims and sessionClaims payloads should not exceed ${CLAIMS_MAX_PAYLOAD_SIZE} characters combined.`);
386
+ }
387
+ }
388
+ }
389
+ exports.validateAuthResponse = validateAuthResponse;
390
+ /**
391
+ * Helper function to generate the update mask for the identity platform changed values
392
+ * @internal
393
+ */
394
+ function getUpdateMask(authResponse) {
395
+ if (!authResponse) {
396
+ return '';
397
+ }
398
+ const updateMask = [];
399
+ for (const key in authResponse) {
400
+ if (key === 'customClaims' || key === 'sessionClaims') {
401
+ continue;
402
+ }
403
+ if (authResponse.hasOwnProperty(key) &&
404
+ typeof authResponse[key] !== 'undefined') {
405
+ updateMask.push(key);
406
+ }
407
+ }
408
+ return updateMask.join(',');
409
+ }
410
+ exports.getUpdateMask = getUpdateMask;
411
+ /** @internal */
412
+ function wrapHandler(eventType, handler) {
413
+ return async (req, res) => {
414
+ try {
415
+ const projectId = process.env.GCLOUD_PROJECT;
416
+ if (!isValidRequest(req)) {
417
+ __1.logger.error('Invalid request, unable to process');
418
+ throw new https_1.HttpsError('invalid-argument', 'Bad Request');
419
+ }
420
+ if (!(0, apps_1.apps)().admin.auth()._verifyAuthBlockingToken) {
421
+ throw new Error('Cannot validate Auth Blocking token. Please update Firebase Admin SDK to >= v10.1.0');
422
+ }
423
+ const decodedPayload = (0, debug_1.isDebugFeatureEnabled)('skipTokenVerification')
424
+ ? unsafeDecodeAuthBlockingToken(req.body.data.jwt)
425
+ : await (0, apps_1.apps)()
426
+ .admin.auth()
427
+ ._verifyAuthBlockingToken(req.body.data.jwt);
428
+ const authUserRecord = parseAuthUserRecord(decodedPayload.user_record);
429
+ const authEventContext = parseAuthEventContext(decodedPayload, projectId);
430
+ let authResponse;
431
+ if (handler.length === 2) {
432
+ authResponse =
433
+ (await handler(authUserRecord, authEventContext)) ||
434
+ undefined;
435
+ }
436
+ else {
437
+ authResponse =
438
+ (await handler({
439
+ ...authEventContext,
440
+ data: authUserRecord,
441
+ })) || undefined;
442
+ }
443
+ validateAuthResponse(eventType, authResponse);
444
+ const updateMask = getUpdateMask(authResponse);
445
+ const result = updateMask.length === 0
446
+ ? {}
447
+ : {
448
+ userRecord: {
449
+ ...authResponse,
450
+ updateMask,
451
+ },
452
+ };
453
+ res.status(200);
454
+ res.setHeader('Content-Type', 'application/json');
455
+ res.send(JSON.stringify(result));
456
+ }
457
+ catch (err) {
458
+ if (!(err instanceof https_1.HttpsError)) {
459
+ // This doesn't count as an 'explicit' error.
460
+ __1.logger.error('Unhandled error', err);
461
+ err = new https_1.HttpsError('internal', 'An unexpected error occurred.');
462
+ }
463
+ const { status } = err.httpErrorCode;
464
+ const body = { error: err.toJSON() };
465
+ res.setHeader('Content-Type', 'application/json');
466
+ res.status(status).send(body);
467
+ }
468
+ };
469
+ }
470
+ exports.wrapHandler = wrapHandler;
@@ -0,0 +1,58 @@
1
+ import * as firebase from 'firebase-admin';
2
+ /** How a task should be retried in the event of a non-2xx return. */
3
+ export interface RetryConfig {
4
+ /**
5
+ * Maximum number of times a request should be attempted.
6
+ * If left unspecified, will default to 3.
7
+ */
8
+ maxAttempts?: number;
9
+ /**
10
+ * Maximum amount of time for retrying failed task.
11
+ * If left unspecified will retry indefinitely.
12
+ */
13
+ maxRetrySeconds?: number;
14
+ /**
15
+ * The maximum amount of time to wait between attempts.
16
+ * If left unspecified will default to 1hr.
17
+ */
18
+ maxBackoffSeconds?: number;
19
+ /**
20
+ * The maximum number of times to double the backoff between
21
+ * retries. If left unspecified will default to 16.
22
+ */
23
+ maxDoublings?: number;
24
+ /**
25
+ * The minimum time to wait between attempts. If left unspecified
26
+ * will default to 100ms.
27
+ */
28
+ minBackoffSeconds?: number;
29
+ }
30
+ /** How congestion control should be applied to the function. */
31
+ export interface RateLimits {
32
+ maxConcurrentDispatches?: number;
33
+ maxDispatchesPerSecond?: number;
34
+ }
35
+ export interface AuthData {
36
+ uid: string;
37
+ token: firebase.auth.DecodedIdToken;
38
+ }
39
+ /** Metadata about a call to a Task Queue function. */
40
+ export interface TaskContext {
41
+ /**
42
+ * The result of decoding and verifying an ODIC token.
43
+ */
44
+ auth?: AuthData;
45
+ }
46
+ /**
47
+ * The request used to call a Task Queue function.
48
+ */
49
+ export interface Request<T = any> {
50
+ /**
51
+ * The parameters used by a client when calling this function.
52
+ */
53
+ data: T;
54
+ /**
55
+ * The result of decoding and verifying an ODIC token.
56
+ */
57
+ auth?: AuthData;
58
+ }
@@ -0,0 +1,77 @@
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.onDispatchHandler = void 0;
25
+ const logger = require("../../logger");
26
+ const https = require("./https");
27
+ /** @internal */
28
+ function onDispatchHandler(handler) {
29
+ return async (req, res) => {
30
+ var _a;
31
+ try {
32
+ if (!https.isValidRequest(req)) {
33
+ logger.error('Invalid request, unable to process.');
34
+ throw new https.HttpsError('invalid-argument', 'Bad Request');
35
+ }
36
+ const authHeader = req.header('Authorization') || '';
37
+ const token = (_a = authHeader.match(/^Bearer (.*)$/)) === null || _a === void 0 ? void 0 : _a[1];
38
+ // Note: this should never happen since task queue functions are guarded by IAM.
39
+ if (!token) {
40
+ throw new https.HttpsError('unauthenticated', 'Unauthenticated');
41
+ }
42
+ // We skip authenticating the token since tq functions are guarded by IAM.
43
+ const authToken = await https.unsafeDecodeIdToken(token);
44
+ const context = {
45
+ auth: {
46
+ uid: authToken.uid,
47
+ token: authToken,
48
+ },
49
+ };
50
+ const data = https.decode(req.body.data);
51
+ if (handler.length === 2) {
52
+ await handler(data, context);
53
+ }
54
+ else {
55
+ const arg = {
56
+ ...context,
57
+ data,
58
+ };
59
+ // For some reason the type system isn't picking up that the handler
60
+ // is a one argument function.
61
+ await handler(arg);
62
+ }
63
+ res.status(204).end();
64
+ }
65
+ catch (err) {
66
+ if (!(err instanceof https.HttpsError)) {
67
+ // This doesn't count as an 'explicit' error.
68
+ logger.error('Unhandled error', err);
69
+ err = new https.HttpsError('internal', 'INTERNAL');
70
+ }
71
+ const { status } = err.httpErrorCode;
72
+ const body = { error: err.toJSON() };
73
+ res.status(status).send(body);
74
+ }
75
+ };
76
+ }
77
+ exports.onDispatchHandler = onDispatchHandler;
@@ -9,6 +9,7 @@ import * as https from './providers/https';
9
9
  import * as pubsub from './providers/pubsub';
10
10
  import * as remoteConfig from './providers/remoteConfig';
11
11
  import * as storage from './providers/storage';
12
+ import * as tasks from './providers/tasks';
12
13
  import * as testLab from './providers/testLab';
13
14
  /**
14
15
  * Configure the regions that the function is deployed to.
@@ -78,12 +79,14 @@ export declare class FunctionBuilder {
78
79
  * @param handler A method that takes a data and context and returns a value.
79
80
  */
80
81
  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>;
82
+ };
83
+ get tasks(): {
81
84
  /**
82
85
  * Declares a task queue function for clients to call using a Firebase Admin SDK.
83
86
  * @param options Configurations for the task queue function.
84
87
  */
85
88
  /** @hidden */
86
- taskQueue: (options?: https.TaskQueueOptions) => https.TaskQueueBuilder;
89
+ taskQueue: (options?: tasks.TaskQueueOptions) => tasks.TaskQueueBuilder;
87
90
  };
88
91
  get database(): {
89
92
  /**
@@ -175,7 +178,7 @@ export declare class FunctionBuilder {
175
178
  /**
176
179
  * Handle events related to Firebase authentication users.
177
180
  */
178
- user: () => auth.UserBuilder;
181
+ user: (userOptions?: auth.UserOptions) => auth.UserBuilder;
179
182
  };
180
183
  get testLab(): {
181
184
  /**
@@ -32,6 +32,7 @@ const https = require("./providers/https");
32
32
  const pubsub = require("./providers/pubsub");
33
33
  const remoteConfig = require("./providers/remoteConfig");
34
34
  const storage = require("./providers/storage");
35
+ const tasks = require("./providers/tasks");
35
36
  const testLab = require("./providers/testLab");
36
37
  /**
37
38
  * Assert that the runtime options passed in are valid.
@@ -238,13 +239,17 @@ class FunctionBuilder {
238
239
  * @param handler A method that takes a data and context and returns a value.
239
240
  */
240
241
  onCall: (handler) => https._onCallWithOptions(handler, this.options),
242
+ };
243
+ }
244
+ get tasks() {
245
+ return {
241
246
  /**
242
247
  * Declares a task queue function for clients to call using a Firebase Admin SDK.
243
248
  * @param options Configurations for the task queue function.
244
249
  */
245
250
  /** @hidden */
246
251
  taskQueue: (options) => {
247
- return new https.TaskQueueBuilder(options, this.options);
252
+ return new tasks.TaskQueueBuilder(options, this.options);
248
253
  },
249
254
  };
250
255
  }
@@ -351,7 +356,7 @@ class FunctionBuilder {
351
356
  /**
352
357
  * Handle events related to Firebase authentication users.
353
358
  */
354
- user: () => auth._userWithOptions(this.options),
359
+ user: (userOptions) => auth._userWithOptions(this.options, userOptions),
355
360
  };
356
361
  }
357
362
  get testLab() {
@@ -8,6 +8,7 @@ import * as https from './providers/https';
8
8
  import * as pubsub from './providers/pubsub';
9
9
  import * as remoteConfig from './providers/remoteConfig';
10
10
  import * as storage from './providers/storage';
11
+ import * as tasks from './providers/tasks';
11
12
  import * as testLab from './providers/testLab';
12
13
  /**
13
14
  * The `HandlerBuilder` class facilitates the writing of functions by developers
@@ -40,9 +41,19 @@ export declare class HandlerBuilder {
40
41
  get https(): {
41
42
  onRequest: (handler: (req: express.Request, resp: express.Response) => void) => HttpsFunction;
42
43
  onCall: (handler: (data: any, context: https.CallableContext) => any | Promise<any>) => HttpsFunction;
43
- /** @hidden */
44
+ };
45
+ /**
46
+ * Create a handler for tasks functions.
47
+ *
48
+ * @example
49
+ * ```javascript
50
+ * exports.myFunction = functions.handler.tasks.onDispatch((data, context) => { ... })
51
+ * ```
52
+ */
53
+ /** @hidden */
54
+ get tasks(): {
44
55
  readonly taskQueue: {
45
- onEnqueue(handler: (data: any, context: https.TaskContext) => void | Promise<void>): https.TaskQueueFunction;
56
+ onDispatch: (handler: (data: any, context: tasks.TaskContext) => void | Promise<void>) => HttpsFunction;
46
57
  };
47
58
  };
48
59
  /**