firebase-functions 3.20.1 → 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 (53) hide show
  1. package/lib/apps.js +1 -1
  2. package/lib/bin/firebase-functions.js +1 -1
  3. package/lib/cloud-functions.d.ts +8 -0
  4. package/lib/cloud-functions.js +12 -12
  5. package/lib/common/providers/https.js +8 -6
  6. package/lib/common/providers/identity.d.ts +7 -1
  7. package/lib/common/providers/identity.js +55 -207
  8. package/lib/function-builder.d.ts +1 -1
  9. package/lib/function-builder.js +1 -1
  10. package/lib/handler-builder.js +3 -3
  11. package/lib/index.js +6 -2
  12. package/lib/logger/compat.js +1 -1
  13. package/lib/providers/analytics.js +1 -1
  14. package/lib/providers/auth.d.ts +19 -6
  15. package/lib/providers/auth.js +62 -10
  16. package/lib/providers/database.js +11 -11
  17. package/lib/providers/firestore.js +7 -7
  18. package/lib/providers/https.js +7 -7
  19. package/lib/providers/pubsub.js +2 -2
  20. package/lib/providers/remoteConfig.js +1 -1
  21. package/lib/providers/storage.js +2 -2
  22. package/lib/providers/tasks.d.ts +1 -1
  23. package/lib/providers/tasks.js +9 -9
  24. package/lib/providers/testLab.js +1 -1
  25. package/lib/runtime/loader.js +9 -7
  26. package/lib/runtime/manifest.d.ts +5 -0
  27. package/lib/setup.js +3 -3
  28. package/lib/v2/core.d.ts +3 -21
  29. package/lib/v2/index.d.ts +3 -2
  30. package/lib/v2/index.js +5 -3
  31. package/lib/v2/options.d.ts +13 -2
  32. package/lib/v2/options.js +31 -39
  33. package/lib/v2/providers/alerts/alerts.d.ts +15 -7
  34. package/lib/v2/providers/alerts/appDistribution.d.ts +11 -11
  35. package/lib/v2/providers/alerts/appDistribution.js +1 -1
  36. package/lib/v2/providers/alerts/billing.d.ts +17 -11
  37. package/lib/v2/providers/alerts/billing.js +1 -1
  38. package/lib/v2/providers/alerts/crashlytics.d.ts +62 -29
  39. package/lib/v2/providers/alerts/crashlytics.js +1 -1
  40. package/lib/v2/providers/alerts/index.js +5 -1
  41. package/lib/v2/providers/eventarc.d.ts +1 -1
  42. package/lib/v2/providers/eventarc.js +3 -3
  43. package/lib/v2/providers/https.d.ts +6 -3
  44. package/lib/v2/providers/https.js +5 -11
  45. package/lib/v2/providers/identity.d.ts +22 -0
  46. package/lib/v2/providers/identity.js +73 -0
  47. package/lib/v2/providers/pubsub.d.ts +3 -3
  48. package/lib/v2/providers/pubsub.js +2 -5
  49. package/lib/v2/providers/storage.d.ts +17 -13
  50. package/lib/v2/providers/storage.js +5 -4
  51. package/lib/v2/providers/tasks.d.ts +1 -1
  52. package/lib/v2/providers/tasks.js +7 -8
  53. package/package.json +16 -3
package/lib/apps.js CHANGED
@@ -103,7 +103,7 @@ exports.apps = apps;
103
103
  this._emulatedAdminApp = app;
104
104
  }
