Newer
Older
import {createInstance} from './i18n.js';
import JSONLD from '@dbp-toolkit/common/jsonld';
import {LoginStatus} from './util.js';
import {AdapterLitElement} from '@dbp-toolkit/common';
import {send} from '@dbp-toolkit/common/notification';
/**
* Keycloak auth web component
* https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter
*
* Emits a dbp-set-property event for the attribute "auth":
Patrizio Bekerle
committed
* auth.subject: Keycloak username
* auth.login-status: Login status (see object LoginStatus)
Patrizio Bekerle
committed
* auth.token: Keycloak token to send with your requests
* auth.user-full-name: Full name of the user

Reiter, Christoph
committed
* auth.user-id: Identifier of the user
export class AuthKeycloak extends AdapterLitElement {
constructor() {
super();
this.forceLogin = false;
this.token = '';
this.subject = '';
this.name = '';

Reiter, Christoph
committed
this._user = null;

Reiter, Christoph
committed
this._authenticated = false;
this.requestedLoginStatus = LoginStatus.UNKNOWN;
this._i18n = createInstance();
this.lang = this._i18n.language;
// Keycloak config
this.keycloakUrl = null;
this.realm = null;
this.clientId = null;
this.silentCheckSsoRedirectUri = null;
this.scope = null;
this._onKCChanged = this._onKCChanged.bind(this);
update(changedProperties) {
// console.log("changedProperties", changedProperties);
changedProperties.forEach((oldValue, propName) => {
this._i18n.changeLanguage(this.lang);
case 'entryPointUrl':
// for preloading the instance
JSONLD.getInstance(this.entryPointUrl, this.lang);
console.log('requested-login-status changed', this.requestedLoginStatus);
switch (this.requestedLoginStatus) {
case LoginStatus.LOGGED_IN:
this._kcwrapper.login({lang: this.lang, scope: this.scope || ''});
case LoginStatus.LOGGED_OUT:
// Keycloak will redirect right away without emitting events, so we have
// to do this manually here
if (this._loginStatus === LoginStatus.LOGGED_IN) {
this._setLoginStatus(LoginStatus.LOGGING_OUT);
}
this._kcwrapper.logout();
// In case logout was aborted, for example with beforeunload,
// revert back to being logged in
if (this._loginStatus === LoginStatus.LOGGING_OUT) {
this._setLoginStatus(LoginStatus.LOGGED_IN);
}
}
});
super.update(changedProperties);

Reiter, Christoph
committed
async _fetchUser(userId) {
jsonld = await JSONLD.getInstance(this.entryPointUrl, this.lang);

Reiter, Christoph
committed
let baseUrl = '';
try {
baseUrl = jsonld.getApiUrlForEntityName('FrontendUser');
} catch (error) {

Reiter, Christoph
committed
// backwards compat
baseUrl = jsonld.getApiUrlForEntityName('Person');

Reiter, Christoph
committed
}
const apiUrl = baseUrl + '/' + encodeURIComponent(userId);
let response = await fetch(apiUrl, {
headers: {

Reiter, Christoph
committed
},
});
if (!response.ok) {
throw response;
}
let user = await response.json();
let dummyUser = {
roles: user['roles'] ?? [],
};
return dummyUser;
}
async _onKCChanged(event) {

Reiter, Christoph
committed
this._authenticated = kc.authenticated;
this.name = kc.idTokenParsed.name;
this.token = kc.token;
this.subject = kc.subject;

Reiter, Christoph
committed
const userId = kc.idTokenParsed.preferred_username;

Reiter, Christoph
committed
if (userChanged) {
this._userId = userId;
let user;
try {
user = await this._fetchUser(userId);
} catch (error) {
// In case fetching the user failed then likely the API backend
// is not set up or broken. Return a user without any roles so we
// can show something at least.
console.error(error);
user = {roles: []};
}

Reiter, Christoph
committed
if (userId === this._userId) {
this._user = user;
}

Reiter, Christoph
committed
if (this._user !== null) {
this._setLoginStatus(LoginStatus.LOGGED_IN, tokenChanged || userChanged);
}
} else {
if (this._loginStatus === LoginStatus.LOGGED_IN) {
this._setLoginStatus(LoginStatus.LOGGING_OUT);
}
this.name = '';
this.token = '';
this.subject = '';
this._userId = '';

Reiter, Christoph
committed
this._user = null;
this._setLoginStatus(LoginStatus.LOGGED_OUT);
}
}
sendSetPropertyEvents() {

Reiter, Christoph
committed
'user-id': this._userId,
// Deprecated
'person-id': this._userId,
// inject a window.DBPAuth variable for Cypress/Playwright
if (window.Cypress || window.playwright) {
console.log("Cypress/Playwright detected");
}
this.sendSetPropertyEvent('auth', auth);
}
if (this._loginStatus === status && !force) return;
lang: {type: String},
forceLogin: {type: Boolean, attribute: 'force-login'},
tryLogin: {type: Boolean, attribute: 'try-login'},
entryPointUrl: {type: String, attribute: 'entry-point-url'},
name: {type: String, attribute: false},
token: {type: String, attribute: false},
subject: {type: String, attribute: false},
_userId: {type: String, attribute: false},
_user: {type: Object, attribute: false},
_loginStatus: {type: String, attribute: false},
keycloakUrl: {type: String, attribute: 'url'},
realm: {type: String},
clientId: {type: String, attribute: 'client-id'},
silentCheckSsoRedirectUri: {type: String, attribute: 'silent-check-sso-redirect-uri'},
scope: {type: String},
idpHint: {type: String, attribute: 'idp-hint'},
requestedLoginStatus: {type: String, attribute: 'requested-login-status'},
}
connectedCallback() {
super.connectedCallback();
if (!this.keycloakUrl) throw Error('url not set');
if (!this.realm) throw Error('realm not set');
if (!this.clientId) throw Error('client-id not set');
this._kcwrapper = new KeycloakWrapper(
this.keycloakUrl,
this.realm,
this.clientId,
this.silentCheckSsoRedirectUri,
this.idpHint
);
this._kcwrapper.addEventListener('changed', this._onKCChanged);
const handleLogin = async () => {
try {
if (this.forceLogin || this._kcwrapper.isLoggingIn()) {
this._setLoginStatus(LoginStatus.LOGGING_IN);
await this._kcwrapper.login({lang: this.lang, scope: this.scope || ''});
} else if (this.tryLogin) {
this._setLoginStatus(LoginStatus.LOGGING_IN);
await this._kcwrapper.tryLogin();
if (!this._authenticated) {
this._setLoginStatus(LoginStatus.LOGGED_OUT);
}
} else {
this._setLoginStatus(LoginStatus.LOGGED_OUT);

Reiter, Christoph
committed
}
} catch (error) {
// In case the keycloak server is offline for example
this._setLoginStatus(LoginStatus.LOGGED_OUT);
send({
summary: this._i18n.t('login-failed'),
type: 'danger',
timeout: 5,
});
throw error;
}
};
handleLogin();
}
disconnectedCallback() {

Reiter, Christoph
committed
this._kcwrapper.close();
this._kcwrapper.removeEventListener('changed', this._onKCChanged);
super.disconnectedCallback();