Skip to content

Commit

Permalink
feat(camunda8): support Basic Auth
Browse files Browse the repository at this point in the history
Implement Basic Auth provider for all clients

fixes #165
  • Loading branch information
jwulf committed May 24, 2024
1 parent 44320bf commit 1457277
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 17 deletions.
39 changes: 25 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,34 +61,42 @@ Some number values - for example: "_total returned results_ " - may be specified

For `int64` values whose type is not known ahead of time, such as job variables, you can pass an annotated data transfer object (DTO) to decode them reliably. If no DTO is specified, the default behavior of the SDK is to serialise all numbers to JavaScript `number`, and if a number value is detected at a runtime that cannot be accurately stored as `number`, to throw an exception.

## OAuth
## Authorization

Calls to APIs are authorized using a token that is obtained via a client id/secret pair exchange, and then passes as an authorization header on API calls. The SDK handles this for you.
Calls to APIs can be authorized using basic auth or via OAuth - a token that is obtained via a client id/secret pair exchange.

If your Camunda 8 platform is secured using token exchange, provide the client id and secret to the SDK.
### Disable Auth

### Disable OAuth
To disable OAuth, set the environment variable `CAMUNDA_OAUTH_STRATEGY=NONE`. You can use this when running against a minimal Zeebe broker in a development environment, for example.

To disable OAuth, set the environment variable `CAMUNDA_OAUTH_DISABLED`. You can use this when running against a minimal Zeebe broker in a development environment, for example.
### Basic Auth

With this environment variable set, the SDK will inject a `NullAuthProvider` that does nothing.
To use basic auth, set the following values either via the environment or explicitly in code via the constructor:

### Configuring OAuth
```bash
CAMUNDA_AUTH_STRATEGY=BASIC
CAMUNDA_BASIC_AUTH_USERNAME=....
CAMUNDA_BASIC_AUTH_PASSWORD=...
```

### OAuth

To get a token for use with the application APIs, provide the following configuration fields at a minimum, either via the `Camunda8` constructor or in environment variables:
If your platform is secured with OAuth token exchange (Camunda SaaS or Self-Managed with Identity), provide the following configuration fields at a minimum, either via the `Camunda8` constructor or in environment variables:

```bash
ZEEBE_GRPC_ADDRESS
ZEEBE_CLIENT_ID
ZEEBE_CLIENT_SECRET
CAMUNDA_OAUTH_URL
CAMUNDA_AUTH_STRATEGY=OAUTH
ZEEBE_GRPC_ADDRESS=...
ZEEBE_CLIENT_ID=...
ZEEBE_CLIENT_SECRET=...
CAMUNDA_OAUTH_URL=...
```

To get a token for the Camunda SaaS Administration API or the Camunda SaaS Modeler API, set the following:

```bash
CAMUNDA_CONSOLE_CLIENT_ID
CAMUNDA_CONSOLE_CLIENT_SECRET
CAMUNDA_AUTH_STRATEGY=OAUTH
CAMUNDA_CONSOLE_CLIENT_ID=...
CAMUNDA_CONSOLE_CLIENT_SECRET=...
```

### Token caching
Expand Down Expand Up @@ -123,6 +131,7 @@ export ZEEBE_GRPC_ADDRESS='localhost:26500'
export ZEEBE_REST_ADDRESS='http://localhost:8080'
export ZEEBE_CLIENT_ID='zeebe'
export ZEEBE_CLIENT_SECRET='zecret'
export CAMUNDA_OAUTH_STRATEGY='OAUTH'
export CAMUNDA_OAUTH_URL='http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token'
export CAMUNDA_TASKLIST_BASE_URL='http://localhost:8082'
export CAMUNDA_OPERATE_BASE_URL='http://localhost:8081'
Expand Down Expand Up @@ -153,6 +162,7 @@ const c8 = new Camunda8({
ZEEBE_REST_ADDRESS: 'http://localhost:8080',
ZEEBE_CLIENT_ID: 'zeebe',
ZEEBE_CLIENT_SECRET: 'zecret',
CAMUNDA_OAUTH_STRATEGY: 'OAUTH',
CAMUNDA_OAUTH_URL:
'http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token',
CAMUNDA_TASKLIST_BASE_URL: 'http://localhost:8082',
Expand All @@ -178,6 +188,7 @@ export CAMUNDA_TASKLIST_BASE_URL='https://syd-1.tasklist.camunda.io/5c34c0a7-7f2
export CAMUNDA_OPTIMIZE_BASE_URL='https://syd-1.optimize.camunda.io/5c34c0a7-7f29-4424-8414-125615f7a9b9'
export CAMUNDA_OPERATE_BASE_URL='https://syd-1.operate.camunda.io/5c34c0a7-7f29-4424-8414-125615f7a9b9'
export CAMUNDA_OAUTH_URL='https://login.cloud.camunda.io/oauth/token'
export CAMUNDA_AUTH_STRATEGY='OAUTH'

# This is on by default, but we include it in case it got turned off for local tests
export CAMUNDA_SECURE_CONNECTION=true
Expand Down
23 changes: 23 additions & 0 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@sitapati/testcontainers": "^2.8.1",
"@types/basic-auth": "^1.1.8",
"@types/debug": "^4.1.12",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.11",
Expand All @@ -109,6 +110,7 @@
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"basic-auth": "^2.0.1",
"commitizen": "^4.3.0",
"cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0",
Expand Down
44 changes: 43 additions & 1 deletion src/__tests__/oauth/OAuthProvider.unit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import http from 'http'
import os from 'os'
import path from 'path'

import auth from 'basic-auth'
import got from 'got'
import jwt from 'jsonwebtoken'

import { EnvironmentSetup } from '../../lib'
import { EnvironmentSetup, constructOAuthProvider } from '../../lib'
import { OAuthProvider } from '../../oauth'

jest.setTimeout(10000)
Expand Down Expand Up @@ -573,4 +575,44 @@ describe('OAuthProvider', () => {
expect(thrown).toBe(true)
})
})

