diff --git a/.env.development.keycloak-auth b/.env.development.keycloak-auth
new file mode 100644
index 00000000..302187da
--- /dev/null
+++ b/.env.development.keycloak-auth
@@ -0,0 +1,9 @@
+FTA_FMEA_BASENAME=''
+FTA_FMEA_API_URL=http://localhost:1235/services/fta-fmea-server
+FTA_FMEA_ADMIN_REGISTRATION_ONLY=false
+FTA_FMEA_TITLE='Development FTA/FMEA Tool'
+FTA_FMEA_AUTHENTICATION: oidc
+
+// TODO: Define auth env variables
+# FTA_FMEA_AUTH_SERVER_URL:
+# FTA_FMEA_AUTH_CLIENT_ID:
diff --git a/deploy/.docker/config.js.template b/deploy/.docker/config.js.template
index 23081aeb..e99c14d9 100644
--- a/deploy/.docker/config.js.template
+++ b/deploy/.docker/config.js.template
@@ -4,4 +4,7 @@ window.__config__ = {
FTA_FMEA_API_URL:'${FTA_FMEA_API_URL}',
FTA_FMEA_ADMIN_REGISTRATION_ONLY:'${FTA_FMEA_ADMIN_REGISTRATION_ONLY}',
FTA_FMEA_TITLE:'${FTA_FMEA_TITLE}'
+ FTA_FMEA_AUTHENTICATION:'${FTA_FMEA_AUTHENTICATION}'
+ FTA_FMEA_AUTH_SERVER_URL:'${FTA_FMEA_AUTH_SERVER_URL}'
+ FTA_FMEA_AUTH_CLIENT_ID:'${FTA_FMEA_AUTH_CLIENT_ID}'
}
diff --git a/deploy/.docker/docker-entrypoint.sh b/deploy/.docker/docker-entrypoint.sh
index 5ad1aa2f..e35282cc 100644
--- a/deploy/.docker/docker-entrypoint.sh
+++ b/deploy/.docker/docker-entrypoint.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env sh
set -eu
-envsubst '${FTA_FMEA_BASENAME} ${FTA_FMEA_API_URL} ${FTA_FMEA_ADMIN_REGISTRATION_ONLY} ${FTA_FMEA_TITLE}' < /etc/nginx/config.js.template > /usr/share/nginx/html/config.js
+envsubst '${FTA_FMEA_BASENAME} ${FTA_FMEA_API_URL} ${FTA_FMEA_ADMIN_REGISTRATION_ONLY} ${FTA_FMEA_TITLE} ${FTA_FMEA_AUTHENTICATION} ${FTA_FMEA_AUTH_SERVER_URL} ${FTA_FMEA_AUTH_CLIENT_ID}' < /etc/nginx/config.js.template > /usr/share/nginx/html/config.js
exec "$@"
diff --git a/deploy/keycloak-auth/.env b/deploy/keycloak-auth/.env
new file mode 100644
index 00000000..59087ed5
--- /dev/null
+++ b/deploy/keycloak-auth/.env
@@ -0,0 +1,15 @@
+# Prefix for name of all docker containers. By default it is set to "ff".
+RECORD_SET_NAME=ff-iauth-demo
+
+# Host machine port that provides main entrypoint for the application. The application will be locally accessible at http://localhost:$INTERNAL_HOST_PORT/$FTA_FMEA_PATH (by default it is set to "1235")
+INTERNAL_HOST_PORT=1235
+
+# Public origin of URL where FTA/FMEA tool UI will run, e.g. https://kbss.fel.cvut.cz, https://kbss.fel.cvut.cz:8080, http://localhost. ! This option can be used only with running reverse proxy pointing to http://localhost:$INTERNAL_HOST_PORT !
+#PUBLIC_ORIGIN=http://localhost
+
+# Root path for all applications and services, e.g., "" or "/my-company". By default it is set to "". MUST start with slash and MUST NOT end with slash.
+#APP_ROOT_PATH=/ff-demo
+
+# Relative path for root FTA/FMEA tool application starting from APP_ROOT_PATH (by default it is set to "/fta-fmea"). MUST start with slash and MUST NOT end with slash.
+#FTA_FMEA_PATH=/fta-fmea-demo
+
diff --git a/deploy/keycloak-auth/docker-compose.yml b/deploy/keycloak-auth/docker-compose.yml
new file mode 100644
index 00000000..10c32538
--- /dev/null
+++ b/deploy/keycloak-auth/docker-compose.yml
@@ -0,0 +1,132 @@
+version: "3.9"
+
+# Provide access to fta-fmea-ui that runs locally in dev mode
+x-access-for-local-development: &local-dev-env
+ cors.allowedOrigins: "http://localhost:4173,http://localhost:5173"
+
+# Provide logging to Java application (e.g. fta-fmea-server)
+x-logging-java-application: &logging-java
+ LOGGING_LEVEL_ROOT: "debug"
+
+# Expose port to access db-server directly, bypassing nginx
+x-access-db-server-development-port: &db-server-dev-port
+ ports:
+ - "127.0.0.1:${DB_SERVER_DEV_PORT:-7205}:7200"
+
+services:
+ nginx:
+ image: nginx:latest
+ container_name: ${RECORD_SET_NAME:-ff}-nginx
+ ports:
+ - "127.0.0.1:${INTERNAL_HOST_PORT:-1235}:80"
+ restart: always
+ depends_on:
+ - fta-fmea
+ - fta-fmea-server
+ - db-server
+ environment:
+ NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx
+ APP_ORIGIN: "${PUBLIC_ORIGIN:-http://localhost:${INTERNAL_HOST_PORT:-1235}}"
+ APP_ROOT_PATH: "${APP_ROOT_PATH:-}"
+ FTA_FMEA_PATH: "${FTA_FMEA_PATH:-/fta-fmea}"
+ volumes:
+ - ./nginx/nginx.conf:/etc/nginx/templates/nginx.conf.template:ro
+ - ../shared/nginx/error.html:/usr/share/nginx/html/error.html:ro
+
+ fta-fmea:
+ image: ghcr.io/kbss-cvut/fta-fmea-ui:latest
+ container_name: ${RECORD_SET_NAME:-ff}-fta-fmea
+ expose:
+ - "80"
+ depends_on:
+ - fta-fmea-server
+ environment:
+ FTA_FMEA_API_URL: "${PUBLIC_ORIGIN:-http://localhost:${INTERNAL_HOST_PORT:-1235}}${APP_ROOT_PATH:-}/services/fta-fmea-server"
+ FTA_FMEA_BASENAME: "${APP_ROOT_PATH:-}${FTA_FMEA_PATH:-/fta-fmea}"
+ FTA_FMEA_ADMIN_REGISTRATION_ONLY: ${ADMIN_REGISTRATION_ONLY:-false}
+ FTA_FMEA_AUTHENTICATION: "oidc"
+ FTA_FMEA_AUTH_SERVER_URL: "${PUBLIC_ORIGIN:-http://localhost:${INTERNAL_HOST_PORT:-1235}}${APP_ROOT_PATH:-}/services/auth/realms/fta-fmea"
+ FTA_FMEA_AUTH_CLIENT_ID: "fta-fmea"
+
+ fta-fmea-server:
+ image: ghcr.io/kbss-cvut/fta-fmea:latest
+ container_name: ${RECORD_SET_NAME:-ff}-fta-fmea-server
+ expose:
+ - "9999"
+ depends_on:
+ - db-server
+ restart: always
+ environment:
+ <<: *local-dev-env
+ REPOSITORY_URL: ${REPOSITORY_URL:-http://db-server:7200/repositories/fta-fmea}
+ server.servlet.context-path: "/fta-fmea"
+ SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURI: "${PUBLIC_ORIGIN:-http://localhost:${INTERNAL_HOST_PORT:-1235}}${APP_ROOT_PATH:-}/services/auth/realms/fta-fmea"
+ SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWKSETURI: "http://auth-server:8080/realms/fta-fmea/protocol/openid-connect/certs"
+ SERVER_MAXHTTPREQUESTHEADERSIZE: "40KB"
+
+ db-server:
+ <<: *db-server-dev-port
+ image: ${RECORD_SET_NAME:-ff}-db-server
+ container_name: ${RECORD_SET_NAME:-ff}-db-server
+ build:
+ context: ../shared/db-server
+ environment:
+ GDB_JAVA_OPTS: "-Dgraphdb.external-url=${PUBLIC_ORIGIN:-http://localhost:${INTERNAL_HOST_PORT:-1235}}${APP_ROOT_PATH:-}/services/db-server"
+ expose:
+ - "7200"
+ restart: always
+ volumes:
+ - ../shared/db-server/init-data:/root/graphdb-import:ro
+ - db-server:/opt/graphdb/home
+
+ auth-server-db:
+ image: postgres:13
+ container_name: ${RECORD_SET_NAME:-rm}-auth-server-db
+ environment:
+ POSTGRES_DB: keycloak
+ POSTGRES_USER: keycloak
+ POSTGRES_PASSWORD: keycloak
+ volumes:
+ - auth-server-db:/var/lib/postgresql/data
+
+ auth-server:
+ image: ghcr.io/kbss-cvut/keycloak-graphdb-user-replicator/keycloak-graphdb:latest
+ container_name: ${RECORD_SET_NAME:-rm}-auth-server
+ command:
+ - start --import-realm --features="token-exchange,admin-fine-grained-authz"
+ environment:
+ KC_IMPORT: realm-export.json
+ KC_HOSTNAME_URL: "${PUBLIC_ORIGIN:-http://localhost:${INTERNAL_HOST_PORT:-1235}}${APP_ROOT_PATH:-}/services/auth/"
+ KC_HOSTNAME_ADMIN_URL: "${PUBLIC_ORIGIN:-http://localhost:${INTERNAL_HOST_PORT:-1235}}${APP_ROOT_PATH:-}/services/auth/"
+ KC_HOSTNAME_STRICT_BACKCHANNEL: false
+ KC_HTTP_ENABLED: true
+ KEYCLOAK_ADMIN: ${KC_ADMIN_USER}
+ KEYCLOAK_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD}
+ DB_VENDOR: POSTGRES
+ DB_ADDR: auth-server-db
+ DB_DATABASE: keycloak
+ DB_USER: keycloak
+ DB_PASSWORD: keycloak
+ DB_SCHEMA: "public"
+ DB_SERVER_URL: "http://db-server:7200"
+ DB_SERVER_REPOSITORY_ID: "record-manager-app"
+ REPOSITORY_LANGUAGE: "en"
+ VOCABULARY_USER_TYPE: "http://onto.fel.cvut.cz/ontologies/record-manager/user"
+ VOCABULARY_USER_FIRST_NAME: "http://xmlns.com/foaf/0.1/firstName"
+ VOCABULARY_USER_LAST_NAME: "http://xmlns.com/foaf/0.1/lastName"
+ VOCABULARY_USER_USERNAME: "http://xmlns.com/foaf/0.1/accountName"
+ VOCABULARY_USER_EMAIL: "http://xmlns.com/foaf/0.1/mbox"
+ ADD_ACCOUNTS: false
+ REALM_ID: "fta-fmea-tool"
+ expose:
+ - "8080"
+ volumes:
+ - auth-server:/opt/keycloak/data
+ - ./keycloak:/opt/keycloak/data/import
+ depends_on:
+ - auth-server-db
+
+volumes:
+ db-server:
+ auth-server:
+ auth-server-db:
diff --git a/deploy/keycloak-auth/nginx/nginx.conf b/deploy/keycloak-auth/nginx/nginx.conf
new file mode 100644
index 00000000..1bc48448
--- /dev/null
+++ b/deploy/keycloak-auth/nginx/nginx.conf
@@ -0,0 +1,92 @@
+worker_processes 1;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+
+ client_max_body_size 100M;
+
+ include mime.types;
+ default_type application/octet-stream;
+
+ map $status $status_text {
+ 400 'Bad Request';
+ 401 'Unauthorized';
+ 403 'Forbidden';
+ 404 'Not Found';
+ 405 'Method Not Allowed';
+ 406 'Not Acceptable';
+ 413 'Payload Too Large';
+ 414 'URI Too Long';
+ 431 'Request Header Fields Too Large';
+ 500 'Internal Server Error';
+ 501 'Not Implemented';
+ 502 'Bad Gateway';
+ 503 'Service Unavailable';
+ 504 'Gateway Timeout';
+ }
+
+ server {
+ listen 80;
+ server_name localhost;
+
+ error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
+ 415 416 417 418 421 422 423 424 426 428 429 431 451 500 501 502 503
+ 504 505 506 507 508 510 511 /error.html;
+
+ location = /error.html {
+ ssi on;
+ internal;
+ root /usr/share/nginx/html;
+ }
+
+ location = ${FTA_FMEA_PATH} {
+ return 302 ${APP_ORIGIN}${APP_ROOT_PATH}${FTA_FMEA_PATH}/;
+ }
+
+ location ${FTA_FMEA_PATH}/ {
+ proxy_pass http://fta-fmea/; # keep the trailing slash to cut off matched prefix
+ }
+
+ location /services/fta-fmea-server/ {
+ proxy_pass http://fta-fmea-server:9999/fta-fmea/; # keep the trailing slash to cut off matched prefix
+ proxy_cookie_path /fta-fmea ${APP_ROOT_PATH}/services;
+ }
+
+ location = /services/db-server {
+ return 302 ${APP_ORIGIN}${APP_ROOT_PATH}/services/db-server/;
+ }
+
+ location /services/db-server/ {
+ proxy_pass http://db-server:7200/; # keep the trailing slash to cut off matched prefix
+ }
+
+ location = /services/auth {
+ return 302 ${APP_ORIGIN}${APP_ROOT_PATH}/services/auth/;
+ }
+
+ location /services/auth/ {
+ proxy_pass http://auth-server:8080/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-Host $server_name;
+ proxy_set_header X-Forwarded-Port $http_x_forwarded_port;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header Cookie $http_cookie;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ # Increase buffer sizes to handle large headers sent by Keycloak and its clients
+ proxy_buffer_size 128k;
+ proxy_buffers 4 256k;
+ proxy_busy_buffers_size 256k;
+ }
+
+ location /health-check {
+ return 200;
+ access_log off;
+ }
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 9d1cee95..35ffccf6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -29,6 +29,7 @@
"jquery": "^3.7.0",
"jsonld": "8.3.2",
"lodash": "^4.17.21",
+ "oidc-client": "^1.11.5",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
@@ -2788,6 +2789,25 @@
"dev": true,
"peer": true
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ -3077,6 +3097,16 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
},
+ "node_modules/core-js": {
+ "version": "3.37.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.0.tgz",
+ "integrity": "sha512-fu5vHevQ8ZG4og+LXug8ulUtVxjOcEYvifJr7L5Bfq9GOztVqsKd9/59hUk2ZSbCrS3BqUr3EpaYGIYzq7g3Ug==",
+ "hasInstallScript": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -3133,6 +3163,11 @@
"node": ">= 8"
}
},
+ "node_modules/crypto-js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
+ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
+ },
"node_modules/css-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
@@ -5811,6 +5846,37 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/oidc-client": {
+ "version": "1.11.5",
+ "resolved": "https://registry.npmjs.org/oidc-client/-/oidc-client-1.11.5.tgz",
+ "integrity": "sha512-LcKrKC8Av0m/KD/4EFmo9Sg8fSQ+WFJWBrmtWd+tZkNn3WT/sQG3REmPANE9tzzhbjW6VkTNy4xhAXCfPApAOg==",
+ "dependencies": {
+ "acorn": "^7.4.1",
+ "base64-js": "^1.5.1",
+ "core-js": "^3.8.3",
+ "crypto-js": "^4.0.0",
+ "serialize-javascript": "^4.0.0"
+ }
+ },
+ "node_modules/oidc-client/node_modules/acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/oidc-client/node_modules/serialize-javascript": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
+ "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -6182,7 +6248,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
- "peer": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
@@ -6631,8 +6696,7 @@
"type": "consulting",
"url": "https://feross.org/support"
}
- ],
- "peer": true
+ ]
},
"node_modules/safer-buffer": {
"version": "2.1.2",
diff --git a/package.json b/package.json
index 27784e5d..a0b455d2 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"jquery": "^3.7.0",
"jsonld": "8.3.2",
"lodash": "^4.17.21",
+ "oidc-client": "^1.11.5",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
@@ -69,7 +70,7 @@
"prettier": "3.2.5",
"ts-loader": "^9.4.4",
"vite-tsconfig-paths": "^4.3.1",
- "vitest":"^1.4.0",
+ "vitest": "^1.4.0",
"jsdom": "^24.0.0"
},
"lint-staged": {
diff --git a/src/App.tsx b/src/App.tsx
index 57a5ff3f..b70a8a51 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,6 +1,6 @@
import * as React from "react";
import { useEffect } from "react";
-import { ThemeProvider, Theme, StyledEngineProvider } from "@mui/material/styles";
+import { ThemeProvider, StyledEngineProvider } from "@mui/material/styles";
import AppRoutes from "@components/routes/AppRoutes";
import { appTheme } from "@styles/App.styles";
import { SnackbarProvider } from "@hooks/useSnackbar";
@@ -8,6 +8,8 @@ import { ConfirmDialogProvider } from "@hooks/useConfirmDialog";
import { ENVVariable, SELECTED_LANGUAGE_KEY, PRIMARY_LANGUAGE } from "@utils/constants";
import { Suspense } from "react";
import { useTranslation } from "react-i18next";
+import { isUsingOidcAuth } from "@utils/OidcUtils";
+import OidcAuthWrapper from "@oidc/OidcAuthWrapper";
declare module "@mui/material/styles" {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
@@ -36,7 +38,13 @@ const App = () => {
Redirecting...
; +}; + +export default OidcSignInCallback; diff --git a/src/misc/oidc/OidcSilentCallback.tsx b/src/misc/oidc/OidcSilentCallback.tsx new file mode 100644 index 00000000..a6269e07 --- /dev/null +++ b/src/misc/oidc/OidcSilentCallback.tsx @@ -0,0 +1,12 @@ +import React, { useEffect } from "react"; +import { getUserManager } from "@utils/OidcUtils"; + +const OidcSilentCallback = () => { + useEffect(() => { + getUserManager().signinSilentCallback(); + }, []); + + return ; +}; + +export default OidcSilentCallback; diff --git a/src/utils/OidcUtils.ts b/src/utils/OidcUtils.ts new file mode 100644 index 00000000..9d89759d --- /dev/null +++ b/src/utils/OidcUtils.ts @@ -0,0 +1,76 @@ +// Taken from https://github.com/datagov-cz/assembly-line-shared but using a different config processing mechanism +import { UserManager } from "oidc-client"; +import { ENVVariable } from "./constants"; + +// Singleton UserManager instance +let userManager: UserManager; +export const getUserManager = () => { + if (!userManager) { + userManager = new UserManager(getOidcConfig()); + } + return userManager; +}; + +/** + * Base64 encoding helper + */ +const encodeBase64 = (uri: string) => { + return window.btoa(uri); +}; + +/** + * Forward URI encoding helper + */ +const encodeForwardUri = (uri: string) => { + // Since base64 produces equal signs on the end, it needs to be further encoded + return encodeURI(encodeBase64(uri)); +}; + +export const getOidcConfig = () => { + const clientId = ENVVariable.AUTH_CLIENT_ID; + const baseUrl = resolveUrl(); + return { + authority: ENVVariable.AUTH_SERVER_URL, + client_id: clientId, + redirect_uri: `${baseUrl}/oidc-signin-callback?forward_uri=${encodeForwardUri(baseUrl)}`, + silent_redirect_uri: `${baseUrl}/oidc-silent-callback`, + post_logout_redirect_uri: `${baseUrl}`, + response_type: "code", + loadUserInfo: true, + automaticSilentRenew: true, + revokeAccessTokenOnSignout: true, + }; +}; + +function resolveUrl() { + const loc = window.location; + let url = loc.protocol + "//" + loc.host; + const basename = ENVVariable.BASENAME; + if (basename !== "/" && basename !== "./") { + url += basename; + } + return url; +} + +export const userProfileLink = () => { + return `${ENVVariable.AUTH_SERVER_URL}/account`; +}; + +/** + * Helper to generate redirect Uri + */ +export const generateRedirectUri = (forwardUri: string) => { + return `${resolveUrl()}/oidc-signin-callback?forward_uri=${encodeForwardUri(forwardUri)}`; +}; + +/** + * OIDC Session storage key name + */ +export const getOidcIdentityStorageKey = () => { + const oidcConfig = getOidcConfig(); + return `oidc.user:${oidcConfig.authority}:${oidcConfig.client_id}`; +}; + +export function isUsingOidcAuth() { + return ENVVariable.AUTHENTICATION === "oidc"; +} diff --git a/src/utils/constants.tsx b/src/utils/constants.tsx index bdf82dd5..da51c194 100644 --- a/src/utils/constants.tsx +++ b/src/utils/constants.tsx @@ -23,6 +23,9 @@ export const ENVVariable = { BASENAME: getEnv("FTA_FMEA_BASENAME", ""), ADMIN_REGISTRATION_ONLY: getEnv("FTA_FMEA_ADMIN_REGISTRATION_ONLY", "false"), TITLE: getEnv("FTA_FMEA_TITLE", "FTA/FMEA"), + AUTHENTICATION: getEnv("FTA_FMEA_AUTHENTICATION", "internal"), + AUTH_SERVER_URL: getEnv("FTA_FMEA_AUTH_SERVER_URL", ""), + AUTH_CLIENT_ID: getEnv("FTA_FMEA_AUTH_CLIENT_ID", ""), }; export const JSONLD = "application/ld+json"; @@ -36,6 +39,8 @@ export const ROUTES = { ADMINISTRATION: "/administration", LOGIN: "/login", LOGOUT: "/logout", + OIDC_SIGNIN_CALLBACK: "/oidc-signin-callback", + OIDC_SILENT_CALLBACK: "/oidc-silent-callback", DASHBOARD: "/", SYSTEMS: "/systems", @@ -66,3 +71,8 @@ export const SVG_PAN_ZOOM_OPTIONS = { export const SELECTED_LANGUAGE_KEY = "default-fta-language"; export const PRIMARY_LANGUAGE = "en"; export const SECONDARY_LANGUAGE = "cs"; + +export const ROLE = { + ADMIN: "Admin", + DOCTOR: "Regular User", +}; diff --git a/tsconfig.json b/tsconfig.json index 62dcbbeb..a00de184 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,8 @@ "@services/*": ["./src/services/*"], "@styles/*": ["./src/styles/*"], "@utils/*": ["./src/utils/*"], - "@hooks/*": ["./src/hooks/*"] + "@hooks/*": ["./src/hooks/*"], + "@oidc/*": ["./src/misc/oidc/*"] }, "lib": ["es2017", "dom", "dom.iterable"], "types": ["vite/client"] diff --git a/vite.config.js b/vite.config.js index f29f2d3d..84327d70 100644 --- a/vite.config.js +++ b/vite.config.js @@ -20,6 +20,7 @@ export default defineConfig({ "@utils": "/src/utils", "@services": "/src/services", "@models": "/src/models", + "@oidc": "/src/misc/oidc", }, }, test: {