firebase-functions 6.6.0 → 7.0.0-rc.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/_virtual/rolldown_runtime.js +34 -0
- package/lib/bin/firebase-functions.js +78 -103
- package/lib/common/app.js +35 -55
- package/lib/common/change.js +54 -75
- package/lib/common/config.js +41 -41
- package/lib/common/debug.js +23 -47
- package/lib/common/encoding.js +59 -82
- package/lib/common/onInit.js +26 -28
- package/lib/common/options.js +22 -42
- package/lib/common/params.d.ts +6 -6
- package/lib/common/params.js +0 -23
- package/lib/common/providers/database.js +270 -300
- package/lib/common/providers/firestore.js +66 -92
- package/lib/common/providers/https.d.ts +0 -1
- package/lib/common/providers/https.js +537 -539
- package/lib/common/providers/identity.js +393 -444
- package/lib/common/providers/tasks.js +64 -98
- package/lib/common/timezone.js +544 -542
- package/lib/common/trace.d.ts +0 -1
- package/lib/common/trace.js +63 -55
- package/lib/common/utilities/assertions.d.ts +11 -0
- package/lib/common/utilities/assertions.js +18 -0
- package/lib/common/utilities/encoder.js +20 -37
- package/lib/common/utilities/path-pattern.js +106 -132
- package/lib/common/utilities/path.js +28 -27
- package/lib/common/utilities/utils.js +23 -45
- package/lib/esm/_virtual/rolldown_runtime.mjs +16 -0
- package/lib/esm/bin/firebase-functions.mjs +91 -0
- package/lib/esm/common/app.mjs +39 -0
- package/lib/esm/common/change.mjs +57 -0
- package/lib/esm/common/config.mjs +45 -0
- package/lib/esm/common/debug.mjs +28 -0
- package/lib/esm/common/encoding.mjs +69 -0
- package/lib/esm/common/onInit.mjs +33 -0
- package/lib/esm/common/options.mjs +22 -0
- package/lib/esm/common/params.mjs +1 -0
- package/lib/esm/common/providers/database.mjs +269 -0
- package/lib/esm/common/providers/firestore.mjs +78 -0
- package/lib/esm/common/providers/https.mjs +573 -0
- package/lib/esm/common/providers/identity.mjs +428 -0
- package/lib/esm/common/providers/tasks.mjs +67 -0
- package/lib/esm/common/timezone.mjs +544 -0
- package/lib/esm/common/trace.mjs +73 -0
- package/lib/esm/common/utilities/assertions.mjs +17 -0
- package/lib/esm/common/utilities/encoder.mjs +21 -0
- package/lib/esm/common/utilities/path-pattern.mjs +116 -0
- package/lib/esm/common/utilities/path.mjs +35 -0
- package/lib/esm/common/utilities/utils.mjs +29 -0
- package/lib/esm/function-configuration.mjs +1 -0
- package/lib/esm/logger/common.mjs +23 -0
- package/lib/esm/logger/compat.mjs +25 -0
- package/lib/esm/logger/index.mjs +131 -0
- package/lib/esm/params/index.mjs +160 -0
- package/lib/esm/params/types.mjs +400 -0
- package/lib/esm/runtime/loader.mjs +132 -0
- package/lib/esm/runtime/manifest.mjs +134 -0
- package/lib/esm/types/global.d.mjs +1 -0
- package/lib/esm/v1/cloud-functions.mjs +206 -0
- package/lib/esm/v1/config.mjs +14 -0
- package/lib/esm/v1/function-builder.mjs +252 -0
- package/lib/esm/v1/function-configuration.mjs +72 -0
- package/lib/esm/v1/index.mjs +27 -0
- package/lib/esm/v1/providers/analytics.mjs +212 -0
- package/lib/esm/v1/providers/auth.mjs +156 -0
- package/lib/esm/v1/providers/database.mjs +243 -0
- package/lib/esm/v1/providers/firestore.mjs +131 -0
- package/lib/esm/v1/providers/https.mjs +82 -0
- package/lib/esm/v1/providers/pubsub.mjs +175 -0
- package/lib/esm/v1/providers/remoteConfig.mjs +64 -0
- package/lib/esm/v1/providers/storage.mjs +163 -0
- package/lib/esm/v1/providers/tasks.mjs +63 -0
- package/lib/esm/v1/providers/testLab.mjs +94 -0
- package/lib/esm/v2/core.mjs +4 -0
- package/lib/esm/v2/index.mjs +28 -0
- package/lib/esm/v2/options.mjs +102 -0
- package/lib/esm/v2/providers/alerts/alerts.mjs +85 -0
- package/lib/esm/v2/providers/alerts/appDistribution.mjs +75 -0
- package/lib/esm/v2/providers/alerts/billing.mjs +51 -0
- package/lib/esm/v2/providers/alerts/crashlytics.mjs +122 -0
- package/lib/esm/v2/providers/alerts/index.mjs +22 -0
- package/lib/esm/v2/providers/alerts/performance.mjs +66 -0
- package/lib/esm/v2/providers/database.mjs +197 -0
- package/lib/esm/v2/providers/dataconnect.mjs +130 -0
- package/lib/esm/v2/providers/eventarc.mjs +51 -0
- package/lib/esm/v2/providers/firestore.mjs +294 -0
- package/lib/esm/v2/providers/https.mjs +210 -0
- package/lib/esm/v2/providers/identity.mjs +103 -0
- package/lib/esm/v2/providers/pubsub.mjs +148 -0
- package/lib/esm/v2/providers/remoteConfig.mjs +52 -0
- package/lib/esm/v2/providers/scheduler.mjs +84 -0
- package/lib/esm/v2/providers/storage.mjs +155 -0
- package/lib/esm/v2/providers/tasks.mjs +65 -0
- package/lib/esm/v2/providers/testLab.mjs +53 -0
- package/lib/esm/v2/trace.mjs +20 -0
- package/lib/function-configuration.d.ts +0 -0
- package/lib/function-configuration.js +0 -0
- package/lib/logger/common.js +21 -41
- package/lib/logger/compat.js +18 -33
- package/lib/logger/index.js +119 -130
- package/lib/params/index.d.ts +4 -2
- package/lib/params/index.js +150 -144
- package/lib/params/types.js +389 -423
- package/lib/runtime/loader.js +114 -148
- package/lib/runtime/manifest.js +106 -126
- package/lib/types/global.d.js +0 -0
- package/lib/v1/cloud-functions.d.ts +2 -2
- package/lib/v1/cloud-functions.js +193 -241
- package/lib/v1/config.d.ts +4 -7
- package/lib/v1/config.js +13 -75
- package/lib/v1/function-builder.js +239 -368
- package/lib/v1/function-configuration.js +70 -63
- package/lib/v1/index.js +118 -73
- package/lib/v1/providers/analytics.js +188 -235
- package/lib/v1/providers/auth.d.ts +2 -1
- package/lib/v1/providers/auth.js +159 -164
- package/lib/v1/providers/database.js +237 -242
- package/lib/v1/providers/firestore.js +131 -130
- package/lib/v1/providers/https.d.ts +2 -1
- package/lib/v1/providers/https.js +79 -86
- package/lib/v1/providers/pubsub.js +175 -172
- package/lib/v1/providers/remoteConfig.js +64 -68
- package/lib/v1/providers/storage.js +161 -163
- package/lib/v1/providers/tasks.d.ts +1 -1
- package/lib/v1/providers/tasks.js +65 -80
- package/lib/v1/providers/testLab.js +94 -94
- package/lib/v2/core.d.ts +1 -1
- package/lib/v2/core.js +5 -32
- package/lib/v2/index.d.ts +6 -3
- package/lib/v2/index.js +123 -75
- package/lib/v2/options.js +88 -114
- package/lib/v2/providers/alerts/alerts.js +76 -95
- package/lib/v2/providers/alerts/appDistribution.js +73 -78
- package/lib/v2/providers/alerts/billing.js +49 -53
- package/lib/v2/providers/alerts/crashlytics.js +110 -102
- package/lib/v2/providers/alerts/index.js +56 -53
- package/lib/v2/providers/alerts/performance.js +64 -74
- package/lib/v2/providers/database.js +177 -180
- package/lib/v2/providers/dataconnect.d.ts +95 -0
- package/lib/v2/providers/dataconnect.js +137 -0
- package/lib/v2/providers/eventarc.js +55 -77
- package/lib/v2/providers/firestore.js +262 -260
- package/lib/v2/providers/https.d.ts +3 -2
- package/lib/v2/providers/https.js +210 -247
- package/lib/v2/providers/identity.d.ts +2 -1
- package/lib/v2/providers/identity.js +96 -105
- package/lib/v2/providers/pubsub.js +149 -167
- package/lib/v2/providers/remoteConfig.js +54 -63
- package/lib/v2/providers/scheduler.js +84 -96
- package/lib/v2/providers/storage.js +147 -162
- package/lib/v2/providers/tasks.d.ts +1 -1
- package/lib/v2/providers/tasks.js +68 -95
- package/lib/v2/providers/testLab.js +55 -64
- package/lib/v2/trace.js +18 -19
- package/package.json +290 -226
- package/protos/compiledFirestore.mjs +3512 -0
- package/protos/update.sh +28 -7
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { debug, error, warn } from "../../logger/index.mjs";
|
|
2
|
+
import { getApp } from "../app.mjs";
|
|
3
|
+
import { isDebugFeatureEnabled } from "../debug.mjs";
|
|
4
|
+
import { HttpsError, unsafeDecodeToken } from "./https.mjs";
|
|
5
|
+
import * as auth from "firebase-admin/auth";
|
|
6
|
+
|
|
7
|
+
//#region src/common/providers/identity.ts
|
|
8
|
+
const DISALLOWED_CUSTOM_CLAIMS = [
|
|
9
|
+
"acr",
|
|
10
|
+
"amr",
|
|
11
|
+
"at_hash",
|
|
12
|
+
"aud",
|
|
13
|
+
"auth_time",
|
|
14
|
+
"azp",
|
|
15
|
+
"cnf",
|
|
16
|
+
"c_hash",
|
|
17
|
+
"exp",
|
|
18
|
+
"iat",
|
|
19
|
+
"iss",
|
|
20
|
+
"jti",
|
|
21
|
+
"nbf",
|
|
22
|
+
"nonce",
|
|
23
|
+
"firebase"
|
|
24
|
+
];
|
|
25
|
+
const CLAIMS_MAX_PAYLOAD_SIZE = 1e3;
|
|
26
|
+
const EVENT_MAPPING = {
|
|
27
|
+
beforeCreate: "providers/cloud.auth/eventTypes/user.beforeCreate",
|
|
28
|
+
beforeSignIn: "providers/cloud.auth/eventTypes/user.beforeSignIn",
|
|
29
|
+
beforeSendEmail: "providers/cloud.auth/eventTypes/user.beforeSendEmail",
|
|
30
|
+
beforeSendSms: "providers/cloud.auth/eventTypes/user.beforeSendSms"
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Helper class to create the user metadata in a `UserRecord` object.
|
|
34
|
+
*/
|
|
35
|
+
var UserRecordMetadata = class {
|
|
36
|
+
constructor(creationTime, lastSignInTime) {
|
|
37
|
+
this.creationTime = creationTime;
|
|
38
|
+
this.lastSignInTime = lastSignInTime;
|
|
39
|
+
}
|
|
40
|
+
/** Returns a plain JavaScript object with the properties of UserRecordMetadata. */
|
|
41
|
+
toJSON() {
|
|
42
|
+
return {
|
|
43
|
+
creationTime: this.creationTime,
|
|
44
|
+
lastSignInTime: this.lastSignInTime
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Helper function that creates a `UserRecord` class from data sent over the wire.
|
|
50
|
+
* @param wireData data sent over the wire
|
|
51
|
+
* @returns an instance of `UserRecord` with correct toJSON functions
|
|
52
|
+
*/
|
|
53
|
+
function userRecordConstructor(wireData) {
|
|
54
|
+
const falseyValues = {
|
|
55
|
+
email: null,
|
|
56
|
+
emailVerified: false,
|
|
57
|
+
displayName: null,
|
|
58
|
+
photoURL: null,
|
|
59
|
+
phoneNumber: null,
|
|
60
|
+
disabled: false,
|
|
61
|
+
providerData: [],
|
|
62
|
+
customClaims: {},
|
|
63
|
+
passwordSalt: null,
|
|
64
|
+
passwordHash: null,
|
|
65
|
+
tokensValidAfterTime: null
|
|
66
|
+
};
|
|
67
|
+
const record = {
|
|
68
|
+
...falseyValues,
|
|
69
|
+
...wireData
|
|
70
|
+
};
|
|
71
|
+
const meta = record.metadata;
|
|
72
|
+
if (meta) {
|
|
73
|
+
record.metadata = new UserRecordMetadata(meta.createdAt || meta.creationTime, meta.lastSignedInAt || meta.lastSignInTime);
|
|
74
|
+
} else {
|
|
75
|
+
record.metadata = new UserRecordMetadata(null, null);
|
|
76
|
+
}
|
|
77
|
+
record.toJSON = () => {
|
|
78
|
+
const { uid, email, emailVerified, displayName, photoURL, phoneNumber, disabled, passwordHash, passwordSalt, tokensValidAfterTime } = record;
|
|
79
|
+
const json = {
|
|
80
|
+
uid,
|
|
81
|
+
email,
|
|
82
|
+
emailVerified,
|
|
83
|
+
displayName,
|
|
84
|
+
photoURL,
|
|
85
|
+
phoneNumber,
|
|
86
|
+
disabled,
|
|
87
|
+
passwordHash,
|
|
88
|
+
passwordSalt,
|
|
89
|
+
tokensValidAfterTime
|
|
90
|
+
};
|
|
91
|
+
json.metadata = record.metadata.toJSON();
|
|
92
|
+
json.customClaims = JSON.parse(JSON.stringify(record.customClaims));
|
|
93
|
+
json.providerData = record.providerData.map((entry) => {
|
|
94
|
+
const newEntry = { ...entry };
|
|
95
|
+
newEntry.toJSON = () => entry;
|
|
96
|
+
return newEntry;
|
|
97
|
+
});
|
|
98
|
+
return json;
|
|
99
|
+
};
|
|
100
|
+
return record;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Checks for a valid identity platform web request, otherwise throws an HttpsError.
|
|
104
|
+
* @internal
|
|
105
|
+
*/
|
|
106
|
+
function isValidRequest(req) {
|
|
107
|
+
if (req.method !== "POST") {
|
|
108
|
+
warn(`Request has invalid method "${req.method}".`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
const contentType = (req.header("Content-Type") || "").toLowerCase();
|
|
112
|
+
if (!contentType.includes("application/json")) {
|
|
113
|
+
warn("Request has invalid header Content-Type.");
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
if (!req.body?.data?.jwt) {
|
|
117
|
+
warn("Request has an invalid body.");
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Decode, but not verify, an Auth Blocking token.
|
|
124
|
+
*
|
|
125
|
+
* Do not use in production. Token should always be verified using the Admin SDK.
|
|
126
|
+
*
|
|
127
|
+
* This is exposed only for testing.
|
|
128
|
+
*/
|
|
129
|
+
function unsafeDecodeAuthBlockingToken(token) {
|
|
130
|
+
const decoded = unsafeDecodeToken(token);
|
|
131
|
+
decoded.uid = decoded.sub;
|
|
132
|
+
return decoded;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Helper function to parse the decoded metadata object into a `UserMetaData` object
|
|
136
|
+
* @internal
|
|
137
|
+
*/
|
|
138
|
+
function parseMetadata(metadata) {
|
|
139
|
+
const creationTime = metadata?.creation_time ? new Date(metadata.creation_time).toUTCString() : null;
|
|
140
|
+
const lastSignInTime = metadata?.last_sign_in_time ? new Date(metadata.last_sign_in_time).toUTCString() : null;
|
|
141
|
+
return {
|
|
142
|
+
creationTime,
|
|
143
|
+
lastSignInTime
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Helper function to parse the decoded user info array into an `AuthUserInfo` array.
|
|
148
|
+
* @internal
|
|
149
|
+
*/
|
|
150
|
+
function parseProviderData(providerData) {
|
|
151
|
+
const providers = [];
|
|
152
|
+
for (const provider of providerData) {
|
|
153
|
+
providers.push({
|
|
154
|
+
uid: provider.uid,
|
|
155
|
+
displayName: provider.display_name,
|
|
156
|
+
email: provider.email,
|
|
157
|
+
photoURL: provider.photo_url,
|
|
158
|
+
providerId: provider.provider_id,
|
|
159
|
+
phoneNumber: provider.phone_number
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return providers;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Helper function to parse the date into a UTC string.
|
|
166
|
+
* @internal
|
|
167
|
+
*/
|
|
168
|
+
function parseDate(tokensValidAfterTime) {
|
|
169
|
+
if (!tokensValidAfterTime) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
tokensValidAfterTime = tokensValidAfterTime * 1e3;
|
|
173
|
+
try {
|
|
174
|
+
const date = new Date(tokensValidAfterTime);
|
|
175
|
+
if (!isNaN(date.getTime())) {
|
|
176
|
+
return date.toUTCString();
|
|
177
|
+
}
|
|
178
|
+
} catch {}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Helper function to parse the decoded enrolled factors into a valid MultiFactorSettings
|
|
183
|
+
* @internal
|
|
184
|
+
*/
|
|
185
|
+
function parseMultiFactor(multiFactor) {
|
|
186
|
+
if (!multiFactor) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const parsedEnrolledFactors = [];
|
|
190
|
+
for (const factor of multiFactor.enrolled_factors || []) {
|
|
191
|
+
if (!factor.uid) {
|
|
192
|
+
throw new HttpsError("internal", "INTERNAL ASSERT FAILED: Invalid multi-factor info response");
|
|
193
|
+
}
|
|
194
|
+
const enrollmentTime = factor.enrollment_time ? new Date(factor.enrollment_time).toUTCString() : null;
|
|
195
|
+
parsedEnrolledFactors.push({
|
|
196
|
+
uid: factor.uid,
|
|
197
|
+
factorId: factor.phone_number ? factor.factor_id || "phone" : factor.factor_id,
|
|
198
|
+
displayName: factor.display_name,
|
|
199
|
+
enrollmentTime,
|
|
200
|
+
phoneNumber: factor.phone_number
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (parsedEnrolledFactors.length > 0) {
|
|
204
|
+
return { enrolledFactors: parsedEnrolledFactors };
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Parses the decoded user record into a valid UserRecord for use in the handler
|
|
210
|
+
* @internal
|
|
211
|
+
*/
|
|
212
|
+
function parseAuthUserRecord(decodedJWTUserRecord) {
|
|
213
|
+
if (!decodedJWTUserRecord.uid) {
|
|
214
|
+
throw new HttpsError("internal", "INTERNAL ASSERT FAILED: Invalid user response");
|
|
215
|
+
}
|
|
216
|
+
const disabled = decodedJWTUserRecord.disabled || false;
|
|
217
|
+
const metadata = parseMetadata(decodedJWTUserRecord.metadata);
|
|
218
|
+
const providerData = parseProviderData(decodedJWTUserRecord.provider_data);
|
|
219
|
+
const tokensValidAfterTime = parseDate(decodedJWTUserRecord.tokens_valid_after_time);
|
|
220
|
+
const multiFactor = parseMultiFactor(decodedJWTUserRecord.multi_factor);
|
|
221
|
+
return {
|
|
222
|
+
uid: decodedJWTUserRecord.uid,
|
|
223
|
+
email: decodedJWTUserRecord.email,
|
|
224
|
+
emailVerified: decodedJWTUserRecord.email_verified,
|
|
225
|
+
displayName: decodedJWTUserRecord.display_name,
|
|
226
|
+
photoURL: decodedJWTUserRecord.photo_url,
|
|
227
|
+
phoneNumber: decodedJWTUserRecord.phone_number,
|
|
228
|
+
disabled,
|
|
229
|
+
metadata,
|
|
230
|
+
providerData,
|
|
231
|
+
passwordHash: decodedJWTUserRecord.password_hash,
|
|
232
|
+
passwordSalt: decodedJWTUserRecord.password_salt,
|
|
233
|
+
customClaims: decodedJWTUserRecord.custom_claims,
|
|
234
|
+
tenantId: decodedJWTUserRecord.tenant_id,
|
|
235
|
+
tokensValidAfterTime,
|
|
236
|
+
multiFactor
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/** Helper to get the `AdditionalUserInfo` from the decoded JWT */
|
|
240
|
+
function parseAdditionalUserInfo(decodedJWT) {
|
|
241
|
+
let profile;
|
|
242
|
+
let username;
|
|
243
|
+
if (decodedJWT.raw_user_info) {
|
|
244
|
+
try {
|
|
245
|
+
profile = JSON.parse(decodedJWT.raw_user_info);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
debug(`Parse Error: ${err.message}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (profile) {
|
|
251
|
+
if (decodedJWT.sign_in_method === "github.com") {
|
|
252
|
+
username = profile.login;
|
|
253
|
+
}
|
|
254
|
+
if (decodedJWT.sign_in_method === "twitter.com") {
|
|
255
|
+
username = profile.screen_name;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
providerId: decodedJWT.sign_in_method === "emailLink" ? "password" : decodedJWT.sign_in_method,
|
|
260
|
+
profile,
|
|
261
|
+
username,
|
|
262
|
+
isNewUser: decodedJWT.event_type === "beforeCreate" ? true : false,
|
|
263
|
+
recaptchaScore: decodedJWT.recaptcha_score,
|
|
264
|
+
email: decodedJWT.email,
|
|
265
|
+
phoneNumber: decodedJWT.phone_number
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Helper to generate a response from the blocking function to the Firebase Auth backend.
|
|
270
|
+
* @internal
|
|
271
|
+
*/
|
|
272
|
+
function generateResponsePayload(authResponse) {
|
|
273
|
+
if (!authResponse) {
|
|
274
|
+
return {};
|
|
275
|
+
}
|
|
276
|
+
const { recaptchaActionOverride,...formattedAuthResponse } = authResponse;
|
|
277
|
+
const result = {};
|
|
278
|
+
const updateMask = getUpdateMask(formattedAuthResponse);
|
|
279
|
+
if (updateMask.length !== 0) {
|
|
280
|
+
result.userRecord = {
|
|
281
|
+
...formattedAuthResponse,
|
|
282
|
+
updateMask
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
if (recaptchaActionOverride !== undefined) {
|
|
286
|
+
result.recaptchaActionOverride = recaptchaActionOverride;
|
|
287
|
+
}
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
/** Helper to get the Credential from the decoded JWT */
|
|
291
|
+
function parseAuthCredential(decodedJWT, time) {
|
|
292
|
+
if (!decodedJWT.sign_in_attributes && !decodedJWT.oauth_id_token && !decodedJWT.oauth_access_token && !decodedJWT.oauth_refresh_token) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
claims: decodedJWT.sign_in_attributes,
|
|
297
|
+
idToken: decodedJWT.oauth_id_token,
|
|
298
|
+
accessToken: decodedJWT.oauth_access_token,
|
|
299
|
+
refreshToken: decodedJWT.oauth_refresh_token,
|
|
300
|
+
expirationTime: decodedJWT.oauth_expires_in ? new Date(time + decodedJWT.oauth_expires_in * 1e3).toUTCString() : undefined,
|
|
301
|
+
secret: decodedJWT.oauth_token_secret,
|
|
302
|
+
providerId: decodedJWT.sign_in_method === "emailLink" ? "password" : decodedJWT.sign_in_method,
|
|
303
|
+
signInMethod: decodedJWT.sign_in_method
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Parses the decoded jwt into a valid AuthEventContext for use in the handler
|
|
308
|
+
* @internal
|
|
309
|
+
*/
|
|
310
|
+
function parseAuthEventContext(decodedJWT, projectId, time = new Date().getTime()) {
|
|
311
|
+
const eventType = (EVENT_MAPPING[decodedJWT.event_type] || decodedJWT.event_type) + (decodedJWT.sign_in_method ? `:${decodedJWT.sign_in_method}` : "");
|
|
312
|
+
return {
|
|
313
|
+
locale: decodedJWT.locale,
|
|
314
|
+
ipAddress: decodedJWT.ip_address,
|
|
315
|
+
userAgent: decodedJWT.user_agent,
|
|
316
|
+
eventId: decodedJWT.event_id,
|
|
317
|
+
eventType,
|
|
318
|
+
authType: decodedJWT.user_record ? "USER" : "UNAUTHENTICATED",
|
|
319
|
+
resource: {
|
|
320
|
+
service: "identitytoolkit.googleapis.com",
|
|
321
|
+
name: decodedJWT.tenant_id ? `projects/${projectId}/tenants/${decodedJWT.tenant_id}` : `projects/${projectId}`
|
|
322
|
+
},
|
|
323
|
+
timestamp: new Date(decodedJWT.iat * 1e3).toUTCString(),
|
|
324
|
+
additionalUserInfo: parseAdditionalUserInfo(decodedJWT),
|
|
325
|
+
credential: parseAuthCredential(decodedJWT, time),
|
|
326
|
+
emailType: decodedJWT.email_type,
|
|
327
|
+
smsType: decodedJWT.sms_type,
|
|
328
|
+
params: {}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Checks the handler response for invalid customClaims & sessionClaims objects
|
|
333
|
+
* @internal
|
|
334
|
+
*/
|
|
335
|
+
function validateAuthResponse(eventType, authRequest) {
|
|
336
|
+
if (!authRequest) {
|
|
337
|
+
authRequest = {};
|
|
338
|
+
}
|
|
339
|
+
if (authRequest.customClaims) {
|
|
340
|
+
const invalidClaims = DISALLOWED_CUSTOM_CLAIMS.filter((claim) => authRequest.customClaims.hasOwnProperty(claim));
|
|
341
|
+
if (invalidClaims.length > 0) {
|
|
342
|
+
throw new HttpsError("invalid-argument", `The customClaims claims "${invalidClaims.join(",")}" are reserved and cannot be specified.`);
|
|
343
|
+
}
|
|
344
|
+
if (JSON.stringify(authRequest.customClaims).length > CLAIMS_MAX_PAYLOAD_SIZE) {
|
|
345
|
+
throw new HttpsError("invalid-argument", `The customClaims payload should not exceed ${CLAIMS_MAX_PAYLOAD_SIZE} characters.`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (eventType === "beforeSignIn" && authRequest.sessionClaims) {
|
|
349
|
+
const invalidClaims = DISALLOWED_CUSTOM_CLAIMS.filter((claim) => authRequest.sessionClaims.hasOwnProperty(claim));
|
|
350
|
+
if (invalidClaims.length > 0) {
|
|
351
|
+
throw new HttpsError("invalid-argument", `The sessionClaims claims "${invalidClaims.join(",")}" are reserved and cannot be specified.`);
|
|
352
|
+
}
|
|
353
|
+
if (JSON.stringify(authRequest.sessionClaims).length > CLAIMS_MAX_PAYLOAD_SIZE) {
|
|
354
|
+
throw new HttpsError("invalid-argument", `The sessionClaims payload should not exceed ${CLAIMS_MAX_PAYLOAD_SIZE} characters.`);
|
|
355
|
+
}
|
|
356
|
+
const combinedClaims = {
|
|
357
|
+
...authRequest.customClaims,
|
|
358
|
+
...authRequest.sessionClaims
|
|
359
|
+
};
|
|
360
|
+
if (JSON.stringify(combinedClaims).length > CLAIMS_MAX_PAYLOAD_SIZE) {
|
|
361
|
+
throw new HttpsError("invalid-argument", `The customClaims and sessionClaims payloads should not exceed ${CLAIMS_MAX_PAYLOAD_SIZE} characters combined.`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Helper function to generate the update mask for the identity platform changed values
|
|
367
|
+
* @internal
|
|
368
|
+
*/
|
|
369
|
+
function getUpdateMask(authResponse) {
|
|
370
|
+
if (!authResponse) {
|
|
371
|
+
return "";
|
|
372
|
+
}
|
|
373
|
+
const updateMask = [];
|
|
374
|
+
for (const key in authResponse) {
|
|
375
|
+
if (authResponse.hasOwnProperty(key) && typeof authResponse[key] !== "undefined") {
|
|
376
|
+
updateMask.push(key);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return updateMask.join(",");
|
|
380
|
+
}
|
|
381
|
+
/** @internal */
|
|
382
|
+
function wrapHandler(eventType, handler) {
|
|
383
|
+
return async (req, res) => {
|
|
384
|
+
try {
|
|
385
|
+
const projectId = process.env.GCLOUD_PROJECT;
|
|
386
|
+
if (!isValidRequest(req)) {
|
|
387
|
+
error("Invalid request, unable to process");
|
|
388
|
+
throw new HttpsError("invalid-argument", "Bad Request");
|
|
389
|
+
}
|
|
390
|
+
if (!auth.getAuth(getApp())._verifyAuthBlockingToken) {
|
|
391
|
+
throw new Error("Cannot validate Auth Blocking token. Please update Firebase Admin SDK to >= v10.1.0");
|
|
392
|
+
}
|
|
393
|
+
const decodedPayload = isDebugFeatureEnabled("skipTokenVerification") ? unsafeDecodeAuthBlockingToken(req.body.data.jwt) : handler.platform === "gcfv1" ? await auth.getAuth(getApp())._verifyAuthBlockingToken(req.body.data.jwt) : await auth.getAuth(getApp())._verifyAuthBlockingToken(req.body.data.jwt, "run.app");
|
|
394
|
+
let authUserRecord;
|
|
395
|
+
if (decodedPayload.event_type === "beforeCreate" || decodedPayload.event_type === "beforeSignIn") {
|
|
396
|
+
authUserRecord = parseAuthUserRecord(decodedPayload.user_record);
|
|
397
|
+
}
|
|
398
|
+
const authEventContext = parseAuthEventContext(decodedPayload, projectId);
|
|
399
|
+
let authResponse;
|
|
400
|
+
if (handler.platform === "gcfv1") {
|
|
401
|
+
authResponse = authUserRecord ? await handler(authUserRecord, authEventContext) || undefined : await handler(authEventContext) || undefined;
|
|
402
|
+
} else {
|
|
403
|
+
authResponse = await handler({
|
|
404
|
+
...authEventContext,
|
|
405
|
+
data: authUserRecord
|
|
406
|
+
}) || undefined;
|
|
407
|
+
}
|
|
408
|
+
validateAuthResponse(eventType, authResponse);
|
|
409
|
+
const result = generateResponsePayload(authResponse);
|
|
410
|
+
res.status(200);
|
|
411
|
+
res.setHeader("Content-Type", "application/json");
|
|
412
|
+
res.send(JSON.stringify(result));
|
|
413
|
+
} catch (err) {
|
|
414
|
+
let httpErr = err;
|
|
415
|
+
if (!(httpErr instanceof HttpsError)) {
|
|
416
|
+
error("Unhandled error", err);
|
|
417
|
+
httpErr = new HttpsError("internal", "An unexpected error occurred.");
|
|
418
|
+
}
|
|
419
|
+
const { status } = httpErr.httpErrorCode;
|
|
420
|
+
const body = { error: httpErr.toJSON() };
|
|
421
|
+
res.setHeader("Content-Type", "application/json");
|
|
422
|
+
res.status(status).send(body);
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
//#endregion
|
|
428
|
+
export { HttpsError, UserRecordMetadata, generateResponsePayload, getUpdateMask, isValidRequest, parseAuthEventContext, parseAuthUserRecord, parseDate, parseMetadata, parseMultiFactor, parseProviderData, userRecordConstructor, validateAuthResponse, wrapHandler };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { error } from "../../logger/index.mjs";
|
|
2
|
+
import { HttpsError, decode, isValidRequest, unsafeDecodeIdToken } from "./https.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/common/providers/tasks.ts
|
|
5
|
+
/** @internal */
|
|
6
|
+
function onDispatchHandler(handler) {
|
|
7
|
+
return async (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
if (!isValidRequest(req)) {
|
|
10
|
+
error("Invalid request, unable to process.");
|
|
11
|
+
throw new HttpsError("invalid-argument", "Bad Request");
|
|
12
|
+
}
|
|
13
|
+
const headers = {};
|
|
14
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
15
|
+
if (!Array.isArray(value)) {
|
|
16
|
+
headers[key] = value;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const context = {
|
|
20
|
+
queueName: req.header("X-CloudTasks-QueueName"),
|
|
21
|
+
id: req.header("X-CloudTasks-TaskName"),
|
|
22
|
+
retryCount: req.header("X-CloudTasks-TaskRetryCount") ? Number(req.header("X-CloudTasks-TaskRetryCount")) : undefined,
|
|
23
|
+
executionCount: req.header("X-CloudTasks-TaskExecutionCount") ? Number(req.header("X-CloudTasks-TaskExecutionCount")) : undefined,
|
|
24
|
+
scheduledTime: req.header("X-CloudTasks-TaskETA"),
|
|
25
|
+
previousResponse: req.header("X-CloudTasks-TaskPreviousResponse") ? Number(req.header("X-CloudTasks-TaskPreviousResponse")) : undefined,
|
|
26
|
+
retryReason: req.header("X-CloudTasks-TaskRetryReason"),
|
|
27
|
+
headers
|
|
28
|
+
};
|
|
29
|
+
if (!process.env.FUNCTIONS_EMULATOR) {
|
|
30
|
+
const authHeader = req.header("Authorization") || "";
|
|
31
|
+
const token = authHeader.match(/^Bearer (.*)$/)?.[1];
|
|
32
|
+
if (!token) {
|
|
33
|
+
throw new HttpsError("unauthenticated", "Unauthenticated");
|
|
34
|
+
}
|
|
35
|
+
const authToken = unsafeDecodeIdToken(token);
|
|
36
|
+
context.auth = {
|
|
37
|
+
uid: authToken.uid,
|
|
38
|
+
token: authToken,
|
|
39
|
+
rawToken: token
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const data = decode(req.body.data);
|
|
43
|
+
if (handler.length === 2) {
|
|
44
|
+
await handler(data, context);
|
|
45
|
+
} else {
|
|
46
|
+
const arg = {
|
|
47
|
+
...context,
|
|
48
|
+
data
|
|
49
|
+
};
|
|
50
|
+
await handler(arg);
|
|
51
|
+
}
|
|
52
|
+
res.status(204).end();
|
|
53
|
+
} catch (err) {
|
|
54
|
+
let httpErr = err;
|
|
55
|
+
if (!(err instanceof HttpsError)) {
|
|
56
|
+
error("Unhandled error", err);
|
|
57
|
+
httpErr = new HttpsError("internal", "INTERNAL");
|
|
58
|
+
}
|
|
59
|
+
const { status } = httpErr.httpErrorCode;
|
|
60
|
+
const body = { error: httpErr.toJSON() };
|
|
61
|
+
res.status(status).send(body);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { onDispatchHandler };
|