diff --git a/package-lock.json b/package-lock.json index c62e4a9e..18e390fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13389,6 +13389,13 @@ "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", "license": "MIT" }, + "node_modules/@types/psl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/psl/-/psl-1.1.3.tgz", + "integrity": "sha512-Iu174JHfLd7i/XkXY6VDrqSlPvTDQOtQI7wNAXKKOAADJ9TduRLkNdMgjGiMxSttUIZnomv81JAbAbC0DhggxA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/qs": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", @@ -13476,13 +13483,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/tldjs": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@types/tldjs/-/tldjs-2.3.4.tgz", - "integrity": "sha512-QVjtDOA3H6c+6uB+8018XwXUMR9WJ0zaA68lqXmzXETKkp/XD3t0GGyo6OWoKhVT9qVX28RW+zcOuyonIxqEbQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", @@ -16753,6 +16753,21 @@ "node": ">=0.8" } }, + "node_modules/clone-regexp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz", + "integrity": "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==", + "license": "MIT", + "dependencies": { + "is-regexp": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -17006,6 +17021,18 @@ "node": ">= 0.6" } }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -21111,6 +21138,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-0.1.1.tgz", + "integrity": "sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/function.prototype.name": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", @@ -22297,6 +22336,18 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ip-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", + "integrity": "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -22602,6 +22653,22 @@ "node": ">=8" } }, + "node_modules/is-ip": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-5.0.1.tgz", + "integrity": "sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==", + "license": "MIT", + "dependencies": { + "ip-regex": "^5.0.0", + "super-regex": "^0.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -22698,6 +22765,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-set": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", @@ -27712,6 +27791,18 @@ "node": ">=6" } }, + "node_modules/parse-domain": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/parse-domain/-/parse-domain-8.2.2.tgz", + "integrity": "sha512-CoksenD3UDqphCHlXIcNh/TX0dsYLHo6dSAUC/QBcJRWJXcV5rc1mwsS4WbhYGu4LD4Uxc0v3ZzGo+OHCGsLcw==", + "license": "MIT", + "dependencies": { + "is-ip": "^5.0.1" + }, + "bin": { + "parse-domain-update": "bin/update.js" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -28460,17 +28551,18 @@ "license": "MIT" }, "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true, - "license": "MIT" + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.10.0.tgz", + "integrity": "sha512-KSKHEbjAnpUuAUserOq0FxGXCUrzC3WniuSJhvdbs102rL55266ZcHBqLWOsG30spQMlPdpy7icATiAQehg/iA==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + } }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -30674,6 +30766,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/super-regex": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-0.2.0.tgz", + "integrity": "sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==", + "license": "MIT", + "dependencies": { + "clone-regexp": "^3.0.0", + "function-timeout": "^0.1.0", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -31000,6 +31109,21 @@ "dev": true, "license": "MIT" }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "license": "MIT", + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -31054,25 +31178,6 @@ "node": ">=14.0.0" } }, - "node_modules/tldjs": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tldjs/-/tldjs-2.3.1.tgz", - "integrity": "sha512-W/YVH/QczLUxVjnQhFC61Iq232NWu3TqDdO0S/MtXVz4xybejBov4ud+CIwN9aYqjOecEqIy0PscGkwpG9ZyTw==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "punycode": "^1.4.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/tldjs/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "license": "MIT" - }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -35308,13 +35413,14 @@ "dependencies": { "@ory/client-fetch": "^1.15.6", "cookie": "^1.0.1", - "set-cookie-parser": "^2.7.0", - "tldjs": "^2.3.1" + "parse-domain": "^8.2.2", + "psl": "^1.10.0", + "set-cookie-parser": "^2.7.0" }, "devDependencies": { "@types/cookie": "^0.6.0", + "@types/psl": "^1.1.3", "@types/set-cookie-parser": "^2.4.10", - "@types/tldjs": "^2.3.4", "tsup": "8.3.0" }, "peerDependencies": { diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 90925206..14aa257f 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -9,13 +9,14 @@ "dependencies": { "@ory/client-fetch": "^1.15.6", "cookie": "^1.0.1", - "set-cookie-parser": "^2.7.0", - "tldjs": "^2.3.1" + "parse-domain": "^8.2.2", + "psl": "^1.10.0", + "set-cookie-parser": "^2.7.0" }, "devDependencies": { "@types/cookie": "^0.6.0", + "@types/psl": "^1.1.3", "@types/set-cookie-parser": "^2.4.10", - "@types/tldjs": "^2.3.4", "tsup": "8.3.0" }, "keywords": [ diff --git a/packages/nextjs/src/utils/cookie.test.ts b/packages/nextjs/src/utils/cookie.test.ts index 08c6d915..1362aa1a 100644 --- a/packages/nextjs/src/utils/cookie.test.ts +++ b/packages/nextjs/src/utils/cookie.test.ts @@ -1,10 +1,7 @@ -// Copyright © 2024 Ory Corp -// SPDX-License-Identifier: Apache-2.0 - import { guessCookieDomain } from "./cookie" describe("cookie guesser", () => { - test("uses force domain", () => { + test("uses force domain", async () => { expect( guessCookieDomain("https://localhost", { forceCookieDomain: "some-domain", @@ -12,37 +9,46 @@ describe("cookie guesser", () => { ).toEqual("some-domain") }) - test("does not use any guessing domain", () => { - expect(guessCookieDomain("https://localhost", {})).toEqual(undefined) + test("does not use any guessing domain", async () => { + expect(guessCookieDomain("https://localhost", {})).toEqual("localhost") }) - test("is not confused by invalid data", () => { - expect(guessCookieDomain("5qw5tare4g", {})).toEqual(undefined) + test("is not confused by invalid data", async () => { expect(guessCookieDomain("https://123.123.123.123.123", {})).toEqual( undefined, ) }) - test("is not confused by IP", () => { - expect(guessCookieDomain("https://123.123.123.123", {})).toEqual(undefined) - expect( - guessCookieDomain("https://2001:0db8:0000:0000:0000:ff00:0042:8329", {}), - ).toEqual(undefined) + test("is not confused by IPv4", async () => { + expect(guessCookieDomain("https://123.123.123.123", {})).toEqual( + "123.123.123.123", + ) }) - test("returns undefined for public suffix", () => { - expect(guessCookieDomain("https://asdf.vercel.app", {})).toEqual(undefined) + test("is not confused by IPv6", async () => { + expect( + guessCookieDomain("https://2001:0000:130F:0000:0000:09C0:876A:130B", {}), + ).toEqual(undefined) }) - test("uses TLD", () => { - expect(guessCookieDomain("https://foo.localhost", {})).toEqual( - "foo.localhost", + test("uses TLD", async () => { + expect(guessCookieDomain("https://www.example.org", {})).toEqual( + "example.org", ) - expect(guessCookieDomain("https://foo.localhost:1234", {})).toEqual( + expect(guessCookieDomain("https://www.example.org:1234", {})).toEqual( + "example.org", + ) + expect(guessCookieDomain("https://localhost/123", {})).toEqual("localhost") + expect(guessCookieDomain("https://foo.localhost/123", {})).toEqual( "foo.localhost", ) + expect(guessCookieDomain("https://localhost:1234/123", {})).toEqual( + "localhost", + ) + }) + test("understands public suffix list", () => { expect( guessCookieDomain( "https://spark-public.s3.amazonaws.com/dataanalysis/loansData.csv", @@ -53,10 +59,8 @@ describe("cookie guesser", () => { expect(guessCookieDomain("spark-public.s3.amazonaws.com", {})).toEqual( "spark-public.s3.amazonaws.com", ) - - expect(guessCookieDomain("https://localhost/123", {})).toEqual("localhost") - expect(guessCookieDomain("https://localhost:1234/123", {})).toEqual( - "localhost", - ) + expect( + guessCookieDomain("https://docs-gamma-seven.vercel.app", {}), + ).toEqual("docs-gamma-seven.vercel.app") }) }) diff --git a/packages/nextjs/src/utils/cookie.ts b/packages/nextjs/src/utils/cookie.ts index cae2e6af..f349deed 100644 --- a/packages/nextjs/src/utils/cookie.ts +++ b/packages/nextjs/src/utils/cookie.ts @@ -1,28 +1,49 @@ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { parse } from "tldjs" import { OryConfig } from "../types" +import { parse } from "psl" + +function isIPv6(address: string): boolean { + const ipv6Pattern = + /^(?:[a-zA-Z][a-zA-Z\d+.-]*:\/\/)?(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}$|^(?:[a-zA-Z][a-zA-Z\d+.-]*:\/\/)?::(?:[a-fA-F0-9]{1,4}:){0,6}[a-fA-F0-9]{1,4}$|^(?:[a-zA-Z][a-zA-Z\d+.-]*:\/\/)?(?:[a-fA-F0-9]{1,4}:){1,7}:$|^(?:[a-zA-F0-9]{1,4}:){1,6}:[a-fA-F0-9]{1,4}$|^(?:[a-fA-F0-9]{1,4}:){1,5}(?::[a-fA-F0-9]{1,4}){1,2}$|^(?:[a-fA-F0-9]{1,4}:){1,4}(?::[a-fA-F0-9]{1,4}){1,3}$|^(?:[a-fA-F0-9]{1,4}:){1,3}(?::[a-fA-F0-9]{1,4}){1,4}$|^(?:[a-fA-F0-9]{1,4}:){1,2}(?::[a-fA-F0-9]{1,4}){1,5}$|^[a-fA-F0-9]{1,4}:(?::[a-fA-F0-9]{1,4}){1,6}$|^:(?::[a-fA-F0-9]{1,4}){1,7}$|^::$/ + return ipv6Pattern.test(address) +} export function guessCookieDomain(url: string | undefined, config: OryConfig) { if (!url || config.forceCookieDomain) { return config.forceCookieDomain } - const parsed = parse(url || "") - - if (!parsed.isValid || parsed.isIp) { - return undefined + let parsedUrl + try { + parsedUrl = new URL(url).hostname + } catch (e) { + parsedUrl = url } - if (parsed.publicSuffix) { - // We can't set the cookie for public domain suffixes. + const parsed = parse(parsedUrl) + + if (parsed.error) { return undefined } - if (!parsed.domain) { - return parsed.hostname + if (isIPAddress(parsedUrl)) { + return parsedUrl } - return parsed.domain + return parsed.domain || parsed.input +} + +// Helper function to check if the hostname is an IP address +export function isIPAddress(hostname: string) { + // IPv4 pattern: four groups of 1-3 digits, separated by dots, each between 0-255 + const ipv4Pattern = + /^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})){3}$/ + + // IPv6 pattern: eight groups of 1-4 hexadecimal digits, separated by colons, optional shorthand (::) + const ipv6Pattern = + /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))$/ + + return ipv4Pattern.test(hostname) || ipv6Pattern.test(hostname) }