update: fix
This commit is contained in:
@@ -635,6 +635,9 @@ export const enum BusinessMessage {
|
||||
DOMAIN_CONNECTION_TROUBLESHOOTING_2 = "- برخی از ارائهدهندگان DNS برای دامنههای اصلی به نماد @ نیاز دارند",
|
||||
DOMAIN_CONNECTION_TROUBLESHOOTING_3 = "- بررسی کنید که مقادیر را دقیقاً همانطور که نشان داده شده کپی کردهاید",
|
||||
DOMAIN_CONNECTION_TROUBLESHOOTING_4 = "- تغییرات DNS میتواند برای انتشار زمان ببرد (معمولاً ۱۵ دقیقه تا ۴۸ ساعت)",
|
||||
DOMAIN_CONNECTION_TROUBLESHOOTING_5 = "- اگر از Cloudflare استفاده میکنید، حالت ابری (پروکسی) را خاموش کنید و فقط از DNS استفاده کنید",
|
||||
DOMAIN_CONNECTION_TROUBLESHOOTING_6 = "- برای دامنه اصلی (@)، برخی ارائه دهندگان DNS به جای CNAME از رکورد ANAME یا ALIAS استفاده میکنند",
|
||||
DOMAIN_CONNECTION_TROUBLESHOOTING_7 = "- اگر نمیتوانید CNAME برای دامنه اصلی تنظیم کنید، از رکورد A با IP سرور استفاده کنید",
|
||||
|
||||
// Domain Setup Instructions in Farsi
|
||||
DOMAIN_SETUP_VERIFICATION_TITLE = "مرحله ۱: تایید مالکیت دامنه",
|
||||
@@ -644,4 +647,6 @@ export const enum BusinessMessage {
|
||||
DOMAIN_SETUP_TROUBLESHOOTING_2 = "- بعضی از ارائهدهندگان DNS برای دامنههای اصلی به نماد @ نیاز دارند",
|
||||
DOMAIN_SETUP_TROUBLESHOOTING_3 = "- اطمینان حاصل کنید که مقادیر را دقیقاً همانطور که نشان داده شده کپی کردهاید",
|
||||
DOMAIN_SETUP_TROUBLESHOOTING_4 = "- حداقل ۱۵ دقیقه قبل از بررسی وضعیت تأیید صبر کنید",
|
||||
DOMAIN_SETUP_TROUBLESHOOTING_5 = "- برخی از ارائهدهندگان DNS ممکن است نیاز به تنظیمات خاص برای رکوردهای TXT داشته باشند",
|
||||
DOMAIN_SETUP_TROUBLESHOOTING_6 = "- اگر از Cloudflare استفاده میکنید، مطمئن شوید که رکوردهای TXT را بدون پروکسی تنظیم کردهاید",
|
||||
}
|
||||
|
||||
@@ -62,124 +62,4 @@ export class BusinessesController {
|
||||
getDomainStatus(@BusinessDec() business: Business) {
|
||||
return this.domainVerificationService.getDomainStatus(business.id);
|
||||
}
|
||||
|
||||
@Post("domain/update-dns-records")
|
||||
@ApiOperation({ summary: "Get detailed instructions for updating DNS records" })
|
||||
@AuthGuards()
|
||||
getDetailedDnsInstructions(@BusinessDec() business: Business) {
|
||||
// This endpoint provides detailed DNS setup instructions for various DNS providers
|
||||
if (!business.domain) {
|
||||
return {
|
||||
success: false,
|
||||
message: "No domain configured for this business",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
domain: business.domain,
|
||||
instructions: {
|
||||
title: "راهنمای تنظیم رکوردهای DNS برای دامنه شما",
|
||||
description: "برای اتصال دامنه خود به سرویس دیزون، لطفا یکی از روشهای زیر را انتخاب کنید:",
|
||||
methods: [
|
||||
{
|
||||
title: "روش A: استفاده از رکورد A (آدرس IP)",
|
||||
description: "این روش برای همه ارائهدهندگان DNS کار میکند و برای دامنه اصلی مناسب است.",
|
||||
steps: [
|
||||
"۱. وارد پنل مدیریت DNS خود شوید",
|
||||
"۲. یک رکورد A جدید ایجاد کنید",
|
||||
"۳. در فیلد Host یا Name، @ را وارد کنید (برای دامنه اصلی)",
|
||||
`۴. در فیلد Value یا Points to، آدرس IP سرور ما را وارد کنید: 194.5.192.20`,
|
||||
"۵. TTL را روی 3600 (یک ساعت) یا کمترین مقدار ممکن تنظیم کنید",
|
||||
"۶. تغییرات را ذخیره کنید",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "روش B: استفاده از رکورد CNAME (نام مستعار)",
|
||||
description: "این روش برای زیردامنهها مانند www توصیه میشود.",
|
||||
steps: [
|
||||
"۱. وارد پنل مدیریت DNS خود شوید",
|
||||
"۲. یک رکورد CNAME جدید ایجاد کنید",
|
||||
"۳. در فیلد Host یا Name، www را وارد کنید (یا زیردامنه دلخواه)",
|
||||
`۴. در فیلد Value یا Points to، آدرس سرور ما را وارد کنید: app.dzone.danakcorp.com`,
|
||||
"۵. TTL را روی 3600 (یک ساعت) یا کمترین مقدار ممکن تنظیم کنید",
|
||||
"۶. تغییرات را ذخیره کنید",
|
||||
],
|
||||
note: "توجه: برخی ارائهدهندگان DNS اجازه استفاده از CNAME برای دامنه اصلی (@) را نمیدهند. در این صورت از روش A استفاده کنید.",
|
||||
},
|
||||
],
|
||||
providerSpecific: [
|
||||
{
|
||||
provider: "Cloudflare",
|
||||
instructions: [
|
||||
"۱. حالت ابری (پروکسی) را خاموش کنید و فقط از DNS استفاده کنید",
|
||||
"۲. برای دامنه اصلی (@) از رکورد A استفاده کنید",
|
||||
"۳. برای زیردامنه www از رکورد CNAME استفاده کنید",
|
||||
],
|
||||
},
|
||||
{
|
||||
provider: "cPanel",
|
||||
instructions: [
|
||||
"۱. به بخش Zone Editor بروید",
|
||||
"۲. روی دامنه خود کلیک کنید",
|
||||
"۳. برای دامنه اصلی، رکورد A با مقدار 194.5.192.20 اضافه کنید",
|
||||
"۴. برای www، رکورد CNAME با مقدار app.dzone.danakcorp.com اضافه کنید",
|
||||
],
|
||||
},
|
||||
{
|
||||
provider: "سایر ارائهدهندگان",
|
||||
instructions: [
|
||||
"اگر نمیتوانید CNAME برای دامنه اصلی تنظیم کنید، از رکورد A استفاده کنید",
|
||||
"برخی ارائهدهندگان از رکوردهای ANAME یا ALIAS برای دامنه اصلی پشتیبانی میکنند",
|
||||
"اگر در تنظیم رکوردها مشکل دارید، با پشتیبانی ارائهدهنده DNS خود تماس بگیرید",
|
||||
],
|
||||
},
|
||||
],
|
||||
troubleshooting: [
|
||||
"- تغییرات DNS ممکن است بین ۱۵ دقیقه تا ۴۸ ساعت طول بکشد تا در سراسر اینترنت منتشر شود",
|
||||
"- اگر پس از ۴۸ ساعت هنوز مشکل دارید، مطمئن شوید که رکوردها را درست تنظیم کردهاید",
|
||||
"- برای بررسی وضعیت رکوردهای DNS خود میتوانید از ابزارهایی مانند dnschecker.org استفاده کنید",
|
||||
"- اگر همچنان مشکل دارید، با پشتیبانی ما تماس بگیرید",
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@Post("domain/configure-caprover")
|
||||
@ApiOperation({ summary: "Manually configure domain in Caprover (Admin use only)" })
|
||||
@AuthGuards()
|
||||
async manualCaproverConfig(@BusinessDec() business: Business) {
|
||||
if (!business.domain) {
|
||||
return {
|
||||
success: false,
|
||||
message: "No domain configured for this business",
|
||||
};
|
||||
}
|
||||
|
||||
if (!business.isDomainVerified) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Domain must be verified before configuring in Caprover",
|
||||
domain: business.domain,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.domainVerificationService.configureCaproverDomain(business.domain);
|
||||
|
||||
return {
|
||||
success: result,
|
||||
message: result
|
||||
? `Domain ${business.domain} successfully configured in Caprover`
|
||||
: `Failed to configure domain ${business.domain} in Caprover`,
|
||||
domain: business.domain,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Error configuring domain in Caprover: ${error instanceof Error ? error.message : String(error)}`,
|
||||
domain: business.domain,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,66 +49,6 @@ export class DomainVerificationService {
|
||||
}
|
||||
|
||||
//--------------------------------
|
||||
private async getCaproverToken(): Promise<string> {
|
||||
try {
|
||||
// Check if we already have a valid token
|
||||
if (this.caproverAuthToken && this.tokenExpiry && new Date() < new Date(this.tokenExpiry.getTime() - this.TOKEN_REFRESH_BEFORE_EXPIRY_MS)) {
|
||||
this.logger.debug("Using existing Caprover token");
|
||||
return this.caproverAuthToken;
|
||||
}
|
||||
|
||||
this.logger.log("Getting new Caprover authentication token");
|
||||
|
||||
const loginUrl = `${this.caproverApiUrl}/login`;
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService
|
||||
.post(
|
||||
loginUrl,
|
||||
{
|
||||
password: this.caproverPassword,
|
||||
otpToken: "",
|
||||
// email: this.caproverEmail,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"x-namespace": "captain",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
)
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("Error in Caprover authentication", err);
|
||||
throw new InternalServerErrorException("Failed to authenticate with Caprover");
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
if (response.status === 200 && response.data?.data?.token) {
|
||||
// Set the token and expiry (default to 24 hours if not provided by Caprover)
|
||||
this.caproverAuthToken = response.data.data.token;
|
||||
|
||||
// Set expiry to 23 hours from now to be safe (Caprover tokens typically last 24 hours)
|
||||
this.tokenExpiry = new Date(Date.now() + 1 * 60 * 60 * 1000);
|
||||
|
||||
this.logger.log("Successfully obtained new Caprover token");
|
||||
return this.caproverAuthToken;
|
||||
} else {
|
||||
this.logger.error(`Failed to get Caprover token: ${JSON.stringify(response.data)}`);
|
||||
throw new InternalServerErrorException("Failed to get Caprover authentication token");
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
this.logger.error(`Error getting Caprover token: ${error instanceof Error ? error.message : String(error)}`);
|
||||
throw new InternalServerErrorException("Failed to authenticate with Caprover");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Domain - Core method for setting/updating the domain and returning instructions
|
||||
*
|
||||
* This is the main entry point for users to set their domain
|
||||
*/
|
||||
async setDomain(businessId: string, domainDto: UpdateBusinessDomainDto) {
|
||||
const business = await this.businessesService.getBusinessById(businessId);
|
||||
|
||||
@@ -142,12 +82,7 @@ export class DomainVerificationService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify Domain - Check domain ownership and connection status
|
||||
*
|
||||
* This method checks if the domain is properly configured with TXT record and
|
||||
* if it points to our servers
|
||||
*/
|
||||
//--------------------------------
|
||||
async verifyDomain(businessId: string) {
|
||||
const business = await this.businessesService.getBusinessById(businessId);
|
||||
|
||||
@@ -239,7 +174,8 @@ export class DomainVerificationService {
|
||||
}
|
||||
}
|
||||
|
||||
//*********************************** */
|
||||
//--------------------------------
|
||||
|
||||
async getDomainStatus(businessId: string) {
|
||||
const business = await this.businessesService.getBusinessById(businessId);
|
||||
|
||||
@@ -252,6 +188,16 @@ export class DomainVerificationService {
|
||||
|
||||
const domainPointsToServer = await this.checkDomainPointsToServer(business.domain);
|
||||
|
||||
let caproverConfigured = false;
|
||||
if (domainPointsToServer) {
|
||||
try {
|
||||
caproverConfigured = await this.configureCaproverDomain(business.domain);
|
||||
this.logger.log(`Domain ${business.domain} configuration in Caprover: ${caproverConfigured ? "successful" : "failed"}`);
|
||||
} catch (error: unknown) {
|
||||
this.logger.error(`Failed to configure domain in Caprover: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hasDomain: true,
|
||||
domain: business.domain,
|
||||
@@ -268,11 +214,58 @@ export class DomainVerificationService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a domain in Caprover
|
||||
* This method sends a request to Caprover API to add a custom domain to an app
|
||||
*/
|
||||
async configureCaproverDomain(domain: string): Promise<boolean> {
|
||||
//private methods
|
||||
//--------------------------------
|
||||
|
||||
private async enableCaproverHttps(domain: string): Promise<boolean> {
|
||||
try {
|
||||
// Get a fresh token before making the request
|
||||
const token = await this.getCaproverToken();
|
||||
|
||||
this.logger.log(`Enabling HTTPS for domain ${domain} in Caprover`);
|
||||
|
||||
const url = `${this.caproverApiUrl}/user/apps/appDefinitions/enablecustomdomainssl`;
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService
|
||||
.post(
|
||||
url,
|
||||
{
|
||||
appName: this.caproverAppName,
|
||||
customDomain: domain,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"X-Captain-Auth": token,
|
||||
"x-namespace": "captain",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
)
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in enabling https in caprover", err);
|
||||
throw new InternalServerErrorException("error in enabling https in caprover");
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
if (response.status === 200 && response.data?.status === 100) {
|
||||
this.logger.log(`Successfully enabled HTTPS for domain ${domain} in Caprover`);
|
||||
return true;
|
||||
} else {
|
||||
this.logger.error(`Failed to enable HTTPS in Caprover: ${JSON.stringify(response.data)}`);
|
||||
return false;
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
this.logger.error(`Error enabling HTTPS in Caprover: ${error instanceof Error ? error.message : String(error)}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------
|
||||
|
||||
private async configureCaproverDomain(domain: string): Promise<boolean> {
|
||||
try {
|
||||
// Get a fresh token before making the request
|
||||
const token = await this.getCaproverToken();
|
||||
@@ -321,30 +314,30 @@ export class DomainVerificationService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable HTTPS for a domain in Caprover
|
||||
*/
|
||||
private async enableCaproverHttps(domain: string): Promise<boolean> {
|
||||
//--------------------------------
|
||||
private async getCaproverToken(): Promise<string> {
|
||||
try {
|
||||
// Get a fresh token before making the request
|
||||
const token = await this.getCaproverToken();
|
||||
// Check if we already have a valid token
|
||||
if (this.caproverAuthToken && this.tokenExpiry && new Date() < new Date(this.tokenExpiry.getTime() - this.TOKEN_REFRESH_BEFORE_EXPIRY_MS)) {
|
||||
this.logger.debug("Using existing Caprover token");
|
||||
return this.caproverAuthToken;
|
||||
}
|
||||
|
||||
this.logger.log(`Enabling HTTPS for domain ${domain} in Caprover`);
|
||||
this.logger.log("Getting new Caprover authentication token");
|
||||
|
||||
const url = `${this.caproverApiUrl}/user/apps/appDefinitions/enablecustomdomainssl`;
|
||||
const loginUrl = `${this.caproverApiUrl}/login`;
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService
|
||||
.post(
|
||||
url,
|
||||
loginUrl,
|
||||
{
|
||||
appName: this.caproverAppName,
|
||||
customDomain: domain,
|
||||
password: this.caproverPassword,
|
||||
otpToken: "",
|
||||
// email: this.caproverEmail,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"X-Captain-Auth": token,
|
||||
"x-namespace": "captain",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -352,33 +345,38 @@ export class DomainVerificationService {
|
||||
)
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error("error in enabling https in caprover", err);
|
||||
throw new InternalServerErrorException("error in enabling https in caprover");
|
||||
this.logger.error("Error in Caprover authentication", err);
|
||||
throw new InternalServerErrorException("Failed to authenticate with Caprover");
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
if (response.status === 200 && response.data?.status === 100) {
|
||||
this.logger.log(`Successfully enabled HTTPS for domain ${domain} in Caprover`);
|
||||
return true;
|
||||
if (response.status === 200 && response.data?.data?.token) {
|
||||
// Set the token and expiry (default to 24 hours if not provided by Caprover)
|
||||
this.caproverAuthToken = response.data.data.token;
|
||||
|
||||
// Set expiry to 23 hours from now to be safe (Caprover tokens typically last 24 hours)
|
||||
this.tokenExpiry = new Date(Date.now() + 1 * 60 * 60 * 1000);
|
||||
|
||||
this.logger.log("Successfully obtained new Caprover token");
|
||||
return this.caproverAuthToken;
|
||||
} else {
|
||||
this.logger.error(`Failed to enable HTTPS in Caprover: ${JSON.stringify(response.data)}`);
|
||||
return false;
|
||||
this.logger.error(`Failed to get Caprover token: ${JSON.stringify(response.data)}`);
|
||||
throw new InternalServerErrorException("Failed to get Caprover authentication token");
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
this.logger.error(`Error enabling HTTPS in Caprover: ${error instanceof Error ? error.message : String(error)}`);
|
||||
return false;
|
||||
this.logger.error(`Error getting Caprover token: ${error instanceof Error ? error.message : String(error)}`);
|
||||
throw new InternalServerErrorException("Failed to authenticate with Caprover");
|
||||
}
|
||||
}
|
||||
|
||||
//*********************************** */
|
||||
//private methods
|
||||
//--------------------------------
|
||||
|
||||
private generateVerificationToken(): string {
|
||||
return `dzone-verify-${crypto.randomBytes(16).toString("hex")}`;
|
||||
}
|
||||
|
||||
// Get domain connection instructions with enhanced guidance for different DNS providers
|
||||
//--------------------------------
|
||||
|
||||
private getDomainConnectionInstructions(domain: string) {
|
||||
return {
|
||||
title: BusinessMessage.DOMAIN_CONNECTION_TITLE,
|
||||
@@ -409,16 +407,16 @@ export class DomainVerificationService {
|
||||
BusinessMessage.DOMAIN_CONNECTION_TROUBLESHOOTING_2,
|
||||
BusinessMessage.DOMAIN_CONNECTION_TROUBLESHOOTING_3,
|
||||
BusinessMessage.DOMAIN_CONNECTION_TROUBLESHOOTING_4,
|
||||
// Add new troubleshooting instructions for common DNS providers
|
||||
"- اگر از Cloudflare استفاده میکنید، حالت ابری (پروکسی) را خاموش کنید و فقط از DNS استفاده کنید",
|
||||
"- برای دامنه اصلی (@)، برخی ارائه دهندگان DNS به جای CNAME از رکورد ANAME یا ALIAS استفاده میکنند",
|
||||
"- اگر نمیتوانید CNAME برای دامنه اصلی تنظیم کنید، از رکورد A با IP سرور استفاده کنید",
|
||||
BusinessMessage.DOMAIN_CONNECTION_TROUBLESHOOTING_5,
|
||||
BusinessMessage.DOMAIN_CONNECTION_TROUBLESHOOTING_6,
|
||||
BusinessMessage.DOMAIN_CONNECTION_TROUBLESHOOTING_7,
|
||||
],
|
||||
domain: domain,
|
||||
};
|
||||
}
|
||||
|
||||
// Get comprehensive domain setup instructions
|
||||
//--------------------------------
|
||||
|
||||
private getDomainSetupInstructions(domain: string, token: string) {
|
||||
return {
|
||||
verificationInstructions: {
|
||||
@@ -431,22 +429,21 @@ export class DomainVerificationService {
|
||||
},
|
||||
connectionInstructions: this.getDomainConnectionInstructions(domain),
|
||||
general: {
|
||||
verificationEndpoint: `/business/domain/verify/check-dns`,
|
||||
propagationNote: BusinessMessage.DOMAIN_SETUP_PROPAGATION_NOTE,
|
||||
troubleshooting: [
|
||||
BusinessMessage.DOMAIN_SETUP_TROUBLESHOOTING_1,
|
||||
BusinessMessage.DOMAIN_SETUP_TROUBLESHOOTING_2,
|
||||
BusinessMessage.DOMAIN_SETUP_TROUBLESHOOTING_3,
|
||||
BusinessMessage.DOMAIN_SETUP_TROUBLESHOOTING_4,
|
||||
// Add new troubleshooting instructions
|
||||
"- برخی از ارائهدهندگان DNS ممکن است نیاز به تنظیمات خاص برای رکوردهای TXT داشته باشند",
|
||||
"- اگر از Cloudflare استفاده میکنید، مطمئن شوید که رکوردهای TXT را بدون پروکسی تنظیم کردهاید",
|
||||
BusinessMessage.DOMAIN_SETUP_TROUBLESHOOTING_5,
|
||||
BusinessMessage.DOMAIN_SETUP_TROUBLESHOOTING_6,
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Verify domain using DNS TXT record
|
||||
//--------------------------------
|
||||
|
||||
private async checkDnsTxtRecord(domain: string, token: string): Promise<boolean> {
|
||||
try {
|
||||
this.logger.log(`Checking TXT records for domain: ${domain}`);
|
||||
@@ -465,8 +462,8 @@ export class DomainVerificationService {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if domain points to our server via A or CNAME record
|
||||
// Enhanced to handle both root domain and www subdomain
|
||||
//--------------------------------
|
||||
|
||||
private async checkDomainPointsToServer(domain: string): Promise<boolean> {
|
||||
try {
|
||||
this.logger.log(`Checking if domain ${domain} points to our servers`);
|
||||
|
||||
Reference in New Issue
Block a user