diff --git a/charts/traction/README.md b/charts/traction/README.md index ad6942b9f..a6c56e16c 100644 --- a/charts/traction/README.md +++ b/charts/traction/README.md @@ -312,6 +312,7 @@ kubectl delete secret,pvc --selector "app.kubernetes.io/instance"=my-release | `ui.oidc.roleName` | OIDC role name | `innkeeper` | | `ui.oidc.session.timeoutSeconds` | OIDC session timeout seconds | `600` | | `ui.oidc.session.countdownSeconds` | OIDC session countdown seconds | `30` | +| `ui.oidc.extraQueryParams` | OIDC client login additional parameters | `{}` | | `ui.smtp.server` | SMTP server | `""` | | `ui.smtp.port` | SMTP port | `25` | | `ui.smtp.senderAddress` | SMTP sender address | `""` | diff --git a/charts/traction/templates/ui/configmap.yaml b/charts/traction/templates/ui/configmap.yaml index b4a006f38..53c2a6e8e 100644 --- a/charts/traction/templates/ui/configmap.yaml +++ b/charts/traction/templates/ui/configmap.yaml @@ -10,6 +10,7 @@ data: FRONTEND_INNKEEPER_OIDC_AUTHORITY: {{ .Values.ui.oidc.authority | quote }} FRONTEND_INNKEEPER_OIDC_CLIENT: {{ .Values.ui.oidc.client | quote }} FRONTEND_INNKEEPER_OIDC_LABEL: {{ .Values.ui.oidc.label | quote }} + FRONTEND_INNKEEPER_OIDC_EXTRA_PARAMS: {{ .Values.ui.oidc.extraQueryParams | quote }} FRONTEND_INNKEEPER_SHOW_ADMIN: {{ .Values.ui.oidc.showInnkeeperAdminLogin | quote }} FRONTEND_TENANT_SHOW_WRITABLE_COMPONENTS: {{ .Values.ui.oidc.showWritableComponents | quote }} FRONTEND_SHOW_OIDC_RESERVATION_LOGIN: {{ .Values.ui.showOIDCReservationLogin | quote }} diff --git a/charts/traction/values.yaml b/charts/traction/values.yaml index 002016486..1216be51f 100644 --- a/charts/traction/values.yaml +++ b/charts/traction/values.yaml @@ -651,6 +651,8 @@ ui: ## @param ui.oidc.reservationForm reservationForm: >- {} + ## @param ui.oidc.extraQueryParams + extraQueryParams: '{}' ## SMTP Configuration smtp: diff --git a/deploy/traction/values-development.yaml b/deploy/traction/values-development.yaml index c744d2db0..43d3fa8f1 100644 --- a/deploy/traction/values-development.yaml +++ b/deploy/traction/values-development.yaml @@ -96,6 +96,7 @@ ui: active: true authority: https://dev.loginproxy.gov.bc.ca/auth/realms/digitaltrust-citz jwksUri: https://dev.loginproxy.gov.bc.ca/auth/realms/digitaltrust-citz/protocol/openid-connect/certs + extraQueryParams: '{"kc_idp_hint":"idir"}' reservationForm: >- { "formDataSchema": { diff --git a/deploy/traction/values-pr.yaml b/deploy/traction/values-pr.yaml index 9d2ff097e..f74251c22 100644 --- a/deploy/traction/values-pr.yaml +++ b/deploy/traction/values-pr.yaml @@ -87,6 +87,7 @@ ui: active: true authority: https://dev.loginproxy.gov.bc.ca/auth/realms/digitaltrust-citz jwksUri: https://dev.loginproxy.gov.bc.ca/auth/realms/digitaltrust-citz/protocol/openid-connect/certs + extraQueryParams: '{"kc_idp_hint":"idir"}' reservationForm: >- { "formDataSchema": { diff --git a/services/tenant-ui/config/custom-environment-variables.json b/services/tenant-ui/config/custom-environment-variables.json index 70303ef5c..0bbe5d0c4 100644 --- a/services/tenant-ui/config/custom-environment-variables.json +++ b/services/tenant-ui/config/custom-environment-variables.json @@ -15,7 +15,8 @@ "active": "FRONTEND_INNKEEPER_OIDC_ACTIVE", "authority": "FRONTEND_INNKEEPER_OIDC_AUTHORITY", "client": "FRONTEND_INNKEEPER_OIDC_CLIENT", - "label": "FRONTEND_INNKEEPER_OIDC_LABEL" + "label": "FRONTEND_INNKEEPER_OIDC_LABEL", + "extraQueryParams": "FRONTEND_INNKEEPER_OIDC_EXTRA_PARAMS" }, "ux": { "appTitle": "UX_APP_TITLE", diff --git a/services/tenant-ui/config/default.json b/services/tenant-ui/config/default.json index e21b49d96..7760a80af 100644 --- a/services/tenant-ui/config/default.json +++ b/services/tenant-ui/config/default.json @@ -17,7 +17,8 @@ "active": false, "authority": "https://dev.loginproxy.gov.bc.ca/auth/realms/digitaltrust-citz", "client": "innkeeper-frontend", - "label": "IDIR" + "label": "IDIR", + "extraQueryParams": {} }, "ux": { "appTitle": "Traction Tenant Console", diff --git a/services/tenant-ui/frontend/src/helpers/index.ts b/services/tenant-ui/frontend/src/helpers/index.ts index 54b9795e9..57d649935 100644 --- a/services/tenant-ui/frontend/src/helpers/index.ts +++ b/services/tenant-ui/frontend/src/helpers/index.ts @@ -106,3 +106,18 @@ export function formatGuid(guid: string): string { export function stringOrBooleanTruthy(value: string | boolean) { return value === 'true' || value === true; } + +export function configStringToObject(value: string) { + // As config values come into the FE as strings, we need to convert them to objects + try { + // if an object IS supplied, return that + if (typeof value === 'object') return value; + return JSON.parse(value); + } catch (e) { + // If the value from config is not a stringified JSON object, return an empty object + console.warn( + `configStringToObject: non-string config value passed ${value}` + ); + return {}; + } +} diff --git a/services/tenant-ui/frontend/src/store/innkeeper/innkeeperOidcStore.ts b/services/tenant-ui/frontend/src/store/innkeeper/innkeeperOidcStore.ts index f055403ed..bdb886d81 100644 --- a/services/tenant-ui/frontend/src/store/innkeeper/innkeeperOidcStore.ts +++ b/services/tenant-ui/frontend/src/store/innkeeper/innkeeperOidcStore.ts @@ -5,6 +5,7 @@ import { useConfigStore } from '../configStore'; import { UserManager } from 'oidc-client-ts'; import { API_PATH } from '@/helpers/constants'; import { useTokenStore } from '../tokenStore'; +import { configStringToObject } from '@/helpers'; export const useInnkeeperOidcStore = defineStore('innkeeperOidcStore', () => { // other stores @@ -20,6 +21,9 @@ export const useInnkeeperOidcStore = defineStore('innkeeperOidcStore', () => { automaticSilentRenew: false, // don't need to renew for our needs at this point post_logout_redirect_uri: `${window.location.origin}/innkeeper`, loadUserInfo: true, + extraQueryParams: configStringToObject( + config.value.frontend.oidc.extraQueryParams || '' + ), }; const _userManager: UserManager = new UserManager(_settings); diff --git a/services/tenant-ui/frontend/src/store/oidc/oidcStore.ts b/services/tenant-ui/frontend/src/store/oidc/oidcStore.ts index 0dec4968a..568132e00 100644 --- a/services/tenant-ui/frontend/src/store/oidc/oidcStore.ts +++ b/services/tenant-ui/frontend/src/store/oidc/oidcStore.ts @@ -2,6 +2,7 @@ import { defineStore, storeToRefs } from 'pinia'; import { ref } from 'vue'; import { useConfigStore } from '../configStore'; import { UserManager } from 'oidc-client-ts'; +import { configStringToObject } from '@/helpers'; export const useOidcStore = defineStore('oidcStore', () => { // Stores @@ -15,6 +16,9 @@ export const useOidcStore = defineStore('oidcStore', () => { automaticSilentRenew: false, post_logout_redirect_uri: `${window.location.origin}`, loadUserInfo: true, + extraQueryParams: configStringToObject( + config.value.frontend.oidc.extraQueryParams || '' + ), }; const userManager: UserManager = new UserManager(settings); diff --git a/services/tenant-ui/frontend/test/helpers/index.test.ts b/services/tenant-ui/frontend/test/helpers/index.test.ts new file mode 100644 index 000000000..66a4c965b --- /dev/null +++ b/services/tenant-ui/frontend/test/helpers/index.test.ts @@ -0,0 +1,101 @@ +import { + configStringToObject, + toKebabCase, + formatGuid, + stringOrBooleanTruthy, + isJsonString, + formatUnixDate, +} from '@/helpers'; +import { expect, test, describe } from 'vitest'; + +describe('helpers/index.ts', () => { + describe('formatUnixDate', () => { + test('formats correctly with formatUnixDate', async () => { + expect(formatUnixDate(1703027241)).toEqual('December 19 2023'); + }); + }); + + describe('toKebabCase', () => { + test('formats correctly with toKebabCase', async () => { + expect(toKebabCase('abc')).toEqual('abc'); + expect(toKebabCase('AbcXyz123')).toEqual('abc-xyz123'); + }); + }); + + describe('formatGuid', () => { + test('formats correctly with formatGuid', async () => { + expect(formatGuid('d9cfa0d1ed514a4e9c63f6f620bbd360')).toEqual( + 'd9cfa0d1-ed51-4a4e-9c63-f6f620bbd360' + ); + expect(formatGuid('d9cfa0d1-ed51-4a4e-9c63-f6f620bbd360')).toEqual( + 'd9cfa0d1-ed51-4a4e-9c63-f6f620bbd360' + ); + }); + }); + + describe('stringOrBooleanTruthy', () => { + test('returns the right boolean for boolean supplied', async () => { + expect(stringOrBooleanTruthy(true)).toEqual(true); + expect(stringOrBooleanTruthy(false)).toEqual(false); + }); + + test('returns the right boolean for strings', async () => { + expect(stringOrBooleanTruthy('true')).toEqual(true); + expect(stringOrBooleanTruthy('false')).toEqual(false); + expect(stringOrBooleanTruthy('True')).toEqual(false); + }); + + test('returns the false for unexpected types', async () => { + // @ts-expect-error unit test other types + expect(stringOrBooleanTruthy(undefined)).toEqual(false); + // @ts-expect-error unit test other types + expect(stringOrBooleanTruthy(1)).toEqual(false); + // @ts-expect-error unit test other types + expect(stringOrBooleanTruthy(null)).toEqual(false); + // @ts-expect-error unit test other types + expect(stringOrBooleanTruthy({ a: 1 })).toEqual(false); + }); + }); + + describe('isJsonString', () => { + test('returns true for valid object strings', async () => { + expect(isJsonString('{ "abc": {"xyz": 123}}')).toEqual(true); + expect(isJsonString('{ }')).toEqual(true); + }); + + test('returns false for invalid object strings', async () => { + expect(isJsonString('test')).toEqual(false); + }); + + test('returns false for undefined', async () => { + // @ts-expect-error unit test other types + expect(isJsonString(undefined)).toEqual(false); + }); + + test('returns true for null object', async () => { + // @ts-expect-error unit test other types + expect(isJsonString(null)).toEqual(true); + }); + }); + + describe('configStringToObject', () => { + test('returns a parsed object from the format a cfg value comes in', async () => { + expect(configStringToObject('{"abc": {"xyz": 123}}')).toEqual({ + abc: { xyz: 123 }, + }); + }); + + test('returns a blank object for non parsed strings', async () => { + expect(configStringToObject('testz')).toEqual({}); + // @ts-expect-error unit test other types + expect(configStringToObject(undefined)).toEqual({}); + }); + + test('returns back objects if object type is supplied', async () => { + // @ts-expect-error unit test other types + expect(configStringToObject({ abc: '123' })).toEqual({ abc: '123' }); + // @ts-expect-error unit test other types + expect(configStringToObject(null)).toEqual(null); + }); + }); +});