Skip to content

Commit 30877e1

Browse files
committed
fix credential parsing based on code review
1 parent 10deeaa commit 30877e1

File tree

2 files changed

+58
-56
lines changed

2 files changed

+58
-56
lines changed

src/auth/credential.ts

Lines changed: 56 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -355,63 +355,19 @@ export class ApplicationDefaultCredential implements Credential {
355355
private credential_: Credential;
356356

357357
constructor(httpAgent?: Agent) {
358-
359-
if (typeof process.env.GOOGLE_APPLICATION_CREDENTIALS === 'string' &&
360-
!fs.existsSync(process.env.GOOGLE_APPLICATION_CREDENTIALS)) {
361-
throw new Error(
362-
"env variable 'GOOGLE_APPLICATION_CREDENTIAL' set to invalid path: " +
363-
process.env.GOOGLE_APPLICATION_CREDENTIALS);
358+
if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
359+
this.credential_ = credentialFromFile(process.env.GOOGLE_APPLICATION_CREDENTIALS, httpAgent);
360+
return;
364361
}
365362

366-
let lastError: Error;
367-
const paths: string[] = process.env.GOOGLE_APPLICATION_CREDENTIALS ?
368-
[ process.env.GOOGLE_APPLICATION_CREDENTIALS ] : [];
369-
if (GCLOUD_CREDENTIAL_PATH !== process.env.GOOGLE_APPLICATION_CREDENTIALS) {
370-
paths.push(GCLOUD_CREDENTIAL_PATH);
371-
}
372-
paths.forEach((filePath) => {
373-
if (!this.credential_ && filePath && typeof filePath === 'string') {
374-
let fileText: string;
375-
try {
376-
fileText = fs.readFileSync(filePath, 'utf8');
377-
} catch (error) {
378-
// ignore and return to allow default
379-
return;
380-
}
381-
try {
382-
const fileContents = JSON.parse(fileText);
383-
if (!fileContents.type) {
384-
lastError = new FirebaseAppError(
385-
AppErrorCodes.INVALID_CREDENTIAL, "Certificate file did not have 'type' parameter");
386-
} else {
387-
switch (fileContents.type) {
388-
case 'authorized_user':
389-
this.credential_ = new RefreshTokenCredential(new RefreshToken(fileContents), httpAgent);
390-
break;
391-
case 'service_account':
392-
this.credential_ = new CertCredential(new Certificate(fileContents), httpAgent);
393-
break;
394-
default:
395-
lastError = new FirebaseAppError(AppErrorCodes.INVALID_CREDENTIAL,
396-
`invalid certificate type: '${fileContents.type}'`);
397-
break;
398-
}
399-
}
400-
} catch (error) {
401-
// Throw a nicely formed error message if the file contents cannot be parsed
402-
lastError = new FirebaseAppError(
403-
AppErrorCodes.INVALID_CREDENTIAL,
404-
`Failed to parse certificate key file (${filePath}): ${error}`,
405-
);
406-
}
407-
}
408-
});
409-
if (lastError) {
410-
throw lastError;
411-
}
412-
if (!this.credential_) {
413-
this.credential_ = new MetadataServiceCredential(httpAgent);
363+
// It is OK to not have this file. If it is present, it must be valid.
364+
const refreshToken = RefreshToken.fromPath(GCLOUD_CREDENTIAL_PATH);
365+
if (refreshToken) {
366+
this.credential_ = new RefreshTokenCredential(refreshToken, httpAgent);
367+
return;
414368
}
369+
370+
this.credential_ = new MetadataServiceCredential(httpAgent);
415371
}
416372

417373
public getAccessToken(): Promise<GoogleOAuthAccessToken> {
@@ -427,3 +383,49 @@ export class ApplicationDefaultCredential implements Credential {
427383
return this.credential_;
428384
}
429385
}
386+
387+
function credentialFromFile(filePath: string, httpAgent?: Agent): Credential {
388+
const credentialsFile = readCredentialFile(filePath);
389+
if (typeof credentialsFile !== 'object') {
390+
throw new FirebaseAppError(
391+
AppErrorCodes.INVALID_CREDENTIAL,
392+
'Failed to parse contents of the credentials file as an object',
393+
);
394+
}
395+
if (credentialsFile.type === 'service_account') {
396+
return new CertCredential(credentialsFile, httpAgent);
397+
}
398+
if (credentialsFile.type === 'authorized_user') {
399+
return new RefreshTokenCredential(credentialsFile, httpAgent);
400+
}
401+
throw new FirebaseAppError(
402+
AppErrorCodes.INVALID_CREDENTIAL,
403+
'Invalid contents in the credentials file',
404+
);
405+
}
406+
407+
function readCredentialFile(filePath: string): {[key: string]: any} {
408+
if (typeof filePath !== 'string') {
409+
throw new FirebaseAppError(
410+
AppErrorCodes.INVALID_CREDENTIAL,
411+
'Failed to parse credentials file: TypeError: path must be a string',
412+
);
413+
}
414+
let fileText: string;
415+
try {
416+
fileText = fs.readFileSync(filePath, 'utf8');
417+
} catch (error) {
418+
throw new FirebaseAppError(
419+
AppErrorCodes.INVALID_CREDENTIAL,
420+
`Failed to read credentials from file ${filePath}: ` + error,
421+
);
422+
}
423+
try {
424+
return JSON.parse(fileText);
425+
} catch (error) {
426+
throw new FirebaseAppError(
427+
AppErrorCodes.INVALID_CREDENTIAL,
428+
'Failed to parse contents of the credentials file as an object: ' + error,
429+
);
430+
}
431+
}

test/unit/auth/credential.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,14 +359,14 @@ describe('Credential', () => {
359359
it('should throw error if type not specified on cert file', () => {
360360
fsStub = sinon.stub(fs, 'readFileSync').returns(JSON.stringify({}));
361361
expect(() => new ApplicationDefaultCredential())
362-
.to.throw(Error, "Certificate file did not have 'type' parameter");
362+
.to.throw(Error, 'Invalid contents in the credentials file');
363363
});
364364

365365
it('should throw error if type is unknown on cert file', () => {
366366
fsStub = sinon.stub(fs, 'readFileSync').returns(JSON.stringify({
367367
type: 'foo',
368368
}));
369-
expect(() => new ApplicationDefaultCredential()).to.throw(Error, "invalid certificate type: 'foo'");
369+
expect(() => new ApplicationDefaultCredential()).to.throw(Error, 'Invalid contents in the credentials file');
370370
});
371371

372372
it('should return a RefreshTokenCredential with gcloud login', () => {

0 commit comments

Comments
 (0)