Skip to content

Commit

Permalink
Keycloak JS upgrade
Browse files Browse the repository at this point in the history
Version 26 of the 'keycloak-js' module has introduced siginificant changes.

See here for the upgrade guide: https://www.keycloak.org/docs/latest/upgrading/index.html#keycloak-js
  • Loading branch information
TomJKing committed Dec 3, 2024
1 parent c6bb8c7 commit eb9c197
Show file tree
Hide file tree
Showing 12 changed files with 71 additions and 64 deletions.
5 changes: 4 additions & 1 deletion app/configuration/ApplicationConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ class ApplicationConfig @Inject() (configuration: Configuration) {
get("consignmentapi.url"),
get("environment"),
get("region"),
get("upload.url")
get("upload.url"),
get("auth.url"),
get("auth.clientId"),
get("auth.realm")
)

val numberOfItemsOnViewTransferPage: Int = configuration.get[Int]("viewTransfers.numberOfItemsPerPage")
Expand Down
3 changes: 3 additions & 0 deletions app/views/partials/frontEndInputs.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
<input type="hidden" class="stage" value="@frontEndInfo.stage">
<input type="hidden" class="region" value="@frontEndInfo.region">
<input type="hidden" class="upload-url" value="@frontEndInfo.uploadUrl">
<input type="hidden" class="auth-url" value="@frontEndInfo.authUrl">
<input type="hidden" class="client-id" value="@frontEndInfo.clientId">
<input type="hidden" class="realm" value="@frontEndInfo.realm">
5 changes: 4 additions & 1 deletion app/viewsapi/FrontEndInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ case class FrontEndInfo(
apiUrl: String,
stage: String,
region: String,
uploadUrl: String
uploadUrl: String,
authUrl: String,
clientId: String,
realm: String
)
2 changes: 2 additions & 0 deletions conf/application.base.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pekko.actor.warn-about-java-serializer-usage = "off"
auth.secret = ${AUTH_SECRET}
read.auth.secret = ${READ_AUTH_SECRET}
auth.url=${AUTH_URL}
auth.clientId="tdr-fe"
auth.realm="tdr"

consignmentapi.url = ${consignmentapi.domain}"/graphql"

Expand Down
45 changes: 7 additions & 38 deletions npm/package-lock.json

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

2 changes: 1 addition & 1 deletion npm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"copyfiles": "^2.4.1",
"events": "^3.3.0",
"graphql": "^16.9.0",
"keycloak-js": "25.0.6",
"keycloak-js": "26.0.4",
"minify": "^11.4.1",
"npm-run-all": "^4.1.5",
"sass": "^1.80.4",
Expand Down
21 changes: 12 additions & 9 deletions npm/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import Keycloak from "keycloak-js"
import { IKeycloakTokenParsed } from "../upload"
import {IFrontEndInfo} from "../index";

