Skip to content

Commit

Permalink
Fix #769 -- Use native form submission in Django admin (#770)
Browse files Browse the repository at this point in the history
The fetch-based integration ignored the redirect provided by the
backend. This may prohibit user input sanitation. It's not severe but
unexpected. This patch amends a form element to the body to utilize the
native form submission.
  • Loading branch information
codingjoe authored Dec 21, 2024
1 parent 7fb00b0 commit dbf47d3
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 26 deletions.
42 changes: 28 additions & 14 deletions hijack/static/hijack/hijack.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,43 @@ export function ready (fn, context) {
*/
export function mount (fn, query) {
ready(() => {
document.querySelectorAll(query).forEach(element => fn(element))
document.querySelectorAll(query).forEach((element) => fn(element))
})
}

/**
* Hijack user session.
* @param {Event} event - Click event.
* @return {Promise<void>}
* @returns {void}
*/
export async function hijack (event) {
export function hijack (event) {
const element = event.currentTarget
const form = new FormData()
form.append('csrfmiddlewaretoken', document.querySelector('input[name=csrfmiddlewaretoken]').value)
form.append('user_pk', element.dataset.hijackUser)
const form = document.createElement('form')
form.method = 'POST'
form.style.display = 'none'
form.action = element.dataset.hijackUrl
const csrfTokenInput = document.createElement('input')
csrfTokenInput.type = 'hidden'
csrfTokenInput.name = 'csrfmiddlewaretoken'
csrfTokenInput.value =
document.querySelector('input[name=csrfmiddlewaretoken]').value
form.appendChild(csrfTokenInput)
const userPkInput = document.createElement('input')
userPkInput.type = 'hidden'
userPkInput.name = 'user_pk'
userPkInput.value = element.dataset.hijackUser
form.appendChild(userPkInput)
if (element.dataset.hijackNext) {
form.append('next', element.dataset.hijackNext)
const nextInput = document.createElement('input')
nextInput.type = 'hidden'
nextInput.name = 'next'
nextInput.value = element.dataset.hijackNext
form.appendChild(nextInput)
}
await fetch(element.dataset.hijackUrl, {
method: 'POST',
body: form,
credentials: 'same-origin'
})
globalThis.location.href = element.dataset.hijackNext
document.body.appendChild(form)
form.submit()
}

mount(function (element) { element.addEventListener('click', hijack) }, '[data-hijack-user]')
mount(function (element) {
element.addEventListener('click', hijack)
}, '[data-hijack-user]')
18 changes: 6 additions & 12 deletions tests/__tests__/hijack.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe('mount', () => {
})

describe('hijack', () => {
test('hijack', async () => {
test('hijack', () => {
const event = {
currentTarget: {
dataset: {
Expand All @@ -73,16 +73,10 @@ describe('hijack', () => {
}
}
}
document.body.innerHTML =
'<input name="csrfmiddlewaretoken" value="token">'
globalThis.fetch = mock.fn()
await hijack.hijack(event)
const call = globalThis.fetch.mock.calls[0]
assert(call.arguments[0] === '/hijack/')
assert(call.arguments[1].method === 'POST')
assert(call.arguments[1].credentials === 'same-origin')
assert(call.arguments[1].body.get('csrfmiddlewaretoken') === 'token')
assert(call.arguments[1].body.get('user_pk') === '1')
assert(call.arguments[1].body.get('next') === '/')
document.body.innerHTML = '<input name="csrfmiddlewaretoken" value="token">'
hijack.hijack(event)
assert.equal(
document.body.innerHTML,
'<input name="csrfmiddlewaretoken" value="token"><form method="POST" style="display: none;" action="/hijack/"><input type="hidden" name="csrfmiddlewaretoken" value="token"><input type="hidden" name="user_pk" value="1"><input type="hidden" name="next" value="/"></form>')
})
})

0 comments on commit dbf47d3

Please sign in to comment.