Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix verifier admin panel #28

Merged
merged 4 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading