Skip to content

Commit

Permalink
init connect dwn integration
Browse files Browse the repository at this point in the history
  • Loading branch information
shamilovtim committed Aug 21, 2024
1 parent 2d0b423 commit 217aa2e
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 46 deletions.
13 changes: 9 additions & 4 deletions examples/wallet-connect.html
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,15 @@ <h1>Success</h1>
method: "Query",
protocol: "http://profile-protocol.xyz",
},
{
interface: "Records",
method: "Read",
protocol: "http://profile-protocol.xyz",
},
];

try {
const { delegateDid } = await Web5.connect({
const { delegateDid, delegateGrants } = await Web5.connect({
walletConnectOptions: {
walletUri: "web5://connect",
connectServerUrl: "http://localhost:3000/connect",
Expand All @@ -162,7 +167,7 @@ <h1>Success</h1>
},
});

goToEndScreen(delegateDid);
goToEndScreen(delegateDid, delegateGrants);
} catch (e) {
document.getElementById(
"errorMessage"
Expand Down Expand Up @@ -205,10 +210,10 @@ <h1>Success</h1>
document.getElementById("pinScreen").style.display = "block";
}

function goToEndScreen(delegateDid) {
function goToEndScreen(delegateDid, delegateGrants) {
document.getElementById("didInformation").innerText = `${JSON.stringify(
delegateDid
)}`;
)} \n \n \n \n ${JSON.stringify(delegateGrants)}`;

document.getElementById("pinScreen").style.display = "none";
document.getElementById("endScreen").style.display = "block";
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"build": "pnpm --recursive --stream build",
"test:node": "pnpm --recursive test:node",
"audit-ci": "audit-ci --config ./audit-ci.json",
"wallet:connect:example": "npx http-server & HTTP_SERVER_PID=$! && sleep 2 && open 'http://localhost:8080/examples/wallet-connect.html' && wait $HTTP_SERVER_PID"
"wallet:connect:example": "npx http-server -c-1 & HTTP_SERVER_PID=$! && sleep 2 && open 'http://localhost:8080/examples/wallet-connect.html' && wait $HTTP_SERVER_PID"
},
"repository": {
"type": "git",
Expand Down
15 changes: 14 additions & 1 deletion packages/agent/src/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ async function initClient({
// a route to its web5 connect provider flow and the params of where to fetch the auth request.
const generatedWalletUri = new URL(walletUri);
generatedWalletUri.searchParams.set('request_uri', parData.request_uri);
generatedWalletUri.searchParams.set('encryption_key', Convert.uint8Array(encryptionKey).toBase64Url());
generatedWalletUri.searchParams.set(
'encryption_key',
Convert.uint8Array(encryptionKey).toBase64Url()
);

// call user's callback so they can send the URI to the wallet as they see fit
onWalletUriReady(generatedWalletUri.toString());
Expand Down Expand Up @@ -179,4 +182,14 @@ export type ConnectPermissionRequest = {
permissionScopes: DwnRecordsPermissionScope[];
};

// TODO: add a convenience method for generating scopes and protcol definitions?

// const SomeObject = 'i\'m a protocol';

// Web5.connect({
// walletConnectOptions: {
// permissionRequests: [{ protcolDefinition: SomeObject, permissionScopes: [new PermissionScope()] }],
// },
// });

export const WalletConnect = { initClient };
135 changes: 96 additions & 39 deletions packages/agent/src/oidc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ import { xchacha20poly1305 } from '@noble/ciphers/chacha';
import type { ConnectPermissionRequest } from './connect.js';
import { DidDocument, DidJwk, PortableDid, type BearerDid } from '@web5/dids';
import { AgentDwnApi } from './dwn-api.js';
import { DwnInterfaceName, DwnMethodName } from '@tbd54566975/dwn-sdk-js';
import {
DwnInterfaceName,
DwnMethodName,
PermissionScope,
} from '@tbd54566975/dwn-sdk-js';
import { DwnInterface } from './types/dwn.js';
import { AgentPermissionsApi } from './permissions-api.js';

/**
* Sent to an OIDC server to authorize a client. Allows clients
Expand Down Expand Up @@ -240,10 +245,7 @@ async function generateCodeChallenge() {
async function createAuthRequest(
options: RequireOnly<
Web5ConnectAuthRequest,
| 'client_id'
| 'scope'
| 'redirect_uri'
| 'permissionRequests'
'client_id' | 'scope' | 'redirect_uri' | 'permissionRequests'
>
) {
// Generate a random state value to associate the authorization request with the response.
Expand Down Expand Up @@ -606,39 +608,83 @@ function encryptAuthResponse({
* Creates the permission grants that assign to the selectedDid the level of
* permissions that the web app requested in the {@link Web5ConnectAuthRequest}
*/
export async function createPermissionGrants(
// TODO: need another helper to call multiple times for each protocol
async function createPermissionGrants(
selectedDid: string,
delegateDid: BearerDid,
dwn: AgentDwnApi
dwn: AgentDwnApi,
permissionsApi: AgentPermissionsApi,
// we assume something generated the scopes
scopes: PermissionScope[],
protocolUri: string
) {
// TODO: remove mock after adding functionality: https://github.com/TBD54566975/web5-js/issues/827
const permissionRequestData = {
description:
'The app is asking to Records Write to http://profile-protocol.xyz',
scope: {
interface : DwnInterfaceName.Records,
method : DwnMethodName.Write,
protocol : 'http://profile-protocol.xyz',
},
};
// PermissionsApi.createRequest();
// grantedTo: delegateDid

// 1. fllw calls connect
// 2. fllw knows what it needs to operate: ['read', 'write', 'query']
const permissionGrants = await Promise.all(
scopes.map((scope) =>
permissionsApi.createGrant({
grantedTo : delegateDid.uri,
scope,
dateExpires : '2040-06-25T16:09:16.693356Z',
author : selectedDid,
})
)
);

// TODO: remove mock after adding functionality: https://github.com/TBD54566975/web5-js/issues/827
const message = await dwn.processRequest({
author : selectedDid,
target : selectedDid,
messageType : DwnInterface.RecordsWrite,
messageParams : {
recipient : delegateDid.uri,
protocolPath : 'grant',
protocol : ' https://tbd.website/dwn/permissions',
dataFormat : 'application/json',
data : Convert.object(permissionRequestData).toUint8Array(),
// By default we must grant Messages Query and Messages Read for sync
permissionGrants.push(await permissionsApi.createGrant({
grantedTo : delegateDid.uri,
scope : {
interface : DwnInterfaceName.Messages,
method : DwnMethodName.Query,
protocol : protocolUri
},
// todo: is it data or datastream?
// dataStream: await Convert.object(permissionRequestData).toBlobAsync(),
});
dateExpires : '2040-06-25T16:09:16.693356Z',
author : selectedDid,
}));
permissionGrants.push(await permissionsApi.createGrant({
grantedTo : delegateDid.uri,
scope : {
interface : DwnInterfaceName.Messages,
method : DwnMethodName.Read,
protocol : protocolUri
},
dateExpires : '2040-06-25T16:09:16.693356Z',
author : selectedDid,
}));

for (const grant of permissionGrants) {
// Quirk: we have to pull out encodedData out of the message the schema validator doesnt want it there
const { encodedData, ...rawMessage } = grant.message;

const data = Convert.base64Url(encodedData).toUint8Array();
const params = {
author : selectedDid,
target : selectedDid,
messageType : DwnInterface.RecordsWrite,
dataStream : new Blob([data]),
rawMessage
};

// TODO: remove mock after adding functionality: https://github.com/TBD54566975/web5-js/issues/827
const message = await dwn.processRequest(params);
const sent = await dwn.sendRequest(params);

// TODO: cleanup all grants if one fails by deleting them from the DWN: https://github.com/TBD54566975/web5-js/issues/849
if (message.reply.status.code !== 202) {
throw new Error(`Could not process the message. Error details: ${message.reply.status.detail}`);
}
if (sent.reply.status.code !== 202) {
throw new Error(`Could not send the message. Error details: ${message.reply.status.detail}`);
}
}

return [message];
const messages = permissionGrants.map((grant) => grant.message);

return messages;
}

/**
Expand All @@ -654,16 +700,27 @@ async function submitAuthResponse(
selectedDid: string,
authRequest: Web5ConnectAuthRequest,
randomPin: string,
dwn: AgentDwnApi
agentDwnApi: AgentDwnApi,
agentPermissionsApi: AgentPermissionsApi
) {
const delegateDid = await DidJwk.create();
const delegateDidPortable = await delegateDid.export();

const permissionGrants = await Oidc.createPermissionGrants(
selectedDid,
delegateDid,
dwn
);
let grantArr = [];

for (const permissionRequest of authRequest.permissionRequests) {
// TODO: validate to make sure the scopes and definition are assigned to the same protocol
const permissionGrants = await Oidc.createPermissionGrants(
selectedDid,
delegateDid,
agentDwnApi,
agentPermissionsApi,
permissionRequest.permissionScopes,
permissionRequest.protocolDefinition.protocol
);

grantArr.push(...permissionGrants);
}

const responseObject = await Oidc.createResponseObject({
//* the IDP's did that was selected to be connected
Expand All @@ -674,7 +731,7 @@ async function submitAuthResponse(
aud : authRequest.client_id,
//* the nonce of the original auth request
nonce : authRequest.nonce,
delegateGrants : permissionGrants,
delegateGrants : grantArr,
delegateDid : delegateDidPortable,
});

Expand Down
8 changes: 7 additions & 1 deletion packages/api/src/web5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ export type Web5ConnectResult = {
* {@link WalletConnectOptions} was provided.
*/
delegateDid?: string;

delegateGrants: any;
};

/**
Expand Down Expand Up @@ -226,6 +228,7 @@ export class Web5 {
walletConnectOptions,
}: Web5ConnectOptions = {}): Promise<Web5ConnectResult> {
let delegateDid: string | undefined;
let returnedGrants: any;
if (agent === undefined) {
// A custom Web5Agent implementation was not specified, so use default managed user agent.
const userAgent = await Web5UserAgent.create({ agentVault });
Expand Down Expand Up @@ -261,6 +264,9 @@ export class Web5 {
try {
// TEMPORARY: Placeholder for WalletConnect integration
const { connectedDid, delegateDid, delegateGrants } = await WalletConnect.initClient(walletConnectOptions);
returnedGrants = delegateGrants;
console.log('DELEGATEGRANTS ARE: ');
console.log(delegateGrants);

// Import the delegated DID as an Identity in the User Agent.
// Setting the connectedDID in the metadata applies a relationship between the signer identity and the one it is impersonating.
Expand Down Expand Up @@ -383,7 +389,7 @@ export class Web5 {

const web5 = new Web5({ agent, connectedDid, delegateDid });

return { web5, did: connectedDid, delegateDid, recoveryPhrase };
return { web5, did: connectedDid, delegateDid, recoveryPhrase, delegateGrants: returnedGrants };
}

/**
Expand Down

0 comments on commit 217aa2e

Please sign in to comment.