Проверка, владеет ли пользователь доменом с помощью JavaScript

Мы используем тот же метод, который используется Google, Microsoft и другими для проверки ваших полномочий в отношении домена. Так что, хотя это и небезопасно, по крайней мере, мы в хорошей компании!

Код в этой статье - TypeScript, но тот же метод будет работать на большинстве языков.

Обзор

Все методы проверки, которые я видел для них, полагаются на то, что пользователь может каким-либо образом изменить сайт - что имеет смысл, поскольку вы проверяете, контролируют ли они сайт, который они пытаются использовать.

Большинство из них, похоже, решили использовать какую-либо форму записи DNS - особую запись, которую они могут проверить, действительно существует.

Краткое введение в DNS

Это очень кратко; для (немного) более полного введения в DNS см. Другой мой пост.

Система доменных имен состоит из записей, предоставляющих информацию компьютерам, имеющим доступ в Интернет. Есть довольно много разных типов записей. Самый простой из них называется записью A, а для адреса - A. По сути, он говорит: «этот текст - foobar.example.com - указывает на этот IP-адрес».

Есть ряд зарезервированных адресов, которые имеют определенное значение. Один из полезных адресов - 127.0.0.1, что всегда означает «этот компьютер». Его символическое имя localhost.

План

Мы хотим проверить, может ли пользователь изменять записи DNS для этого домена, но без каких-либо серьезных проблем или сложностей - чем сложнее мы это сделаем, тем больше вероятность того, что пользовательская ошибка закрадется.

Самый простой способ - сгенерировать случайный поддомен и попросить их создать запись A, указывающую на 127.0.0.1.

Создание псевдонима

Есть много разных способов сделать это. Я решил использовать модуль узла uuid и взять первые 8 символов. 8 был выбран потому, что он был достаточно случайным для наших целей, и потому что это был первый кусок в UUID v4.

siteDetails["alias"] = uuid().substr(0, 8);

Проверка псевдонима

Используя узел dns module, мы можем разрешить созданный нами псевдоним; мы добавляем после него домен пользователя, делая alias субдоменом.

Простые dns методы основаны на обратном вызове; он также предоставляет dnsPromises набор API, основанных на Promise. Для удобства воспользуемся методом разрешения.

import dns from "dns";
const dnsPromises = dns.promises;

type Site = {
  alias: string;        // Alias we'll be verifying
  domain: string;       // Domain the user gave us
  verified: boolean;    // Is it verified yet
}

async function verifySite(site: Site) {
  try {
    const res = await dnsPromises.resolve(site.alias + "." + site.domain);
    const valid = ((res.length == 1) && (res[0] == "127.0.0.1"));
    site.verified = valid;
  } catch (err) {
    console.error(`Error ${err} doing site ${site.id} verification`);
  }
}

Мы ожидаем, что результатом поиска будет одна запись, 127.0.0.1 - если это так, то мы называем ее проверенной. Наконец, мы убеждаемся, что данные отражают то, что мы только что нашли.

Выполнение проверок в фоновом режиме

Теперь у нас есть функция, которую мы можем использовать для проверки доменов. Последний этап - периодическая работа в фоновом режиме, а не по требованию.

Реализация, которую я использовал, представлена ​​ниже. Я не включил служебные функции (например, getAllSites), но код все равно должен быть понятен без них.

startBackground использует DOMAIN_VERIFY_PERIOD_SECONDS из среды, если она определена, или 300 секунд, если нет. Затем он использует setInterval для планирования verifySites. setInterval принимает в качестве аргумента миллисекунды, поэтому сначала мы его преобразуем.

verifySites просто получает текущий список сайтов и запускает verifySite на всех из них.

Наконец, stopBackground отменит функцию интервала, если она запланирована для запуска.

import { getAllSites } from "./queries";

let domainCheckId: NodeJS.Timeout | null = null;

export async function verifySites() {
  const sites: Site[] = await getAllSites();
  sites.forEach(site => verifySite(site));
}

export function startBackground(): void {
  const SECOND = 1000;
  const period: number = parseInt(process.env.DOMAIN_VERIFY_PERIOD_SECONDS || "300");
  console.log(`Starting domainCheck, period ${period} seconds`);

  domainCheckId = setInterval(verifySites, SECOND * period);
}

export function stopBackground(): void {
  if (domainCheckId) {
    clearInterval(domainCheckId);
    domainCheckId = null;
  }
}

И все - этих функций достаточно, чтобы начать проверку доменов в фоновом режиме. Сообщите мне, если вы воспользуетесь им!