dev: add TXT record verification for domain names

This commit is contained in:
KernelDeimos
2025-05-14 15:59:01 -04:00
committed by Neal Shah
parent df602d5edb
commit 4fadd26459
7 changed files with 174 additions and 7 deletions
+3
View File
@@ -42,6 +42,7 @@ const { KVStoreModule } = require("./src/modules/kvstore/KVStoreModule.js");
const { ExternalExtrasModule } = require("./src/modules/external-extras/ExternalExtrasModule.js");
const { FirebaseModule } = require("./src/modules/firebase/FirebaseModule.js");
const { DomainModule } = require("./src/modules/domain/DomainModule.js");
const { DNSModule } = require("./src/modules/dns/DNSModule.js");
module.exports = {
helloworld: () => {
@@ -82,6 +83,8 @@ module.exports = {
InternetModule,
CaptchaModule,
KVStoreModule,
FirebaseModule,
DNSModule,
// Development modules
PerfMonModule,
+1
View File
@@ -36,6 +36,7 @@
"convertapi": "^1.15.0",
"cookie-parser": "^1.4.6",
"dedent": "^1.5.3",
"dns2": "^2.1.0",
"express": "^4.18.2",
"file-type": "^18.5.0",
"firebase-admin": "^13.3.0",
+14
View File
@@ -0,0 +1,14 @@
const { AdvancedBase } = require("@heyputer/putility");
class DNSModule extends AdvancedBase {
async install (context) {
const services = context.get('services');
const { DNSService } = require('./DNSService');
services.registerService('dns', DNSService);
}
}
module.exports = {
DNSModule,
};
+99
View File
@@ -0,0 +1,99 @@
const BaseService = require("../../services/BaseService");
const { sleep } = require("../../util/asyncutil");
class DNSService extends BaseService {
async _init () {
const dns2 = require('dns2');
// this.dns = new dns2(this.config.client);
this.dns = new dns2({
nameServers: ['127.0.0.1'],
port: 5300,
});
if ( this.config.test_server ) {
this.test_server_();
}
}
get_client () {
return this.dns;
}
test_server_ () {
const dns2 = require('dns2');
const { Packet } = dns2
const server = dns2.createServer({
udp: true,
handle: (request, send, rinfo) => {
const { questions } = request;
const response = Packet.createResponseFromRequest(request);
for (const question of questions) {
if (question.type === Packet.TYPE.A || question.type === Packet.TYPE.ANY) {
response.answers.push({
name: question.name,
type: Packet.TYPE.A,
class: Packet.CLASS.IN,
ttl: 300,
address: '127.0.0.11',
});
}
if (question.type === Packet.TYPE.TXT || question.type === Packet.TYPE.ANY) {
response.answers.push({
name: question.name,
type: Packet.TYPE.TXT,
class: Packet.CLASS.IN,
ttl: 300,
data: [
JSON.stringify({ username: 'ed3' })
],
});
}
}
send(response);
}
});
server.on('listening', () => {
this.log.info('Fake DNS server listening', server.addresses());
if ( this.config.test_server_selftest ) (async () => {
await sleep(5000);
{
console.log('Trying first test')
const result = await this.dns.resolveA('test.local');
console.log('Test 1', result);
}
{
console.log('Trying second test')
const result = await this.dns.resolve(`_puter-verify.test.local`, 'TXT');
console.log('Test 2', result);
}
})();
});
server.on('close', () => {
console.log('Fake DNS server closed');
this.log.noticeme('Fake DNS server closed');
})
server.on('request', (request, response, rinfo) => {
console.log(request.header.id, request.questions[0]);
});
server.on('requestError', (error) => {
console.log('Client sent an invalid request', error);
});
server.listen({
udp: {
port: 5300,
address: "127.0.0.1",
},
});
}
}
module.exports = { DNSService };
@@ -2,6 +2,9 @@ const { get_user } = require("../../helpers");
const BaseService = require("../../services/BaseService");
class DomainVerificationService extends BaseService {
_init () {
this._register_commands();
}
async get_controlling_user ({ domain }) {
const svc_event = this.services.get('event');
@@ -19,6 +22,20 @@ class DomainVerificationService extends BaseService {
// controlling user.
return await get_user({ username: 'admin' });
}
_register_commands (commands) {
const svc_commands = this.services.get('commands');
svc_commands.registerCommands('domain', [
{
id: 'user',
description: '',
handler: async (args, log) => {
const res = await this.get_controlling_user({ domain: args[0] });
log.log(res);
}
}
]);
}
}
module.exports = {
@@ -1,17 +1,32 @@
const { get_user } = require("../../helpers");
const BaseService = require("../../services/BaseService");
const { atimeout } = require("../../util/asyncutil");
class TXTVerifyService extends BaseService {
async _init () {
const require = this.require;
const dns = require('dns').promises;
['__on_boot.consolidation'] () {
const svc_dns = this.services.get('dns');
const dns = svc_dns.get_client();
const svc_event = this.services.get('event');
svc_event.on('domain.get-controlling-user', async (_, event) => {
let records = await dns.resolveTxt(`_puter-verify.${event.domain}`);
records = records.flat();
console.log('got records :: ', records);
const record_name = `_puter-verify.${event.domain}`;
try {
const result = await atimeout(
5000,
dns.resolve(record_name, 'TXT'),
);
const answer = result.answers.filter(
a => a.name === record_name &&
a.type === 16
)[0];
const data_raw = answer.data;
const data = JSON.parse(data_raw);
event.user = await get_user({ username: data.username });
} catch (e) {
console.error('ERROR', e);
}
})
}
}
+18
View File
@@ -0,0 +1,18 @@
const sleep = async ms => {
await new Promise(rslv => setTimeout(rslv, ms));
}
const atimeout = async (ms, p) => {
return await Promise.race([
p,
new Promise(async (rslv, rjct) => {
await sleep(ms);
rjct("timeout");
}),
])
};
module.exports = {
sleep,
atimeout,
};