diff --git a/public/css/style.css b/public/css/style.css index 06e0297..580b45b 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -178,8 +178,59 @@ body { text-align: center; font-size: 20px } +.button-sd { + left: 40%; +} label { display: inline-block; margin: 10px; } + +.card-wide.mdl-card { + width: 512px; + background-color: rgba(0, 0, 255, 0.02); +} + +.card-table { + width: 100%; +} +.card-small.mdl-card { + width: fit-content; + min-height: 0; + height: fit-content; + background-color: rgba(0, 0, 255, 0.02); +} + +.card-wide > .mdl-card__title { + background-image: linear-gradient(rgba(0, 0, 255, 0.5), rgba(0, 0, 255, 0)), url('https://www.gxfs.eu/wp-content/uploads/2022/01/GFXS_DE_Logo.jpg'); + background-size: 512px, 80px; + background-repeat: no-repeat; + background-position: top right; + color: #000; + height: 80px; +} +.card-wide > .mdl-card__supporting-text { + background-color: #000 10%; +} + +.card-small > .mdl-card__title { + background-image: linear-gradient(rgba(0, 0, 255, 0.5), rgba(0, 0, 255, 0)); + background-size: 512px, 30px; + background-repeat: no-repeat; + background-position: top right; + color: #000; + height: 30px; +} +.card-small > .mdl-card__supporting-text { + background-color: #000 10%; + width: 100%; +} + + +.block { + float: left; + padding: 0 10px; + width: 50%; +} + diff --git a/server.js b/server.js index 8728038..9dca2b2 100644 --- a/server.js +++ b/server.js @@ -248,6 +248,50 @@ async function get_entities(type, req_session) { } } +async function register_sd(sd, req_session) { + let result = { + err: null, + status: null + } + const trustedIssuerEntity = { + type: "TrustedIssuer", + id: "urn:ngsi-ld:TrustedIssuer:" + sd.id, + issuer: { + type: "Property", + value: sd.id + }, + selfDescription: { + type: "Property", + value: sd + } + } + var path = req_session.cb_endpoint + '/entities'; + var url = new URL(path); + try { + info("Register trusted issuer: " + JSON.stringify(trustedIssuerEntity)) + const post_response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + req_session.access_token, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(trustedIssuerEntity) + }); + if (post_response.status != 201) { + const errorBody = await post_response.text(); + result.err = `Failed to register issuer: ${post_response.status} - ${errorBody}`; + info('Failed to register issuer: ${post_response.status} - ${errorBody}'); + return result; + } + debug('Successfully registered issuer'); + result.status = post_response.status; + return result; + } catch (e) { + result.err = e; + return result; + } +} + // PATCH change delivery attribute async function patch_delivery(id, attr, val, req_session) { let result = { @@ -272,7 +316,7 @@ async function patch_delivery(id, attr, val, req_session) { }, body: JSON.stringify(body) }); - if (patch_response.status != 204) { + if (patch_response.status != 20) { const errorBody = await patch_response.text(); result.err = `Access denied when patching delivery order: ${errorBody}`; debug('Received error when patching delivery order: %o', errorBody); @@ -297,6 +341,27 @@ function render_error(res, user, error) { }); } +async function evaluate_selfdescription(req_session) { + info("Evaluate session") + if (req_session.access_token) { + info("The token " + req_session.access_token) + var decoded = jwt(req_session.access_token) + if (decoded['verifiablePresentation']) { + info("Evaluate vp " + JSON.stringify(decoded['verifiablePresentation'])) + // we have a gaia-x credential + for(const vp of decoded['verifiablePresentation']) { + info("Evaluate vc in vp " + JSON.stringify(vp)) + if (vp['credentialSubject']['type'] === "gx:LegalParticipant") { + info("The subject " + JSON.stringify(vp['credentialSubject'])) + return vp['credentialSubject'] + } + } + } + } + info("No sd") + return null; +} + // Obtain email parameter from JWT access_token of user async function evaluate_user(req_session) { info("Evaluate session") @@ -304,14 +369,25 @@ async function evaluate_user(req_session) { info("The token " + req_session.access_token) var decoded = jwt(req_session.access_token) if (decoded['email']) { + info("EMAIL") // plain oidc return decoded['email'] - } else if (decoded['verifiableCredential']){ + } else if (decoded['verifiableCredential']) { + info("VC") // we have a vc return decoded['verifiableCredential']['credentialSubject']['firstName'] + " "+ decoded['verifiableCredential']['credentialSubject']['familyName'] + } else if (decoded['verifiablePresentation']) { + info("VP") + // we have a gaia-x credential + for(const vp of decoded['verifiablePresentation']) { + info("Evaluate" + vp) + if (vp['credentialSubject']['firstName'] && vp['credentialSubject']['familyName']) { + return vp['credentialSubject']['firstName'] + " "+ vp['credentialSubject']['familyName'] + } + } } - - } + info(JSON.stringify(decoded)) + } info("No token") return null; } @@ -457,21 +533,86 @@ app.get('/logout', (req, res) => { app.get('/portal', async (req, res) => { info('GET /portal: Call to portal page'); var user = await evaluate_user(req.session); + var sd = await evaluate_selfdescription(req.session); if (!user) { info('User was not logged in'); render_error(res, null, 'Not logged in'); return; } + let trusted_issuers = [] + if (sd) { + info("Got " + JSON.stringify(sd)) + trusted_issuers_result = await get_entities("TrustedIssuer", req.session) + if (!trusted_issuers_result.err) { + for (let i = 0; i < trusted_issuers_result.entities.length; i++) { + trusted_issuers.push(trusted_issuers_result.entities[i].selfDescription.value) + } + } + } res.render('portal', { title: config.title, entity_id: '', user: user, + sd: sd, + trusted_participants: trusted_issuers, get_label: config.getLabel, input_label: config.inputLabel }); }); +app.post('/sd', async(req, res) => { + info('Try to post self-description.') + // just for rendering + var user = await evaluate_user(req.session); + // the sd to be registerd + var sd = await evaluate_selfdescription(req.session) + if(!sd) { + console.warn('Session does not conatin a self description.'); + render_error(res, null, 'Not logged in'); + return; + } + + const result = await register_sd(sd, req.session) + + let trusted_issuers = [] + + trusted_issuers_result = await get_entities("TrustedIssuer", req.session) + if (!trusted_issuers_result.err) { + for (let i = 0; i < trusted_issuers_result.entities.length; i++) { + trusted_issuers.push(trusted_issuers_result.entities[i].selfDescription.value) + } + } + + + if (result.err) { + res.render('portal', { + title: config.title, + entity_id: '', + user: user, + sd: sd, + registered: false, + error: result.err, + trusted_participants: trusted_issuers, + get_label: config.getLabel, + input_label: config.inputLabel + }); + return + } else { + res.render('portal', { + title: config.title, + entity_id: '', + user: user, + sd: sd, + registered: true, + trusted_participants: trusted_issuers, + get_label: config.getLabel, + input_label: config.inputLabel + }); + return + } +}) + // POST /portal // View/change delivery order app.post('/portal', async (req, res) => { diff --git a/views/default.pug b/views/default.pug index 4979d95..10b6da9 100644 --- a/views/default.pug +++ b/views/default.pug @@ -3,12 +3,16 @@ html head title #{title} link(rel='stylesheet', href='/css/style.css') + link(rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Material+Icons') + link(rel='stylesheet', href='https://code.getmdl.io/1.3.0/material.indigo-pink.min.css') + script(defer, src='https://code.getmdl.io/1.3.0/material.min.js') meta(name="viewport" content="width=device-width, initial-scale=1") body - main - block header - header.header - a.title(href='/') - h1 #{title} - block topnav - block content + .mdl-layout.mdl-js-layout.mdl-layout--fixed-header + main + block header + header.header + a.title(href='/') + h1 #{title} + block topnav + block content diff --git a/views/portal-layout.pug b/views/portal-layout.pug index 1afdccb..ca7d895 100644 --- a/views/portal-layout.pug +++ b/views/portal-layout.pug @@ -7,10 +7,13 @@ block topnav block content div.container - block search - if (entities) + if (!sd) + block search + if (entities && !sd) block entities if (entity) block entity if (delivery) block delivery + if (sd) + block sd \ No newline at end of file diff --git a/views/portal.pug b/views/portal.pug index 17d3bea..7e7c3ff 100644 --- a/views/portal.pug +++ b/views/portal.pug @@ -36,6 +36,82 @@ block entities tr td #{i.type} td #{i.id} +block sd + div.block + div.card-wide.mdl-card.mdl-shadow--2dp + div.mdl-card__title + h2.mdl-card__title-text + | #{sd.type} + div.mdl-card__supporting-text + div.mdl-list + div.mdl-list__item--two-line + span.mdl-list__item-primary-content + i.material-icons.mdl-list__item-icon badge + if sd["id"] + span + h6 Id + span.mdl-list__item-text-body #{sd["id"]} + div.mdl-list__item--two-line + span.mdl-list__item-primary-content + i.material-icons.mdl-list__item-icon badge + if sd["gx:legalName"] + span + h6 Legal Name + span.mdl-list__item-text-body #{sd["gx:legalName"]} + div.mdl-list__item--two-line + span.mdl-list__item-primary-content + i.material-icons.mdl-list__item-icon location_city + if sd["gx:headquarterAddress"] + span + h6 Headquarter Address + span.mdl-list__item-text-body #{JSON.stringify(sd["gx:headquarterAddress"])} + div.mdl-list__item--two-line + span.mdl-list__item-primary-content + i.material-icons.mdl-list__item-icon location_city + if sd["gx:legalAddress"] + span + h6 Legal Address + span.mdl-list__item-text-body #{JSON.stringify(sd["gx:legalAddress"])} + div.mdl-list__item--two-line + span.mdl-list__item-primary-content + i.material-icons.mdl-list__item-icon numbers + if sd["gx:legalRegistrationNumber"] + span + h6 Registration Number + span.mdl-list__item-text-body #{JSON.stringify(sd["gx:legalRegistrationNumber"])} + div.mdl-list__item--two-line + span.mdl-list__item-primary-content + i.material-icons.mdl-list__item-icon dataset + if sd["gx-terms-and-conditions:gaiaxTermsAndConditions"] + span + h6 Terms and conditions + span.mdl-list__item-text-body #{sd["gx-terms-and-conditions:gaiaxTermsAndConditions"]} + div.mdl-card__actions.mdl-card--border + if (error) + button.mdl-button.mdl-js-button.mdl-button--fab.mdl-button--colored + i.material-icons warning + span #{error} + if (!registered && !error) + form(action='/sd', method='POST') + div.form-group + button.button-sd.mdl-button.mdl-js-button.mdl-button--fab.mdl-button--colored(type="submit") + i.material-icons add + if (registered) + button.button-sd.mdl-button.mdl-js-button.mdl-button--fab.mdl-button--colored + i.material-icons done + div.block + h4 Trusted Participants + each sd in trusted_participants + div.card-small.mdl-card.mdl-shadow--2dp + div.mdl-card__title + div.mdl-card__supporting-text + table.card-table + if sd["id"] + tr + td + i.material-icons.mdl-list__item-icon badge + td Id + td #{sd["id"]} block delivery div.content-delivery-row h2.delivery-id