Cloudflare Hosts Service
๐ Overviewโ
Service Path: external/Integrations/Cloudflare/services/hosts.service.js
The Cloudflare Hosts service manages custom hostnames (domains) on Cloudflare's CDN infrastructure for DashClick's Lightning Domains feature. Core responsibilities include:
- Custom Hostname Management: Create, read, update, and delete custom hostnames
- SSL Certificate Provisioning: Automatic SSL certificate generation via Let's Encrypt
- Domain Verification: DNS and ownership verification for custom domains
- OneBalance Integration: Charge accounts for domain additions (with free tier)
- Funnel Management: Track which funnels use which domains
- Domain Cleanup: Remove domain associations when deleted
๐๏ธ Collections Usedโ
hostname- Domain namecf_id- Cloudflare custom hostname IDssl- SSL certificate statusstatus- Domain statuscname_validation- DNS validation recordsincluded- Whether domain is in free tieraccount_id- Owner accountparent_account- Main account (for white-label)
๐ Data Flowโ
Custom Hostname Creation Flowโ
sequenceDiagram
participant Client
participant HostsService
participant OneBalance
participant LightningDomainDB
participant CloudflareAPI
participant FunnelCleanup
Client->>HostsService: createHost(domain, account_id, parent_account)
HostsService->>HostsService: Check if free tier available
Note over HostsService: First 2 domains free for main accounts
alt Free Tier Available
HostsService->>HostsService: Mark as included = true
else Must Pay
HostsService->>OneBalance: verifyBalance(lighting_domain)
OneBalance-->>HostsService: Balance verified
end
HostsService->>CloudflareAPI: POST /custom_hostnames
Note over HostsService,CloudflareAPI: SSL: DV with TXT validation
CloudflareAPI-->>HostsService: Custom hostname created
HostsService->>CloudflareAPI: GET /custom_hostnames/:id
Note over HostsService,CloudflareAPI: Get full verification details
CloudflareAPI-->>HostsService: Full hostname data
HostsService->>LightningDomainDB: Upsert domain record
Note over HostsService,LightningDomainDB: Store cf_id, ssl status, CNAME records
HostsService->>FunnelCleanup: cleanupFunnelDomain()
Note over HostsService,FunnelCleanup: Remove old domain associations
HostsService-->>Client: Lightning domain created
style HostsService fill:#e3f2fd
style CloudflareAPI fill:#fff4e6
style OneBalance fill:#ffe0b2
Domain Deletion Flowโ
flowchart TD
A[Delete Request] --> B[Find domain in DB]
B --> C{Domain exists?}
C -->|No| D[Throw 404 error]
C -->|Yes| E[Delete from Cloudflare API]
E --> F{Cloudflare deletion successful?}
F -->|No| G[Throw error]
F -->|Yes| H[Cleanup funnel associations]
H --> I[Delete from LightningDomain DB]
I --> J[Update funnel domain status]
J --> K{Was domain 'included'?}
K -->|Yes| L[Promote another domain to 'included']
K -->|No| M[No promotion needed]
L --> N[Return success]
M --> N
D --> O[End]
G --> O
style E fill:#ffcdd2
style H fill:#e1bee7
style L fill:#c8e6c9
๐ง Business Logic & Functionsโ
createHost({ domain, account_id, parent_account, auth })โ
Purpose: Create a new custom hostname on Cloudflare with SSL certificate
Parameters:
domain(String) - Custom domain name (e.g., 'example.com')account_id(ObjectId) - DashClicks account IDparent_account(ObjectId) - Main account ID (for white-label accounts)auth(Object) - Authentication contextaccount(Object) - Account objectuid(ObjectId) - User ID
Returns:
{
_id: ObjectId,
hostname: String,
cf_id: String, // Cloudflare custom hostname ID
cname_validation: {
name: String, // Subdomain part (e.g., 'example')
value: String // CNAME target: 'sites.urlme.app'
},
ownership_verification: {
name: String, // TXT record name
value: String, // TXT record value
type: String // 'txt'
},
ssl: {
status: String, // 'pending_validation', 'active', etc.
method: String, // 'txt'
type: String, // 'dv' (Domain Validation)
validation_records: Array
},
status: String, // 'pending', 'active', 'moved'
account_id: ObjectId,
parent_account: ObjectId,
included: Boolean, // True if in free tier
created_at: Date
}
Business Logic Flow:
-
Check Free Tier Eligibility
let included = false;
if (account_id.toString() == parent_account.toString()) {
const totalIncluded = await LightningDomain.countDocuments({
included: true,
parent_account,
});
if (totalIncluded < 2) {
included = true; // First 2 domains free for main accounts
}
} -
Verify OneBalance (If Not Free)
if (!included) {
await verifyBalance({
event: 'lighting_domain',
account: auth.account,
user_id: auth.uid,
quantity: 1,
});
} -
Create Custom Hostname on Cloudflare
const reqBody = {
hostname: domain,
ssl: {
type: 'dv', // Domain Validation
method: 'txt', // TXT record verification
hosts: [domain],
settings: {
min_tls_version: '1.2',
},
bundle_method: 'ubiquitous',
wildcard: false,
},
};
let response = await axios.post(
`${BASE_URL}/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames`,
reqBody,
{
headers: {
Authorization: `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
},
},
); -
Fetch Full Hostname Details
response = await axios.get(
`${BASE_URL}/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames/${response.result.id}`,
{
headers: {
Authorization: `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
},
},
); -
Store in Database
response.result.cf_id = response.result.id;
delete response.result.id;
let lightningDomain = await LightningDomain.findOneAndUpdate(
{ hostname: response.result.hostname },
{
account_id,
parent_account,
...response.result,
cname_validation: {
name: response.result.hostname.split('.')[0],
value: 'sites.urlme.app',
},
included: included ? included : undefined,
},
{ upsert: true, new: true },
); -
Cleanup Funnel Associations
try {
await cleanupFunnelDomain({ hostname: hostname });
} catch (error) {}
API Endpoint: POST https://api.cloudflare.com/client/v4/zones/:zone_id/custom_hostnames
Key Business Rules:
- Free Tier: Main accounts get first 2 domains free (included = true)
- Billing: Non-free domains charge OneBalance before creation
- SSL Type: Always uses Domain Validation (DV) with TXT method
- TLS Version: Minimum TLS 1.2 enforced
- CNAME Target: All domains point to 'sites.urlme.app'
- Upsert Logic: Updates existing domain if hostname already exists
getHosts({ account_id, connected })โ
Purpose: List all custom hostnames for an account with funnel associations
Parameters:
account_id(ObjectId) - Account IDconnected(String) - Filter by connection status'all'- All domainsundefinedor other - Only connected/active domains
Returns:
[
{
_id: ObjectId,
hostname: String,
cf_id: String,
cname_validation: Object,
ownership_verification: Object,
ownership_verification_http: Object,
ssl: {
status: String, // 'pending_validation', 'active'
// ... other SSL fields
},
status: String, // 'pending', 'active', 'moved'
created_at: Date,
verification_errors: Array,
defaultDomainHomePageRule: Object,
defaultDomain404Rule: Object,
funnels: [
// Joined from funnels collection
{
funnel_id: ObjectId,
funnel_name: String,
},
],
},
];
Business Logic Flow:
-
Build Match Condition
let condition = {
account_id,
};
if (connected && connected != 'all') {
condition.$and = [{ 'ssl.status': 'active' }, { status: 'active' }];
} -
Aggregate with Funnels
let lDomains = await LightningDomain.aggregate([
{ $match: condition },
{
$lookup: {
from: 'funnels',
localField: '_id',
foreignField: 'domain_id',
as: 'funnels',
},
},
{
$addFields: {
funnels: {
$map: {
input: '$funnels',
as: 'funnel',
in: {
funnel_id: '$$funnel._id',
funnel_name: '$$funnel.name',
},
},
},
},
},
{
$project: {
_id: 1,
hostname: 1,
cf_id: 1,
cname_validation: 1,
ownership_verification: 1,
ownership_verification_http: 1,
ssl: 1,
status: 1,
created_at: 1,
verification_errors: 1,
defaultDomainHomePageRule: 1,
defaultDomain404Rule: 1,
funnels: 1,
},
},
]);
return lDomains;
Key Business Rules:
- Connected Filter: Only returns domains with
ssl.status='active'ANDstatus='active' - Funnel Associations: Includes all funnels using each domain
- Account Scoped: Only returns domains for specified account
updateHost({ host_id, hostInfo })โ
Purpose: Update custom hostname configuration (placeholder - not implemented)
Parameters:
host_id(ObjectId) - Lightning domain IDhostInfo(Object) - Updated host information
Implementation Status: ๐ง NOT IMPLEMENTED
try {
//Will change default page and 404 page here only
} catch (error) {
throw badRequest('Bad request', 'BAD_REQUEST');
}
Intended Use: Update default homepage and 404 page rules for domain
getHost({ domain_id, account_id })โ
Purpose: Refresh single domain status from Cloudflare and sync to database
Parameters:
domain_id(ObjectId) - Lightning domain IDaccount_id(ObjectId) - Account ID (for ownership validation)
Returns: Updated LightningDomain document
Business Logic Flow:
-
Find Domain in Database
let lDomain = await LightningDomain.findOne({ _id: domain_id, account_id });
if (!lDomain) throw notFound('Domain not found'); -
Fetch Latest Status from Cloudflare
try {
response = await axios.get(
`${BASE_URL}/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames/${lDomain.cf_id}`,
{
headers: {
Authorization: `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
},
},
);
} catch (err) {
// Handle deleted hostname on Cloudflare
if (err?.response?.data?.errors?.[0]?.message == 'The custom hostname was not found.') {
await LightningDomain.deleteOne({ _id: domain_id });
await reviewAndSetStatusForDomain({ domain_id: domain_id });
}
throw badRequest(err?.response?.data?.errors?.[0]?.message || 'Something went wrong');
} -
Update Database with Cloudflare Data
response = response.data;
if (!response?.success) {
throw badRequest(response.errors?.[0]?.message);
}
lDomain = await LightningDomain.findByIdAndUpdate(
domain_id,
{ ...response.result },
{ new: true },
);
return lDomain;
Key Business Rules:
- Sync with Cloudflare: Updates local database with latest Cloudflare status
- Ownership Validation: Ensures domain belongs to requesting account
- Orphan Cleanup: Removes domain from DB if deleted from Cloudflare
- Status Review: Triggers funnel domain status update after sync
deleteHost({ domain_id, account_id })โ
Purpose: Delete custom hostname from Cloudflare and database
Parameters:
domain_id(ObjectId) - Lightning domain IDaccount_id(ObjectId) - Account ID
Returns: true on success
Business Logic Flow:
-
Find and Validate Domain
let lDomain = await LightningDomain.findOne({ _id: domain_id, account_id });
if (!lDomain) throw notFound('Domain not found'); -
Delete from Cloudflare
let response = await axios.delete(
`${BASE_URL}/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames/${lDomain.cf_id}`,
{
headers: {
Authorization: `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
},
},
);
response = response.data;
if (!response?.success) {
throw badRequest(response.errors?.[0]?.message);
} -
Cleanup Funnel Associations
try {
await cleanupFunnelDomain({ hostname: hostname, domain_id: domain_id });
} catch (error) {} -
Delete from Database
await LightningDomain.deleteOne({ _id: domain_id, account_id });
await reviewAndSetStatusForDomain({ domain_id: domain_id }); -
Promote Replacement Domain (If Free Tier)
if (lDomain.included) {
// Find another domain to make free
await LightningDomain.findOneAndUpdate(
{ account_id, included: { $ne: true } },
{ included: true },
);
}
Key Business Rules:
- Cloudflare First: Deletes from Cloudflare before database
- Funnel Cleanup: Removes domain associations from funnels
- Free Tier Replacement: If deleted domain was free, promotes another domain to free tier
- Status Review: Updates funnel domain status after deletion
๐ Integration Pointsโ
Lightning Domains Creationโ
const hostsService = require('./services/hosts.service');
// Create custom hostname
const domain = await hostsService.createHost({
domain: 'mybusiness.com',
account_id: accountId,
parent_account: mainAccountId,
auth: {
account: accountObj,
uid: userId,
},
});
console.log(`Domain created: ${domain.hostname}`);
console.log(`CNAME record: ${domain.cname_validation.name} โ ${domain.cname_validation.value}`);
console.log(`TXT verification: ${domain.ownership_verification.value}`);
Domain Verification Statusโ
// Refresh domain status from Cloudflare
const domain = await hostsService.getHost({
domain_id: domainId,
account_id: accountId,
});
if (domain.ssl.status === 'active' && domain.status === 'active') {
console.log('Domain is fully connected!');
} else {
console.log(`SSL: ${domain.ssl.status}, Domain: ${domain.status}`);
}
List Active Domainsโ
// Get only connected domains
const activeDomains = await hostsService.getHosts({
account_id: accountId,
connected: 'connected', // Only active domains
});
activeDomains.forEach(domain => {
console.log(`${domain.hostname} - ${domain.funnels.length} funnels`);
});
๐งช Edge Cases & Special Handlingโ
Free Tier Managementโ
First 2 Domains Free for Main Accounts:
if (account_id.toString() == parent_account.toString()) {
const totalIncluded = await LightningDomain.countDocuments({
included: true,
parent_account,
});
if (totalIncluded < 2) {
included = true;
}
}
Free Tier Replacement on Deletion:
if (lDomain.included) {
await LightningDomain.findOneAndUpdate(
{ account_id, included: { $ne: true } },
{ included: true },
);
}
Cloudflare API Errorsโ
Custom Hostname Not Found:
if (err?.response?.data?.errors?.[0]?.message == 'The custom hostname was not found.') {
await LightningDomain.deleteOne({ _id: domain_id });
await reviewAndSetStatusForDomain({ domain_id: domain_id });
}
OneBalance Verification Failureโ
Insufficient Balance:
await verifyBalance({
event: 'lighting_domain',
account: auth.account,
user_id: auth.uid,
quantity: 1,
});
// Throws error if balance insufficient
Duplicate Domain Creationโ
Upsert Logic:
let lightningDomain = await LightningDomain.findOneAndUpdate(
{ hostname: response.result.hostname },
{
/* ... */
},
{ upsert: true, new: true }, // Updates if exists, creates if not
);
โ ๏ธ Important Notesโ
-
Free Tier: Main accounts get 2 free domains. Sub-accounts never get free domains.
-
OneBalance: Domains beyond free tier deduct from OneBalance before creation.
-
SSL Provisioning: Automatic via Let's Encrypt using TXT record validation.
-
CNAME Target: All domains point to
sites.urlme.appfor content delivery. -
Verification: Requires both DNS (CNAME) and ownership (TXT) verification for SSL activation.
-
Status Sync:
getHost()refreshes domain status from Cloudflare to keep database in sync. -
Funnel Cleanup: Deleting domain removes associations from funnels automatically.
-
Cloudflare Zone: Uses single Cloudflare zone (
LD_CLOUDFLARE_ZONE_ID) for all custom hostnames. -
TLS Security: Enforces minimum TLS 1.2 for all SSL certificates.
-
Error Handling: Cloudflare API errors propagated to client with error messages.
-
Orphan Detection: If domain deleted from Cloudflare, automatically removed from database.
-
Free Tier Promotion: When free domain deleted, another domain automatically promoted to free tier.
๐ Related Documentationโ
- Funnels Integration - Funnel domain associations
- OneBalance System - Billing integration
- Cloudflare Custom Hostnames API - Official API docs