it('Can use Basic Auth as a strategy', async () => {
const server = http.createServer((req, res) => {
const credentials = auth(req)

if (
!credentials ||
credentials.name !== 'admin' ||
credentials.pass !== 'supersecret'
) {
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Basic realm="example"')
res.end('Access denied')
} else {
res.end('Access granted')
}
})

server.listen(3033, () => {
console.log('Server running on port 3033')
})

const oAuthProvider = constructOAuthProvider({
CAMUNDA_AUTH_STRATEGY: 'BASIC',
CAMUNDA_BASIC_AUTH_PASSWORD: 'supersecret',
CAMUNDA_BASIC_AUTH_USERNAME: 'admin',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any)
const token = await oAuthProvider.getToken('ZEEBE')
await got
.get('http://localhost:3033', {
headers: {
Authorization: 'Basic ' + token,
},
})
.then((res) => {
server.close()
expect(res).toBeTruthy()
})
})
})
17 changes: 17 additions & 0 deletions src/lib/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,21 @@ const getMainEnv = () =>
type: 'string',
optional: true,
},
/** Username for Basic Auth */
CAMUNDA_BASIC_AUTH_USERNAME: {
type: 'string',
optional: true,
},
/** Username for Basic Auth */
CAMUNDA_BASIC_AUTH_PASSWORD: {
type: 'string',
optional: true,
},
CAMUNDA_AUTH_STRATEGY: {
type: 'string',
choices: ['BASIC', 'OAUTH', 'NONE'],
default: 'OAUTH',
},
})

const getZeebeEnv = () =>
Expand Down Expand Up @@ -377,6 +392,8 @@ export const CamundaEnvironmentVariableDictionary =
'GRPC_KEEPALIVE_TIME_MS',
'ZEEBE_REST_ADDRESS',
'ZEEBE_GRPC_ADDRESS',
'CAMUNDA_BASIC_AUTH_USERNAME',
'CAMUNDA_BASIC_AUTH_PASSWORD',
'ZEEBE_ADDRESS',
'ZEEBE_CLIENT_ID',
'ZEEBE_CLIENT_SECRET',
Expand Down
21 changes: 19 additions & 2 deletions src/lib/ConstructOAuthProvider.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
import debug from 'debug'

import { NullAuthProvider, OAuthProvider } from '../oauth'
import { BasicAuthProvider } from '../oauth/lib/BasicAuthProvider'

import { CamundaPlatform8Configuration } from './Configuration'

const trace = debug('camunda:oauth')

export function constructOAuthProvider(config: CamundaPlatform8Configuration) {
if (config.CAMUNDA_OAUTH_DISABLED) {
trace(`Auth strategy is ${config.CAMUNDA_AUTH_STRATEGY}`)
trace(`OAuth disabled is ${config.CAMUNDA_OAUTH_DISABLED}`)
if (
config.CAMUNDA_OAUTH_DISABLED ||
config.CAMUNDA_AUTH_STRATEGY === 'NONE'
) {
trace(`Disabling Auth`)
return new NullAuthProvider()
} else {
return new OAuthProvider({ config })
if (config.CAMUNDA_AUTH_STRATEGY === 'BASIC') {
trace(`Using Basic Auth`)
return new BasicAuthProvider({ config })
} else {
trace(`Using OAuth`)
return new OAuthProvider({ config })
}
}
}
39 changes: 39 additions & 0 deletions src/oauth/lib/BasicAuthProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import debug from 'debug'

import {
CamundaEnvironmentConfigurator,
CamundaPlatform8Configuration,
DeepPartial,
RequireConfiguration,
} from '../../lib'

import { IOAuthProvider, TokenGrantAudienceType } from './IOAuthProvider'

const trace = debug('camunda:oauth')

export class BasicAuthProvider implements IOAuthProvider {
private username: string | undefined
private password: string | undefined
constructor(options?: {
config?: DeepPartial<CamundaPlatform8Configuration>
}) {
const config = CamundaEnvironmentConfigurator.mergeConfigWithEnvironment(
options?.config ?? {}
)
this.username = RequireConfiguration(
config.CAMUNDA_BASIC_AUTH_USERNAME,
'CAMUNDA_BASIC_AUTH_USERNAME'
)
this.password = RequireConfiguration(
config.CAMUNDA_BASIC_AUTH_PASSWORD,
'CAMUNDA_BASIC_AUTH_PASSWORD'
)
}
getToken(audience: TokenGrantAudienceType): Promise<string> {
trace(`Requesting token for audience ${audience}`)
const token = Buffer.from(`${this.username}:${this.password}`).toString(
'base64'
)
return Promise.resolve(token)
}
}

0 comments on commit 1457277

Please sign in to comment.