From d9cf8ab4a958512a90a3e180af088b9cc0fc0001 Mon Sep 17 00:00:00 2001 From: Patrizio Bekerle <patrizio.bekerle@tugraz.at> Date: Tue, 16 Jul 2019 09:49:57 +0200 Subject: [PATCH] Initial commit of the authentication web component with demo page --- packages/auth/.gitignore | 3 + packages/auth/README.md | 17 ++ packages/auth/favicon.ico | Bin 0 -> 2550 bytes packages/auth/i18n.js | 29 ++++ packages/auth/i18n/de/translation.json | 4 + packages/auth/i18n/en/translation.json | 4 + packages/auth/i18next-scanner.config.js | 12 ++ packages/auth/index.html | 14 ++ packages/auth/index.js | 2 + packages/auth/jsonld.js | 218 ++++++++++++++++++++++++ packages/auth/package.json | 34 ++++ packages/auth/rollup.config.js | 41 +++++ packages/auth/utils.js | 58 +++++++ packages/auth/vars.js | 32 ++++ packages/auth/vpu-auth-demo.js | 43 +++++ packages/auth/vpu-auth.js | 183 ++++++++++++++++++++ 16 files changed, 694 insertions(+) create mode 100644 packages/auth/.gitignore create mode 100644 packages/auth/README.md create mode 100644 packages/auth/favicon.ico create mode 100644 packages/auth/i18n.js create mode 100644 packages/auth/i18n/de/translation.json create mode 100644 packages/auth/i18n/en/translation.json create mode 100644 packages/auth/i18next-scanner.config.js create mode 100644 packages/auth/index.html create mode 100644 packages/auth/index.js create mode 100644 packages/auth/jsonld.js create mode 100644 packages/auth/package.json create mode 100644 packages/auth/rollup.config.js create mode 100644 packages/auth/utils.js create mode 100644 packages/auth/vars.js create mode 100644 packages/auth/vpu-auth-demo.js create mode 100644 packages/auth/vpu-auth.js diff --git a/packages/auth/.gitignore b/packages/auth/.gitignore new file mode 100644 index 00000000..28e5452d --- /dev/null +++ b/packages/auth/.gitignore @@ -0,0 +1,3 @@ +dist +node_modules +.idea \ No newline at end of file diff --git a/packages/auth/README.md b/packages/auth/README.md new file mode 100644 index 00000000..aebebbcf --- /dev/null +++ b/packages/auth/README.md @@ -0,0 +1,17 @@ +## VPU Auth Web Component + +[GitLab Repository](https://gitlab.tugraz.at/VPU/WebComponents/Auth) + +## Local development + +```bash +npm install + +# constantly builds dist/bundle.js +npm run watch-local + +# run local webserver +cd dist; php -S localhost:8002 +``` + +Jump to <http://localhost:8002> and you should get a Single Sign On login page. diff --git a/packages/auth/favicon.ico b/packages/auth/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f6cf22d1afc22f071d2b64c7779bc5214c3adae5 GIT binary patch literal 2550 zcmZQzU}Ruo5D);-91Iz(3=C=v3{buTLk0^2Lmw*xg9b>9fq_AR0iuq9fq}t+5kx{U z68r!E{|pQa{~0DTTx0nE<_yEJYwsDZJ%7$HVdYu|hX2M43=C-u3=A_F7#PklFo3wm z#taO`X$%a;GZ`3+L1O<QO45uO7}C-h7}91kFr=MfU`YE9(KgeVfnjDE1H;Uj3=A{R zFfh#g4>pS7j4=blnKTB5Gcy?&&YWRjIP;%@;eQ$f!~dBK4FAtC7=ySB#tg;`X$-~; zGZ~B-&M+7={0BS9*w~oC*f@>B*mx#`vGExOW8?o2C#4xP7^kH%7^lr-FitzeV4U_J z;>?-G48}9l7>sAmWH6q2hQWB|e~8o17&923Nn<cRGn2vi%ozsbGyfTk{~I$H|4(Bu z{y&q!80?P!;1B@0B8>qAXELNQoMA{~_|E|LvavBknsFLKn(<7AG~+W2X~zE{!H{Om zkd~Ilkd`)+Aua6;Bv@t|Go;N-V@R7hlOb&;NbEl(sLmKOq@9_`kap$_L)w}D;Gj$U zZ_JSPe<nlP|1%6{K>lVp0|`RonG6tk1{^ChjX|N4#xT<u#QzTo>NI19nZ{`hGt*`= z%uGAOFw^)y!_1k+3^Qk@G0dDflVRpekXipB(E+wQjbY}QnG7?}oMD)G=07-!X8t#3 zn0Y3RVdnpt3^V_qVVL><KPUx2V*(TcGZ{cG14Zu{V`GLh#%T;^jAt^OF+Rg^#`r%Z zYC-NuOJg{bHk08D$Q^0_A<;e4nBmOKG=?)XXEL0bd4}Q4%>R%iaK@P7%$YQXGiPQp zoH=ub;mn!;;AC+I<d*+w3}^n&WH<u~53pN6sR0xVAUi=x)tKSGaT>#a<CzTqjn6Rr zH~tSvPHDyrXVTIb{-@1k_@8!$;Y`|pND`ZA%<z9^8pHpYGa3HRJj3vR=6_JKWB7l@ znBo7KG=~3YW-|OgbB5vnng0y`{~I%$`Jcw{|Nl&e|NqZ0q@{u4D2*ZQKgbS{*&sD( z42ld(8RFyP8OqDc8QR*~7^Y2|#&G)dX@*UkHZfekex2d&-Mb8rA3tVz_39PF$B!Qw ze*E~s@ZtA=h9wIYFf=qYfb$^(I6MA_P@s4R<%9qK8RlI}XAod61>+Q628P?~wlQq0 zO=L(CPiHu~?l8mTy^|Ss)%G&jvhy<B<Xg=UC0oa^rAUM!%<3M)xjF9`YL9+lc&VSx za9-d%gYn~S45u}ZKnnwkC?+N*CS(Ip`5+z`Gk{12FbQHYfmskfm<bUCGr<Iu4>cLY zgEGMsIv*@Z1_3JiknzL+3=AOLpvb@g!Yzyp3?TfUpMe34!EwRB0Mmn7&Vou?1_lQf zG8{6Zw7^0SMw9A!m_8UytbUk!d^E@`ba{{%2&0Q*<6~0?5(8m$_1MJF)qwaQbs!94 zBV%l0$nr2YNF5;zQwyUBsYjQG>4VYe@*|rM3l$g*Dq}zxRK|cXsEh$&a2W$CYtYIW E03q~Dvj6}9 literal 0 HcmV?d00001 diff --git a/packages/auth/i18n.js b/packages/auth/i18n.js new file mode 100644 index 00000000..a2380632 --- /dev/null +++ b/packages/auth/i18n.js @@ -0,0 +1,29 @@ +import i18next from 'i18next'; + +import de from './i18n/de/translation.json'; +import en from './i18n/en/translation.json'; + +const i18n = i18next.createInstance(); + +i18n.init({ + lng: 'de', + fallbackLng: ['de'], + debug: false, + initImmediate: false, // Don't init async + resources: { + en: {translation: en}, + de: {translation: de} + }, +}); + +console.assert(i18n.isInitialized); + +function dateTimeFormat(date, options) { + return new Intl.DateTimeFormat(i18n.languages, options).format(date); +} + +function numberFormat(number, options) { + return new Intl.NumberFormat(i18n.languages, options).format(number); +} + +export {i18n, dateTimeFormat, numberFormat}; diff --git a/packages/auth/i18n/de/translation.json b/packages/auth/i18n/de/translation.json new file mode 100644 index 00000000..48287f0d --- /dev/null +++ b/packages/auth/i18n/de/translation.json @@ -0,0 +1,4 @@ +{ + "login": "Einloggen", + "logout": "Ausloggen" +} diff --git a/packages/auth/i18n/en/translation.json b/packages/auth/i18n/en/translation.json new file mode 100644 index 00000000..8c6f4faa --- /dev/null +++ b/packages/auth/i18n/en/translation.json @@ -0,0 +1,4 @@ +{ + "login": "Login", + "logout": "Logout" +} diff --git a/packages/auth/i18next-scanner.config.js b/packages/auth/i18next-scanner.config.js new file mode 100644 index 00000000..6c112e37 --- /dev/null +++ b/packages/auth/i18next-scanner.config.js @@ -0,0 +1,12 @@ +module.exports = { + input: [ + '*.js', + ], + output: './', + options: { + debug: false, + removeUnusedKeys: true, + sort: true, + lngs: ['en','de'], + }, +} diff --git a/packages/auth/index.html b/packages/auth/index.html new file mode 100644 index 00000000..f332292e --- /dev/null +++ b/packages/auth/index.html @@ -0,0 +1,14 @@ +<!doctype html> +<html> +<head> + <meta charset="UTF-8"> + <script src="webcomponents-loader.js"></script> + <script type="module" id="vpu-auth-wc-src" src="bundle.js"></script> +</head> + +<body> + +<vpu-auth-demo lang="de"></vpu-auth-demo> + +</body> +</html> diff --git a/packages/auth/index.js b/packages/auth/index.js new file mode 100644 index 00000000..6f9872ca --- /dev/null +++ b/packages/auth/index.js @@ -0,0 +1,2 @@ +import './vpu-auth'; +import './vpu-auth-demo'; diff --git a/packages/auth/jsonld.js b/packages/auth/jsonld.js new file mode 100644 index 00000000..6079bb66 --- /dev/null +++ b/packages/auth/jsonld.js @@ -0,0 +1,218 @@ +"use strict"; + +let instances = {}; +let successFunctions = {}; +let failureFunctions = {}; +let initStarted = {}; + +module.exports = class JSONLD { + constructor(baseApiUrl, entities) { + this.entities = entities; + this.baseApiUrl = baseApiUrl; + + let idToEntityNameMatchList = {}; + for (const entityName in entities) { + const id = entities[entityName]["@id"]; + idToEntityNameMatchList[id] = entityName; + } + + this.idToEntityNameMatchList = idToEntityNameMatchList; + } + + static initialize(apiUrl, successFnc, failureFnc) { + // if init api call was already successfully finished execute the success function + if (instances[apiUrl] !== undefined) { + if (typeof successFnc == 'function') successFnc(instances[apiUrl]); + + return; + } + + // init the arrays + if (successFunctions[apiUrl] === undefined) successFunctions[apiUrl] = []; + if (failureFunctions[apiUrl] === undefined) failureFunctions[apiUrl] = []; + + // add success and failure functions + if (typeof successFnc == 'function') successFunctions[apiUrl].push(successFnc); + if (typeof failureFnc == 'function') failureFunctions[apiUrl].push(failureFnc); + + // check if api call was already started + if (initStarted[apiUrl] !== undefined) { + return; + } + + initStarted[apiUrl] = true; + + // window.VPUAuthToken will be set by on vpu-auth-init + document.addEventListener("vpu-auth-init", function(e) + { + const xhr = new XMLHttpRequest(); + xhr.open("GET", apiUrl, true); + xhr.setRequestHeader('Authorization', 'Bearer ' + window.VPUAuthToken); + + xhr.onreadystatechange = function () { + if (xhr.readyState === 4 && xhr.status === 200) { + const json = JSON.parse(xhr.responseText); + + let entryPoints = {}; + for (let property in json) { + // for some reason the properties start with a lower case character + if (!property.startsWith("@")) entryPoints[property.toLowerCase()] = json[property]; + } + + // read the link header of the api response + const utils = require("./utils"); + const links = utils.parseLinkHeader(this.getResponseHeader("link")); + + // get the hydra apiDocumentation url + const apiDocUrl = links["http://www.w3.org/ns/hydra/core#apiDocumentation"]; + + if (apiDocUrl !== undefined) { + // load the hydra apiDocumentation + const docXhr = new XMLHttpRequest(); + docXhr.open("GET", apiDocUrl, true); + docXhr.setRequestHeader("Content-Type", "application/json"); + docXhr.onreadystatechange = function () { + if (docXhr.readyState === 4 && docXhr.status === 200) { + const json = JSON.parse(docXhr.responseText); + const supportedClasses = json["hydra:supportedClass"]; + + let entities = {}; + const baseUrl = utils.parseBaseUrl(apiUrl); + + // gather the entities + supportedClasses.forEach(function (classData) { + // add entry point url + const entityName = classData["hydra:title"]; + let entryPoint = entryPoints[entityName.toLowerCase()]; + if (entryPoint !== undefined && !entryPoint.startsWith("http")) entryPoint = baseUrl + entryPoint; + classData["@entryPoint"] = entryPoint; + + entities[entityName] = classData; + }); + + const instance = new JSONLD(baseUrl, entities); + instances[apiUrl] = instance; + + // return the initialized JSONLD object + for (const fnc of successFunctions[apiUrl]) if (typeof fnc == 'function') fnc(instance); + successFunctions[apiUrl] = []; + } else { + for (const fnc of failureFunctions[apiUrl]) if (typeof fnc == 'function') fnc(); + failureFunctions[apiUrl] = []; + } + }; + + docXhr.send(); + } else { + for (const fnc of failureFunctions[apiUrl]) if (typeof fnc == 'function') fnc(); + failureFunctions[apiUrl] = []; + } + } else { + for (const fnc of failureFunctions[apiUrl]) if (typeof fnc == 'function') fnc(); + failureFunctions[apiUrl] = []; + } + }; + + xhr.send(); + }); + } + + static getInstance(apiUrl) { + return instances[apiUrl]; + } + + getEntityForIdentifier(identifier) { + let entityName = this.getEntityNameForIdentifier(identifier); + return this.getEntityForEntityName(entityName); + } + + getEntityForEntityName(entityName) { + return this.entities[entityName]; + } + + getApiUrlForIdentifier(identifier) { + return this.getEntityForIdentifier(identifier)["@entryPoint"]; + } + + getApiUrlForEntityName(entityName) { + return this.getEntityForEntityName(entityName)["@entryPoint"]; + } + + getEntityNameForIdentifier(identifier) { + return this.idToEntityNameMatchList[identifier]; + } + + getApiIdentifierList() { + let keys = []; + for (const property in this.idToEntityNameMatchList) { + keys.push(property); + } + + return keys; + } + + /** + * Expands a member of a list to a object with schema.org properties + * + * @param member + */ + expandMember(member) { + const type = member["@type"]; + + const entity = this.getEntityForIdentifier(type); + let result = {"@id": member["@id"]}; + + entity["hydra:supportedProperty"].forEach(function (property) { + const id = property["hydra:property"]["@id"]; + const title = property["hydra:title"]; + + result[id] = member[title]; + }); + + return result; + } + + /** + * Compacts an expanded member of a list to a object with local properties + * + * @param member + * @param localContext + */ + static compactMember(member, localContext) { + let result = {}; + + for (const property in localContext) { + const value = member[localContext[property]]; + + if (value !== undefined) { + result[property] = value; + } + } + + return result; + } + + /** + * Transforms hydra members to a local context + * + * @param data + * @param localContext + * @returns {Array} + */ + transformMembers(data, localContext) { + const members = data['hydra:member']; + + if (members === undefined || members.length === 0) { + return []; + } + + let results = []; + let that = this; + + members.forEach(function (member) { + results.push(JSONLD.compactMember(that.expandMember(member), localContext)); + }); + + return results; + } +}; diff --git a/packages/auth/package.json b/packages/auth/package.json new file mode 100644 index 00000000..29ab4b98 --- /dev/null +++ b/packages/auth/package.json @@ -0,0 +1,34 @@ +{ + "name": "vpu-auth-wc", + "version": "1.0.0", + "devDependencies": { + "node-sass": "^4.12.0", + "rollup": "^1.11.3", + "rollup-plugin-commonjs": "^9.3.4", + "rollup-plugin-copy": "^2.0.1", + "rollup-plugin-node-resolve": "^4.2.3", + "rollup-plugin-postcss": "^2.0.3", + "rollup-plugin-serve": "^1.0.1", + "rollup-plugin-terser": "^4.0.4", + "rollup-plugin-json": "^4.0.0", + "rollup-plugin-replace": "^2.2.0", + "i18next-scanner": "^2.10.2" + }, + "dependencies": { + "@webcomponents/webcomponentsjs": "^2.2.10", + "lit-element": "^2.1.0", + "i18next": "^17.0.3" + }, + "scripts": { + "clean": "rm dist/*", + "build": "npm run build-local", + "build-local": "rollup -c", + "build-dev": "rollup -c --environment BUILD:development", + "build-prod": "rollup -c --environment BUILD:production", + "build-demo": "rollup -c --environment BUILD:demo", + "i18next": "i18next-scanner", + "watch": "npm run watch-local", + "watch-local": "rollup -c --watch", + "watch-dev": "rollup -c --watch --environment BUILD:development" + } +} diff --git a/packages/auth/rollup.config.js b/packages/auth/rollup.config.js new file mode 100644 index 00000000..affebebe --- /dev/null +++ b/packages/auth/rollup.config.js @@ -0,0 +1,41 @@ +import resolve from 'rollup-plugin-node-resolve'; +import commonjs from 'rollup-plugin-commonjs'; +import postcss from 'rollup-plugin-postcss'; +import copy from 'rollup-plugin-copy'; +import {terser} from "rollup-plugin-terser"; +import json from 'rollup-plugin-json'; +import replace from "rollup-plugin-replace"; + +const build = (typeof process.env.BUILD !== 'undefined') ? process.env.BUILD : 'local'; +console.log("build: " + build); + +export default { + input: 'index.js', + output: { + file: 'dist/bundle.js', + format: 'esm' + }, + plugins: [ + resolve(), + commonjs(), + json(), + replace({ + "process.env.BUILD": '"' + build + '"', + }), + postcss({ + inject: false, + minimize: false, + plugins: [] + }), + terser(), + copy({ + targets: [ + 'index.html', + 'favicon.ico', + 'node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js', + 'node_modules/@webcomponents/webcomponentsjs/bundles', + ], + outputFolder: 'dist' + }) + ] +}; diff --git a/packages/auth/utils.js b/packages/auth/utils.js new file mode 100644 index 00000000..30eaa4ec --- /dev/null +++ b/packages/auth/utils.js @@ -0,0 +1,58 @@ +const vars = require("./vars"); + +module.exports = { + getAPiUrl: function(path = "", withPrefix = true) { + return vars.apiBaseUrl + (withPrefix ? vars.apiUrlPrefix : "") + path; + }, + + /** + * Parses a link header + * + * The node module parse-link-header didn't work, so https://gist.github.com/niallo/3109252 became handy + * + * @param header + */ + parseLinkHeader: (header) => { + if (header.length === 0) { + throw new Error("input must not be of zero length"); + } + + // Split parts by comma + const parts = header.split(','); + const links = {}; + + // Parse each part into a named link + for(let i=0; i<parts.length; i++) { + const section = parts[i].split(';'); + if (section.length !== 2) { + throw new Error("section could not be split on ';'"); + } + const url = section[0].replace(/<(.*)>/, '$1').trim(); + const name = section[1].replace(/rel="(.*)"/, '$1').trim(); + links[name] = url; + } + + return links; + }, + + /** + * Parses the base url from an url + * + * @param url + * @returns {string} + */ + parseBaseUrl: (url) => { + const pathArray = url.split('/'); + const protocol = pathArray[0]; + const host = pathArray[2]; + return protocol + '//' + host; + }, + + /** + * Reads a setting + * + * @param key + * @returns {*} + */ + setting: (key) => vars[key] +}; diff --git a/packages/auth/vars.js b/packages/auth/vars.js new file mode 100644 index 00000000..d34d7b63 --- /dev/null +++ b/packages/auth/vars.js @@ -0,0 +1,32 @@ + +switch(process.env.BUILD) { + case "development": + module.exports = { + apiBaseUrl: 'https://mw-dev.tugraz.at', + apiUrlPrefix: '', + keyCloakClientId: 'auth-dev-mw-frontend', + }; + + break; + case "production": + module.exports = { + apiBaseUrl: 'https://mw.tugraz.at', + apiUrlPrefix: '', + keyCloakClientId: 'auth-prod-mw-frontend', + }; + break; + case "demo": + module.exports = { + apiBaseUrl: 'https://api-demo.tugraz.at', + apiUrlPrefix: '', + keyCloakClientId: 'auth-dev-mw-frontend', + }; + break; + case "local": + default: + module.exports = { + apiBaseUrl: 'http://127.0.0.1:8000', + apiUrlPrefix: '', + keyCloakClientId: 'auth-dev-mw-frontend-local', + }; +} diff --git a/packages/auth/vpu-auth-demo.js b/packages/auth/vpu-auth-demo.js new file mode 100644 index 00000000..af9600c3 --- /dev/null +++ b/packages/auth/vpu-auth-demo.js @@ -0,0 +1,43 @@ +import utils from './utils.js'; +import {i18n} from './i18n.js'; +import {html, LitElement} from 'lit-element'; + +class LibraryShelving extends LitElement { + constructor() { + super(); + this.lang = 'de'; + } + + static get properties() { + return { + lang: { type: String }, + }; + } + + connectedCallback() { + super.connectedCallback(); + i18n.changeLanguage(this.lang); + + this.updateComplete.then(()=>{ + }); + } + + render() { + return html` + <style> + </style> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css"> + + <section class="section"> + <div class="container"> + <h1 class="title">Auth-Demo</h1> + </div> + <div class="container"> + <vpu-auth lang="${this.lang}" client-id="${utils.setting('keyCloakClientId')}" load-person></vpu-auth> + </div> + </section> + `; + } +} + +customElements.define('vpu-auth-demo', LibraryShelving); diff --git a/packages/auth/vpu-auth.js b/packages/auth/vpu-auth.js new file mode 100644 index 00000000..3f15b840 --- /dev/null +++ b/packages/auth/vpu-auth.js @@ -0,0 +1,183 @@ +import {i18n} from './i18n.js'; +import {html, LitElement} from 'lit-element'; +import JSONLD from "./jsonld"; +import utils from "./utils"; + +/** + * Keycloak auth web component + * https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter + * + * Dispatches an event `vpu-auth-init` and sets some global variables: + * window.VPUAuthSubject: Keycloak username + * window.VPUAuthToken: Keycloak token to send with your requests + * window.VPUUserFullName: Full name of the user + * window.VPUPersonId: Person identifier of the user + * window.VPUPerson: Person json object of the user (optional, enable by setting the `load-person` attribute, + * which will dispatch a `vpu-auth-person-init` event when loaded) + */ +class VPUAuth extends LitElement { + constructor() { + super(); + this.lang = 'de'; + this.loadPerson = false; + this.clientId = ""; + this.keyCloakInitCalled = false; + this._keycloak = null; + this.token = ""; + this.subject = ""; + this.name = ""; + this.personId = ""; + + // Create the init event + this.initEvent = new CustomEvent("vpu-auth-init", { "detail": "KeyCloak init event" }); + this.personInitEvent = new CustomEvent("vpu-auth-person-init", { "detail": "KeyCloak person init event" }); + } + + /** + * See: https://lit-element.polymer-project.org/guide/properties#initialize + */ + static get properties() { + return { + lang: { type: String }, + loadPerson: { type: Boolean, attribute: 'load-person' }, + clientId: { type: String, attribute: 'client-id' }, + name: { type: String, attribute: false }, + token: { type: String, attribute: false }, + subject: { type: String, attribute: false }, + personId: { type: String, attribute: false }, + keycloak: { type: Object, attribute: false }, + }; + } + + connectedCallback() { + super.connectedCallback(); + i18n.changeLanguage(this.lang); + + this.loadKeyCloak(); + + this.updateComplete.then(()=>{ + }); + } + + loadKeyCloak() { + const that = this; + console.log("loadKeyCloak"); + + if (!this.keyCloakInitCalled) { + // inject Keycloak javascript file + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.async = true; + script.onload = function () { + that.keyCloakInitCalled = true; + + that._keycloak = Keycloak({ + url: 'https://auth-dev.tugraz.at/auth', + realm: 'tugraz', + clientId: that.clientId, + }); + + that._keycloak.init({onLoad: 'login-required'}).success(function (authenticated) { + console.log(authenticated ? 'authenticated' : 'not authenticated!'); + console.log(that._keycloak); + + that.updateKeycloakData(); + that.dispatchInitEvent(); + + if (that.loadPerson) { + JSONLD.initialize(utils.getAPiUrl(), (jsonld) => { + // find the correct api url for the current person + // we are fetching the logged-in person directly to respect the REST philosophy + // see: https://github.com/api-platform/api-platform/issues/337 + const apiUrl = jsonld.getApiUrlForEntityName("Person") + '/' + that.personId; + + fetch(apiUrl, { + headers: { + 'Content-Type': 'application/ld+json', + 'Authorization': 'Bearer ' + that.token, + }, + }) + .then(response => response.json()) + .then((person) => { + window.VPUPerson = person; + that.dispatchPersonInitEvent(); + }); + }); + } + + }).error(function () { + console.log('Failed to initialize'); + }); + + // auto-refresh token + that._keycloak.onTokenExpired = function() { + that._keycloak.updateToken(5).success(function(refreshed) { + if (refreshed) { + console.log('Token was successfully refreshed'); + that.updateKeycloakData(); + } else { + console.log('Token is still valid'); + } + }).error(function() { + console.log('Failed to refresh the token, or the session has expired'); + }); + } + }; + + // https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_a + script.src = '//auth-dev.tugraz.at/auth/js/keycloak.js'; + + //Append it to the document header + document.head.appendChild(script); + } + } + + logout(e) { + this._keycloak.logout(); + } + + /** + * Dispatches the init event + */ + dispatchInitEvent() { + document.dispatchEvent(this.initEvent); + } + + /** + * Dispatches the person init event + */ + dispatchPersonInitEvent() { + document.dispatchEvent(this.personInitEvent); + } + + updateKeycloakData() { + this.name = this._keycloak.idTokenParsed.name; + this.token = this._keycloak.token; + this.subject = this._keycloak.subject; + this.personId = this._keycloak.idTokenParsed.preferred_username; + + window.VPUAuthSubject = this.subject; + window.VPUAuthToken = this.token; + window.VPUUserFullName = this.name; + window.VPUPersonId = this.personId; + + console.log("Bearer " + this.token); + } + + render() { + return html` + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css"> + + <div class="columns is-vcentered""> + <div class="column"> + ${this.name} + </div> + <div class="column"> + <button @click="${this.logout}" class="button">${i18n.t('logout')}</button> + </div> + </div> + `; + } +} + +customElements.define('vpu-auth', VPUAuth); -- GitLab