Skip to content

Commit 0d4cf33

Browse files
andypalmiandypalmi
andauthored
fix: normalise Expert inbound origins to bare origin (#7783)
## Description Fixes #7782. The Expert panel dropped every inbound `postMessage` sent by the nr-assistant editor plugin on instances that run the editor under a non-root `httpAdminRoot`, logging `Received message from unknown origin. Ignoring.` on repeat. With the messages rejected, the Expert never received `assistant-ready` and the panel did not populate. `allowedInboundOrigins` compared the incoming `MessageEvent.origin` against `instance.url` / `device.editor.url` directly. Those are full editor URLs that can carry the editor path (`httpAdminRoot`) or a trailing slash, but a `MessageEvent` origin is always a bare `scheme://host:port`, so the strict `includes()` check never matched. Example (instance with `httpAdminRoot: '/admin'`): - allow-list entry: `https://my-instance.flowfuse.cloud/admin` - `event.origin`: `https://my-instance.flowfuse.cloud` This normalises each allow-list entry to its origin via `new URL(url).origin` before comparison. Instances served at the bare origin are unchanged. ## Testing `test/unit/frontend/stores/product-assistant.spec.js` gains two regression cases: an `instance.url` carrying an editor path and one with a trailing slash both resolve to the bare origin. Full suite passes (89 tests). Co-authored-by: andypalmi <andrea@flowfuse.com>
1 parent fe7148b commit 0d4cf33

2 files changed

Lines changed: 32 additions & 6 deletions

File tree

frontend/src/stores/product-assistant.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -296,16 +296,26 @@ export const useProductAssistantStore = defineStore('product-assistant', {
296296
return state.debugLogEntries || []
297297
},
298298
allowedInboundOrigins () {
299-
const allowedOrigins = [window.origin]
299+
const allowedOrigins = new Set([window.origin])
300300
const instance = useContextStore().instance
301301
const device = useContextStore().device
302-
if (instance?.url) allowedOrigins.push(instance.url)
302+
// instance.url / device.editor.url can carry an editor path (httpAdminRoot) or a
303+
// trailing slash, but a MessageEvent's origin is always a bare scheme://host:port.
304+
// Reduce each entry to its origin so the comparison in handleMessage matches.
305+
const addOrigin = (url) => {
306+
try {
307+
allowedOrigins.add(new URL(url).origin)
308+
} catch {
309+
// not a parseable URL - ignore
310+
}
311+
}
312+
if (instance?.url) {
313+
addOrigin(instance.url)
314+
}
303315
if (device?.editor?.url) {
304-
// todo this might not be needed because it's just the path to the editor tunnel, not an actual origin
305-
// and the only origin we might receive messages is the current window origin
306-
allowedOrigins.push(device.editor.url)
316+
addOrigin(device.editor.url)
307317
}
308-
return allowedOrigins
318+
return [...allowedOrigins]
309319
},
310320
isEditorRunning: (state) => {
311321
// NOTE: this is achieved via dynamic event registration for 'flows:loaded' and 'runtime-state' events,

test/unit/frontend/stores/product-assistant.spec.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,22 @@ describe('product-assistant store', () => {
233233
contextStore.setDevice({ id: 'dev-1', editor: { url: 'http://device.local' } })
234234
expect(store.allowedInboundOrigins).toContain('http://device.local')
235235
})
236+
237+
it('reduces an instance url carrying an editor path to its bare origin', () => {
238+
const contextStore = useContextStore()
239+
const store = useProductAssistantStore()
240+
contextStore.setInstance({ id: 'inst-1', url: 'https://node-red.local/admin' })
241+
expect(store.allowedInboundOrigins).toContain('https://node-red.local')
242+
expect(store.allowedInboundOrigins).not.toContain('https://node-red.local/admin')
243+
})
244+
245+
it('reduces an instance url with a trailing slash to its bare origin', () => {
246+
const contextStore = useContextStore()
247+
const store = useProductAssistantStore()
248+
contextStore.setInstance({ id: 'inst-1', url: 'https://node-red.local/' })
249+
expect(store.allowedInboundOrigins).toContain('https://node-red.local')
250+
expect(store.allowedInboundOrigins).not.toContain('https://node-red.local/')
251+
})
236252
})
237253

238254
describe('isEditorRunning', () => {

0 commit comments

Comments
 (0)