Skip to content

Commit

Permalink
feat: modify accept share method
Browse files Browse the repository at this point in the history
Ticket: PX-3296
We are modifying the accept share method for go account wallet invitations
  • Loading branch information
ravneet-bitgo committed Apr 11, 2024
1 parent 0005adc commit 10c6203
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 14 deletions.
73 changes: 70 additions & 3 deletions modules/sdk-api/src/v1/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,14 +225,71 @@ Wallets.prototype.acceptShare = function (params, callback) {

const self = this;
let encryptedXprv = params.overrideEncryptedXprv;
let keyId = params.keyId;

const shareOfcAccountWithSpenders = async (walletId: string) => {
const wallet = await self.bitgo.wallets().get({ id: walletId });
const enterpriseUsersResponse = await self.bitgo.get(`/api/v2/enterprise/${wallet.enterprise}/user`);
console.log('🚀 ~ shareOfcAccountWithSpenders ~ wallet:', wallet);
console.log('🚀 ~ shareOfcAccountWithSpenders ~ enterpriseUsersResponse:', enterpriseUsersResponse);

wallet.users.forEach(async (user) => {
console.log('🚀 ~ shareOfcAccountWithSpenders ~ user:', user);
try {
if (user.permissions.includes('spend')) {
console.log('🚀 ~ user permission includes spend');
const userObject = enterpriseUsersResponse.users.find((enterpriseUser) => enterpriseUser.id === user.user);
const shareParams = {
walletId: walletId,
user: user.user,
permissions: user.permissions.join(','),
walletPassphrase: params.userPassword,
email: userObject.email,
coin: wallet.coin,
};
console.log('🚀 ~ wallet.users.forEach ~ shareParams:', shareParams);
await self.bitgo.wallets().shareWallet(shareParams);
}
} catch (e) {
console.error(e);
}
});
};

return this.getShare({ walletShareId: params.walletShareId })
.then(function (walletShare) {
.then(async function (walletShare) {
if (walletShare.keychainOverrideRequired && walletShare.permissions.indexOf('admin') !== -1) {
if (!params.userPassword) {
throw new Error('userPassword param must be provided to decrypt shared key');
}
console.log('Creating new keychain for wallet share');
// generate new keychain
const sdkCoin = await self.coin('ofc');
const keychains = sdkCoin.keychains();
const newKeychain = keychains.create();
const originalPasscodeEncryptionCode = self.bitgo.generateRandomPassword();

const encryptedPrv = self.bitgo.encrypt({
password: params.userPassword,
input: newKeychain.prv,
});

const walletKeychain = await keychains.add({
encryptedPrv,
originalPasscodeEncryptionCode,
pub: newKeychain.pub,
source: 'user',
});
keyId = walletKeychain.id;
console.log('Keychain created successfully and returning wallet share 1');
return walletShare;
}

// Return right away if there is no keychain to decrypt, or if explicit encryptedXprv was provided
if (!walletShare.keychain || !walletShare.keychain.encryptedXprv || encryptedXprv) {
console.log('No keychain to decrypt, returning wallet share');
return walletShare;
}

// More than viewing was requested, so we need to process the wallet keys using the shared ecdh scheme
if (!params.userPassword) {
throw new Error('userPassword param must be provided to decrypt shared key');
Expand All @@ -243,6 +300,7 @@ Wallets.prototype.acceptShare = function (params, callback) {
throw new Error('EncryptedXprv was not found on sharing keychain');
}

console.log('Decrypting shared keychain');
// Now we have the sharing keychain, we can work out the secret used for sharing the wallet with us
sharingKeychain.xprv = self.bitgo.decrypt({
password: params.userPassword,
Expand All @@ -266,6 +324,7 @@ Wallets.prototype.acceptShare = function (params, callback) {
encryptedXprv = self.bitgo.encrypt({ password: newWalletPassphrase, input: decryptedSharedWalletXprv });

// Carry on to the next block where we will post the acceptance of the share with the encrypted xprv
console.log('Decrypted shared keychain successfully and returning wallet share 2');
return walletShare;
});
})
Expand All @@ -278,8 +337,16 @@ Wallets.prototype.acceptShare = function (params, callback) {
if (encryptedXprv) {
updateParams.encryptedXprv = encryptedXprv;
}
if (keyId && walletShare.keychainOverrideRequired && walletShare.permissions.indexOf('admin') !== -1) {
updateParams.keyId = keyId;
}
console.log('🚀 ~ updateParams:', updateParams);
self.updateShare(updateParams);

return self.updateShare(updateParams);
if (walletShare.keychainOverrideRequired && walletShare.permissions.indexOf('admin') !== -1) {
console.log('Sharing wallet with spenders');
shareOfcAccountWithSpenders(walletShare.wallet);
}
})
.nodeify(callback);
};
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-core/src/bitgo/wallet/iWallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface UpdateShareOptions {
walletShareId?: string;
state?: string;
encryptedPrv?: string;
keyId?: string;
}

export interface AcceptShareOptions {
Expand Down
100 changes: 89 additions & 11 deletions modules/sdk-core/src/bitgo/wallet/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
WalletWithKeychains,
} from './iWallets';
import { Wallet } from './wallet';
import * as sjcl from '@bitgo/sjcl';
import * as bs58 from 'bs58';

export class Wallets implements IWallets {
private readonly bitgo: BitGoBase;
Expand Down Expand Up @@ -506,10 +508,7 @@ export class Wallets implements IWallets {
async updateShare(params: UpdateShareOptions = {}): Promise<any> {
common.validateParams(params, ['walletShareId'], []);

return await this.bitgo
.post(this.baseCoin.url('/walletshare/' + params.walletShareId))
.send(params)
.result();
return await this.bitgo.post(this.baseCoin.url('/walletshare/' + params.walletShareId)).send(params);
}

/**
Expand Down Expand Up @@ -538,6 +537,66 @@ export class Wallets implements IWallets {
.result();
}

async shareOfcAccountWithSpenders(walletId: string, userPassword: string) {
const wallet = await this.bitgo.wallets().get({ id: walletId });
const enterpriseUsersResponse = (await this.bitgo
.get(`/api/v2/enterprise/${wallet.enterprise}/user`)
.result()) as any;
console.log('🚀 ~ shareOfcAccountWithSpenders ~ wallet:', wallet);
console.log('🚀 ~ shareOfcAccountWithSpenders ~ enterpriseUsersResponse:', enterpriseUsersResponse);

wallet.users.forEach(async (user) => {
console.log('🚀 ~ shareOfcAccountWithSpenders ~ user:', user);
try {
if (user.permissions.includes('spend')) {
console.log('🚀 ~ user permission includes spend');
const userObject = enterpriseUsersResponse.users.find((enterpriseUser) => enterpriseUser.id === user.user);
const shareParams = {
walletId: walletId,
user: user.user,
permissions: user.permissions.join(','),
walletPassphrase: userPassword,
email: userObject.email,
coin: wallet.coin,
};
console.log('🚀 ~ wallet.users.forEach ~ shareParams:', shareParams);
await this.bitgo.wallets().shareWallet(shareParams);
}
} catch (e) {
console.error(e);
}
});
}

/**
* Generate a random password
* @param {Number} numWords Number of 32-bit words
* @returns {String} base58 random password
*/
generateRandomPassword(numWords = 5): string {
const bytes = sjcl.codec.bytes.fromBits(sjcl.random.randomWords(numWords));
return bs58.encode(bytes);
}

async createKeychain(userPassword: string) {
const sdkCoin = await this.bitgo.coin('ofc');
const keychains = sdkCoin.keychains();
const newKeychain = keychains.create();
const originalPasscodeEncryptionCode = this.generateRandomPassword();

const encryptedPrv = this.bitgo.encrypt({
password: userPassword,
input: newKeychain.prv,
});

const walletKeychain = await keychains.add({
encryptedPrv,
originalPasscodeEncryptionCode,
pub: newKeychain.pub,
source: 'user',
});
return walletKeychain;
}
/**
* Accepts a wallet share, adding the wallet to the user's list
* Needs a user's password to decrypt the shared key
Expand All @@ -554,15 +613,34 @@ export class Wallets implements IWallets {
common.validateParams(params, ['walletShareId'], ['overrideEncryptedPrv', 'userPassword', 'newWalletPassphrase']);

let encryptedPrv = params.overrideEncryptedPrv;

console.log('line 619');
const walletShare = (await this.getShare({ walletShareId: params.walletShareId })) as any;

// Return right away if there is no keychain to decrypt, or if explicit encryptedPrv was provided
if (!walletShare.keychain || !walletShare.keychain.encryptedPrv || encryptedPrv) {
return this.updateShare({
if (walletShare.keychainOverrideRequired && walletShare.permissions.indexOf('admin') !== -1) {
if (_.isUndefined(params.userPassword)) {
throw new Error('userPassword param must be provided to decrypt shared key');
}
const walletKeychain = await this.createKeychain(params.userPassword);
const response = await this.updateShare({
walletShareId: params.walletShareId,
state: 'accepted',
keyId: walletKeychain.id,
});
console.log('🚀 ~ Wallets ~ acceptShare ~ response:', response);
if (response.statusCode === 200 || response.result().changed) {
console.log('line 631');
await this.shareOfcAccountWithSpenders(walletShare.wallet, params.userPassword);
}
return response;
}
console.log('line 636');
// Return right away if there is no keychain to decrypt, or if explicit encryptedPrv was provided
if (!walletShare.keychain || !walletShare.keychain.encryptedPrv || encryptedPrv) {
return (
this.updateShare({
walletShareId: params.walletShareId,
state: 'accepted',
}) as any
).result();
}

// More than viewing was requested, so we need to process the wallet keys using the shared ecdh scheme
Expand Down Expand Up @@ -606,8 +684,8 @@ export class Wallets implements IWallets {
if (encryptedPrv) {
updateParams.encryptedPrv = encryptedPrv;
}

return this.updateShare(updateParams);
console.log('686');
return (this.updateShare(updateParams) as any).result();
}

/**
Expand Down

0 comments on commit 10c6203

Please sign in to comment.