blob: 022c692f4225d9ffad2e82a6a3e1574be76b4aff [file]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// The message type used for the embedded comms handshake. Must stay in sync
// with the parent SDK's handshake messages.
export const MESSAGE_TYPE = '__embedded_comms__';
/**
* Normalizes an allowed-domain entry to its origin (`scheme://host[:port]`),
* matching the comparison the backend performs via `flask_wtf.csrf.same_origin`.
*
* A browser-provided `event.origin` never carries a path or trailing slash, but
* configured allowed domains may (e.g. `https://example.com/` or
* `https://app.example.com/embed`). Reducing each entry to its origin keeps the
* frontend and backend in agreement about what an "allowed domain" is. Entries
* that cannot be parsed as a URL fall back to the raw value so an exact match is
* still possible.
*/
function normalizeToOrigin(domain: string): string {
try {
return new URL(domain).origin;
} catch {
return domain;
}
}
/**
* Validates the origin of an incoming postMessage event against the dashboard's
* configured allowed domains.
*
* Enforcement is opt-in by configuration: if the allowed-domains list is empty
* or undefined, any origin is accepted (no restriction), which preserves the
* historical behavior for embeds that did not configure domains. When the list
* is non-empty, only origins present in the list are accepted.
*/
export function isMessageOriginAllowed(
origin: string,
allowedDomains?: string[],
): boolean {
if (!allowedDomains || allowedDomains.length === 0) {
return true;
}
if (allowedDomains.some(domain => normalizeToOrigin(domain) === origin)) {
return true;
}
// eslint-disable-next-line no-console
console.warn(
`[superset] ignoring embedded message from origin "${origin}" which is not in the list of allowed domains`,
);
return false;
}
/**
* Validates that an incoming message is intended for embedded comms and that it
* originates from an allowed domain. Returns `true` when the message should be
* processed, `false` otherwise.
*/
export function validateMessageEvent(
event: MessageEvent,
allowedDomains?: string[],
): boolean {
if (!isMessageOriginAllowed(event.origin, allowedDomains)) {
return false;
}
if (
typeof event.data !== 'object' ||
event.data === null ||
event.data.type !== MESSAGE_TYPE
) {
return false;
}
return true;
}