feat: Security

This commit is contained in:
2026-06-05 18:34:01 +02:00
parent ee293d7daf
commit 45675192a5
134 changed files with 4128 additions and 446 deletions

View File

@@ -30,6 +30,8 @@ import {
type PluginClientApiMethodPath
} from '../../domain/logic/plugin-client-api-surface.rules';
import { PluginCapabilityError, PluginCapabilityService } from './plugin-capability.service';
import { MessageRevisionService } from '../../../chat/application/services/message-revision.service';
import { MessageSigningService } from '../../../authentication/application/services/message-signing.service';
import { PluginClientApiService } from './plugin-client-api.service';
import { PluginDesktopStateService } from './plugin-desktop-state.service';
import { PluginLoggerService } from './plugin-logger.service';
@@ -106,17 +108,31 @@ describe('PluginClientApiService', () => {
}));
});
it('sends plugin messages and broadcasts them to peers', () => {
it('sends plugin messages and broadcasts them to peers', async () => {
const api = context.service.createApi(TEST_MANIFEST);
const message = api.messages.send('hello plugin');
expect(message.content).toBe('hello plugin');
expect(message.roomId).toBe('room-1');
expect(context.store.dispatch).toHaveBeenCalledWith(MessagesActions.sendMessageSuccess({ message }));
await new Promise((resolve) => setTimeout(resolve, 0));
expect(context.store.dispatch).toHaveBeenCalledWith(
MessagesActions.sendMessageSuccess({
message: expect.objectContaining({
content: 'hello plugin',
roomId: 'room-1'
})
})
);
expect(context.voice.broadcastMessage).toHaveBeenCalledWith(expect.objectContaining({
type: 'chat-message',
message
message: expect.objectContaining({
content: 'hello plugin',
roomId: 'room-1'
})
}));
expect(context.messageRevisions.broadcastRevision).toHaveBeenCalled();
});
it('publishes typing state through the realtime facade', () => {
@@ -264,6 +280,11 @@ interface ServiceTestContext {
setLocalStream: ReturnType<typeof vi.fn>;
setOutputVolume: ReturnType<typeof vi.fn>;
};
messageRevisions: {
broadcastRevision: ReturnType<typeof vi.fn>;
createSignedRevision: ReturnType<typeof vi.fn>;
persistRevision: ReturnType<typeof vi.fn>;
};
}
function createServiceTestContext(): ServiceTestContext {
@@ -305,6 +326,28 @@ function createServiceTestContext(): ServiceTestContext {
setLocalStream: vi.fn(async () => undefined),
setOutputVolume: vi.fn()
};
const messageRevisions = {
createSignedRevision: vi.fn(async (input: Parameters<MessageRevisionService['createSignedRevision']>[0]) => ({
messageId: input.message.id,
revision: input.type === 'create' ? 0 : (input.message.revision ?? 0) + 1,
prevRevisionHash: input.type === 'create' ? '' : (input.message.headHash ?? ''),
headHash: 'test-head-hash',
type: input.type,
actorId: input.actorId,
senderId: input.message.senderId,
roomId: input.message.roomId,
channelId: input.message.channelId,
senderName: input.message.senderName,
content: input.content ?? input.message.content,
editedAt: input.editedAt,
isDeleted: input.isDeleted ?? false,
replyToId: input.message.replyToId,
pluginId: input.pluginId,
signature: input.sign === false ? undefined : 'test-signature'
})),
persistRevision: vi.fn(async () => undefined),
broadcastRevision: vi.fn()
};
const realtime = {
onSignalingMessage: new Subject<unknown>(),
sendRawMessage: vi.fn()
@@ -357,11 +400,24 @@ function createServiceTestContext(): ServiceTestContext {
{
provide: DatabaseService,
useValue: {
getMessageById: vi.fn(async () => null),
saveMessage: vi.fn(async () => undefined),
updateMessage: vi.fn(async () => undefined),
updateRoom: vi.fn(async () => undefined)
}
},
{
provide: MessageRevisionService,
useValue: messageRevisions
},
{
provide: MessageSigningService,
useValue: {
signRevision: vi.fn(async () => 'test-signature'),
fetchSigningPublicKey: vi.fn(async () => null),
verifyRevisionSignature: vi.fn(async () => true)
}
},
{
provide: PluginDesktopStateService,
useValue: {
@@ -426,7 +482,8 @@ function createServiceTestContext(): ServiceTestContext {
storage,
store,
uiRegistry,
voice
voice,
messageRevisions
};
}