webcomponent: update CommandsManager to use targetIframeOrigin for improved security

This commit is contained in:
Carlos Santos 2025-05-22 14:11:41 +02:00
parent 738c7cb878
commit 1bdd968f79
2 changed files with 36 additions and 16 deletions

View File

@ -11,7 +11,15 @@ import { InboundCommandMessage } from '../models/message.type';
*/
export class CommandsManager {
private iframe: HTMLIFrameElement;
private allowedOrigin: string;
/**
* The origin of the iframe content that messages will be sent to.
* Used as the 'targetOrigin' parameter in postMessage calls.
* Initially set to '*' (insecure) until the actual iframe URL is loaded.
*
* SECURITY NOTE: The value '*' should be replaced with the actual origin
* as soon as possible using setTargetOrigin().
*/
private targetIframeOrigin: string;
/**
* A map to store event handlers for different events.
* This allows for dynamic event handling and can be used to add or remove event listeners.
@ -21,9 +29,15 @@ export class CommandsManager {
*/
private eventHandlers: Map<string, Set<Function>> = new Map();
constructor(iframe: HTMLIFrameElement, allowedOrigin: string) {
/**
* Creates a new CommandsManager instance
*
* @param iframe - The iframe element used for communication
* @param initialTargetOrigin - The initial target origin for postMessage
*/
constructor(iframe: HTMLIFrameElement, initialTargetOrigin: string) {
this.iframe = iframe;
this.allowedOrigin = allowedOrigin;
this.targetIframeOrigin = initialTargetOrigin;
}
/**
@ -148,16 +162,24 @@ export class CommandsManager {
// }
/**
* Sets the allowed origin for the current instance.
* Updates the target origin used when sending messages to the iframe.
* This should be called once the iframe URL is known to improve security.
*
* @param newOrigin - The new origin to be set as allowed.
* @param newOrigin - The origin of the content loaded in the iframe
* (e.g. 'https://meet.example.com')
*/
public setAllowedOrigin(newOrigin: string): void {
this.allowedOrigin = newOrigin;
public setTargetOrigin(newOrigin: string): void {
this.targetIframeOrigin = newOrigin;
}
private sendMessage(message: InboundCommandMessage, targetOrigin?: string): void {
targetOrigin = targetOrigin || this.allowedOrigin;
this.iframe.contentWindow?.postMessage(message, targetOrigin);
/**
* Sends a message to the iframe using window.postMessage
*
* @param message - The message to send to the iframe
* @param explicitTargetOrigin - Optional override for the target origin
*/
private sendMessage(message: InboundCommandMessage, explicitTargetOrigin?: string): void {
explicitTargetOrigin = explicitTargetOrigin || this.targetIframeOrigin;
this.iframe.contentWindow?.postMessage(message, explicitTargetOrigin);
}
}

View File

@ -1,5 +1,3 @@
import { WebComponentCommand } from '../models/command.model';
import { InboundCommandMessage } from '../models/message.type';
import { CommandsManager } from './CommandsManager';
import { EventsManager } from './EventsManager';
import styles from '../assets/css/styles.css';
@ -30,7 +28,7 @@ export class OpenViduMeet extends HTMLElement {
private commandsManager: CommandsManager;
private eventsManager: EventsManager;
//!FIXME: Insecure by default
private allowedOrigin: string = '*';
private targetIframeOrigin: string = '*';
private loadTimeout: any;
private iframeLoaded = false;
private errorMessage: string | null = null;
@ -44,7 +42,7 @@ export class OpenViduMeet extends HTMLElement {
'camera; microphone; display-capture; fullscreen; autoplay; compute-pressure;'
);
this.commandsManager = new CommandsManager(this.iframe, this.allowedOrigin);
this.commandsManager = new CommandsManager(this.iframe, this.targetIframeOrigin);
this.eventsManager = new EventsManager(this);
// Listen for changes in attributes to update the iframe src
@ -140,8 +138,8 @@ export class OpenViduMeet extends HTMLElement {
}
const url = new URL(baseUrl);
this.allowedOrigin = url.origin;
this.commandsManager.setAllowedOrigin(this.allowedOrigin);
this.targetIframeOrigin = url.origin;
this.commandsManager.setTargetOrigin(this.targetIframeOrigin);
// Update query params
Array.from(this.attributes).forEach((attr) => {