zeebe-node
Version:
The Node.js client library for the Zeebe Workflow Automation Engine.
225 lines • 8.93 kB
JavaScript
"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