export const getKeycloakInstance: () => Promise<
Keycloak.KeycloakInstance | Error
> = async () => {
const keycloakInstance: Keycloak.KeycloakInstance = new Keycloak(
`${window.location.origin}/keycloak.json`
)
export const getKeycloakInstance: (frontEndInfo: IFrontEndInfo) => Promise<
Keycloak | Error
> = async (frontEndInfo: IFrontEndInfo) => {
const keycloakInstance = new Keycloak({
url: `${frontEndInfo.authUrl}`,
realm: `${frontEndInfo.realm}`,
clientId: `${frontEndInfo.clientId}`
})
const errorHandlingModule = await import("../errorhandling")
try {
const authenticated = await keycloakInstance.init({
Expand Down Expand Up @@ -38,7 +41,7 @@ const isRefreshTokenExpired: (
}

export const scheduleTokenRefresh: (
keycloak: Keycloak.KeycloakInstance,
keycloak: Keycloak,
cookiesUrl: string,
idleSessionMinValiditySecs?: number
) => void = (keycloak, cookiesUrl, idleSessionMinValiditySecs = 60) => {
Expand All @@ -64,7 +67,7 @@ export const scheduleTokenRefresh: (
}

export const refreshOrReturnToken: (
keycloak: Keycloak.KeycloakInstance,
keycloak: Keycloak,
tokenMinValidityInSecs?: number
) => Promise<string | Error> = async (
keycloak,
Expand All @@ -74,7 +77,7 @@ export const refreshOrReturnToken: (
if (isRefreshTokenExpired(keycloak.refreshTokenParsed)) {
const errorHandlingModule = await import("../errorhandling")
const error = new errorHandlingModule.LoggedOutError(
keycloak.createLoginUrl(),
await keycloak.createLoginUrl(),
"Refresh token has expired: User is logged out"
)
errorHandlingModule.handleUploadError(error)
Expand Down
11 changes: 6 additions & 5 deletions npm/src/auth/session-timeout.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import Keycloak from "keycloak-js"
import {IFrontEndInfo} from "../index";

const timeoutDialog: HTMLDialogElement | null =
document.querySelector(".timeout-dialog")

export const initialiseSessionTimeout = async (): Promise<void> => {
export const initialiseSessionTimeout: (frontEndInfo: IFrontEndInfo) => Promise<void> = async (frontEndInfo: IFrontEndInfo): Promise<void> => {
const authModule = await import("./index")
const errorHandlingModule = await import("../errorhandling")
const keycloak = await authModule.getKeycloakInstance()
const keycloak = await authModule.getKeycloakInstance(frontEndInfo)
const now: () => number = () => Math.round(new Date().getTime() / 1000)
//Set timeToShowDialog to how many seconds from expiry you want the dialog log box to appear
const timeToShowDialog = 300
await setInterval(async () => {
setInterval(async () => {
if (!errorHandlingModule.isError(keycloak)) {
const timeUntilExpire = keycloak.refreshTokenParsed!.exp! - now()
if (timeUntilExpire < 0) {
keycloak.logout()
await keycloak.logout()
} else if (timeUntilExpire < timeToShowDialog) {
await showModal(keycloak)
}
Expand All @@ -28,7 +29,7 @@ const showModal = async (keycloak: Keycloak): Promise<void> => {
if (timeoutDialog && !timeoutDialog.open) {
timeoutDialog.showModal()
if (extendTimeout) {
await extendTimeout.addEventListener("click", async (ev) => {
extendTimeout.addEventListener("click", async (ev) => {
ev.preventDefault()
await refreshToken(keycloak)
})
Expand Down
28 changes: 22 additions & 6 deletions npm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ window.onload = async function () {
export interface IFrontEndInfo {
apiUrl: string
uploadUrl: string
authUrl: string
stage: string
region: string
clientId: string
realm: string
}

const getFrontEndInfo: () => IFrontEndInfo | Error = () => {
Expand All @@ -26,12 +29,21 @@ const getFrontEndInfo: () => IFrontEndInfo | Error = () => {
document.querySelector(".region")
const uploadUrlElement: HTMLInputElement | null =
document.querySelector(".upload-url")
if (apiUrlElement && stageElement && regionElement && uploadUrlElement) {
const authUrlElement: HTMLInputElement | null =
document.querySelector(".auth-url")
const clientIdElement: HTMLInputElement | null =
document.querySelector(".client-id")
const realmElement: HTMLInputElement | null =
document.querySelector(".realm")
if (apiUrlElement && stageElement && regionElement && uploadUrlElement && authUrlElement && clientIdElement && realmElement) {
return {
apiUrl: apiUrlElement.value,
stage: stageElement.value,
region: regionElement.value,
uploadUrl: uploadUrlElement.value
uploadUrl: uploadUrlElement.value,
authUrl: authUrlElement.value,
clientId: clientIdElement.value,
realm: realmElement.value
}
} else {
return Error("The front end information is missing")
Expand Down Expand Up @@ -63,7 +75,7 @@ export const renderModules = async () => {
const errorHandlingModule = await import("./errorhandling")
if (!errorHandlingModule.isError(frontEndInfo)) {
const authModule = await import("./auth")
const keycloak = await authModule.getKeycloakInstance()
const keycloak = await authModule.getKeycloakInstance(frontEndInfo)
if (!errorHandlingModule.isError(keycloak)) {
const metadataUploadModule = await import("./clientfilemetadataupload")
const clientFileProcessing =
Expand Down Expand Up @@ -91,7 +103,7 @@ export const renderModules = async () => {
const errorHandlingModule = await import("./errorhandling")
if (!errorHandlingModule.isError(frontEndInfo)) {
const authModule = await import("./auth")
const keycloak = await authModule.getKeycloakInstance()
const keycloak = await authModule.getKeycloakInstance(frontEndInfo)
if (!errorHandlingModule.isError(keycloak)) {
const isJudgmentUser = keycloak.tokenParsed?.judgment_user
const checksModule = await import("./checks")
Expand Down Expand Up @@ -122,7 +134,7 @@ export const renderModules = async () => {
const errorHandlingModule = await import("./errorhandling")
if (!errorHandlingModule.isError(frontEndInfo)) {
const authModule = await import("./auth")
const keycloak = await authModule.getKeycloakInstance()
const keycloak = await authModule.getKeycloakInstance(frontEndInfo)
if (!errorHandlingModule.isError(keycloak)) {
const checksModule = await import("./checks")
const resultOrError =
Expand All @@ -139,8 +151,12 @@ export const renderModules = async () => {
}

if (timeoutDialog) {
const frontEndInfo = getFrontEndInfo()
const sessionTimeoutModule = await import("./auth/session-timeout")
await sessionTimeoutModule.initialiseSessionTimeout()
const errorHandlingModule = await import("./errorhandling")
if (!errorHandlingModule.isError(frontEndInfo)) {
await sessionTimeoutModule.initialiseSessionTimeout(frontEndInfo)
}
}
if (fileSelectionTree) {
const trees: NodeListOf<HTMLUListElement> =
Expand Down
2 changes: 1 addition & 1 deletion npm/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"sourceMap": true,
"moduleResolution": "node",
"moduleResolution": "bundler",
"strictFunctionTypes": true
},
"include": ["src/*"],
Expand Down
6 changes: 5 additions & 1 deletion test/testUtils/CheckPageForStaticElements.scala
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ class CheckPageForStaticElements() {
"""<input type="hidden" class="api-url" value="https://mock-api-url.com/graphql">
|<input type="hidden" class="stage" value="mockStage">
|<input type="hidden" class="region" value="mockRegion">
|<input type="hidden" class="upload-url" value="https://mock-upload-url.com">""".stripMargin
|<input type="hidden" class="upload-url" value="https://mock-upload-url.com">
|<input type="hidden" class="auth-url" value="https://mock-auth-url.com">
|<input type="hidden" class="client-id" value="mockClientId">
|<input type="hidden" class="realm" value="mockRealm">
|""".stripMargin
)
}
}
Expand Down
5 changes: 4 additions & 1 deletion test/testUtils/FrontEndTestHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,10 @@ trait FrontEndTestHelper extends PlaySpec with MockitoSugar with Injecting with
"https://mock-api-url.com/graphql",
"mockStage",
"mockRegion",
"https://mock-upload-url.com"
"https://mock-upload-url.com",
"https://mock-auth-url.com",
"mockClientId",
"mockRealm"
)
)
frontEndInfoConfiguration
Expand Down

0 comments on commit eb9c197

Please sign in to comment.