Skip to content

Commit

Permalink
feat: improve delegation flow
Browse files Browse the repository at this point in the history
  • Loading branch information
bao-sotatek committed Apr 16, 2024
1 parent f1aeb0b commit bfa22ad
Show file tree
Hide file tree
Showing 14 changed files with 136 additions and 29 deletions.
16 changes: 16 additions & 0 deletions src/core/agent/agent.types.ts
Expand Up @@ -231,6 +231,20 @@ enum NotificationRoute {
MultiSigRot = "/multisig/rot",
}

enum OOBIScan {
Delegated,
Aid,
}

type ScanOOBIRes =
| {
oobiType: OOBIScan.Aid;
}
| {
oobiType: OOBIScan.Delegated;
delegatePrefix: string;
};

export {
Blockchain,
ConnectionStatus,
Expand All @@ -240,6 +254,7 @@ export {
AcdcKeriEventTypes,
NotificationRoute,
MultiSigRoute,
OOBIScan,
};

export type {
Expand All @@ -262,4 +277,5 @@ export type {
MultiSigExnMessage,
Aid,
IdentifierResult,
ScanOOBIRes,
};
6 changes: 6 additions & 0 deletions src/core/agent/records/identifierMetadataRecord.test.ts
Expand Up @@ -9,6 +9,9 @@ const mockData: IdentifierMetadataRecordProps = {
isArchived: true,
theme: 0,
signifyName: "Test",
delegated: {
delegatePrefix: "delegatePrefix",
},
};

describe("Identifier Record", () => {
Expand All @@ -27,6 +30,9 @@ describe("Identifier Record", () => {
expect(settingsRecord.getTags()).toMatchObject({
isArchived: mockData.isArchived,
});
expect(settingsRecord.delegated).toEqual({
delegatePrefix: "delegatePrefix",
});
});

test("should fallback to the current time if not supplied", async () => {
Expand Down
3 changes: 3 additions & 0 deletions src/core/agent/records/identifierMetadataRecord.ts
Expand Up @@ -11,6 +11,7 @@ interface IdentifierMetadataRecordProps {
theme: number;
signifyOpName?: string;
multisigManageAid?: string;
delegated?: Record<string, unknown>;
}

class IdentifierMetadataRecord
Expand All @@ -22,6 +23,7 @@ class IdentifierMetadataRecord
isDeleted?: boolean;
isPending?: boolean;
signifyOpName?: string | undefined;
delegated?: Record<string, unknown>;
signifyName!: string;
theme!: number;
multisigManageAid?: string | undefined;
Expand All @@ -43,6 +45,7 @@ class IdentifierMetadataRecord
this.multisigManageAid = props.multisigManageAid;
this.createdAt = props.createdAt ?? new Date();
this.theme = props.theme;
this.delegated = props.delegated;
}
}

Expand Down
64 changes: 51 additions & 13 deletions src/core/agent/services/connectionService.test.ts
Expand Up @@ -93,11 +93,19 @@ const signifyClient = jest.mocked({

const session = {};

const identifierStorage = jest.mocked({
getIdentifierMetadata: jest.fn(),
getAllIdentifierMetadata: jest.fn(),
getKeriIdentifiersMetadata: jest.fn(),
updateIdentifierMetadata: jest.fn(),
createIdentifierMetadataRecord: jest.fn(),
});

const agentServicesProps = {
basicStorage: basicStorage as any,
signifyClient: signifyClient as any,
eventService: new EventService(),
identifierStorage: new IdentifierStorage(session as any),
identifierStorage: identifierStorage as any,
credentialStorage: new CredentialStorage(session as any),
};

Expand Down Expand Up @@ -225,19 +233,58 @@ describe("Connection service of agent", () => {
});

test("can get a KERI OOBI with an alias (URL encoded)", async () => {
const signifyName = "signifyName";
identifierStorage.getIdentifierMetadata.mockResolvedValue({
signifyName,
});
signifyClient.oobis().get = jest.fn().mockImplementation((name: string) => {
return `${oobiPrefix}${name}`;
});
const signifyName = "keriuuid";
const KeriOobi = await connectionService.getKeriOobi(
const keriOobi = await connectionService.getKeriOobi(
signifyName,
"alias with spaces"
);
expect(KeriOobi).toEqual(
expect(keriOobi).toEqual(
`${oobiPrefix}${signifyName}?name=alias%20with%20spaces`
);
});

test("can get KERI OOBI with delegated identifier", async () => {
const identifier = "keriuuid";
const signifyName = "signifyName";
identifierStorage.getIdentifierMetadata.mockResolvedValue({
signifyName,
delegated: {
delegateePrefix: "uuid",
},
});
signifyClient.oobis().get = jest.fn().mockImplementation((name: string) => {
return `${oobiPrefix}${name}`;
});
const KeriOobi = await connectionService.getKeriOobi(identifier);
expect(KeriOobi).toEqual(oobiPrefix + signifyName + "?delegated=true");
});

test("can get a KERI OOBI with an alias and delegated (URL encoded)", async () => {
const signifyName = "signifyName";
identifierStorage.getIdentifierMetadata.mockResolvedValue({
signifyName,
delegated: {
delegateePrefix: "uuid",
},
});
signifyClient.oobis().get = jest.fn().mockImplementation((name: string) => {
return `${oobiPrefix}${name}`;
});
const keriOobi = await connectionService.getKeriOobi(
signifyName,
"alias with spaces"
);
expect(keriOobi).toEqual(
`${oobiPrefix}${signifyName}?name=alias%20with%20spaces&delegated=true`
);
});

test("can get connection keri (short detail view) by id", async () => {
basicStorage.findById = jest.fn().mockResolvedValue({
id: keriContacts[0].id,
Expand All @@ -259,15 +306,6 @@ describe("Connection service of agent", () => {
expect(basicStorage.findById).toBeCalledWith(keriContacts[0].id);
});

test("can get KERI OOBI", async () => {
signifyClient.oobis().get = jest.fn().mockImplementation((name: string) => {
return `${oobiPrefix}${name}`;
});
const signifyName = "keriuuid";
const KeriOobi = await connectionService.getKeriOobi(signifyName);
expect(KeriOobi).toEqual(oobiPrefix + signifyName);
});

test("Should call createIdentifierMetadataRecord when there are un-synced KERI contacts", async () => {
contactListMock.mockReturnValue([
{
Expand Down
38 changes: 33 additions & 5 deletions src/core/agent/services/connectionService.ts
Expand Up @@ -10,6 +10,8 @@ import {
ConnectionShortDetails,
ConnectionStatus,
KeriContact,
OOBIScan,
ScanOOBIRes,
} from "../agent.types";
import { AgentService } from "./agentService";
import { Agent } from "../agent";
Expand Down Expand Up @@ -45,7 +47,7 @@ class ConnectionService extends AgentService {
);
}

async receiveInvitationFromUrl(url: string): Promise<void> {
async receiveInvitationFromUrl(url: string): Promise<ScanOOBIRes> {
this.eventService.emit<ConnectionKeriStateChangedEvent>({
type: ConnectionKeriEventTypes.ConnectionKeriStateChanged,
payload: {
Expand Down Expand Up @@ -105,13 +107,19 @@ class ConnectionService extends AgentService {
}
}

return this.eventService.emit<ConnectionKeriStateChangedEvent>({
await this.eventService.emit<ConnectionKeriStateChangedEvent>({
type: ConnectionKeriEventTypes.ConnectionKeriStateChanged,
payload: {
connectionId: operation.response.i,
status: ConnectionStatus.CONFIRMED,
},
});
return new URL(url).searchParams.get("delegated")
? {
oobiType: OOBIScan.Delegated,
delegatePrefix: "TODO", // TODO: must recheck when confirmed with Fergal
}
: { oobiType: OOBIScan.Aid };
}

async getConnections(): Promise<ConnectionShortDetails[]> {
Expand Down Expand Up @@ -195,10 +203,30 @@ class ConnectionService extends AgentService {
return this.basicStorage.deleteById(connectionNoteId);
}

async getKeriOobi(signifyName: string, alias?: string): Promise<string> {
const result = await this.signifyClient.oobis().get(signifyName);
async getKeriOobi(identifier: string, alias?: string): Promise<string> {
const metadata = await this.identifierStorage.getIdentifierMetadata(
identifier
);
const result = await this.signifyClient.oobis().get(metadata.signifyName);
const oobi = result.oobis[0];
return alias ? `${oobi}?name=${encodeURIComponent(alias)}` : oobi;
const encodedParams: Record<string, string> = {};
if (alias) {
encodedParams["name"] = alias;
}
if (metadata.delegated) {
encodedParams["delegated"] = "true";
}
return Object.keys(encodedParams).length > 0
? `${oobi}?${Object.keys(encodedParams)
.map(function (key) {
return (
encodeURIComponent(key) +
"=" +
encodeURIComponent(encodedParams[key])
);
})
.join("&")}`
: oobi;
}

private async createConnectionKeriMetadata(
Expand Down
1 change: 1 addition & 0 deletions src/core/agent/services/identifier.types.ts
Expand Up @@ -7,6 +7,7 @@ interface IdentifierShortDetails {
signifyName: string;
theme: number;
isPending: boolean;
delegated?: Record<string, unknown>;
}

interface IdentifierFullDetails extends IdentifierShortDetails {
Expand Down
8 changes: 6 additions & 2 deletions src/core/agent/services/identifierService.test.ts
@@ -1,8 +1,6 @@
import { IdentifierMetadataRecord } from "../records/identifierMetadataRecord";
import { Agent } from "../agent";
import { EventService } from "./eventService";
import { IdentifierService } from "./identifierService";
import { CredentialStorage } from "../records/credentialStorage";

const basicStorage = jest.mocked({
open: jest.fn(),
Expand Down Expand Up @@ -135,6 +133,9 @@ const keriMetadataRecordProps = {
signifyName: "uuid-here",
createdAt: now,
theme: 0,
delegated: {
delegatePrefix: "delegatePrefix",
},
};
const archivedMetadataRecord = new IdentifierMetadataRecord({
...keriMetadataRecordProps,
Expand Down Expand Up @@ -177,6 +178,9 @@ describe("Single sig service of agent", () => {
createdAtUTC: nowISO,
theme: 0,
isPending: false,
delegated: {
delegatePrefix: "delegatePrefix",
},
},
]);
});
Expand Down
1 change: 1 addition & 0 deletions src/core/agent/services/identifierService.ts
Expand Up @@ -52,6 +52,7 @@ class IdentifierService extends AgentService {
createdAtUTC: metadata.createdAt.toISOString(),
theme: metadata.theme,
isPending: metadata.isPending ?? false,
delegated: metadata.delegated,
});
}
return identifiers;
Expand Down
11 changes: 10 additions & 1 deletion src/core/agent/services/multiSigService.test.ts
Expand Up @@ -428,7 +428,16 @@ describe("Multisig sig service of agent", () => {
signifyName: expect.any(String),
});
expect(identifiersCreateMock).toBeCalled();
expect(identifierStorage.createIdentifierMetadataRecord).toBeCalledTimes(1);
expect(identifierStorage.createIdentifierMetadataRecord).toBeCalledWith({
delegated: {
delegatePrefix: "delegationPrefix",
},
displayName: "newDisplayName",
id: "i",
isPending: true,
signifyName: expect.any(String),
theme: 0,
});
});

test("should call the interactDelegation method of the signify module with the given arguments", async () => {
Expand Down
1 change: 1 addition & 0 deletions src/core/agent/services/multiSigService.ts
Expand Up @@ -441,6 +441,7 @@ class MultiSigService extends AgentService {
...metadata,
signifyName: signifyName,
isPending: true,
delegated: { delegatePrefix: delegatorPrefix },
});
return { identifier, signifyName };
}
Expand Down
4 changes: 2 additions & 2 deletions src/ui/components/ShareIdentifier/ShareIdentifier.test.tsx
Expand Up @@ -14,7 +14,7 @@ const setIsOpen = jest.fn();
const props: ShareIdentifierProps = {
isOpen: true,
setIsOpen,
signifyName: identifierFix[0].signifyName,
identifier: identifierFix[0].id,
};
jest.mock("../../../core/agent/agent", () => ({
Agent: {
Expand All @@ -39,7 +39,7 @@ describe("Share Indentifier (OOBI)", () => {
<ShareIdentifier
isOpen={props.isOpen}
setIsOpen={props.setIsOpen}
signifyName={props.signifyName}
identifier={props.identifier}
/>
</Provider>
);
Expand Down
8 changes: 4 additions & 4 deletions src/ui/components/ShareIdentifier/ShareIdentifier.tsx
Expand Up @@ -17,7 +17,7 @@ import { ResponsiveModal } from "../layout/ResponsiveModal";
const ShareIdentifier = ({
isOpen,
setIsOpen,
signifyName,
identifier,
}: ShareIdentifierProps) => {
const componentId = "share-identifier-modal";
const dispatch = useAppDispatch();
Expand All @@ -26,10 +26,10 @@ const ShareIdentifier = ({
const [oobi, setOobi] = useState("");

useEffect(() => {
if (signifyName) {
if (identifier) {
const fetchOobi = async () => {
const oobiValue = await Agent.agent.connections.getKeriOobi(
`${signifyName}`,
`${identifier}`,
userName
);
if (oobiValue) {
Expand All @@ -38,7 +38,7 @@ const ShareIdentifier = ({
};
fetchOobi();
}
}, [signifyName, userName]);
}, [identifier, userName]);

return (
<ResponsiveModal
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/ShareIdentifier/ShareIdentifier.types.ts
@@ -1,7 +1,7 @@
interface ShareIdentifierProps {
isOpen: boolean;
setIsOpen: (value: boolean) => void;
signifyName?: string;
identifier?: string;
}

export type { ShareIdentifierProps };
2 changes: 1 addition & 1 deletion src/ui/pages/IdentifierDetails/IdentifierDetails.tsx
Expand Up @@ -287,7 +287,7 @@ const IdentifierDetails = () => {
<ShareIdentifier
isOpen={shareIsOpen}
setIsOpen={setShareIsOpen}
signifyName={cardData.signifyName}
identifier={cardData.id}
/>
<IdentifierOptions
optionsIsOpen={identifierOptionsIsOpen}
Expand Down

0 comments on commit bfa22ad

Please sign in to comment.