diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index c0a6e5a..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] -} diff --git a/package-lock.json b/package-lock.json index 978d687..774cc3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,8 @@ "@vuelidate/core": "^2.0.0", "@vuelidate/validators": "^2.0.0", "axios": "^1.2.2", + "jwt-decode": "^3.1.2", + "keycloak-js": "^12.0.4", "vue": "^3.2.45", "vue-i18n": "^9.2.2", "vue-router": "^4.1.6", @@ -2766,6 +2768,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -5863,6 +5870,11 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5923,6 +5935,20 @@ "node": ">=6" } }, + "node_modules/jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, + "node_modules/keycloak-js": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-12.0.4.tgz", + "integrity": "sha512-O/BHtyiDrZrUnKBrVF8POojqd3gmhuiDw4FiI+FbnB14nu7G5jKFrKYZa9Q0JYKIZXHJOBzSaKQcMp2WUI+zmA==", + "dependencies": { + "base64-js": "1.3.1", + "js-sha256": "0.9.0" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", diff --git a/package.json b/package.json index fe86d2d..393063d 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore" + "lint": "eslint . --ext .vue,.ts,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.2.1", @@ -17,6 +17,8 @@ "@vuelidate/core": "^2.0.0", "@vuelidate/validators": "^2.0.0", "axios": "^1.2.2", + "jwt-decode": "^3.1.2", + "keycloak-js": "^12.0.4", "vue": "^3.2.45", "vue-i18n": "^9.2.2", "vue-router": "^4.1.6", diff --git a/public/silent-check-sso.html b/public/silent-check-sso.html new file mode 100644 index 0000000..974ed55 --- /dev/null +++ b/public/silent-check-sso.html @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/src/authentication/AuthHelper.js b/src/authentication/AuthHelper.js new file mode 100644 index 0000000..6824ea0 --- /dev/null +++ b/src/authentication/AuthHelper.js @@ -0,0 +1,42 @@ +import Keycloak from 'keycloak-js' + +let keycloak = null + +function setup() { + keycloak = new Keycloak({ + url: 'https://auth.denysoft.eu/', + realm: 'FST', + clientId: 'frontend' + }) + return keycloak + .init({ + onLoad: 'login-required', + checkLoginIframe: false + }) + .then(function (authenticated) { + setInterval(() => { + keycloak + .updateToken(90) + .then(refreshed => { + if (refreshed) { + console.info('Token refreshed ' + refreshed) + } + }) + .catch(() => { + console.error('Failed to refresh token') + }) + }, 6000) + }) + .catch(function () { + alert('failed to initialize') + }) +} +function login() { + keycloak.login() +} + +function getKeycloak() { + return keycloak +} + +export default { setup, login, getKeycloak } diff --git a/src/components/ForbiddenPage.vue b/src/components/ForbiddenPage.vue new file mode 100644 index 0000000..e2a80d5 --- /dev/null +++ b/src/components/ForbiddenPage.vue @@ -0,0 +1,5 @@ + diff --git a/src/main.js b/src/main.js index ca76c4e..643ad47 100644 --- a/src/main.js +++ b/src/main.js @@ -7,14 +7,15 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { faUserSecret } from '@fortawesome/free-solid-svg-icons' +import './assets/main.css' +import AuthHelper from './authentication/AuthHelper' + /* add icons to the library */ library.add(faUserSecret) -import './assets/main.css' - const i18n = createI18n() -const app = createApp(App) +let app = createApp(App) app.use(router) app.use(i18n) diff --git a/src/router/index.js b/src/router/index.js index b6a549b..ee08e3b 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,26 +1,58 @@ import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' +import AuthHelper from '../authentication/AuthHelper' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', - name: 'home', - component: HomeView + name: 'main', + component: HomeView, + meta: { + requiresAuth: false + } }, { path: '/about', name: 'about', // this page is lazy-loaded when the route is visited. - component: () => import('../views/AboutView.vue') + component: () => import('../views/AboutView.vue'), + meta: { + requiresAuth: true + } + }, + { + path: '/forbidden', + name: 'forbidden', + component: () => import('../components/ForbiddenPage.vue'), + meta: { + requiresAuth: false + } }, { path: '/:pathMatch(.*)*', name: 'notfound', - component: () => import('../components/PageNotFound.vue') + component: () => import('../components/PageNotFound.vue'), + meta: { + requiresAuth: false + } } ] }) +router.beforeEach(async (to, from, next) => { + if (to.meta.requiresAuth === true) { + if (AuthHelper.getKeycloak() === null) { + await AuthHelper.setup() + } + if (!AuthHelper.getKeycloak().authenticated) { + next('/forbidden') + } + next() + } else { + next() + } +}) + export default router diff --git a/src/store/index.js b/src/store/index.js index af97c30..11df0d5 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,9 +1,11 @@ import { createStore } from 'vuex' import moduleA from './moduleA.module' import moduleB from './moduleB.module' +import keycloak from './keycloak.module' export default createStore({ modules: { + keycloak: keycloak, a: moduleA, b: moduleB } diff --git a/src/store/keycloak.module.js b/src/store/keycloak.module.js new file mode 100644 index 0000000..1b54d48 --- /dev/null +++ b/src/store/keycloak.module.js @@ -0,0 +1,20 @@ +export default { + state: () => ({ + keycloak: null + }), + mutations: { + initKeycloak(state, data) { + state.keycloak = data + } + }, + actions: { + initKeycloak({ commit }) { + commit('initKeycloak') + } + }, + getters: { + getKeycloak(state) { + return state.keycloak + } + } +}