Skip to content

Commit

Permalink
refactor(oauth): use got in place of node-fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
jwulf committed Apr 28, 2024
1 parent b6302d0 commit b023d87
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 108 deletions.
62 changes: 1 addition & 61 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@
"long": "^4.0.0",
"lossless-json": "^4.0.1",
"neon-env": "^0.1.3",
"node-fetch": "^2.7.0",
"promise-retry": "^1.1.1",
"reflect-metadata": "^0.2.1",
"stack-trace": "0.0.10",
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/oauth/OAuthProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ test('Uses a custom audience for an Operate token, if one is configured', (done)
o.getToken('OPERATE')
})

test.only('Passes scope, if provided', () => {
test('Passes scope, if provided', () => {
const serverPort3004 = 3004
const o = new OAuthProvider({
config: {
Expand Down Expand Up @@ -510,7 +510,7 @@ test('Passes no audience for Modeler API when self-hosted', (done) => {

req.on('end', () => {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(`{"token": "${access_token}"}`)
res.end(`{"access_token": "${access_token}"}`)
expect(body).toEqual(
'client_id=clientId17&client_secret=clientSecret&grant_type=client_credentials'
)
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/operate/operate-integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ afterAll(async () => {
restoreZeebeLogging()
})

jest.setTimeout(15000)
jest.setTimeout(20000)
describe('Operate Integration', () => {
xtest('It can get the Incident', async () => {
const c = new OperateApiClient()
Expand Down Expand Up @@ -61,7 +61,7 @@ test('getJSONVariablesforProcess works', async () => {
})

// Wait for Operate to catch up.
await new Promise((res) => setTimeout(() => res(null), 12000))
await new Promise((res) => setTimeout(() => res(null), 15000))
// Make sure that the process instance exists in Operate.
const process = await c.getProcessInstance(p.processInstanceKey)
// If this fails, it is probably a timing issue.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ZeebeGrpcClient } from '../../../zeebe/index'
import { cancelProcesses } from '../../../zeebe/lib/cancelProcesses'
import { DeployResourceResponse, ProcessDeployment } from '../../../zeebe/types'

jest.setTimeout(15000)

suppressZeebeLogging()

let res: DeployResourceResponse<ProcessDeployment> | undefined
Expand All @@ -21,15 +23,12 @@ afterAll(async () => {
const zbc = new ZeebeGrpcClient()

test('ZeebeGrpcClient can migrate a process instance', async () => {
expect(true).toBe(true)
// Deploy a process model

res = await zbc.deployResource({
processFilename: './src/__tests__/testdata/MigrateProcess-Version-1.bpmn',
})

// Create an instance of the process model

const processInstance = await zbc.createProcessInstance({
bpmnProcessId: 'migrant-work',
variables: {},
Expand Down Expand Up @@ -62,6 +61,7 @@ test('ZeebeGrpcClient can migrate a process instance', async () => {
})

// Migrate the process instance to the updated process model

await zbc.migrateProcessInstance({
processInstanceKey: processInstance.processInstanceKey,
migrationPlan: {
Expand Down Expand Up @@ -105,7 +105,7 @@ test('ZeebeGrpcClient can migrate a process instance', async () => {
},
})
})

await zbc.close()
expect(instanceKey).toBe(processInstance.processInstanceKey)
expect(processVersion).toBe('2')
})
88 changes: 50 additions & 38 deletions src/oauth/lib/OAuthProvider.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import * as fs from 'fs'
import https from 'https'
import * as os from 'os'

import { debug } from 'debug'
import got from 'got'
import { jwtDecode } from 'jwt-decode'
import fetch from 'node-fetch'

import {
CamundaEnvironmentConfigurator,
CamundaPlatform8Configuration,
DeepPartial,
GetCertificateAuthority,
GotRetryConfig,
RequireConfiguration,
createUserAgentString,
gotBeforeErrorHook,
gotErrorHandler,
} from '../../lib'
import { IOAuthProvider, Token, TokenError } from '../index'

Expand All @@ -29,7 +31,6 @@ export class OAuthProvider implements IOAuthProvider {
private authServerUrl: string
private clientId: string | undefined
private clientSecret: string | undefined
private customRootCert?: Buffer
private useFileCache: boolean
public tokenCache: { [key: string]: Token } = {}
private failed = false
Expand All @@ -43,6 +44,7 @@ export class OAuthProvider implements IOAuthProvider {
private isCamundaSaaS: boolean
private camundaModelerOAuthAudience: string | undefined
private refreshWindow: number
private rest: typeof got

constructor(options?: {
config?: DeepPartial<CamundaPlatform8Configuration>
Expand Down Expand Up @@ -83,11 +85,18 @@ export class OAuthProvider implements IOAuthProvider {
) {
throw new Error('You need to supply both a client ID and a client secret')
}
const certificateAuthority = GetCertificateAuthority(config)

const customRootCert = GetCertificateAuthority(config)
this.customRootCert = customRootCert
? Buffer.from(customRootCert)
: undefined
this.rest = got.extend({
retry: GotRetryConfig,
https: {
certificateAuthority,
},
handlers: [gotErrorHandler],
hooks: {
beforeError: [gotBeforeErrorHook],
},
})

this.scope = config.CAMUNDA_TOKEN_SCOPE
this.useFileCache = !config.CAMUNDA_TOKEN_DISK_CACHE_DISABLE
Expand Down Expand Up @@ -125,6 +134,20 @@ export class OAuthProvider implements IOAuthProvider {
'If you are running on AWS Lambda, set the HOME environment variable of your lambda function to /tmp'
)
}

const certificateAuthority = GetCertificateAuthority(config)

this.rest = got.extend({
// prefixUrl,
retry: GotRetryConfig,
https: {
certificateAuthority,
},
handlers: [gotErrorHandler],
hooks: {
beforeError: [gotBeforeErrorHook],
},
})
}

this.isCamundaSaaS = this.authServerUrl.includes(
Expand All @@ -134,7 +157,6 @@ export class OAuthProvider implements IOAuthProvider {

public async getToken(audienceType: TokenGrantAudienceType): Promise<string> {
debug(`Token request for ${audienceType}`)
// tslint:disable-next-line: no-console
// We use the Console credential set if it we are requesting from
// the SaaS OAuth endpoint, and it is a Modeler or Admin Console token.
// Otherwise we use the application credential set, unless a Console credential set exists.
Expand Down Expand Up @@ -261,45 +283,26 @@ export class OAuthProvider implements IOAuthProvider {
/* Add a scope to the token request, if one is set */
const bodyWithScope = this.scope ? `${body}&scope=${this.scope}` : body

if (this.customRootCert) {
trace('Using custom root certificate')
}

const customAgent = this.customRootCert
? new https.Agent({ ca: this.customRootCert })
: undefined

const options = {
agent: customAgent,
method: 'POST',
body: bodyWithScope,
headers: {
'content-type': 'application/x-www-form-urlencoded',
'user-agent': this.userAgentString,
accept: '*/*',
},
}
const optionsWithAgent = this.customRootCert
? { ...options, agent: customAgent }
: options

trace(`Making token request to the token endpoint: `)
trace(` ${this.authServerUrl}`)
trace(optionsWithAgent)
return fetch(this.authServerUrl, optionsWithAgent)
trace(options)
return this.rest
.post(this.authServerUrl, options)
.catch((e) => {
console.log(`Erroring requesting token for Client Id ${clientIdToUse}`)
console.log(e)
throw e
})
.then((res) =>
res.json().catch(() => {
trace(
`Failed to parse response from token endpoint. Status ${res.status}: ${res.statusText}`
)
throw new Error(
`Failed to parse response from token endpoint. Status ${res.status}: ${res.statusText}`
)
})
)
.then((res) => JSON.parse(res.body))
.then((t) => {
trace(
`Got token for Client Id ${clientIdToUse}: ${JSON.stringify(
Expand All @@ -315,6 +318,10 @@ export class OAuthProvider implements IOAuthProvider {
`Failed to get token: ${t.error} - ${t.error_description}`
)
}
if (t.access_token === undefined) {
console.error(audienceType, t)
throw new Error('Failed to get token: no access_token in response')
}
const token = { ...(t as Token), audience: audienceType }
if (this.useFileCache) {
this.sendToFileCache({
Expand All @@ -336,11 +343,16 @@ export class OAuthProvider implements IOAuthProvider {
token: Token
}) {
const key = this.getCacheKey(audience)

const decoded = jwtDecode(token.access_token)

token.expiry = decoded.exp ?? 0
this.tokenCache[key] = token
try {
const decoded = jwtDecode(token.access_token)

token.expiry = decoded.exp ?? 0
this.tokenCache[key] = token
} catch (e) {
console.error('audience', audience)
console.error('token', token.access_token)
throw e
}
}

private retrieveFromFileCache(
Expand Down

0 comments on commit b023d87

Please sign in to comment.