From b224dd4f688d67fa7e2013ae951cf76ac88c21ce Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Fri, 23 May 2025 21:39:34 +0330 Subject: [PATCH] update: fix --- src/common/enums/message.enum.ts | 5 + .../businesses/businesses.controller.ts | 120 ---------- .../services/domain-verification.service.ts | 217 +++++++++--------- 3 files changed, 112 insertions(+), 230 deletions(-) diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 80f4260..37300f6 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -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 را بدون پروکسی تنظیم کرده‌اید", } diff --git a/src/modules/businesses/businesses.controller.ts b/src/modules/businesses/businesses.controller.ts index 789bb9a..480e64e 100644 --- a/src/modules/businesses/businesses.controller.ts +++ b/src/modules/businesses/businesses.controller.ts @@ -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, - }; - } - } } diff --git a/src/modules/businesses/services/domain-verification.service.ts b/src/modules/businesses/services/domain-verification.service.ts index 4e6e35a..f809e1e 100644 --- a/src/modules/businesses/services/domain-verification.service.ts +++ b/src/modules/businesses/services/domain-verification.service.ts @@ -49,66 +49,6 @@ export class DomainVerificationService { } //-------------------------------- - private async getCaproverToken(): Promise { - 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 { + //private methods + //-------------------------------- + + private async enableCaproverHttps(domain: string): Promise { + 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 { 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 { + //-------------------------------- + private async getCaproverToken(): Promise { 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 { 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 { try { this.logger.log(`Checking if domain ${domain} points to our servers`);