1 |
|
2 | "use strict";
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | Object.defineProperty(exports, "__esModule", { value: true });
|
19 | exports.UserImportBuilder = exports.convertMultiFactorInfoToServerFormat = void 0;
|
20 | var deep_copy_1 = require("../utils/deep-copy");
|
21 | var utils = require("../utils");
|
22 | var validator = require("../utils/validator");
|
23 | var error_1 = require("../utils/error");
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | function convertMultiFactorInfoToServerFormat(multiFactorInfo) {
|
30 | var enrolledAt;
|
31 | if (typeof multiFactorInfo.enrollmentTime !== 'undefined') {
|
32 | if (validator.isUTCDateString(multiFactorInfo.enrollmentTime)) {
|
33 |
|
34 | enrolledAt = new Date(multiFactorInfo.enrollmentTime).toISOString();
|
35 | }
|
36 | else {
|
37 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ENROLLMENT_TIME, "The second factor \"enrollmentTime\" for \"" + multiFactorInfo.uid + "\" must be a valid " +
|
38 | 'UTC date string.');
|
39 | }
|
40 | }
|
41 |
|
42 | if (isPhoneFactor(multiFactorInfo)) {
|
43 |
|
44 | var authFactorInfo = {
|
45 | mfaEnrollmentId: multiFactorInfo.uid,
|
46 | displayName: multiFactorInfo.displayName,
|
47 |
|
48 | phoneInfo: multiFactorInfo.phoneNumber,
|
49 | enrolledAt: enrolledAt,
|
50 | };
|
51 | for (var objKey in authFactorInfo) {
|
52 | if (typeof authFactorInfo[objKey] === 'undefined') {
|
53 | delete authFactorInfo[objKey];
|
54 | }
|
55 | }
|
56 | return authFactorInfo;
|
57 | }
|
58 | else {
|
59 |
|
60 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.UNSUPPORTED_SECOND_FACTOR, "Unsupported second factor \"" + JSON.stringify(multiFactorInfo) + "\" provided.");
|
61 | }
|
62 | }
|
63 | exports.convertMultiFactorInfoToServerFormat = convertMultiFactorInfoToServerFormat;
|
64 | function isPhoneFactor(multiFactorInfo) {
|
65 | return multiFactorInfo.factorId === 'phone';
|
66 | }
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | function getNumberField(obj, key) {
|
73 | if (typeof obj[key] !== 'undefined' && obj[key] !== null) {
|
74 | return parseInt(obj[key].toString(), 10);
|
75 | }
|
76 | return NaN;
|
77 | }
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | function populateUploadAccountUser(user, userValidator) {
|
86 | var result = {
|
87 | localId: user.uid,
|
88 | email: user.email,
|
89 | emailVerified: user.emailVerified,
|
90 | displayName: user.displayName,
|
91 | disabled: user.disabled,
|
92 | photoUrl: user.photoURL,
|
93 | phoneNumber: user.phoneNumber,
|
94 | providerUserInfo: [],
|
95 | mfaInfo: [],
|
96 | tenantId: user.tenantId,
|
97 | customAttributes: user.customClaims && JSON.stringify(user.customClaims),
|
98 | };
|
99 | if (typeof user.passwordHash !== 'undefined') {
|
100 | if (!validator.isBuffer(user.passwordHash)) {
|
101 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PASSWORD_HASH);
|
102 | }
|
103 | result.passwordHash = utils.toWebSafeBase64(user.passwordHash);
|
104 | }
|
105 | if (typeof user.passwordSalt !== 'undefined') {
|
106 | if (!validator.isBuffer(user.passwordSalt)) {
|
107 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PASSWORD_SALT);
|
108 | }
|
109 | result.salt = utils.toWebSafeBase64(user.passwordSalt);
|
110 | }
|
111 | if (validator.isNonNullObject(user.metadata)) {
|
112 | if (validator.isNonEmptyString(user.metadata.creationTime)) {
|
113 | result.createdAt = new Date(user.metadata.creationTime).getTime();
|
114 | }
|
115 | if (validator.isNonEmptyString(user.metadata.lastSignInTime)) {
|
116 | result.lastLoginAt = new Date(user.metadata.lastSignInTime).getTime();
|
117 | }
|
118 | }
|
119 | if (validator.isArray(user.providerData)) {
|
120 | user.providerData.forEach(function (providerData) {
|
121 | result.providerUserInfo.push({
|
122 | providerId: providerData.providerId,
|
123 | rawId: providerData.uid,
|
124 | email: providerData.email,
|
125 | displayName: providerData.displayName,
|
126 | photoUrl: providerData.photoURL,
|
127 | });
|
128 | });
|
129 | }
|
130 |
|
131 | if (validator.isNonNullObject(user.multiFactor) &&
|
132 | validator.isNonEmptyArray(user.multiFactor.enrolledFactors)) {
|
133 | user.multiFactor.enrolledFactors.forEach(function (multiFactorInfo) {
|
134 | result.mfaInfo.push(convertMultiFactorInfoToServerFormat(multiFactorInfo));
|
135 | });
|
136 | }
|
137 |
|
138 | var key;
|
139 | for (key in result) {
|
140 | if (typeof result[key] === 'undefined') {
|
141 | delete result[key];
|
142 | }
|
143 | }
|
144 | if (result.providerUserInfo.length === 0) {
|
145 | delete result.providerUserInfo;
|
146 | }
|
147 | if (result.mfaInfo.length === 0) {
|
148 | delete result.mfaInfo;
|
149 | }
|
150 |
|
151 |
|
152 | if (typeof userValidator === 'function') {
|
153 | userValidator(result);
|
154 | }
|
155 | return result;
|
156 | }
|
157 |
|
158 |
|
159 |
|
160 |
|
161 | var UserImportBuilder = (function () {
|
162 | |
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 | function UserImportBuilder(users, options, userRequestValidator) {
|
170 | this.requiresHashOptions = false;
|
171 | this.validatedUsers = [];
|
172 | this.userImportResultErrors = [];
|
173 | this.indexMap = {};
|
174 | this.validatedUsers = this.populateUsers(users, userRequestValidator);
|
175 | this.validatedOptions = this.populateOptions(options, this.requiresHashOptions);
|
176 | }
|
177 | |
178 |
|
179 |
|
180 |
|
181 | UserImportBuilder.prototype.buildRequest = function () {
|
182 | var users = this.validatedUsers.map(function (user) {
|
183 | return deep_copy_1.deepCopy(user);
|
184 | });
|
185 | return deep_copy_1.deepExtend({ users: users }, deep_copy_1.deepCopy(this.validatedOptions));
|
186 | };
|
187 | |
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 | UserImportBuilder.prototype.buildResponse = function (failedUploads) {
|
194 | var _this = this;
|
195 |
|
196 | var importResult = {
|
197 | successCount: this.validatedUsers.length,
|
198 | failureCount: this.userImportResultErrors.length,
|
199 | errors: deep_copy_1.deepCopy(this.userImportResultErrors),
|
200 | };
|
201 | importResult.failureCount += failedUploads.length;
|
202 | importResult.successCount -= failedUploads.length;
|
203 | failedUploads.forEach(function (failedUpload) {
|
204 | importResult.errors.push({
|
205 |
|
206 | index: _this.indexMap[failedUpload.index],
|
207 | error: new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_USER_IMPORT, failedUpload.message),
|
208 | });
|
209 | });
|
210 |
|
211 | importResult.errors.sort(function (a, b) {
|
212 | return a.index - b.index;
|
213 | });
|
214 |
|
215 | return importResult;
|
216 | };
|
217 | |
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 | UserImportBuilder.prototype.populateOptions = function (options, requiresHashOptions) {
|
225 | var populatedOptions;
|
226 | if (!requiresHashOptions) {
|
227 | return {};
|
228 | }
|
229 | if (!validator.isNonNullObject(options)) {
|
230 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, '"UserImportOptions" are required when importing users with passwords.');
|
231 | }
|
232 | if (!validator.isNonNullObject(options.hash)) {
|
233 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.MISSING_HASH_ALGORITHM, '"hash.algorithm" is missing from the provided "UserImportOptions".');
|
234 | }
|
235 | if (typeof options.hash.algorithm === 'undefined' ||
|
236 | !validator.isNonEmptyString(options.hash.algorithm)) {
|
237 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_ALGORITHM, '"hash.algorithm" must be a string matching the list of supported algorithms.');
|
238 | }
|
239 | var rounds;
|
240 | switch (options.hash.algorithm) {
|
241 | case 'HMAC_SHA512':
|
242 | case 'HMAC_SHA256':
|
243 | case 'HMAC_SHA1':
|
244 | case 'HMAC_MD5':
|
245 | if (!validator.isBuffer(options.hash.key)) {
|
246 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_KEY, 'A non-empty "hash.key" byte buffer must be provided for ' +
|
247 | ("hash algorithm " + options.hash.algorithm + "."));
|
248 | }
|
249 | populatedOptions = {
|
250 | hashAlgorithm: options.hash.algorithm,
|
251 | signerKey: utils.toWebSafeBase64(options.hash.key),
|
252 | };
|
253 | break;
|
254 | case 'MD5':
|
255 | case 'SHA1':
|
256 | case 'SHA256':
|
257 | case 'SHA512': {
|
258 |
|
259 | rounds = getNumberField(options.hash, 'rounds');
|
260 | var minRounds = options.hash.algorithm === 'MD5' ? 0 : 1;
|
261 | if (isNaN(rounds) || rounds < minRounds || rounds > 8192) {
|
262 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_ROUNDS, "A valid \"hash.rounds\" number between " + minRounds + " and 8192 must be provided for " +
|
263 | ("hash algorithm " + options.hash.algorithm + "."));
|
264 | }
|
265 | populatedOptions = {
|
266 | hashAlgorithm: options.hash.algorithm,
|
267 | rounds: rounds,
|
268 | };
|
269 | break;
|
270 | }
|
271 | case 'PBKDF_SHA1':
|
272 | case 'PBKDF2_SHA256':
|
273 | rounds = getNumberField(options.hash, 'rounds');
|
274 | if (isNaN(rounds) || rounds < 0 || rounds > 120000) {
|
275 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_ROUNDS, 'A valid "hash.rounds" number between 0 and 120000 must be provided for ' +
|
276 | ("hash algorithm " + options.hash.algorithm + "."));
|
277 | }
|
278 | populatedOptions = {
|
279 | hashAlgorithm: options.hash.algorithm,
|
280 | rounds: rounds,
|
281 | };
|
282 | break;
|
283 | case 'SCRYPT': {
|
284 | if (!validator.isBuffer(options.hash.key)) {
|
285 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_KEY, 'A "hash.key" byte buffer must be provided for ' +
|
286 | ("hash algorithm " + options.hash.algorithm + "."));
|
287 | }
|
288 | rounds = getNumberField(options.hash, 'rounds');
|
289 | if (isNaN(rounds) || rounds <= 0 || rounds > 8) {
|
290 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_ROUNDS, 'A valid "hash.rounds" number between 1 and 8 must be provided for ' +
|
291 | ("hash algorithm " + options.hash.algorithm + "."));
|
292 | }
|
293 | var memoryCost = getNumberField(options.hash, 'memoryCost');
|
294 | if (isNaN(memoryCost) || memoryCost <= 0 || memoryCost > 14) {
|
295 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_MEMORY_COST, 'A valid "hash.memoryCost" number between 1 and 14 must be provided for ' +
|
296 | ("hash algorithm " + options.hash.algorithm + "."));
|
297 | }
|
298 | if (typeof options.hash.saltSeparator !== 'undefined' &&
|
299 | !validator.isBuffer(options.hash.saltSeparator)) {
|
300 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_SALT_SEPARATOR, '"hash.saltSeparator" must be a byte buffer.');
|
301 | }
|
302 | populatedOptions = {
|
303 | hashAlgorithm: options.hash.algorithm,
|
304 | signerKey: utils.toWebSafeBase64(options.hash.key),
|
305 | rounds: rounds,
|
306 | memoryCost: memoryCost,
|
307 | saltSeparator: utils.toWebSafeBase64(options.hash.saltSeparator || Buffer.from('')),
|
308 | };
|
309 | break;
|
310 | }
|
311 | case 'BCRYPT':
|
312 | populatedOptions = {
|
313 | hashAlgorithm: options.hash.algorithm,
|
314 | };
|
315 | break;
|
316 | case 'STANDARD_SCRYPT': {
|
317 | var cpuMemCost = getNumberField(options.hash, 'memoryCost');
|
318 | if (isNaN(cpuMemCost)) {
|
319 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_MEMORY_COST, 'A valid "hash.memoryCost" number must be provided for ' +
|
320 | ("hash algorithm " + options.hash.algorithm + "."));
|
321 | }
|
322 | var parallelization = getNumberField(options.hash, 'parallelization');
|
323 | if (isNaN(parallelization)) {
|
324 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_PARALLELIZATION, 'A valid "hash.parallelization" number must be provided for ' +
|
325 | ("hash algorithm " + options.hash.algorithm + "."));
|
326 | }
|
327 | var blockSize = getNumberField(options.hash, 'blockSize');
|
328 | if (isNaN(blockSize)) {
|
329 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_BLOCK_SIZE, 'A valid "hash.blockSize" number must be provided for ' +
|
330 | ("hash algorithm " + options.hash.algorithm + "."));
|
331 | }
|
332 | var dkLen = getNumberField(options.hash, 'derivedKeyLength');
|
333 | if (isNaN(dkLen)) {
|
334 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_DERIVED_KEY_LENGTH, 'A valid "hash.derivedKeyLength" number must be provided for ' +
|
335 | ("hash algorithm " + options.hash.algorithm + "."));
|
336 | }
|
337 | populatedOptions = {
|
338 | hashAlgorithm: options.hash.algorithm,
|
339 | cpuMemCost: cpuMemCost,
|
340 | parallelization: parallelization,
|
341 | blockSize: blockSize,
|
342 | dkLen: dkLen,
|
343 | };
|
344 | break;
|
345 | }
|
346 | default:
|
347 | throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_HASH_ALGORITHM, "Unsupported hash algorithm provider \"" + options.hash.algorithm + "\".");
|
348 | }
|
349 | return populatedOptions;
|
350 | };
|
351 | |
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 | UserImportBuilder.prototype.populateUsers = function (users, userValidator) {
|
362 | var _this = this;
|
363 | var populatedUsers = [];
|
364 | users.forEach(function (user, index) {
|
365 | try {
|
366 | var result = populateUploadAccountUser(user, userValidator);
|
367 | if (typeof result.passwordHash !== 'undefined') {
|
368 | _this.requiresHashOptions = true;
|
369 | }
|
370 |
|
371 | populatedUsers.push(result);
|
372 |
|
373 | _this.indexMap[populatedUsers.length - 1] = index;
|
374 | }
|
375 | catch (error) {
|
376 |
|
377 | _this.userImportResultErrors.push({
|
378 | index: index,
|
379 | error: error,
|
380 | });
|
381 | }
|
382 | });
|
383 | return populatedUsers;
|
384 | };
|
385 | return UserImportBuilder;
|
386 | }());
|
387 | exports.UserImportBuilder = UserImportBuilder;
|