From c86e550747f23205dac9fe199a38217b3a583f76 Mon Sep 17 00:00:00 2001 From: Josh Wulf Date: Wed, 22 May 2024 19:23:52 +1200 Subject: [PATCH] fix(oauth): correctly expire cached token (#164) fixes #163 --- package-lock.json | 118 ++- package.json | 3 +- src/__tests__/admin/admin.integration.spec.ts | 5 +- .../oauth/OAuthProvider.unit.spec.ts | 944 +++++++++--------- src/oauth/lib/OAuthProvider.ts | 16 +- 5 files changed, 608 insertions(+), 478 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4ed63135..da11da13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,13 +31,13 @@ "devDependencies": { "@commitlint/cli": "^18.4.3", "@commitlint/config-conventional": "^18.4.3", - "@mokuteki/jwt": "^1.0.2", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@sitapati/testcontainers": "^2.8.1", "@types/debug": "^4.1.12", "@types/express": "^4.17.21", "@types/jest": "^29.5.11", + "@types/jsonwebtoken": "^9.0.6", "@types/lodash.mergewith": "^4.6.9", "@types/node": "^20.9.4", "@types/node-fetch": "^2.6.11", @@ -56,6 +56,7 @@ "grpc-tools": "^1.12.4", "husky": "^8.0.3", "jest": "^29.7.0", + "jsonwebtoken": "^9.0.2", "lint-staged": "^15.2.0", "prettier": "^3.1.1", "semantic-release": "^22.0.12", @@ -2209,12 +2210,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/@mokuteki/jwt": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@mokuteki/jwt/-/jwt-1.0.2.tgz", - "integrity": "sha512-KMMPes910EQGQWdj4DeKr683VTzHRgbar17xydzAIr2bYsDp9hln32hq9f/ddFF9ZNmnWfyozjaURBeshxhP7w==", - "dev": true - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "dev": true, @@ -3732,6 +3727,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", + "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/keyv": { "version": "3.1.4", "license": "MIT", @@ -4897,6 +4901,12 @@ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", "dev": true }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "node_modules/buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -6471,6 +6481,15 @@ "readable-stream": "^2.0.2" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -10770,6 +10789,61 @@ "node": "*" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dev": true, + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/jwt-decode": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", @@ -11141,11 +11215,35 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true + }, "node_modules/lodash.isfunction": { "version": "3.0.9", "dev": true, "license": "MIT" }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "dev": true, @@ -11182,6 +11280,12 @@ "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, "node_modules/lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", diff --git a/package.json b/package.json index 89d129d2..18a8a49e 100644 --- a/package.json +++ b/package.json @@ -93,13 +93,13 @@ "devDependencies": { "@commitlint/cli": "^18.4.3", "@commitlint/config-conventional": "^18.4.3", - "@mokuteki/jwt": "^1.0.2", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@sitapati/testcontainers": "^2.8.1", "@types/debug": "^4.1.12", "@types/express": "^4.17.21", "@types/jest": "^29.5.11", + "@types/jsonwebtoken": "^9.0.6", "@types/lodash.mergewith": "^4.6.9", "@types/node": "^20.9.4", "@types/node-fetch": "^2.6.11", @@ -118,6 +118,7 @@ "grpc-tools": "^1.12.4", "husky": "^8.0.3", "jest": "^29.7.0", + "jsonwebtoken": "^9.0.2", "lint-staged": "^15.2.0", "prettier": "^3.1.1", "semantic-release": "^22.0.12", diff --git a/src/__tests__/admin/admin.integration.spec.ts b/src/__tests__/admin/admin.integration.spec.ts index cb5d9260..2072e04d 100644 --- a/src/__tests__/admin/admin.integration.spec.ts +++ b/src/__tests__/admin/admin.integration.spec.ts @@ -18,13 +18,14 @@ test('createClient', async () => { const c = new AdminApiClient() const clusters = await c.getClusters() const clusterUuid = clusters[0].uuid - c.getClient(clusterUuid, 'testors') + const clientName = 'test_generated-delete-me' + c.getClient(clusterUuid, clientName) .then((res) => c.deleteClient(clusterUuid, res.ZEEBE_CLIENT_ID)) .catch((e) => e) const res = await c.createClient({ clusterUuid, - clientName: 'testors', + clientName, permissions: ['Zeebe'], }) const client = await c.getClient(clusterUuid, res.clientId) diff --git a/src/__tests__/oauth/OAuthProvider.unit.spec.ts b/src/__tests__/oauth/OAuthProvider.unit.spec.ts index 798167fb..20625e3b 100644 --- a/src/__tests__/oauth/OAuthProvider.unit.spec.ts +++ b/src/__tests__/oauth/OAuthProvider.unit.spec.ts @@ -4,21 +4,11 @@ import http from 'http' import os from 'os' import path from 'path' -import { HS256Strategy, JSONWebToken } from '@mokuteki/jwt' +import jwt from 'jsonwebtoken' import { EnvironmentSetup } from '../../lib' import { OAuthProvider } from '../../oauth' -const strategy = new HS256Strategy({ - ttl: 30000, - secret: 'YOUR_SECRET', -}) - -const jwt = new JSONWebToken(strategy) -const payload = { id: 1 } - -const access_token = jwt.generate(payload) - jest.setTimeout(10000) let server: http.Server @@ -42,523 +32,545 @@ function removeCacheDir(dirpath: string) { } } -test('Throws in the constructor if there in no clientId credentials', () => { - let thrown = false - let message = '' - try { - new OAuthProvider({ - config: { CAMUNDA_OAUTH_URL: 'url' }, - }) - } catch (e) { - thrown = true - message = (e as Error).message - } - expect(thrown).toBe(true) - expect( - message.includes('ZEEBE_CLIENT_ID') && - message.includes('CAMUNDA_CONSOLE_CLIENT_ID') - ).toBe(true) -}) +describe('OAuthProvider', () => { + it('Throws in the constructor if there in no clientId credentials', () => { + let thrown = false + let message = '' + try { + new OAuthProvider({ + config: { CAMUNDA_OAUTH_URL: 'url' }, + }) + } catch (e) { + thrown = true + message = (e as Error).message + } + expect(thrown).toBe(true) + expect( + message.includes('ZEEBE_CLIENT_ID') && + message.includes('CAMUNDA_CONSOLE_CLIENT_ID') + ).toBe(true) + }) -test('Throws in the constructor if there in no clientSecret credentials', () => { - let thrown = false - let message = '' - try { - new OAuthProvider({ - config: { - CAMUNDA_CONSOLE_CLIENT_ID: 'clientId1', - CAMUNDA_OAUTH_URL: 'url', - }, - }) - } catch (e) { - thrown = true - message = (e as Error).message - } - expect(thrown).toBe(true) - expect( - message.includes('ZEEBE_CLIENT_SECRET') && - message.includes('CAMUNDA_CONSOLE_CLIENT_SECRET') - ).toBe(true) -}) + it('Throws in the constructor if there in no clientSecret credentials', () => { + let thrown = false + let message = '' + try { + new OAuthProvider({ + config: { + CAMUNDA_CONSOLE_CLIENT_ID: 'clientId1', + CAMUNDA_OAUTH_URL: 'url', + }, + }) + } catch (e) { + thrown = true + message = (e as Error).message + } + expect(thrown).toBe(true) + expect( + message.includes('ZEEBE_CLIENT_SECRET') && + message.includes('CAMUNDA_CONSOLE_CLIENT_SECRET') + ).toBe(true) + }) + + it('Throws in the constructor if there are insufficient credentials', () => { + let thrown = false + let message = '' + try { + new OAuthProvider({ + config: { + CAMUNDA_CONSOLE_CLIENT_ID: 'clientId2', + ZEEBE_CLIENT_SECRET: 'zeebe-secret', + CAMUNDA_OAUTH_URL: 'url', + }, + }) + } catch (e) { + thrown = true + message = (e as Error).message + } + expect(thrown).toBe(true) + expect( + message.includes('client ID') && message.includes('client secret') + ).toBe(true) + }) -test('Throws in the constructor if there are insufficient credentials', () => { - let thrown = false - let message = '' - try { - new OAuthProvider({ + it('Gets the token cache dir from the environment', () => { + const tokenCacheDir = path.join(__dirname, '.token-cache') + removeCacheDir(tokenCacheDir) + expect(fs.existsSync(tokenCacheDir)).toBe(false) + process.env.CAMUNDA_TOKEN_CACHE_DIR = tokenCacheDir + + const o = new OAuthProvider({ config: { - CAMUNDA_CONSOLE_CLIENT_ID: 'clientId2', - ZEEBE_CLIENT_SECRET: 'zeebe-secret', + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + ZEEBE_CLIENT_ID: 'clientId3', + ZEEBE_CLIENT_SECRET: 'clientSecret', CAMUNDA_OAUTH_URL: 'url', }, }) - } catch (e) { - thrown = true - message = (e as Error).message - } - expect(thrown).toBe(true) - expect( - message.includes('client ID') && message.includes('client secret') - ).toBe(true) -}) + expect(o).toBeTruthy() + const exists = fs.existsSync(tokenCacheDir) + expect(exists).toBe(true) + removeCacheDir(tokenCacheDir) -test('Gets the token cache dir from the environment', () => { - const tokenCacheDir = path.join(__dirname, '.token-cache') - removeCacheDir(tokenCacheDir) - expect(fs.existsSync(tokenCacheDir)).toBe(false) - process.env.CAMUNDA_TOKEN_CACHE_DIR = tokenCacheDir - - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - ZEEBE_CLIENT_ID: 'clientId3', - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: 'url', - }, + expect(fs.existsSync(tokenCacheDir)).toBe(false) }) - expect(o).toBeTruthy() - const exists = fs.existsSync(tokenCacheDir) - expect(exists).toBe(true) - removeCacheDir(tokenCacheDir) - expect(fs.existsSync(tokenCacheDir)).toBe(false) -}) + it('Creates the token cache dir if it does not exist', () => { + const tokenCacheDir = path.join(__dirname, '.token-cache') + process.env.CAMUNDA_TOKEN_CACHE_DIR = tokenCacheDir + removeCacheDir(tokenCacheDir) + + expect(fs.existsSync(tokenCacheDir)).toBe(false) -test('Creates the token cache dir if it does not exist', () => { - const tokenCacheDir = path.join(__dirname, '.token-cache') - process.env.CAMUNDA_TOKEN_CACHE_DIR = tokenCacheDir - removeCacheDir(tokenCacheDir) + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + ZEEBE_CLIENT_ID: 'clientId4', + ZEEBE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: 'url', + }, + }) - expect(fs.existsSync(tokenCacheDir)).toBe(false) + expect(o).toBeTruthy() + expect(fs.existsSync(tokenCacheDir)).toBe(true) + removeCacheDir(tokenCacheDir) - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - ZEEBE_CLIENT_ID: 'clientId4', - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: 'url', - }, + expect(fs.existsSync(tokenCacheDir)).toBe(false) }) - expect(o).toBeTruthy() - expect(fs.existsSync(tokenCacheDir)).toBe(true) - removeCacheDir(tokenCacheDir) - - expect(fs.existsSync(tokenCacheDir)).toBe(false) -}) + it('Throws in the constructor if the token cache is not writable', () => { + const tokenCacheDir = path.join(__dirname, '.token-cache') + process.env.CAMUNDA_TOKEN_CACHE_DIR = tokenCacheDir + removeCacheDir(tokenCacheDir) + + expect(fs.existsSync(tokenCacheDir)).toBe(false) + if (os.platform() === 'win32') { + fs.mkdirSync(tokenCacheDir) + expect(fs.existsSync(tokenCacheDir)).toBe(true) + // Make the directory read-only on Windows + // Note that this requires administrative privileges + execSync( + `icacls ${tokenCacheDir} /deny Everyone:(OI)(CI)W /inheritance:r` + ) + } else { + // Make the directory read-only on Unix + fs.mkdirSync(tokenCacheDir, 0o400) + expect(fs.existsSync(tokenCacheDir)).toBe(true) + } -test('Throws in the constructor if the token cache is not writable', () => { - const tokenCacheDir = path.join(__dirname, '.token-cache') - process.env.CAMUNDA_TOKEN_CACHE_DIR = tokenCacheDir - removeCacheDir(tokenCacheDir) + let thrown = false + try { + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + ZEEBE_CLIENT_ID: 'clientId5', + ZEEBE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: 'url', + }, + }) + expect(o).toBeTruthy() + } catch { + thrown = true + } - expect(fs.existsSync(tokenCacheDir)).toBe(false) - if (os.platform() === 'win32') { - fs.mkdirSync(tokenCacheDir) - expect(fs.existsSync(tokenCacheDir)).toBe(true) - // Make the directory read-only on Windows + // Make the directory writeable on Windows, so it can be deleted // Note that this requires administrative privileges - execSync(`icacls ${tokenCacheDir} /deny Everyone:(OI)(CI)W /inheritance:r`) - } else { - // Make the directory read-only on Unix - fs.mkdirSync(tokenCacheDir, 0o400) - expect(fs.existsSync(tokenCacheDir)).toBe(true) - } + if (os.platform() === 'win32') { + execSync(`icacls ${tokenCacheDir} /grant Everyone:(OI)(CI)(F)`) + } + removeCacheDir(tokenCacheDir) + expect(thrown).toBe(true) + expect(fs.existsSync(tokenCacheDir)).toBe(false) + }) + + // Added test for https://github.com/camunda/camunda-saas-oauth-nodejs/issues/8 + // "Can not renew expired token" + // Updated test for https://github.com/camunda/camunda-8-js-sdk/issues/3 + // "Remove expiry timer from oAuth token implementation" + it('In-memory cache is populated and evicted after expiry', (done) => { + const delay = (timeout: number) => + new Promise((res) => setTimeout(() => res(null), timeout)) + const serverPort3002 = 3002 - let thrown = false - try { const o = new OAuthProvider({ config: { CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - ZEEBE_CLIENT_ID: 'clientId5', + ZEEBE_CLIENT_ID: 'clientId6', ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: 'url', + CAMUNDA_OAUTH_URL: `http://127.0.0.1:${serverPort3002}`, + CAMUNDA_TOKEN_DISK_CACHE_DISABLE: true, + CAMUNDA_OAUTH_TOKEN_REFRESH_THRESHOLD_MS: 0, }, }) - expect(o).toBeTruthy() - } catch { - thrown = true - } - - // Make the directory writeable on Windows, so it can be deleted - // Note that this requires administrative privileges - if (os.platform() === 'win32') { - execSync(`icacls ${tokenCacheDir} /grant Everyone:(OI)(CI)(F)`) - } - removeCacheDir(tokenCacheDir) - expect(thrown).toBe(true) - expect(fs.existsSync(tokenCacheDir)).toBe(false) -}) - -// Added test for https://github.com/camunda/camunda-saas-oauth-nodejs/issues/8 -// "Can not renew expired token" -// Updated test for https://github.com/camunda/camunda-8-js-sdk/issues/3 -// "Remove expiry timer from oAuth token implementation" -test('In-memory cache is populated and evicted after timeout', (done) => { - const delay = (timeout: number) => - new Promise((res) => setTimeout(() => res(null), timeout)) - const serverPort3002 = 3002 - - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - ZEEBE_CLIENT_ID: 'clientId6', - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: `http://127.0.0.1:${serverPort3002}`, - CAMUNDA_TOKEN_DISK_CACHE_DISABLE: true, - CAMUNDA_OAUTH_TOKEN_REFRESH_THRESHOLD_MS: 0, - }, - }) - const strategy = new HS256Strategy({ - ttl: 2000, - secret: 'YOUR_SECRET', + const secret = 'YOUR_SECRET' + const ttl = 2 // 2 seconds + const payload = { id: 1 } + const access_token = jwt.sign(payload, secret, { expiresIn: ttl }) + + // On subsequent requests, we will return a different token + let requested = false + + server = http + .createServer((req, res) => { + if (req.method === 'POST') { + let body = '' + req.on('data', (chunk) => { + body += chunk + }) + + req.on('end', () => { + console.log(body) + res.writeHead(200, { 'Content-Type': 'application/json' }) + const expiresIn = 2 // seconds + const token = requested + ? jwt.sign(payload, secret, { expiresIn: ttl }) + : access_token + requested = true + res.end(`{"access_token": "${token}", "expires_in": ${expiresIn}}`) + }) + } + }) + .listen(serverPort3002) + + o.getToken('ZEEBE').then(async (token) => { + const token1 = token + expect(token).toBe(token1) + await delay(500) + const token2 = await o.getToken('ZEEBE') + expect(token2).toBe(token1) + await delay(1600) + const token3 = await o.getToken('ZEEBE') + expect(token3).not.toBe(token1) + done() + }) }) - const jwt = new JSONWebToken(strategy) - const payload = { id: 1 } - - const access_token = jwt.generate(payload) - - let requestCount = 0 - server = http - .createServer((req, res) => { - if (req.method === 'POST') { - let body = '' - req.on('data', (chunk) => { - body += chunk - }) - - req.on('end', () => { - res.writeHead(200, { 'Content-Type': 'application/json' }) - const expiresIn = 2 // seconds - const token = `${access_token}${requestCount}` - res.end(`{"access_token": "${token}", "expires_in": ${expiresIn}}`) - requestCount++ - expect(body).toEqual( - 'audience=token&client_id=clientId6&client_secret=clientSecret&grant_type=client_credentials' - ) - }) - } + it('Uses form encoding for request', (done) => { + const serverPort3001 = 3001 + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + ZEEBE_CLIENT_ID: 'clientId8', + ZEEBE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: `http://127.0.0.1:${serverPort3001}`, + }, }) - .listen(serverPort3002) - - o.getToken('ZEEBE').then(async (token) => { - expect(token).toBe(`${access_token}0`) - await delay(500) - const token2 = await o.getToken('ZEEBE') - expect(token2).toBe(`${access_token}0`) - await delay(1600) - const token3 = await o.getToken('ZEEBE') - expect(token3).toBe(`${access_token}1`) - done() + const secret = 'YOUR_SECRET' + const ttl = 2 // 2 seconds + const payload = { id: 1 } + const access_token = jwt.sign(payload, secret, { expiresIn: ttl }) + server = http + .createServer((req, res) => { + if (req.method === 'POST') { + let body = '' + req.on('data', (chunk) => { + body += chunk + }) + + req.on('end', () => { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(`{"access_token": "${access_token}", "expires_in": "5"}`) + server.close() + expect(body).toEqual( + 'audience=operate.camunda.io&client_id=clientId8&client_secret=clientSecret&grant_type=client_credentials' + ) + done() + }) + } + }) + .listen(serverPort3001) + o.getToken('OPERATE') }) -}) -test('Uses form encoding for request', (done) => { - const serverPort3001 = 3001 - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - ZEEBE_CLIENT_ID: 'clientId8', - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: `http://127.0.0.1:${serverPort3001}`, - }, - }) - server = http - .createServer((req, res) => { - if (req.method === 'POST') { - let body = '' - req.on('data', (chunk) => { - body += chunk - }) - - req.on('end', () => { - res.writeHead(200, { 'Content-Type': 'application/json' }) - res.end(`{"access_token": "${access_token}", "expires_in": "5"}`) - server.close() - expect(body).toEqual( - 'audience=operate.camunda.io&client_id=clientId8&client_secret=clientSecret&grant_type=client_credentials' - ) - done() - }) - } + it('Uses a custom audience for an Operate token, if one is configured', (done) => { + const serverPort3003 = 3003 + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + ZEEBE_CLIENT_ID: 'clientId9', + ZEEBE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: `http://127.0.0.1:${serverPort3003}`, + CAMUNDA_OPERATE_OAUTH_AUDIENCE: 'custom.operate.audience', + }, }) - .listen(serverPort3001) - o.getToken('OPERATE') -}) - -test('Uses a custom audience for an Operate token, if one is configured', (done) => { - const serverPort3003 = 3003 - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - ZEEBE_CLIENT_ID: 'clientId9', - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: `http://127.0.0.1:${serverPort3003}`, - CAMUNDA_OPERATE_OAUTH_AUDIENCE: 'custom.operate.audience', - }, + const secret = 'YOUR_SECRET' + const ttl = 2 // 2 seconds + const payload = { id: 1 } + const access_token = jwt.sign(payload, secret, { expiresIn: ttl }) + server = http + .createServer((req, res) => { + if (req.method === 'POST') { + let body = '' + req.on('data', (chunk) => { + body += chunk + }) + + req.on('end', () => { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(`{"access_token": "${access_token}", "expires_in": "5"}`) + server.close() + expect(body).toEqual( + 'audience=custom.operate.audience&client_id=clientId9&client_secret=clientSecret&grant_type=client_credentials' + ) + done() + }) + } + }) + .listen(serverPort3003) + o.getToken('OPERATE') }) - server = http - .createServer((req, res) => { - if (req.method === 'POST') { - let body = '' - req.on('data', (chunk) => { - body += chunk - }) - - req.on('end', () => { - res.writeHead(200, { 'Content-Type': 'application/json' }) - res.end(`{"access_token": "${access_token}", "expires_in": "5"}`) - server.close() - expect(body).toEqual( - 'audience=custom.operate.audience&client_id=clientId9&client_secret=clientSecret&grant_type=client_credentials' - ) - done() - }) - } - }) - .listen(serverPort3003) - o.getToken('OPERATE') -}) -test('Passes scope, if provided', () => { - const serverPort3004 = 3004 - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - CAMUNDA_TOKEN_SCOPE: 'scope', - ZEEBE_CLIENT_ID: 'clientId10', - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: `http://127.0.0.1:${serverPort3004}`, - }, - }) - server = http - .createServer((req, res) => { - if (req.method === 'POST') { - let body = '' - req.on('data', (chunk) => { - body += chunk - }) - - req.on('end', () => { - res.writeHead(200, { 'Content-Type': 'application/json' }) - res.end(`{"access_token": "${access_token}", "expires_in": "5"}`) - - expect(body).toEqual( - 'audience=token&client_id=clientId10&client_secret=clientSecret&grant_type=client_credentials&scope=scope' - ) - }) - } + it('Passes scope, if provided', () => { + const serverPort3004 = 3004 + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + CAMUNDA_TOKEN_SCOPE: 'scope', + ZEEBE_CLIENT_ID: 'clientId10', + ZEEBE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: `http://127.0.0.1:${serverPort3004}`, + }, }) - .listen(serverPort3004) - - return o.getToken('ZEEBE') -}) - -test('Can get scope from environment', () => { - const serverPort3005 = 3005 - process.env.CAMUNDA_TOKEN_SCOPE = 'scope2' - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - ZEEBE_CLIENT_ID: 'clientId11', - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: `http://127.0.0.1:${serverPort3005}`, - }, + const secret = 'YOUR_SECRET' + const ttl = 5 // 5 seconds + const payload = { id: 1 } + const access_token = jwt.sign(payload, secret, { expiresIn: ttl }) + server = http + .createServer((req, res) => { + if (req.method === 'POST') { + let body = '' + req.on('data', (chunk) => { + body += chunk + }) + + req.on('end', () => { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(`{"access_token": "${access_token}", "expires_in": "5"}`) + + expect(body).toEqual( + 'audience=token&client_id=clientId10&client_secret=clientSecret&grant_type=client_credentials&scope=scope' + ) + }) + } + }) + .listen(serverPort3004) + + return o.getToken('ZEEBE') }) - server = http - .createServer((req, res) => { - if (req.method === 'POST') { - let body = '' - req.on('data', (chunk) => { - body += chunk - }) - - req.on('end', () => { - res.writeHead(200, { 'Content-Type': 'application/json' }) - res.end(`{"access_token": "${access_token}", "expires_in": "5"}`) - - expect(body).toEqual( - 'audience=token&client_id=clientId11&client_secret=clientSecret&grant_type=client_credentials&scope=scope2' - ) - }) - } - }) - .listen(serverPort3005) - return o.getToken('ZEEBE') -}) - -test('Creates the token cache dir if it does not exist', () => { - const tokenCache = path.join(__dirname, '.token-cache') - if (fs.existsSync(tokenCache)) { - fs.rmdirSync(tokenCache) - } - expect(fs.existsSync(tokenCache)).toBe(false) - - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - CAMUNDA_TOKEN_CACHE_DIR: tokenCache, - ZEEBE_CLIENT_ID: 'clientId12', - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: 'url', - }, + it('Can get scope from environment', () => { + const serverPort3005 = 3005 + process.env.CAMUNDA_TOKEN_SCOPE = 'scope2' + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + ZEEBE_CLIENT_ID: 'clientId11', + ZEEBE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: `http://127.0.0.1:${serverPort3005}`, + }, + }) + const secret = 'YOUR_SECRET' + const ttl = 5 // 5 seconds + const payload = { id: 1 } + const access_token = jwt.sign(payload, secret, { expiresIn: ttl }) + server = http + .createServer((req, res) => { + if (req.method === 'POST') { + let body = '' + req.on('data', (chunk) => { + body += chunk + }) + + req.on('end', () => { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(`{"access_token": "${access_token}", "expires_in": "5"}`) + + expect(body).toEqual( + 'audience=token&client_id=clientId11&client_secret=clientSecret&grant_type=client_credentials&scope=scope2' + ) + }) + } + }) + .listen(serverPort3005) + + return o.getToken('ZEEBE') }) - expect(o).toBeTruthy() - expect(fs.existsSync(tokenCache)).toBe(true) - if (fs.existsSync(tokenCache)) { - fs.rmdirSync(tokenCache) - } - expect(fs.existsSync(tokenCache)).toBe(false) -}) - -test('Gets the token cache dir from the environment', () => { - const tokenCache = path.join(__dirname, '.token-cache') - if (fs.existsSync(tokenCache)) { - fs.rmdirSync(tokenCache) - } - expect(fs.existsSync(tokenCache)).toBe(false) - process.env.CAMUNDA_TOKEN_CACHE_DIR = tokenCache - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - ZEEBE_CLIENT_ID: 'clientId13', - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: 'url', - }, - }) + it('Creates the token cache dir if it does not exist', () => { + const tokenCache = path.join(__dirname, '.token-cache') + if (fs.existsSync(tokenCache)) { + fs.rmdirSync(tokenCache) + } + expect(fs.existsSync(tokenCache)).toBe(false) - expect(o).toBeTruthy() - expect(fs.existsSync(tokenCache)).toBe(true) - if (fs.existsSync(tokenCache)) { - fs.rmdirSync(tokenCache) - } - expect(fs.existsSync(tokenCache)).toBe(false) -}) + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + CAMUNDA_TOKEN_CACHE_DIR: tokenCache, + ZEEBE_CLIENT_ID: 'clientId12', + ZEEBE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: 'url', + }, + }) -test('Uses an explicit token cache over the environment', () => { - const tokenCache1 = path.join(__dirname, '.token-cache1') - const tokenCache2 = path.join(__dirname, '.token-cache2') - ;[tokenCache1, tokenCache2].forEach((tokenCache) => { + expect(o).toBeTruthy() + expect(fs.existsSync(tokenCache)).toBe(true) if (fs.existsSync(tokenCache)) { fs.rmdirSync(tokenCache) } expect(fs.existsSync(tokenCache)).toBe(false) }) - process.env.CAMUNDA_TOKEN_CACHE_DIR = tokenCache1 - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - CAMUNDA_TOKEN_CACHE_DIR: tokenCache2, - ZEEBE_CLIENT_ID: 'clientId14', - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: 'url', - }, - }) - expect(o).toBeTruthy() - expect(fs.existsSync(tokenCache2)).toBe(true) - expect(fs.existsSync(tokenCache1)).toBe(false) - ;[tokenCache1, tokenCache2].forEach((tokenCache) => { + it('Gets the token cache dir from the environment', () => { + const tokenCache = path.join(__dirname, '.token-cache') if (fs.existsSync(tokenCache)) { fs.rmdirSync(tokenCache) } expect(fs.existsSync(tokenCache)).toBe(false) - }) -}) + process.env.CAMUNDA_TOKEN_CACHE_DIR = tokenCache + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + ZEEBE_CLIENT_ID: 'clientId13', + ZEEBE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: 'url', + }, + }) -test('Can set a custom user agent', () => { - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - ZEEBE_CLIENT_ID: 'clientId16', - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: 'http://127.0.0.1:3005', - CAMUNDA_CUSTOM_USER_AGENT_STRING: 'modeler', - }, + expect(o).toBeTruthy() + expect(fs.existsSync(tokenCache)).toBe(true) + if (fs.existsSync(tokenCache)) { + fs.rmdirSync(tokenCache) + } + expect(fs.existsSync(tokenCache)).toBe(false) }) - expect(o.userAgentString.includes(' modeler')).toBe(true) -}) + it('Uses an explicit token cache over the environment', () => { + const tokenCache1 = path.join(__dirname, '.token-cache1') + const tokenCache2 = path.join(__dirname, '.token-cache2') + ;[tokenCache1, tokenCache2].forEach((tokenCache) => { + if (fs.existsSync(tokenCache)) { + fs.rmdirSync(tokenCache) + } + expect(fs.existsSync(tokenCache)).toBe(false) + }) + process.env.CAMUNDA_TOKEN_CACHE_DIR = tokenCache1 + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + CAMUNDA_TOKEN_CACHE_DIR: tokenCache2, + ZEEBE_CLIENT_ID: 'clientId14', + ZEEBE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: 'url', + }, + }) -// See: https://github.com/camunda/camunda-8-js-sdk/issues/60 -test('Passes no audience for Modeler API when self-hosted', (done) => { - const serverPort3006 = 3006 - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - ZEEBE_CLIENT_ID: 'clientId17', - CAMUNDA_TOKEN_DISK_CACHE_DISABLE: true, - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: `http://127.0.0.1:${serverPort3006}`, - }, - }) - server = http - .createServer((req, res) => { - if (req.method === 'POST') { - let body = '' - req.on('data', (chunk) => { - body += chunk - }) - - req.on('end', () => { - res.writeHead(200, { 'Content-Type': 'application/json' }) - res.end(`{"access_token": "${access_token}"}`) - expect(body).toEqual( - 'client_id=clientId17&client_secret=clientSecret&grant_type=client_credentials' - ) - done() - }) + expect(o).toBeTruthy() + expect(fs.existsSync(tokenCache2)).toBe(true) + expect(fs.existsSync(tokenCache1)).toBe(false) + ;[tokenCache1, tokenCache2].forEach((tokenCache) => { + if (fs.existsSync(tokenCache)) { + fs.rmdirSync(tokenCache) } + expect(fs.existsSync(tokenCache)).toBe(false) + }) + }) + + it('Can set a custom user agent', () => { + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + ZEEBE_CLIENT_ID: 'clientId16', + ZEEBE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: 'http://127.0.0.1:3005', + CAMUNDA_CUSTOM_USER_AGENT_STRING: 'modeler', + }, }) - .listen(serverPort3006) - o.getToken('MODELER') -}) -// See: https://github.com/camunda/camunda-8-js-sdk/issues/60 -test('Throws if you try to get a Modeler token from SaaS without console creds', async () => { - let thrown = false - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - ZEEBE_CLIENT_ID: 'clientId18', - CAMUNDA_TOKEN_DISK_CACHE_DISABLE: true, - ZEEBE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: 'https://login.cloud.camunda.io/oauth/token', - }, + expect(o.userAgentString.includes(' modeler')).toBe(true) }) - await o - .getToken('MODELER') - .catch(() => { - thrown = true + // See: https://github.com/camunda/camunda-8-js-sdk/issues/60 + it('Passes no audience for Modeler API when self-hosted', (done) => { + const serverPort3006 = 3006 + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + ZEEBE_CLIENT_ID: 'clientId17', + CAMUNDA_TOKEN_DISK_CACHE_DISABLE: true, + ZEEBE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: `http://127.0.0.1:${serverPort3006}`, + }, }) - .then(() => { - expect(thrown).toBe(true) + const secret = 'YOUR_SECRET' + const ttl = 5 // 5 seconds + const payload = { id: 1 } + const access_token = jwt.sign(payload, secret, { expiresIn: ttl }) + server = http + .createServer((req, res) => { + if (req.method === 'POST') { + let body = '' + req.on('data', (chunk) => { + body += chunk + }) + + req.on('end', () => { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(`{"access_token": "${access_token}"}`) + expect(body).toEqual( + 'client_id=clientId17&client_secret=clientSecret&grant_type=client_credentials' + ) + done() + }) + } + }) + .listen(serverPort3006) + o.getToken('MODELER') + }) + + // See: https://github.com/camunda/camunda-8-js-sdk/issues/60 + it('Throws if you try to get a Modeler token from SaaS without console creds', async () => { + let thrown = false + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + ZEEBE_CLIENT_ID: 'clientId18', + CAMUNDA_TOKEN_DISK_CACHE_DISABLE: true, + ZEEBE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: 'https://login.cloud.camunda.io/oauth/token', + }, }) -}) -// See: https://github.com/camunda/camunda-8-js-sdk/issues/60 -test('Throws if you try to get a Modeler token from Self-hosted without application creds', async () => { - let thrown = false - const o = new OAuthProvider({ - config: { - CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', - CAMUNDA_CONSOLE_CLIENT_ID: 'clientId19', - CAMUNDA_TOKEN_DISK_CACHE_DISABLE: true, - CAMUNDA_CONSOLE_CLIENT_SECRET: 'clientSecret', - CAMUNDA_OAUTH_URL: 'https://localhost', - }, + await o + .getToken('MODELER') + .catch(() => { + thrown = true + }) + .then(() => { + expect(thrown).toBe(true) + }) }) - await o - .getToken('MODELER') - .catch(() => { - thrown = true - }) - .then(() => { - expect(thrown).toBe(true) + + // See: https://github.com/camunda/camunda-8-js-sdk/issues/60 + it('Throws if you try to get a Modeler token from Self-hosted without application creds', async () => { + let thrown = false + const o = new OAuthProvider({ + config: { + CAMUNDA_ZEEBE_OAUTH_AUDIENCE: 'token', + CAMUNDA_CONSOLE_CLIENT_ID: 'clientId19', + CAMUNDA_TOKEN_DISK_CACHE_DISABLE: true, + CAMUNDA_CONSOLE_CLIENT_SECRET: 'clientSecret', + CAMUNDA_OAUTH_URL: 'https://localhost', + }, }) + await o + .getToken('MODELER') + .catch(() => { + thrown = true + }) + .then(() => { + expect(thrown).toBe(true) + }) + }) }) diff --git a/src/oauth/lib/OAuthProvider.ts b/src/oauth/lib/OAuthProvider.ts index ee7db305..e180bf64 100644 --- a/src/oauth/lib/OAuthProvider.ts +++ b/src/oauth/lib/OAuthProvider.ts @@ -353,7 +353,8 @@ export class OAuthProvider implements IOAuthProvider { const key = this.getCacheKey(audience) try { const decoded = jwtDecode(token.access_token) - + trace(`Caching token: ${JSON.stringify(decoded, null, 2)}`) + trace(`Caching token for ${audience} in memory. Expiry: ${decoded.exp}`) token.expiry = decoded.exp ?? 0 this.tokenCache[key] = token } catch (e) { @@ -371,9 +372,11 @@ export class OAuthProvider implements IOAuthProvider { const tokenFileName = this.getCachedTokenFileName(clientId, audience) const tokenCachedInFile = fs.existsSync(tokenFileName) if (!tokenCachedInFile) { + trace(`No file cached token for ${audience} found`) return null } try { + trace(`Reading file cached token for ${audience}`) token = JSON.parse( fs.readFileSync(this.getCachedTokenFileName(clientId, audience), 'utf8') ) @@ -422,10 +425,19 @@ export class OAuthProvider implements IOAuthProvider { private isExpired(token: Token) { const d = new Date() const currentTime = d.setSeconds(d.getSeconds()) + + // token.expiry is seconds since Unix Epoch + // The Date constructor expects milliseconds since Unix Epoch + const tokenExpiryMs = token.expiry * 1000 + + trace(`Checking token expiry for ${token.audience}`) + trace(` Current time: ${currentTime}`) + trace(` Token expiry: ${tokenExpiryMs}`) + // If the token has 10 seconds (by default) or less left, renew it. // The Identity server token cache is cleared 30 seconds before the token expires, allowing us to renew it // See: https://github.com/camunda/camunda-8-js-sdk/issues/125 - const tokenIsExpired = currentTime >= token.expiry - this.refreshWindow + const tokenIsExpired = currentTime >= tokenExpiryMs - this.refreshWindow return tokenIsExpired }