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.
- package/lib/apps.js +1 -1
- package/lib/bin/firebase-functions.js +1 -1
- package/lib/cloud-functions.d.ts +8 -0
- package/lib/cloud-functions.js +12 -12
- package/lib/common/providers/https.js +8 -6
- package/lib/common/providers/identity.d.ts +7 -1
- package/lib/common/providers/identity.js +55 -207
- package/lib/function-builder.d.ts +1 -1
- package/lib/function-builder.js +1 -1
- package/lib/handler-builder.js +3 -3
- package/lib/index.js +6 -2
- package/lib/logger/compat.js +1 -1
- package/lib/providers/analytics.js +1 -1
- package/lib/providers/auth.d.ts +19 -6
- package/lib/providers/auth.js +62 -10
- package/lib/providers/database.js +11 -11
- package/lib/providers/firestore.js +7 -7
- package/lib/providers/https.js +7 -7
- package/lib/providers/pubsub.js +2 -2
- package/lib/providers/remoteConfig.js +1 -1
- package/lib/providers/storage.js +2 -2
- package/lib/providers/tasks.d.ts +1 -1
- package/lib/providers/tasks.js +9 -9
- package/lib/providers/testLab.js +1 -1
- package/lib/runtime/loader.js +9 -7
- package/lib/runtime/manifest.d.ts +5 -0
- package/lib/setup.js +3 -3
- package/lib/v2/core.d.ts +3 -21
- package/lib/v2/index.d.ts +3 -2
- package/lib/v2/index.js +5 -3
- package/lib/v2/options.d.ts +13 -2
- package/lib/v2/options.js +31 -39
- package/lib/v2/providers/alerts/alerts.d.ts +15 -7
- package/lib/v2/providers/alerts/appDistribution.d.ts +11 -11
- package/lib/v2/providers/alerts/appDistribution.js +1 -1
- package/lib/v2/providers/alerts/billing.d.ts +17 -11
- package/lib/v2/providers/alerts/billing.js +1 -1
- package/lib/v2/providers/alerts/crashlytics.d.ts +62 -29
- package/lib/v2/providers/alerts/crashlytics.js +1 -1
- package/lib/v2/providers/alerts/index.js +5 -1
- package/lib/v2/providers/eventarc.d.ts +1 -1
- package/lib/v2/providers/eventarc.js +3 -3
- package/lib/v2/providers/https.d.ts +6 -3
- package/lib/v2/providers/https.js +5 -11
- 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 +1 -1
- package/lib/v2/providers/tasks.js +7 -8
- 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
|
}
|
package/lib/cloud-functions.d.ts
CHANGED
|
@@ -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.
|
package/lib/cloud-functions.js
CHANGED
|
@@ -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) => ({
|
|
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.
|
|
25
|
-
const
|
|
26
|
-
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");
|
|
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
|
|
90
|
+
const meta = record.metadata;
|
|
102
91
|
if (meta) {
|
|
103
|
-
record
|
|
92
|
+
record.metadata = new UserRecordMetadata(meta.createdAt || meta.creationTime, meta.lastSignedInAt || meta.lastSignInTime);
|
|
104
93
|
}
|
|
105
94
|
else {
|
|
106
|
-
record
|
|
95
|
+
record.metadata = new UserRecordMetadata(null, null);
|
|
107
96
|
}
|
|
108
|
-
|
|
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
|
|
128
|
-
json
|
|
129
|
-
json
|
|
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
|
-
*
|
|
217
|
-
*
|
|
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
|
|
220
|
-
const
|
|
221
|
-
|
|
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
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
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
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
-
|
|
463
|
+
const { status } = err.httpErrorCode;
|
|
464
|
+
const body = { error: err.toJSON() };
|
|
618
465
|
res.setHeader('Content-Type', 'application/json');
|
|
619
|
-
res.
|
|
466
|
+
res.status(status).send(body);
|
|
620
467
|
}
|
|
621
468
|
};
|
|
622
469
|
}
|
|
470
|
+
exports.wrapHandler = wrapHandler;
|
package/lib/function-builder.js
CHANGED
|
@@ -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() {
|
package/lib/handler-builder.js
CHANGED
|
@@ -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.
|
|
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)();
|
package/lib/logger/compat.js
CHANGED
|
@@ -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',
|
package/lib/providers/auth.d.ts
CHANGED
|
@@ -1,27 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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?:
|
|
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
|
}
|