diff --git a/README.md b/README.md index 671ad0f1..5a7e7a47 100644 --- a/README.md +++ b/README.md @@ -20,19 +20,21 @@ For a **video demo** of how this looks, you can [check our docs](https://docs.te Set these environment variables if you need to change their defaults -| Variable | Description | Default | -| ----------------------------- | ----------------------------------------------------------------- | ----------------------------- | -| TEMPORAL_GRPC_ENDPOINT | String representing server gRPC endpoint | 127.0.0.1:7233 | -| TEMPORAL_WEB_PORT | HTTP port to serve on | 8088 | -| TEMPORAL_CONFIG_PATH | Path to config file, see [configurations](#configuring-authentication-optional) | ./server/config.yml | -| TEMPORAL_PERMIT_WRITE_API | Boolean to permit write API methods such as Terminating Workflows | true | -| TEMPORAL_WEB_ROOT_PATH | The root path to serve the app under. Ex. "/test/" | / | -| TEMPORAL_HOT_RELOAD_PORT | HTTP port used by hot reloading in development | 8081 | -| TEMPORAL_HOT_RELOAD_TEST_PORT | HTTP port used by hot reloading in tests | 8082 | -| TEMPORAL_SESSION_SECRET | Secret used to hash the session with HMAC | "ensure secret in production" | -| TEMPORAL_EXTERNAL_SCRIPTS | Additional JavaScript tags to serve in the UI | | -| TEMPORAL_GRPC_MAX_MESSAGE_LENGTH | gRPC max message length (bytes) | 4194304 (4mb) | -| TEMPORAL_DATA_ENCODER_ENDPOINT | Remote Data Encoder Endpoint, explained below | | +| Variable | Description | Default | +| -------------------------------- | ----------------------------------------------------------------- | ----------------------------- | +| TEMPORAL_GRPC_ENDPOINT | String representing server gRPC endpoint | 127.0.0.1:7233 | +| TEMPORAL_WEB_PORT | HTTP port to serve on | 8088 | +| TEMPORAL_CONFIG_PATH | Path to config file, see [configurations](#configuring-authentication-optional) | ./server/config.yml | +| TEMPORAL_PERMIT_WRITE_API | Boolean to permit write API methods such as Terminating Workflows | true | +| TEMPORAL_WEB_ROOT_PATH | The root path to serve the app under. Ex. "/test/" | / | +| TEMPORAL_HOT_RELOAD_PORT | HTTP port used by hot reloading in development | 8081 | +| TEMPORAL_HOT_RELOAD_TEST_PORT | HTTP port used by hot reloading in tests | 8082 | +| TEMPORAL_SESSION_SECRET | Secret used to hash the session with HMAC | "ensure secret in production" | +| TEMPORAL_EXTERNAL_SCRIPTS | Additional JavaScript tags to serve in the UI | | +| TEMPORAL_GRPC_MAX_MESSAGE_LENGTH | gRPC max message length (bytes) | 4194304 (4mb) | +| TEMPORAL_CODEC_ENDPOINT | Codec Endpoint, explained below | | +| TEMPORAL_CODEC_PASS_ACCESS_TOKEN | Send OIDC access token to Codec Server | false | +
@@ -60,21 +62,21 @@ Setting `TEMPORAL_TLS_REFRESH_INTERVAL` will make the TLS certs reload every N s
-### Configuring Remote Data Encoder (optional) +### Configuring a Codec Endpoint (optional) -If you are using a data converter on your workers to encrypt Temporal Payloads you may wish to deploy a remote data encoder so that your users can see the unencrypted Payloads while using Temporal Web. The documentation for the Temporal SDK you are using for your application should include documentation on how to build a remote data encoder. Please let us know if this is not the case. Once you have a remote data encoder running you can configure Temporal Web to use it to decode Payloads for a user in 2 ways: +If you are using a codec on your workers to encrypt or compress Temporal Payloads you may wish to deploy a codec server so that your users can see the decoded Payloads while using Temporal Web. The samples for the Temporal SDK you are using for your application should include examples of how to build a codec server. Please let us know if this is not the case. Once you have a codec server running you can configure Temporal Web to use it to decode Payloads for a user in 2 ways: 1. Edit the `server/config.yml` file: ```yaml - data_encoder: - endpoint: https://remote_encoder.myorg.com + codec: + endpoint: https://codec.myorg.com ``` -2. Set the environment variable TEMPORAL_DATA_ENCODER_ENDPOINT to the URL for your remote data encoder. This is often a more convenient option when running Temporal Web in a docker container. +2. Set the environment variable TEMPORAL_CODEC_ENDPOINT to the URL for your remote codec server. This is often a more convenient option when running Temporal Web in a docker container. -Temporal Web will then configure it's UI to decode Payloads as appropriate via the remote data encoder. +Temporal Web will then configure it's UI to decode Payloads as appropriate via the codec. -Please note that requests to the remote data encoder will be made from the user's browser directly, not via Temporal Web's server. This means that the Temporal Web server will never see the decoded Payloads and does not need to be able to connect to the remote data encoder. This allows using remote data encoders on internal and secure networks while using an externally hosted Temporal Web instance, such that provided by Temporal Cloud. +Please note that requests to the codec server will be made from the user's browser directly, not via Temporal Web's server. This means that the Temporal Web server will never see the decoded Payloads and does not need to be able to connect to the codec server. This allows using codec servers on internal and secure networks while using an externally hosted Temporal Web instance, such that provided by Temporal Cloud. ### Configuring Authentication (optional) diff --git a/client/features/data-conversion.js b/client/features/data-conversion.js index dfbc6f7d..f7357ed1 100644 --- a/client/features/data-conversion.js +++ b/client/features/data-conversion.js @@ -1,7 +1,13 @@ import WebSocketAsPromised from 'websocket-as-promised'; -export const convertEventPayloadsWithRemoteEncoder = async (namespace, events, endpointTemplate) => { - const headers = { 'Content-Type': 'application/json', 'X-Namespace': namespace }; +export const convertEventPayloadsWithCodec = async (namespace, events, endpointTemplate, accessToken) => { + let headers = { + 'Content-Type': 'application/json', + 'X-Namespace': namespace, + }; + if (accessToken) { + headers['Authorization'] = `Bearer ${accessToken}`; + } const requests = []; const endpoint = endpointTemplate.replaceAll('{namespace}', namespace); diff --git a/client/routes/workflow/index.vue b/client/routes/workflow/index.vue index 4a3e897b..abd094f9 100644 --- a/client/routes/workflow/index.vue +++ b/client/routes/workflow/index.vue @@ -80,7 +80,7 @@ import { import { NOTIFICATION_TYPE_ERROR } from '~constants'; import { getErrorMessage } from '~helpers'; import { NavigationBar, NavigationLink } from '~components'; -import { convertEventPayloadsWithWebsocket, convertEventPayloadsWithRemoteEncoder } from '~features/data-conversion'; +import { convertEventPayloadsWithWebsocket, convertEventPayloadsWithCodec } from '~features/data-conversion'; export default { data() { @@ -139,7 +139,7 @@ export default { )}/${encodeURIComponent(runId)}`; }, historyUrl() { - const rawPayloads = (this.webSettings?.dataConverter?.port || this.webSettings?.dataEncoder?.endpoint) + const rawPayloads = (this.webSettings?.dataConverter?.port || this.webSettings?.codec?.endpoint) ? '&rawPayloads=true' : ''; const historyUrl = `${this.baseAPIURL}/history?waitForNewEvent=true${rawPayloads}`; @@ -205,7 +205,7 @@ export default { }) .then(events => { const port = this.webSettings?.dataConverter?.port; - const endpoint = this.webSettings?.dataEncoder?.endpoint; + const endpoint = this.webSettings?.codec?.endpoint; if (port !== undefined) { return convertEventPayloadsWithWebsocket(events, port).catch(error => { @@ -221,7 +221,9 @@ export default { } if (endpoint !== undefined) { - return convertEventPayloadsWithRemoteEncoder(this.namespace, events, endpoint).catch(error => { + const accessToken = this.webSettings.codec.accessToken; + + return convertEventPayloadsWithCodec(this.namespace, events, endpoint, accessToken).catch(error => { console.error(error); this.$emit('onNotification', { diff --git a/server/config.yml b/server/config.yml index a5cd56f7..5a452daf 100644 --- a/server/config.yml +++ b/server/config.yml @@ -17,6 +17,7 @@ routing: default_to_namespace: # internal use only issue_report_link: https://github.com/temporalio/web/issues/new/choose # set this field if you need to direct people to internal support forums -# data_encoder: - # Remote Data Encoder Endpoint - # endpoint: https://remote_encoder.myorg.com \ No newline at end of file +# codec: + # Codec Server Endpoint + # endpoint: https://codec.myorg.com + # pass_access_token: false \ No newline at end of file diff --git a/server/config/index.js b/server/config/index.js index f30ba468..5ff4947e 100644 --- a/server/config/index.js +++ b/server/config/index.js @@ -4,7 +4,8 @@ const yaml = require('js-yaml'); const logger = require('../logger'); const configPath = process.env.TEMPORAL_CONFIG_PATH || './server/config.yml'; -const dataEncoderEndpoint = process.env.TEMPORAL_DATA_ENCODER_ENDPOINT; +const codecEndpointFromEnv = process.env.TEMPORAL_CODEC_ENDPOINT; +const codecPassAccessTokenFromEnv = process.env.TEMPORAL_CODEC_PASS_ACCESS_TOKEN ? ![false, 'false'].includes(process.env.TEMPORAL_CODEC_PASS_ACCESS_TOKEN) : undefined; const readConfigSync = () => { const cfgContents = readFileSync(configPath, { @@ -28,16 +29,15 @@ const getAuthConfig = async () => { return auth; }; -const getDataEncoderConfig = async () => { - let { data_encoder } = await readConfig(); +const getCodecConfig = async () => { + let { codec } = await readConfig(); - // Data encoder endpoint from the environment takes precedence over - // configuration file value. - const dataEncoderConfig = { - endpoint: dataEncoderEndpoint || data_encoder?.endpoint + const codecConfig = { + endpoint: codecEndpointFromEnv || codec?.endpoint, + passAccessToken: codecPassAccessTokenFromEnv || !!codec?.pass_access_token, } - return dataEncoderConfig; + return codecConfig; } const getRoutingConfig = async () => { @@ -84,7 +84,7 @@ logger.log( module.exports = { getAuthConfig, - getDataEncoderConfig, + getCodecConfig, getRoutingConfig, getTlsConfig, }; diff --git a/server/routes.js b/server/routes.js index 32aa80df..0c1e21e5 100644 --- a/server/routes.js +++ b/server/routes.js @@ -3,7 +3,7 @@ const Router = require('koa-router'), moment = require('moment'), losslessJSON = require('lossless-json'), { isWriteApiPermitted } = require('./utils'), - { getAuthConfig, getRoutingConfig, getDataEncoderConfig } = require('./config'), + { getAuthConfig, getRoutingConfig, getCodecConfig } = require('./config'), authRoutes = require('./routes-auth'), { getTemporalClient: tClient } = require('./temporal-client-provider'); @@ -350,22 +350,26 @@ router.post('/api/web-settings/data-converter/:port', async (ctx) => { ctx.status = 200; }); -router.post('/api/web-settings/remote-data-encoder/:endpoint', async (ctx) => { - ctx.session.dataEncoder = { endpoint: ctx.params.endpoint }; +router.post('/api/web-settings/codec/:endpoint', async (ctx) => { + ctx.session.codec = { endpoint: ctx.params.endpoint }; ctx.status = 200; }); router.get('/api/web-settings', async (ctx) => { const routing = await getRoutingConfig(); const { enabled } = await getAuthConfig(); - const dataEncoder = await getDataEncoderConfig(); + const codecConfig = await getCodecConfig(); const permitWriteApi = isWriteApiPermitted(); const dataConverter = ctx.session.dataConverter; - - // Encoder endpoint from the session has higher priority than global config. + + // Codec endpoint from the session has higher priority than global config. // This is to allow for testing of new remote encoder endpoints. - if (ctx.session.dataEncoder?.endpoint) { - dataEncoder.endpoint = ctx.session.dataEncoder.endpoint; + let codec = { + endpoint: ctx.session.codec?.endpoint || codecConfig.endpoint + }; + + if (codecConfig.passAccessToken && !!ctx.state.user) { + codec.accessToken = ctx.state.user.accessToken; } const auth = { enabled }; // only include non-sensitive data @@ -375,7 +379,7 @@ router.get('/api/web-settings', async (ctx) => { auth, permitWriteApi, dataConverter, - dataEncoder, + codec, }; });