Preprocess before form submit

I have some forms that are encrypted client side.

On them I have a hook, that decrypt a data attribute and set the input value.

Now, the issue is that I want to encrypt the data back before submission.

The problem is that I cannot run code before the live view submit event.

This is my workaround (code for hook placed on form element):

    async mounted() {
        this.submit = false
        this.el.addEventListener("submit", async (evt) => {
            if (this.submit) {
                this.submit = false
                return
            }
            evt.preventDefault()
            evt.stopPropagation()
            this.submit = true
            await this.encrypt()
            this.el.dispatchEvent(
                new Event("submit", { bubbles: true, cancelable: true }),
            )
        })
        await this.decrypt()
    },
    async updated() {
        await this.decrypt()
    },

Decrypt and encrypt read and write hidden inputs with base64 values.

Then idea is that when the user submit the form, I catch the event, cancel it, encrypt the form, then I submit it again, this time my handler does nothing.

I was wondering if there wasn’t a better way or if it was a fine approach.

How do you deal with the phx-change event handler on the form? It’s necessary for state recovery, but unless you take measures you’d end up sending unencrypted user data to the server, which you’re clearly trying to avoid.

Maybe I’d deal with it by having encrypted data stored in named hidden form fields, while all visible inputs would have no name to be ignored (or part of a different form?)

I don’t use phx-change at present, and to avoid sending unencrypted data, I do not set the name attribute on input, and the encrypt callback populate hidden inputs with encrypted data.

TBH I think I would remove all Phoenix form handling here and handle the whole submission in a JS hook yourself. You can send the data in a custom event and ensure it is properly encrypted every time without a race.

If you get too hacky with the form interception you risk things breaking on future releases, which would be bad given the security context.

https://hexdocs.pm/phoenix_live_view/form-bindings.html#recovery-following-crashes-or-disconnects

You need an id and phx-change to recover following disconnects (e.g. deployment, user temporarily loses connectivity, tab sleeps/wake up).

You can listen for changes on the visible inputs and update the corresponding hidden fields.

Having two separate forms would probably prevent races as well, no server events triggered from text input field changes.

Yeah, but removing all the phoenix form handling requires quit a lot of work to make it work nicely. I guess the best thing would be for live view to support the use case natively.

I suppose if you’re mixing the encrypted/unencrypted fields in the form then I see why you would want to keep the default submit behavior.

What about this: encrypt the content from the user field to the hidden field synchronously on each keystroke. Then you can submit the form normally and it will never be out of sync.

I was doing that (encrypt on keystroke), but it doesn’t work on mobile.

For example, if you write “hi” and your autocorrect is “waiting” (on android it is underlined), your input never has “hi” in it (input.value is empty), and ONLY on submit, android will populate the field.

Thats an even bigger reason to take phoenix form handling entirely out of the picture. Implement it fully as a hook imo.

Does this really not trigger change first? If so that’s ridiculous, but I guess there’s nothing you can do about it.

No it does not, and I still discover some mobile edge cases. That is why using phoenix built in form input and just hooking on submit solves quite a lot of edge cases that has been taken care of by the phoenix team.

1 Like