Skip to content

Commit

Permalink
Merge pull request #12 from Indicio-tech/feature/boarding-pass-issuan…
Browse files Browse the repository at this point in the history
…ce-requests

feat: credentials and issuance requests APIs
  • Loading branch information
mikekebert authored Sep 21, 2023
2 parents 829ce39 + 256a913 commit 6aeae93
Show file tree
Hide file tree
Showing 10 changed files with 721 additions and 142 deletions.
7 changes: 6 additions & 1 deletion adminAPI/credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ const autoIssueCred = async (
return response
} catch (error) {
console.error('Credential Issuance Error')
throw error
return {
error: {
message: error.response.statusText,
code: error.response.status,
},
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions agentLogic/connections.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ const getConnections = async () => {
}
}

const getConnectionsByContactId = async (contact_id) => {
try {
const connections = await Connections.readConnectionsByContactId(contact_id)

return connections
} catch (error) {
console.log('Error getting connections: ', error)
}
}

const getAllPendingConnections = async (params = {}) => {
try {
const connections = await Connections.readPendingConnections(params)
Expand Down Expand Up @@ -146,6 +156,7 @@ const handleConnectionReuse = async (message) => {
module.exports = {
getConnection,
getConnections,
getConnectionsByContactId,
getAllPendingConnections,
updateOrCreateConnection,
updateExistingConnection,
Expand Down
44 changes: 40 additions & 4 deletions agentLogic/contacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ const AdminAPI = require('../adminAPI')
const Websockets = require('../websockets.js')

const Connections = require('../agentLogic/connections')
const IssuanceRequests = require('../agentLogic/issuanceRequests')

let Contacts = require('../orm/contacts.js')
let ContactsCompiled = require('../orm/contactsCompiled.js')
const Contacts = require('../orm/contacts.js')
const ContactsCompiled = require('../orm/contactsCompiled.js')

const {v4: uuid} = require('uuid')

Expand Down Expand Up @@ -148,8 +149,19 @@ const adminMessage = async (connectionMessage) => {
{}, // meta_data
)
} else {
console.log('Reusing existing contact id')
// (mikekebert) If we have a contact_id already, we should use it
console.log('Contact id is provided')
const existingContact = await Contacts.readBaseContact(
connection.contact_id,
)

if (!existingContact) {
await Contacts.createContact(
connection.contact_id,
connectionMessage.their_label, // label
{}, // meta_data
)
}

contact_id = connection.contact_id
console.log('Provided contact_id: ', contact_id)
}
Expand All @@ -174,6 +186,28 @@ const adminMessage = async (connectionMessage) => {
connectionMessage.error_msg,
contact_id,
)

const invitation = await Invitations.getInvitationByConnectionId(
connectionMessage.connection_id,
)

if (invitation) {
//TODO: Handle verification requests with both contact_id and invitation_id. Current implementation is triggered in agentWebhook.js
// console.log('')
// console.log(
// '_____________Verification flow triggered - process pending requests_____________',
// )
// await Verifications.startRule(contact_id, invitation.invitation_id)

console.log('')
console.log(
'_____________Credential flow triggered - process pending requests_____________',
)
await IssuanceRequests.processRequests(
contact_id,
invitation.invitation_id,
)
}
}
} catch (error) {
console.error('Error Storing Connection Message')
Expand All @@ -188,3 +222,5 @@ module.exports = {
getAll,
getContactByConnection,
}

const Invitations = require('./invitations')
8 changes: 7 additions & 1 deletion agentLogic/credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,15 @@ const autoIssueCredential = async (
false,
false,
)

if (response.error) {
throw response.error
} else {
return response
}
} catch (error) {
console.error('Error Issuing Credential')
throw error
return {error}
}
}

Expand Down
16 changes: 16 additions & 0 deletions agentLogic/invitations.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,21 @@ const getInvitation = async (invitation_id) => {
}
}

const getInvitationsByContactId = async (contact_id) => {
try {
const invitationRecords = await Invitations.readInvitationsByContactId(
contact_id,
)

console.log('Invitation Record:', invitationRecords)

return invitationRecords
} catch (error) {
console.error('Error Fetching Invitation Record by contact id')
throw error
}
}

