Skip to content

Commit

Permalink
Added web5 connect client/app display name for dynamic rendering in t…
Browse files Browse the repository at this point in the history
…he wallet
  • Loading branch information
thehenrytsai committed Oct 3, 2024
1 parent b747a2d commit 1288e37
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 7 deletions.
11 changes: 9 additions & 2 deletions packages/agent/src/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { DwnInterfaceName, DwnMethodName } from '@tbd54566975/dwn-sdk-js';
* a did from a provider.
*/
async function initClient({
displayName,
connectServerUrl,
walletUri,
permissionRequests,
Expand Down Expand Up @@ -44,10 +45,12 @@ async function initClient({
const request = await Oidc.createAuthRequest({
client_id : clientDid.uri,
scope : 'openid did:jwk',
redirect_uri : callbackEndpoint,
// custom properties:
// code_challenge : codeChallengeBase64Url,
// code_challenge_method : 'S256',
permissionRequests : permissionRequests,
redirect_uri : callbackEndpoint,
displayName,
});

// Sign the Request Object using the Client DID's signing key.
Expand Down Expand Up @@ -91,6 +94,7 @@ async function initClient({

// a deeplink to a web5 compatible wallet. if the wallet scans this link it should receive
// a route to its web5 connect provider flow and the params of where to fetch the auth request.
console.log('Wallet URI:', walletUri);
const generatedWalletUri = new URL(walletUri);
generatedWalletUri.searchParams.set('request_uri', parData.request_uri);
generatedWalletUri.searchParams.set(
Expand Down Expand Up @@ -133,7 +137,10 @@ async function initClient({
* a did from a provider.
*/
export type WalletConnectOptions = {
/** The URL of the intermediary server which relays messages between the client and provider */
/** The user friendly name of the client/app to be displayed when prompting end-user with permission requests. */
displayName: string;

/** The URL of the intermediary server which relays messages between the client and provider. */
connectServerUrl: string;

/**
Expand Down
25 changes: 21 additions & 4 deletions packages/agent/src/oidc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ export type SIOPv2AuthRequest = {
* The contents of this are inserted into a JWT inside of the {@link PushedAuthRequest}.
*/
export type Web5ConnectAuthRequest = {
/** The user friendly name of the client/app to be displayed when prompting end-user with permission requests. */
displayName: string;

/** PermissionGrants that are to be sent to the provider */
permissionRequests: ConnectPermissionRequest[];
} & SIOPv2AuthRequest;
Expand Down Expand Up @@ -242,7 +245,7 @@ async function generateCodeChallenge() {
async function createAuthRequest(
options: RequireOnly<
Web5ConnectAuthRequest,
'client_id' | 'scope' | 'redirect_uri' | 'permissionRequests'
'client_id' | 'scope' | 'redirect_uri' | 'permissionRequests' | 'displayName'
>
) {
// Generate a random state value to associate the authorization request with the response.
Expand Down Expand Up @@ -628,6 +631,7 @@ async function createPermissionGrants(
const permissionsApi = new AgentPermissionsApi({ agent });

// TODO: cleanup all grants if one fails by deleting them from the DWN: https://github.com/TBD54566975/web5-js/issues/849
console.log(`Creating permission grants for ${scopes.length} scopes given...`);
const permissionGrants = await Promise.all(
scopes.map((scope) => {
// check if the scope is a records permission scope, or a protocol configure scope, if so it should use a delegated permission.
Expand All @@ -643,6 +647,7 @@ async function createPermissionGrants(
})
);

console.log(`Sending ${permissionGrants.length} permission grants to remote DWN...`);
const messagePromises = permissionGrants.map(async (grant) => {
// Quirk: we have to pull out encodedData out of the message the schema validator doesn't want it there
const { encodedData, ...rawMessage } = grant.message;
Expand All @@ -658,6 +663,8 @@ async function createPermissionGrants(

// check if the message was sent successfully, if the remote returns 409 the message may have come through already via sync
if (reply.status.code !== 202 && reply.status.code !== 409) {
console.log('Error sending RecordsWrite:', reply.status.detail);
console.log('RecordsWrite message:', rawMessage);
throw new Error(
`Could not send the message. Error details: ${reply.status.detail}`
);
Expand All @@ -666,9 +673,13 @@ async function createPermissionGrants(
return grant.message;
});

const messages = await Promise.all(messagePromises);

return messages;
try {
const messages = await Promise.all(messagePromises);
return messages;
} catch (error) {
console.error('Error during batch-send of permission grants:', error instanceof Error ? error.message : error);
throw error;
}
}

/**
Expand All @@ -693,6 +704,7 @@ async function prepareProtocol(
`Could not fetch protocol: ${queryMessage.reply.status.detail}`
);
} else if (queryMessage.reply.entries === undefined || queryMessage.reply.entries.length === 0) {
console.log('Protocol does not exist, creating:', protocolDefinition.protocol);

// send the protocol definition to the remote DWN first, if it passes we can process it locally
const { reply: sendReply, message: configureMessage } = await agent.sendDwnRequest({
Expand All @@ -716,6 +728,7 @@ async function prepareProtocol(
});

} else {
console.log('Protocol already exists:', protocolDefinition.protocol);

// the protocol already exists, let's make sure it exists on the remote DWN as the requesting app will need it
const configureMessage = queryMessage.reply.entries![0];
Expand Down Expand Up @@ -776,6 +789,7 @@ async function submitAuthResponse(

const delegateGrants = (await Promise.all(delegateGrantPromises)).flat();

console.log('Generating auth response object...');
const responseObject = await Oidc.createResponseObject({
//* the IDP's did that was selected to be connected
iss : selectedDid,
Expand All @@ -790,6 +804,7 @@ async function submitAuthResponse(
});

// Sign the Response Object using the ephemeral DID's signing key.
console.log('Signing auth response object...');
const responseObjectJwt = await Oidc.signJwt({
did : delegateBearerDid,
data : responseObject,
Expand All @@ -801,6 +816,7 @@ async function submitAuthResponse(
clientDid?.didDocument!
);

console.log('Encrypting auth response object...');
const encryptedResponse = Oidc.encryptAuthResponse({
jwt : responseObjectJwt!,
encryptionKey : sharedKey,
Expand All @@ -813,6 +829,7 @@ async function submitAuthResponse(
state : authRequest.state,
}).toString();

console.log(`Sending auth response object to Web5 Connect server: ${authRequest.redirect_uri}`);
await fetch(authRequest.redirect_uri, {
body : formEncodedRequest,
method : 'POST',
Expand Down
8 changes: 8 additions & 0 deletions packages/agent/tests/connect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ describe('web5 connect', function () {
});

const options = {
displayName : 'Sample App',
client_id : clientEphemeralPortableDid.uri,
scope : 'openid did:jwk',
// code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(),
Expand Down Expand Up @@ -457,6 +458,7 @@ describe('web5 connect', function () {
fetchStub.callThrough();

const results = await WalletConnect.initClient({
displayName : 'Sample App',
walletUri : 'http://localhost:3000/',
connectServerUrl : 'http://localhost:3000/connect',
permissionRequests : [
Expand Down Expand Up @@ -505,6 +507,7 @@ describe('web5 connect', function () {
});

const options = {
displayName : 'Sample App',
client_id : clientEphemeralPortableDid.uri,
scope : 'openid did:jwk',
// code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(),
Expand Down Expand Up @@ -560,6 +563,7 @@ describe('web5 connect', function () {
});

const options = {
displayName : 'Sample App',
client_id : clientEphemeralPortableDid.uri,
scope : 'openid did:jwk',
// code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(),
Expand Down Expand Up @@ -632,6 +636,7 @@ describe('web5 connect', function () {
});

const options = {
displayName : 'Sample App',
client_id : clientEphemeralPortableDid.uri,
scope : 'openid did:jwk',
// code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(),
Expand Down Expand Up @@ -679,6 +684,7 @@ describe('web5 connect', function () {
});

const options = {
displayName : 'Sample App',
client_id : clientEphemeralPortableDid.uri,
scope : 'openid did:jwk',
// code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(),
Expand Down Expand Up @@ -730,6 +736,7 @@ describe('web5 connect', function () {
});

const options = {
displayName : 'Sample App',
client_id : clientEphemeralPortableDid.uri,
scope : 'openid did:jwk',
// code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(),
Expand Down Expand Up @@ -781,6 +788,7 @@ describe('web5 connect', function () {
mismatchedScopes[0].protocol = 'http://profile-protocol.xyz/other';

const options = {
displayName : 'Sample App',
client_id : clientEphemeralPortableDid.uri,
scope : 'openid did:jwk',
// code_challenge : Convert.uint8Array(codeChallenge).toBase64Url(),
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 @@ -44,16 +44,22 @@ export type ConnectPermissionRequest = {
* The protocol definition for the protocol being requested.
*/
protocolDefinition: DwnProtocolDefinition;

/**
* The permissions being requested for the protocol. If none are provided, the default is to request all permissions.
*/
permissions?: Permission[];
}

/**
* Options for connecting to a Web5 agent. This includes the ability to connect to an external wallet
* Options for connecting to a Web5 agent. This includes the ability to connect to an external wallet.
*
* NOTE: the returned `ConnectPermissionRequest` type is different to the `ConnectPermissionRequest` type in the `@web5/agent` package.
*/
export type ConnectOptions = Omit<WalletConnectOptions, 'permissionRequests'> & {
/** The user friendly name of the client/app to be displayed when prompting end-user with permission requests. */
displayName: string;

/**
* The permissions that are being requested for the connected DID.
* This is used to create the {@link ConnectPermissionRequest} for the wallet connect flow.
Expand Down

0 comments on commit 1288e37

Please sign in to comment.