Skip to content

Commit b062c83

Browse files
committed
feat(passkey-crypto): add removePasskeyFromAccount function
- DELETE /user/otp/{device.id} to remove a passkey from the account - Validates device.id before making the API call - Imports BitGoBase from @bitgo/sdk-core WCN-191
1 parent 3dd77d6 commit b062c83

5 files changed

Lines changed: 89 additions & 1 deletion

File tree

modules/passkey-crypto/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
},
3636
"dependencies": {
3737
"@bitgo/public-types": "6.1.0",
38+
"@bitgo/sdk-core": "^36.42.0",
3839
"@bitgo/sjcl": "^1.1.0"
3940
},
4041
"devDependencies": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { derivePassword } from './derivePassword';
22
export { deriveEnterpriseSalt } from './deriveEnterpriseSalt';
33
export { buildEvalByCredential, matchDeviceByCredentialId } from './prfHelpers';
4+
export { removePasskeyFromAccount } from './removePasskeyFromAccount';
45
export type { WebAuthnOtpDevice, PasskeyAuthResult, PasskeyGetOptions, WebAuthnProvider } from './webAuthnTypes';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { BitGoBase } from '@bitgo/sdk-core';
2+
import type { WebAuthnOtpDevice } from '@bitgo/public-types';
3+
4+
/**
5+
* Permanently removes a passkey credential from the user's account.
6+
* Call removePasskeyFromWallet() for all affected wallets before calling this.
7+
*/
8+
export async function removePasskeyFromAccount(params: { bitgo: BitGoBase; device: WebAuthnOtpDevice }): Promise<void> {
9+
const { bitgo, device } = params;
10+
if (!device.id) {
11+
throw new Error('device.id is required to remove a passkey from the account');
12+
}
13+
await bitgo.del(bitgo.url(`/user/otp/${device.id}`)).result();
14+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import * as assert from 'assert';
2+
import * as sinon from 'sinon';
3+
import { removePasskeyFromAccount } from '../../src/removePasskeyFromAccount';
4+
import type { WebAuthnOtpDevice } from '@bitgo/public-types';
5+
6+
describe('removePasskeyFromAccount', function () {
7+
let mockBitGo: {
8+
url: sinon.SinonStub;
9+
del: sinon.SinonStub;
10+
};
11+
12+
const device: WebAuthnOtpDevice = {
13+
id: 'mongo-object-id-123',
14+
credentialId: 'cred-id-should-not-be-used',
15+
prfSalt: 'some-salt',
16+
isPasskey: true,
17+
};
18+
19+
beforeEach(function () {
20+
mockBitGo = {
21+
url: sinon.stub().callsFake((path: string) => `https://app.bitgo.com/api/v1${path}`),
22+
del: sinon.stub().returns({
23+
result: sinon.stub().resolves(undefined),
24+
}),
25+
};
26+
});
27+
28+
afterEach(function () {
29+
sinon.restore();
30+
});
31+
32+
it('should DELETE /user/otp/{device.id} using device.id', async function () {
33+
await removePasskeyFromAccount({ bitgo: mockBitGo as any, device });
34+
35+
assert.strictEqual(mockBitGo.url.calledOnce, true);
36+
assert.strictEqual(mockBitGo.url.firstCall.args[0], `/user/otp/${device.id}`);
37+
assert.strictEqual(mockBitGo.del.calledOnce, true);
38+
});
39+
40+
it('should not use credentialId', async function () {
41+
await removePasskeyFromAccount({ bitgo: mockBitGo as any, device });
42+
43+
const urlArg: string = mockBitGo.url.firstCall.args[0];
44+
assert.ok(!urlArg.includes(device.credentialId), 'URL should not contain credentialId');
45+
});
46+
47+
it('should resolve without returning a value', async function () {
48+
const result = await removePasskeyFromAccount({ bitgo: mockBitGo as any, device });
49+
assert.strictEqual(result, undefined);
50+
});
51+
52+
it('should throw if device.id is empty', async function () {
53+
const badDevice: WebAuthnOtpDevice = { ...device, id: '' };
54+
await assert.rejects(() => removePasskeyFromAccount({ bitgo: mockBitGo as any, device: badDevice }), {
55+
message: 'device.id is required to remove a passkey from the account',
56+
});
57+
assert.strictEqual(mockBitGo.del.called, false);
58+
});
59+
60+
it('should throw if device.id is undefined', async function () {
61+
const badDevice = { ...device, id: undefined } as unknown as WebAuthnOtpDevice;
62+
await assert.rejects(() => removePasskeyFromAccount({ bitgo: mockBitGo as any, device: badDevice }), {
63+
message: 'device.id is required to remove a passkey from the account',
64+
});
65+
assert.strictEqual(mockBitGo.del.called, false);
66+
});
67+
});

modules/passkey-crypto/tsconfig.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,10 @@
99
"typeRoots": ["../../types", "./node_modules/@types", "../../node_modules/@types"]
1010
},
1111
"include": ["src/**/*", "test/**/*"],
12-
"exclude": ["node_modules"]
12+
"exclude": ["node_modules"],
13+
"references": [
14+
{
15+
"path": "../sdk-core"
16+
}
17+
]
1318
}

0 commit comments

Comments
 (0)