Skip to content

Commit b2f6269

Browse files
Merge pull request #7584 from BitGo/WP-6901
feat(express): update change keychain password for ofc multi-user-key…
2 parents 895fd55 + c306c97 commit b2f6269

File tree

3 files changed

+184
-6
lines changed

3 files changed

+184
-6
lines changed

modules/express/src/clientRoutes.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,15 @@ export async function handleKeychainChangePassword(
10671067
throw new ApiResponseError(`Keychain ${req.params.id} not found`, 404);
10681068
}
10691069

1070+
// OFC wallet supports multiple keys (one per spender) on a single keychain.
1071+
const isMultiUserKey = (keychain.coinSpecific?.[coinName]?.['features'] ?? []).includes('multi-user-key');
1072+
if (isMultiUserKey && !keychain.pub) {
1073+
throw new ApiResponseError(
1074+
`Unexpected missing field "keychain.pub". Please contact [email protected] for more details`,
1075+
500
1076+
);
1077+
}
1078+
10701079
const updatedKeychain = coin.keychains().updateSingleKeychainPassword({
10711080
keychain,
10721081
oldPassword,
@@ -1075,6 +1084,7 @@ export async function handleKeychainChangePassword(
10751084

10761085
return bitgo.put(coin.url(`/key/${updatedKeychain.id}`)).send({
10771086
encryptedPrv: updatedKeychain.encryptedPrv,
1087+
pub: keychain.pub,
10781088
});
10791089
}
10801090

modules/express/src/typedRoutes/api/v2/keychainChangePassword.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const KeychainChangePasswordResponse = {
3333
/** Invalid request or not found */
3434
400: BitgoExpressError,
3535
404: BitgoExpressError,
36+
500: BitgoExpressError,
3637
} as const;
3738

3839
/**

modules/express/test/unit/clientRoutes/changeKeychainPassword.ts

Lines changed: 173 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@ import { BitGo } from 'bitgo';
77
import { ExpressApiRouteRequest } from '../../../src/typedRoutes/api';
88
import { decodeOrElse } from '@bitgo/sdk-core';
99
import { KeychainChangePasswordResponse } from '../../../src/typedRoutes/api/v2/keychainChangePassword';
10+
import * as assert from 'assert';
11+
import { ApiResponseError } from '../../../src/errors';
1012

1113
describe('Change Wallet Password', function () {
14+
const id = '23423423423423';
15+
const oldPassword = 'oldPasswordString';
16+
const newPassword = 'newPasswordString';
17+
18+
const keychainBaseCoinStub = {
19+
keychains: () => ({ updateSingleKeychainPassword: () => Promise.resolve({ result: 'stubbed' }) }),
20+
};
21+
1222
it('should change wallet password', async function () {
13-
const keychainBaseCoinStub = {
14-
keychains: () => ({ updateSingleKeychainPassword: () => Promise.resolve({ result: 'stubbed' }) }),
15-
};
1623
const keychainStub = {
1724
baseCoin: keychainBaseCoinStub,
1825
};
@@ -35,9 +42,6 @@ describe('Change Wallet Password', function () {
3542
});
3643

3744
const coin = 'talgo';
38-
const id = '23423423423423';
39-
const oldPassword = 'oldPasswordString';
40-
const newPassword = 'newPasswordString';
4145
const mockRequest = {
4246
bitgo: stubBitgo,
4347
params: {
@@ -62,4 +66,167 @@ describe('Change Wallet Password', function () {
6266
});
6367
({ result: '200 OK' }).should.be.eql(result);
6468
});
69+
70+
describe('ofc wallet test', function () {
71+
it('should change ofc multi-user-key wallet password', async function () {
72+
const keychainStub = {
73+
baseCoin: keychainBaseCoinStub,
74+
coinSpecific: {
75+
ofc: {
76+
features: ['multi-user-key'],
77+
},
78+
},
79+
pub: 'pub',
80+
};
81+
82+
const coinStub = {
83+
keychains: () => ({
84+
get: () => Promise.resolve(keychainStub),
85+
updateSingleKeychainPassword: () => ({ result: 'stubbed' }),
86+
}),
87+
url: () => 'url',
88+
};
89+
90+
const sendStub = sinon.stub().resolves({ result: '200 OK' });
91+
const stubBitgo = sinon.createStubInstance(BitGo as any, {
92+
coin: coinStub,
93+
});
94+
stubBitgo['put'] = sinon.stub().returns({
95+
send: sendStub,
96+
});
97+
98+
const coin = 'ofc';
99+
const mockRequest = {
100+
bitgo: stubBitgo,
101+
params: {
102+
coin,
103+
id,
104+
},
105+
body: {
106+
oldPassword,
107+
newPassword,
108+
},
109+
decoded: {
110+
oldPassword,
111+
newPassword,
112+
coin,
113+
id,
114+
},
115+
} as unknown as ExpressApiRouteRequest<'express.keychain.changePassword', 'post'>;
116+
117+
const result = await handleKeychainChangePassword(mockRequest);
118+
decodeOrElse('KeychainChangePasswordResponse200', KeychainChangePasswordResponse[200], result, (errors) => {
119+
throw new Error(`Response did not match expected codec: ${errors}`);
120+
});
121+
({ result: '200 OK' }).should.be.eql(result);
122+
sinon.assert.calledWith(sendStub, { encryptedPrv: sinon.match.any, pub: 'pub' });
123+
});
124+
125+
it('should change ofc non multi-user-key wallet password', async function () {
126+
const keychainStub = {
127+
baseCoin: keychainBaseCoinStub,
128+
coinSpecific: {
129+
ofc: {
130+
features: [],
131+
},
132+
},
133+
pub: 'pub',
134+
};
135+
136+
const coinStub = {
137+
keychains: () => ({
138+
get: () => Promise.resolve(keychainStub),
139+
updateSingleKeychainPassword: () => ({ result: 'stubbed' }),
140+
}),
141+
url: () => 'url',
142+
};
143+
144+
const sendStub = sinon.stub().resolves({ result: '200 OK' });
145+
const stubBitgo = sinon.createStubInstance(BitGo as any, {
146+
coin: coinStub,
147+
});
148+
stubBitgo['put'] = sinon.stub().returns({
149+
send: sendStub,
150+
});
151+
152+
const coin = 'ofc';
153+
const mockRequest = {
154+
bitgo: stubBitgo,
155+
params: {
156+
coin,
157+
id,
158+
},
159+
body: {
160+
oldPassword,
161+
newPassword,
162+
},
163+
decoded: {
164+
oldPassword,
165+
newPassword,
166+
coin,
167+
id,
168+
},
169+
} as unknown as ExpressApiRouteRequest<'express.keychain.changePassword', 'post'>;
170+
171+
const result = await handleKeychainChangePassword(mockRequest);
172+
decodeOrElse('KeychainChangePasswordResponse200', KeychainChangePasswordResponse[200], result, (errors) => {
173+
throw new Error(`Response did not match expected codec: ${errors}`);
174+
});
175+
({ result: '200 OK' }).should.be.eql(result);
176+
sinon.assert.calledWith(sendStub, { encryptedPrv: sinon.match.any, pub: 'pub' });
177+
});
178+
179+
it('should throw updating ofc multi-user-key without a valid pub', async function () {
180+
const keychainStub = {
181+
baseCoin: keychainBaseCoinStub,
182+
coinSpecific: {
183+
ofc: {
184+
features: ['multi-user-key'],
185+
},
186+
},
187+
};
188+
189+
const coinStub = {
190+
keychains: () => ({
191+
get: () => Promise.resolve(keychainStub),
192+
updateSingleKeychainPassword: () => ({ result: 'stubbed' }),
193+
}),
194+
url: () => 'url',
195+
};
196+
197+
const sendStub = sinon.stub().resolves({ result: '200 OK' });
198+
const stubBitgo = sinon.createStubInstance(BitGo as any, {
199+
coin: coinStub,
200+
});
201+
stubBitgo['put'] = sinon.stub().returns({
202+
send: sendStub,
203+
});
204+
205+
const coin = 'ofc';
206+
const mockRequest = {
207+
bitgo: stubBitgo,
208+
params: {
209+
coin,
210+
id,
211+
},
212+
body: {
213+
oldPassword,
214+
newPassword,
215+
},
216+
decoded: {
217+
oldPassword,
218+
newPassword,
219+
coin,
220+
id,
221+
},
222+
} as unknown as ExpressApiRouteRequest<'express.keychain.changePassword', 'post'>;
223+
224+
await assert.rejects(
225+
async () => await handleKeychainChangePassword(mockRequest),
226+
(err: ApiResponseError) =>
227+
err.status === 500 &&
228+
err.message === `Unexpected missing field "keychain.pub". Please contact [email protected] for more details`
229+
);
230+
});
231+
});
65232
});

0 commit comments

Comments
 (0)