google-sa-id-token
Version:
Fetch ID Token for Service Account when running in GCloud
107 lines • 4.29 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GoogleSaIdToken = void 0;
const gcp_metadata_1 = require("gcp-metadata");
const utils_1 = require("./utils");
class GoogleSaIdToken {
constructor({ serviceAccountEmail, tokenExpiryMargin, defaultAudience, logger, } = {}) {
this.tokenCache = {};
this.sa = serviceAccountEmail || 'default';
this.tokenExpiryMargin = tokenExpiryMargin || 2000;
this.defaultAudience = defaultAudience;
this.logger = logger;
}
noAudError() {
this.logger?.error?.('noAudError', {});
throw new Error(`audience is required, specify default audience in constructor or in method parameters`);
}
async fetchIdTokenNoCache(audience) {
this.logger?.info?.('fetchIdTokenNoCache.audience', {});
const aud = this.defaultAudience || audience;
if (!aud) {
this.noAudError();
}
const instanceOptions = {
property: `service-accounts/${this.sa}/identity?audience=${aud}`,
};
this.logger?.info?.('fetchIdTokenNoCache.fetchToken', { instanceOptions });
const token = await (0, gcp_metadata_1.instance)(instanceOptions);
this.logger?.info?.('fetchIdTokenNoCache.result', { token });
return {
raw: token,
payload: (0, utils_1.decodeSaToken)(token),
};
}
addFulfilHandler(aud, promise) {
promise
.then((value) => {
this.logger?.info?.('fulfilHandler.resolved', { value });
// 'token promise resolved, refresh status'
this.tokenCache[aud].fetchTokenStatus = 'fulfilled';
return value;
})
.catch((err) => {
this.logger?.error?.('fulfilHandler.rejected', { err });
// 'token promise rejected, refresh status'
this.tokenCache[aud].fetchTokenStatus = 'rejected';
});
return promise;
}
fetchIdTokenCached(aud, opts = {}) {
this.logger?.info?.('fetchIdTokenCached.called', { aud, opts });
const cache = this.tokenCache[aud];
if (opts.forceRefresh) {
this.logger?.info?.('fetchIdTokenCached.forceRefresh', {});
return (this.tokenCache[aud] = {
promise: this.addFulfilHandler(aud, this.fetchIdTokenNoCache(aud)),
fetchTokenStatus: 'pending',
});
}
this.logger?.info?.('fetchIdTokenCached.check-cache', { cache });
if (cache) {
return cache;
}
this.logger?.info?.('fetchIdTokenCached.cache-miss', {});
return (this.tokenCache[aud] = {
fetchTokenStatus: 'pending',
promise: this.addFulfilHandler(aud, this.fetchIdTokenNoCache(aud)),
});
}
async fetchIdTokenDecoded(audience) {
this.logger?.info?.('fetchIdTokenDecoded.called', { audience });
const aud = this.defaultAudience || audience;
if (!aud) {
this.noAudError();
}
this.logger?.info?.('fetchIdTokenDecoded.get-cached', { audience });
const token = this.fetchIdTokenCached(aud);
this.logger?.info?.('fetchIdTokenDecoded.got-token-cache', { token });
if (token.fetchTokenStatus === 'pending') {
return await token.promise;
}
const { payload } = await token.promise;
const result = this.fetchIdTokenCached(aud, {
forceRefresh: token.fetchTokenStatus === 'rejected' || this.isTokenExpired(payload),
});
return await result.promise;
}
async fetchIdToken(audience) {
this.logger?.info?.('fetchIdToken.called', { audience });
return (await this.fetchIdTokenDecoded(audience)).raw;
}
isTokenExpired({ exp }) {
const now = Date.now();
const expMillis = exp * 1000;
const isExpired = expMillis - this.tokenExpiryMargin <= now;
this.logger?.info?.('isTokenExpired.called', {
exp,
expMillis,
tokenExpiryMargin: this.tokenExpiryMargin,
now,
isExpired,
});
return isExpired;
}
}
exports.GoogleSaIdToken = GoogleSaIdToken;
//# sourceMappingURL=google-sa-id-token.js.map