user-record.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. /*! firebase-admin v12.1.1 */
  2. "use strict";
  3. /*!
  4. * @license
  5. * Copyright 2017 Google Inc.
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. */
  19. Object.defineProperty(exports, "__esModule", { value: true });
  20. exports.UserRecord = exports.UserInfo = exports.UserMetadata = exports.MultiFactorSettings = exports.TotpMultiFactorInfo = exports.TotpInfo = exports.PhoneMultiFactorInfo = exports.MultiFactorInfo = void 0;
  21. const deep_copy_1 = require("../utils/deep-copy");
  22. const validator_1 = require("../utils/validator");
  23. const utils = require("../utils");
  24. const error_1 = require("../utils/error");
  25. /**
  26. * 'REDACTED', encoded as a base64 string.
  27. */
  28. const B64_REDACTED = Buffer.from('REDACTED').toString('base64');
  29. /**
  30. * Parses a time stamp string or number and returns the corresponding date if valid.
  31. *
  32. * @param time - The unix timestamp string or number in milliseconds.
  33. * @returns The corresponding date as a UTC string, if valid. Otherwise, null.
  34. */
  35. function parseDate(time) {
  36. try {
  37. const date = new Date(parseInt(time, 10));
  38. if (!isNaN(date.getTime())) {
  39. return date.toUTCString();
  40. }
  41. }
  42. catch (e) {
  43. // Do nothing. null will be returned.
  44. }
  45. return null;
  46. }
  47. var MultiFactorId;
  48. (function (MultiFactorId) {
  49. MultiFactorId["Phone"] = "phone";
  50. MultiFactorId["Totp"] = "totp";
  51. })(MultiFactorId || (MultiFactorId = {}));
  52. /**
  53. * Interface representing the common properties of a user-enrolled second factor.
  54. */
  55. class MultiFactorInfo {
  56. /**
  57. * Initializes the MultiFactorInfo associated subclass using the server side.
  58. * If no MultiFactorInfo is associated with the response, null is returned.
  59. *
  60. * @param response - The server side response.
  61. * @internal
  62. */
  63. static initMultiFactorInfo(response) {
  64. let multiFactorInfo = null;
  65. // PhoneMultiFactorInfo, TotpMultiFactorInfo currently available.
  66. try {
  67. if (response.phoneInfo !== undefined) {
  68. multiFactorInfo = new PhoneMultiFactorInfo(response);
  69. }
  70. else if (response.totpInfo !== undefined) {
  71. multiFactorInfo = new TotpMultiFactorInfo(response);
  72. }
  73. else {
  74. // Ignore the other SDK unsupported MFA factors to prevent blocking developers using the current SDK.
  75. }
  76. }
  77. catch (e) {
  78. // Ignore error.
  79. }
  80. return multiFactorInfo;
  81. }
  82. /**
  83. * Initializes the MultiFactorInfo object using the server side response.
  84. *
  85. * @param response - The server side response.
  86. * @constructor
  87. * @internal
  88. */
  89. constructor(response) {
  90. this.initFromServerResponse(response);
  91. }
  92. /**
  93. * Returns a JSON-serializable representation of this object.
  94. *
  95. * @returns A JSON-serializable representation of this object.
  96. */
  97. toJSON() {
  98. return {
  99. uid: this.uid,
  100. displayName: this.displayName,
  101. factorId: this.factorId,
  102. enrollmentTime: this.enrollmentTime,
  103. };
  104. }
  105. /**
  106. * Initializes the MultiFactorInfo object using the provided server response.
  107. *
  108. * @param response - The server side response.
  109. */
  110. initFromServerResponse(response) {
  111. const factorId = response && this.getFactorId(response);
  112. if (!factorId || !response || !response.mfaEnrollmentId) {
  113. throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid multi-factor info response');
  114. }
  115. utils.addReadonlyGetter(this, 'uid', response.mfaEnrollmentId);
  116. utils.addReadonlyGetter(this, 'factorId', factorId);
  117. utils.addReadonlyGetter(this, 'displayName', response.displayName);
  118. // Encoded using [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format.
  119. // For example, "2017-01-15T01:30:15.01Z".
  120. // This can be parsed directly via Date constructor.
  121. // This can be computed using Data.prototype.toISOString.
  122. if (response.enrolledAt) {
  123. utils.addReadonlyGetter(this, 'enrollmentTime', new Date(response.enrolledAt).toUTCString());
  124. }
  125. else {
  126. utils.addReadonlyGetter(this, 'enrollmentTime', null);
  127. }
  128. }
  129. }
  130. exports.MultiFactorInfo = MultiFactorInfo;
  131. /**
  132. * Interface representing a phone specific user-enrolled second factor.
  133. */
  134. class PhoneMultiFactorInfo extends MultiFactorInfo {
  135. /**
  136. * Initializes the PhoneMultiFactorInfo object using the server side response.
  137. *
  138. * @param response - The server side response.
  139. * @constructor
  140. * @internal
  141. */
  142. constructor(response) {
  143. super(response);
  144. utils.addReadonlyGetter(this, 'phoneNumber', response.phoneInfo);
  145. }
  146. /**
  147. * {@inheritdoc MultiFactorInfo.toJSON}
  148. */
  149. toJSON() {
  150. return Object.assign(super.toJSON(), {
  151. phoneNumber: this.phoneNumber,
  152. });
  153. }
  154. /**
  155. * Returns the factor ID based on the response provided.
  156. *
  157. * @param response - The server side response.
  158. * @returns The multi-factor ID associated with the provided response. If the response is
  159. * not associated with any known multi-factor ID, null is returned.
  160. *
  161. * @internal
  162. */
  163. getFactorId(response) {
  164. return (response && response.phoneInfo) ? MultiFactorId.Phone : null;
  165. }
  166. }
  167. exports.PhoneMultiFactorInfo = PhoneMultiFactorInfo;
  168. /**
  169. * `TotpInfo` struct associated with a second factor
  170. */
  171. class TotpInfo {
  172. }
  173. exports.TotpInfo = TotpInfo;
  174. /**
  175. * Interface representing a TOTP specific user-enrolled second factor.
  176. */
  177. class TotpMultiFactorInfo extends MultiFactorInfo {
  178. /**
  179. * Initializes the `TotpMultiFactorInfo` object using the server side response.
  180. *
  181. * @param response - The server side response.
  182. * @constructor
  183. * @internal
  184. */
  185. constructor(response) {
  186. super(response);
  187. utils.addReadonlyGetter(this, 'totpInfo', response.totpInfo);
  188. }
  189. /**
  190. * {@inheritdoc MultiFactorInfo.toJSON}
  191. */
  192. toJSON() {
  193. return Object.assign(super.toJSON(), {
  194. totpInfo: this.totpInfo,
  195. });
  196. }
  197. /**
  198. * Returns the factor ID based on the response provided.
  199. *
  200. * @param response - The server side response.
  201. * @returns The multi-factor ID associated with the provided response. If the response is
  202. * not associated with any known multi-factor ID, `null` is returned.
  203. *
  204. * @internal
  205. */
  206. getFactorId(response) {
  207. return (response && response.totpInfo) ? MultiFactorId.Totp : null;
  208. }
  209. }
  210. exports.TotpMultiFactorInfo = TotpMultiFactorInfo;
  211. /**
  212. * The multi-factor related user settings.
  213. */
  214. class MultiFactorSettings {
  215. /**
  216. * Initializes the `MultiFactor` object using the server side or JWT format response.
  217. *
  218. * @param response - The server side response.
  219. * @constructor
  220. * @internal
  221. */
  222. constructor(response) {
  223. const parsedEnrolledFactors = [];
  224. if (!(0, validator_1.isNonNullObject)(response)) {
  225. throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid multi-factor response');
  226. }
  227. else if (response.mfaInfo) {
  228. response.mfaInfo.forEach((factorResponse) => {
  229. const multiFactorInfo = MultiFactorInfo.initMultiFactorInfo(factorResponse);
  230. if (multiFactorInfo) {
  231. parsedEnrolledFactors.push(multiFactorInfo);
  232. }
  233. });
  234. }
  235. // Make enrolled factors immutable.
  236. utils.addReadonlyGetter(this, 'enrolledFactors', Object.freeze(parsedEnrolledFactors));
  237. }
  238. /**
  239. * Returns a JSON-serializable representation of this multi-factor object.
  240. *
  241. * @returns A JSON-serializable representation of this multi-factor object.
  242. */
  243. toJSON() {
  244. return {
  245. enrolledFactors: this.enrolledFactors.map((info) => info.toJSON()),
  246. };
  247. }
  248. }
  249. exports.MultiFactorSettings = MultiFactorSettings;
  250. /**
  251. * Represents a user's metadata.
  252. */
  253. class UserMetadata {
  254. /**
  255. * @param response - The server side response returned from the `getAccountInfo`
  256. * endpoint.
  257. * @constructor
  258. * @internal
  259. */
  260. constructor(response) {
  261. // Creation date should always be available but due to some backend bugs there
  262. // were cases in the past where users did not have creation date properly set.
  263. // This included legacy Firebase migrating project users and some anonymous users.
  264. // These bugs have already been addressed since then.
  265. utils.addReadonlyGetter(this, 'creationTime', parseDate(response.createdAt));
  266. utils.addReadonlyGetter(this, 'lastSignInTime', parseDate(response.lastLoginAt));
  267. const lastRefreshAt = response.lastRefreshAt ? new Date(response.lastRefreshAt).toUTCString() : null;
  268. utils.addReadonlyGetter(this, 'lastRefreshTime', lastRefreshAt);
  269. }
  270. /**
  271. * Returns a JSON-serializable representation of this object.
  272. *
  273. * @returns A JSON-serializable representation of this object.
  274. */
  275. toJSON() {
  276. return {
  277. lastSignInTime: this.lastSignInTime,
  278. creationTime: this.creationTime,
  279. lastRefreshTime: this.lastRefreshTime,
  280. };
  281. }
  282. }
  283. exports.UserMetadata = UserMetadata;
  284. /**
  285. * Represents a user's info from a third-party identity provider
  286. * such as Google or Facebook.
  287. */
  288. class UserInfo {
  289. /**
  290. * @param response - The server side response returned from the `getAccountInfo`
  291. * endpoint.
  292. * @constructor
  293. * @internal
  294. */
  295. constructor(response) {
  296. // Provider user id and provider id are required.
  297. if (!response.rawId || !response.providerId) {
  298. throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid user info response');
  299. }
  300. utils.addReadonlyGetter(this, 'uid', response.rawId);
  301. utils.addReadonlyGetter(this, 'displayName', response.displayName);
  302. utils.addReadonlyGetter(this, 'email', response.email);
  303. utils.addReadonlyGetter(this, 'photoURL', response.photoUrl);
  304. utils.addReadonlyGetter(this, 'providerId', response.providerId);
  305. utils.addReadonlyGetter(this, 'phoneNumber', response.phoneNumber);
  306. }
  307. /**
  308. * Returns a JSON-serializable representation of this object.
  309. *
  310. * @returns A JSON-serializable representation of this object.
  311. */
  312. toJSON() {
  313. return {
  314. uid: this.uid,
  315. displayName: this.displayName,
  316. email: this.email,
  317. photoURL: this.photoURL,
  318. providerId: this.providerId,
  319. phoneNumber: this.phoneNumber,
  320. };
  321. }
  322. }
  323. exports.UserInfo = UserInfo;
  324. /**
  325. * Represents a user.
  326. */
  327. class UserRecord {
  328. /**
  329. * @param response - The server side response returned from the getAccountInfo
  330. * endpoint.
  331. * @constructor
  332. * @internal
  333. */
  334. constructor(response) {
  335. // The Firebase user id is required.
  336. if (!response.localId) {
  337. throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid user response');
  338. }
  339. utils.addReadonlyGetter(this, 'uid', response.localId);
  340. utils.addReadonlyGetter(this, 'email', response.email);
  341. utils.addReadonlyGetter(this, 'emailVerified', !!response.emailVerified);
  342. utils.addReadonlyGetter(this, 'displayName', response.displayName);
  343. utils.addReadonlyGetter(this, 'photoURL', response.photoUrl);
  344. utils.addReadonlyGetter(this, 'phoneNumber', response.phoneNumber);
  345. // If disabled is not provided, the account is enabled by default.
  346. utils.addReadonlyGetter(this, 'disabled', response.disabled || false);
  347. utils.addReadonlyGetter(this, 'metadata', new UserMetadata(response));
  348. const providerData = [];
  349. for (const entry of (response.providerUserInfo || [])) {
  350. providerData.push(new UserInfo(entry));
  351. }
  352. utils.addReadonlyGetter(this, 'providerData', providerData);
  353. // If the password hash is redacted (probably due to missing permissions)
  354. // then clear it out, similar to how the salt is returned. (Otherwise, it
  355. // *looks* like a b64-encoded hash is present, which is confusing.)
  356. if (response.passwordHash === B64_REDACTED) {
  357. utils.addReadonlyGetter(this, 'passwordHash', undefined);
  358. }
  359. else {
  360. utils.addReadonlyGetter(this, 'passwordHash', response.passwordHash);
  361. }
  362. utils.addReadonlyGetter(this, 'passwordSalt', response.salt);
  363. if (response.customAttributes) {
  364. utils.addReadonlyGetter(this, 'customClaims', JSON.parse(response.customAttributes));
  365. }
  366. let validAfterTime = null;
  367. // Convert validSince first to UTC milliseconds and then to UTC date string.
  368. if (typeof response.validSince !== 'undefined') {
  369. validAfterTime = parseDate(parseInt(response.validSince, 10) * 1000);
  370. }
  371. utils.addReadonlyGetter(this, 'tokensValidAfterTime', validAfterTime || undefined);
  372. utils.addReadonlyGetter(this, 'tenantId', response.tenantId);
  373. const multiFactor = new MultiFactorSettings(response);
  374. if (multiFactor.enrolledFactors.length > 0) {
  375. utils.addReadonlyGetter(this, 'multiFactor', multiFactor);
  376. }
  377. }
  378. /**
  379. * Returns a JSON-serializable representation of this object.
  380. *
  381. * @returns A JSON-serializable representation of this object.
  382. */
  383. toJSON() {
  384. const json = {
  385. uid: this.uid,
  386. email: this.email,
  387. emailVerified: this.emailVerified,
  388. displayName: this.displayName,
  389. photoURL: this.photoURL,
  390. phoneNumber: this.phoneNumber,
  391. disabled: this.disabled,
  392. // Convert metadata to json.
  393. metadata: this.metadata.toJSON(),
  394. passwordHash: this.passwordHash,
  395. passwordSalt: this.passwordSalt,
  396. customClaims: (0, deep_copy_1.deepCopy)(this.customClaims),
  397. tokensValidAfterTime: this.tokensValidAfterTime,
  398. tenantId: this.tenantId,
  399. };
  400. if (this.multiFactor) {
  401. json.multiFactor = this.multiFactor.toJSON();
  402. }
  403. json.providerData = [];
  404. for (const entry of this.providerData) {
  405. // Convert each provider data to json.
  406. json.providerData.push(entry.toJSON());
  407. }
  408. return json;
  409. }
  410. }
  411. exports.UserRecord = UserRecord;