import { createHash, timingSafeEqual } from "node:crypto";

/**
 * A prova de quem pediu o código (PKCE).
 *
 * Quem começa a conexão sorteia um segredo (o verificador) e manda só o resumo
 * dele (o desafio) na ida. Na hora de trocar o código pelo acesso, mostra o
 * segredo inteiro. Quem interceptou o código no meio do caminho não tem o
 * segredo, e o código não serve para nada na mão dele.
 *
 * Só o método S256 é aceito. O método "plain" manda o próprio segredo na ida,
 * e não protege de quem lê a ida.
 */

const VERIFICADOR = /^[A-Za-z0-9\-._~]{43,128}$/;
/* O resumo SHA-256 em base64url sem enchimento tem sempre 43 caracteres. */
const DESAFIO = /^[A-Za-z0-9_-]{43}$/;

export function verificadorValido(valor: unknown): valor is string {
  return typeof valor === "string" && VERIFICADOR.test(valor);
}

export function desafioValido(valor: unknown): valor is string {
  return typeof valor === "string" && DESAFIO.test(valor);
}

export function desafioDoVerificador(verificador: string) {
  return createHash("sha256").update(verificador, "ascii").digest("base64url");
}

export function pkceConfere(verificador: unknown, desafio: string) {
  if (!verificadorValido(verificador) || !desafioValido(desafio)) return false;
  const calculado = Buffer.from(desafioDoVerificador(verificador));
  const guardado = Buffer.from(desafio);
  return calculado.length === guardado.length && timingSafeEqual(calculado, guardado);
}
