Skip to content
Snippets Groups Projects
vpu-auth.js 7.98 KiB
import {i18n} from './i18n.js';
import {html, LitElement} from 'lit-element';
import JSONLD from 'vpu-common/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.forceLogin = false;
        this.loadPerson = false;
        this.clientId = "";
        this.keyCloakInitCalled = false;
        this._keycloak = null;
        this.token = "";
        this.subject = "";
        this.name = "";
        this.personId = "";

        // Create the events
        this.initEvent = new CustomEvent("vpu-auth-init", { "detail": "KeyCloak init event" });
        this.personInitEvent = new CustomEvent("vpu-auth-person-init", { "detail": "KeyCloak person init event" });
        this.keycloakDataUpdateEvent = new CustomEvent("vpu-auth-keycloak-data-update", { "detail": "KeyCloak data was updated" });
    }

    /**
     * See: https://lit-element.polymer-project.org/guide/properties#initialize
     */
    static get properties() {
        return {
            lang: { type: String },
            forceLogin: { type: Boolean, attribute: 'force-login' },
            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);
        const href = window.location.href;

        // load Keycloak if we want to force the login or if we were redirected from the Keycloak login page
        if (this.forceLogin || (href.indexOf('#state=') > 0 && href.indexOf('&session_state=') > 0)) {
            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,
                });

                // 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);

                    that.setStateToLogin(true);
                    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);
        }
    }

    login(e) {
        this.loadKeyCloak();
    }

    logout(e) {
        this._keycloak.logout();
    }

    /**
     * Dispatches the init event
     */
    dispatchInitEvent() {
        this.setStateToLogin(false);
        document.dispatchEvent(this.initEvent);
    }

    setStateToLogin(state) {
        this.shadowRoot.querySelector('#login-block').style.display = state ? "flex" : "none";
        this.shadowRoot.querySelector('#logout-block').style.display = state ? "none" : "flex";
    }

    /**
     * Dispatches the person init event
     */
    dispatchPersonInitEvent() {
        document.dispatchEvent(this.personInitEvent);
    }

    /**
     * Dispatches the keycloak data update event
     */
    dispatchKeycloakDataUpdateEvent() {
        document.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();
    }

    render() {
        return html`
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
            <style>
                #logout-block { display: none }
            </style>

            <div id="logout-block" 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>

            <div id="login-block" class="columns is-vcentered"">
                <div class="column">
                    <button id="login-button" @click="${this.login}" class="button">${i18n.t('login')}</button>
                </div>
            </div>
        `;
    }
}

customElements.define('vpu-auth', VPUAuth);