Como resolver?

O ponto chave aqui é a resolução do que chamamos na matemática de permutação, ou seja, todas as distribuições possíveis de um grupo de elementos onde a sua ordem importa e sempre usamos todos os elementos. Uma forma simples e elegante de lidarmos com esse problema é através de recursão.

Resolução

Como de costume, vamos começar a pensar no caso base para a nossa função recursiva. Como esse é um problema um pouco diferente, talvez seja mais difícil pensar em um caso baso num primeiro momento, mas podemos considerar como sendo o de um array vazio, ou seja, nenhum elemento. Se temos um array vazio, o retorno será um array bidimensional vazio também, então podemos fazer desse o nosso caso base aqui.

function possiblePasswords(chars) {  if (chars.length === 0) {
    return [[]]
  }
}

Agora temos que prosseguir para obter as permutações. Para conseguir isso precisamos fazer com que a nossa função caminhe em direção ao caso base, o que no nosso caso é caminhar em direção ao array vazio. Para isso, podemos retirar o primeiro elemento do array e chamar recursivamente a nossa própria função e obter dela as possibilidades do nosso array com um elemento a menos.

function possiblePasswords(chars) {
  if (chars.length === 0) {
    return [[]]
  }

  const removedChar = chars[0]
  const partialChars = chars.slice(1)

  const partialPossibilites = possiblePasswords(partialChars)
}

Agora vamos assumir que nós já temos as permutações do array com um elemento a menos, tudo que precisamos fazer é obter um array que contém o elemento que removemos em cada uma das posições possíveis, desde o começo até o final do array. Para armazenar isso vamos criar um array para conter todas as possibilidades e iterar sobre o array que recebemos inserindo o elemento removido em cada uma das posições possíveis de cada conjunto. Como o resultado que recebemos já um array bidimensional, dentro de cada array utilizamos uma repetição para construir um novo array que contém o elemento removido e então inserir esse novo conjunto dentro do array que criamos fora do .forEach(). Feito isso, tudo que precisamos fazer é retornar o array de possibilidades, que agora deve conter todas as permutações de caracteres possíveis.

function possiblePasswords(chars) {
  if (chars.length === 0) {
    return [[]]
  }

  const removedChar = chars[0]
  const partialChars = chars.slice(1)

  const partialPossibilites = possiblePasswords(partialChars)

  const allPossibilities = []

  partialPossibilites.forEach(partialPossibility => {
    for (let i = 0; i <= partialPossibility.length; i++) {
      const completePossibility = [...partialPossibility.slice(0, i), removedChar, ...partialPossibility.slice(i)]
      allPossibilities.push(completePossibility)
    }
  })

  return allPossibilities
}