105
105
  get firebaseArgs() {
106
- return _.assign({}, config_1.firebaseConfig(), {
106
+ return _.assign({}, (0, config_1.firebaseConfig)(), {
107
107
  credential: firebase.credential.applicationDefault(),
108
108
  });
109
109
  }
@@ -31,7 +31,7 @@ app.post('/__/quitquitquit', handleQuitquitquit);
31
31
  if (process.env.FUNCTIONS_CONTROL_API === 'true') {
32
32
  app.get('/__/functions.yaml', async (req, res) => {
33
33
  try {
34
- const stack = await loader_1.loadStack(functionsDir);
34
+ const stack = await (0, loader_1.loadStack)(functionsDir);
35
35
  res.setHeader('content-type', 'text/yaml');
36
36
  res.send(JSON.stringify(stack));
37
37
  }
@@ -180,6 +180,10 @@ export interface Resource {
180
180
  export interface TriggerAnnotated {
181
181
  __trigger: {
182
182
  availableMemoryMb?: number;
183
+ blockingTrigger?: {
184
+ eventType: string;
185
+ options?: Record<string, unknown>;
186
+ };
183
187
  eventTrigger?: {
184
188
  eventType: string;
185
189
  resource: string;
@@ -227,6 +231,10 @@ export interface Runnable<T> {
227
231
  * arguments.
228
232
  */
229
233
  export declare type HttpsFunction = TriggerAnnotated & EndpointAnnotated & ((req: Request, resp: Response) => void | Promise<void>);
234
+ /**
235
+ * The Cloud Function type for Blocking triggers.
236
+ */
237
+ export declare type BlockingFunction = HttpsFunction;
230
238
  /**
231
239
  * The Cloud Function type for all non-HTTPS triggers. This should be exported
232
240
  * from your JavaScript file to define a Cloud Function.
@@ -135,7 +135,7 @@ function makeCloudFunction({ after = () => { }, before = () => { }, contextOnlyH
135
135
  promise = handler(dataOrChange, context);
136
136
  }
137
137
  if (typeof promise === 'undefined') {
138
- logger_1.warn('Function returned undefined, expected Promise or value');
138
+ (0, logger_1.warn)('Function returned undefined, expected Promise or value');
139
139
  }
140
140
  return Promise.resolve(promise)
141
141
  .then((result) => {
@@ -252,8 +252,8 @@ function _detectAuthType(event) {
252
252
  /** @hidden */
253
253
  function optionsToTrigger(options) {
254
254
  const trigger = {};
255
- encoding_1.copyIfPresent(trigger, options, 'regions', 'schedule', 'minInstances', 'maxInstances', 'ingressSettings', 'vpcConnectorEgressSettings', 'vpcConnector', 'labels', 'secrets');
256
- encoding_1.convertIfPresent(trigger, options, 'failurePolicy', 'failurePolicy', (policy) => {
255
+ (0, encoding_1.copyIfPresent)(trigger, options, 'regions', 'schedule', 'minInstances', 'maxInstances', 'ingressSettings', 'vpcConnectorEgressSettings', 'vpcConnector', 'labels', 'secrets');
256
+ (0, encoding_1.convertIfPresent)(trigger, options, 'failurePolicy', 'failurePolicy', (policy) => {
257
257
  if (policy === false) {
258
258
  return undefined;
259
259
  }
@@ -264,8 +264,8 @@ function optionsToTrigger(options) {
264
264
  return policy;
265
265
  }
266
266
  });
267
- encoding_1.convertIfPresent(trigger, options, 'timeout', 'timeoutSeconds', encoding_1.durationFromSeconds);
268
- encoding_1.convertIfPresent(trigger, options, 'availableMemoryMb', 'memory', (mem) => {
267
+ (0, encoding_1.convertIfPresent)(trigger, options, 'timeout', 'timeoutSeconds', encoding_1.durationFromSeconds);
268
+ (0, encoding_1.convertIfPresent)(trigger, options, 'availableMemoryMb', 'memory', (mem) => {
269
269
  const memoryLookup = {
270
270
  '128MB': 128,
271
271
  '256MB': 256,
@@ -277,21 +277,21 @@ function optionsToTrigger(options) {
277
277
  };
278
278
  return memoryLookup[mem];
279
279
  });
280
- encoding_1.convertIfPresent(trigger, options, 'serviceAccountEmail', 'serviceAccount', encoding_1.serviceAccountFromShorthand);
280
+ (0, encoding_1.convertIfPresent)(trigger, options, 'serviceAccountEmail', 'serviceAccount', encoding_1.serviceAccountFromShorthand);
281
281
  return trigger;
282
282
  }
283
283
  exports.optionsToTrigger = optionsToTrigger;
284
284
  function optionsToEndpoint(options) {
285
285
  const endpoint = {};
286
- encoding_1.copyIfPresent(endpoint, options, 'minInstances', 'maxInstances', 'ingressSettings', 'labels', 'timeoutSeconds');
287
- encoding_1.convertIfPresent(endpoint, options, 'region', 'regions');
288
- encoding_1.convertIfPresent(endpoint, options, 'serviceAccountEmail', 'serviceAccount', (sa) => sa);
289
- encoding_1.convertIfPresent(endpoint, options, 'secretEnvironmentVariables', 'secrets', (secrets) => secrets.map((secret) => ({ secret, key: secret })));
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) => ({ key: secret })));
290
290
  if (options === null || options === void 0 ? void 0 : options.vpcConnector) {
291
291
  endpoint.vpc = { connector: options.vpcConnector };
292
- encoding_1.convertIfPresent(endpoint.vpc, options, 'egressSettings', 'vpcConnectorEgressSettings');
292
+ (0, encoding_1.convertIfPresent)(endpoint.vpc, options, 'egressSettings', 'vpcConnectorEgressSettings');
293
293
  }
294
- encoding_1.convertIfPresent(endpoint, options, 'availableMemoryMb', 'memory', (mem) => {
294
+ (0, encoding_1.convertIfPresent)(endpoint, options, 'availableMemoryMb', 'memory', (mem) => {
295
295
  const memoryLookup = {
296
296
  '128MB': 128,
297
297
  '256MB': 256,
@@ -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.checkAuthToken = exports.unsafeDecodeAppCheckToken = exports.unsafeDecodeIdToken = exports.decode = exports.encode = exports.isValidRequest = exports.HttpsError = void 0;
24
+ exports.onCallHandler = exports.checkAuthToken = exports.unsafeDecodeAppCheckToken = exports.unsafeDecodeIdToken = exports.unsafeDecodeToken = exports.decode = exports.encode = exports.isValidRequest = 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
@@ -209,6 +209,7 @@ function decode(data) {
209
209
  return data;
210
210
  }
211
211
  exports.decode = decode;
212
+ /** @internal */
212
213
  function unsafeDecodeToken(token) {
213
214
  if (!JWT_REGEX.test(token)) {
214
215
  return {};
@@ -228,6 +229,7 @@ function unsafeDecodeToken(token) {
228
229
  }
229
230
  return payload;
230
231
  }
232
+ exports.unsafeDecodeToken = unsafeDecodeToken;
231
233
  /**
232
234
  * Decode, but not verify, a Auth ID token.
233
235
  *
@@ -310,11 +312,11 @@ async function checkAuthToken(req, ctx) {
310
312
  const idToken = match[1];
311
313
  try {
312
314
  let authToken;
313
- if (debug_1.isDebugFeatureEnabled('skipTokenVerification')) {
315
+ if ((0, debug_1.isDebugFeatureEnabled)('skipTokenVerification')) {
314
316
  authToken = unsafeDecodeIdToken(idToken);
315
317
  }
316
318
  else {
317
- authToken = await apps_1.apps()
319
+ authToken = await (0, apps_1.apps)()
318
320
  .admin.auth()
319
321
  .verifyIdToken(idToken);
320
322
  }
@@ -338,16 +340,16 @@ async function checkAppCheckToken(req, ctx) {
338
340
  return 'MISSING';
339
341
  }
340
342
  try {
341
- if (!apps_1.apps().admin.appCheck) {
343
+ if (!(0, apps_1.apps)().admin.appCheck) {
342
344
  throw new Error('Cannot validate AppCheck token. Please update Firebase Admin SDK to >= v9.8.0');
343
345
  }
344
346
  let appCheckData;
345
- if (debug_1.isDebugFeatureEnabled('skipTokenVerification')) {
347
+ if ((0, debug_1.isDebugFeatureEnabled)('skipTokenVerification')) {
346
348
  const decodedToken = unsafeDecodeAppCheckToken(appCheck);
347
349
  appCheckData = { appId: decodedToken.app_id, token: decodedToken };
348
350
  }
349
351
  else {
350
- appCheckData = await apps_1.apps()
352
+ appCheckData = await (0, apps_1.apps)()
351
353
  .admin.appCheck()
352
354
  .verifyToken(appCheck);
353
355
  }
@@ -1,7 +1,9 @@
1
1
  import * as firebase from 'firebase-admin';
2
- import { HttpsError } from './https';
3
2
  import { EventContext } from '../../cloud-functions';
3
+ import { HttpsError } from './https';
4
4
  export { HttpsError };
5
+ /** Shorthand auth blocking events from GCIP. */
6
+ export declare type AuthBlockingEventType = 'beforeCreate' | 'beforeSignIn';
5
7
  /**
6
8
  * The UserRecord passed to Cloud Functions is the same UserRecord that is returned by the Firebase Admin
7
9
  * SDK.
@@ -198,6 +200,10 @@ export interface AuthEventContext extends EventContext {
198
200
  additionalUserInfo?: AdditionalUserInfo;
199
201
  credential?: Credential;
200
202
  }
203
+ /** Defines the auth event for v2 blocking events */
204
+ export interface AuthBlockingEvent extends AuthEventContext {
205
+ data: AuthUserRecord;
206
+ }
201
207
  /** The handler response type for beforeCreate blocking events */
202
208
  export interface BeforeCreateResponse {
203
209
  displayName?: string;
@@ -21,23 +21,12 @@
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.createHandler = exports.getUpdateMask = exports.validateAuthResponse = exports.parseAuthEventContext = exports.parseAuthUserRecord = exports.parseMultiFactor = exports.parseDate = exports.parseProviderData = exports.parseMetadata = exports.verifyJWT = exports.shouldVerifyJWT = exports.decodeJWT = exports.checkDecodedToken = exports.isAuthorizedCloudFunctionURL = exports.getPublicKeyFromHeader = exports.isValidRequest = exports.setKeyExpirationTime = exports.invalidPublicKeys = exports.userRecordConstructor = exports.UserRecordMetadata = exports.JWT_ISSUER = exports.JWT_ALG = exports.JWT_CLIENT_CERT_PATH = exports.JWT_CLIENT_CERT_URL = exports.INVALID_TOKEN_BUFFER = exports.HttpsError = void 0;
25
- const jwt = require("jsonwebtoken");
26
- const node_fetch_1 = require("node-fetch");
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");
27
28
  const https_1 = require("./https");
28
29
  Object.defineProperty(exports, "HttpsError", { enumerable: true, get: function () { return https_1.HttpsError; } });
29
- const function_configuration_1 = require("../../function-configuration");
30
- const __1 = require("../..");
31
- /** @internal */
32
- exports.INVALID_TOKEN_BUFFER = 60000; // set to 1 minute
33
- /** @internal */
34
- exports.JWT_CLIENT_CERT_URL = 'https://www.googleapis.com';
35
- /** @internal */
36
- exports.JWT_CLIENT_CERT_PATH = 'robot/v1/metadata/x509/securetoken@system.gserviceaccount.com';
37
- /** @internal */
38
- exports.JWT_ALG = 'RS256';
39
- /** @internal */
40
- exports.JWT_ISSUER = 'https://securetoken.google.com/';
41
30
  const DISALLOWED_CUSTOM_CLAIMS = [
42
31
  'acr',
43
32
  'amr',
@@ -98,19 +87,14 @@ function userRecordConstructor(wireData) {
98
87
  tokensValidAfterTime: null,
99
88
  };
100
89
  const record = { ...falseyValues, ...wireData };
101
- const meta = record['metadata'];
90
+ const meta = record.metadata;
102
91
  if (meta) {
103
- 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);
104
93
  }
105
94
  else {
106
- record['metadata'] = new UserRecordMetadata(null, null);
95
+ record.metadata = new UserRecordMetadata(null, null);
107
96
  }
108
- for (const entry of Object.entries(record.providerData)) {
109
- entry['toJSON'] = () => {
110
- return entry;
111
- };
112
- }
113
- record['toJSON'] = () => {
97
+ record.toJSON = () => {
114
98
  const { uid, email, emailVerified, displayName, photoURL, phoneNumber, disabled, passwordHash, passwordSalt, tokensValidAfterTime, } = record;
115
99
  const json = {
116
100
  uid,
@@ -124,58 +108,14 @@ function userRecordConstructor(wireData) {
124
108
  passwordSalt,
125
109
  tokensValidAfterTime,
126
110
  };
127
- json['metadata'] = record['metadata'].toJSON();
128
- json['customClaims'] = JSON.parse(JSON.stringify(record.customClaims));
129
- json['providerData'] = record.providerData.map((entry) => entry.toJSON());
111
+ json.metadata = record.metadata.toJSON();
112
+ json.customClaims = JSON.parse(JSON.stringify(record.customClaims));
113
+ json.providerData = record.providerData.map((entry) => entry.toJSON());
130
114
  return json;
131
115
  };
132
116
  return record;
133
117
  }
134
118
  exports.userRecordConstructor = userRecordConstructor;
135
- /**
136
- * Helper to determine if we refresh the public keys
137
- * @internal
138
- */
139
- function invalidPublicKeys(keys, time = Date.now()) {
140
- if (!keys.publicKeysExpireAt) {
141
- return true;
142
- }
143
- return time + exports.INVALID_TOKEN_BUFFER >= keys.publicKeysExpireAt;
144
- }
145
- exports.invalidPublicKeys = invalidPublicKeys;
146
- /**
147
- * Helper to parse the response headers to obtain the expiration time.
148
- * @internal
149
- */
150
- function setKeyExpirationTime(response, keysCache, time) {
151
- if (response.headers.has('cache-control')) {
152
- const ccHeader = response.headers.get('cache-control');
153
- const maxAgeEntry = ccHeader
154
- .split(', ')
155
- .find((item) => item.includes('max-age'));
156
- if (maxAgeEntry) {
157
- const maxAge = +maxAgeEntry.trim().split('=')[1];
158
- keysCache.publicKeysExpireAt = time + maxAge * 1000;
159
- }
160
- }
161
- }
162
- exports.setKeyExpirationTime = setKeyExpirationTime;
163
- /**
164
- * Fetch the public keys for use in decoding and verifying the jwt sent from identity platform.
165
- */
166
- async function refreshPublicKeys(keysCache, time = Date.now()) {
167
- const url = `${exports.JWT_CLIENT_CERT_URL}/${exports.JWT_CLIENT_CERT_PATH}`;
168
- try {
169
- const response = await node_fetch_1.default(url);
170
- setKeyExpirationTime(response, keysCache, time);
171
- const data = await response.json();
172
- keysCache.publicKeys = data;
173
- }
174
- catch (err) {
175
- __1.logger.error(`Failed to obtain public keys for JWT verification: ${err.message}`);
176
- throw new https_1.HttpsError('internal', 'Failed to obtain the public keys for JWT verification.');
177
- }
178
- }
179
119
  /**
180
120
  * Checks for a valid identity platform web request, otherwise throws an HttpsError
181
121
  * @internal
@@ -198,120 +138,18 @@ function isValidRequest(req) {
198
138
  return true;
199
139
  }
200
140
  exports.isValidRequest = isValidRequest;
201
- /** @internal */
202
- function getPublicKeyFromHeader(header, publicKeys) {
203
- if (header.alg !== exports.JWT_ALG) {
204
- throw new https_1.HttpsError('invalid-argument', `Provided JWT has incorrect algorithm. Expected ${exports.JWT_ALG} but got ${header.alg}.`);
205
- }
206
- if (!header.kid) {
207
- throw new https_1.HttpsError('invalid-argument', 'JWT header missing "kid" claim.');
208
- }
209
- if (!publicKeys.hasOwnProperty(header.kid)) {
210
- throw new https_1.HttpsError('invalid-argument', 'Provided JWT has "kid" claim which does not correspond to a known public key. Most likely the JWT is expired.');
211
- }
212
- return publicKeys[header.kid];
213
- }
214
- exports.getPublicKeyFromHeader = getPublicKeyFromHeader;
215
141
  /**
216
- * Checks for a well forms cloud functions url
217
- * @internal
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.
218
147
  */
219
- function isAuthorizedCloudFunctionURL(cloudFunctionUrl, projectId) {
220
- const re = new RegExp(`^https://(${function_configuration_1.SUPPORTED_REGIONS.join('|')})+-${projectId}\.cloudfunctions\.net/`);
221
- const res = re.exec(cloudFunctionUrl) || [];
222
- return res.length > 0;
223
- }
224
- exports.isAuthorizedCloudFunctionURL = isAuthorizedCloudFunctionURL;
225
- /**
226
- * Checks for errors in a decoded jwt
227
- * @internal
228
- */
229
- function checkDecodedToken(decodedJWT, eventType, projectId) {
230
- if (decodedJWT.event_type !== eventType) {
231
- throw new https_1.HttpsError('invalid-argument', `Expected "${eventType}" but received "${decodedJWT.event_type}".`);
232
- }
233
- if (!isAuthorizedCloudFunctionURL(decodedJWT.aud, projectId)) {
234
- throw new https_1.HttpsError('invalid-argument', 'Provided JWT has incorrect "aud" (audience) claim.');
235
- }
236
- if (decodedJWT.iss !== `${exports.JWT_ISSUER}${projectId}`) {
237
- throw new https_1.HttpsError('invalid-argument', `Provided JWT has incorrect "iss" (issuer) claim. Expected ` +
238
- `"${exports.JWT_ISSUER}${projectId}" but got "${decodedJWT.iss}".`);
239
- }
240
- if (typeof decodedJWT.sub !== 'string' || decodedJWT.sub.length === 0) {
241
- throw new https_1.HttpsError('invalid-argument', 'Provided JWT has no "sub" (subject) claim.');
242
- }
243
- if (decodedJWT.sub.length > 128) {
244
- throw new https_1.HttpsError('invalid-argument', 'Provided JWT has "sub" (subject) claim longer than 128 characters.');
245
- }
246
- // set uid to sub
247
- decodedJWT.uid = decodedJWT.sub;
248
- }
249
- exports.checkDecodedToken = checkDecodedToken;
250
- /**
251
- * Helper function to decode the jwt, internally uses the 'jsonwebtoken' package.
252
- * @internal
253
- */
254
- function decodeJWT(token) {
255
- let decoded;
256
- try {
257
- decoded = jwt.decode(token, { complete: true });
258
- }
259
- catch (err) {
260
- __1.logger.error('Decoding the JWT failed', err);
261
- throw new https_1.HttpsError('internal', 'Failed to decode the JWT.');
262
- }
263
- if (!(decoded === null || decoded === void 0 ? void 0 : decoded.payload)) {
264
- throw new https_1.HttpsError('internal', 'The decoded JWT is not structured correctly.');
265
- }
148
+ function unsafeDecodeAuthBlockingToken(token) {
149
+ const decoded = (0, https_1.unsafeDecodeToken)(token);
150
+ decoded.uid = decoded.sub;
266
151
  return decoded;
267
152
  }
268
- exports.decodeJWT = decodeJWT;
269
- /**
270
- * Helper function to determine if we need to do full verification of the jwt
271
- * @internal
272
- */
273
- function shouldVerifyJWT() {
274
- // TODO(colerogers): add emulator support to skip verification
275
- return true;
276
- }
277
- exports.shouldVerifyJWT = shouldVerifyJWT;
278
- /**
279
- * Verifies the jwt using the 'jwt' library and decodes the token with the public keys
280
- * Throws an error if the event types do not match
281
- * @internal
282
- */
283
- function verifyJWT(token, rawDecodedJWT, keysCache, time = Date.now()) {
284
- if (!rawDecodedJWT.header) {
285
- throw new https_1.HttpsError('internal', 'Unable to verify JWT payload, the decoded JWT does not have a header property.');
286
- }
287
- const header = rawDecodedJWT.header;
288
- let publicKey;
289
- try {
290
- if (invalidPublicKeys(keysCache, time)) {
291
- refreshPublicKeys(keysCache);
292
- }
293
- publicKey = getPublicKeyFromHeader(header, keysCache.publicKeys);
294
- return jwt.verify(token, publicKey, {
295
- algorithms: [exports.JWT_ALG],
296
- });
297
- }
298
- catch (err) {
299
- __1.logger.error('Verifying the JWT failed', err);
300
- }
301
- // force refresh keys and retry one more time
302
- try {
303
- refreshPublicKeys(keysCache);
304
- publicKey = getPublicKeyFromHeader(header, keysCache.publicKeys);
305
- return jwt.verify(token, publicKey, {
306
- algorithms: [exports.JWT_ALG],
307
- });
308
- }
309
- catch (err) {
310
- __1.logger.error('Verifying the JWT failed again', err);
311
- throw new https_1.HttpsError('internal', 'Failed to verify the JWT.');
312
- }
313
- }
314
- exports.verifyJWT = verifyJWT;
315
153
  /**
316
154
  * Helper function to parse the decoded metadata object into a UserMetaData object
317
155
  * @internal
@@ -436,13 +274,14 @@ exports.parseAuthUserRecord = parseAuthUserRecord;
436
274
  /** Helper to get the AdditionalUserInfo from the decoded jwt */
437
275
  function parseAdditionalUserInfo(decodedJWT) {
438
276
  let profile, username;
439
- if (decodedJWT.raw_user_info)
277
+ if (decodedJWT.raw_user_info) {
440
278
  try {
441
279
  profile = JSON.parse(decodedJWT.raw_user_info);
442
280
  }
443
281
  catch (err) {
444
282
  __1.logger.debug(`Parse Error: ${err.message}`);
445
283
  }
284
+ }
446
285
  if (profile) {
447
286
  if (decodedJWT.sign_in_method === 'github.com') {
448
287
  username = profile.login;
@@ -570,17 +409,7 @@ function getUpdateMask(authResponse) {
570
409
  }
571
410
  exports.getUpdateMask = getUpdateMask;
572
411
  /** @internal */
573
- function createHandler(handler, eventType, keysCache) {
574
- const wrappedHandler = wrapHandler(handler, eventType, keysCache);
575
- return (req, res) => {
576
- return new Promise((resolve) => {
577
- res.on('finish', resolve);
578
- resolve(wrappedHandler(req, res));
579
- });
580
- };
581
- }
582
- exports.createHandler = createHandler;
583
- function wrapHandler(handler, eventType, keysCache) {
412
+ function wrapHandler(eventType, handler) {
584
413
  return async (req, res) => {
585
414
  try {
586
415
  const projectId = process.env.GCLOUD_PROJECT;
@@ -588,22 +417,39 @@ function wrapHandler(handler, eventType, keysCache) {
588
417
  __1.logger.error('Invalid request, unable to process');
589
418
  throw new https_1.HttpsError('invalid-argument', 'Bad Request');
590
419
  }
591
- const rawDecodedJWT = decodeJWT(req.body.data.jwt);
592
- const decodedPayload = shouldVerifyJWT()
593
- ? verifyJWT(req.body.data.jwt, rawDecodedJWT, keysCache)
594
- : rawDecodedJWT.payload;
595
- checkDecodedToken(decodedPayload, eventType, projectId);
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);
596
428
  const authUserRecord = parseAuthUserRecord(decodedPayload.user_record);
597
429
  const authEventContext = parseAuthEventContext(decodedPayload, projectId);
598
- const authResponse = (await handler(authUserRecord, authEventContext)) || undefined;
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
+ }
599
443
  validateAuthResponse(eventType, authResponse);
600
444
  const updateMask = getUpdateMask(authResponse);
601
- const result = {
602
- userRecord: {
603
- ...authResponse,
604
- updateMask,
605
- },
606
- };
445
+ const result = updateMask.length === 0
446
+ ? {}
447
+ : {
448
+ userRecord: {
449
+ ...authResponse,
450
+ updateMask,
451
+ },
452
+ };
607
453
  res.status(200);
608
454
  res.setHeader('Content-Type', 'application/json');
609
455
  res.send(JSON.stringify(result));
@@ -614,9 +460,11 @@ function wrapHandler(handler, eventType, keysCache) {
614
460
  __1.logger.error('Unhandled error', err);
615
461
  err = new https_1.HttpsError('internal', 'An unexpected error occurred.');
616
462
  }
617
- res.status(err.code);
463
+ const { status } = err.httpErrorCode;
464
+ const body = { error: err.toJSON() };
618
465
  res.setHeader('Content-Type', 'application/json');
619
- res.send({ error: err.toJson() });
466
+ res.status(status).send(body);
620
467
  }
621
468
  };
622
469
  }
470
+ exports.wrapHandler = wrapHandler;
@@ -178,7 +178,7 @@ export declare class FunctionBuilder {
178
178
  /**
179
179
  * Handle events related to Firebase authentication users.
180
180
  */
181
- user: () => auth.UserBuilder;
181
+ user: (userOptions?: auth.UserOptions) => auth.UserBuilder;
182
182
  };
183
183
  get testLab(): {
184
184
  /**
@@ -356,7 +356,7 @@ class FunctionBuilder {
356
356
  /**
357
357
  * Handle events related to Firebase authentication users.
358
358
  */
359
- user: () => auth._userWithOptions(this.options),
359
+ user: (userOptions) => auth._userWithOptions(this.options, userOptions),
360
360
  };
361
361
  }
362
362
  get testLab() {
@@ -141,12 +141,12 @@ class HandlerBuilder {
141
141
  get instance() {
142
142
  return {
143
143
  get ref() {
144
- return new database.RefBuilder(apps_1.apps(), () => null, {});
144
+ return new database.RefBuilder((0, apps_1.apps)(), () => null, {});
145
145
  },
146
146
  };
147
147
  },
148
148
  get ref() {
149
- return new database.RefBuilder(apps_1.apps(), () => null, {});
149
+ return new database.RefBuilder((0, apps_1.apps)(), () => null, {});
150
150
  },
151
151
  };
152
152
  }
@@ -323,7 +323,7 @@ class HandlerBuilder {
323
323
  get auth() {
324
324
  return {
325
325
  get user() {
326
- return new auth.UserBuilder(() => null, {});
326
+ return new auth.UserBuilder(() => null, {}, {});
327
327
  },
328
328
  };
329
329
  }
package/lib/index.js CHANGED
@@ -22,7 +22,11 @@
22
22
  // SOFTWARE.
23
23
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
24
  if (k2 === undefined) k2 = k;
25
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
25
+ var desc = Object.getOwnPropertyDescriptor(m, k);
26
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
27
+ desc = { enumerable: true, get: function() { return m[k]; } };
28
+ }
29
+ Object.defineProperty(o, k2, desc);
26
30
  }) : (function(o, m, k, k2) {
27
31
  if (k2 === undefined) k2 = k;
28
32
  o[k2] = m[k];
@@ -66,4 +70,4 @@ __exportStar(require("./cloud-functions"), exports);
66
70
  __exportStar(require("./config"), exports);
67
71
  __exportStar(require("./function-builder"), exports);
68
72
  __exportStar(require("./function-configuration"), exports);
69
- setup_1.setup();
73
+ (0, setup_1.setup)();
@@ -6,7 +6,7 @@ const common_1 = require("./common");
6
6
  function patchedConsole(severity) {
7
7
  return function (data, ...args) {
8
8
  if (common_1.SUPPORTS_STRUCTURED_LOGS) {
9
- common_1.UNPATCHED_CONSOLE[common_1.CONSOLE_SEVERITY[severity]](JSON.stringify({ severity, message: util_1.format(data, ...args) }));
9
+ common_1.UNPATCHED_CONSOLE[common_1.CONSOLE_SEVERITY[severity]](JSON.stringify({ severity, message: (0, util_1.format)(data, ...args) }));
10
10
  return;
11
11
  }
12
12
  common_1.UNPATCHED_CONSOLE[common_1.CONSOLE_SEVERITY[severity]](data, ...args);
@@ -73,7 +73,7 @@ class AnalyticsEventBuilder {
73
73
  const dataConstructor = (raw) => {
74
74
  return new AnalyticsEvent(raw.data);
75
75
  };
76
- return cloud_functions_1.makeCloudFunction({
76
+ return (0, cloud_functions_1.makeCloudFunction)({
77
77
  handler,
78
78
  provider: exports.provider,
79
79
  eventType: 'event.log',
@@ -1,27 +1,40 @@
1
- import { UserRecord, UserInfo, UserRecordMetadata, userRecordConstructor } from '../common/providers/identity';
2
- import { CloudFunction, EventContext } from '../cloud-functions';
1
+ import { BlockingFunction, CloudFunction, EventContext } from '../cloud-functions';
2
+ import { AuthEventContext, AuthUserRecord, BeforeCreateResponse, BeforeSignInResponse, HttpsError, UserInfo, UserRecord, userRecordConstructor, UserRecordMetadata } from '../common/providers/identity';
3
3
  import { DeploymentOptions } from '../function-configuration';
4
4
  export { UserRecord, UserInfo, UserRecordMetadata, userRecordConstructor };
5
+ export { HttpsError };
5
6
  /** @hidden */
6
7
  export declare const provider = "google.firebase.auth";
7
8
  /** @hidden */
8
9
  export declare const service = "firebaseauth.googleapis.com";
10
+ /** Resource level options */
11
+ export interface UserOptions {
12
+ blockingOptions?: {
13
+ idToken?: boolean;
14
+ accessToken?: boolean;
15
+ refreshToken?: boolean;
16
+ };
17
+ }
9
18
  /**
10
19
  * Handle events related to Firebase authentication users.
11
20
  */
12
- export declare function user(): UserBuilder;
21
+ export declare function user(userOptions?: UserOptions): UserBuilder;
13
22
  /** @hidden */
14
- export declare function _userWithOptions(options: DeploymentOptions): UserBuilder;
23
+ export declare function _userWithOptions(options: DeploymentOptions, userOptions: UserOptions): UserBuilder;
15
24
  /** Builder used to create Cloud Functions for Firebase Auth user lifecycle events. */
16
25
  export declare class UserBuilder {
17
26
  private triggerResource;
18
- private options?;
27
+ private options;
28
+ private userOptions?;
19
29
  private static dataConstructor;
20
30
  /** @hidden */
21
- constructor(triggerResource: () => string, options?: DeploymentOptions);
31
+ constructor(triggerResource: () => string, options: DeploymentOptions, userOptions?: UserOptions);
22
32
  /** Respond to the creation of a Firebase Auth user. */
23
33
  onCreate(handler: (user: UserRecord, context: EventContext) => PromiseLike<any> | any): CloudFunction<UserRecord>;
24
34
  /** Respond to the deletion of a Firebase Auth user. */
25
35
  onDelete(handler: (user: UserRecord, context: EventContext) => PromiseLike<any> | any): CloudFunction<UserRecord>;
36
+ beforeCreate(handler: (user: AuthUserRecord, context: AuthEventContext) => BeforeCreateResponse | void | Promise<BeforeCreateResponse> | Promise<void>): BlockingFunction;
37
+ beforeSignIn(handler: (user: AuthUserRecord, context: AuthEventContext) => BeforeSignInResponse | void | Promise<BeforeSignInResponse> | Promise<void>): BlockingFunction;
26
38
  private onOperation;
39
+ private beforeOperation;
27
40
  }