UNPKG

zeebe-node

Version:

The Node.js client library for the Zeebe Workflow Automation Engine.

225 lines 8.93 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OAuthProvider = void 0; const fs = __importStar(require("fs")); const got_1 = __importDefault(require("got")); const os = __importStar(require("os")); const timers_1 = require("timers"); const uuid = require("uuid"); const pkg = require("../../package.json"); const homedir = os.homedir(); const debug = require('debug')('oauth'); const trace = require('debug')('oauth:trace'); const BACKOFF_TOKEN_ENDPOINT_MAX = 60000; // 60 seconds class OAuthProvider { constructor({ /** OAuth Endpoint URL */ url, /** OAuth Audience */ audience, /** OAuth Scope */ scope, cacheDir, clientId, clientSecret, /** Custom TLS certificate for OAuth */ customRootCert, /** Cache token in memory and on filesystem? */ cacheOnDisk, }) { this.tokenCache = {}; this.currentBackoffTime = 1; this.cachedTokenFile = (clientId) => `${this.cacheDir}/oauth-token-${clientId}.json`; this.url = url; this.audience = audience; this.scope = scope; this.clientId = clientId; this.clientSecret = clientSecret; this.customRootCert = customRootCert; this.useFileCache = cacheOnDisk; this.cacheDir = cacheDir || OAuthProvider.getTokenCacheDirFromEnv(); this.uuid = uuid.v4(); const CUSTOM_AGENT_STRING = process.env.ZEEBE_CLIENT_CUSTOM_AGENT_STRING; this.userAgentString = `zeebe-client-nodejs/${pkg.version}${CUSTOM_AGENT_STRING ? ' ' + CUSTOM_AGENT_STRING : ''}`; if (this.useFileCache) { try { if (!fs.existsSync(this.cacheDir)) { fs.mkdirSync(this.cacheDir); } fs.accessSync(this.cacheDir, fs.constants.W_OK); } catch (e) { throw new Error(`FATAL: Cannot write to OAuth cache dir ${cacheDir}\n` + 'If you are running on AWS Lambda, set the HOME environment variable of your lambda function to /tmp'); } } } async getToken() { if (this.tokenCache[this.clientId]) { debug(`Using cached token from memory...`); return this.tokenCache[this.clientId].access_token; } if (this.useFileCache) { const cachedToken = this.fromFileCache(this.clientId); if (cachedToken) { debug(`Using cached token from file...`); return cachedToken.access_token; } } if (!this.inflightTokenRequest) { this.inflightTokenRequest = new Promise((resolve, reject) => { setTimeout(() => { this.debouncedTokenRequest() .then(res => { this.currentBackoffTime = 1; this.inflightTokenRequest = undefined; resolve(res); }) .catch(e => { if (this.currentBackoffTime === 1) { this.currentBackoffTime = 1000; } this.currentBackoffTime = Math.min(this.currentBackoffTime * 2, BACKOFF_TOKEN_ENDPOINT_MAX); this.inflightTokenRequest = undefined; reject(e); }); }, this.currentBackoffTime); }); } return this.inflightTokenRequest; } stopExpiryTimer() { if (this.expiryTimer) { (0, timers_1.clearTimeout)(this.expiryTimer); trace(`${this.uuid} stop`); } } debouncedTokenRequest() { const form = { audience: this.audience, client_id: this.clientId, client_secret: this.clientSecret, grant_type: 'client_credentials', ...(this.scope && { scope: this.scope } || {}) }; debug(`Requesting token from token endpoint...`); return got_1.default .post(this.url, { form, headers: { 'content-type': 'application/x-www-form-urlencoded', 'user-agent': this.userAgentString, }, https: { certificateAuthority: this.customRootCert } }) .then(res => { return this.safeJSONParse(res.body).then(token => { debug(`Received token from token endpoint.`); const d = new Date(); token.expiry = d.setSeconds(d.getSeconds()) + (token.expires_in * 1000); if (this.useFileCache) { this.toFileCache(token); } this.tokenCache[this.clientId] = token; this.startExpiryTimer(token); return token.access_token; }); }); } safeJSONParse(thing) { return new Promise((resolve, reject) => { try { resolve(JSON.parse(thing)); } catch (e) { reject(e); } }); } fromFileCache(clientId) { let token; const tokenCachedInFile = fs.existsSync(this.cachedTokenFile(clientId)); debug(`Checking token cache file...`); if (!tokenCachedInFile) { debug(`No token cache file found...`); return null; } try { debug(`Using token cache file ${this.cachedTokenFile(clientId)}`); token = JSON.parse(fs.readFileSync(this.cachedTokenFile(clientId), 'utf8')); if (this.isExpired(token)) { debug(`Cached token is expired...`); return null; } this.tokenCache[this.clientId] = token; this.startExpiryTimer(token); return token; } catch (e) { debug(`Failed to load cached token: ${e.message}`); return null; } } toFileCache(token) { const file = this.cachedTokenFile(this.clientId); fs.writeFile(file, JSON.stringify(token), e => { if (!e) { return; } // tslint:disable-next-line console.error('Error writing OAuth token to file' + file); // tslint:disable-next-line console.error(e); }); } isExpired(token) { const d = new Date(); return token.expiry <= d.setSeconds(d.getSeconds()); } startExpiryTimer(token) { const d = new Date(); const current = d.setSeconds(d.getSeconds()); const validityPeriod = token.expiry - current; if (validityPeriod <= 0) { delete this.tokenCache[this.clientId]; return; } // renew token 1s before it expires to avoid race conditions on the wire // evict disk cache at same time as in-memory cache // See: https://github.com/camunda-community-hub/zeebe-client-node-js/issues/336 const minimumCacheLifetime = 0; // Minimum cache lifetime in milliseconds const renewTokenAfterMs = Math.max(validityPeriod - 1000, minimumCacheLifetime); this.expiryTimer = setTimeout(() => { trace(`${this.uuid} token expired`); delete this.tokenCache[this.clientId]; if (this.useFileCache && fs.existsSync(this.cachedTokenFile(this.clientId))) { fs.unlinkSync(this.cachedTokenFile(this.clientId)); } }, renewTokenAfterMs); trace(`${this.uuid} token expiry timer start: ${renewTokenAfterMs}ms`); } } exports.OAuthProvider = OAuthProvider; OAuthProvider.defaultTokenCache = `${homedir}/.camunda`; OAuthProvider.getTokenCacheDirFromEnv = () => process.env.ZEEBE_TOKEN_CACHE_DIR || OAuthProvider.defaultTokenCache; //# sourceMappingURL=OAuthProvider.js.map