UNPKG

google-sa-id-token

Version:

Fetch ID Token for Service Account when running in GCloud

107 lines 4.29 kB
"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