Files
privacy.sexy/tests/unit/presentation/electron/preload/ContextBridging/ApiContextBridge.spec.ts
undergroundwires efa05f42bc Improve security by isolating code execution more
This commit enhances application security against potential attacks by
isolating dependencies that access the host system (like file
operations) from the renderer process. It narrows the exposed
functionality to script execution only, adding an extra security layer.

The changes allow secure and scalable API exposure, preparing for future
functionalities such as desktop notifications for script errors (#264),
improved script execution handling (#296), and creating restore points
(#50) in a secure and repeatable way.

Changes include:

- Inject `CodeRunner` into Vue components via dependency injection.
- Move `CodeRunner` to the application layer as an abstraction for
  better domain-driven design alignment.
- Refactor `SystemOperations` and related interfaces, removing the `I`
  prefix.
- Update architecture documentation for clarity.
- Update return types in `NodeSystemOperations` to match the Node APIs.
- Improve `WindowVariablesProvider` integration tests for better error
  context.
- Centralize type checks with common functions like `isArray` and
  `isNumber`.
- Change `CodeRunner` to use `os` parameter, ensuring correct window
  variable injection.
- Streamline API exposure to the renderer process:
  - Automatically bind function contexts to prevent loss of original
    context.
  - Implement a way to create facades (wrapper/proxy objects) for
    increased security.
2023-12-18 17:30:56 +01:00

97 lines
2.8 KiB
TypeScript

import { it, describe, expect } from 'vitest';
import { BridgeConnector, MethodContextBinder, connectApisWithContextBridge } from '@/presentation/electron/preload/ContextBridging/ApiContextBridge';
describe('ApiContextBridge', () => {
describe('connectApisWithContextBridge', () => {
it('connects properties as keys', () => {
// arrange
const context = new BridgeConnectorTestContext();
const { exposedItems, bridgeConnector } = mockBridgeConnector();
const expectedKeys = ['a', 'b'];
const api = {
[`${expectedKeys[0]}`]: () => {},
[`${expectedKeys[1]}`]: () => {},
};
// act
context
.withApiObject(api)
.withBridgeConnector(bridgeConnector)
.run();
// assert
expect(exposedItems).to.have.lengthOf(expectedKeys.length);
expect(exposedItems.map(([key]) => key)).to.have.members(expectedKeys);
});
it('connects values after binding their context', () => {
// arrange
const context = new BridgeConnectorTestContext();
const { exposedItems, bridgeConnector } = mockBridgeConnector();
const rawValues = ['a', 'b'];
const api = {
first: rawValues[0],
second: rawValues[1],
};
const boundValues = {
[`${rawValues[0]}`]: 'bound-a',
[`${rawValues[1]}`]: 'bound-b',
};
const expectedValues = Object.values(boundValues);
const contextBinderMock: MethodContextBinder = (property) => {
return boundValues[property as string] as never;
};
// act
context
.withApiObject(api)
.withContextBinder(contextBinderMock)
.withBridgeConnector(bridgeConnector)
.run();
// assert
expect(exposedItems).to.have.lengthOf(rawValues.length);
expect(exposedItems.map(([,value]) => value)).to.have.members(expectedValues);
});
});
});
function mockBridgeConnector() {
const exposedItems = new Array<[string, unknown]>();
const bridgeConnector: BridgeConnector = (key, api) => exposedItems.push([key, api]);
return {
exposedItems,
bridgeConnector,
};
}
class BridgeConnectorTestContext {
private bridgeConnector: BridgeConnector = () => {};
private apiObject: object = {};
private contextBinder: MethodContextBinder = (obj) => obj;
public withBridgeConnector(bridgeConnector: BridgeConnector): this {
this.bridgeConnector = bridgeConnector;
return this;
}
public withApiObject(apiObject: object): this {
this.apiObject = apiObject;
return this;
}
public withContextBinder(contextBinder: MethodContextBinder): this {
this.contextBinder = contextBinder;
return this;
}
public run() {
return connectApisWithContextBridge(
this.bridgeConnector,
this.apiObject,
this.contextBinder,
);
}
}