credential-internal.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. /*! firebase-admin v12.1.1 */
  2. "use strict";
  3. /*!
  4. * @license
  5. * Copyright 2020 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.getApplicationDefault = exports.isApplicationDefault = exports.ImpersonatedServiceAccountCredential = exports.RefreshTokenCredential = exports.ComputeEngineCredential = exports.ServiceAccountCredential = void 0;
  21. const fs = require("fs");
  22. const os = require("os");
  23. const path = require("path");
  24. const error_1 = require("../utils/error");
  25. const api_request_1 = require("../utils/api-request");
  26. const util = require("../utils/validator");
  27. const GOOGLE_TOKEN_AUDIENCE = 'https://accounts.google.com/o/oauth2/token';
  28. const GOOGLE_AUTH_TOKEN_HOST = 'accounts.google.com';
  29. const GOOGLE_AUTH_TOKEN_PATH = '/o/oauth2/token';
  30. // NOTE: the Google Metadata Service uses HTTP over a vlan
  31. const GOOGLE_METADATA_SERVICE_HOST = 'metadata.google.internal';
  32. const GOOGLE_METADATA_SERVICE_TOKEN_PATH = '/computeMetadata/v1/instance/service-accounts/default/token';
  33. const GOOGLE_METADATA_SERVICE_IDENTITY_PATH = '/computeMetadata/v1/instance/service-accounts/default/identity';
  34. const GOOGLE_METADATA_SERVICE_PROJECT_ID_PATH = '/computeMetadata/v1/project/project-id';
  35. const GOOGLE_METADATA_SERVICE_ACCOUNT_ID_PATH = '/computeMetadata/v1/instance/service-accounts/default/email';
  36. const configDir = (() => {
  37. // Windows has a dedicated low-rights location for apps at ~/Application Data
  38. const sys = os.platform();
  39. if (sys && sys.length >= 3 && sys.substring(0, 3).toLowerCase() === 'win') {
  40. return process.env.APPDATA;
  41. }
  42. // On *nix the gcloud cli creates a . dir.
  43. return process.env.HOME && path.resolve(process.env.HOME, '.config');
  44. })();
  45. const GCLOUD_CREDENTIAL_SUFFIX = 'gcloud/application_default_credentials.json';
  46. const GCLOUD_CREDENTIAL_PATH = configDir && path.resolve(configDir, GCLOUD_CREDENTIAL_SUFFIX);
  47. const REFRESH_TOKEN_HOST = 'www.googleapis.com';
  48. const REFRESH_TOKEN_PATH = '/oauth2/v4/token';
  49. const ONE_HOUR_IN_SECONDS = 60 * 60;
  50. const JWT_ALGORITHM = 'RS256';
  51. /**
  52. * Implementation of Credential that uses a service account.
  53. */
  54. class ServiceAccountCredential {
  55. /**
  56. * Creates a new ServiceAccountCredential from the given parameters.
  57. *
  58. * @param serviceAccountPathOrObject - Service account json object or path to a service account json file.
  59. * @param httpAgent - Optional http.Agent to use when calling the remote token server.
  60. * @param implicit - An optinal boolean indicating whether this credential was implicitly discovered from the
  61. * environment, as opposed to being explicitly specified by the developer.
  62. *
  63. * @constructor
  64. */
  65. constructor(serviceAccountPathOrObject, httpAgent, implicit = false) {
  66. this.httpAgent = httpAgent;
  67. this.implicit = implicit;
  68. const serviceAccount = (typeof serviceAccountPathOrObject === 'string') ?
  69. ServiceAccount.fromPath(serviceAccountPathOrObject)
  70. : new ServiceAccount(serviceAccountPathOrObject);
  71. this.projectId = serviceAccount.projectId;
  72. this.privateKey = serviceAccount.privateKey;
  73. this.clientEmail = serviceAccount.clientEmail;
  74. this.httpClient = new api_request_1.HttpClient();
  75. }
  76. getAccessToken() {
  77. const token = this.createAuthJwt_();
  78. const postData = 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3A' +
  79. 'grant-type%3Ajwt-bearer&assertion=' + token;
  80. const request = {
  81. method: 'POST',
  82. url: `https://${GOOGLE_AUTH_TOKEN_HOST}${GOOGLE_AUTH_TOKEN_PATH}`,
  83. headers: {
  84. 'Content-Type': 'application/x-www-form-urlencoded',
  85. },
  86. data: postData,
  87. httpAgent: this.httpAgent,
  88. };
  89. return requestAccessToken(this.httpClient, request);
  90. }
  91. // eslint-disable-next-line @typescript-eslint/naming-convention
  92. createAuthJwt_() {
  93. const claims = {
  94. scope: [
  95. 'https://www.googleapis.com/auth/cloud-platform',
  96. 'https://www.googleapis.com/auth/firebase.database',
  97. 'https://www.googleapis.com/auth/firebase.messaging',
  98. 'https://www.googleapis.com/auth/identitytoolkit',
  99. 'https://www.googleapis.com/auth/userinfo.email',
  100. ].join(' '),
  101. };
  102. // eslint-disable-next-line @typescript-eslint/no-var-requires
  103. const jwt = require('jsonwebtoken');
  104. // This method is actually synchronous so we can capture and return the buffer.
  105. return jwt.sign(claims, this.privateKey, {
  106. audience: GOOGLE_TOKEN_AUDIENCE,
  107. expiresIn: ONE_HOUR_IN_SECONDS,
  108. issuer: this.clientEmail,
  109. algorithm: JWT_ALGORITHM,
  110. });
  111. }
  112. }
  113. exports.ServiceAccountCredential = ServiceAccountCredential;
  114. /**
  115. * A struct containing the properties necessary to use service account JSON credentials.
  116. */
  117. class ServiceAccount {
  118. static fromPath(filePath) {
  119. try {
  120. return new ServiceAccount(JSON.parse(fs.readFileSync(filePath, 'utf8')));
  121. }
  122. catch (error) {
  123. // Throw a nicely formed error message if the file contents cannot be parsed
  124. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Failed to parse service account json file: ' + error);
  125. }
  126. }
  127. constructor(json) {
  128. if (!util.isNonNullObject(json)) {
  129. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Service account must be an object.');
  130. }
  131. copyAttr(this, json, 'projectId', 'project_id');
  132. copyAttr(this, json, 'privateKey', 'private_key');
  133. copyAttr(this, json, 'clientEmail', 'client_email');
  134. let errorMessage;
  135. if (!util.isNonEmptyString(this.projectId)) {
  136. errorMessage = 'Service account object must contain a string "project_id" property.';
  137. }
  138. else if (!util.isNonEmptyString(this.privateKey)) {
  139. errorMessage = 'Service account object must contain a string "private_key" property.';
  140. }
  141. else if (!util.isNonEmptyString(this.clientEmail)) {
  142. errorMessage = 'Service account object must contain a string "client_email" property.';
  143. }
  144. if (typeof errorMessage !== 'undefined') {
  145. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, errorMessage);
  146. }
  147. // eslint-disable-next-line @typescript-eslint/no-var-requires
  148. const forge = require('node-forge');
  149. try {
  150. forge.pki.privateKeyFromPem(this.privateKey);
  151. }
  152. catch (error) {
  153. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Failed to parse private key: ' + error);
  154. }
  155. }
  156. }
  157. /**
  158. * Implementation of Credential that gets access tokens from the metadata service available
  159. * in the Google Cloud Platform. This authenticates the process as the default service account
  160. * of an App Engine instance or Google Compute Engine machine.
  161. */
  162. class ComputeEngineCredential {
  163. constructor(httpAgent) {
  164. this.httpClient = new api_request_1.HttpClient();
  165. this.httpAgent = httpAgent;
  166. }
  167. getAccessToken() {
  168. const request = this.buildRequest(GOOGLE_METADATA_SERVICE_TOKEN_PATH);
  169. return requestAccessToken(this.httpClient, request);
  170. }
  171. /**
  172. * getIDToken returns a OIDC token from the compute metadata service
  173. * that can be used to make authenticated calls to audience
  174. * @param audience the URL the returned ID token will be used to call.
  175. */
  176. getIDToken(audience) {
  177. const request = this.buildRequest(`${GOOGLE_METADATA_SERVICE_IDENTITY_PATH}?audience=${audience}`);
  178. return requestIDToken(this.httpClient, request);
  179. }
  180. getProjectId() {
  181. if (this.projectId) {
  182. return Promise.resolve(this.projectId);
  183. }
  184. const request = this.buildRequest(GOOGLE_METADATA_SERVICE_PROJECT_ID_PATH);
  185. return this.httpClient.send(request)
  186. .then((resp) => {
  187. this.projectId = resp.text;
  188. return this.projectId;
  189. })
  190. .catch((err) => {
  191. const detail = (err instanceof api_request_1.HttpError) ? getDetailFromResponse(err.response) : err.message;
  192. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, `Failed to determine project ID: ${detail}`);
  193. });
  194. }
  195. getServiceAccountEmail() {
  196. if (this.accountId) {
  197. return Promise.resolve(this.accountId);
  198. }
  199. const request = this.buildRequest(GOOGLE_METADATA_SERVICE_ACCOUNT_ID_PATH);
  200. return this.httpClient.send(request)
  201. .then((resp) => {
  202. this.accountId = resp.text;
  203. return this.accountId;
  204. })
  205. .catch((err) => {
  206. const detail = (err instanceof api_request_1.HttpError) ? getDetailFromResponse(err.response) : err.message;
  207. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, `Failed to determine service account email: ${detail}`);
  208. });
  209. }
  210. buildRequest(urlPath) {
  211. return {
  212. method: 'GET',
  213. url: `http://${GOOGLE_METADATA_SERVICE_HOST}${urlPath}`,
  214. headers: {
  215. 'Metadata-Flavor': 'Google',
  216. },
  217. httpAgent: this.httpAgent,
  218. };
  219. }
  220. }
  221. exports.ComputeEngineCredential = ComputeEngineCredential;
  222. /**
  223. * Implementation of Credential that gets access tokens from refresh tokens.
  224. */
  225. class RefreshTokenCredential {
  226. /**
  227. * Creates a new RefreshTokenCredential from the given parameters.
  228. *
  229. * @param refreshTokenPathOrObject - Refresh token json object or path to a refresh token
  230. * (user credentials) json file.
  231. * @param httpAgent - Optional http.Agent to use when calling the remote token server.
  232. * @param implicit - An optinal boolean indicating whether this credential was implicitly
  233. * discovered from the environment, as opposed to being explicitly specified by the developer.
  234. *
  235. * @constructor
  236. */
  237. constructor(refreshTokenPathOrObject, httpAgent, implicit = false) {
  238. this.httpAgent = httpAgent;
  239. this.implicit = implicit;
  240. this.refreshToken = (typeof refreshTokenPathOrObject === 'string') ?
  241. RefreshToken.fromPath(refreshTokenPathOrObject)
  242. : new RefreshToken(refreshTokenPathOrObject);
  243. this.httpClient = new api_request_1.HttpClient();
  244. }
  245. getAccessToken() {
  246. const postData = 'client_id=' + this.refreshToken.clientId + '&' +
  247. 'client_secret=' + this.refreshToken.clientSecret + '&' +
  248. 'refresh_token=' + this.refreshToken.refreshToken + '&' +
  249. 'grant_type=refresh_token';
  250. const request = {
  251. method: 'POST',
  252. url: `https://${REFRESH_TOKEN_HOST}${REFRESH_TOKEN_PATH}`,
  253. headers: {
  254. 'Content-Type': 'application/x-www-form-urlencoded',
  255. },
  256. data: postData,
  257. httpAgent: this.httpAgent,
  258. };
  259. return requestAccessToken(this.httpClient, request);
  260. }
  261. }
  262. exports.RefreshTokenCredential = RefreshTokenCredential;
  263. class RefreshToken {
  264. /*
  265. * Tries to load a RefreshToken from a path. Throws if the path doesn't exist or the
  266. * data at the path is invalid.
  267. */
  268. static fromPath(filePath) {
  269. try {
  270. return new RefreshToken(JSON.parse(fs.readFileSync(filePath, 'utf8')));
  271. }
  272. catch (error) {
  273. // Throw a nicely formed error message if the file contents cannot be parsed
  274. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Failed to parse refresh token file: ' + error);
  275. }
  276. }
  277. constructor(json) {
  278. copyAttr(this, json, 'clientId', 'client_id');
  279. copyAttr(this, json, 'clientSecret', 'client_secret');
  280. copyAttr(this, json, 'refreshToken', 'refresh_token');
  281. copyAttr(this, json, 'type', 'type');
  282. let errorMessage;
  283. if (!util.isNonEmptyString(this.clientId)) {
  284. errorMessage = 'Refresh token must contain a "client_id" property.';
  285. }
  286. else if (!util.isNonEmptyString(this.clientSecret)) {
  287. errorMessage = 'Refresh token must contain a "client_secret" property.';
  288. }
  289. else if (!util.isNonEmptyString(this.refreshToken)) {
  290. errorMessage = 'Refresh token must contain a "refresh_token" property.';
  291. }
  292. else if (!util.isNonEmptyString(this.type)) {
  293. errorMessage = 'Refresh token must contain a "type" property.';
  294. }
  295. if (typeof errorMessage !== 'undefined') {
  296. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, errorMessage);
  297. }
  298. }
  299. }
  300. /**
  301. * Implementation of Credential that uses impersonated service account.
  302. */
  303. class ImpersonatedServiceAccountCredential {
  304. /**
  305. * Creates a new ImpersonatedServiceAccountCredential from the given parameters.
  306. *
  307. * @param impersonatedServiceAccountPathOrObject - Impersonated Service account json object or
  308. * path to a service account json file.
  309. * @param httpAgent - Optional http.Agent to use when calling the remote token server.
  310. * @param implicit - An optional boolean indicating whether this credential was implicitly
  311. * discovered from the environment, as opposed to being explicitly specified by the developer.
  312. *
  313. * @constructor
  314. */
  315. constructor(impersonatedServiceAccountPathOrObject, httpAgent, implicit = false) {
  316. this.httpAgent = httpAgent;
  317. this.implicit = implicit;
  318. this.impersonatedServiceAccount = (typeof impersonatedServiceAccountPathOrObject === 'string') ?
  319. ImpersonatedServiceAccount.fromPath(impersonatedServiceAccountPathOrObject)
  320. : new ImpersonatedServiceAccount(impersonatedServiceAccountPathOrObject);
  321. this.httpClient = new api_request_1.HttpClient();
  322. }
  323. getAccessToken() {
  324. const postData = 'client_id=' + this.impersonatedServiceAccount.clientId + '&' +
  325. 'client_secret=' + this.impersonatedServiceAccount.clientSecret + '&' +
  326. 'refresh_token=' + this.impersonatedServiceAccount.refreshToken + '&' +
  327. 'grant_type=refresh_token';
  328. const request = {
  329. method: 'POST',
  330. url: `https://${REFRESH_TOKEN_HOST}${REFRESH_TOKEN_PATH}`,
  331. headers: {
  332. 'Content-Type': 'application/x-www-form-urlencoded',
  333. },
  334. data: postData,
  335. httpAgent: this.httpAgent,
  336. };
  337. return requestAccessToken(this.httpClient, request);
  338. }
  339. }
  340. exports.ImpersonatedServiceAccountCredential = ImpersonatedServiceAccountCredential;
  341. /**
  342. * A struct containing the properties necessary to use impersonated service account JSON credentials.
  343. */
  344. class ImpersonatedServiceAccount {
  345. /*
  346. * Tries to load a ImpersonatedServiceAccount from a path. Throws if the path doesn't exist or the
  347. * data at the path is invalid.
  348. */
  349. static fromPath(filePath) {
  350. try {
  351. return new ImpersonatedServiceAccount(JSON.parse(fs.readFileSync(filePath, 'utf8')));
  352. }
  353. catch (error) {
  354. // Throw a nicely formed error message if the file contents cannot be parsed
  355. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Failed to parse impersonated service account file: ' + error);
  356. }
  357. }
  358. constructor(json) {
  359. const sourceCredentials = json['source_credentials'];
  360. if (sourceCredentials) {
  361. copyAttr(this, sourceCredentials, 'clientId', 'client_id');
  362. copyAttr(this, sourceCredentials, 'clientSecret', 'client_secret');
  363. copyAttr(this, sourceCredentials, 'refreshToken', 'refresh_token');
  364. copyAttr(this, sourceCredentials, 'type', 'type');
  365. }
  366. let errorMessage;
  367. if (!util.isNonEmptyString(this.clientId)) {
  368. errorMessage = 'Impersonated Service Account must contain a "source_credentials.client_id" property.';
  369. }
  370. else if (!util.isNonEmptyString(this.clientSecret)) {
  371. errorMessage = 'Impersonated Service Account must contain a "source_credentials.client_secret" property.';
  372. }
  373. else if (!util.isNonEmptyString(this.refreshToken)) {
  374. errorMessage = 'Impersonated Service Account must contain a "source_credentials.refresh_token" property.';
  375. }
  376. else if (!util.isNonEmptyString(this.type)) {
  377. errorMessage = 'Impersonated Service Account must contain a "source_credentials.type" property.';
  378. }
  379. if (typeof errorMessage !== 'undefined') {
  380. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, errorMessage);
  381. }
  382. }
  383. }
  384. /**
  385. * Checks if the given credential was loaded via the application default credentials mechanism. This
  386. * includes all ComputeEngineCredential instances, and the ServiceAccountCredential and RefreshTokenCredential
  387. * instances that were loaded from well-known files or environment variables, rather than being explicitly
  388. * instantiated.
  389. *
  390. * @param credential - The credential instance to check.
  391. */
  392. function isApplicationDefault(credential) {
  393. return credential instanceof ComputeEngineCredential ||
  394. (credential instanceof ServiceAccountCredential && credential.implicit) ||
  395. (credential instanceof RefreshTokenCredential && credential.implicit) ||
  396. (credential instanceof ImpersonatedServiceAccountCredential && credential.implicit);
  397. }
  398. exports.isApplicationDefault = isApplicationDefault;
  399. function getApplicationDefault(httpAgent) {
  400. if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
  401. return credentialFromFile(process.env.GOOGLE_APPLICATION_CREDENTIALS, httpAgent, false);
  402. }
  403. // It is OK to not have this file. If it is present, it must be valid.
  404. if (GCLOUD_CREDENTIAL_PATH) {
  405. const credential = credentialFromFile(GCLOUD_CREDENTIAL_PATH, httpAgent, true);
  406. if (credential)
  407. return credential;
  408. }
  409. return new ComputeEngineCredential(httpAgent);
  410. }
  411. exports.getApplicationDefault = getApplicationDefault;
  412. /**
  413. * Copies the specified property from one object to another.
  414. *
  415. * If no property exists by the given "key", looks for a property identified by "alt", and copies it instead.
  416. * This can be used to implement behaviors such as "copy property myKey or my_key".
  417. *
  418. * @param to - Target object to copy the property into.
  419. * @param from - Source object to copy the property from.
  420. * @param key - Name of the property to copy.
  421. * @param alt - Alternative name of the property to copy.
  422. */
  423. function copyAttr(to, from, key, alt) {
  424. const tmp = from[key] || from[alt];
  425. if (typeof tmp !== 'undefined') {
  426. to[key] = tmp;
  427. }
  428. }
  429. /**
  430. * Obtain a new OAuth2 token by making a remote service call.
  431. */
  432. function requestAccessToken(client, request) {
  433. return client.send(request).then((resp) => {
  434. const json = resp.data;
  435. if (!json.access_token || !json.expires_in) {
  436. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, `Unexpected response while fetching access token: ${JSON.stringify(json)}`);
  437. }
  438. return json;
  439. }).catch((err) => {
  440. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, getErrorMessage(err));
  441. });
  442. }
  443. /**
  444. * Obtain a new OIDC token by making a remote service call.
  445. */
  446. function requestIDToken(client, request) {
  447. return client.send(request).then((resp) => {
  448. if (!resp.text) {
  449. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Unexpected response while fetching id token: response.text is undefined');
  450. }
  451. return resp.text;
  452. }).catch((err) => {
  453. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, getErrorMessage(err));
  454. });
  455. }
  456. /**
  457. * Constructs a human-readable error message from the given Error.
  458. */
  459. function getErrorMessage(err) {
  460. const detail = (err instanceof api_request_1.HttpError) ? getDetailFromResponse(err.response) : err.message;
  461. return `Error fetching access token: ${detail}`;
  462. }
  463. /**
  464. * Extracts details from the given HTTP error response, and returns a human-readable description. If
  465. * the response is JSON-formatted, looks up the error and error_description fields sent by the
  466. * Google Auth servers. Otherwise returns the entire response payload as the error detail.
  467. */
  468. function getDetailFromResponse(response) {
  469. if (response.isJson() && response.data.error) {
  470. const json = response.data;
  471. let detail = json.error;
  472. if (json.error_description) {
  473. detail += ' (' + json.error_description + ')';
  474. }
  475. return detail;
  476. }
  477. return response.text || 'Missing error payload';
  478. }
  479. function credentialFromFile(filePath, httpAgent, ignoreMissing) {
  480. const credentialsFile = readCredentialFile(filePath, ignoreMissing);
  481. if (typeof credentialsFile !== 'object' || credentialsFile === null) {
  482. if (ignoreMissing) {
  483. return null;
  484. }
  485. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Failed to parse contents of the credentials file as an object');
  486. }
  487. if (credentialsFile.type === 'service_account') {
  488. return new ServiceAccountCredential(credentialsFile, httpAgent, true);
  489. }
  490. if (credentialsFile.type === 'authorized_user') {
  491. return new RefreshTokenCredential(credentialsFile, httpAgent, true);
  492. }
  493. if (credentialsFile.type === 'impersonated_service_account') {
  494. return new ImpersonatedServiceAccountCredential(credentialsFile, httpAgent, true);
  495. }
  496. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Invalid contents in the credentials file');
  497. }
  498. function readCredentialFile(filePath, ignoreMissing) {
  499. let fileText;
  500. try {
  501. fileText = fs.readFileSync(filePath, 'utf8');
  502. }
  503. catch (error) {
  504. if (ignoreMissing) {
  505. return null;
  506. }
  507. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, `Failed to read credentials from file ${filePath}: ` + error);
  508. }
  509. try {
  510. return JSON.parse(fileText);
  511. }
  512. catch (error) {
  513. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Failed to parse contents of the credentials file as an object: ' + error);
  514. }
  515. }