diff --git a/src/lib/server/email/__tests__/generate-base-url.test.ts b/src/lib/server/email/__tests__/generate-base-url.test.ts index a34d581..869a452 100644 --- a/src/lib/server/email/__tests__/generate-base-url.test.ts +++ b/src/lib/server/email/__tests__/generate-base-url.test.ts @@ -1,6 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { generateBaseUrl } from "../email-service"; -import type { SelectTenant } from "$lib/server/db/central-schema"; // Mock NODE_ENV const mockEnv = vi.hoisted(() => ({ @@ -22,365 +21,16 @@ describe("generateBaseUrl", () => { vi.clearAllMocks(); }); - describe("Development/Local Environment", () => { - it("should return localhost URL regardless of tenant", () => { - const requestUrl = new URL("http://localhost:5173"); - const tenant: SelectTenant = { - id: "tenant-1", - shortName: "acme", - longName: "ACME Corp", - logo: null, - createdAt: new Date(), - updatedAt: new Date(), - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "tenant.example.com", - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("http://localhost:5173"); - }); - - it("should return localhost URL for null tenant", () => { - const requestUrl = new URL("http://localhost:3000"); - - const result = generateBaseUrl(requestUrl, null); - expect(result).toBe("http://localhost:3000"); - }); - - it("should preserve port for localhost", () => { - const requestUrl = new URL("http://localhost:8080"); - const tenant: SelectTenant = { - id: "tenant-1", - shortName: "test", - longName: "Test Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "tenant.example.com", - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("http://localhost:8080"); - }); - - it("should handle 127.x.x.x addresses", () => { - const requestUrl = new URL("http://127.0.0.1:3000"); - const tenant: SelectTenant = { - id: "tenant-1", - shortName: "acme", - longName: "ACME Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "tenant.example.com", - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("http://127.0.0.1:3000"); - }); - - it("should handle 192.168.x.x addresses", () => { - const requestUrl = new URL("http://192.168.1.100:8080"); - const tenant: SelectTenant = { - id: "tenant-1", - shortName: "acme", - longName: "ACME Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "tenant.example.com", - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("http://192.168.1.100:8080"); - }); + it("should return localhost with port", () => { + const requestUrl = new URL("http://localhost:5173"); + const result = generateBaseUrl(requestUrl); + expect(result).toBe("http://localhost:5173"); }); - describe("Production Environment", () => { - beforeEach(() => { - mockEnv.NODE_ENV = "production"; - }); + it("should return tenant domain", () => { + const requestUrl = new URL("https://example.com"); - it("should return main domain for null tenant", () => { - const requestUrl = new URL("https://example.com"); - - const result = generateBaseUrl(requestUrl, null); - expect(result).toBe("https://example.com"); - }); - - it("should return main domain with port for null tenant", () => { - const requestUrl = new URL("https://example.com:8443"); - - const result = generateBaseUrl(requestUrl, null); - expect(result).toBe("https://example.com:8443"); - }); - - it("should create subdomain URL for tenant on main domain", () => { - const requestUrl = new URL("https://example.com"); - const tenant: SelectTenant = { - id: "tenant-1", - shortName: "acme", - domain: "acme", - longName: "ACME Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("https://acme.example.com"); - }); - - it("should create subdomain URL with port", () => { - const requestUrl = new URL("https://example.com:8443"); - const tenant: SelectTenant = { - id: "tenant-1", - shortName: "acme", - longName: "ACME Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "acme", - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("https://acme.example.com:8443"); - }); - - it("should replace existing subdomain with tenant shortName", () => { - const requestUrl = new URL("https://old-tenant.example.com"); - const tenant: SelectTenant = { - id: "tenant-2", - shortName: "new-tenant", - longName: "New Tenant Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "new-tenant", - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("https://new-tenant.old-tenant.example.com"); - }); - - it("should replace existing subdomain with port", () => { - const requestUrl = new URL("https://old-tenant.example.com:8443"); - const tenant: SelectTenant = { - id: "tenant-2", - shortName: "new-tenant", - longName: "New Tenant Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "new-tenant", - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("https://new-tenant.old-tenant.example.com:8443"); - }); - - it("should handle complex subdomains (keep last two parts)", () => { - const requestUrl = new URL("https://admin.api.example.com"); - const tenant: SelectTenant = { - id: "tenant-1", - shortName: "tenant", - longName: "Tenant Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "tenant", - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("https://tenant.admin.api.example.com"); - }); - - it("should handle http protocol", () => { - const requestUrl = new URL("http://example.com"); - const tenant: SelectTenant = { - id: "tenant-1", - shortName: "acme", - longName: "ACME Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "acme", - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("http://acme.example.com"); - }); - - it("should return main domain when tenant has no shortName", () => { - const requestUrl = new URL("https://example.com"); - const tenant: SelectTenant = { - id: "tenant-1", - shortName: "", // Empty shortName - longName: "ACME Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "", - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("https://example.com"); - }); - }); - - describe("Edge Cases", () => { - it("should handle single domain names in production", () => { - mockEnv.NODE_ENV = "production"; - const requestUrl = new URL("https://app"); - const tenant: SelectTenant = { - id: "tenant-1", - shortName: "acme", - longName: "ACME Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "acme", - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("https://acme.app"); - }); - - it("should handle localhost in production (still treated as development)", () => { - mockEnv.NODE_ENV = "production"; - const requestUrl = new URL("https://localhost:8443"); - const tenant: SelectTenant = { - id: "tenant-1", - shortName: "acme", - longName: "ACME Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "tenant.example.com", - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("https://localhost:8443"); - }); - - it("should handle IP addresses as development", () => { - mockEnv.NODE_ENV = "production"; - const requestUrl = new URL("https://192.168.1.100:8443"); - const tenant: SelectTenant = { - id: "tenant-1", - shortName: "acme", - longName: "ACME Corp", - descriptions: { en: "" }, - languages: ["en"], - defaultLanguage: "en", - databaseUrl: "", - setupState: "SETTINGS", - logo: null, - links: { website: "", imprint: "", privacyStatement: "" }, - domain: "tenant.example.com", - createdAt: new Date(), - updatedAt: new Date(), - }; - - const result = generateBaseUrl(requestUrl, tenant); - expect(result).toBe("https://192.168.1.100:8443"); - }); - - it("should handle default HTTP port (80)", () => { - mockEnv.NODE_ENV = "production"; - const requestUrl = new URL("http://example.com:80"); - - // URL constructor should handle port 80 correctly - const result = generateBaseUrl(requestUrl, null); - // Default HTTP port shouldn't be included in URL - expect(result).toBe("http://example.com"); - }); - - it("should handle default HTTPS port (443)", () => { - mockEnv.NODE_ENV = "production"; - const requestUrl = new URL("https://example.com:443"); - - // URL constructor should handle port 443 correctly - const result = generateBaseUrl(requestUrl, null); - // Default HTTPS port shouldn't be included in URL - expect(result).toBe("https://example.com"); - }); + const result = generateBaseUrl(requestUrl); + expect(result).toBe("https://example.com"); }); }); diff --git a/src/lib/server/email/email-service.ts b/src/lib/server/email/email-service.ts index f654e58..69e6d13 100644 --- a/src/lib/server/email/email-service.ts +++ b/src/lib/server/email/email-service.ts @@ -147,7 +147,7 @@ export async function sendPinResetEmail( locale, user, tenant, - loginUrl: generateBaseUrl(requestUrl, tenant) ?? "http://localhost:5173", + loginUrl: generateBaseUrl(requestUrl), }, }); const html = renderOutputToHtml(emailRender); @@ -399,23 +399,10 @@ export async function sendAppointmentUpdatedEmail( * @param {SelectTenant | null} tenant - Tenant information (null for global admin) * @returns {string} The appropriate base URL */ -export function generateBaseUrl(requestUrl: URL, tenant: SelectTenant | null): string { - const protocol = requestUrl.protocol; - const port = requestUrl.port ? `:${requestUrl.port}` : ""; - const hostname = requestUrl.hostname; - - // In development, always use the original hostname regardless of tenant - if (hostname === "localhost" || hostname.startsWith("127.") || hostname.startsWith("192.168.")) { - return `${protocol}//${hostname}${port}`; - } - - // In production, handle tenant subdomains. - // Exclude the system tenant when determining if we should use the tenant's domain for the URL, as the system tenant does not have a domain and should use the main domain. - if (tenant?.domain && tenant.id !== "system") { - return `${protocol}//${tenant.domain}.${hostname}${port}`; - } - - // For global admin or no tenant, use main domain +export function generateBaseUrl(url: URL): string { + const protocol = url.protocol; + const port = url.port ? `:${url.port}` : ""; + const hostname = url.hostname; return `${protocol}//${hostname}${port}`; } @@ -437,7 +424,7 @@ export async function sendConfirmationEmail( requestUrl: URL, ): Promise { // Generate appropriate base URL if request URL is provided - const baseUrl = requestUrl ? generateBaseUrl(requestUrl, tenant) : "http://localhost:5173"; + const baseUrl = generateBaseUrl(requestUrl); const confirmUrl = `${baseUrl}/confirm/${confirmationCode}`; const recipient = user; // Generate email