implemented first keycloak stuff, still needs some config on keycloak and backend side to work

This commit is contained in:
Jona Kläß
2024-04-14 18:36:34 +02:00
parent 2afdee807e
commit ceac1855f3
11 changed files with 237 additions and 29 deletions

View File

@@ -71,8 +71,6 @@ module.exports = {
'prefer-promise-reject-errors': 'off',
quotes: ['warn', 'single', { avoidEscape: true }],
// this rule, if on, would require explicit return type on the `render` function
'@typescript-eslint/explicit-function-return-type': 'off',

View File

@@ -9,13 +9,14 @@
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png">
<!-- <link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="icon" type="image/ico" href="favicon.ico">
<link rel="icon" type="image/ico" href="favicon.ico">-->
</head>
<body>
<script type="module" src="/src/main.ts"></script>
<!-- quasar:entry-point -->
</body>
</html>

42
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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

View File

@@ -1,9 +1,27 @@
<template>
<router-view />
</template>
<script setup lang="ts">
import { RouterView } from 'vue-router';
import ProtectedData from './components/ProtectedData.vue';
defineOptions({
name: 'App'
});
</script>
<template>
<header>
<img alt="Quasar logo" src="./assets/quasar-logo-vertical.svg" width="200vw"/>
<ProtectedData />
</header>
<router-view />
</template>
<style scoped>
header {
line-height: 1.5;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 7vh;
}
</style>

View File

@@ -0,0 +1,40 @@
<script setup lang="ts">
import { useKeycloakStore } from '../stores/keycloakStore';
import { onMounted, ref } from 'vue';
const protectedData = ref("");
const keycloakStore = useKeycloakStore();
function fetchProtectedData() {
const keycloak = keycloakStore.getKeycloak;
if(!keycloak){
return;
}
let myHeaders = new Headers();
if(!keycloak.token){
return;
}
myHeaders.append("Authorization", "Bearer " + keycloak.token);
let requestOptons = {
headers: myHeaders,
};
fetch("localhost:"+ import.meta.env.PORT_BACKEND + "/api/protected", requestOptons)
.then((response) => response.text())
.then((data) => {
protectedData.value = data;
console.info("Protected data: ", data);
}).catch((e)=>console.error("Error fetching protected data: ", e));
}
onMounted(() => {
console.info("Protected component mounted");
fetchProtectedData();
});
</script>
<template>
<h3>Data: {{ protectedData }}</h3>
</template>
<style scoped>
</style>

View File

@@ -21,7 +21,6 @@
<q-drawer
v-model="leftDrawerOpen"
show-if-above
bordered
>
<q-list>

76
src/main.ts Normal file
View File

@@ -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);
});

View File

@@ -11,7 +11,7 @@
<script setup lang="ts">
import { ref } from 'vue';
import { Todo, Meta } from 'components/models';
import { Todo, Meta } from '../components/models';
import ExampleComponent from 'components/ExampleComponent.vue';
defineOptions({

View File

@@ -0,0 +1,18 @@
import type Keycloak from 'keycloak-js';
import { defineStore } from 'pinia';
//import api from 'axios';
export const useKeycloakStore = defineStore('keycloakStore', {
state: ()=>({
keycloak: null as Keycloak| null,
}),
getters: {
getKeycloak: (state) => state.keycloak,
},
actions:{
setKeycloak(keycloak: Keycloak): void {
this.keycloak = keycloak;
},
},
});