diff --git a/src/utils/crypto-signer.ts b/src/utils/crypto-signer.ts index 3123cee209..f2b8bcbafd 100644 --- a/src/utils/crypto-signer.ts +++ b/src/utils/crypto-signer.ts @@ -22,6 +22,7 @@ import { AuthorizedHttpClient, HttpRequestConfig, HttpClient, RequestResponseErr import { Algorithm } from 'jsonwebtoken'; import { ErrorInfo } from '../utils/error'; +import * as utils from '../utils/index'; import * as validator from '../utils/validator'; const ALGORITHM_RS256: Algorithm = 'RS256' as const; @@ -105,22 +106,23 @@ export class IAMSigner implements CryptoSigner { private readonly httpClient: AuthorizedHttpClient; private serviceAccountId?: string; + private app?: App; - constructor(httpClient: AuthorizedHttpClient, serviceAccountId?: string) { + constructor(httpClient: AuthorizedHttpClient, app?: App) { if (!httpClient) { throw new CryptoSignerError({ code: CryptoSignerErrorCode.INVALID_ARGUMENT, message: 'INTERNAL ASSERT: Must provide a HTTP client to initialize IAMSigner.', }); } - if (typeof serviceAccountId !== 'undefined' && !validator.isNonEmptyString(serviceAccountId)) { + if (app && (typeof app !== 'object' || app === null || !('options' in app))) { throw new CryptoSignerError({ code: CryptoSignerErrorCode.INVALID_ARGUMENT, - message: 'INTERNAL ASSERT: Service account ID must be undefined or a non-empty string.', + message: 'INTERNAL ASSERT: Must provide a valid Firebase app instance.', }); } this.httpClient = httpClient; - this.serviceAccountId = serviceAccountId; + this.app = app; } /** @@ -152,9 +154,16 @@ export class IAMSigner implements CryptoSigner { /** * @inheritDoc */ - public getAccountId(): Promise { + public async getAccountId(): Promise { if (validator.isNonEmptyString(this.serviceAccountId)) { - return Promise.resolve(this.serviceAccountId); + return this.serviceAccountId; + } + if (this.app) { + const accountId = await utils.findServiceAccountEmail(this.app!) + if (accountId) { + this.serviceAccountId = accountId; + return accountId; + } } const request: HttpRequestConfig = { method: 'GET', @@ -197,7 +206,7 @@ export function cryptoSignerFromApp(app: App): CryptoSigner { return new ServiceAccountSigner(credential); } - return new IAMSigner(new AuthorizedHttpClient(app as FirebaseApp), app.options.serviceAccountId); + return new IAMSigner(new AuthorizedHttpClient(app as FirebaseApp), app); } /** diff --git a/test/unit/utils/crypto-signer.spec.ts b/test/unit/utils/crypto-signer.spec.ts index 024be3ecbe..cf38160a2f 100644 --- a/test/unit/utils/crypto-signer.spec.ts +++ b/test/unit/utils/crypto-signer.spec.ts @@ -103,7 +103,7 @@ describe('CryptoSigner', () => { const input = Buffer.from('input'); const signRequest = { method: 'POST', - url: 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/test-service-account:signBlob', + url: 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/foo@project_id.iam.gserviceaccount.com:signBlob', headers: { Authorization: `Bearer ${mockAccessToken}`, 'X-Goog-Api-Client': getMetricsHeader() @@ -120,7 +120,7 @@ describe('CryptoSigner', () => { const expectedResult = utils.responseFrom(response); stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult); const requestHandler = new AuthorizedHttpClient(mockApp); - const signer = new IAMSigner(requestHandler, 'test-service-account'); + const signer = new IAMSigner(requestHandler, mockApp); return signer.sign(input).then((signature) => { expect(signature.toString('base64')).to.equal(response.signedBlob); expect(stub).to.have.been.calledOnce.and.calledWith(signRequest); @@ -136,7 +136,7 @@ describe('CryptoSigner', () => { }); stub = sinon.stub(HttpClient.prototype, 'send').rejects(expectedResult); const requestHandler = new AuthorizedHttpClient(mockApp); - const signer = new IAMSigner(requestHandler, 'test-service-account'); + const signer = new IAMSigner(requestHandler, mockApp); return signer.sign(input).catch((err) => { expect(err).to.be.instanceOf(CryptoSignerError); expect(err.message).to.equal('Server responded with status 500.'); @@ -145,9 +145,9 @@ describe('CryptoSigner', () => { }); }); - it('should return the explicitly specified service account', () => { - const signer = new IAMSigner(new AuthorizedHttpClient(mockApp), 'test-service-account'); - return signer.getAccountId().should.eventually.equal('test-service-account'); + it('should return the service account from the app', () => { + const signer = new IAMSigner(new AuthorizedHttpClient(mockApp), mockApp); + return signer.getAccountId().should.eventually.equal('foo@project_id.iam.gserviceaccount.com'); }); });