UNPKG

@web5/agent

Version:
191 lines 8.63 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { Oidc, } from './oidc.js'; import { pollWithTtl } from './utils.js'; import { Convert, logger } from '@web5/common'; import { CryptoUtils } from '@web5/crypto'; import { DidJwk } from '@web5/dids'; import { DwnInterfaceName, DwnMethodName } from '@tbd54566975/dwn-sdk-js'; /** * Initiates the wallet connect process. Used when a client wants to obtain * a did from a provider. */ function initClient({ displayName, connectServerUrl, walletUri, permissionRequests, onWalletUriReady, validatePin, }) { return __awaiter(this, void 0, void 0, function* () { // ephemeral client did for ECDH, signing, verification // TODO: use separate keys for ECDH vs. sign/verify. could maybe use secp256k1. const clientDid = yield DidJwk.create(); // TODO: properly implement PKCE. this implementation is lacking server side validations and more. // https://github.com/TBD54566975/web5-js/issues/829 // Derive the code challenge based on the code verifier // const { codeChallengeBytes, codeChallengeBase64Url } = // await Oidc.generateCodeChallenge(); const encryptionKey = CryptoUtils.randomBytes(32); // build callback URL to pass into the auth request const callbackEndpoint = Oidc.buildOidcUrl({ baseURL: connectServerUrl, endpoint: 'callback', }); // build the PAR request const request = yield Oidc.createAuthRequest({ client_id: clientDid.uri, scope: 'openid did:jwk', redirect_uri: callbackEndpoint, // custom properties: // code_challenge : codeChallengeBase64Url, // code_challenge_method : 'S256', permissionRequests: permissionRequests, displayName, }); // Sign the Request Object using the Client DID's signing key. const requestJwt = yield Oidc.signJwt({ did: clientDid, data: request, }); if (!requestJwt) { throw new Error('Unable to sign requestObject'); } // Encrypt the Request Object JWT using the code challenge. const requestObjectJwe = yield Oidc.encryptAuthRequest({ jwt: requestJwt, encryptionKey, }); // Convert the encrypted Request Object to URLSearchParams for form encoding. const formEncodedRequest = new URLSearchParams({ request: requestObjectJwe, }); const pushedAuthorizationRequestEndpoint = Oidc.buildOidcUrl({ baseURL: connectServerUrl, endpoint: 'pushedAuthorizationRequest', }); const parResponse = yield fetch(pushedAuthorizationRequestEndpoint, { body: formEncodedRequest, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }); if (!parResponse.ok) { throw new Error(`${parResponse.status}: ${parResponse.statusText}`); } const parData = yield parResponse.json(); // 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. logger.log(`Wallet URI: ${walletUri}`); const generatedWalletUri = new URL(walletUri); generatedWalletUri.searchParams.set('request_uri', parData.request_uri); 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()); const tokenUrl = Oidc.buildOidcUrl({ baseURL: connectServerUrl, endpoint: 'token', tokenParam: request.state, }); // subscribe to receiving a response from the wallet with default TTL. receive ciphertext of {@link Web5ConnectAuthResponse} const authResponse = yield pollWithTtl(() => fetch(tokenUrl)); if (authResponse) { const jwe = yield (authResponse === null || authResponse === void 0 ? void 0 : authResponse.text()); // get the pin from the user and use it as AAD to decrypt const pin = yield validatePin(); const jwt = yield Oidc.decryptAuthResponse(clientDid, jwe, pin); const verifiedAuthResponse = (yield Oidc.verifyJwt({ jwt, })); return { delegateGrants: verifiedAuthResponse.delegateGrants, delegatePortableDid: verifiedAuthResponse.delegatePortableDid, connectedDid: verifiedAuthResponse.iss, }; } }); } /** * Creates a set of Dwn Permission Scopes to request for a given protocol. * * If no permissions are provided, the default is to request all relevant record permissions (write, read, delete, query, subscribe). * 'configure' is not included by default, as this gives the application a lot of control over the protocol. */ function createPermissionRequestForProtocol({ definition, permissions }) { const requests = []; // Add the ability to query for the specific protocol requests.push({ protocol: definition.protocol, interface: DwnInterfaceName.Protocols, method: DwnMethodName.Query, }); // In order to enable sync, we must request permissions for `MessagesQuery`, `MessagesRead` and `MessagesSubscribe` requests.push({ protocol: definition.protocol, interface: DwnInterfaceName.Messages, method: DwnMethodName.Read, }, { protocol: definition.protocol, interface: DwnInterfaceName.Messages, method: DwnMethodName.Query, }, { protocol: definition.protocol, interface: DwnInterfaceName.Messages, method: DwnMethodName.Subscribe, }); // We also request any additional permissions the user has requested for this protocol for (const permission of permissions) { switch (permission) { case 'write': requests.push({ protocol: definition.protocol, interface: DwnInterfaceName.Records, method: DwnMethodName.Write, }); break; case 'read': requests.push({ protocol: definition.protocol, interface: DwnInterfaceName.Records, method: DwnMethodName.Read, }); break; case 'delete': requests.push({ protocol: definition.protocol, interface: DwnInterfaceName.Records, method: DwnMethodName.Delete, }); break; case 'query': requests.push({ protocol: definition.protocol, interface: DwnInterfaceName.Records, method: DwnMethodName.Query, }); break; case 'subscribe': requests.push({ protocol: definition.protocol, interface: DwnInterfaceName.Records, method: DwnMethodName.Subscribe, }); break; case 'configure': requests.push({ protocol: definition.protocol, interface: DwnInterfaceName.Protocols, method: DwnMethodName.Configure, }); break; } } return { protocolDefinition: definition, permissionScopes: requests, }; } export const WalletConnect = { initClient, createPermissionRequestForProtocol }; //# sourceMappingURL=connect.js.map