Проверка, владеет ли пользователь доменом с помощью 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;
}
}
И все - этих функций достаточно, чтобы начать проверку доменов в фоновом режиме. Сообщите мне, если вы воспользуетесь им!