diff --git a/boot/d4s-boot.js b/boot/d4s-boot.js index 0b27676..819142f 100644 --- a/boot/d4s-boot.js +++ b/boot/d4s-boot.js @@ -1,367 +1,273 @@ +/** + * D4Science boot component that handles keykloak authz + * { - - console.log("Keycloak loaded") - return this.initKeycloak() - - }).then((authenticated)=>{ - - if(!authenticated) throw "Failed to authenticate"; - - console.log("Keycloak initialized and user authenticated") - - //if an audience is provided then perform also authorization - if(this.#audience){ - return this.loadKeycloakAuthorization().then( - ()=>{ - this.#authorization = new KeycloakAuthorization(this.#keycloak) - console.log("Keycloak authorization loaded and initialized", this.#authorization) - } - ) - }else{ - return Promise.resolve() - } - - }).then(()=>{ + connectedCallback() { + this.startStateChecker() - this.#authenticated = true - this.unlock() - this.fire("authenticated") - - }).catch(err=>{ - console.error("Unable to initialize Keycloak",err) - }) - } + this.loadKeycloak().then(() => { + console.log("Keycloak loaded") + return this.initKeycloak() - loadKeycloak(){ - return new Promise((resolve, reject)=>{ - if (typeof Keycloak === 'undefined') { - const script = document.createElement('script') - script.src = this.#url + '/js/keycloak.js' - script.type = 'text/javascript' - script.addEventListener('load', resolve) - document.head.appendChild(script) - } else { - resolve() - } - }) - } - - initKeycloak(){ - this.#keycloak = new Keycloak({ - url: this.#url, - realm: this.#realm, - clientId: this.#clientId - }) - - return this.#keycloak.init({onLoad: 'login-required', checkLoginIframe: false }) - } - - loadKeycloakAuthorization(){ - return new Promise((resolve, reject)=>{ - if (typeof KeycloakAuthorization === 'undefined') { - const authz = document.createElement('script') - authz.src = this.#url + '/js/keycloak-authz.js' - authz.type = 'text/javascript' - authz.addEventListener('load', resolve) - document.head.appendChild(authz) - } else { - resolve() - } - }) - } - - startStateChecker(){ - this.#interval = window.setInterval(()=>{ - if(this.#locked){ - console.log("Still locked. Currently has " + this.#queue.length + " pending requests.") - }else if (!this.authenticated){ - window.alert("Not authorized!") - }else{ - if(this.#queue.length > 0){ - this.#keycloak.updateToken(30).then(()=>{ - if(this.#audience){ - console.log("Checking entitlement for audience", this.#audience) - const audience = encodeURIComponent(this.#audience) - return this.#authorization.entitlement(audience) - } else { - return Promise.resolve(this.#keycloak.token) - } - }).then( - token => { - console.log("Authorized") - //transform all queued requests to fetches - console.log("All pending requests to promises") - let promises = this.#queue.map(r => { - r.request.headers["Authorization"] = "Bearer " + token - return r.resolve( fetch(r.url, r.request) ) - }) - //clear queue - this.#queue = [] - console.log("Resolving all fetches") - return Promise.all(promises) - } - ).catch(err => alert("Unable to make calls: " + err)) - } - } - }, 300) - } - - parseJwt(token) { - var base64Url = token.split('.')[1] - var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/') - var jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) - }).join('')) - - return JSON.parse(jsonPayload); - } - - expirationDate(utc) { - let d = new Date(0) - d.setUTCSeconds(utc) - return d - } - - checkContext() { - const parseJwt = this.parseJwt - const expDt = this.expirationDate - const audience = encodeURIComponent(this.#audience) - this.#authorization.entitlement(audience).then(function (rpt) { - // onGrant callback function. - // If authorization was successful you'll receive an RPT - // with the necessary permissions to access the resource server - console.log(rpt) - //console.log("rpt expires: " + expDt(parseJwt(rpt).exp)) + }).then((authenticated) => { + if (!authenticated) { + throw "Failed to authenticate" + } + console.log("Keycloak initialized and user authenticated") + //if an audience is provided then perform also authorization + if (this.#audience) { + return this.loadKeycloakAuthorization().then(() => { + this.#authorization = new KeycloakAuthorization(this.#keycloak) + console.log("Keycloak authorization loaded and initialized", this.#authorization) }) - } + } else { + return Promise.resolve() + } - secureFetch(url, request){ - const p = new Promise((resolve, reject)=>{ - let req = request ? request : {} - if("headers" in req){ - req.headers["Authorization"] = null - } else { - req.headers = { "Authorization" : null} - } - console.log("Queued request to url ", url) - this.#queue.push({ url : url, request : req, resolve : resolve, reject : reject}) - }) - return p - } + }).then(() => { + this.#authenticated = true + this.unlock() + this.fire("authenticated") - download(url, name) { - this.secureFetch(url).then(reply=>{ - if(reply.status !== 200) throw "Unable to download"; - return reply.blob() - }).then(blob=>{ - const objectURL = URL.createObjectURL(blob) - var tmplnk = document.createElement("a") - tmplnk.download = name - tmplnk.href = objectURL - document.body.appendChild(tmplnk) - tmplnk.click() - document.body.removeChild(tmplnk) - }).catch(err=>alert(err)) - } + }).catch(err => { + console.error("Unable to initialize Keycloak", err) + }) + } - logout() { - if (this.#keycloak) { - if (!this.#redirectUrl) { - console.error("Missing required @redirectUrl attribute in d4s-boot") + loadKeycloak() { + return new Promise((resolve, reject) => { + if (typeof Keycloak === 'undefined') { + const script = document.createElement('script') + script.src = this.#url + '/js/keycloak.js' + script.type = 'text/javascript' + script.addEventListener('load', resolve) + document.head.appendChild(script) + } else { + resolve() + } + }) + } + + initKeycloak() { + this.#keycloak = new Keycloak({ + url: this.#url, + realm: this.#realm, + clientId: this.#clientId + }) + + return this.#keycloak.init({onLoad: 'login-required', checkLoginIframe: false }) + } + + loadKeycloakAuthorization() { + return new Promise((resolve, reject) => { + if (typeof KeycloakAuthorization === 'undefined') { + const authz = document.createElement('script') + authz.src = this.#url + '/js/keycloak-authz.js' + authz.type = 'text/javascript' + authz.addEventListener('load', resolve) + document.head.appendChild(authz) + } else { + resolve() + } + }) + } + + startStateChecker() { + this.#interval = window.setInterval(() => { + if (this.#locked) { + console.log("Still locked. Currently has " + this.#queue.length + " pending requests.") + } else if (!this.authenticated) { + window.alert("Not authorized!") + } else { + if (this.#queue.length > 0) { + this.#keycloak.updateToken(30).then(() => { + if (this.#audience) { + console.log("Checking entitlement for audience", this.#audience) + const audience = encodeURIComponent(this.#audience) + return this.#authorization.entitlement(audience) } else { - this.#keycloak.logout({ - redirectUri: this.#redirectUrl - }) + return Promise.resolve(this.#keycloak.token) } + + }).then(token => { + console.log("Authorized") + //transform all queued requests to fetches + console.log("All pending requests to promises") + let promises = this.#queue.map(r => { + r.request.headers["Authorization"] = "Bearer " + token + return r.resolve( fetch(r.url, r.request) ) + }) + //clear queue + this.#queue = [] + console.log("Resolving all fetches") + return Promise.all(promises) + + }).catch(err => console.error("Unable to make calls: " + err)) } - } + } + }, 300) + } - static get observedAttributes() { - return ["url", "realm", "gateway", "redirect-url", "context"]; - } + parseJwt(token) { + var base64Url = token.split('.')[1] + var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/') + var jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + }).join('')) - attributeChangedCallback(name, oldValue, newValue) { - if (oldValue !== newValue) { - switch (name) { - case "url": - this.#url = newValue - break - case "realm": - this.#realm = newValue - break - case "gateway": - this.#clientId = newValue - break - case "redirect-url": - this.#redirectUrl = newValue - break - case "context": - this.#audience = newValue - break - } - } - } - - get authenticated(){ - return this.#authenticated - } + return JSON.parse(jsonPayload); + } - get url() { - return this.#url - } + expirationDate(utc) { + let d = new Date(0) + d.setUTCSeconds(utc) + return d + } - set url(url) { - this.#url = url - this.setAttribute("url", url) - } + checkContext() { + const parseJwt = this.parseJwt + const expDt = this.expirationDate + const audience = encodeURIComponent(this.#audience) + this.#authorization.entitlement(audience).then(function (rpt) { + // onGrant callback function. + // If authorization was successful you'll receive an RPT + // with the necessary permissions to access the resource server + console.log(rpt) + //console.log("rpt expires: " + expDt(parseJwt(rpt).exp)) + }) + } - get realm() { - return this.#realm - } + secureFetch(url, request) { + const p = new Promise((resolve, reject) => { + let req = request ? request : {} + if ("headers" in req) { + req.headers["Authorization"] = null + } else { + req.headers = { "Authorization" : null} + } + console.log("Queued request to url ", url) + this.#queue.push({ url : url, request : req, resolve : resolve, reject : reject}) + }) + return p + } - set realm(realm) { - this.#realm = realm - this.setAttribute("realm", realm) + logout() { + if (this.#keycloak) { + if (!this.#redirectUrl) { + console.error("Missing required @redirectUrl attribute in d4s-boot") + } else { + this.#keycloak.logout({ + redirectUri: this.#redirectUrl + }) + } } + } - get clientId() { - return this.#clientId - } + static get observedAttributes() { + return ["url", "realm", "gateway", "redirect-url", "context"]; + } - set clientId(clientId) { - this.#clientId = clientId - this.setAttribute("gateway", clientId) + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue) { + switch (name) { + case "url": + this.#url = newValue + break + case "realm": + this.#realm = newValue + break + case "gateway": + this.#clientId = newValue + break + case "redirect-url": + this.#redirectUrl = newValue + break + case "context": + this.#audience = newValue + break + } } + } - get redirectUrl() { - return this.#redirectUrl - } + get authenticated(){ + return this.#authenticated + } - set redirectUrl(redirectUrl) { - this.#redirectUrl = redirectUrl - this.setAttribute("redirect-url", redirectUrl) - } + get url() { + return this.#url + } - get context() { - return this.#audience - } + set url(url) { + this.#url = url + this.setAttribute("url", url) + } - set context(context) { - this.#audience = context - this.setAttribute("context", context) - } + get realm() { + return this.#realm + } + + set realm(realm) { + this.#realm = realm + this.setAttribute("realm", realm) + } + + get clientId() { + return this.#clientId + } + + set clientId(clientId) { + this.#clientId = clientId + this.setAttribute("gateway", clientId) + } + + get redirectUrl() { + return this.#redirectUrl + } + + set redirectUrl(redirectUrl) { + this.#redirectUrl = redirectUrl + this.setAttribute("redirect-url", redirectUrl) + } + + get context() { + return this.#audience + } + + set context(context) { + this.#audience = context + this.setAttribute("context", context) + } }) - /*connectedCallback() { - if (typeof Keycloak === 'undefined') { - const script = document.createElement('script') - script.src = this.#url + '/js/keycloak.js' - script.type = 'text/javascript' - script.addEventListener('load', () => { - this.initKeycloak() - }) - document.head.appendChild(script) - } else { - this.initKeycloak() - } - }*/ - - /*initKeycloak() { - this.#keycloak = new Keycloak({ - url: this.#url, - realm: this.#realm, - clientId: this.#clientId - }) - - const keycloak = this.#keycloak - const url = this.#url - - keycloak.init({ - onLoad: 'login-required' - , checkLoginIframe: false - - }).then((authenticated) => { - console.log(authenticated ? 'Authenticated in ' + this.#realm : 'Not authenticated') - if (typeof KeycloakAuthorization === 'undefined') { - const authz = document.createElement('script') - authz.src = url + '/js/keycloak-authz.js' - authz.type = 'text/javascript' - authz.addEventListener('load', () => { - this.#authorization = new KeycloakAuthorization(keycloak) - }) - document.head.appendChild(authz) - } else { - this.#authorization = new KeycloakAuthorization(keycloak) - } - //console.log("expires: " + this.expirationDate(keycloak.tokenParsed.exp)) - - }).catch(function(err) { - console.error("Failed to initialize d4science boot component") - - }) - }*/ - - /*service(endpoint, method, params, onSuccess, onForbidden) { - if (this.#authorization == null) { - if (this.#attempts-- > 0) { - setTimeout(() => this.service(endpoint, method, params, onSuccess, onForbidden), this.#timer) - return - } else { - console.error("Impossible to initialize D4Science authorization component") - throw "Fatal error" - } - } - this.#keycloak.updateToken(30).then(() => { - const audience = encodeURIComponent(this.#audience) - this.#authorization.entitlement(audience).then(function (rpt) { - var req = new XMLHttpRequest() - req.open(method, endpoint, true) - req.setRequestHeader('Accept', 'application/json') - req.setRequestHeader('Authorization', 'Bearer ' + rpt) - - req.onreadystatechange = function () { - if (req.readyState == 4) { - if (req.status == 200) { - onSuccess(req.response) - } else if (req.status == 403) { - onForbidden(req.statusText) - } - } - } - req.send(params) - }) - }).catch(function() { - onForbidden('Failed to refresh token') - }) - }*/