diff --git a/.gitignore b/.gitignore index 3dbdce7d..e7fdff45 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ typings/ # generated files /docs/.docusaurus /docs/.cache-loader +.history # Built Go Binaries platform-linux diff --git a/build-image.sh b/build-image.sh index b3887716..fa911b74 100755 --- a/build-image.sh +++ b/build-image.sh @@ -5,11 +5,11 @@ TAG_NAME=${1:-latest} if [ -z "$1" ]; then # we grep out 'image: jembi/platform:2.x' from which we cut on : and choose the last column # this will always be the image tag or an empty string - ImageTag=$(grep 'image:' ${PWD}/config.yaml | cut -d : -f 3) + ImageTag=$(grep 'image:' "${PWD}"/config.yaml | cut -d : -f 3) # only overwrite TAG_NAME if we have a tag present, and it's not just the base image name if [ -n "$ImageTag" ]; then TAG_NAME=${ImageTag} fi fi -docker build -t jembi/platform:"$TAG_NAME" . +docker build -t jembi/platform:"$TAG_NAME" . --no-cache diff --git a/config.yaml b/config.yaml index 8c6372b3..53421667 100644 --- a/config.yaml +++ b/config.yaml @@ -1,8 +1,9 @@ projectName: platform -image: jembi/platform:latest +image: jembi/platform:local logPath: /tmp/logs packages: + - smart-health-links-portal - interoperability-layer-openhim - reverse-proxy-nginx - fhir-datastore-hapi-fhir @@ -30,6 +31,7 @@ packages: - datalake profiles: + - name: cdr-dw packages: - message-bus-kafka @@ -93,3 +95,13 @@ profiles: - openhim-mapping-mediator envFiles: - mpi-qa.env + + - name: smart-health-links-portal + packages: + - smart-health-links-portal + - interoperability-layer-openhim + - fhir-datastore-hapi-fhir + - database-postgres + envFiles: + - smart-health-links-portal.env + dev: true diff --git a/identity-access-manager-keycloak/docker-compose.yml b/identity-access-manager-keycloak/docker-compose.yml index 3c1fbc69..1da17be5 100644 --- a/identity-access-manager-keycloak/docker-compose.yml +++ b/identity-access-manager-keycloak/docker-compose.yml @@ -7,12 +7,17 @@ services: [ "start", "--proxy=edge", - "--hostname-url=${KC_FRONTEND_URL}", + "--hostname=${KC_FRONTEND_URL}", "--import-realm", ] hostname: identity-access-manager-keycloak healthcheck: - test: curl --fail http://localhost:8080/health/ready || exit 1 + #test: curl --fail http://localhost:8080/health/ready || exit 1 + test: + [ + "CMD-SHELL", + "exec 3<>/dev/tcp/127.0.0.1/9000;echo -e 'GET /health/ready HTTP/1.1\r\nhost: http://localhost\r\nConnection: close\r\n\r\n' >&3;if [ $$? -eq 0 ]; then echo 'Healthcheck Successful';exit 0;else echo 'Healthcheck Failed';exit 1;fi;", + ] interval: 10s timeout: 5s retries: 5 diff --git a/smart-health-links-portal.env b/smart-health-links-portal.env new file mode 100644 index 00000000..f6d4665b --- /dev/null +++ b/smart-health-links-portal.env @@ -0,0 +1,17 @@ +# General +CLUSTERED_MODE=false + +# Log +DEBUG=0 +BASHLOG_FILE=0 +BASHLOG_FILE_PATH=platform.log + +# Smart Health Links Portal +# SHLP_NEXTAUTH_URL=http://localhost:3000 +SHLP_KEYCLOAK_CLIENT_SECRET="gX0nfIp57bO3bY68br3kdTXung2Auwpr" +SHLP_KEYCLOAK_ISSUER=http://localhost:9088/realms/shlp +SHLP_NEXTAUTH_URL=http://localhost:7457 +SHLP_NEXTAUTH_SECRET="ArmMS4LBmH3VTR77UrSAY2lPs04HO0Nk2/4BcU0jMvc=" +SHLP_NEXT_LOCAL_KEYCLOAK_URL=http://localhost:9088 + +KEYCLOAK_IMAGE="keycloak/keycloak:25.0" diff --git a/smart-health-links-portal/docker-compose.dev.yml b/smart-health-links-portal/docker-compose.dev.yml new file mode 100644 index 00000000..446ede4b --- /dev/null +++ b/smart-health-links-portal/docker-compose.dev.yml @@ -0,0 +1,8 @@ +version: '3.9' + +services: + smart-health-links-portal: + ports: + - target: 3000 + published: ${SMART_HEALTH_LINKS_PORTAL_PORT} + mode: host diff --git a/smart-health-links-portal/docker-compose.yml b/smart-health-links-portal/docker-compose.yml new file mode 100644 index 00000000..ca8ad6a8 --- /dev/null +++ b/smart-health-links-portal/docker-compose.yml @@ -0,0 +1,46 @@ +version: "3.9" + +services: + smart-health-links-portal: + image: jembi/smart-health-links-portal:${SMART_HEALTH_LINKS_PORTAL_VERSION} + networks: + hapi-fhir: + openhim: + reverse-proxy: + postgres: + environment: + HAPI_FHIR_BASE_URL: ${HAPI_FHIR_BASE_URL} + HAPI_FHIR_INSTANCES: ${HAPI_FHIR_INSTANCES} + OPENHIM_CONSOLE_BASE_URL: ${OPENHIM_CONSOLE_BASE_URL} + OPENHIM_API_USERNAME: ${OPENHIM_USERNAME} + OPENHIM_API_PASSWORD: ${OPENHIM_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} + KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} + KEYCLOAK_CLIENT_ID: ${SHLP_KEYCLOAK_CLIENT_ID} + KEYCLOAK_CLIENT_SECRET: ${SHLP_KEYCLOAK_CLIENT_SECRET} + KEYCLOAK_ISSUER: ${SHLP_KEYCLOAK_ISSUER} + NEXTAUTH_URL: ${SHLP_NEXTAUTH_URL} + NEXTAUTH_SECRET: ${SHLP_NEXTAUTH_SECRET} + NEXT_CONTAINER_KEYCLOAK_ENDPOINT: ${SHLP_NEXT_CONTAINER_KEYCLOAK_ENDPOINT} + NEXT_LOCAL_KEYCLOAK_URL: ${SHLP_NEXT_LOCAL_KEYCLOAK_URL} + NEXT_PUBLIC_DOMAIN: ${SHLP_VIEWER_DOMAIN} + EXTERNAL_URL: ${SHLP_EXTERNAL_URL} + POSTGRES_PRISMA_URL: ${POSTGRES_PRISMA_URL} + +networks: + hapi-fhir: + name: hapi-fhir_public + external: true + openhim: + name: openhim_public + external: true + reverse-proxy: + name: reverse-proxy_public + external: true + postgres: + name: postgres_public + external: true + default: diff --git a/smart-health-links-portal/importer/docker-compose.keycloak.yml b/smart-health-links-portal/importer/docker-compose.keycloak.yml new file mode 100644 index 00000000..1a555464 --- /dev/null +++ b/smart-health-links-portal/importer/docker-compose.keycloak.yml @@ -0,0 +1,41 @@ +version: '3.9' + +services: + # container for executing config import scripts for creating the Keycloak Realm used for SHLP User Auth + shlp-keycloak-config-importer: + image: node:erbium-alpine + networks: + public: + environment: + KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} + KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} + NEXTAUTH_URL: ${SHLP_NEXTAUTH_URL} + KEYCLOAK_CLIENT_SECRET: ${SHLP_KEYCLOAK_CLIENT_SECRET} + + command: sh -c "node keycloakConfig.js" + configs: + - source: keycloakConfig.js + target: /keycloakConfig.js + - source: keycloak-realm.json + target: /keycloak-realm.json + deploy: + replicas: 1 + restart_policy: + condition: none + +configs: + keycloakConfig.js: + file: ./volume/keycloak/keycloakConfig.js + name: keycloakConfig.js-${keycloakConfig_js_DIGEST:?err} + labels: + name: smart-health-links-importer + keycloak-realm.json: + file: ./volume/keycloak/keycloak-realm.json + name: keycloak-realm.json-${keycloak_realm_json_DIGEST:?err} + labels: + name: smart-health-links-importer + +networks: + public: + name: keycloak_public + external: true diff --git a/smart-health-links-portal/importer/docker-compose.openhim.yml b/smart-health-links-portal/importer/docker-compose.openhim.yml new file mode 100644 index 00000000..86dc052f --- /dev/null +++ b/smart-health-links-portal/importer/docker-compose.openhim.yml @@ -0,0 +1,43 @@ +version: '3.9' + +services: + # container for executing config import scripts for creating the OpenHIM channels used by the Mediator + shlp-openhim-config-importer: + image: node:erbium-alpine + networks: + openhim: + default: + environment: + OPENHIM_API_USERNAME: ${OPENHIM_USERNAME} + OPENHIM_API_PASSWORD: ${OPENHIM_PASSWORD} + # Reject unauthorised is only needed if the OpenHIM's SSL is not setup + NODE_TLS_REJECT_UNAUTHORIZED: 0 + OPENHIM_CONSOLE_BASE_URL: ${OPENHIM_CONSOLE_BASE_URL} + command: sh -c "node openhimConfig.js" + configs: + - source: openhimConfig.js + target: /openhimConfig.js + - source: openhim-import.json + target: /openhim-import.json + deploy: + replicas: 1 + restart_policy: + condition: none + +configs: + openhimConfig.js: + file: ./volume/openhim/openhimConfig.js + name: openhimConfig.js-${openhimConfig_js_DIGEST:?err} + labels: + name: smart-health-links-importer + openhim-import.json: + file: ./volume/openhim/openhim-import.json + name: openhim-import.json-${openhim_import_json_DIGEST:?err} + labels: + name: smart-health-links-importer + +networks: + openhim: + name: openhim_public + external: true + default: diff --git a/smart-health-links-portal/importer/docker-compose.postgres.yml b/smart-health-links-portal/importer/docker-compose.postgres.yml new file mode 100644 index 00000000..bd7caf1f --- /dev/null +++ b/smart-health-links-portal/importer/docker-compose.postgres.yml @@ -0,0 +1,43 @@ +version: '3.9' + +services: + shlp_db_config: + image: node:erbium-alpine + command: sh -c "pwd && cd /importer && ls && npm i && ls && node create-db.js" + configs: + - source: package.json + target: /importer/package.json + - source: create-db.js + target: /importer/create-db.js + networks: + postgres: + environment: + POSTGRES_SERVICE: ${POSTGRES_SERVICE} + POSTGRES_PORT: ${POSTGRES_PORT} + POSTGRES_USER: ${POSTGRES_USER_ADMIN} + POSTGRES_DATABASE: ${POSTGRES_USER_ADMIN_DATABASE} + POSTGRES_PASSWORD: ${POSTGRES_USER_ADMIN_PASSWORD} + NEW_DATABASE_NAME: ${SHLP_POSTGRES_DB} + NEW_DATABASE_USER: ${SHLP_POSTGRES_USER} + NEW_DATABASE_PASSWORD: ${SHLP_POSTGRES_PASSWORD} + deploy: + replicas: 1 + restart_policy: + condition: none + +networks: + postgres: + name: postgres_public + external: true + +configs: + package.json: + file: ./volume/postgres/package.json + name: package.json-${package_json_DIGEST:?err} + labels: + name: smart-health-links-importer + create-db.js: + file: ./volume/postgres/create-db.js + name: create_db.js-${create_db_js_DIGEST:?err} + labels: + name: smart-health-links-importer \ No newline at end of file diff --git a/smart-health-links-portal/importer/volume/keycloak/keycloak-realm.json b/smart-health-links-portal/importer/volume/keycloak/keycloak-realm.json new file mode 100644 index 00000000..e84c8617 --- /dev/null +++ b/smart-health-links-portal/importer/volume/keycloak/keycloak-realm.json @@ -0,0 +1,2273 @@ +{ + "id": "d81a0f2e-948c-42ae-95bd-69aeb1d71177", + "realm": "shlp", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": true, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxTemporaryLockouts": 0, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "019764e8-9c6c-43a7-be9f-8b13996c1916", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "d81a0f2e-948c-42ae-95bd-69aeb1d71177", + "attributes": {} + }, + { + "id": "7b1abd22-b77f-49e2-809a-7be117bbfe93", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "d81a0f2e-948c-42ae-95bd-69aeb1d71177", + "attributes": {} + }, + { + "id": "59bae58a-d17d-453e-b623-a9a7832ae902", + "name": "admin", + "description": "Administrator", + "composite": false, + "clientRole": false, + "containerId": "d81a0f2e-948c-42ae-95bd-69aeb1d71177", + "attributes": {} + }, + { + "id": "19f8848e-9311-4051-a5a9-72435c657fd0", + "name": "default-roles-shlp", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": ["offline_access", "uma_authorization"], + "client": { + "account": ["manage-account", "view-profile"] + } + }, + "clientRole": false, + "containerId": "d81a0f2e-948c-42ae-95bd-69aeb1d71177", + "attributes": {} + } + ], + "client": { + "nextjs": [ + { + "id": "3982ebb6-e184-456e-b8b4-5ed05498a22e", + "name": "admin", + "description": "Admin", + "composite": false, + "clientRole": true, + "containerId": "82130b50-6e41-4e32-b0e2-a1bbdbd829d7", + "attributes": {} + } + ], + "realm-management": [ + { + "id": "207d1b6f-87e9-4708-ac74-82fef511548b", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "dd46391e-7c58-4313-a305-01e3df3df335", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-users", "query-groups"] + } + }, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "8deb8cf7-517e-4f99-90d6-748e40df668e", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "0717f0f4-f7e1-4399-b5b3-b90d7eca24cd", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "644e300f-3cf6-4f47-9364-2abe81ec0da1", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "c5fec611-d37a-4d10-98c9-c8bed01bf9f7", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "78aa26f6-34a2-4e27-b90b-3a64a150d472", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "6794952b-b947-4355-ae94-03f7f87fde31", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "1a2f167d-0e31-430e-a62f-0b13efa4c126", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "56ec9ee6-73aa-4dc0-8c8a-dced49737a4e", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "12c32cfe-4b32-44ba-aef4-3bcf42d1a5fd", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "23fe71a9-5dd3-4e00-8c08-85299c9b9d18", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "065e3152-1558-4163-9523-9eb5180ca4f2", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-clients"] + } + }, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "13cdb0e2-b71d-452c-be42-4be5193af43a", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "db113a59-6dd4-43b4-9fcf-9d28afc13821", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "view-events", + "view-users", + "manage-identity-providers", + "query-realms", + "impersonation", + "manage-events", + "manage-realm", + "query-groups", + "query-clients", + "view-realm", + "manage-authorization", + "query-users", + "view-clients", + "manage-clients", + "create-client", + "view-identity-providers", + "manage-users", + "view-authorization" + ] + } + }, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "1aef2487-f97b-447a-b384-8706b63e7f2a", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "ab17a88b-c5ec-4d6b-858c-3fce74e21c3c", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "9b6ffc4d-8c8f-4988-ac95-69ddef6f563c", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + }, + { + "id": "db2c6df0-1ea7-469e-97d3-89adbe20f170", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "7cb622cc-229e-4e80-9de7-2dfc91f98b25", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "daa629ab-b19d-4423-8c9b-64a37a8d9c61", + "attributes": {} + } + ], + "account": [ + { + "id": "32369a8a-27ff-45ae-b059-94ab7bfbad72", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "23fdf3ca-c91e-4e9a-a609-d8e0aeb35b9f", + "attributes": {} + }, + { + "id": "44b7942c-5a27-49bb-a24f-cef58e8ec352", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "23fdf3ca-c91e-4e9a-a609-d8e0aeb35b9f", + "attributes": {} + }, + { + "id": "8fecdc8c-7c9c-46f9-8904-58c3a32d10de", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": ["manage-account-links"] + } + }, + "clientRole": true, + "containerId": "23fdf3ca-c91e-4e9a-a609-d8e0aeb35b9f", + "attributes": {} + }, + { + "id": "db049810-09d5-450f-bec2-c171b44e7c4c", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "23fdf3ca-c91e-4e9a-a609-d8e0aeb35b9f", + "attributes": {} + }, + { + "id": "d97b34cf-5293-4326-98c9-a8ce0132a0ad", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "23fdf3ca-c91e-4e9a-a609-d8e0aeb35b9f", + "attributes": {} + }, + { + "id": "0b3683ac-bafd-4b8f-a4ea-afa2f441ce76", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": ["view-consent"] + } + }, + "clientRole": true, + "containerId": "23fdf3ca-c91e-4e9a-a609-d8e0aeb35b9f", + "attributes": {} + }, + { + "id": "286f313f-22f3-465c-8147-c8b26609bcd9", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "23fdf3ca-c91e-4e9a-a609-d8e0aeb35b9f", + "attributes": {} + }, + { + "id": "c8d6a725-ff38-4987-b62c-9c58a0ddae1b", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "23fdf3ca-c91e-4e9a-a609-d8e0aeb35b9f", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "19f8848e-9311-4051-a5a9-72435c657fd0", + "name": "default-roles-shlp", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "d81a0f2e-948c-42ae-95bd-69aeb1d71177" + }, + "requiredCredentials": ["password"], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppGoogleName", + "totpAppMicrosoftAuthenticatorName" + ], + "localizationTexts": {}, + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": ["ES256"], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyExtraOrigins": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": ["offline_access"] + } + ], + "clientScopeMappings": { + "nextjs": [ + { + "clientScope": "roles", + "roles": ["admin"] + } + ], + "account": [ + { + "client": "account-console", + "roles": ["manage-account", "view-groups"] + } + ] + }, + "clients": [ + { + "id": "23fdf3ca-c91e-4e9a-a609-d8e0aeb35b9f", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/shlp/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/shlp/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "2924eafd-e191-4a37-8b10-c799a934d848", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/shlp/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/shlp/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "6488393f-d22a-435a-a3c8-5f8284cc9193", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "fd1963f6-8967-46ff-8b4e-73e26b3ef2b1", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "daa629ab-b19d-4423-8c9b-64a37a8d9c61", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "82130b50-6e41-4e32-b0e2-a1bbdbd829d7", + "clientId": "nextjs", + "name": "", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "", + "redirectUris": ["/api/auth/callback/keycloak"], + "webOrigins": [""], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": "1724862322", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "ffc7e123-90af-4f19-8aae-c7aa142a3b29", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "176b6b7e-b9f0-4ddf-a0ce-c0bd3fa8a6b0", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/shlp/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/admin/shlp/console/*"], + "webOrigins": ["+"], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "03ab070f-69e6-464a-92df-c758fd7c12f7", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "76ce86c5-3d7c-43df-bc4b-6006ad6e249e", + "name": "basic", + "description": "OpenID Connect scope for add all basic claims to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "facc5769-bf38-4e2c-8f89-a678e7686fe6", + "name": "sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-sub-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "dbd02afe-97c2-4942-990e-57dfa8d2f39b", + "name": "auth_time", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "AUTH_TIME", + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "auth_time", + "jsonType.label": "long" + } + } + ] + }, + { + "id": "70706f7a-6f4a-4a8a-b501-45d4c68e8c8f", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "46252d6a-0e0a-4a8f-8b84-81d100ac680d", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "e4a792d0-1c3b-4b82-b329-2fa61a65cfd1", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "8710214f-45d2-4673-aae6-f7ff94db8706", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "aa2593df-6ea8-470f-8dc0-e9efbe5aabd3", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${profileScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "d96cc237-53b8-4629-b7dd-516676eb1461", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "e4ed0ec0-63e1-4064-aba9-3a3af310377e", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "9031d941-e0e8-4b47-95c5-f655bbf12bbe", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "94a27798-92be-4dd3-abba-e7f2380036ae", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "470398ec-cb41-4b38-a7db-d744a84a5139", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "3258016c-adda-4793-b0d5-238fabdde9d6", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "e557daf7-acfc-4d4c-bc4a-4f21bcca0268", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "ba89e136-4cd9-49db-b5f3-85ae1f6d69fe", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "b94cfa86-441f-4fe0-bcda-8c3c092ca0f5", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "a218c6a2-650c-43a0-8d01-a74cc5959b37", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "46bc2d39-9822-4542-9302-3092e329f11e", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "5d892180-4c10-4f24-9422-c1eab134cc62", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "3fa9449d-ddea-42b7-b342-a71544e1e099", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "10cf92e0-5637-458e-a928-3d411d4f76e7", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + } + ] + }, + { + "id": "05ad6ded-3de0-438b-8f66-707143709ab6", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${rolesScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "03b6e161-0b38-4ddb-8180-1a84ccbb9e44", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "3b5b8f5d-aa25-4264-9a3d-f8873fabc177", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "f1432758-5600-41f6-961f-e65935c7b544", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "52103237-2fb3-48d1-bab1-d6aa498fc75a", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "fb73a34e-f81f-4cf9-9d7c-343d85265c32", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "4225f522-63ae-4407-8c09-33de2d5e33da", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "b09b80e9-0bba-49be-8234-3177fb28c9f6", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "5dfd6b3b-329d-4ac8-a46e-2670125cead2", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "7578f651-d14b-49ff-9561-ac43cdc151bf", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "7cfd9f0a-b4ba-420e-9924-433289d1715f", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "acd5b59c-8d08-49f3-a5d0-5705dfb1d1a4", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "7a064e48-d961-4024-a87f-8713d5744037", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "5b1936a7-f98d-4b6a-85cd-127dbfdef90c", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "fd23ecf1-a888-45c1-8165-4ab8a52c712b", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "191e19e1-f617-46cb-b789-c1351bcc281e", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "11643997-e057-4ff0-973e-4299962d7e27", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${addressScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "512bf059-79d3-47e7-95a1-0f2b6000b464", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "introspection.token.claim": "true", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr", + "basic" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "referrerPolicy": "no-referrer", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": { + "replyToDisplayName": "", + "starttls": "false", + "auth": "", + "port": "1025", + "host": "mail-catcher", + "replyTo": "", + "from": "no-reply@users.com", + "fromDisplayName": "Authenticator", + "envelopeFrom": "", + "ssl": "false" + }, + "eventsEnabled": false, + "eventsListeners": ["jboss-logging"], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "51c03279-0b6a-40d1-a498-e3824b31eebe", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-property-mapper", + "saml-role-list-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "saml-user-attribute-mapper" + ] + } + }, + { + "id": "9ed0a52a-b5e7-4c99-8e0c-1a928a647788", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-role-list-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-address-mapper", + "saml-user-attribute-mapper" + ] + } + }, + { + "id": "72fef253-6099-4a55-bd09-02fa23cc65b5", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "fc48b30d-0457-4238-9303-7765063ffae8", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "ee141652-22ac-43ff-97e7-5b557773a804", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": ["200"] + } + }, + { + "id": "9147ab59-1a75-4ba7-a248-f5465e339ffe", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] + } + }, + { + "id": "fded23e4-0cd7-419c-a85c-2ab2308a1089", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "f0edb20f-e083-46da-8a8f-bcb0d3b00d30", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "32766212-6c18-4833-adab-c12ddae617ca", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": { + "kc.user.profile.config": [ + "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"phone\",\"displayName\":\"Phone number\",\"validations\":{\"pattern\":{\"pattern\":\"^(?:\\\\+254|0)(7|1)\\\\d{8}$\",\"error-message\":\"Invalid phone number, it should follow the pattern (0/+254)(1/7)*******\"}},\"annotations\":{\"inputType\":\"html5-tel\"},\"required\":{\"roles\":[\"admin\",\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"dob\",\"displayName\":\"Date of birth\",\"validations\":{},\"annotations\":{\"inputType\":\"html5-date\"},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "9ad0d281-cb36-43e4-b5a2-4b976a8c541b", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "kid": ["b0769026-f122-4290-a549-f216a5868c50"], + "secret": ["r1AokIKf4pIspvm6FHJtpQ"], + "priority": ["100"] + } + }, + { + "id": "d7031669-1034-4718-9b53-dec5958c0687", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEowIBAAKCAQEAnFpv5ZQLBxMJxGTDwnA2YkvwHf+RgsNgjEdJg9eT1Oelg3Bam/fZOJPBakwQwSqDmGjh0ZbfN7KrGKmie67jy8g9ExXnFn/Q0fIZwe7byFTzqZtlWk+wk1KA5ZhTzHn167mXgpXGdZ6L/kC7aQiGCQIJxv5PB9iwWIdcEnllCeR+R73B0NtuuY/RapTpjeNWBUvOcYQN/dSoP1YbGT+5AZAEHWHjKSK/QP5YEA9qK04CHf7ctg+IvYIBODcFUTJO/Eg1QcCwaBdTOIQYxC2XdmFblZS7StLGlw00HOr/e9eQhlNNxJYyVZFHnaDjYyunQvTGC043kyryJDaDc1ELFwIDAQABAoIBADBekABbeL/jBWIC9EaqNUDakjmNj0P/UlaDVTxazEqQsTUE5JscpVAp8auDRamkj9puE/IldP/Dn8e87t3JgIJR+4b0fy2INB9HAO3siYSjt0+wmb3DKnKhEuw0YDRBRLF8lGEbGa7YciGhf/jh4KlXvpOXp/Za9E8m7PZwJLBWyifv4RyIODs7KwcdcyhsiEnsu65s25Q6TKP1XJV4Px5ySlPTz7VvvGOPRmf4CdMBkqKuMN83UY4vU594ToGNOp5/zvZoVGBY/PsUjqZwrn1kAGgMbMPBBuTzjBSkC8v3wgSYkrR2JlR6orV6yI/3DWfymUDQpXfSRGcEPlTWbmECgYEA1YAgWvqaZg/SApiPaU9q155XmSXBaZ57ReBWs0+n6/UDUaYjhUSikvd1+eX+7AwsUvKiNa4sF/0CRaR4tEOOz829URcSruR3ocKhXHEX+kMJFKJfpX4xcNS7wDPgg7LAgxq+ubbKIyvfM3Wt8mZZ25Oph0JQ2dqimTzA3OUnMqECgYEAu3oeN/r5gBFrp8Mvdlvh1esZLyNvvDEPq9ez2/48XrOtf3I1yvPa/iOk6s9eF7fw8YNfSNL+J7TaMVyNklDvnVzwtQdNZTzSQK8sK2urtWE5W1+COVj459TPiuaydRFjzDSG3kXv7y8jXpHuaGhiiYB8mBuDRMb2Qoaq8yd3mrcCgYEAnWzwbqVCm+cmb4uWk4xft8WlyeMxDnQxaYDV7TnbE/2CCP58SpQlXEDj2PRiWCNRu4f1W6emE7oXpspzxlfaQhfudJCS0ircm4Yr/UHW8E3lsDVaWWkHg68ZEcwuzG1Nzl42LDJAMlbzKbqoHIij0Wi8JGQdwcWZB2zouPivQoECgYAcr7boAfMjcYbziJ4+khigTvTvjZCPT0KCnOLciq5aH0ww8BzjDSU5NeMKOui+4Fpx2hQ+ZGqXUMkCPJM9DD1d6ta6HH/vObtCQouTqUoA2vuXy6l1dPPBJYMMUfiEiUCoAnGQmUr5YlEbK2E7gJ1zRMHL1InlgDw2Obz2r9dRSQKBgCxRlAHB2LVFK0qd8MeERCi+Vqlu2GR6ul4OvMStydb03rDK9ERG8kHTmwZwcu3xw2d8mp1cUU146Ce6Z54j5YWOA4Y2/qv5fdCEMZhl5EA3X9RMD5jZw9XG+lQB4xEjuLoE+23TgGTvk4GVW+CmdPPOogaAetVxuRze+x9jkOW0" + ], + "keyUse": ["ENC"], + "certificate": [ + "MIIClzCCAX8CBgGRmcfgEjANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARzaGxwMB4XDTI0MDgyODE2MTYzOFoXDTM0MDgyODE2MTgxOFowDzENMAsGA1UEAwwEc2hscDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJxab+WUCwcTCcRkw8JwNmJL8B3/kYLDYIxHSYPXk9TnpYNwWpv32TiTwWpMEMEqg5ho4dGW3zeyqxiponuu48vIPRMV5xZ/0NHyGcHu28hU86mbZVpPsJNSgOWYU8x59eu5l4KVxnWei/5Au2kIhgkCCcb+TwfYsFiHXBJ5ZQnkfke9wdDbbrmP0WqU6Y3jVgVLznGEDf3UqD9WGxk/uQGQBB1h4ykiv0D+WBAPaitOAh3+3LYPiL2CATg3BVEyTvxINUHAsGgXUziEGMQtl3ZhW5WUu0rSxpcNNBzq/3vXkIZTTcSWMlWRR52g42Mrp0L0xgtON5Mq8iQ2g3NRCxcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAPwNZ7/fL9Fff6T9j4SM54fP4+L1xGSfYq0MMU6k6142WJrHPB6NbpRmDt7ddKzBLKObLhjaVXSCltntYa0ObaxarN6VuZnZL9Sk7F15K2wqUszKRU/KtXD4XjWO2dl4iFTYBvU14u7iOvWC1Ac9uHcvZUSCi1uriQm6C5XIi5L/p33oxdB121Y1AHt4iFIKcKpqG0rADRnAtqLG0V1uf56thQ+0iy4FtCwrYWvneV+K7u/vLK4jkgk+xUyl+cuRK6rt2YMzq59+VfkHjPbMG/GGwbsgixRc0avoNqrlGqL0p+T54n5z/bc2SJ/md6FnvMKUEVAYHEtfgo6G9719xeQ==" + ], + "priority": ["100"], + "algorithm": ["RSA-OAEP"] + } + }, + { + "id": "569477d3-c9de-4b48-a2f7-3bbf3cf28551", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEowIBAAKCAQEAo1lxDgleOSSF0OTapOt+t+tDt/tESHfVtsq8axhToRIvmpK947YPDVlkpNbYhsax8L7U0fAPNM719WhKuFvUh1sQocMFkplZ1E022UdbYzaCOWC9Tg3fsmSfrl9d1EqaRi7J6LygWN6Z1RbqZkNJ3+PyXXhipO6Iow/83x15tsRcgsxDLfH0xr+84siSlB1VHo8rqGZojxVf6EalrrMAjfMTaxBH6uFHRW/6IjyZBwNLNzJ03wdQseZZ8Y6Y6BQPBJ+Bs0JYbB2PXw1B9sLdU4OLpirrWwlvNNFHzgpqJmY845tW5gV4VqcUQ3T4rnm1n/5+ZaJbfR+8uGdy527QGwIDAQABAoIBADpPGR4rlejL8sCIkMvCDG/WmUu5c7xymmUvpWaNPn0GotuIeiQovuLDADwfPRePIcGQPSPbj3IIVqANZG6+yPSwOxWjKgAUoY45KdURtbuKn52fvLN6HFnvtVhnTnUTVFKtLQHZI1vVyg/YqD9s0ZgMJF3dNUkupLOgZ2G1gdcNWmyY/w5DuyF3o/7lxZrYil0teDwQbw3ad5NceQMatBCpBp+EQ4BsyOk3Y48E0DI4126gFXCS3Oit+NWm9/3i6IKOXNhNwJ0XkwzrxPwcUwljELkMe0Mc0HumKyuHtdhi7dingsRIuZDEM0xzgClu6zyiCi5S3wXQ2fZZd5N4q8ECgYEA36EoqTraGAUlUS9jjmLEfCLROcQ4OK4H2Kyj/9pME8etmAeqyUYFZVFxdzzyfPa9C5IggGiv4d1uhOh7lmziHjfw6+oci8xQPxdwwnjT2a7EUUYBz8gxJ1NBjCgqH2dNai9XQXZt0jKu5bpTbxNX5XRVCuLaed5AV63+HTzFuTECgYEAuv6I7Ro3eOZA9PESXpjM5UO0blFtg0LunBH8YEfgHdPwLapRsts4chJ0xkcuaRzY9DWypBFmc82WFXjIRecKCcb7aU5h/15WzLIqQR5HF7dTuRgTsykA59GOQfchCZdHJzBBxSkU1poAwqcViY1Paol+svRKrc8cU95y5kz2ywsCgYATUDvOwcJsKKWcaVdTyLvnbKpdLtEcNAuBbweK5TOE0c3wP1lEoMN+iJYgEidvG3AgO7KK5H+sJg+RcvKZfzXYHkS0Wzez6yVTDZczdHP3bNyPmFg3tbZpCwuyKxXYOHe7H/ONN8ksSixIstrZ0PXaXz2o/waXV9Z+vAwiQklhQQKBgCEXlofPUe3VYb13RDb1d51DJBU519WY7L01+aHYVnMw5JpQP/EVFqIpXf0Q78euUT62mgyq/6SSx3K5U0f7oS0lx6xVMMaOnXHQPeYUQ6SQzwI8no/gO8bKsRA/edtGqFLpGVqY8JUh3+ySwR78oQdLmd51mRm1Q2AcR9011Z8NAoGBAM5kuEySo2a2WLE3gQ1QiiLDGASkWR+ws06XiD48jTkfu8jfpzpZwFNUEZkhy7uUav2EpcCZRMwBsK7pnBl+AdFsa/h/xNMvhi+iwSDPeHH10STyeFO79obqRlceG+Ta3yyjiX0KT0PMJ1Und/O2ZBp/B4O3GfSV4/R1CyN9HbEG" + ], + "keyUse": ["SIG"], + "certificate": [ + "MIIClzCCAX8CBgGRmcfffjANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARzaGxwMB4XDTI0MDgyODE2MTYzOFoXDTM0MDgyODE2MTgxOFowDzENMAsGA1UEAwwEc2hscDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKNZcQ4JXjkkhdDk2qTrfrfrQ7f7REh31bbKvGsYU6ESL5qSveO2Dw1ZZKTW2IbGsfC+1NHwDzTO9fVoSrhb1IdbEKHDBZKZWdRNNtlHW2M2gjlgvU4N37Jkn65fXdRKmkYuyei8oFjemdUW6mZDSd/j8l14YqTuiKMP/N8debbEXILMQy3x9Ma/vOLIkpQdVR6PK6hmaI8VX+hGpa6zAI3zE2sQR+rhR0Vv+iI8mQcDSzcydN8HULHmWfGOmOgUDwSfgbNCWGwdj18NQfbC3VODi6Yq61sJbzTRR84KaiZmPOObVuYFeFanFEN0+K55tZ/+fmWiW30fvLhncudu0BsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAJlp34Q0qvmAysvHTca9sbWTngrYNpNUz1F0v+jbcD4w00nKSKuWfclITJ1S2vsQeRhj1Qv8SpUTFVrQibNeeFtwa7Mm5ZUhCOWHfIEmS2FG3/Izrc6HTUBkExpCg71nA582a6O5GV0F5bMYrCjCgYvLUcOsV/o96PsLHjPPrnyh02kdOJvu1jxcseKZJcn1Ap+KYPhYyYv14gEymaPbakBJnkM4KSERNKgPcVkn4HE+2KT+c+HrI1SEz5f/NlySdTv9Smu4tWH4n6UD3/uDC1VrZu8guzAflD/unwN6fVffLqGsVMohn45rPWN1MKUHBSyUN9Ng00+uKXxAl5JMAZw==" + ], + "priority": ["100"] + } + }, + { + "id": "a6743a2e-12b6-4a49-a7a7-ccd9129678a7", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "kid": ["b0003546-cd41-47de-818f-c905b79fadc5"], + "secret": [ + "m6Pt4QIrIJRp98p9_TjocfAY2F6U1hFmsCctJLYfDJV-OG9Rq6rw9Fkwr9jqrNjflqz1iucDVukhvGMz-BxBJnThoNKo1Y6wnB8hYiKrtofm-yI3TONxYNr88hNUEHzlnht3BPFXqqV14WNuMrjHq3FL481ElQHmeyb7fvFIo3s" + ], + "priority": ["100"], + "algorithm": ["HS512"] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "8e738488-d69f-4d08-9c90-f557361bb082", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "d792d42e-fed3-4dac-8b1c-bc32cebf33a7", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "6e08beac-2e4d-4a88-96df-198b31966aae", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "9d34c115-9883-4409-b8b0-83bbab806d8b", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "7120c329-a7ef-48e0-8204-a4471d19cca9", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "31a0d9fd-36f8-49b8-b625-c513784dbd35", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "785832ca-edf9-4665-be4a-eb547bfa7eab", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "24df81fa-4ae8-40fd-b097-5b69ce45f966", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "7497700a-5d3f-479a-bbfe-072cbce41463", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "731d6b86-7c74-4e9c-bc38-197c951c5c56", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "1fb6aa8e-b9fa-401b-87dd-fe99cc650ad2", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "6cb23d0c-7f4e-4386-a370-682fcfe7ad57", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "28ed7334-7f3d-47cd-9509-019b57305b75", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "b430a808-7eaa-40ec-9f41-c1a20b8b0854", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "a0a6a187-c3cb-446a-9f28-ed765af68fda", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "b725c2cc-6620-4898-b211-23e4f07804ad", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-terms-and-conditions", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 70, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "beab53d5-b8f2-486a-9a36-a4c8668206a5", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "68804926-f171-4aa9-af75-ef36c87c379e", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "c011738d-a623-4970-8978-c32aeae9b9d2", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "0f06c5a3-66ea-4eff-a41d-bdebd2cf8054", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "VERIFY_PROFILE", + "name": "Verify Profile", + "providerId": "VERIFY_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 90, + "config": {} + }, + { + "alias": "delete_credential", + "name": "Delete Credential", + "providerId": "delete_credential", + "enabled": true, + "defaultAction": false, + "priority": 100, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaAuthRequestedUserHint": "login_hint", + "clientOfflineSessionMaxLifespan": "0", + "oauth2DevicePollingInterval": "5", + "clientSessionIdleTimeout": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5", + "realmReusableOtpCode": "false", + "cibaExpiresIn": "120", + "oauth2DeviceCodeLifespan": "600", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0" + }, + "keycloakVersion": "25.0.4", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} diff --git a/smart-health-links-portal/importer/volume/keycloak/keycloakConfig.js b/smart-health-links-portal/importer/volume/keycloak/keycloakConfig.js new file mode 100644 index 00000000..97ae73d7 --- /dev/null +++ b/smart-health-links-portal/importer/volume/keycloak/keycloakConfig.js @@ -0,0 +1,142 @@ +const http = require('http'); +const path = require('path'); +const fs = require('fs'); + +// Keycloak configuration +const keycloakUrl = 'http://localhost'; // http://identity-access-manager-keycloak +const keycloakPort = 9088; + +const adminUsername = process.env.KEYCLOAK_ADMIN; +const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD; + +// Load realm configuration from JSON file +let realmConfig = JSON.parse( + fs.readFileSync(path.join(__dirname, 'keycloak-realm.json'), 'utf8') +); + +function replacePlaceholders(obj) { + const nextAuthUrl = process.env.NEXTAUTH_URL || 'http://localhost' + const shlpKeycloakClientSecret = process.env.KEYCLOAK_CLIENT_SECRET || 'client_secret' + + for (let key in obj) { + if (typeof obj[key] === 'object') { + replacePlaceholders(obj[key]); + } else if (typeof obj[key] === 'string') { + obj[key] = obj[key].replace(//g, nextAuthUrl); + obj[key] = obj[key].replace(//g, shlpKeycloakClientSecret); + } + } +} + +function makeAsyncRequest(options, data) { + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + res.setEncoding('utf8'); + let responseBody = ''; + + res.on('data', (chunk) => { + responseBody += chunk; + }); + + res.on('end', () => { + resolve({ + message: responseBody ? JSON.parse(responseBody) : null, + status: res.statusCode + }); + }); + }); + + req.on('error', (err) => { + reject(err); + }); + + if (data) { + req.write(data) + } + + req.end(); + }); +} + +const getAccessToken = async () => { + // Get an admin access token + const tokenOptions = { + hostname: keycloakUrl.replace('http://', '').replace('https://', ''), + port: keycloakPort, + path: '/realms/master/protocol/openid-connect/token', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }; + const tokenRequestData = new URLSearchParams({ + grant_type: 'password', + client_id: 'admin-cli', + username: adminUsername, + password: adminPassword + }).toString(); + const accessToken = await makeAsyncRequest(tokenOptions, tokenRequestData); + + if (accessToken.status !== 200) { + throw new Error("Could not retrieve access token") + } + + return accessToken.message.access_token; +} + +const doesRealmExist = async (config) => { + const realmOptions = { + hostname: keycloakUrl.replace('http://', '').replace('https://', ''), + port: keycloakPort, + path: `/admin/realms/${config.realm}`, + method: 'GET', + headers: { + 'Authorization': `Bearer ${config.token}`, + 'Content-Type': 'application/json' + } + }; + + const res = await makeAsyncRequest(realmOptions) + + // return true if status is 200 - found + return res.status === 200; +} + +const createOrUpdateRealm = async (config, realmExist, data) => { + const realmOptions = { + hostname: keycloakUrl.replace('http://', '').replace('https://', ''), + port: keycloakPort, + path: realmExist ? `/admin/realms/${config.realm}` : `/admin/realms`, + method: realmExist ? 'PUT' : 'POST', + headers: { + 'Authorization': `Bearer ${config.token}`, + 'Content-Type': 'application/json' + } + }; + + return await makeAsyncRequest(realmOptions, JSON.stringify(data)); +} + +(async () => { + // replace placeholder for pointing the correct instance + replacePlaceholders(realmConfig); + + // get the keycloak access token + const token = await getAccessToken() + + // check if realm already exists - to perform an update instead of create + const config = { token, realm: realmConfig.realm } + const realmExists = await doesRealmExist({ token, realm: realmConfig.realm }) + console.log(realmExists) + + const updateCreateRealmRes = await createOrUpdateRealm(config, realmExists, realmConfig) + + if ([201, 204].includes(updateCreateRealmRes.status)) { + // successfully created or updated + console.log('Successfully created or updated Realm settings') + } else { + console.error('Something went wrong with the keycloak config update: ', updateCreateRealmRes) + } + + //await new Promise(resolve => setTimeout(resolve, 60000)); +})(); diff --git a/smart-health-links-portal/importer/volume/openhim/openhim-import.json b/smart-health-links-portal/importer/volume/openhim/openhim-import.json new file mode 100644 index 00000000..406f6218 --- /dev/null +++ b/smart-health-links-portal/importer/volume/openhim/openhim-import.json @@ -0,0 +1,71 @@ +{ + "Users": [], + "Passports": [], + "Clients": [ + { + "roles": ["smart-health-links"], + "customTokenID": "shlp", + "clientID": "shlp", + "name": "Smart Health Links Portal" + } + ], + "Channels": [ + { + "name": "FHIR Server via Smart Health Links Portal", + "description": "A FHIR server (HAPI FHIR)", + "urlPattern": "^/shlp-fhir.*$", + "isAsynchronousProcess": false, + "methods": [ + "GET" + ], + "type": "http", + "priority": 1, + "tcpPort": null, + "tcpHost": null, + "pollingSchedule": null, + "requestBody": true, + "responseBody": true, + "allow": [ + "smart-health-links" + ], + "whitelist": [], + "authType": "private", + "routes": [ + { + "name": "FHIR Server via Smart Health Links Portal", + "type": "http", + "status": "enabled", + "secured": false, + "host": "hapi-fhir", + "port": 8080, + "path": "", + "pathTransform": "s/shlp-//g", + "primary": true, + "username": "", + "password": "", + "forwardAuthHeader": false + } + ], + "matchContentTypes": [], + "matchContentRegex": null, + "matchContentXpath": null, + "matchContentJson": null, + "matchContentValue": null, + "properties": [], + "txViewAcl": [], + "txViewFullAcl": [], + "txRerunAcl": [], + "alerts": [], + "status": "enabled", + "rewriteUrls": false, + "addAutoRewriteRules": true, + "rewriteUrlsConfig": [], + "autoRetryEnabled": false, + "autoRetryPeriodMinutes": 60, + "updatedBy": { + "id": "65e1e4efa8321c79f2ec73af", + "name": "Super User" + } + } + ] +} diff --git a/smart-health-links-portal/importer/volume/openhim/openhimConfig.js b/smart-health-links-portal/importer/volume/openhim/openhimConfig.js new file mode 100644 index 00000000..1911cfc2 --- /dev/null +++ b/smart-health-links-portal/importer/volume/openhim/openhimConfig.js @@ -0,0 +1,78 @@ +const fs = require("fs"); +const https = require("https"); +const path = require("path"); + +("use strict"); + +const OPENHIM_CORE_SERVICE_NAME = "openhim-core"; +const OPENHIM_MEDIATOR_API_PORT = 8080; +const OPENHIM_API_PASSWORD = process.env.OPENHIM_API_PASSWORD || "instant101"; +const OPENHIM_API_USERNAME = + process.env.OPENHIM_API_USERNAME || "root@openhim.org"; + +const authHeader = Buffer.from( + `${OPENHIM_API_USERNAME}:${OPENHIM_API_PASSWORD}` +).toString("base64"); + +function makeRequest(options, data) { + const req = https.request(options, (res) => { + if (res.statusCode == 401) { + throw new Error(`Incorrect OpenHIM API credentials`); + } + + if (![201, 200].includes(res.statusCode)) { + throw new Error(`Failed to import OpenHIM config: ${res.statusCode}`); + } + + console.log("Successfully Imported OpenHIM Config"); + }); + + req.on("error", (error) => { + throw new Error(`Failed to import OpenHIM config: ${error}`); + }); + + req.write(data); + req.end(); +} + +const jsonData = JSON.parse( + fs.readFileSync(path.resolve(__dirname, "openhim-import.json")) +); + +if (!process.env.OPENHIM_CONSOLE_BASE_URL) { + throw new Error("Environment variable OPENHIM_CONSOLE_BASE_URL is not set"); +} + +const data = JSON.stringify(jsonData); + +const options = { + protocol: "https:", + hostname: OPENHIM_CORE_SERVICE_NAME, + port: OPENHIM_MEDIATOR_API_PORT, + headers: { + "Content-Type": "application/json", + Authorization: `Basic ${authHeader}`, + }, +}; + +const reqOptions = { + ...options, + path: "/metadata", + method: "POST", + headers: { + ...options.headers, + "Content-Length": data.length, + }, +}; + +const importMapRebuildOptions = { + ...options, + path: "/apps", + method: "GET", + headers: { + ...options.headers, + }, +}; + +makeRequest(reqOptions, data); +makeRequest(importMapRebuildOptions, ""); diff --git a/smart-health-links-portal/importer/volume/postgres/create-db.js b/smart-health-links-portal/importer/volume/postgres/create-db.js new file mode 100644 index 00000000..6f236569 --- /dev/null +++ b/smart-health-links-portal/importer/volume/postgres/create-db.js @@ -0,0 +1,54 @@ +const { Pool } = require('pg'); + +const user = process.env.POSTGRES_USER || 'postgres' +const host = process.env.POSTGRES_SERVICE || 'localhost' +const database = process.env.POSTGRES_DATABASE || 'postgres' +const password = process.env.POSTGRES_PASSWORD || 'instant101' +const port = process.env.POSTGRES_PORT || 5432 +const newDbs = process.env.NEW_DATABASE_NAME || 'shlp' +const newUser = process.env.NEW_DATABASE_USER || 'shlp_user' +const newUserPassword = process.env.NEW_DATABASE_PASSWORD || 'shlp_password' + +const pool = new Pool({ + user, + host, + database, + password, + port +}); + +(async () => { + const client = await pool.connect() + + const createDb = async db => { + //Check db exists before creating + const result = await client.query('SELECT 1 FROM pg_database WHERE datname = $1', [db]) + + if (!result.rows.length) { + await client.query(`CREATE DATABASE ${db};`) + + const user = await client.query('SELECT 1 FROM pg_user WHERE usename = $1', [newUser]) + + if (!user.rows.length) { + await client.query(`CREATE USER ${newUser} WITH ENCRYPTED PASSWORD '${newUserPassword}' CREATEDB;`) + await client.query(`GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${newUser};`) + console.log(`User ${newUser} created`) + } + + console.log(`Database '${db}' created successfully`) + } else { + console.log(`Database '${db}' already exists`) + } + } + + try { + await Promise.all(newDbs.split(',').map(db => createDb(db))) + } catch (error) { + console.error('Error creating database:', error.message) + } finally { + client.release() + pool.end() + } + + await new Promise(resolve => setTimeout(resolve, 60000)); +})(); diff --git a/smart-health-links-portal/importer/volume/postgres/package.json b/smart-health-links-portal/importer/volume/postgres/package.json new file mode 100644 index 00000000..f5cc4d22 --- /dev/null +++ b/smart-health-links-portal/importer/volume/postgres/package.json @@ -0,0 +1,11 @@ +{ + "name": "postgres-config", + "version": "0.0.1", + "main": "index.js", + "scripts": {}, + "author": "Jembi", + "license": "Apache-2.0", + "dependencies": { + "pg": "^8.11.3" + } +} \ No newline at end of file diff --git a/smart-health-links-portal/package-metadata.json b/smart-health-links-portal/package-metadata.json new file mode 100644 index 00000000..6ab7f4ee --- /dev/null +++ b/smart-health-links-portal/package-metadata.json @@ -0,0 +1,47 @@ +{ + "id": "smart-health-links-portal", + "name": "Smart Health Links Portal", + "description": "The package provides a Backend and Frontend for managing Patient's Smart Health Links for sharing their medical data", + "type": "infrastructure", + "version": "0.0.1", + "dependencies": [ + "interoperability-layer-openhim", + "fhir-datastore-hapi-fhir", + "identity-access-manager-keycloak", + "database-postgres" + ], + "environmentVariables": { + "HAPI_FHIR_BASE_URL": "http://hapi-fhir:8080/fhir", + "HAPI_FHIR_INSTANCES": 1, + "SMART_HEALTH_LINKS_PORTAL_PORT": 7457, + "SMART_HEALTH_LINKS_PORTAL_VERSION": "latest", + "OPENHIM_CONSOLE_BASE_URL": "http://localhost:9000", + "OPENHIM_API_USERNAME": "root@openhim.org", + "OPENHIM_API_PASSWORD": "instant101", + "POSTGRES_DB": "keycloak_db", + "POSTGRES_USER": "keycloak_db_user", + "POSTGRES_PASSWORD": "keycloak_db_user_password", + "KEYCLOAK_ADMIN": "admin", + "KEYCLOAK_ADMIN_PASSWORD": "dev_password_only", + "SHLP_KEYCLOAK_CLIENT_ID": "nextjs", + "SHLP_KEYCLOAK_CLIENT_SECRET": "keycloak_client_secret", + "SHLP_KEYCLOAK_ISSUER": "http://localhost:9088/realms/shlp", + "SHLP_NEXTAUTH_URL": "http://localhost:3000", + "SHLP_NEXTAUTH_SECRET": "nextauth_secret", + "SHLP_NEXT_CONTAINER_KEYCLOAK_ENDPOINT": "http://identity-access-manager-keycloak:8080", + "SHLP_NEXT_LOCAL_KEYCLOAK_URL": "http://localhost:9088", + "SHLP_VIEWER_DOMAIN": "http://localhost:3000", + "SHLP_EXTERNAL_URL": "http://localhost:3000", + "POSTGRES_PRISMA_URL": "postgresql://shlp_user:shlp_password@postgres-1:5432/shlp", + + "SHLP_POSTGRES_DB": "shlp", + "SHLP_POSTGRES_USER": "shlp_user", + "SHLP_POSTGRES_PASSWORD": "shlp_password", + + "POSTGRES_SERVICE": "postgres-1", + "POSTGRES_PORT": 5432, + "POSTGRES_USER_ADMIN": "postgres", + "POSTGRES_USER_ADMIN_PASSWORD": "instant101", + "POSTGRES_USER_ADMIN_DATABASE": "postgres" + } +} diff --git a/smart-health-links-portal/swarm.sh b/smart-health-links-portal/swarm.sh new file mode 100644 index 00000000..bb2d469a --- /dev/null +++ b/smart-health-links-portal/swarm.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +declare ACTION="" +declare MODE="" +declare COMPOSE_FILE_PATH="" +declare UTILS_PATH="" +declare STACK="smart-health-links-portal" + +function init_vars() { + ACTION=$1 + MODE=$2 + + COMPOSE_FILE_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" || exit + pwd -P + ) + UTILS_PATH="${COMPOSE_FILE_PATH}/../utils" + readonly ACTION + readonly MODE + readonly COMPOSE_FILE_PATH + readonly UTILS_PATH + readonly STACK +} +# shellcheck disable=SC1091 +function import_sources() { + source "${UTILS_PATH}/docker-utils.sh" + source "${UTILS_PATH}/config-utils.sh" + source "${UTILS_PATH}/log.sh" +} + +function initialize_package() { + local smart_health_links_portal_dev_compose_filename="" + + if [[ "${MODE}" == "dev" ]]; then + log info "Running package in DEV mode" + smart_health_links_portal_dev_compose_filename="docker-compose.dev.yml" + else + log info "Running package in PROD mode" + fi + + # postgres config importer to run before service - scaffold database / user + docker::deploy_config_importer $STACK "$COMPOSE_FILE_PATH/importer/docker-compose.postgres.yml" "shlp-postgres-config-importer" "smart-health-links-portal" + + ( + docker::deploy_service "$STACK" "${COMPOSE_FILE_PATH}" "docker-compose.yml" "$smart_health_links_portal_dev_compose_filename" + ) || { + log error "Failed to deploy package" + exit 1 + } + + docker::deploy_config_importer $STACK "$COMPOSE_FILE_PATH/importer/docker-compose.openhim.yml" "shlp-openhim-config-importer" "smart-health-links-portal" + docker::deploy_config_importer $STACK "$COMPOSE_FILE_PATH/importer/docker-compose.keycloak.yml" "shlp-keycloak-config-importer" "smart-health-links-portal" +} + +function destroy_package() { + docker::stack_destroy "$STACK" +} + +main() { + init_vars "$@" + import_sources + + if [[ "${ACTION}" == "init" ]] || [[ "${ACTION}" == "up" ]]; then + if [[ "${CLUSTERED_MODE}" == "true" ]]; then + log info "Running package in Cluster node mode" + else + log info "Running package in Single node mode" + fi + + initialize_package + elif [[ "${ACTION}" == "down" ]]; then + log info "Scaling down package" + + docker::scale_services "$STACK" 0 + elif [[ "${ACTION}" == "destroy" ]]; then + log info "Destroying package" + destroy_package + else + log error "Valid options are: init, up, down, or destroy" + fi +} + +main "$@"