Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/passkey-crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
},
"dependencies": {
"@bitgo/public-types": "6.1.0",
"@bitgo/sdk-core": "^36.42.0",
"@bitgo/sjcl": "^1.1.0"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions modules/passkey-crypto/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { derivePassword } from './derivePassword';
export { deriveEnterpriseSalt } from './deriveEnterpriseSalt';
export { buildEvalByCredential, matchDeviceByCredentialId } from './prfHelpers';
export { removePasskeyFromAccount } from './removePasskeyFromAccount';
export type { WebAuthnOtpDevice, PasskeyAuthResult, PasskeyGetOptions, WebAuthnProvider } from './webAuthnTypes';
14 changes: 14 additions & 0 deletions modules/passkey-crypto/src/removePasskeyFromAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { BitGoBase } from '@bitgo/sdk-core';
import type { WebAuthnOtpDevice } from '@bitgo/public-types';

/**
* Permanently removes a passkey credential from the user's account.
* Call removePasskeyFromWallet() for all affected wallets before calling this.
*/
export async function removePasskeyFromAccount(params: { bitgo: BitGoBase; device: WebAuthnOtpDevice }): Promise<void> {
const { bitgo, device } = params;
if (!device.id) {
throw new Error('device.id is required to remove a passkey from the account');
}
await bitgo.del(bitgo.url(`/user/otp/${device.id}`)).result();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as assert from 'assert';
import * as sinon from 'sinon';
import { removePasskeyFromAccount } from '../../src/removePasskeyFromAccount';
import type { WebAuthnOtpDevice } from '@bitgo/public-types';

describe('removePasskeyFromAccount', function () {
let mockBitGo: {
url: sinon.SinonStub;
del: sinon.SinonStub;
};

const device: WebAuthnOtpDevice = {
id: 'mongo-object-id-123',
credentialId: 'cred-id-should-not-be-used',
prfSalt: 'some-salt',
isPasskey: true,
};

beforeEach(function () {
mockBitGo = {
url: sinon.stub().callsFake((path: string) => `https://app.bitgo.com/api/v1${path}`),
del: sinon.stub().returns({
result: sinon.stub().resolves(undefined),
}),
};
});

afterEach(function () {
sinon.restore();
});

it('should DELETE /user/otp/{device.id} using device.id', async function () {
await removePasskeyFromAccount({ bitgo: mockBitGo as any, device });

assert.strictEqual(mockBitGo.url.calledOnce, true);
assert.strictEqual(mockBitGo.url.firstCall.args[0], `/user/otp/${device.id}`);
assert.strictEqual(mockBitGo.del.calledOnce, true);
});

it('should not use credentialId', async function () {
await removePasskeyFromAccount({ bitgo: mockBitGo as any, device });

const urlArg: string = mockBitGo.url.firstCall.args[0];
assert.ok(!urlArg.includes(device.credentialId), 'URL should not contain credentialId');
});

it('should resolve without returning a value', async function () {
const result = await removePasskeyFromAccount({ bitgo: mockBitGo as any, device });
assert.strictEqual(result, undefined);
});

it('should throw if device.id is empty', async function () {
const badDevice: WebAuthnOtpDevice = { ...device, id: '' };
await assert.rejects(() => removePasskeyFromAccount({ bitgo: mockBitGo as any, device: badDevice }), {
message: 'device.id is required to remove a passkey from the account',
});
assert.strictEqual(mockBitGo.del.called, false);
});

it('should throw if device.id is undefined', async function () {
const badDevice = { ...device, id: undefined } as unknown as WebAuthnOtpDevice;
await assert.rejects(() => removePasskeyFromAccount({ bitgo: mockBitGo as any, device: badDevice }), {
message: 'device.id is required to remove a passkey from the account',
});
assert.strictEqual(mockBitGo.del.called, false);
});
});
7 changes: 6 additions & 1 deletion modules/passkey-crypto/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,10 @@
"typeRoots": ["../../types", "./node_modules/@types", "../../node_modules/@types"]
},
"include": ["src/**/*", "test/**/*"],
"exclude": ["node_modules"]
"exclude": ["node_modules"],
"references": [
{
"path": "../sdk-core"
}
]
}
Loading