From f7f962db9231d0d9186b1037bd845b1313c14bc6 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Thu, 10 Oct 2024 10:47:42 +0800 Subject: [PATCH] dashboard: fix not redirecting to login (#1017) Signed-off-by: Teo Koon Peng --- packages/dashboard/.gitignore | 1 + .../dashboard/examples/keycloak/README.md | 34 +++ .../examples/keycloak/api_server_config.py | 14 ++ .../dashboard/examples/keycloak/index.html | 18 ++ .../dashboard/examples/keycloak/index.tsx | 126 +++++++++++ .../examples/keycloak/keycloak-setup.bash | 205 ++++++++++++++++++ .../dashboard/examples/keycloak/utils.bash | 63 ++++++ .../src/components/rmf-dashboard.tsx | 144 ++++++------ packages/dashboard/src/services/keycloak.ts | 1 - packages/dashboard/src/services/rmf-api.ts | 9 - 10 files changed, 538 insertions(+), 77 deletions(-) create mode 100644 packages/dashboard/examples/keycloak/README.md create mode 100644 packages/dashboard/examples/keycloak/api_server_config.py create mode 100644 packages/dashboard/examples/keycloak/index.html create mode 100644 packages/dashboard/examples/keycloak/index.tsx create mode 100755 packages/dashboard/examples/keycloak/keycloak-setup.bash create mode 100644 packages/dashboard/examples/keycloak/utils.bash diff --git a/packages/dashboard/.gitignore b/packages/dashboard/.gitignore index abe6a5761..2e53989ea 100644 --- a/packages/dashboard/.gitignore +++ b/packages/dashboard/.gitignore @@ -29,3 +29,4 @@ web_server.log /.rmf *storybook.log +/keycloak-example.pub diff --git a/packages/dashboard/examples/keycloak/README.md b/packages/dashboard/examples/keycloak/README.md new file mode 100644 index 000000000..684821ae6 --- /dev/null +++ b/packages/dashboard/examples/keycloak/README.md @@ -0,0 +1,34 @@ +This is an example using a local keycloak authentication provider. + +Follow the instructions below to run the example + +1. Start a keycloak instance + +```bash +docker run --rm -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:25.0.2 start-dev +``` + +2. Setup keycloak for rmf-web + +```bash +examples/keycloak/keycloak-setup.bash -u admin -o keycloak-example.pub +``` + +when prompted for the admin password, use "admin" + +the script will + +1. create a "rmf-web" realm +1. create a user in "rmf-web" realm with the same credentials as the keycloak admin +1. create a "dashboard" client +1. create a rmf api server client scope +1. create mappers for rmf api server client scopes +1. link the dashboard client with the client scope +1. export the public key + +3. Start rmf api server with the keycloak config + +```bash +# be sure to run from packages/dashboard directory and source a supported rmf installation +RMF_API_SERVER_CONFIG="$(pwd)/examples/keycloak/api_server_config.py" pnpm -C ../api-server start +``` diff --git a/packages/dashboard/examples/keycloak/api_server_config.py b/packages/dashboard/examples/keycloak/api_server_config.py new file mode 100644 index 000000000..c7cb76063 --- /dev/null +++ b/packages/dashboard/examples/keycloak/api_server_config.py @@ -0,0 +1,14 @@ +from os.path import dirname + +from api_server.default_config import config + +here = dirname(__file__) +run_dir = f"{here}/run" + +config.update( + { + "jwt_public_key": f"{here}/../../keycloak-example.pub", + "jwt_secret": None, + "iss": "http://localhost:8080/realms/rmf-web", + } +) diff --git a/packages/dashboard/examples/keycloak/index.html b/packages/dashboard/examples/keycloak/index.html new file mode 100644 index 000000000..f13043862 --- /dev/null +++ b/packages/dashboard/examples/keycloak/index.html @@ -0,0 +1,18 @@ + + + + + + + + + RMF Dashboard + + +
+ + + diff --git a/packages/dashboard/examples/keycloak/index.tsx b/packages/dashboard/examples/keycloak/index.tsx new file mode 100644 index 000000000..b5f5c7250 --- /dev/null +++ b/packages/dashboard/examples/keycloak/index.tsx @@ -0,0 +1,126 @@ +import '@fontsource/roboto/300.css'; +import '@fontsource/roboto/400.css'; +import '@fontsource/roboto/500.css'; +import '@fontsource/roboto/700.css'; + +import ReactDOM from 'react-dom/client'; +import { + InitialWindow, + LocallyPersistentWorkspace, + RmfDashboard, + Workspace, +} from 'rmf-dashboard/components'; +import { MicroAppManifest } from 'rmf-dashboard/components/micro-app'; +import doorsApp from 'rmf-dashboard/micro-apps/doors-app'; +import liftsApp from 'rmf-dashboard/micro-apps/lifts-app'; +import createMapApp from 'rmf-dashboard/micro-apps/map-app'; +import robotMutexGroupsApp from 'rmf-dashboard/micro-apps/robot-mutex-groups-app'; +import robotsApp from 'rmf-dashboard/micro-apps/robots-app'; +import tasksApp from 'rmf-dashboard/micro-apps/tasks-app'; +import KeycloakAuthenticator from 'rmf-dashboard/services/keycloak'; + +const mapApp = createMapApp({ + attributionPrefix: 'Open-RMF', + defaultMapLevel: 'L1', + defaultRobotZoom: 20, + defaultZoom: 6, +}); + +const appRegistry: MicroAppManifest[] = [ + mapApp, + doorsApp, + liftsApp, + robotsApp, + robotMutexGroupsApp, + tasksApp, +]; + +const homeWorkspace: InitialWindow[] = [ + { + layout: { x: 0, y: 0, w: 12, h: 6 }, + microApp: mapApp, + }, +]; + +const robotsWorkspace: InitialWindow[] = [ + { + layout: { x: 0, y: 0, w: 7, h: 4 }, + microApp: robotsApp, + }, + { layout: { x: 8, y: 0, w: 5, h: 8 }, microApp: mapApp }, + { layout: { x: 0, y: 0, w: 7, h: 4 }, microApp: doorsApp }, + { layout: { x: 0, y: 0, w: 7, h: 4 }, microApp: liftsApp }, + { layout: { x: 8, y: 0, w: 5, h: 4 }, microApp: robotMutexGroupsApp }, +]; + +const tasksWorkspace: InitialWindow[] = [ + { layout: { x: 0, y: 0, w: 7, h: 8 }, microApp: tasksApp }, + { layout: { x: 8, y: 0, w: 5, h: 8 }, microApp: mapApp }, +]; + +export default function App() { + return ( + , + }, + { + name: 'Robots', + route: 'robots', + element: , + }, + { + name: 'Tasks', + route: 'tasks', + element: , + }, + { + name: 'Custom', + route: 'custom', + element: ( + + ), + }, + ]} + /> + ); +} + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render(); diff --git a/packages/dashboard/examples/keycloak/keycloak-setup.bash b/packages/dashboard/examples/keycloak/keycloak-setup.bash new file mode 100755 index 000000000..f7664c495 --- /dev/null +++ b/packages/dashboard/examples/keycloak/keycloak-setup.bash @@ -0,0 +1,205 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_NAME="$(basename "$0")" +PARSED=$(getopt -o 'u:o:' -n $SCRIPT_NAME -- "$@") +eval set -- "$PARSED" + +KEYCLOAK_ADMIN= +KEY_OUT= +while true; do + case "$1" in + -u) + KEYCLOAK_ADMIN="$2" + shift 2 + ;; + -o) + KEY_OUT="$2" + shift 2 + ;; + *) + shift + break + esac +done + +print_usage() { + echo "Usage: $SCRIPT_NAME -u -o " +} + +if [[ ! $KEYCLOAK_ADMIN || ! $KEY_OUT ]]; then + print_usage + exit 1 +fi + +. "$(dirname "$0")/utils.bash" + + +: ${ROOT_URL:='http://localhost:5173'} # root url of the client +: ${KEYCLOAK_BASE_URL='http://localhost:8080'} + +REALM_URL="$KEYCLOAK_BASE_URL/admin/realms" +REALM_CLIENT_URL="$REALM_URL/rmf-web/clients" +REALM_USERS_URL="$REALM_URL/rmf-web/users" +REALM_EVENTS_URL="$REALM_URL/rmf-web/events" +REALM_CLIENT_SCOPES_URL="$REALM_URL/rmf-web/client-scopes" + +command -v jq >>/dev/null || { echo "Please install jq dependency (sudo apt install jq)"; exit 1; } + +__msg_info "Retrieving JWT Token" +read -p "Enter password for $KEYCLOAK_ADMIN: " -s KEYCLOAK_ADMIN_PASSWD +kc_login "$KEYCLOAK_BASE_URL" "$KEYCLOAK_ADMIN" "$KEYCLOAK_ADMIN_PASSWD"> /dev/null + +__msg_info "Creating rmf-web realm" +REALM_DATA="$(jq -cn '{ + "id":"rmf-web", + "realm":"rmf-web", + "enabled":"true" +}')" +# try to update the realm if it already exists, else create it +REALM_CREATION_RESPONSE=$(kc_api -X PUT \ + -d "$REALM_DATA" \ + "$REALM_URL/rmf-web") && exit_code=0 || exit_code=$? +if [[ $exit_code != 0 ]]; then + __msg_info "Creating realm rmf-web" + REALM_CREATION_RESPONSE=$(kc_api -X POST \ + -d "$REALM_DATA" \ + "$REALM_URL") && exit_code=0 || exit_code=$? + if [[ $exit_code != 0 ]]; then + __error_exit $LINENO "Failed to create realm" + fi + __msg_info "Realm rmf-web created" +else + __msg_info "Realm already exists, skipping" +fi + +__msg_info "Creating Admin User." +RMF_WEB_USER_ID=$(kc_api -X GET \ + "$REALM_USERS_URL?username=$KEYCLOAK_ADMIN" | jq -r ".[0].id") +if [[ "$RMF_WEB_USER_ID" == "null" ]]; then + RMF_WEB_USER_DATA="$(jq -cn --arg username "$KEYCLOAK_ADMIN" '{ + "username": $username, + "enabled": true, + "email": "\($username)@example.com", + "emailVerified": true, + "firstName": $username, + "lastName": $username + }')" + USER_CREATION_RESPONSE=$(kc_api -X POST \ + -d "$RMF_WEB_USER_DATA" \ + "$REALM_USERS_URL") || __error_exit $LINENO "Failed to create admin user" + RMF_WEB_USER_ID=$(kc_api -X GET \ + "$REALM_USERS_URL?username=$KEYCLOAK_ADMIN" | jq -r ".[0].id") + RESET_PASSWORD_RESPONSE=$(kc_api -X PUT \ + -d '{"value": "'$KEYCLOAK_ADMIN_PASSWD'", "temporary": "false"}' \ + "$REALM_USERS_URL/$RMF_WEB_USER_ID/reset-password") || __error_exit $LINENO "Something went wrong resetting admin password." + __msg_info "rmf-web user created" +else + __msg_debug "rmf-web user already exists. Skipping." +fi + +DASHBOARD_CLIENT_REQUEST_JSON=$( + jq -cn \ + --arg rootUrl "$ROOT_URL" \ + --arg redirectRootUrl "$ROOT_URL/*" \ + '{ + "clientId": "dashboard", + "rootUrl": $rootUrl, + "redirectUris": [$redirectRootUrl], + "webOrigins": [$rootUrl], + "publicClient": true + }' +) + +__msg_info "Creating dashboard client" +DASHBOARD_CLIENT_ID=$(kc_api -X GET \ + "$REALM_CLIENT_URL?clientId=dashboard" | jq -r '.[0].id') +if [[ $DASHBOARD_CLIENT_ID == "null" ]]; then + CLIENT_DASHBOARD_CREATION_RESPONSE=$(kc_api -X POST \ + -d "$DASHBOARD_CLIENT_REQUEST_JSON" \ + "$REALM_CLIENT_URL") || __error_exit $LINENO "Failed to create dashboard client" + DASHBOARD_CLIENT_ID=$(kc_api -X GET \ + "$REALM_CLIENT_URL?clientId=dashboard" | jq -r '.[0].id') + __msg_info "Created dashboard client" +else + __msg_info "Found existing dashboard client. Updating instead." + CLIENT_DASHBOARD_CREATION_RESPONSE=$(kc_api -X PUT \ + -d "$DASHBOARD_CLIENT_REQUEST_JSON" \ + "$REALM_CLIENT_URL/$DASHBOARD_CLIENT_ID") || __error_exit $LINENO "Failed to update dashboard client" + __msg_info "Updated dashboard client" +fi + +RMF_API_SERVER_CLIENT_SCOPE_DATA="$(jq -cn '{ + "name": "rmf_api_server", + "protocol": "openid-connect", + "description": "rmf_api_server audience scope" +}')" +__msg_info "Creating rmf api server client scope" +RMF_API_SERVER_CLIENT_SCOPE_ID=$(kc_api -X GET \ + "$REALM_CLIENT_SCOPES_URL" | jq -r '.[] | select ( .name == "rmf_api_server" ) | .id') +if [[ $RMF_API_SERVER_CLIENT_SCOPE_ID != "" ]]; then + __msg_info "Found existing client scope, skipping" +else + CLIENT_SCOPE_CREATION_RESPONSE=$(kc_api -X POST \ + -d "$RMF_API_SERVER_CLIENT_SCOPE_DATA" \ + "$REALM_CLIENT_SCOPES_URL") || __error_exit $LINENO "Failed to create client scope" + RMF_API_SERVER_CLIENT_SCOPE_ID=$(kc_api -X GET \ + "$REALM_CLIENT_SCOPES_URL" | jq -r '.[] | select ( .name == "rmf_api_server" ) | .id') + __msg_info "Created rmf api server client scope" +fi + +RMF_API_SERVER_MAPPER_DATA="$(jq -cn '{ + "name": "rmf_api_server_audience", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "access.token.claim": "true", + "id.token.claim": "false", + "included.client.audience": "rmf_api_server" + } +}')" +__msg_info "Creating rmf api server protocol mappers" +PROTOCOL_MAPPERS_RESP="$(kc_api -X GET "$REALM_CLIENT_SCOPES_URL/$RMF_API_SERVER_CLIENT_SCOPE_ID/protocol-mappers/models")" +RMF_API_SERVER_MAPPER_ID="$(echo $PROTOCOL_MAPPERS_RESP | jq -r '.[] | select ( .name == "rmf_api_server_audience" ) | .id')" +if [[ $RMF_API_SERVER_MAPPER_ID ]]; then + __msg_debug "RMF_API_SERVER_MAPPER_ID=$RMF_API_SERVER_MAPPER_ID" + __msg_info "Found existing rmf api server audience mapper, updating instead" + MAPPER_DATA_WITH_ID="$(echo "$RMF_API_SERVER_MAPPER_DATA" | jq -c --arg mapper_id "$RMF_API_SERVER_MAPPER_ID" '. + {"id": $mapper_id}')" + kc_api -X PUT \ + -d "$MAPPER_DATA_WITH_ID" \ + "$REALM_CLIENT_SCOPES_URL/$RMF_API_SERVER_CLIENT_SCOPE_ID/protocol-mappers/models/$RMF_API_SERVER_MAPPER_ID" > /dev/null + __msg_info "Updated rmf api server protocol mappers" +else + MAPPER_DATA_WITH_ID="$(echo "$RMF_API_SERVER_MAPPER_DATA" | jq -c --arg mapper_id "$RMF_API_SERVER_MAPPER_ID" '. + {"id": $mapper_id}')" + kc_api -X POST \ + -d "$RMF_API_SERVER_MAPPER_DATA" \ + "$REALM_CLIENT_SCOPES_URL/$RMF_API_SERVER_CLIENT_SCOPE_ID/protocol-mappers/models" > /dev/null + __msg_info "Created rmf api server protocol mappers" +fi + +__msg_info "Linking up Client Scopes and Clients." +kc_api -X PUT \ + -d '{"value": "admin", "temporary": "false"}' \ + "$REALM_CLIENT_URL/$DASHBOARD_CLIENT_ID/default-client-scopes/$RMF_API_SERVER_CLIENT_SCOPE_ID" > /dev/null || \ + __error_exit $LINENO "Something went wrong assigning client scope." + +__msg_info "Fetching token public key" + +JWKS_URI=$(kc_api -X GET \ + "$KEYCLOAK_BASE_URL/realms/rmf-web/.well-known/openid-configuration" | jq -r '.jwks_uri') +__msg_debug "JWKS_URL=$JWKS_URI" + +JWKS_X5C=$(kc_api -X GET \ + "$JWKS_URI" | jq -r '[ .keys[] | select(.use == "sig") ][0].x5c[0]') +__msg_debug "JWKS_X5C=$JWKS_X5C" + +[[ -n "$JWKS_X5C" ]] || __error_exit $LINENO "Something went wrong trying to retrieve certificate." + +PEM_FILE="-----BEGIN CERTIFICATE----- +$JWKS_X5C +-----END CERTIFICATE-----" +PUB_KEY=$(echo "$PEM_FILE" | openssl x509 -pubkey -noout) + +__msg_info "Saving public key to $KEY_OUT" +echo -e "$PUB_KEY" > "$KEY_OUT" diff --git a/packages/dashboard/examples/keycloak/utils.bash b/packages/dashboard/examples/keycloak/utils.bash new file mode 100644 index 000000000..31616b504 --- /dev/null +++ b/packages/dashboard/examples/keycloak/utils.bash @@ -0,0 +1,63 @@ +#/usr/bin/bash + +LOG_ERROR=1 +__msg_error() { + { [[ $LOG_ERROR == "1" ]] && echo -e "\e[31m[ERROR]:" "$@" "\e[0m" >&2; } || true +} + +LOG_WARN=1 +__msg_warn() { + { [[ $LOG_WARN == "1" ]] && echo -e "\e[33m[WARN]:" "$@" "\e[0m" >&2; } || true +} + +LOG_DEBUG=0 +__msg_debug() { + { [[ $LOG_DEBUG == "1" ]] && echo -e "\e[90m[DEBUG]:" "$@" "\e[0m" >&2; } || true +} + +LOG_INFO=1 +__msg_info() { + { [[ $LOG_INFO == "1" ]] && echo -e "\e[34m[INFO]:" "$@" "\e[0m" >&2; } || true +} + +__error_exit() { + local line + line="$1" + shift + __msg_error "$SCRIPT_NAME:$line" "$@" + exit 1 +} + +KC_TOKEN= + +kc_api() { + local exit_code + local resp + __msg_debug kc_api ${*@Q} + curl -ks --fail-with-body \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $KC_TOKEN" \ + "$@" +} + +# usage: kc_login +kc_login() { + local base_url=$1 + local user=$2 + local passwd=$3 + TOKEN_REQUEST_RESPONSE="$(curl -ks --fail-with-body -X POST \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=$user" \ + -d "password=$passwd" \ + -d "grant_type=password" \ + -d "client_id=admin-cli" \ + "$base_url/realms/master/protocol/openid-connect/token" | + jq -r '.access_token' + )" || __msg_error "Failed to retrieve token, is Keycloak up?" + if [[ "$TOKEN_REQUEST_RESPONSE" == "null" ]]; then + echo 'Something went wrong retrieving token. Check credentials.' + else + KC_TOKEN="$TOKEN_REQUEST_RESPONSE" + echo 'Successfully logged in to keycloak' + fi +} diff --git a/packages/dashboard/src/components/rmf-dashboard.tsx b/packages/dashboard/src/components/rmf-dashboard.tsx index 471e713ad..3b9a24b06 100644 --- a/packages/dashboard/src/components/rmf-dashboard.tsx +++ b/packages/dashboard/src/components/rmf-dashboard.tsx @@ -19,7 +19,7 @@ import { Resources, ResourcesProvider } from '../hooks/use-resources'; import { RmfApiProvider } from '../hooks/use-rmf-api'; import { SettingsProvider } from '../hooks/use-settings'; import { TaskRegistry, TaskRegistryProvider } from '../hooks/use-task-registry'; -import { UserProfileProvider, useUserProfile } from '../hooks/use-user-profile'; +import { UserProfileProvider } from '../hooks/use-user-profile'; import { LoginPage } from '../pages'; import { Authenticator, UserProfile } from '../services/authenticator'; import { DefaultRmfApi } from '../services/rmf-api'; @@ -120,6 +120,7 @@ export function RmfDashboard(props: RmfDashboardProps) { themes, resources, tasks, + baseUrl = import.meta.env.BASE_URL, alertAudioPath, } = props; @@ -153,12 +154,25 @@ export function RmfDashboard(props: RmfDashboardProps) { ); const [userProfile, setUserProfile] = React.useState(null); + const [authReady, setAuthReady] = React.useState(false); React.useEffect(() => { (async () => { await authenticator.init(); - const user = (await rmfApi.defaultApi.getUserUserGet()).data; - const perm = (await rmfApi.defaultApi.getEffectivePermissionsPermissionsGet()).data; - setUserProfile({ user, permissions: perm }); + if (!authenticator.user) { + setUserProfile(null); + setAuthReady(true); + return; + } + try { + const user = (await rmfApi.defaultApi.getUserUserGet()).data; + const perm = (await rmfApi.defaultApi.getEffectivePermissionsPermissionsGet()).data; + setUserProfile({ user, permissions: perm }); + } catch (e) { + console.error(e); + setUserProfile(null); + } finally { + setAuthReady(true); + } })(); }, [authenticator, rmfApi]); @@ -194,59 +208,71 @@ export function RmfDashboard(props: RmfDashboardProps) { return themes[settings.themeMode] || themes.default; }, [themes, settings.themeMode]); - const providers = userProfile && ( - - - - - - - - - - - - - - {/* TODO: Support stacking of alerts */} - setShowAlert(false)} - autoHideDuration={alertDuration} - > - + + + ) : ( + authenticator.login(`${window.location.origin}${baseUrl}`)} + /> + ) + } + /> + : } + > + + {userProfile && ( + + + + + + + + + + + + + {/* TODO: Support stacking of alerts */} + setShowAlert(false)} - severity={alertSeverity} - sx={{ width: '100%' }} + autoHideDuration={alertDuration} > - {alertMessage} - - - - - - - - - - - + setShowAlert(false)} + severity={alertSeverity} + sx={{ width: '100%' }} + > + {alertMessage} + + + + + + + + + + + )} + ); return theme ? {providers} : providers; } -interface RequireAuthProps { - redirectTo: string; - children: React.ReactNode; -} - -function RequireAuth({ redirectTo, children }: RequireAuthProps) { - const userProfile = useUserProfile(); - return userProfile ? children : ; -} - function NotFound() { return ( @@ -265,11 +291,9 @@ interface DashboardContentsProps extends RmfDashboardProps { } function DashboardContents({ - authenticator, helpLink, reportIssueLink, themes, - resources, tabs, baseUrl = import.meta.env.BASE_URL, extraAppbarItems, @@ -290,16 +314,6 @@ function DashboardContents({ return ( - authenticator.login(`${window.location.origin}${baseUrl}`)} - /> - } - /> @@ -328,11 +342,7 @@ function DashboardContents({ } > {allTabs.map((t) => ( - {t.element}} - /> + ))} } /> diff --git a/packages/dashboard/src/services/keycloak.ts b/packages/dashboard/src/services/keycloak.ts index 399aaf15c..f8904410c 100644 --- a/packages/dashboard/src/services/keycloak.ts +++ b/packages/dashboard/src/services/keycloak.ts @@ -85,7 +85,6 @@ export class KeycloakAuthenticator this._user = this._inst.tokenParsed && this._getUser(); this._isAdmin = this._isUserAdmin(); - console.log(this._inst.tokenParsed); this._initialized = true; } diff --git a/packages/dashboard/src/services/rmf-api.ts b/packages/dashboard/src/services/rmf-api.ts index 57b78047e..cbcc75f0f 100644 --- a/packages/dashboard/src/services/rmf-api.ts +++ b/packages/dashboard/src/services/rmf-api.ts @@ -119,15 +119,6 @@ export class DefaultRmfApi implements RmfApi { console.error(`Axios request error: ${error}`); }, ); - axiosInst.interceptors.response.use( - (response) => response, - (error) => { - console.error(`Axios response error: ${error}`); - if (error.response.status === 401) { - window.location.href = '/'; - } - }, - ); const apiConfig = new Configuration({ accessToken: authenticator.token, basePath: apiServerUrl,