AeThex-Connect/integration-package/utils/domainVerification.js

249 lines
7.3 KiB
JavaScript

const crypto = require('crypto');
const dns = require('dns').promises;
const { ethers } = require('ethers');
const db = require('../database/db');
/**
* Generates a unique verification token for domain ownership
* @param {string} userId - User's UUID
* @param {string} domain - Domain to verify (e.g., dev.aethex.dev)
* @returns {Promise<Object>} Verification instructions
*/
async function generateDomainVerificationToken(userId, domain) {
// Generate random 32-byte token
const token = crypto.randomBytes(32).toString('hex');
// Store in database
await db.query(
`INSERT INTO domain_verifications
(user_id, domain, verification_token, verification_type)
VALUES ($1, $2, $3, 'dns')
ON CONFLICT (user_id, domain)
DO UPDATE SET verification_token = $3, created_at = NOW(), expires_at = NOW() + INTERVAL '7 days', verified = false`,
[userId, domain, token]
);
return {
domain: domain,
recordType: 'TXT',
recordName: '_aethex-verify',
recordValue: `aethex-verification=${token}`,
fullRecord: `_aethex-verify.${domain} TXT "aethex-verification=${token}"`,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
};
}
/**
* Verifies domain ownership by checking DNS TXT records
* @param {string} userId - User's UUID
* @param {string} domain - Domain to verify
* @returns {Promise<Object>} Verification result
*/
async function verifyDomainOwnership(userId, domain) {
try {
// Get stored verification token
const result = await db.query(
'SELECT verification_token, expires_at FROM domain_verifications WHERE user_id = $1 AND domain = $2',
[userId, domain]
);
if (result.rows.length === 0) {
return { verified: false, error: 'No verification request found' };
}
const { verification_token: expectedToken, expires_at: expiresAt } = result.rows[0];
// Check if verification request has expired
if (new Date() > new Date(expiresAt)) {
return { verified: false, error: 'Verification request has expired. Please request a new verification.' };
}
// Query DNS for TXT records
const txtRecords = await dns.resolveTxt(`_aethex-verify.${domain}`);
// Flatten nested arrays that DNS returns
const flatRecords = txtRecords.flat();
// Check if our token exists
const verified = flatRecords.some(record =>
record.includes(`aethex-verification=${expectedToken}`)
);
if (verified) {
// Update database
await db.query(
`UPDATE domain_verifications
SET verified = true, verified_at = NOW()
WHERE user_id = $1 AND domain = $2`,
[userId, domain]
);
await db.query(
`UPDATE users
SET verified_domain = $2, domain_verified_at = NOW()
WHERE id = $1`,
[userId, domain]
);
return {
verified: true,
domain: domain,
verifiedAt: new Date()
};
}
return {
verified: false,
error: 'Verification token not found in DNS records. Please ensure the TXT record has been added and DNS has propagated (5-10 minutes).'
};
} catch (error) {
// DNS lookup failed (domain doesn't exist, no TXT records, etc.)
if (error.code === 'ENOTFOUND' || error.code === 'ENODATA') {
return {
verified: false,
error: 'DNS lookup failed: TXT record not found. Please ensure you\'ve added the record and wait 5-10 minutes for DNS propagation.'
};
}
return {
verified: false,
error: `DNS lookup failed: ${error.message}`
};
}
}
/**
* Verifies .aethex domain ownership via blockchain
* @param {string} userId - User's UUID
* @param {string} domain - Domain to verify (e.g., anderson.aethex)
* @param {string} walletAddress - User's connected wallet address
* @returns {Promise<Object>} Verification result
*/
async function verifyAethexDomain(userId, domain, walletAddress) {
try {
// Connect to blockchain (Polygon/Ethereum)
const provider = new ethers.JsonRpcProvider(process.env.RPC_ENDPOINT);
// Freename contract ABI (simplified - you'll need the actual ABI)
// This is a minimal ERC721-like interface
const FREENAME_ABI = [
"function ownerOf(string memory domain) view returns (address)",
"function getDomainOwner(string memory domain) view returns (address)"
];
const contract = new ethers.Contract(
process.env.FREENAME_REGISTRY_ADDRESS,
FREENAME_ABI,
provider
);
// Query who owns this .aethex domain
// Try both common method names
let owner;
try {
owner = await contract.ownerOf(domain);
} catch (e) {
owner = await contract.getDomainOwner(domain);
}
// Check if connected wallet owns the domain
const verified = owner.toLowerCase() === walletAddress.toLowerCase();
if (verified) {
// Store verification
await db.query(
`INSERT INTO domain_verifications
(user_id, domain, verification_token, verification_type, verified, verified_at)
VALUES ($1, $2, $3, 'blockchain', true, NOW())
ON CONFLICT (user_id, domain)
DO UPDATE SET verified = true, verified_at = NOW()`,
[userId, domain, walletAddress]
);
await db.query(
`UPDATE users
SET verified_domain = $2, domain_verified_at = NOW()
WHERE id = $1`,
[userId, domain]
);
return {
verified: true,
domain: domain,
verifiedAt: new Date()
};
}
return {
verified: false,
error: 'Wallet does not own this domain. The domain is owned by a different address.'
};
} catch (error) {
return {
verified: false,
error: `Blockchain verification failed: ${error.message}`
};
}
}
/**
* Get verification status for a user
* @param {string} userId - User's UUID
* @returns {Promise<Object>} Verification status
*/
async function getVerificationStatus(userId) {
const result = await db.query(
`SELECT verified_domain, domain_verified_at FROM users WHERE id = $1`,
[userId]
);
if (result.rows.length === 0) {
return { hasVerifiedDomain: false };
}
const { verified_domain, domain_verified_at } = result.rows[0];
if (!verified_domain) {
return { hasVerifiedDomain: false };
}
// Get verification type
const verificationInfo = await db.query(
`SELECT verification_type FROM domain_verifications
WHERE user_id = $1 AND domain = $2 AND verified = true
ORDER BY verified_at DESC LIMIT 1`,
[userId, verified_domain]
);
return {
hasVerifiedDomain: true,
domain: verified_domain,
verifiedAt: domain_verified_at,
verificationType: verificationInfo.rows[0]?.verification_type || 'dns'
};
}
/**
* Clean up expired verification requests
* Should be run periodically (e.g., daily cron job)
*/
async function cleanupExpiredVerifications() {
const result = await db.query(
`DELETE FROM domain_verifications
WHERE verified = false AND expires_at < NOW()
RETURNING id`
);
console.log(`Cleaned up ${result.rowCount} expired verification requests`);
return result.rowCount;
}
module.exports = {
generateDomainVerificationToken,
verifyDomainOwnership,
verifyAethexDomain,
getVerificationStatus,
cleanupExpiredVerifications
};