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

feat(core): linking of ipex flows to contacts #422

Merged
merged 21 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6ef8a97
feat: rework some API in cred server
Sotatek-BaoHoanga Mar 4, 2024
ce55883
feat: add apply schema API
Sotatek-BaoHoanga Mar 5, 2024
e9e38eb
refactor: function and var name and dependency link
Sotatek-BaoHoanga Mar 5, 2024
814e78b
feat: add full ipex flow for idw and cred server
Sotatek-BaoHoanga Mar 11, 2024
c8ce453
refactor: testing and some missing logic
Sotatek-BaoHoanga Mar 11, 2024
4f0156e
feature: save linked IPEX messages for connections
Sotatek-HocNguyena Apr 16, 2024
6f29933
Merge branch 'develop' of github.com:cardano-foundation/cf-identity-w…
Sotatek-HocNguyena May 31, 2024
c035d41
refactor: change the file contruction
Sotatek-HocNguyena Jun 3, 2024
f66f763
Merge branch 'develop' of github.com:cardano-foundation/cf-identity-w…
Sotatek-HocNguyena Jun 20, 2024
5cd3036
Merge branch 'develop' of github.com:cardano-foundation/cf-identity-w…
Sotatek-TungNguyen2a Jul 26, 2024
44c7d0f
Merge branch 'develop' of github.com:cardano-foundation/cf-identity-w…
Sotatek-TungNguyen2a Jul 26, 2024
cc73acd
Merge branch 'develop' of github.com:cardano-foundation/cf-identity-w…
Sotatek-TungNguyen2a Jul 27, 2024
0caf0a4
feat: linking of IPEX flows to contacts
Sotatek-TungNguyen2a Jul 29, 2024
9bc6440
fix: update unit tests
Sotatek-TungNguyen2a Jul 29, 2024
5f71099
update: add isUpdate to linkedIpexMessage record
Sotatek-TungNguyen2a Jul 29, 2024
092ba49
update: add credentialType to linkedIpexMessageRecord
Sotatek-TungNguyen2a Jul 29, 2024
a6750ea
update: get linkedIpexRecord from exchange message
Sotatek-TungNguyen2a Jul 30, 2024
29115e5
update: get linkedIpexRecord id from exchange message SAID
Sotatek-TungNguyen2a Jul 30, 2024
78b490e
feat: resolve schema if we fail to get the schema
Sotatek-TungNguyen2a Jul 31, 2024
39739e0
Merge branch 'develop' of github.com:cardano-foundation/cf-identity-w…
Sotatek-TungNguyen2a Jul 31, 2024
ba8ddbc
feat: handle for apply and agree routes
Sotatek-TungNguyen2a Jul 31, 2024
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
229 changes: 126 additions & 103 deletions services/credential-server/package-lock.json
iFergal marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/core/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ class Agent {
this.notificationStorage,
this.identifierStorage,
this.operationPendingStorage,
this.connectionStorage
this.connectionStorage,
this.ipexMessageStorage
);
}
return this.signifyNotificationService;
Expand Down
17 changes: 17 additions & 0 deletions src/core/agent/records/ipexMessageStorage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,23 @@ describe("ipexMessage Storage", () => {
expect(storageService.save).toBeCalledWith(ipexMessageRecordA);
});

test("Should find ipexMessage record by id", async () => {
storageService.findById.mockResolvedValue(ipexMessageRecordB);
const result = await ipexMessageStorage.getIpexMessageMetadata(
ipexMessageRecordB.connectionId
);
expect(result).toEqual(ipexMessageRecordB);
});

test("Should throw error if there is no matching record", async () => {
storageService.findById.mockResolvedValue(null);
await expect(
ipexMessageStorage.getIpexMessageMetadata("not-found-id")
).rejects.toThrowError(
IpexMessageStorage.IPEX_MESSAGE_METADATA_RECORD_MISSING
);
});

