interface Cookie { name: string; value: string; domain: string; path: string; secure?: boolean; httpOnly?: boolean; sameSite?: 'Strict' | 'Lax' | 'None'; } export class CookieConfiguration { private readonly cookie: Cookie; private readonly targetUrl: URL; constructor(cookie: Cookie, targetUrl: string) { this.cookie = cookie; try { this.targetUrl = new URL(targetUrl); } catch (error) { throw new Error(`Invalid target URL: ${targetUrl}`); } this.validate(); } private validate(): void { if (!this.isValidDomain()) { throw new Error( `Domain mismatch: Cookie domain "${this.cookie.domain}" is invalid for target "${this.targetUrl.hostname}"` ); } if (!this.isValidPath()) { throw new Error( `Path not valid: Cookie path "${this.cookie.path}" is invalid for target path "${this.targetUrl.pathname}"` ); } } private isValidDomain(): boolean { const targetHost = this.targetUrl.hostname; const cookieDomain = this.cookie.domain; // Empty domain is invalid if (!cookieDomain) { return false; } // Exact match if (cookieDomain === targetHost) { return true; } // Wildcard domain (e.g., ".iracing.com" matches "members-ng.iracing.com") if (cookieDomain.startsWith('.')) { const domainWithoutDot = cookieDomain.slice(1); return targetHost === domainWithoutDot || targetHost.endsWith('.' + domainWithoutDot); } // Subdomain compatibility: Allow cookies from related subdomains if they share the same base domain // Example: "members.iracing.com" → "members-ng.iracing.com" (both share "iracing.com") if (this.isSameBaseDomain(cookieDomain, targetHost)) { return true; } return false; } /** * Check if two domains share the same base domain (last 2 parts) * @example * isSameBaseDomain('members.iracing.com', 'members-ng.iracing.com') // true * isSameBaseDomain('example.com', 'iracing.com') // false */ private isSameBaseDomain(domain1: string, domain2: string): boolean { const parts1 = domain1.split('.'); const parts2 = domain2.split('.'); // Need at least 2 parts (domain.tld) for valid comparison if (parts1.length < 2 || parts2.length < 2) { return false; } // Compare last 2 parts (e.g., "iracing.com") const base1 = parts1.slice(-2).join('.'); const base2 = parts2.slice(-2).join('.'); return base1 === base2; } private isValidPath(): boolean { // Empty path is invalid if (!this.cookie.path) { return false; } // Path must be prefix of target pathname return this.targetUrl.pathname.startsWith(this.cookie.path); } getValidatedCookie(): Cookie { return { ...this.cookie }; } }