From d326d3832949c968410a89f611d1319580a01692 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Sun, 26 Jan 2025 20:06:47 -0500 Subject: [PATCH] feat: Update session cookie domain handling using publicsuffix2 and psl libraries --- backend/server/main/settings.py | 17 ++++++++++++++--- backend/server/requirements.txt | 3 ++- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 16 ++++++++++++++++ frontend/src/routes/login/+page.server.ts | 23 +++++++++++------------ 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/backend/server/main/settings.py b/backend/server/main/settings.py index d8aa026..4471b0d 100644 --- a/backend/server/main/settings.py +++ b/backend/server/main/settings.py @@ -14,6 +14,7 @@ from dotenv import load_dotenv from os import getenv from pathlib import Path from urllib.parse import urlparse +from publicsuffix2 import get_sld # Load environment variables from .env file load_dotenv() @@ -132,16 +133,26 @@ SESSION_COOKIE_SAMESITE = None SESSION_COOKIE_SECURE = FRONTEND_URL.startswith('https') +# Parse the FRONTEND_URL parsed_url = urlparse(FRONTEND_URL) hostname = parsed_url.hostname + +# Check if the hostname is an IP address is_ip_address = hostname.replace('.', '').isdigit() + if is_ip_address: # Do not set a domain for IP addresses SESSION_COOKIE_DOMAIN = None else: - # Calculate the cookie domain for valid domain names - domain_parts = hostname.split('.') - SESSION_COOKIE_DOMAIN = '.' + '.'.join(domain_parts[-2:]) if len(domain_parts) > 1 else hostname + # Use publicsuffix2 to calculate the correct cookie domain + cookie_domain = get_sld(hostname) + if cookie_domain: + SESSION_COOKIE_DOMAIN = f".{cookie_domain}" + else: + # Fallback to the hostname if parsing fails + SESSION_COOKIE_DOMAIN = hostname + +print("SESSION_COOKIE_DOMAIN:", SESSION_COOKIE_DOMAIN) # Static files (CSS, JavaScript, Images) diff --git a/backend/server/requirements.txt b/backend/server/requirements.txt index 80ba65b..dcd0125 100644 --- a/backend/server/requirements.txt +++ b/backend/server/requirements.txt @@ -20,4 +20,5 @@ django-ical==1.9.2 icalendar==6.1.0 ijson==3.3.0 tqdm==4.67.1 -overpy==0.7 \ No newline at end of file +overpy==0.7 +publicsuffix2==2.20191221 \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index fcf148c..b80fe8d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,6 +44,7 @@ "emoji-picker-element": "^1.26.0", "gsap": "^3.12.7", "marked": "^15.0.4", + "psl": "^1.15.0", "qrcode": "^1.5.4", "svelte-i18n": "^4.0.1", "svelte-maplibre": "^0.9.8", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 546738e..ea075fa 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: marked: specifier: ^15.0.4 version: 15.0.4 + psl: + specifier: ^1.15.0 + version: 1.15.0 qrcode: specifier: ^1.5.4 version: 1.5.4 @@ -1913,6 +1916,13 @@ packages: protocol-buffers-schema@3.6.0: resolution: {integrity: sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==} + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + qrcode@1.5.4: resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} engines: {node: '>=10.13.0'} @@ -4154,6 +4164,12 @@ snapshots: protocol-buffers-schema@3.6.0: {} + psl@1.15.0: + dependencies: + punycode: 2.3.1 + + punycode@2.3.1: {} + qrcode@1.5.4: dependencies: dijkstrajs: 1.0.3 diff --git a/frontend/src/routes/login/+page.server.ts b/frontend/src/routes/login/+page.server.ts index b2571a1..f952225 100644 --- a/frontend/src/routes/login/+page.server.ts +++ b/frontend/src/routes/login/+page.server.ts @@ -1,5 +1,6 @@ import { fail, redirect, type RequestEvent } from '@sveltejs/kit'; - +// @ts-ignore +import psl from 'psl'; import type { Actions, PageServerLoad, RouteParams } from './$types'; import { getRandomBackground, getRandomQuote } from '$lib'; import { fetchCSRFToken } from '$lib/index.server'; @@ -105,7 +106,7 @@ export const actions: Actions = { } }; -function handleSuccessfulLogin(event: RequestEvent, response: Response) { +function handleSuccessfulLogin(event: RequestEvent, response: Response) { const setCookieHeader = response.headers.get('Set-Cookie'); if (setCookieHeader) { const sessionIdRegex = /sessionid=([^;]+).*?expires=([^;]+)/; @@ -113,24 +114,22 @@ function handleSuccessfulLogin(event: RequestEvent, response: Response) { if (match) { const [, sessionId, expiryString] = match; - // Get the proper cookie domain + // Get the proper cookie domain using psl const hostname = event.url.hostname; - const domainParts = hostname.split('.'); - let cookieDomain: string | undefined = undefined; + let cookieDomain; // Check if hostname is an IP address const isIPAddress = /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname); if (!isIPAddress) { - if (domainParts.length > 2) { - // For subdomains like app.mydomain.com -> .mydomain.com - cookieDomain = '.' + domainParts.slice(-2).join('.'); - } else if (domainParts.length === 2) { - // For root domains like mydomain.com -> .mydomain.com - cookieDomain = '.' + hostname; + const parsed = psl.parse(hostname); + + if (parsed && parsed.domain) { + // Use the parsed domain (e.g., mydomain.com) + cookieDomain = `.${parsed.domain}`; } } - // Do not set a domain for IP addresses or single-part hostnames + // Do not set a domain for IP addresses or invalid hostnames console.log('Setting sessionid cookie with domain:', cookieDomain);