-
Reiter, Christoph authoredReiter, Christoph authored
vpu-auth.js 10.29 KiB
import {i18n} from './i18n.js';
import {html, css, LitElement} from 'lit-element';
import JSONLD from 'vpu-common/jsonld'
import * as commonUtils from 'vpu-common/utils';
import 'vpu-common/vpu-icon.js';
/**
* 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.forceLogin = false;
this.loadPerson = false;
this.clientId = "";
this.keyCloakInitCalled = false;
this._keycloak = null;
this.token = "";
this.subject = "";
this.name = "";
this.personId = "";
this.loggedIn = false;
this.rememberLogin = false
this.person = null;
// Create the events
this.initEvent = new CustomEvent("vpu-auth-init", { "detail": "KeyCloak init event", bubbles: true, composed: true });
this.personInitEvent = new CustomEvent("vpu-auth-person-init", { "detail": "KeyCloak person init event", bubbles: true, composed: true });
this.keycloakDataUpdateEvent = new CustomEvent("vpu-auth-keycloak-data-update", { "detail": "KeyCloak data was updated", bubbles: true, composed: true });
this.closeDropdown = this.closeDropdown.bind(this);
}
/**
* See: https://lit-element.polymer-project.org/guide/properties#initialize
*/
static get properties() {
return {
lang: { type: String },
forceLogin: { type: Boolean, attribute: 'force-login' },
rememberLogin: { type: Boolean, attribute: 'remember-login' },
loggedIn: { type: Boolean},
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 },
person: { type: Object, attribute: false },
};
}
connectedCallback() {
super.connectedCallback();
const href = window.location.href;
if (this.rememberLogin && sessionStorage.getItem('vpu-logged-in')) {
this.forceLogin = true;
}
// load Keycloak if we want to force the login or if we were redirected from the Keycloak login page
if (this.forceLogin || (href.search('[&#]state=') >= 0 && href.search('[&#]session_state=') >= 0)) {
this.loadKeycloak();
}
this.updateComplete.then(()=>{
});
document.addEventListener('click', this.closeDropdown);
}
disconnectedCallback() {
document.removeEventListener('click', this.closeDropdown);
super.disconnectedCallback();
}
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,
});
// See: https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter
that._keycloak.init({onLoad: 'login-required'}).success(function (authenticated) {
console.log(authenticated ? 'authenticated' : 'not authenticated!');
console.log(that._keycloak);
this.loggedIn = false;
that.updateKeycloakData();
that.dispatchInitEvent();
if (that.loadPerson) {
JSONLD.initialize(commonUtils.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) => {
that.person = 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.min.js';
//Append it to the document header
document.head.appendChild(script);
}
}
login(e) {
this.loadKeycloak();
}
logout(e) {
sessionStorage.removeItem('vpu-logged-in');
this._keycloak.logout();
}
/**
* Dispatches the init event
*/
dispatchInitEvent() {
this.loggedIn = true;
this.dispatchEvent(this.initEvent);
}
/**
* Dispatches the person init event
*/
dispatchPersonInitEvent() {
this.dispatchEvent(this.personInitEvent);
}
/**
* Dispatches the keycloak data update event
*/
dispatchKeycloakDataUpdateEvent() {
this.dispatchEvent(this.keycloakDataUpdateEvent);
}
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);
this.dispatchKeycloakDataUpdateEvent();
}
update(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
if (propName === "lang") {
i18n.changeLanguage(this.lang);
}
if (propName == "loggedIn") {
if (this.loggedIn)
sessionStorage.setItem('vpu-logged-in', true);
else
sessionStorage.removeItem('vpu-logged-in');
}
});
super.update(changedProperties);
}
static get styles() {
return css`
a.dropdown-item {
width: initial !important;
}
.main-button {
min-width: 150px;
}
vpu-icon {
height: 2em;
width: 2em;
}
`;
}
onDropdownClick(event) {
event.stopPropagation();
event.currentTarget.classList.toggle('is-active');
}
closeDropdown() {
var dropdowns = this.shadowRoot.querySelectorAll('.dropdown');
dropdowns.forEach(function (el) {
el.classList.remove('is-active');
});
}
renderLoggedIn() {
const imageURL = (this.person && this.person.image) ? this.person.image : null;
return html`
<div class="dropdown" @click="${this.onDropdownClick}">
<div class="dropdown-trigger">
<button class="button main-button" aria-haspopup="true" aria-controls="dropdown-menu2">
<span>${this.name}</span>
<vpu-icon name="menu-down"></vpu-icon>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu2" role="menu">
<div class="dropdown-content">
${imageURL ? html`<img src="${imageURL}" width="40%" height="40%" class="dropdown-item">` : ''}
<a href="#" @click="${this.logout}" class="dropdown-item">
${i18n.t('logout')}
</a>
</div>
</div>
</div>
`;
}
renderLoggedOut() {
return html`
<button id="login-button" @click="${this.login}" class="button main-button">${i18n.t('login')}</button>
`;
}
render() {
return html`
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
<div>
${this.loggedIn ? this.renderLoggedIn() : this.renderLoggedOut()}
</div>
`;
}
}
commonUtils.defineCustomElement('vpu-auth', VPUAuth);