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.
- package/lib/cloud-functions.d.ts +8 -0
- package/lib/cloud-functions.js +4 -7
- package/lib/common/providers/https.d.ts +20 -49
- package/lib/common/providers/https.js +7 -45
- package/lib/common/providers/identity.d.ts +193 -4
- package/lib/common/providers/identity.js +402 -28
- package/lib/common/providers/tasks.d.ts +58 -0
- package/lib/common/providers/tasks.js +77 -0
- package/lib/function-builder.d.ts +5 -2
- package/lib/function-builder.js +7 -2
- package/lib/handler-builder.d.ts +13 -2
- package/lib/handler-builder.js +17 -4
- package/lib/index.d.ts +2 -1
- package/lib/index.js +3 -1
- package/lib/providers/auth.d.ts +19 -6
- package/lib/providers/auth.js +60 -8
- package/lib/providers/https.d.ts +2 -44
- package/lib/providers/https.js +1 -49
- package/lib/providers/tasks.d.ts +46 -0
- package/lib/providers/tasks.js +75 -0
- package/lib/runtime/loader.js +9 -7
- package/lib/runtime/manifest.d.ts +8 -10
- package/lib/runtime/manifest.js +21 -0
- package/lib/v2/core.d.ts +3 -21
- package/lib/v2/index.d.ts +4 -1
- package/lib/v2/index.js +7 -1
- package/lib/v2/options.d.ts +13 -2
- package/lib/v2/options.js +22 -30
- package/lib/v2/providers/alerts/alerts.d.ts +15 -7
- package/lib/v2/providers/alerts/alerts.js +4 -10
- package/lib/v2/providers/alerts/appDistribution.d.ts +11 -11
- package/lib/v2/providers/alerts/billing.d.ts +17 -11
- package/lib/v2/providers/alerts/crashlytics.d.ts +62 -29
- package/lib/v2/providers/eventarc.d.ts +32 -0
- package/lib/v2/providers/eventarc.js +65 -0
- package/lib/v2/providers/https.d.ts +8 -23
- package/lib/v2/providers/https.js +3 -48
- package/lib/v2/providers/identity.d.ts +22 -0
- package/lib/v2/providers/identity.js +73 -0
- package/lib/v2/providers/pubsub.d.ts +3 -3
- package/lib/v2/providers/pubsub.js +2 -5
- package/lib/v2/providers/storage.d.ts +17 -13
- package/lib/v2/providers/storage.js +5 -4
- package/lib/v2/providers/tasks.d.ts +22 -0
- package/lib/v2/providers/tasks.js +88 -0
- 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
|
|
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 =
|
|
64
|
-
const meta =
|
|
89
|
+
const record = { ...falseyValues, ...wireData };
|
|
90
|
+
const meta = record.metadata;
|
|
65
91
|
if (meta) {
|
|
66
|
-
|
|
92
|
+
record.metadata = new UserRecordMetadata(meta.createdAt || meta.creationTime, meta.lastSignedInAt || meta.lastSignInTime);
|
|
67
93
|
}
|
|
68
94
|
else {
|
|
69
|
-
|
|
95
|
+
record.metadata = new UserRecordMetadata(null, null);
|
|
70
96
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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?:
|
|
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
|
/**
|
package/lib/function-builder.js
CHANGED
|
@@ -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
|
|
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() {
|
package/lib/handler-builder.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
56
|
+
onDispatch: (handler: (data: any, context: tasks.TaskContext) => void | Promise<void>) => HttpsFunction;
|
|
46
57
|
};
|
|
47
58
|
};
|
|
48
59
|
/**
|