@web5/agent
Version:
191 lines • 8.63 kB
JavaScript
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