feat: Security
This commit is contained in:
+1
-1
@@ -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(() => ({
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user