From f4687cf82417bde679c59068cfa3e3140d1cdd53 Mon Sep 17 00:00:00 2001 From: parth5805 Date: Wed, 26 Mar 2025 16:07:47 +0530 Subject: [PATCH 1/2] Updated to latest ACA-Py version for compatibility and improvements Signed-off-by: Parth Patel --- .../controllers/acme-controller/Dockerfile | 20 +- .../controllers/acme-controller/app.js | 7 + .../acme-controller/docker-compose.yml | 13 + .../controllers/acme-controller/package.json | 1 + .../acme-controller/routes/connection.js | 69 ++++- .../acme-controller/routes/proof.js | 89 +++---- .../acme-controller/services/AgentService.js | 243 +++++++++--------- .../views/accept_connection.hbs | 30 ++- .../views/partials/proof/proof_card.hbs | 62 ++++- .../acme-controller/views/request_proof.hbs | 46 ++-- .../alice-controller/docker-compose.yml | 13 + .../accept-connection.component.ts | 15 +- .../proof-card/proof-card.component.html | 9 +- .../src/app/services/agent.service.ts | 46 +++- .../Components/AcceptConnection.razor | 39 ++- .../Connection/Components/NewConnection.razor | 39 ++- .../Credential/Credential.razor | 41 +-- .../FaberController/FaberController.csproj | 2 + .../Services/FCAgentService.cs | 87 +++++-- .../FaberController/Startup.cs | 21 +- .../faber-controller/docker-compose.yml | 13 + AliceFaberAcmeDemo/run_demo | 201 +++++++++------ 22 files changed, 756 insertions(+), 350 deletions(-) create mode 100644 AliceFaberAcmeDemo/controllers/acme-controller/docker-compose.yml create mode 100644 AliceFaberAcmeDemo/controllers/alice-controller/docker-compose.yml create mode 100644 AliceFaberAcmeDemo/controllers/faber-controller/docker-compose.yml diff --git a/AliceFaberAcmeDemo/controllers/acme-controller/Dockerfile b/AliceFaberAcmeDemo/controllers/acme-controller/Dockerfile index 9811e86..e22fc3e 100644 --- a/AliceFaberAcmeDemo/controllers/acme-controller/Dockerfile +++ b/AliceFaberAcmeDemo/controllers/acme-controller/Dockerfile @@ -1,4 +1,18 @@ -FROM node:22 as build -COPY . . +FROM node:22 + +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies RUN npm install -ENTRYPOINT [ "npm", "start" ] + +# Copy the rest of the application files +COPY . . + +# Expose the port the app runs on +EXPOSE 80 + +# Start the application +CMD ["node", "app.js"] diff --git a/AliceFaberAcmeDemo/controllers/acme-controller/app.js b/AliceFaberAcmeDemo/controllers/acme-controller/app.js index 2b33249..b3ce47e 100644 --- a/AliceFaberAcmeDemo/controllers/acme-controller/app.js +++ b/AliceFaberAcmeDemo/controllers/acme-controller/app.js @@ -54,4 +54,11 @@ app.use(function(err, req, res, next) { res.render('error'); }); +// Start the server +const PORT = process.env.PORT || 80; +app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +}); + + module.exports = app; diff --git a/AliceFaberAcmeDemo/controllers/acme-controller/docker-compose.yml b/AliceFaberAcmeDemo/controllers/acme-controller/docker-compose.yml new file mode 100644 index 0000000..e9c7b4f --- /dev/null +++ b/AliceFaberAcmeDemo/controllers/acme-controller/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3' +services: + acme-controller: + build: . + ports: + - "8140:80" # Expose a different port for the controller + environment: + - AGENT_HOST=host.docker.internal + - AGENT_PORT=8040 + networks: + - aca +networks: + aca: \ No newline at end of file diff --git a/AliceFaberAcmeDemo/controllers/acme-controller/package.json b/AliceFaberAcmeDemo/controllers/acme-controller/package.json index a6463ba..1483437 100644 --- a/AliceFaberAcmeDemo/controllers/acme-controller/package.json +++ b/AliceFaberAcmeDemo/controllers/acme-controller/package.json @@ -10,6 +10,7 @@ "ext": "js,json,hbs,css" }, "dependencies": { + "axios": "^1.8.3", "cookie-parser": "^1.4.7", "debug": "^2.6.9", "express": "^4.21.1", diff --git a/AliceFaberAcmeDemo/controllers/acme-controller/routes/connection.js b/AliceFaberAcmeDemo/controllers/acme-controller/routes/connection.js index 259dd59..72a7c69 100644 --- a/AliceFaberAcmeDemo/controllers/acme-controller/routes/connection.js +++ b/AliceFaberAcmeDemo/controllers/acme-controller/routes/connection.js @@ -64,6 +64,7 @@ async function handleNewConnectionPost(req, res, next) { const agentService = require('../services/AgentService'); const invitation = await agentService.createInvitation(); + console.log('Invitation Object:', invitation); // Debugging log if (invitation) { invitation.invitation = JSON.stringify(invitation.invitation, null, 4); } @@ -103,18 +104,80 @@ async function handleAcceptConnectionGet(req, res, next) { }); } +// async function handleAcceptConnectionPost(req, res, next) { +// const agentService = require('../services/AgentService'); + +// const errors = validationResult(req); +// if (!errors.isEmpty()) { +// req.errors = errors.array({ onlyFirstError: true }); +// req.invitaion = req.body; +// return next(); +// } + +// const result=await agentService.receiveInvitation(req.body.invitation_object); +// console.log(result); +// res.status(201).redirect('/connections/active'); +// } + +// async function handleAcceptConnectionPost(req, res, next) { +// const agentService = require('../services/AgentService'); + +// const errors = validationResult(req); +// if (!errors.isEmpty()) { +// req.errors = errors.array({ onlyFirstError: true }); +// req.invitaion = req.body; +// return next(); +// } + +// await agentService.receiveInvitation(req.body.invitation_object); +// res.status(201).redirect('/connections/active'); +// } + + +// async function handleAcceptConnectionPost(req, res, next) { +// const agentService = require('../services/AgentService'); + +// const errors = validationResult(req); +// if (!errors.isEmpty()) { +// req.errors = errors.array({ onlyFirstError: true }); +// req.invitation = req.body; +// console.error('Validation errors:', req.errors); // Debugging log +// return next(); +// } + +// console.log('Received invitation object:', req.body.invitation_object); // Debugging log + +// try { +// const response = await agentService.receiveInvitation(req.body.invitation_object); +// console.log('API response from receiveInvitation:', response); // Debugging log +// res.status(201).redirect('/connections/active'); +// } catch (error) { +// console.error('Error in handleAcceptConnectionPost:', error); // Debugging log +// res.status(500).send('Failed to process invitation'); +// } +// } + async function handleAcceptConnectionPost(req, res, next) { const agentService = require('../services/AgentService'); const errors = validationResult(req); if (!errors.isEmpty()) { req.errors = errors.array({ onlyFirstError: true }); - req.invitaion = req.body; + req.invitation = req.body; + console.error('Validation errors:', req.errors); // Debugging log return next(); } - await agentService.receiveInvitation(req.body.invitation_object); - res.status(201).redirect('/connections/active'); + console.log('Received invitation object:', req.body.invitation_object); // Debugging log + + try { + const response = await agentService.receiveInvitation(req.body.invitation_object); + console.log('API response from receiveInvitation:', response); // Debugging log + res.status(201).redirect('/connections/active'); + } catch (error) { + console.error('Error in handleAcceptConnectionPost:', error); // Debugging log + res.status(500).send('Failed to process invitation'); + } } router.get('/:id/remove', async function (req, res, next) { diff --git a/AliceFaberAcmeDemo/controllers/acme-controller/routes/proof.js b/AliceFaberAcmeDemo/controllers/acme-controller/routes/proof.js index d1d4813..2a2f806 100644 --- a/AliceFaberAcmeDemo/controllers/acme-controller/routes/proof.js +++ b/AliceFaberAcmeDemo/controllers/acme-controller/routes/proof.js @@ -10,53 +10,56 @@ navLinkService.registerCustomLinks([ ]); const proofJSON = { + "comment": "This is a comment about the reason for the proof", "connection_id": "", - "proof_request": { - "name": "Proof of Education", - "version": "1.0", - "requested_attributes": { - "0_name_uuid": { - "name": "name", - "restrictions": [ - { - "cred_def_id": "" + "presentation_request": { + "indy": { + "name": "Proof of Education", + "version": "1.0", + "requested_attributes": { + "0_name_uuid": { + "name": "name", + "restrictions": [ + { + "cred_def_id": "" + } + ] + }, + "0_date_uuid": { + "name": "date", + "restrictions": [ + { + "cred_def_id": "" + } + ] + }, + "0_degree_uuid": { + "name": "degree", + "restrictions": [ + { + "cred_def_id": "" + } + ] + }, + "0_self_attested_thing_uuid": { + "name": "self_attested_thing" + } + }, + "requested_predicates": { + "0_age_GE_uuid": { + "name": "birthdate_dateint", + "p_type": ">=", + "p_value": 18, + "restrictions": [ + { + "cred_def_id": "" + } + ] + } } - ] - }, - "0_date_uuid": { - "name": "date", - "restrictions": [ - { - "cred_def_id": "" - } - ] - }, - "0_degree_uuid": { - "name": "degree", - "restrictions": [ - { - "cred_def_id": "" - } - ] - }, - "0_self_attested_thing_uuid": { - "name": "self_attested_thing" - } - }, - "requested_predicates": { - "0_age_GE_uuid": { - "name": "birthdate_dateint", - "p_type": ">=", - "p_value": 18, - "restrictions": [ - { - "cred_def_id": "" - } - ] } - } } - } +}; router.use(function (req, res, next) { navLinkService.clearLinkClasses(); diff --git a/AliceFaberAcmeDemo/controllers/acme-controller/services/AgentService.js b/AliceFaberAcmeDemo/controllers/acme-controller/services/AgentService.js index ccf1a61..20ff278 100644 --- a/AliceFaberAcmeDemo/controllers/acme-controller/services/AgentService.js +++ b/AliceFaberAcmeDemo/controllers/acme-controller/services/AgentService.js @@ -1,76 +1,30 @@ const http = require('http'); +const axios = require('axios'); -const hostname = process.env.ACME_AGENT_HOST || 'localhost'; + +const hostname = process.env.ACME_AGENT_HOST || 'host.docker.internal'; const port = 8041; -console.log('Agent is running on: ' + `http://${hostname}:${port}`); -function httpAsync(options, body) { - return new Promise(function (resolve, reject) { - const req = http.request(options, (res) => { - const { statusCode } = res; - const contentType = res.headers['content-type']; - - let e; - if (statusCode !== 200) { - e = new Error('Request Failed.\n' + `Status Code: ${statusCode}`); - } else if (!/^application\/json/.test(contentType)) { - e = new Error('Invalid content-type.\n' + `Expected application/json but received ${contentType}`); - } - if (e) { - // Consume response data to free up memory - res.resume(); - return reject(e); - } +console.log('Agent is running on: ' + `http://${hostname}:${port}`); - res.setEncoding('utf8'); - let rawData = ''; - res.on('data', (chunk) => { rawData += chunk; }); - res.on('end', () => { - try { - const parsedData = JSON.parse(rawData); - return resolve(parsedData); - } catch (e) { - return reject(e); - } - }); - }).on('error', (e) => { - return reject(e); - }); - - if (body) { - req.write(body || ''); - } - - req.end(); - }); -} class AgentService { + async getStatus() { try { - const response = await httpAsync({ - hostname: hostname, - port: port, - path: '/status', - method: 'GET' - }); - return response; + const response = await axios.get(`http://${hostname}:${port}/status`); + return response.data; } catch (error) { console.error(error); return null; } } - + async getConnections() { try { - const response = await httpAsync({ - hostname: hostname, - port: port, - path: '/connections', - method: 'GET' - }); - return response.results; + const response = await axios.get(`http://${hostname}:${port}/connections`); + return response.data.results; } catch (error) { console.error(error); return []; @@ -79,81 +33,136 @@ class AgentService { async createInvitation() { try { - const response = await httpAsync({ - hostname: hostname, - port: port, - path: '/connections/create-invitation', - method: 'POST' + // Define the request payload + const invitationRequest = { + handshake_protocols: ["https://didcomm.org/didexchange/1.1"] + }; + + // Make the POST request using axios + const response = await axios.post(`http://${hostname}:${port}/out-of-band/create-invitation`, invitationRequest, { + headers: { + 'Content-Type': 'application/json' + } }); - return response; + + // Log the invitation data + console.log("Invitation created successfully:", response.data); + + return response.data; } catch (error) { - console.error(error); + // Log any errors + console.error("Error creating invitation:", error.message); return {}; } } - async receiveInvitation(invitation) { - try { - const response = await httpAsync({ - hostname: hostname, - port: port, - path: '/connections/receive-invitation', - method: 'POST' - }, invitation); - return response; - } catch (error) { - console.error(error); - return; +// async receiveInvitation(invitation) { +// try { +// // Step 1: Receive the invitation +// const receiveInvitationUrl = `http://${hostname}:${port}/out-of-band/receive-invitation`; +// console.log('Sending request to /out-of-band/receive-invitation with URL:', receiveInvitationUrl); +// console.log('Request body:', JSON.stringify(invitation)); + +// const response = await axios.post(receiveInvitationUrl, invitation, { +// headers: { +// 'Content-Type': 'application/json' +// } +// }); + +// console.log('API response from /out-of-band/receive-invitation:', response.data); + +// // Step 2: Extract connection ID and accept the invitation +// const connId = response.data.connection_id; +// if (connId) { +// const acceptInvitationUrl = `http://${hostname}:${port}/didexchange/${connId}/accept-invitation`; +// console.log(`Sending request to /didexchange/${connId}/accept-invitation with URL:`, acceptInvitationUrl); + +// await axios.post(acceptInvitationUrl, {}, { +// headers: { +// 'Content-Type': 'application/json' +// } +// }); + +// console.log(`Invitation accepted for connection ID: ${connId}`); +// } else { +// console.error('No connection ID found in the response.'); +// } + +// return response.data; +// } catch (error) { +// console.error('Error in receiveInvitation:', error.message); +// if (error.response) { +// console.error('Response data:', error.response.data); +// console.error('Response status:', error.response.status); +// } +// throw error; // Propagate the error to the caller +// } +// } + +async receiveInvitation(invitation) { + try { + // Step 1: Receive the invitation + const receiveInvitationUrl = `http://${hostname}:${port}/out-of-band/receive-invitation`; + console.log('Sending request to /out-of-band/receive-invitation with URL:', receiveInvitationUrl); + console.log('Request body:', JSON.stringify(invitation)); + + const response = await axios.post(receiveInvitationUrl, invitation, { + headers: { + 'Content-Type': 'application/json' + } + }); + + console.log('API response from /out-of-band/receive-invitation:', response.data); + + return response.data; // Return only the response from receive-invitation + } catch (error) { + console.error('Error in receiveInvitation:', error.message); + if (error.response) { + console.error('Response data:', error.response.data); + console.error('Response status:', error.response.status); } + throw error; // Propagate the error to the caller } +} - async removeConnection(connectionId) { - try { - await httpAsync({ - hostname: hostname, - port: port, - path: `/connections/${connectionId}/remove`, - method: 'POST' - }); - } catch (error) { - console.error(error); - } finally { - return; - } +async removeConnection(connectionId) { + if (!connectionId) { + console.error('Must provide a connection ID'); + return; } - async getProofRequests() { - try { - const response = await httpAsync({ - hostname: hostname, - port: port, - path: '/present-proof/records', - method: 'GET' - }); - return response.results; - } catch (error) { - console.error(error); - return []; - } + console.log(`Removing connection with ID: ${connectionId}`); + + try { + await axios.delete(`http://${hostname}:${port}/connections/${connectionId}`); + console.log(`Connection with ID ${connectionId} removed successfully`); + } catch (error) { + console.error(`Failed to remove connection with ID ${connectionId}:`, error); } +} - async sendProofRequest(proofRequest) { - try { - await httpAsync({ - hostname: hostname, - port: port, - path: '/present-proof/send-request', - method: 'POST', - headers: { - 'Content-Type': 'application/json' - } - }, proofRequest); - } catch (error) { - console.error(error); - } finally { - return; - } +async getProofRequests() { + try { + const response = await axios.get(`http://${hostname}:${port}/present-proof-2.0/records`); + return response.data.results; + } catch (error) { + console.error(error); + return []; } } +async sendProofRequest(proofRequest) { + try { + await axios.post(`http://${hostname}:${port}/present-proof-2.0/send-request`, proofRequest, { + headers: { + 'Content-Type': 'application/json' + } + }); + } catch (error) { + console.error(error); + } +} + +} + module.exports = new AgentService(); \ No newline at end of file diff --git a/AliceFaberAcmeDemo/controllers/acme-controller/views/accept_connection.hbs b/AliceFaberAcmeDemo/controllers/acme-controller/views/accept_connection.hbs index 4637d35..9aae956 100644 --- a/AliceFaberAcmeDemo/controllers/acme-controller/views/accept_connection.hbs +++ b/AliceFaberAcmeDemo/controllers/acme-controller/views/accept_connection.hbs @@ -84,7 +84,7 @@ } } - function decode(e) { + {{!-- function decode(e) { try { const value = e.target.value; if (!value) { @@ -96,6 +96,34 @@ throw new Error(); } + objectInput.value = JSON.stringify(JSON.parse(atob(invitationParam)), null, 4); + removeInvalidObjectErr(); + } catch (error) { + urlInput.parentNode.appendChild(invalidUrlEl); + } + } --}} + + function decode(e) { + try { + const value = e.target.value; + if (!value) { + return; + } + const url = new URL(value); + + // Try to get the 'c_i' parameter first + let invitationParam = url.searchParams.get('c_i'); + + // If 'c_i' is not found, try to get the 'oob' parameter + if (!invitationParam) { + invitationParam = url.searchParams.get('oob'); + } + + if (!invitationParam) { + throw new Error('No valid invitation parameter found in the URL.'); + } + + // Decode the Base64-encoded invitation parameter objectInput.value = JSON.stringify(JSON.parse(atob(invitationParam)), null, 4); removeInvalidObjectErr(); } catch (error) { diff --git a/AliceFaberAcmeDemo/controllers/acme-controller/views/partials/proof/proof_card.hbs b/AliceFaberAcmeDemo/controllers/acme-controller/views/partials/proof/proof_card.hbs index ed40a7c..cb80c10 100644 --- a/AliceFaberAcmeDemo/controllers/acme-controller/views/partials/proof/proof_card.hbs +++ b/AliceFaberAcmeDemo/controllers/acme-controller/views/partials/proof/proof_card.hbs @@ -2,9 +2,9 @@
- {{ proof.presentation_request.name }} - {{#if proof.presentation_request.version}} -  ({{ proof.presentation_request.version }}) + {{ proof.by_format.pres_request.indy.name }} + {{#if proof.by_format.pres_request.indy.version}} +  ({{ proof.by_format.pres_request.indy.version }}) {{/if}}
@@ -17,11 +17,46 @@ {{ proof.created_at }}
+
Presentation Exchange ID:  -

{{ proof.presentation_exchange_id }}

+

{{ proof.pres_ex_id }}

+ + {{#if proof.by_format.pres_request.indy.requested_attributes}} +
+ Requested Attributes: +
    + {{#each proof.by_format.pres_request.indy.requested_attributes}} +
  • {{this.name}}
  • + {{/each}} +
+
+ {{/if}} + + {{#if proof.by_format.pres.indy.requested_proof.revealed_attrs}} +
+ Revealed Attributes: +
    + {{#each proof.by_format.pres.indy.requested_proof.revealed_attrs}} +
  • {{@key}}: {{this.raw}}
  • + {{/each}} +
+
+ {{/if}} + + {{#if proof.by_format.pres.indy.requested_proof.self_attested_attrs}} +
+ Self-Attested Attributes: +
    + {{#each proof.by_format.pres.indy.requested_proof.self_attested_attrs}} +
  • {{@key}}: {{this}}
  • + {{/each}} +
+
+ {{/if}} + {{#eq proof.verified 'true'}}
@@ -30,9 +65,28 @@   Verified
+ {{else}} + {{#if (or (eq proof.state 'done') (eq proof.state 'abandoned'))}} +
+ + + +   + Rejected +
+ {{else}} +
+ + + +   + Processing +
+ {{/if}} {{/eq}}
+