Skip to content

Commit

Permalink
Merge pull request #28 from wwWallet/verifier-revamp
Browse files Browse the repository at this point in the history
Fix verifier admin panel
  • Loading branch information
pstamatop authored Mar 26, 2024
2 parents 85fb153 + 6528994 commit a9610f3
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 94 deletions.
14 changes: 14 additions & 0 deletions public/js/check-expired.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function updateLayoutForExpiredCredentials(credentialPayloads) {
const currentDate = new Date();
credentialPayloads.forEach((credential, index) => {
const expirationDate = new Date(credential.expirationDate);
if (expirationDate < currentDate) {
const credentialBox = document.querySelectorAll('.credential-box')[index];
credentialBox.classList.add('expired-credential');
const expiredLabel = document.createElement('div');
expiredLabel.className = 'expired-label';
expiredLabel.textContent = 'Expired';
credentialBox.appendChild(expiredLabel);
}
});
}
20 changes: 17 additions & 3 deletions src/services/OpenidForPresentationReceivingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,14 +298,13 @@ export class OpenidForPresentationsReceivingService implements OpenidForPresenta

// store presentation
const newVerifiablePresentation = new VerifiablePresentationEntity()
newVerifiablePresentation.presentation_definition_id = presentation_submission.definition_id;
newVerifiablePresentation.presentation_definition_id = (JSON.parse(presentation_submission) as any).definition_id;
newVerifiablePresentation.claims = presentationClaims ?? null;
newVerifiablePresentation.status = true;
newVerifiablePresentation.raw_presentation = vp_token;
newVerifiablePresentation.presentation_submission = presentationSubmissionObject;
newVerifiablePresentation.date = new Date();
newVerifiablePresentation.state = verifierStateId as string;

this.verifiablePresentationRepository.save(newVerifiablePresentation);

console.error(msg);
Expand Down Expand Up @@ -404,7 +403,7 @@ export class OpenidForPresentationsReceivingService implements OpenidForPresenta
presentationClaims[desc.id].push({ name: claimName, value: value } as ClaimRecord);
});
console.log("Verification result = ", verificationResult)
if (!verificationResult.isValid) {
if (!verificationResult.isSignatureValid || !verificationResult.areRequiredClaimsIncluded) {
return { error: new Error("SD_JWT_VERIFICATION_FAILURE"), error_description: new Error(`Verification result ${JSON.stringify(verificationResult)}`) };
}
}
Expand Down Expand Up @@ -479,4 +478,19 @@ export class OpenidForPresentationsReceivingService implements OpenidForPresenta
else
return { status: false };
}

public async getPresentationById(id: string): Promise<{ status: boolean, presentationClaims?: PresentationClaims, rawPresentation?: string }> {
const vp = await this.verifiablePresentationRepository.createQueryBuilder('vp')
.where("id = :id", { id: id })
.getOne();

if (!vp?.raw_presentation || !vp.claims) {
return { status: false };
}

if (vp)
return { status: true, presentationClaims: vp.claims, rawPresentation: vp?.raw_presentation };
else
return { status: false };
}
}
1 change: 1 addition & 0 deletions src/services/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface OpenidForPresentationsReceivingInterface {
generateAuthorizationRequestURL(ctx: { req: Request, res: Response }, presentation_definition_id: string, directPostEndpoint?: string): Promise<{ url: URL; stateId: string }>;
getPresentationDefinitionHandler(ctx: { req: Request, res: Response }): Promise<void>;
getPresentationByState(state: string): Promise<{ status: boolean, presentationClaims?: PresentationClaims, rawPresentation?: string }>;
getPresentationById(id: string): Promise<{ status: boolean, presentationClaims?: PresentationClaims, rawPresentation?: string }>;

/**
* @throws
Expand Down
101 changes: 33 additions & 68 deletions src/verifier/verifierPanelRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ import AppDataSource from "../AppDataSource";
import { VerifiablePresentationEntity } from "../entities/VerifiablePresentation.entity";
import { appContainer } from "../services/inversify.config";
import { TYPES } from "../services/types";
import { VerifierConfigurationInterface } from "../services/interfaces";
import { OpenidForPresentationsReceivingInterface, VerifierConfigurationInterface } from "../services/interfaces";
import base64url from "base64url";
import locale from "../configuration/locale";
import { VerifierConfigurationService } from "../configuration/verifier/VerifierConfigurationService";
import { PresentationSubmission } from "@wwwallet/ssi-sdk";
import { JSONPath } from "jsonpath-plus";

const openidForPresentationReceivingService = appContainer.get<OpenidForPresentationsReceivingInterface>(TYPES.OpenidForPresentationsReceivingService);


const verifierPanelRouter = Router();
Expand All @@ -35,96 +33,63 @@ verifierPanelRouter.get('/', async (req, res) => {
})
})

type VerifiablePresentationWithDetails = VerifiablePresentationEntity & { holderInfo?: string, claims?: any };

verifierPanelRouter.get('/filter/by/definition/:definition_id', async (req, res) => {
const definition_id = req.params.definition_id;
if (!definition_id) {
return res.status(500).send({ error: "No definition id was specified" });
}
const verifiablePresentations = await verifiablePresentationRepository.createQueryBuilder('vp')
let verifiablePresentations = await verifiablePresentationRepository.createQueryBuilder('vp')
.where("vp.presentation_definition_id = :definition_id", { definition_id: definition_id })
.getMany();

const presentationsWithDetails: VerifiablePresentationWithDetails[] = verifiablePresentations.map(vp => {
try {
const decoded = vp.raw_presentation ? JSON.parse(base64url.decode(vp.raw_presentation.split('.')[1])) : null as any;
const holderInfo = decoded?.vp?.holder || "No Holder Info";
const claims = vp.claims;
return { ...vp, holderInfo, claims } as VerifiablePresentationWithDetails;
} catch (error) {
console.error("Error decoding VP:", error);
return { ...vp, holderInfo: 'Error decoding holder info' } as VerifiablePresentationWithDetails;
}
});

return res.render('verifier/presentations.pug', {
lang: req.lang,
verifiablePresentations: verifiablePresentations,
verifiablePresentations: presentationsWithDetails,
locale: locale[req.lang]
})
})



type PresentationView = {
descriptor_mapped_value: {
inputDescriptorId: string;
vcView: VerifiableCredentialView;
}[]
}

type VerifiableCredentialView = {
rows: {
name: string;
value: string;
}[];
}


verifierPanelRouter.get('/presentation/:presentation_id', async (req, res) => {
const presentation_id = req.params.presentation_id;
if (!presentation_id) {
return res.status(500).send({ error: "No presentation_id was specified" });
}
const verifiablePresentation = await verifiablePresentationRepository.createQueryBuilder('vp')
.where("vp.id = :presentation_id", { presentation_id: presentation_id })
.getOne();

if (!verifiablePresentation || !verifiablePresentation.raw_presentation) {
return res.status(400).render('error', {
msg: "Verifiable presentation not found",
const { presentationClaims, rawPresentation } = await openidForPresentationReceivingService.getPresentationById(presentation_id as string);

if (!presentationClaims || !rawPresentation) {
return res.render('error.pug', {
msg: "Failed to get presentation",
code: 0,
lang: req.lang,
locale: locale[req.lang]
});
locale: locale[req.lang],
})
}

const presentationSubmission = verifiablePresentation.presentation_submission as PresentationSubmission;

const payload = JSON.parse(base64url.decode(verifiablePresentation.raw_presentation?.split('.')[1])) as any;
const vcList = payload.vp.verifiableCredential as string[];
const credentialList = [];
for (const vc of vcList) {
const vcPayload = JSON.parse(base64url.decode(vc.split('.')[1])) as any;
credentialList.push(vcPayload.vc);
}
const presentationPayload = JSON.parse(base64url.decode(rawPresentation.split('.')[1])) as any;
const credentials = presentationPayload.vp.verifiableCredential.map((vcString: any) => {
return JSON.parse(base64url.decode(vcString.split('.')[1]));
}).map((credential: any) => credential.vc);

const presentationDefinition = appContainer.resolve(VerifierConfigurationService)
.getPresentationDefinitions()
.filter(pd =>
pd.id == verifiablePresentation.presentation_definition_id
)[0];

let view: PresentationView = { descriptor_mapped_value: [] };
for (const descriptor of presentationDefinition.input_descriptors) {
const correspondingElementOnSubmission = presentationSubmission.descriptor_map.filter(desc => desc.id == descriptor.id)[0];

const vcRows = descriptor.constraints.fields.map((field) => {
const vcPath = correspondingElementOnSubmission.path;
const payload = JSON.parse(base64url.decode(verifiablePresentation.raw_presentation?.split('.')[1] as string)) as any;
const vcJwtString = JSONPath({ json: payload.vp, path: vcPath })[0];
const vcPayload = JSON.parse(base64url.decode(vcJwtString.split('.')[1])) as any;
const valuePath = field.path[0];
const value = JSONPath({ json: vcPayload.vc, path: valuePath })[0] as string;
return { name: valuePath, value: value }
});
view.descriptor_mapped_value.push({
inputDescriptorId: descriptor.id,
vcView: { rows: vcRows }
});
}
console.dir(view, { depth: null })
return res.render('verifier/detailed-presentation.pug', {
view: view,
status: verifiablePresentation.status,
lang: req.lang,
locale: locale[req.lang]
presentationClaims: presentationClaims,
credentialPayloads: credentials,
locale: locale[req.lang],
})
})

Expand Down
7 changes: 3 additions & 4 deletions views/verifier/definitions.pug
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
extends ../layout.pug
block layout-content
.container
h1 Job positions
h1 Verifiable Presentations
.card-container
for definition in presentationDefinitions
.card
.card-title Software Engineer - Core
p.card-text Send your Bachelor Diploma to apply for "Software Engineer - Core"
.card-title #{definition.title}
p.card-text #{definition.description}
.card-buttons
button.btn.btn-primary(onclick=`window.location.href = "/verifier-panel/filter/by/definition/${definition.id}"`) See presentations

46 changes: 35 additions & 11 deletions views/verifier/detailed-presentation.pug
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
extends ../layout.pug

block layout-content
.container
h1 Detailed Presentation
.card-container
for inputDescriptor in view.descriptor_mapped_value
div.table-container
table
tbody
each row, index in inputDescriptor.vcView.rows
tr(class= index % 2 === 0 ? 'even' : 'odd')
td #{row.name}
td #{row.value}
#success-container
.container2
.centered-layout
h1 Detailed Presentation
.inline-box-container(style="display: flex; gap:30px; flex-direction: row; flex-wrap: wrap;")
// Iterate over each credential and display an inline box with dynamic background color and text color
each credential, index in credentialPayloads
- const branding = credential.credentialBranding || { backgroundColor: 'red', textColor: 'black' }
- const imageUrl = credential.credentialBranding.image.url // Get the corresponding image URL
.credential-box(style=`display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-start; position: relative; padding: 10px;`, id=`credential-box-${index}`)
img(style="width: 240px; height: 150px; border-radius: 10px;")(src=imageUrl)

h3 Requested claims extracted from credentials

// Text area to display claims
textarea#json-textarea.wide(style="border: 1px solid #ccc; padding: 10px; margin-bottom:20px; padding-top:0px; background-color: white; color: black; overflow: auto; resize: vertical; width:500px; min-height: 200px;")
//- Display the 'credentialPayloads' JSON object
| #{JSON.stringify(presentationClaims, null, 2)}

h3 Credentials

// Text area to display credentials JSON format
textarea#json-textarea.wide(style="border: 1px solid #ccc; padding: 10px; margin-bottom:20px; padding-top:0px; background-color: white; color: black; overflow: auto; resize: vertical; width:500px; min-height: 200px;")
//- Display the 'credentialPayloads' JSON object
| #{JSON.stringify(credentialPayloads, null, 2)}

script(src="/js/check-expired.js")
script.
var credentialPayloads = !{JSON.stringify(credentialPayloads)};
updateLayoutForExpiredCredentials(credentialPayloads);

block footer
include ../footer.pug

link(rel="stylesheet" href="/styles/index.css")
link(rel="stylesheet" href="/styles/detailed-presentation.css")
script(src="/js/index.js")
20 changes: 13 additions & 7 deletions views/verifier/presentations.pug
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ extends ../layout.pug
block layout-content
.container
h1 Received Verifiable Presentations
.card-container
for vp in verifiablePresentations
.card
.card-title #{vp.id}
p.card-text Date received: #{vp.date}
.card-buttons
button.btn.btn-secondary(onclick=`window.location.href = "/verifier-panel/presentation/${vp.id}"`) Inspect details
if verifiablePresentations && verifiablePresentations.length > 0
.card-container
for vp in verifiablePresentations
.card
.card-title Unique ID: #{vp.id}
p
.card-text Date: #{vp.date}
.card-text Claims: #{JSON.stringify(vp.claims, null, 2)}
.card-text Holder Info: #{vp.holderInfo}
.card-buttons
button.btn.btn-primary(onclick=`window.location.href = "/verifier-panel/presentation/${vp.id}"`) Inspect details
else
h4 No Verifiable Presentations
2 changes: 1 addition & 1 deletion views/verifier/public_definitions.pug
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
extends ../layout.pug
block layout-content
.container
h1 Job positions
h1 Present your Verifiable Credentials
.card-container
for definition in presentationDefinitions
.card
Expand Down

0 comments on commit a9610f3

Please sign in to comment.