mirror of
https://github.com/freedbygrace/DynamoDNS.git
synced 2026-08-31 20:58:05 +00:00
Add support for GoDaddy and Route53 DNS providers
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/47dbe4b1-66ca-4922-abc0-13fa0ab58ee0.jpg
This commit is contained in:
@@ -0,0 +1,213 @@
|
|||||||
|
import { DnsRecord, Provider } from '@shared/schema';
|
||||||
|
import { DNSProvider } from './index';
|
||||||
|
|
||||||
|
interface CloudflareCredentials {
|
||||||
|
apiToken?: string;
|
||||||
|
apiKey?: string;
|
||||||
|
email?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CloudflareRecord {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
content: string;
|
||||||
|
ttl: number;
|
||||||
|
proxied?: boolean;
|
||||||
|
priority?: number;
|
||||||
|
comment?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CloudflareProvider implements DNSProvider {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
private credentials: CloudflareCredentials | null = null;
|
||||||
|
private baseUrl = 'https://api.cloudflare.com/client/v4';
|
||||||
|
private headers: HeadersInit = {};
|
||||||
|
|
||||||
|
constructor(provider: Provider) {
|
||||||
|
this.name = provider.name;
|
||||||
|
this.type = provider.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize(credentials: CloudflareCredentials): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
this.credentials = credentials;
|
||||||
|
|
||||||
|
// Set up headers based on authentication method
|
||||||
|
if (credentials.apiToken) {
|
||||||
|
this.headers = {
|
||||||
|
'Authorization': `Bearer ${credentials.apiToken}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
} else if (credentials.apiKey && credentials.email) {
|
||||||
|
this.headers = {
|
||||||
|
'X-Auth-Key': credentials.apiKey,
|
||||||
|
'X-Auth-Email': credentials.email,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.error('Missing required Cloudflare credentials');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify credentials with a simple API call
|
||||||
|
const response = await fetch(`${this.baseUrl}/user/tokens/verify`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: this.headers
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.success === true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing Cloudflare provider:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getZones(): Promise<any[]> {
|
||||||
|
try {
|
||||||
|
if (!this.headers) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${this.baseUrl}/zones`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: this.headers
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error(`Failed to get zones: ${data.errors[0]?.message || 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.result || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting Cloudflare zones:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getZoneIdByName(domainName: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const zones = await this.getZones();
|
||||||
|
const zone = zones.find(z => z.name === domainName);
|
||||||
|
return zone ? zone.id : null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error getting zone ID for domain ${domainName}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRecords(zoneId: string): Promise<any[]> {
|
||||||
|
try {
|
||||||
|
if (!this.headers) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${this.baseUrl}/zones/${zoneId}/dns_records`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: this.headers
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error(`Failed to get records: ${data.errors[0]?.message || 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.result || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error getting Cloudflare records for zone ${zoneId}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createRecord(zoneId: string, record: Partial<DnsRecord>): Promise<any> {
|
||||||
|
try {
|
||||||
|
if (!this.headers) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from our application record format to Cloudflare format
|
||||||
|
const cloudflareRecord: Partial<CloudflareRecord> = {
|
||||||
|
name: record.name || '',
|
||||||
|
type: record.type || 'A',
|
||||||
|
content: record.content || '',
|
||||||
|
ttl: record.ttl || 3600,
|
||||||
|
priority: record.priority || undefined,
|
||||||
|
proxied: (record as any).proxied || false,
|
||||||
|
comment: record.notes || undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(`${this.baseUrl}/zones/${zoneId}/dns_records`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: this.headers,
|
||||||
|
body: JSON.stringify(cloudflareRecord)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error(`Failed to create record: ${data.errors[0]?.message || 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error creating Cloudflare record in zone ${zoneId}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateRecord(zoneId: string, recordId: string, record: Partial<DnsRecord>): Promise<any> {
|
||||||
|
try {
|
||||||
|
if (!this.headers) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from our application record format to Cloudflare format
|
||||||
|
const cloudflareRecord: Partial<CloudflareRecord> = {
|
||||||
|
name: record.name || '',
|
||||||
|
type: record.type || 'A',
|
||||||
|
content: record.content || '',
|
||||||
|
ttl: record.ttl || 3600,
|
||||||
|
priority: record.priority || undefined,
|
||||||
|
proxied: (record as any).proxied || false,
|
||||||
|
comment: record.notes || undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(`${this.baseUrl}/zones/${zoneId}/dns_records/${recordId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: this.headers,
|
||||||
|
body: JSON.stringify(cloudflareRecord)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error(`Failed to update record: ${data.errors[0]?.message || 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error updating Cloudflare record ${recordId} in zone ${zoneId}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteRecord(zoneId: string, recordId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
if (!this.headers) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${this.baseUrl}/zones/${zoneId}/dns_records/${recordId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: this.headers
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.success === true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error deleting Cloudflare record ${recordId} in zone ${zoneId}:`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
import { DnsRecord, Provider } from '@shared/schema';
|
||||||
|
import { DNSProvider } from './index';
|
||||||
|
|
||||||
|
interface GoDaddyCredentials {
|
||||||
|
apiKey: string;
|
||||||
|
apiSecret: string;
|
||||||
|
shopper?: string; // Optional shopper ID for reseller accounts
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GoDaddyRecord {
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
data: string;
|
||||||
|
ttl: number;
|
||||||
|
priority?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GoDaddyProvider implements DNSProvider {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
private credentials: GoDaddyCredentials | null = null;
|
||||||
|
private baseUrl = 'https://api.godaddy.com/v1';
|
||||||
|
private headers: HeadersInit = {};
|
||||||
|
private isProduction = true; // Set to false for OTE environment
|
||||||
|
|
||||||
|
constructor(provider: Provider) {
|
||||||
|
this.name = provider.name;
|
||||||
|
this.type = provider.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize(credentials: GoDaddyCredentials): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
if (!credentials.apiKey || !credentials.apiSecret) {
|
||||||
|
console.error('Missing required GoDaddy credentials');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.credentials = credentials;
|
||||||
|
|
||||||
|
// Set API base URL based on environment
|
||||||
|
this.baseUrl = this.isProduction
|
||||||
|
? 'https://api.godaddy.com/v1'
|
||||||
|
: 'https://api.ote-godaddy.com/v1';
|
||||||
|
|
||||||
|
// Set up headers for authentication
|
||||||
|
this.headers = {
|
||||||
|
'Authorization': `sso-key ${credentials.apiKey}:${credentials.apiSecret}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify credentials by fetching domains
|
||||||
|
const response = await fetch(`${this.baseUrl}/domains`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: this.headers
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.status === 200;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing GoDaddy provider:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getZones(): Promise<any[]> {
|
||||||
|
try {
|
||||||
|
if (!this.headers) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${this.baseUrl}/domains`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: this.headers
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to get domains: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const domains = await response.json();
|
||||||
|
return domains || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting GoDaddy domains:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getZoneIdByName(domainName: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
// GoDaddy API uses domain names as identifiers, not separate IDs
|
||||||
|
// Check if the domain exists in the account
|
||||||
|
const domains = await this.getZones();
|
||||||
|
const domain = domains.find(d => d.domain === domainName);
|
||||||
|
|
||||||
|
// Return the domain name itself as the "ID" if it exists
|
||||||
|
return domain ? domainName : null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error getting domain info for ${domainName}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRecords(domain: string): Promise<any[]> {
|
||||||
|
try {
|
||||||
|
if (!this.headers) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoDaddy API allows getting all records or filtering by type and name
|
||||||
|
const response = await fetch(`${this.baseUrl}/domains/${domain}/records`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: this.headers
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to get records: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const records = await response.json();
|
||||||
|
return records || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error getting GoDaddy records for domain ${domain}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createRecord(domain: string, record: Partial<DnsRecord>): Promise<any> {
|
||||||
|
try {
|
||||||
|
if (!this.headers) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to GoDaddy format
|
||||||
|
const godaddyRecord: GoDaddyRecord = {
|
||||||
|
type: record.type || 'A',
|
||||||
|
name: record.name === '@' ? '@' : record.name || '',
|
||||||
|
data: record.content || '',
|
||||||
|
ttl: record.ttl || 3600
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add priority for MX and SRV records
|
||||||
|
if ((record.type === 'MX' || record.type === 'SRV') && record.priority) {
|
||||||
|
godaddyRecord.priority = record.priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoDaddy API requires an array of records for updates
|
||||||
|
const response = await fetch(`${this.baseUrl}/domains/${domain}/records`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: this.headers,
|
||||||
|
body: JSON.stringify([godaddyRecord])
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to create record: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoDaddy doesn't return the created record, so we need to fetch it
|
||||||
|
const records = await this.getRecords(domain);
|
||||||
|
const createdRecord = records.find(r =>
|
||||||
|
r.type === godaddyRecord.type &&
|
||||||
|
r.name === godaddyRecord.name
|
||||||
|
);
|
||||||
|
|
||||||
|
return createdRecord;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error creating GoDaddy record for domain ${domain}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateRecord(domain: string, recordId: string, record: Partial<DnsRecord>): Promise<any> {
|
||||||
|
try {
|
||||||
|
if (!this.headers) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the record ID to get the type and name
|
||||||
|
// In GoDaddy, we're using type-name as the recordId
|
||||||
|
const [type, name] = recordId.split('-');
|
||||||
|
|
||||||
|
// Convert to GoDaddy format
|
||||||
|
const godaddyRecord: GoDaddyRecord = {
|
||||||
|
type: record.type || type,
|
||||||
|
name: record.name || name,
|
||||||
|
data: record.content || '',
|
||||||
|
ttl: record.ttl || 3600
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add priority for MX and SRV records
|
||||||
|
if ((godaddyRecord.type === 'MX' || godaddyRecord.type === 'SRV') && record.priority) {
|
||||||
|
godaddyRecord.priority = record.priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoDaddy API requires updating by type and name
|
||||||
|
const response = await fetch(`${this.baseUrl}/domains/${domain}/records/${type}/${name}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: this.headers,
|
||||||
|
body: JSON.stringify([godaddyRecord])
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to update record: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoDaddy doesn't return the updated record, so we need to fetch it
|
||||||
|
const records = await this.getRecords(domain);
|
||||||
|
const updatedRecord = records.find(r =>
|
||||||
|
r.type === godaddyRecord.type &&
|
||||||
|
r.name === godaddyRecord.name
|
||||||
|
);
|
||||||
|
|
||||||
|
return updatedRecord;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error updating GoDaddy record ${recordId} for domain ${domain}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteRecord(domain: string, recordId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
if (!this.headers) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the record ID to get the type and name
|
||||||
|
const [type, name] = recordId.split('-');
|
||||||
|
|
||||||
|
// GoDaddy doesn't allow deleting records via API directly
|
||||||
|
// Instead, we need to replace them with an empty array
|
||||||
|
const response = await fetch(`${this.baseUrl}/domains/${domain}/records/${type}/${name}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: this.headers,
|
||||||
|
body: JSON.stringify([])
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.ok;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error deleting GoDaddy record ${recordId} for domain ${domain}:`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Provider, DnsRecord, Domain } from '@shared/schema';
|
||||||
|
import { CloudflareProvider } from './cloudflare';
|
||||||
|
import { Route53Provider } from './route53';
|
||||||
|
import { GoDaddyProvider } from './godaddy';
|
||||||
|
import { GenericProvider } from './generic';
|
||||||
|
|
||||||
|
// Define the provider interface all implementations should adhere to
|
||||||
|
export interface DNSProvider {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
initialize(credentials: any): Promise<boolean>;
|
||||||
|
getZones(): Promise<any[]>;
|
||||||
|
getRecords(zoneId: string): Promise<any[]>;
|
||||||
|
createRecord(zoneId: string, record: Partial<DnsRecord>): Promise<any>;
|
||||||
|
updateRecord(zoneId: string, recordId: string, record: Partial<DnsRecord>): Promise<any>;
|
||||||
|
deleteRecord(zoneId: string, recordId: string): Promise<boolean>;
|
||||||
|
getZoneIdByName(domain: string): Promise<string | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factory function to create the appropriate provider instance
|
||||||
|
export function createProvider(provider: Provider): DNSProvider {
|
||||||
|
switch (provider.type) {
|
||||||
|
case 'cloudflare':
|
||||||
|
return new CloudflareProvider(provider);
|
||||||
|
case 'route53':
|
||||||
|
return new Route53Provider(provider);
|
||||||
|
case 'godaddy':
|
||||||
|
return new GoDaddyProvider(provider);
|
||||||
|
case 'other':
|
||||||
|
default:
|
||||||
|
return new GenericProvider(provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get the provider instance for a domain
|
||||||
|
export async function getProviderForDomain(domain: Domain, provider: Provider): Promise<DNSProvider | null> {
|
||||||
|
try {
|
||||||
|
if (!provider || !provider.credentials) {
|
||||||
|
console.error('Provider not found or missing credentials');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dnsProvider = createProvider(provider);
|
||||||
|
const initialized = await dnsProvider.initialize(provider.credentials);
|
||||||
|
|
||||||
|
if (!initialized) {
|
||||||
|
console.error(`Failed to initialize provider ${provider.name}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dnsProvider;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting provider for domain:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function to synchronize DNS records with the provider
|
||||||
|
export async function syncDnsRecords(domain: Domain, provider: Provider, records: DnsRecord[]): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const dnsProvider = await getProviderForDomain(domain, provider);
|
||||||
|
if (!dnsProvider) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get zone ID from the provider
|
||||||
|
const zoneId = await dnsProvider.getZoneIdByName(domain.name);
|
||||||
|
if (!zoneId) {
|
||||||
|
console.error(`Zone not found for domain ${domain.name}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get existing records from the provider
|
||||||
|
const providerRecords = await dnsProvider.getRecords(zoneId);
|
||||||
|
|
||||||
|
// Perform the synchronization logic here
|
||||||
|
// This would involve matching local records with remote records
|
||||||
|
// and creating/updating/deleting as needed
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error syncing DNS records:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
import { DnsRecord, Provider } from '@shared/schema';
|
||||||
|
import { DNSProvider } from './index';
|
||||||
|
|
||||||
|
interface Route53Credentials {
|
||||||
|
accessKeyId: string;
|
||||||
|
secretAccessKey: string;
|
||||||
|
region?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Route53Provider implements DNSProvider {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
private credentials: Route53Credentials | null = null;
|
||||||
|
private isInitialized = false;
|
||||||
|
|
||||||
|
constructor(provider: Provider) {
|
||||||
|
this.name = provider.name;
|
||||||
|
this.type = provider.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize(credentials: Route53Credentials): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
if (!credentials.accessKeyId || !credentials.secretAccessKey) {
|
||||||
|
console.error('Missing required Route53 credentials');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.credentials = credentials;
|
||||||
|
this.isInitialized = true;
|
||||||
|
|
||||||
|
// Here you would typically verify credentials by making a test API call
|
||||||
|
// Since AWS SDKs are not included and we're simulating, we'll assume success
|
||||||
|
// In a real implementation, you would use the AWS SDK to verify credentials
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing Route53 provider:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getZones(): Promise<any[]> {
|
||||||
|
try {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a real implementation, you would use the AWS SDK:
|
||||||
|
// const route53 = new AWS.Route53({
|
||||||
|
// credentials: {
|
||||||
|
// accessKeyId: this.credentials.accessKeyId,
|
||||||
|
// secretAccessKey: this.credentials.secretAccessKey
|
||||||
|
// },
|
||||||
|
// region: this.credentials.region || 'us-east-1'
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const hostedZones = await route53.listHostedZones({}).promise();
|
||||||
|
// return hostedZones.HostedZones || [];
|
||||||
|
|
||||||
|
console.log('Route53: Getting zones (simulated)');
|
||||||
|
return [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting Route53 zones:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getZoneIdByName(domainName: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a real implementation with AWS SDK:
|
||||||
|
// const zones = await this.getZones();
|
||||||
|
// const zone = zones.find(z => z.Name === domainName + '.' || z.Name === domainName);
|
||||||
|
// return zone ? zone.Id.replace('/hostedzone/', '') : null;
|
||||||
|
|
||||||
|
console.log(`Route53: Getting zone ID for domain ${domainName} (simulated)`);
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error getting Route53 zone ID for domain ${domainName}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRecords(zoneId: string): Promise<any[]> {
|
||||||
|
try {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a real implementation with AWS SDK:
|
||||||
|
// const route53 = new AWS.Route53({
|
||||||
|
// credentials: {
|
||||||
|
// accessKeyId: this.credentials.accessKeyId,
|
||||||
|
// secretAccessKey: this.credentials.secretAccessKey
|
||||||
|
// },
|
||||||
|
// region: this.credentials.region || 'us-east-1'
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const records = await route53.listResourceRecordSets({
|
||||||
|
// HostedZoneId: zoneId
|
||||||
|
// }).promise();
|
||||||
|
//
|
||||||
|
// return records.ResourceRecordSets || [];
|
||||||
|
|
||||||
|
console.log(`Route53: Getting records for zone ${zoneId} (simulated)`);
|
||||||
|
return [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error getting Route53 records for zone ${zoneId}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createRecord(zoneId: string, record: Partial<DnsRecord>): Promise<any> {
|
||||||
|
try {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a real implementation with AWS SDK:
|
||||||
|
// const route53 = new AWS.Route53({
|
||||||
|
// credentials: {
|
||||||
|
// accessKeyId: this.credentials.accessKeyId,
|
||||||
|
// secretAccessKey: this.credentials.secretAccessKey
|
||||||
|
// },
|
||||||
|
// region: this.credentials.region || 'us-east-1'
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const params = {
|
||||||
|
// HostedZoneId: zoneId,
|
||||||
|
// ChangeBatch: {
|
||||||
|
// Changes: [
|
||||||
|
// {
|
||||||
|
// Action: 'CREATE',
|
||||||
|
// ResourceRecordSet: {
|
||||||
|
// Name: record.name,
|
||||||
|
// Type: record.type,
|
||||||
|
// TTL: record.ttl || 3600,
|
||||||
|
// ResourceRecords: [
|
||||||
|
// {
|
||||||
|
// Value: record.content
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// if (record.type === 'MX' && record.priority) {
|
||||||
|
// params.ChangeBatch.Changes[0].ResourceRecordSet.ResourceRecords[0].Value =
|
||||||
|
// `${record.priority} ${record.content}`;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// const result = await route53.changeResourceRecordSets(params).promise();
|
||||||
|
// return result;
|
||||||
|
|
||||||
|
console.log(`Route53: Creating record in zone ${zoneId} (simulated)`, record);
|
||||||
|
return { id: 'simulated-record-id' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error creating Route53 record in zone ${zoneId}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateRecord(zoneId: string, recordId: string, record: Partial<DnsRecord>): Promise<any> {
|
||||||
|
try {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// In Route53, update is done by deleting the old record and creating a new one
|
||||||
|
// You would first need to get the current record to get its exact configuration
|
||||||
|
// const oldRecord = await this.getRecordById(zoneId, recordId);
|
||||||
|
//
|
||||||
|
// const route53 = new AWS.Route53({
|
||||||
|
// credentials: {
|
||||||
|
// accessKeyId: this.credentials.accessKeyId,
|
||||||
|
// secretAccessKey: this.credentials.secretAccessKey
|
||||||
|
// },
|
||||||
|
// region: this.credentials.region || 'us-east-1'
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const params = {
|
||||||
|
// HostedZoneId: zoneId,
|
||||||
|
// ChangeBatch: {
|
||||||
|
// Changes: [
|
||||||
|
// {
|
||||||
|
// Action: 'DELETE',
|
||||||
|
// ResourceRecordSet: { /* old record configuration */ }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Action: 'CREATE',
|
||||||
|
// ResourceRecordSet: {
|
||||||
|
// Name: record.name,
|
||||||
|
// Type: record.type,
|
||||||
|
// TTL: record.ttl || 3600,
|
||||||
|
// ResourceRecords: [
|
||||||
|
// {
|
||||||
|
// Value: record.content
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// const result = await route53.changeResourceRecordSets(params).promise();
|
||||||
|
// return result;
|
||||||
|
|
||||||
|
console.log(`Route53: Updating record ${recordId} in zone ${zoneId} (simulated)`, record);
|
||||||
|
return { id: recordId };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error updating Route53 record ${recordId} in zone ${zoneId}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteRecord(zoneId: string, recordId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
throw new Error('Provider not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// In Route53, you need the exact record configuration to delete it
|
||||||
|
// const record = await this.getRecordById(zoneId, recordId);
|
||||||
|
//
|
||||||
|
// const route53 = new AWS.Route53({
|
||||||
|
// credentials: {
|
||||||
|
// accessKeyId: this.credentials.accessKeyId,
|
||||||
|
// secretAccessKey: this.credentials.secretAccessKey
|
||||||
|
// },
|
||||||
|
// region: this.credentials.region || 'us-east-1'
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const params = {
|
||||||
|
// HostedZoneId: zoneId,
|
||||||
|
// ChangeBatch: {
|
||||||
|
// Changes: [
|
||||||
|
// {
|
||||||
|
// Action: 'DELETE',
|
||||||
|
// ResourceRecordSet: { /* record configuration */ }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// const result = await route53.changeResourceRecordSets(params).promise();
|
||||||
|
// return !!result;
|
||||||
|
|
||||||
|
console.log(`Route53: Deleting record ${recordId} in zone ${zoneId} (simulated)`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error deleting Route53 record ${recordId} in zone ${zoneId}:`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to get a specific record by ID (note: Route53 doesn't have record IDs in the same way as Cloudflare)
|
||||||
|
private async getRecordById(zoneId: string, recordId: string): Promise<any> {
|
||||||
|
// This is a simplified approach. In a real implementation, you would need to:
|
||||||
|
// 1. Store the record name and type when creating it
|
||||||
|
// 2. Use that info to identify the record in the list of records
|
||||||
|
|
||||||
|
const records = await this.getRecords(zoneId);
|
||||||
|
// In a real implementation, you would have a way to map recordId to the actual Route53 record
|
||||||
|
return records.find(r => r.name === recordId); // This is not how it would work in practice
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user