const getInvitationByOOBId = async (oob_id) => {
try {
const invitationRecord = await Invitations.readInvitationByOOBId(oob_id)
Expand Down Expand Up @@ -405,6 +420,7 @@ module.exports = {
deleteInvitation,
getAll,
getInvitation,
getInvitationsByContactId,
getInvitationByOOBId,
getInvitationByConnectionId,
updateOOBInvRecord,
Expand Down
221 changes: 221 additions & 0 deletions agentLogic/issuanceRequests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
require('dotenv').config()

const IssuanceRequests = require('../orm/issuanceRequests')
const Connections = require('./connections')
const Invitations = require('./invitations')
const Credentials = require('./credentials')

const addRequest = async function (
contactId,
invitationId,
schemaId,
attributes,
) {
try {
if ((invitationId && contactId) || (invitationId && !contactId)) {
const invitation = await Invitations.getInvitation(invitationId)
if (!invitation) {
throw `No invitation was found by the invitation id ${invitationId}`
} else {
if (invitationId && contactId && invitation.contact_id !== contactId) {
throw `The contact id ${contactId} doesn't match the contact id ${invitation.contact_id} on the found invitation`
}
}
} else {
const invitations = await Invitations.getInvitationsByContactId(contactId)
if (!invitations.length) {
throw `No invitations were found by the contact id ${contactId}`
}
}

const issuanceRequest = await IssuanceRequests.addRequest(
contactId,
invitationId,
schemaId,
attributes,
)
console.log('Issuance request added: ', issuanceRequest)
return issuanceRequest
} catch (error) {
console.error('Error recording issuance request')
return {error}
}
}

const handleCredentialIssued = async (connections, issuanceRequest) => {
try {
if (connections.length) {
for (let i = 0; i < connections.length; i++) {
// (eldersonar) Oneliner to delay execution of the fuction
await new Promise((resolve) => setTimeout(resolve, 1000))

const schemaParts = issuanceRequest.schema_id.split(':')

const credentialIssued = await Credentials.autoIssueCredential(
connections[i].connection_id,
undefined,
undefined,
issuanceRequest.schema_id,
schemaParts[3],
schemaParts[2],
schemaParts[0],
'',
issuanceRequest.attributes,
)

if (credentialIssued.error) {
throw credentialIssued.error
} else {
if (credentialIssued) {
await IssuanceRequests.updateRequestStatus(
issuanceRequest.request_id,
'pending',
)
}
}
}
} else {
return {
warning: `Waiting for connection(s) to become active for contact id ${issuanceRequest.contact_id}`,
}
}
return true
} catch (error) {
return {error}
}
}

const processRequests = async function (contactId, invitationId) {
try {
// (eldersonar) Get all credential issuance requests based on 3 main cases
// 1. contact id provided, invitation id not provided
// 2. contact id not provided, invitation id provided
// 3. both contact id and invitation id provided
const issuanceRequests = await IssuanceRequests.readRequestsByIdentifiers(
contactId,
invitationId,
'new', // (eldersonar) I advise to keep "new" flag and change the "pending" flag to "complete". Or also changeing "new" flag to "pending"
)

if (issuanceRequests.length) {
// (eldersonar) Process pending requests based on the intent encoded into the request itself
for (let i = 0; i < issuanceRequests.length; i++) {
// Send credential issuance to a connection matching invitation where contact id is also on that invitation
if (
(issuanceRequests[i].contact_id &&
issuanceRequests[i].invitation_id) ||
(!issuanceRequests[i].contact_id && issuanceRequests[i].invitation_id)
) {
if (
issuanceRequests[i].contact_id &&
issuanceRequests[i].invitation_id
) {
console.log(
'============FLOW - contact id AND invitation id==============',
)
}

if (
!issuanceRequests[i].contact_id &&
issuanceRequests[i].invitation_id
) {
console.log(
'============FLOW - NO contact id BUT invitation id==============',
)
}
const invitation = await Invitations.getInvitation(invitationId)
if (invitation) {
// Check if the provided contact_id matches the one on the invitation
if (
issuanceRequests[i].contact_id &&
issuanceRequests[i].invitation_id
) {
if (invitation.contact_id !== issuanceRequests[i].contact_id) {
// (eldersonar) I don't think we'll ever hit this check. As of today (12/20/22) if the user
// provides wrong contact id it will break on the credential record creation
console.log(
"There was an error. Contact id doesn't match the invitation record",
)
}
}
const connection = await Connections.getConnection(
invitation.connection_id,
)

if (
connection &&
(connection.state === 'active' ||
connection.state === 'completed')
) {
let connections = [connection]

const credentialIssued = await handleCredentialIssued(
connections,
issuanceRequests[i],
)

if (credentialIssued.error) {
throw credentialIssued.error
}
} else {
return {
warning: `Waiting for connection to become active for invitation id ${issuanceRequests[i].invitation_id}`,
}
}
} else {
throw `No invitation was found by invitation id ${issuanceRequests[i].invitation_id}`
}
}

// Send credential issuance to a all connections found by contact id
else if (
issuanceRequests[i].contact_id &&
!issuanceRequests[i].invitation_id
) {
console.log(
'============FLOW - contact id AND NO invitation id==============',
)
let connections = []
const rawConnections = await Connections.getConnectionsByContactId(
issuanceRequests[i].contact_id,
)
if (rawConnections) {
connections = rawConnections.filter((connection) => {
return (
connection.state === 'active' ||
connection.state === 'completed'
)
})
}

const credentialIssued = await handleCredentialIssued(
connections,
issuanceRequests[i],
)

if (credentialIssued.error) {
throw credentialIssued.error
} else if (credentialIssued.warning) {
return {warning: credentialIssued.warning}
}
} else {
// (eldersonar) We should never hit this condition as far as I can tell. Needs to be tested more
throw `Something went terribly wrong... No credential can be issued by either contact id ${contactId} or invitation id ${invitationId}`
}
}
} else {
throw 'No records were found by provided identifiers. The end of credential issuance flow...'
}
} catch (error) {
// (eldersonar) Make sure to return proper error based on where it was generated.
// The first case is checking for the adminAPI error, the second one will habdle custom errors
return error.message
? {error: error.message, code: error.code}
: {error: error.reason}
}
}

module.exports = {
addRequest,
processRequests,
}
Loading

0 comments on commit 6aeae93

Please sign in to comment.