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
@@ -73,7 +73,7 @@ function createContext(): PeerConnectionManagerContext {
} as unknown as PeerConnectionManagerContext['logger'],
callbacks: {
getIceServers: vi.fn(() => []),
getIdentifyCredentials: vi.fn(() => ({ oderId: 'local-user', displayName: 'Local User' })),
getIdentifyCredentials: vi.fn(() => ({ oderId: 'local-user', token: 'session-token', displayName: 'Local User' })),
getLocalMediaStream: vi.fn(() => null),
getLocalPeerId: vi.fn(() => 'local-peer'),
getVoiceStateSnapshot: vi.fn(() => ({
@@ -133,7 +133,7 @@ function createContext(localOderId: string): PeerConnectionManagerContext {
} as unknown as PeerConnectionManagerContext['logger'],
callbacks: {
getIceServers: vi.fn(() => []),
getIdentifyCredentials: vi.fn(() => ({ oderId: localOderId, displayName: localOderId })),
getIdentifyCredentials: vi.fn(() => ({ oderId: localOderId, token: 'session-token', displayName: localOderId })),
getLocalMediaStream: vi.fn(() => null),
getLocalPeerId: vi.fn(() => localOderId),
getVoiceStateSnapshot: vi.fn(() => ({
@@ -40,6 +40,7 @@ import { ServerSignalingCoordinator } from './signaling/server-signaling-coordin
import { SignalingManager } from './signaling/signaling.manager';
import { SignalingTransportHandler } from './signaling/signaling-transport-handler';
import { WebRtcStateController } from './state/webrtc-state-controller';
import { AuthTokenStoreService } from '../../domains/authentication';
@Injectable({
providedIn: 'root'
@@ -49,6 +50,7 @@ export class WebRTCService implements OnDestroy {
private readonly debugging = inject(DebuggingService);
private readonly screenShareSourcePicker = inject(ScreenShareSourcePickerService);
private readonly iceServerSettings = inject(IceServerSettingsService);
private readonly authTokenStore = inject(AuthTokenStoreService);
private readonly logger = new WebRTCLogger(() => this.debugging.enabled());
private readonly state = new WebRtcStateController();
@@ -144,7 +146,22 @@ export class WebRTCService implements OnDestroy {
this.signalingTransportHandler = new SignalingTransportHandler({
signalingCoordinator: this.signalingCoordinator,
logger: this.logger,
getLocalPeerId: () => this.state.getLocalPeerId()
getLocalPeerId: () => this.state.getLocalPeerId(),
resolveSessionToken: (signalUrl) => {
if (signalUrl) {
return this.authTokenStore.getToken(signalUrl.replace(/^ws/, 'http'));
}
for (const { signalUrl: connectedUrl } of this.signalingCoordinator.getConnectedSignalingManagers()) {
const token = this.authTokenStore.getToken(connectedUrl.replace(/^ws/, 'http'));
if (token) {
return token;
}
}
return null;
}
});
// Now wire up cross-references (all managers are instantiated)
@@ -85,6 +85,8 @@ export const SIGNALING_TYPE_SERVER_USERS = 'server_users';
export const SIGNALING_TYPE_USER_JOINED = 'user_joined';
export const SIGNALING_TYPE_USER_LEFT = 'user_left';
export const SIGNALING_TYPE_ACCESS_DENIED = 'access_denied';
export const SIGNALING_TYPE_AUTH_REQUIRED = 'auth_required';
export const SIGNALING_TYPE_AUTH_ERROR = 'auth_error';
export const SIGNALING_TYPE_KEEPALIVE = 'keepalive';
export const SIGNALING_TYPE_KEEPALIVE_ACK = 'keepalive_ack';
@@ -34,6 +34,8 @@ export interface PeerData {
export interface IdentifyCredentials {
/** The user's unique order / peer identifier. */
oderId: string;
/** Session token proving identity to the signaling server. */
token: string;
/** The user's display name shown to other peers. */
displayName: string;
/** Optional profile description advertised via signaling identity. */
@@ -13,6 +13,7 @@ interface SignalingTransportHandlerDependencies<TMessage> {
signalingCoordinator: ServerSignalingCoordinator<TMessage>;
logger: WebRTCLogger;
getLocalPeerId(): string;
resolveSessionToken(signalUrl?: string): string | null;
}
export class SignalingTransportHandler<TMessage> {
@@ -193,9 +194,16 @@ export class SignalingTransportHandler<TMessage> {
const normalizedHomeSignalServerUrl = typeof profile?.homeSignalServerUrl === 'string'
? (profile.homeSignalServerUrl.trim().replace(/\/+$/, '') || undefined)
: undefined;
const token = this.dependencies.resolveSessionToken(signalUrl);
if (!token) {
this.dependencies.logger.warn('Skipping identify because no session token is available', { signalUrl, oderId });
return;
}
this.lastIdentifyCredentials = {
oderId,
token,
displayName: normalizedDisplayName,
description: normalizedDescription,
profileUpdatedAt: normalizedProfileUpdatedAt,
@@ -205,6 +213,7 @@ export class SignalingTransportHandler<TMessage> {
if (signalUrl) {
this.sendRawMessageToSignalUrl(signalUrl, {
type: SIGNALING_TYPE_IDENTIFY,
token,
oderId,
displayName: normalizedDisplayName,
description: normalizedDescription,
@@ -225,6 +234,7 @@ export class SignalingTransportHandler<TMessage> {
for (const { signalUrl: managerSignalUrl, manager } of connectedManagers) {
manager.sendRawMessage({
type: SIGNALING_TYPE_IDENTIFY,
token,
oderId,
displayName: normalizedDisplayName,
description: normalizedDescription,
@@ -110,6 +110,7 @@ describe('SignalingManager reconnection', () => {
homeSignalServerUrl?: string;
} = {
oderId: 'peer-a',
token: 'session-token',
displayName: 'Peer A',
description: 'hello',
profileUpdatedAt: 42,
@@ -221,6 +222,7 @@ describe('SignalingManager reconnection', () => {
expect(identifyMessage).toMatchObject({
type: 'identify',
token: 'session-token',
oderId: 'peer-a',
displayName: 'Peer A',
description: 'hello',
@@ -274,7 +276,7 @@ describe('SignalingManager reconnection', () => {
const errorSpy = vi.spyOn(logger, 'error');
const manager = new SignalingManager(
logger,
() => ({ oderId: 'peer-a', displayName: 'Peer A' }),
() => ({ oderId: 'peer-a', token: 'session-token', displayName: 'Peer A' }),
() => ({ serverId: 'server-1', userId: 'peer-a' }),
() => new Set(['server-1'])
);
@@ -373,6 +373,7 @@ export class SignalingManager {
if (credentials) {
this.sendRawMessage({
type: SIGNALING_TYPE_IDENTIFY,
token: credentials.token,
oderId: credentials.oderId,
displayName: credentials.displayName,
description: credentials.description,