2

Buefy's Dialog component expects a message prop - string. According to a documentation, that string can contain HTML. I would like to use template values in the string, but of course is should be XSS safe.

Current unsafe example

This is unsafe, as this.name is unsafe. I could use a NPM package to html encode the name, but I really prefer to use Vue.

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  props: {
    name: { type: String, required: false },
  },
  methods: {
    showModal() {
      this.$buefy.dialog.confirm({
        title: 'myTitle',
        message: `<p>Hello ${this.name}</p>`, // unsafe - possible XSS!
        cancelText: 'Cancel',
        confirmText: 'OK',
        type: 'is-success',
        onConfirm: async () => {
          // something
        },
      });
    },
  },
});
</script>

This is an issue of the used Buefy component, as documented here:

enter image description here

Desired Setup

I've created a new component, in this example I call it ModalMessage.Vue

<template>
    <p>Hello {{name}}</p>
</template>

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  props: {
    name: { type: String, required: true },
  },
});
</script>

Then I like to render the ModalMessage.Vue to a string in Typescript:

<script lang="ts">
import Vue from 'vue';
import ModalMessage from 'ModalMessage.vue';

export default Vue.extend({
  props: {
    name: { type: String, required: false },
  },
  methods: {
    showModal() {
      this.$buefy.dialog.confirm({
        title: 'myTitle',
        message:, // todo render ModalMessage and pass name prop
        cancelText: 'Cancel',
        confirmText: 'OK',
        type: 'is-success',
        onConfirm: async () => {
          // something
        },
      });
    },
  },
});
</script>

Question

How could I render the ModalMessage.Vue, and passing the name prop, to a string?

I'm pretty sure this is possible - I have seen it in the past. Unfortunately I cannot find it on the web or StackOverflow. I could only find questions with rendering a template from string, but I don't need that - it needs to be to string.

Michal Levý
  • 33,064
  • 4
  • 68
  • 86
Julian
  • 33,915
  • 22
  • 119
  • 174

2 Answers2

2

Imho your real question is "How to use Buefy's Dialog component with user provided content and be safe in terms of XSS"

So what you want is to create some HTML, include some user provided content (this.name) within that HTML content and display it in a Dialog. You are right that putting unfiltered user input into a message parameter of Dialog is not safe (as clearly noted in Buefy docs)

But your "Desired Setup" seems unnecessary complicated. Imho the easiest way is to use (poorly documented) fact that message parameter of Buefy Dialog configuration objects can be an Array of VNode's instead of a string. It is poorly documented but it is very clear from the source here and here that if you pass an array of VNode's, Buefy puts that content into Dialogs default slot instead of rendering it using v-html (which is the dangerous part)

And easiest way to get Array of VNode in Vue is to use slots...

So the component:

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  methods: {
    showModal() {
      this.$buefy.dialog.confirm({
        title: 'myTitle',
        message: this.$slots.default, // <- this is important part
        cancelText: 'Cancel',
        confirmText: 'OK',
        type: 'is-success',
        onConfirm: async () => {
          // something
        },
      });
    },
  },
});
</script>

and it's usage:

<MyDialog>
  <p>Hello {{name}}</p>
</MyDialog>

or

<MyDialog>
  <ModalMessage :name="name" />
</MyDialog>

In both cases if the name contains any HTML, it will be encoded by Vue

Here is a simple demo of the technique described above (using plain JS - not TS)

tony19
  • 125,647
  • 18
  • 229
  • 307
Michal Levý
  • 33,064
  • 4
  • 68
  • 86
  • Cool! I think is was searching for this. Nevertheless, I'm sure (but indeed over complicated) that we could get the html string of a component. But maybe that should be in another stackoverflow question? Anyway will test this and mark as answer afterwards :) – Julian Feb 24 '21 at 14:21
  • Yes, you can render the component into a string - already [linked](https://stackoverflow.com/a/64285043/381282) that. But it's really not necessary in this case... – Michal Levý Feb 24 '21 at 14:23
0

Try this.

<script lang="ts">
import Vue from 'vue';
import ModalMessage from 'ModalMessage.vue';

export default Vue.extend({
  props: {
    name: { type: String, required: false },
  }, 
  methods: {
    showModal() {
      const message = new Vue({
        components: { ModalMessage },
        render: h => h('ModalMessage', { name: this.name })
      })
      message.$mount()

      const dialog = this.$buefy.dialog.confirm({
        title: 'myTitle',
        message: [message._vnode],
        cancelText: 'Cancel',
        confirmText: 'OK',
        type: 'is-success',
        onConfirm: async () => {
          // something
        },
      });

      dialog.$on('hook:beforeDestroy', () => {
          message.$destroy()
      });
    },
  },
});
</script>

Source code:

enter image description here

enter image description here

Demo:

enter image description here