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

Added parameter for app display name for dynamic rendering in the wallet during web5 connect flow #945

Merged
merged 8 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/lemon-bees-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@web5/agent": patch
---

Added parameter for app display name for dynamic rendering in the wallet during web5 connect flow
5 changes: 5 additions & 0 deletions .changeset/smooth-weeks-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@web5/api": patch
---

Added parameter for app display name for dynamic rendering in the wallet during web5 connect flow
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 @@
* 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 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 @@
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 @@
})
);

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 @@

// 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);

Check warning on line 667 in packages/agent/src/oidc.ts

View check run for this annotation

Codecov / codecov/patch

packages/agent/src/oidc.ts#L666-L667

Added lines #L666 - L667 were not covered by tests
throw new Error(
`Could not send the message. Error details: ${reply.status.detail}`
);
Expand All @@ -666,9 +673,13 @@
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;
}

Check warning on line 682 in packages/agent/src/oidc.ts

View check run for this annotation

Codecov / codecov/patch

packages/agent/src/oidc.ts#L680-L682

Added lines #L680 - L682 were not covered by tests
}

/**
Expand All @@ -693,6 +704,7 @@
`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 @@
});

} 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 @@

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 @@
});

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

console.log('Encrypting auth response object...');
const encryptedResponse = Oidc.encryptAuthResponse({
jwt : responseObjectJwt!,
encryptionKey : sharedKey,
Expand All @@ -813,6 +829,7 @@
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
6 changes: 6 additions & 0 deletions packages/api/tests/web5.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ describe('web5 api', () => {
// connect to the app, the options don't matter because we're stubbing the initClient method
const { web5, did, delegateDid } = await Web5.connect({
walletConnectOptions: {
displayName : 'Sample App',
connectServerUrl : 'https://connect.example.com',
walletUri : 'https://wallet.example.com',
validatePin : async () => { return '1234'; },
Expand Down Expand Up @@ -675,6 +676,7 @@ describe('web5 api', () => {
// connect to the app, the options don't matter because we're stubbing the initClient method
await Web5.connect({
walletConnectOptions: {
displayName : 'Sample App',
connectServerUrl : 'https://connect.example.com',
walletUri : 'https://wallet.example.com',
validatePin : async () => { return '1234'; },
Expand Down Expand Up @@ -735,6 +737,7 @@ describe('web5 api', () => {
await Web5.connect({
sync : 'off',
walletConnectOptions : {
displayName : 'Sample App',
connectServerUrl : 'https://connect.example.com',
walletUri : 'https://wallet.example.com',
validatePin : async () => { return '1234'; },
Expand Down Expand Up @@ -779,6 +782,7 @@ describe('web5 api', () => {
await Web5.connect({
sync : '1m',
walletConnectOptions : {
displayName : 'Sample App',
connectServerUrl : 'https://connect.example.com',
walletUri : 'https://wallet.example.com',
validatePin : async () => { return '1234'; },
Expand Down Expand Up @@ -822,6 +826,7 @@ describe('web5 api', () => {

await Web5.connect({
walletConnectOptions: {
displayName : 'Sample App',
connectServerUrl : 'https://connect.example.com',
walletUri : 'https://wallet.example.com',
validatePin : async () => { return '1234'; },
Expand Down Expand Up @@ -893,6 +898,7 @@ describe('web5 api', () => {

await Web5.connect({
walletConnectOptions: {
displayName : 'Sample App',
connectServerUrl : 'https://connect.example.com',
walletUri : 'https://wallet.example.com',
validatePin : async () => { return '1234'; },
Expand Down
Loading