From ceac1855f341765a1e03c0b8388288e706511ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jona=20Kl=C3=A4=C3=9F?= Date: Sun, 14 Apr 2024 18:36:34 +0200 Subject: [PATCH] implemented first keycloak stuff, still needs some config on keycloak and backend side to work --- .eslintrc.cjs | 8 ++-- index.html | 5 ++- package-lock.json | 42 ++++++++++++++++++ package.json | 27 ++++++------ quasar.config.js | 21 +++++++-- src/App.vue | 26 +++++++++-- src/components/ProtectedData.vue | 40 +++++++++++++++++ src/layouts/MainLayout.vue | 1 - src/main.ts | 76 ++++++++++++++++++++++++++++++++ src/pages/IndexPage.vue | 2 +- src/stores/keycloakStore.ts | 18 ++++++++ 11 files changed, 237 insertions(+), 29 deletions(-) create mode 100644 src/components/ProtectedData.vue create mode 100644 src/main.ts create mode 100644 src/stores/keycloakStore.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 68b682e..e6e2e50 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -46,11 +46,11 @@ module.exports = { // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files // required to lint *.vue files 'vue' - + // https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 // Prettier has not been included as plugin to avoid performance impact // add it as an extension for your IDE - + ], globals: { @@ -68,10 +68,8 @@ module.exports = { // add your custom rules here rules: { - - 'prefer-promise-reject-errors': 'off', - quotes: ['warn', 'single', { avoidEscape: true }], + 'prefer-promise-reject-errors': 'off', // this rule, if on, would require explicit return type on the `render` function '@typescript-eslint/explicit-function-return-type': 'off', diff --git a/index.html b/index.html index 3c8c78f..015e4d3 100644 --- a/index.html +++ b/index.html @@ -9,13 +9,14 @@ - + + diff --git a/package-lock.json b/package-lock.json index 61b9953..7213a78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@quasar/extras": "^1.16.4", "axios": "^1.2.1", + "keycloak-js": "^24.0.2", "pinia": "^2.0.11", "quasar": "^2.8.0", "vue": "^3.4.18", @@ -4677,6 +4678,11 @@ "node": ">=0.10.0" } }, + "node_modules/js-sha256": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", + "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4785,6 +4791,23 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/keycloak-js": { + "version": "24.0.2", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-24.0.2.tgz", + "integrity": "sha512-V2N8cSz3NfON98XHp+DCzvrb1WW35JalL5Zphe/uoVWOxcof7v522Yz9Q2O3BqXqXP3V/H9ml6o24BwwtXUTGA==", + "dependencies": { + "js-sha256": "^0.11.0", + "jwt-decode": "^4.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -10797,6 +10820,11 @@ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true }, + "js-sha256": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", + "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -10884,6 +10912,20 @@ "universalify": "^2.0.0" } }, + "jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==" + }, + "keycloak-js": { + "version": "24.0.2", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-24.0.2.tgz", + "integrity": "sha512-V2N8cSz3NfON98XHp+DCzvrb1WW35JalL5Zphe/uoVWOxcof7v522Yz9Q2O3BqXqXP3V/H9ml6o24BwwtXUTGA==", + "requires": { + "js-sha256": "^0.11.0", + "jwt-decode": "^4.0.0" + } + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", diff --git a/package.json b/package.json index 6ab6d3e..3149ef1 100644 --- a/package.json +++ b/package.json @@ -13,29 +13,30 @@ "build": "quasar build" }, "dependencies": { - "axios": "^1.2.1", - "vue-i18n": "^9.2.2", - "pinia": "^2.0.11", "@quasar/extras": "^1.16.4", + "axios": "^1.2.1", + "keycloak-js": "^24.0.2", + "pinia": "^2.0.11", "quasar": "^2.8.0", "vue": "^3.4.18", + "vue-i18n": "^9.2.2", "vue-router": "^4.0.12" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.10.0", - "@typescript-eslint/parser": "^5.10.0", - "vite-plugin-checker": "^0.6.4", - "vue-tsc": "^1.8.22", - "eslint": "^8.11.0", - "eslint-plugin-vue": "^9.0.0", - "eslint-config-prettier": "^8.1.0", - "prettier": "^2.5.1", - "@types/node": "^12.20.21", "@intlify/vite-plugin-vue-i18n": "^3.3.1", "@quasar/app-vite": "^1.8.0", "@quasar/cli": "^2.4.0", + "@types/node": "^12.20.21", + "@typescript-eslint/eslint-plugin": "^5.10.0", + "@typescript-eslint/parser": "^5.10.0", "autoprefixer": "^10.4.2", - "typescript": "^4.5.4" + "eslint": "^8.11.0", + "eslint-config-prettier": "^8.1.0", + "eslint-plugin-vue": "^9.0.0", + "prettier": "^2.5.1", + "typescript": "^4.5.4", + "vite-plugin-checker": "^0.6.4", + "vue-tsc": "^1.8.22" }, "engines": { "node": "^20 || ^18 || ^16", diff --git a/quasar.config.js b/quasar.config.js index d6b31e9..462dcad 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -60,7 +60,16 @@ module.exports = configure(function (/* ctx */) { // publicPath: '/', // analyze: true, - // env: {}, + env: { + //Defined in realm_export.json + KEYCLOAK_REALM:'PraxistransferKeycloak', + KEYCLOAK_CLIENT_ID:'praxistransfer', + + //Defined in realm_export.json, initialize_keycloak.sh and quasar.config.js + PORT_KEYCLOAK:8083, + PORT_FRONTEND:8080, + PORT_BACKEND:8181, + }, // rawDefine: {} // ignorePublicFolder: true, // minify: false, @@ -96,8 +105,14 @@ module.exports = configure(function (/* ctx */) { // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer devServer: { // https: true - port: 8081, // default: 3000; changed to 8080 to match keycloak settings - open: true // opens browser window automatically + port: 8080, // default: 3000; changed to 8080 to match keycloak settings + open: true, // opens browser window automatically + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true + } + }, }, // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework diff --git a/src/App.vue b/src/App.vue index 1325c29..c1b93fe 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,9 +1,27 @@ - - + + + + diff --git a/src/components/ProtectedData.vue b/src/components/ProtectedData.vue new file mode 100644 index 0000000..8096206 --- /dev/null +++ b/src/components/ProtectedData.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 049f1c3..08eb52d 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -21,7 +21,6 @@ diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..4e0d4dc --- /dev/null +++ b/src/main.ts @@ -0,0 +1,76 @@ +import {createApp} from 'vue'; +import {createPinia} from 'pinia'; + +import App from './App.vue'; + +import Keycloak, {type KeycloakConfig, type KeycloakInitOptions} from 'keycloak-js'; +import { useKeycloakStore } from 'stores/keycloakStore'; + +const app = createApp(App); +app.use(createPinia()); + +/** + * Initialisierungsoptionen für Keycloak. + * @param url URL zu Keycloak, Port definiert in keycloak_init.sh und quasar.config.js-Datei. Müssen identisch sein! + * @param realm Name des Realms, hinterlegt in realm-export.json von Keycloak und quasar.config.js-Datei. Müssen identisch sein! + * @param clientId Name des Clients, hinterlegt in realm-export.json von Keycloak und quasar.config.js-Datei. Müssen identisch sein! + * @param onLoad = 'login-required': Hierdurch wird zum Aufrufen der ganzen Applikation ein login benötigt. + * @param redirectUri: URL, zu der nach dem Login weitergeleitet wird. Hierbei wird die Port des Frontends aus der .env-Datei verwendet. + */ + +console.log("Keycloak Client ID: "+process.env.KEYCLOAK_CLIENT_ID); + +const keycloakConfig: KeycloakConfig = { + url: 'http://localhost:'+ process.env.PORT_KEYCLOAK as string+'/auth', + realm: process.env.KEYCLOAK_REALM as string, + clientId: process.env.KEYCLOAK_CLIENT_ID as string, +}; +const initOptions: KeycloakInitOptions = { + onLoad: 'login-required', + redirectUri: 'http://localhost:8080/'//+ import.meta.env.PORT_FRONTEND as string +'/', +}; +console.log(keycloakConfig); +console.log(initOptions); +const keycloak: Keycloak = new Keycloak(keycloakConfig); + +const keycloakStore = useKeycloakStore(); +keycloakStore.setKeycloak(keycloak); + +/** + * Initialisierung von Keycloak. + * Beginnt authentifizierungsprozess und lädt die Applikation neu, falls Authentifizierung fehlschlägt. + * Weiterhin wird ein Tokenrefresh alle 70 Sekunden initialisiert. + */ +keycloak.init(initOptions).then((auth: boolean) => { + if (!auth) { + window.location.reload(); //Falls Authentifizierung fehlschlägt, wird die Seite neu geladen. + } else { + console.log('Authenticated'); //Loggt, dass Authentifizierung erfolgreich war. + app.mount('#app'); //App wird geladen. + if(keycloak.token) { //Überprüft, ob Token vorhanden ist. + localStorage.setItem('vue-keycloak-token', keycloak.token); //Token wird in localStorage gespeichert, um es später auswerten zu können. + } + if(keycloak.refreshToken) { //Überprüft, ob Refresh-Token vorhanden ist. + localStorage.setItem('vue-keycloak-refresh-token', keycloak.refreshToken); //Refresh-Token wird in localStorage gespeichert, um bei Ablauf des Tokens ein neues Token ohne erneute Authentifizierung zu erhalten. + } + } + + //Token Refresh + setInterval(() => { + keycloak.updateToken(70).then((refreshed: boolean) => { //Token wird alle 70 Sekunden aktualisiert. + if (refreshed) { + console.log('Token refreshed'); + } else { + console.warn('Token not refreshed'); + if(keycloak.tokenParsed && keycloak.tokenParsed.exp && keycloak.timeSkew){ //Fängt Probleme mit undefined Variablen ab + console.log('Token is still valid for ' + + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds'); //Loggt, wie lange das Token noch gültig ist. + } + } + }).catch(() => { + console.error('Failed to refresh token'); //Loggt, dass Token nicht aktualisiert werden konnte. + }); + }, 60000); +}).catch((e) => { + console.error('Authenticated Failed', e); +}); diff --git a/src/pages/IndexPage.vue b/src/pages/IndexPage.vue index eeee211..efe2b03 100644 --- a/src/pages/IndexPage.vue +++ b/src/pages/IndexPage.vue @@ -11,7 +11,7 @@