test("Should find ipexMessage record by connectionId", async () => {
storageService.findAllByQuery.mockResolvedValue([
ipexMessageRecordA,
Expand Down
8 changes: 8 additions & 0 deletions src/core/agent/records/ipexMessageStorage.ts
iFergal marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ class IpexMessageStorage {
await this.storageService.save(record);
}

async getIpexMessageMetadata(id: string): Promise<IpexMessageRecord> {
const metadata = await this.storageService.findById(id, IpexMessageRecord);
if (!metadata) {
throw new Error(IpexMessageStorage.IPEX_MESSAGE_METADATA_RECORD_MISSING);
}
return metadata;
}

async getIpexMessageMetadataByConnectionId(
connectionId: string
): Promise<IpexMessageRecord[]> {
Expand Down
13 changes: 7 additions & 6 deletions src/core/agent/services/connectionService.test.ts
iFergal marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -628,9 +628,10 @@ describe("Connection service of agent", () => {
});

test("Can get connection History by id", async () => {
jest.restoreAllMocks();
const connectionId = "connectionId";
const date1 = new Date("Sat Jul 27 2024 15:02:30 GMT+0700");
const date2 = new Date("Sat Jul 27 2024 15:12:04 GMT+0700");
const date2 = new Date("Sat Jul 27 2024 15:45:04 GMT+0700");
const date3 = new Date("Sat Jul 27 2024 15:30:34 GMT+0700");
getIpexMessageMetadataByConnectionIdMock.mockResolvedValue([
{
Expand All @@ -640,7 +641,7 @@ describe("Connection service of agent", () => {
r: "/ipex/grant",
e: {
acdc: {
d: "EN_tsGwSUI63SYoSiiN8qsysUT8bnka9gZEka8PG_oVQ",
d: "EN_tsGwSUI63SYoSiiN8qsysUT8bnka9gZEka8PG_oVK",
},
},
},
Expand Down Expand Up @@ -690,13 +691,13 @@ describe("Connection service of agent", () => {
);
expect(histories).toEqual([
{
type: ConnectionHistoryType.CREDENTIAL_UPDATE,
timestamp: date3.toISOString(),
type: ConnectionHistoryType.CREDENTIAL_REQUEST_PRESENT,
timestamp: date2.toISOString(),
credentialType: "IIW 2024 Demo Day Attendee",
},
{
type: ConnectionHistoryType.CREDENTIAL_REQUEST_PRESENT,
timestamp: date2.toISOString(),
type: ConnectionHistoryType.CREDENTIAL_UPDATE,
timestamp: date3.toISOString(),
credentialType: "IIW 2024 Demo Day Attendee",
},
{
Expand Down
5 changes: 2 additions & 3 deletions src/core/agent/services/connectionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,8 @@ class ConnectionService extends AgentService {
await this.ipexMessageStorage.getIpexMessageMetadataByConnectionId(
connectionId
);

const requestMessages = linkedIpexMessages
.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
.map((messageRecord) => {
const { historyType, createdAt, credentialType } = messageRecord;
return {
Expand All @@ -319,7 +318,7 @@ class ConnectionService extends AgentService {
credentialType,
};
});
return requestMessages.reverse();
return requestMessages;
}

// @TODO - foconnor: Contacts that are smid/rmids for multisigs will be synced too.
Expand Down
88 changes: 87 additions & 1 deletion src/core/agent/services/ipexCommunicationService.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { EventService } from "./eventService";
import { IpexCommunicationService } from "./ipexCommunicationService";
import { CredentialStatus } from "./credentialService.types";
import { Agent } from "../agent";
import { IdentifierStorage } from "../records";
import { ConfigurationService } from "../../configuration";
import { OperationPendingRecordType } from "../records/operationPendingRecord.type";
import { ConnectionHistoryType } from "./connection.types";

const notificationStorage = jest.mocked({
open: jest.fn(),
Expand Down Expand Up @@ -208,6 +208,49 @@ const ipexCommunicationService = new IpexCommunicationService(
operationPendingStorage as any
);

const grantIpexMessageMock = {
exn: {
v: "KERI10JSON000516_",
t: "exn",
d: "EJ1jbI8vTFCEloTfSsZkBpV0bUJnhGVyak5q-5IFIglL",
i: "EC9bQGHShmp2Juayqp0C5XcheBiHyc1p54pZ_Op-B95x",
p: "",
dt: "2024-07-30T04:19:55.801000+00:00",
r: "/ipex/grant",
q: {},
a: {
m: "",
i: "EE-gjeEni5eCdpFlBtG7s4wkv7LJ0JmWplCS4DNQwW2G",
},
e: {
acdc: {
d: "EEqfWy-6jx_FG0RNuNxZBh_jq6Lq1OPuvX5m3v1Bzxdn",
i: "EC9bQGHShmp2Juayqp0C5XcheBiHyc1p54pZ_Op-B95x",
s: "EBIFDhtSE0cM4nbTnaMqiV1vUIlcnbsqBMeVMmeGmXOu",
a: {
d: "ELHCh_X2aw7C-aYesOM4La23a5lsoNuJDuCsJuxwO2nq",
i: "EE-gjeEni5eCdpFlBtG7s4wkv7LJ0JmWplCS4DNQwW2G",
dt: "2024-07-30T04:19:55.348000+00:00",
attendeeName: "ccc",
},
},
iss: {
t: "iss",
d: "EHStOgwJku_Ln-YN2ohgWUH-CI07SyJnFppSbF8kG4PO",
i: "EEqfWy-6jx_FG0RNuNxZBh_jq6Lq1OPuvX5m3v1Bzxdn",
s: "0",
dt: "2024-07-30T04:19:55.348000+00:00",
},
d: "EKBPPnWxYw2I5CtQSyhyn5VUdSTJ61qF_-h-NwmFRkIF",
},
},
pathed: {
acdc: "-IABEEqfWy-6jx_FG0RNuNxZBh_jq6Lq1OPuvX5m3v1Bzxdn0AAAAAAAAAAAAAAAAAAAAAAAEHStOgwJku_Ln-YN2ohgWUH-CI07SyJnFppSbF8kG4PO",
iss: "-VAS-GAB0AAAAAAAAAAAAAAAAAAAAAAAEEO0xKzC8FOAXV-JgFZGgb0aIT2A3cPXPt9_0l_qcGM9",
anc: "-AABAACBlQqbI_qNpKYkzIog6tauSgt0XufBvGtrumfbnhSInFjSwnaIqZi353QT-c1W_gE9KIz3rgX5QNNWLcqA7bcM",
},
};

describe("Ipex communication service of agent", () => {
beforeAll(async () => {
await new ConfigurationService().start();
Expand Down Expand Up @@ -569,6 +612,49 @@ describe("Ipex communication service of agent", () => {
});
});

test("can create linked ipex message record", async () => {
Agent.agent.getKeriaOnlineStatus = jest.fn().mockReturnValueOnce(true);
const schemaMock = { title: "title" };
schemaGetMock.mockResolvedValueOnce(schemaMock);
await ipexCommunicationService.createLinkedIpexMessageRecord(
grantIpexMessageMock,
ConnectionHistoryType.CREDENTIAL_ISSUANCE
);
expect(ipexMessageRecordStorage.createIpexMessageRecord).toBeCalledWith({
id: grantIpexMessageMock.exn.d,
credentialType: schemaMock.title,
content: grantIpexMessageMock,
connectionId: grantIpexMessageMock.exn.i,
historyType: ConnectionHistoryType.CREDENTIAL_ISSUANCE,
});

schemaGetMock.mockRejectedValueOnce(
new Error("request - 404 - SignifyClient message")
);
await ipexCommunicationService.createLinkedIpexMessageRecord(
grantIpexMessageMock,
ConnectionHistoryType.CREDENTIAL_ISSUANCE
);
expect(ipexMessageRecordStorage.createIpexMessageRecord).toBeCalledWith({
id: grantIpexMessageMock.exn.d,
credentialType: undefined,
content: grantIpexMessageMock,
connectionId: grantIpexMessageMock.exn.i,
historyType: ConnectionHistoryType.CREDENTIAL_ISSUANCE,
});
});

test("Should throw error if schemas.get has an unexpected error", async () => {
Agent.agent.getKeriaOnlineStatus = jest.fn().mockReturnValueOnce(true);
schemaGetMock.mockRejectedValueOnce(new Error("Unknown error"));
await expect(
ipexCommunicationService.createLinkedIpexMessageRecord(
grantIpexMessageMock,
ConnectionHistoryType.CREDENTIAL_REQUEST_PRESENT
)
).rejects.toThrowError(new Error("Unknown error"));
});

test("cannot get matching credential for apply if cannot get the schema", async () => {
Agent.agent.getKeriaOnlineStatus = jest.fn().mockReturnValueOnce(true);
const notiId = "notiId";
Expand Down
37 changes: 32 additions & 5 deletions src/core/agent/services/ipexCommunicationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,16 +344,43 @@ class IpexCommunicationService extends AgentService {
}

async createLinkedIpexMessageRecord(
iFergal marked this conversation as resolved.
Show resolved Hide resolved
iFergal marked this conversation as resolved.
Show resolved Hide resolved
credentialType: string,
connectionId: string,
message: IpexMessage,
historyType: ConnectionHistoryType
): Promise<void> {
const schemaSaid = message.exn.e.acdc.s;
const allSchemaSaids = Object.keys(message.exn.e.acdc?.e || {}).map(
// Chained schemas
(key) => message.exn.e.acdc.e?.[key]?.s
);
iFergal marked this conversation as resolved.
Show resolved Hide resolved
allSchemaSaids.push(schemaSaid);
await Promise.all(
allSchemaSaids.map(
async (schemaSaid) =>
await Agent.agent.connections.resolveOobi(
`${ConfigurationService.env.keri.credentials.testServer.urlInt}/oobi/${schemaSaid}`,
true
)
)
);

const schema = await this.props.signifyClient
.schemas()
.get(schemaSaid)
.catch((error) => {
const errorStack = (error as Error).stack as string;
const status = errorStack.split("-")[1];
if (/404/gi.test(status) && /SignifyClient/gi.test(errorStack)) {
return undefined;
iFergal marked this conversation as resolved.
Show resolved Hide resolved
} else {
throw error;
}
});

await this.ipexMessageStorage.createIpexMessageRecord({
iFergal marked this conversation as resolved.
Show resolved Hide resolved
id: uuidv4(),
credentialType,
id: message.exn.d,
credentialType: schema?.title,
content: message,
connectionId,
connectionId: message.exn.i,
historyType,
});
}
Expand Down
Loading
Loading