249 lines
7.3 KiB
JavaScript
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
|
|
};
|