Reformatting code. Replaced alerts. Removed download method.
This commit is contained in:
parent
1321181ad7
commit
c2421ec5ab
576
boot/d4s-boot.js
576
boot/d4s-boot.js
|
@ -1,367 +1,273 @@
|
|||
/**
|
||||
* D4Science boot component that handles keykloak authz
|
||||
* <d4s-boot-2
|
||||
* /@url: keycloak url, for example: https://accounts.d4science.org/auth]
|
||||
* /@realm: is the realm [defaults to: d4science]
|
||||
* /@gateway: is the client id, for example: "pre.d4science.org"
|
||||
* /@context: is the audience, for example: "%2Fpred4s%2Fpreprod%2FpreVRE"
|
||||
* /@redirect-url: where to go on logout
|
||||
*/
|
||||
window.customElements.define('d4s-boot-2', class extends HTMLElement {
|
||||
|
||||
#keycloak = null
|
||||
#authorization = null
|
||||
#url = null
|
||||
#realm = "d4science"
|
||||
#clientId = null
|
||||
#redirectUrl = null
|
||||
#audience = null
|
||||
#authenticated = false
|
||||
#locked = true
|
||||
#queue = []
|
||||
#interval = null
|
||||
#keycloak = null
|
||||
#authorization = null
|
||||
#url = null
|
||||
#realm = "d4science"
|
||||
#clientId = null
|
||||
#redirectUrl = null
|
||||
#audience = null
|
||||
#authenticated = false
|
||||
#locked = true
|
||||
#queue = []
|
||||
#interval = null
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
unlock(){
|
||||
this.#locked = false
|
||||
}
|
||||
unlock() {
|
||||
this.#locked = false
|
||||
}
|
||||
|
||||
fire(etype){
|
||||
const evt = new CustomEvent(etype, { detail : ''})
|
||||
document.dispatchEvent(evt)
|
||||
}
|
||||
fire(etype) {
|
||||
const evt = new CustomEvent(etype, { detail : ''})
|
||||
document.dispatchEvent(evt)
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
connectedCallback() {
|
||||
this.startStateChecker()
|
||||
|
||||
this.startStateChecker()
|
||||
this.loadKeycloak().then(() => {
|
||||
console.log("Keycloak loaded")
|
||||
return this.initKeycloak()
|
||||
|
||||
this.loadKeycloak().then(()=>{
|
||||
|
||||
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(()=>{
|
||||
|
||||
this.#authenticated = true
|
||||
this.unlock()
|
||||
this.fire("authenticated")
|
||||
|
||||
}).catch(err=>{
|
||||
console.error("Unable to initialize Keycloak",err)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
return JSON.parse(jsonPayload);
|
||||
}
|
||||
|
||||
get authenticated(){
|
||||
return this.#authenticated
|
||||
}
|
||||
expirationDate(utc) {
|
||||
let d = new Date(0)
|
||||
d.setUTCSeconds(utc)
|
||||
return d
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this.#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))
|
||||
})
|
||||
}
|
||||
|
||||
set url(url) {
|
||||
this.#url = url
|
||||
this.setAttribute("url", url)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
get realm() {
|
||||
return this.#realm
|
||||
logout() {
|
||||
if (this.#keycloak) {
|
||||
if (!this.#redirectUrl) {
|
||||
console.error("Missing required @redirectUrl attribute in d4s-boot")
|
||||
} else {
|
||||
this.#keycloak.logout({
|
||||
redirectUri: this.#redirectUrl
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set realm(realm) {
|
||||
this.#realm = realm
|
||||
this.setAttribute("realm", realm)
|
||||
}
|
||||
static get observedAttributes() {
|
||||
return ["url", "realm", "gateway", "redirect-url", "context"];
|
||||
}
|
||||
|
||||
get clientId() {
|
||||
return this.#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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set clientId(clientId) {
|
||||
this.#clientId = clientId
|
||||
this.setAttribute("gateway", clientId)
|
||||
}
|
||||
get authenticated(){
|
||||
return this.#authenticated
|
||||
}
|
||||
|
||||
get redirectUrl() {
|
||||
return this.#redirectUrl
|
||||
}
|
||||
get url() {
|
||||
return this.#url
|
||||
}
|
||||
|
||||
set redirectUrl(redirectUrl) {
|
||||
this.#redirectUrl = redirectUrl
|
||||
this.setAttribute("redirect-url", redirectUrl)
|
||||
}
|
||||
set url(url) {
|
||||
this.#url = url
|
||||
this.setAttribute("url", url)
|
||||
}
|
||||
|
||||
get context() {
|
||||
return this.#audience
|
||||
}
|
||||
get realm() {
|
||||
return this.#realm
|
||||
}
|
||||
|
||||
set context(context) {
|
||||
this.#audience = context
|
||||
this.setAttribute("context", context)
|
||||
}
|
||||
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')
|
||||
})
|
||||
}*/
|
||||
|
|
Loading…
Reference in New Issue