diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb9dcfb03..9a742e8bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: package_json_file: ./frontend/package.json - name: Setup go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "^1.26.4" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f37686802..08d52488a 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -68,7 +68,7 @@ jobs: package_json_file: ./frontend/package.json - name: Install go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "^1.26.4" @@ -117,7 +117,7 @@ jobs: package_json_file: ./frontend/package.json - name: Install go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "^1.26.4" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4ba7cf750..e054e53d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: package_json_file: ./frontend/package.json - name: Install go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "^1.26.4" @@ -92,7 +92,7 @@ jobs: package_json_file: ./frontend/package.json - name: Install go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "^1.26.4" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 4a8565e63..0379391f7 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -38,6 +38,6 @@ jobs: retention-days: 5 - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4 + uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4 with: sarif_file: results.sarif diff --git a/frontend/package.json b/frontend/package.json index e88493646..88bee87a4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,7 +32,7 @@ "react": "^19.2.7", "react-dom": "^19.2.7", "react-hook-form": "^7.81.0", - "react-i18next": "^17.0.9", + "react-i18next": "^17.0.10", "react-markdown": "^10.1.0", "react-router": "^8.2.0", "sonner": "^2.0.7", @@ -47,14 +47,14 @@ "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.3", - "eslint": "^10.6.0", + "eslint": "^10.7.0", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.5.3", "globals": "^17.7.0", "rollup-plugin-visualizer": "^7.0.1", "tw-animate-css": "^1.4.0", "typescript": "~6.0.2", - "typescript-eslint": "^8.63.0", - "vite": "^8.1.4" + "typescript-eslint": "^8.64.0", + "vite": "^8.1.5" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index d99868d54..3fa02f8b5 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: version: 1.3.0(@types/react@19.2.17)(react@19.2.7) '@tailwindcss/vite': specifier: ^4.3.2 - version: 4.3.2(vite@8.1.4(@types/node@26.1.1)(jiti@2.7.0)) + version: 4.3.2(vite@8.1.5(@types/node@26.1.1)(jiti@2.7.0)) '@tanstack/react-query': specifier: ^5.101.2 version: 5.101.2(react@19.2.7) @@ -69,8 +69,8 @@ importers: specifier: ^7.81.0 version: 7.81.0(react@19.2.7) react-i18next: - specifier: ^17.0.9 - version: 17.0.9(i18next@26.3.6(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + specifier: ^17.0.10 + version: 17.0.10(i18next@26.3.6(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) react-markdown: specifier: ^10.1.0 version: 10.1.0(@types/react@19.2.17)(react@19.2.7) @@ -92,10 +92,10 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.6.0(jiti@2.7.0)) + version: 10.0.1(eslint@10.7.0(jiti@2.7.0)) '@tanstack/eslint-plugin-query': specifier: ^5.101.2 - version: 5.101.2(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + version: 5.101.2(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@types/node': specifier: ^26.1.1 version: 26.1.1 @@ -107,16 +107,16 @@ importers: version: 19.2.3(@types/react@19.2.17) '@vitejs/plugin-react': specifier: ^6.0.3 - version: 6.0.3(vite@8.1.4(@types/node@26.1.1)(jiti@2.7.0)) + version: 6.0.3(vite@8.1.5(@types/node@26.1.1)(jiti@2.7.0)) eslint: - specifier: ^10.6.0 - version: 10.6.0(jiti@2.7.0) + specifier: ^10.7.0 + version: 10.7.0(jiti@2.7.0) eslint-plugin-react-hooks: specifier: ^7.0.1 - version: 7.1.1(eslint@10.6.0(jiti@2.7.0)) + version: 7.1.1(eslint@10.7.0(jiti@2.7.0)) eslint-plugin-react-refresh: specifier: ^0.5.3 - version: 0.5.3(eslint@10.6.0(jiti@2.7.0)) + version: 0.5.3(eslint@10.7.0(jiti@2.7.0)) globals: specifier: ^17.7.0 version: 17.7.0 @@ -130,11 +130,11 @@ importers: specifier: ~6.0.2 version: 6.0.3 typescript-eslint: - specifier: ^8.63.0 - version: 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + specifier: ^8.64.0 + version: 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) vite: - specifier: ^8.1.4 - version: 8.1.4(@types/node@26.1.1)(jiti@2.7.0) + specifier: ^8.1.5 + version: 8.1.5(@types/node@26.1.1)(jiti@2.7.0) packages: @@ -1272,16 +1272,16 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@typescript-eslint/eslint-plugin@8.63.0': - resolution: {integrity: sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==} + '@typescript-eslint/eslint-plugin@8.64.0': + resolution: {integrity: sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.63.0 + '@typescript-eslint/parser': ^8.64.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.63.0': - resolution: {integrity: sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==} + '@typescript-eslint/parser@8.64.0': + resolution: {integrity: sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1293,8 +1293,8 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.63.0': - resolution: {integrity: sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==} + '@typescript-eslint/project-service@8.64.0': + resolution: {integrity: sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' @@ -1303,8 +1303,8 @@ packages: resolution: {integrity: sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.63.0': - resolution: {integrity: sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==} + '@typescript-eslint/scope-manager@8.64.0': + resolution: {integrity: sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/tsconfig-utils@8.62.1': @@ -1313,14 +1313,14 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/tsconfig-utils@8.63.0': - resolution: {integrity: sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==} + '@typescript-eslint/tsconfig-utils@8.64.0': + resolution: {integrity: sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.63.0': - resolution: {integrity: sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==} + '@typescript-eslint/type-utils@8.64.0': + resolution: {integrity: sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1330,8 +1330,8 @@ packages: resolution: {integrity: sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.63.0': - resolution: {integrity: sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==} + '@typescript-eslint/types@8.64.0': + resolution: {integrity: sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@8.62.1': @@ -1340,8 +1340,8 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/typescript-estree@8.63.0': - resolution: {integrity: sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==} + '@typescript-eslint/typescript-estree@8.64.0': + resolution: {integrity: sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' @@ -1353,8 +1353,8 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.63.0': - resolution: {integrity: sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==} + '@typescript-eslint/utils@8.64.0': + resolution: {integrity: sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1364,8 +1364,8 @@ packages: resolution: {integrity: sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.63.0': - resolution: {integrity: sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==} + '@typescript-eslint/visitor-keys@8.64.0': + resolution: {integrity: sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.1': @@ -1603,8 +1603,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.6.0: - resolution: {integrity: sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==} + eslint@10.7.0: + resolution: {integrity: sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -1784,8 +1784,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + ignore@7.0.6: + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} engines: {node: '>= 4'} imurmurhash@0.1.4: @@ -2071,8 +2071,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.15: - resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + nanoid@3.3.16: + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -2126,8 +2126,8 @@ packages: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} - postcss@8.5.16: - resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} + postcss@8.5.19: + resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} engines: {node: ^10 || ^12 || >=14} powershell-utils@0.1.0: @@ -2173,8 +2173,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - react-i18next@17.0.9: - resolution: {integrity: sha512-buLzOSqHtXxjf+qgSrLWNTXVZ1jSwO6kUv3uJqSP1roGBPgNnbhFm7OmdVwWcgf2gIbUyP0J333uPyx+Btsi3w==} + react-i18next@17.0.10: + resolution: {integrity: sha512-XneHftyYA774MJkkccSkZ5oKrUpCnXIPmxio3wemqrVzCRLWiGXOMbIzObrer03fNDEnm8g8R5yYls4HcE+esg==} peerDependencies: i18next: '>= 26.2.0' react: '>= 16.8.0' @@ -2357,8 +2357,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.63.0: - resolution: {integrity: sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==} + typescript-eslint@8.64.0: + resolution: {integrity: sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2430,8 +2430,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite@8.1.4: - resolution: {integrity: sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==} + vite@8.1.5: + resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2647,9 +2647,9 @@ snapshots: tslib: 2.8.1 optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0(jiti@2.7.0))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.7.0(jiti@2.7.0))': dependencies: - eslint: 10.6.0(jiti@2.7.0) + eslint: 10.7.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -2670,9 +2670,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.6.0(jiti@2.7.0))': + '@eslint/js@10.0.1(eslint@10.7.0(jiti@2.7.0))': optionalDependencies: - eslint: 10.6.0(jiti@2.7.0) + eslint: 10.7.0(jiti@2.7.0) '@eslint/object-schema@3.0.5': {} @@ -3607,17 +3607,17 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.2 '@tailwindcss/oxide-win32-x64-msvc': 4.3.2 - '@tailwindcss/vite@4.3.2(vite@8.1.4(@types/node@26.1.1)(jiti@2.7.0))': + '@tailwindcss/vite@4.3.2(vite@8.1.5(@types/node@26.1.1)(jiti@2.7.0))': dependencies: '@tailwindcss/node': 4.3.2 '@tailwindcss/oxide': 4.3.2 tailwindcss: 4.3.2 - vite: 8.1.4(@types/node@26.1.1)(jiti@2.7.0) + vite: 8.1.5(@types/node@26.1.1)(jiti@2.7.0) - '@tanstack/eslint-plugin-query@5.101.2(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + '@tanstack/eslint-plugin-query@5.101.2(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.6.0(jiti@2.7.0) + '@typescript-eslint/utils': 8.62.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) optionalDependencies: typescript: 6.0.3 transitivePeerDependencies: @@ -3675,30 +3675,30 @@ snapshots: '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.63.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.64.0(@typescript-eslint/parser@8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.63.0 - '@typescript-eslint/type-utils': 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/utils': 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.63.0 - eslint: 10.6.0(jiti@2.7.0) - ignore: 7.0.5 + '@typescript-eslint/parser': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.64.0 + '@typescript-eslint/type-utils': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.64.0 + eslint: 10.7.0(jiti@2.7.0) + ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/parser@8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.63.0 - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.63.0 + '@typescript-eslint/scope-manager': 8.64.0 + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.64.0 debug: 4.4.3 - eslint: 10.6.0(jiti@2.7.0) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -3712,10 +3712,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.63.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.64.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.63.0(typescript@6.0.3) - '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/tsconfig-utils': 8.64.0(typescript@6.0.3) + '@typescript-eslint/types': 8.64.0 debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: @@ -3726,26 +3726,26 @@ snapshots: '@typescript-eslint/types': 8.62.1 '@typescript-eslint/visitor-keys': 8.62.1 - '@typescript-eslint/scope-manager@8.63.0': + '@typescript-eslint/scope-manager@8.64.0': dependencies: - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/visitor-keys': 8.63.0 + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/visitor-keys': 8.64.0 '@typescript-eslint/tsconfig-utils@8.62.1(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/tsconfig-utils@8.63.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.64.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 10.6.0(jiti@2.7.0) + eslint: 10.7.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -3753,7 +3753,7 @@ snapshots: '@typescript-eslint/types@8.62.1': {} - '@typescript-eslint/types@8.63.0': {} + '@typescript-eslint/types@8.64.0': {} '@typescript-eslint/typescript-estree@8.62.1(typescript@6.0.3)': dependencies: @@ -3770,12 +3770,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.63.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.64.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.63.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.63.0(typescript@6.0.3) - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/visitor-keys': 8.63.0 + '@typescript-eslint/project-service': 8.64.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.64.0(typescript@6.0.3) + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/visitor-keys': 8.64.0 debug: 4.4.3 minimatch: 10.2.5 semver: 7.8.5 @@ -3785,24 +3785,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/utils@8.62.1(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.62.1 '@typescript-eslint/types': 8.62.1 '@typescript-eslint/typescript-estree': 8.62.1(typescript@6.0.3) - eslint: 10.6.0(jiti@2.7.0) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/utils@8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.63.0 - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) - eslint: 10.6.0(jiti@2.7.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.64.0 + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -3812,17 +3812,17 @@ snapshots: '@typescript-eslint/types': 8.62.1 eslint-visitor-keys: 5.0.1 - '@typescript-eslint/visitor-keys@8.63.0': + '@typescript-eslint/visitor-keys@8.64.0': dependencies: - '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/types': 8.64.0 eslint-visitor-keys: 5.0.1 '@ungap/structured-clone@1.3.1': {} - '@vitejs/plugin-react@6.0.3(vite@8.1.4(@types/node@26.1.1)(jiti@2.7.0))': + '@vitejs/plugin-react@6.0.3(vite@8.1.5(@types/node@26.1.1)(jiti@2.7.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.1.4(@types/node@26.1.1)(jiti@2.7.0) + vite: 8.1.5(@types/node@26.1.1)(jiti@2.7.0) acorn-jsx@5.3.2(acorn@8.17.0): dependencies: @@ -3997,20 +3997,20 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@7.1.1(eslint@10.6.0(jiti@2.7.0)): + eslint-plugin-react-hooks@7.1.1(eslint@10.7.0(jiti@2.7.0)): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.3 - eslint: 10.6.0(jiti@2.7.0) + eslint: 10.7.0(jiti@2.7.0) hermes-parser: 0.25.1 zod: 4.4.3 zod-validation-error: 4.0.2(zod@4.4.3) transitivePeerDependencies: - supports-color - eslint-plugin-react-refresh@0.5.3(eslint@10.6.0(jiti@2.7.0)): + eslint-plugin-react-refresh@0.5.3(eslint@10.7.0(jiti@2.7.0)): dependencies: - eslint: 10.6.0(jiti@2.7.0) + eslint: 10.7.0(jiti@2.7.0) eslint-scope@9.1.2: dependencies: @@ -4023,9 +4023,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.6.0(jiti@2.7.0): + eslint@10.7.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.6.0 @@ -4226,7 +4226,7 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.5: {} + ignore@7.0.6: {} imurmurhash@0.1.4: {} @@ -4591,7 +4591,7 @@ snapshots: ms@2.1.3: {} - nanoid@3.3.15: {} + nanoid@3.3.16: {} natural-compare@1.4.0: {} @@ -4648,9 +4648,9 @@ snapshots: picomatch@4.0.5: {} - postcss@8.5.16: + postcss@8.5.19: dependencies: - nanoid: 3.3.15 + nanoid: 3.3.16 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -4736,7 +4736,7 @@ snapshots: dependencies: react: 19.2.7 - react-i18next@17.0.9(i18next@26.3.6(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3): + react-i18next@17.0.10(i18next@26.3.6(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3): dependencies: '@babel/runtime': 7.29.7 html-parse-stringify: 3.0.1 @@ -4923,13 +4923,13 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3): + typescript-eslint@8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.63.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/parser': 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.6.0(jiti@2.7.0) + '@typescript-eslint/eslint-plugin': 8.64.0(@typescript-eslint/parser@8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -5010,11 +5010,11 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@8.1.4(@types/node@26.1.1)(jiti@2.7.0): + vite@8.1.5(@types/node@26.1.1)(jiti@2.7.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.5 - postcss: 8.5.16 + postcss: 8.5.19 rolldown: 1.1.5 tinyglobby: 0.2.17 optionalDependencies: diff --git a/frontend/src/components/quick-actions/quick-actions.tsx b/frontend/src/components/quick-actions/quick-actions.tsx index c141f6160..cc33b2f43 100644 --- a/frontend/src/components/quick-actions/quick-actions.tsx +++ b/frontend/src/components/quick-actions/quick-actions.tsx @@ -32,8 +32,8 @@ import { useTranslation } from "react-i18next"; import { useLocation } from "react-router"; import { useRef } from "react"; import { + searchParamsFromObject, useScreenParams, - recompileScreenParams, } from "@/lib/hooks/screen-params"; import { useMutation } from "@tanstack/react-query"; import axios from "axios"; @@ -70,7 +70,13 @@ export const QuickActions = () => { const redirectTimer = useRef(null); const searchParams = new URLSearchParams(search); const screenParams = useScreenParams(searchParams); - const compiledParams = recompileScreenParams(screenParams); + const compiledParams = (() => { + const params = searchParamsFromObject(screenParams).toString(); + if (params.length > 0) { + return `?${params}`; + } + return ""; + })(); const [isOpen, setIsOpen] = useState(false); diff --git a/frontend/src/lib/hooks/login-for.ts b/frontend/src/lib/hooks/login-for.ts index 8cf115794..b106c5d7e 100644 --- a/frontend/src/lib/hooks/login-for.ts +++ b/frontend/src/lib/hooks/login-for.ts @@ -1,10 +1,11 @@ type UseLoginForProps = { login_for?: "oidc" | "app"; - compiledParams: string; + params: URLSearchParams; }; export const useLoginFor = (props: UseLoginForProps): string => { - const { login_for, compiledParams } = props; + const { login_for, params } = props; + const compiledParams = params.toString() ? "?" + params.toString() : ""; switch (login_for) { case "oidc": @@ -12,6 +13,9 @@ export const useLoginFor = (props: UseLoginForProps): string => { case "app": return "/continue" + compiledParams; default: + if (params.get("redirect_uri")) { + return "/continue" + compiledParams + } return "/logout"; } }; diff --git a/frontend/src/lib/hooks/redirect-uri.ts b/frontend/src/lib/hooks/redirect-uri.ts index c4fc9a125..f7048ffee 100644 --- a/frontend/src/lib/hooks/redirect-uri.ts +++ b/frontend/src/lib/hooks/redirect-uri.ts @@ -88,13 +88,29 @@ const getEffectivePort = (url: URL): string => { return "80"; }; +// https://www.geeksforgeeks.org/javascript/how-to-check-if-a-string-is-a-valid-ip-address-format-in-javascript +const isIP = (str: string): boolean => { + const ipv4 = + /^(\d{1,3}\.){3}\d{1,3}$/; + const ipv6 = + /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/; + return ipv4.test(str) || ipv6.test(str) || str.startsWith("["); +} + +const trimPeriod = (str: string): string => { + if(str.lastIndexOf('.') === (str.length - 1)){ + str = str.substring(0, str.length - 1); + } + return str +} + export const isTrustedDomain = ( url: URL, appUrl: URL, cookieDomain: string, subdomainsEnabled: boolean, ): boolean => { - if (url.protocol != appUrl.protocol) { + if (isIP(url.hostname)) { return false; } @@ -102,7 +118,7 @@ export const isTrustedDomain = ( return false; } - if (url.hostname == appUrl.hostname) { + if (trimPeriod(url.hostname) == trimPeriod(appUrl.hostname)) { return true; } @@ -110,9 +126,6 @@ export const isTrustedDomain = ( return false; } - if (url.hostname.endsWith("." + cookieDomain.toLowerCase())) { - return true; - } - - return false; + return trimPeriod(url.hostname).endsWith("." + cookieDomain.toLowerCase()) + || trimPeriod(url.hostname) == cookieDomain.toLowerCase(); }; diff --git a/frontend/src/lib/hooks/screen-params.ts b/frontend/src/lib/hooks/screen-params.ts index abf3a41a5..6a459733e 100644 --- a/frontend/src/lib/hooks/screen-params.ts +++ b/frontend/src/lib/hooks/screen-params.ts @@ -27,16 +27,10 @@ export function useScreenParams(params: URLSearchParams): ScreenParams { return parsed.data; } -export function recompileScreenParams(params: ScreenParams): string { - const p = new URLSearchParams( +export function searchParamsFromObject(obj: object): URLSearchParams { + return new URLSearchParams( Object.fromEntries( - Object.entries(params).filter(([, v]) => v !== undefined), + Object.entries(obj).filter(([, v]) => v !== undefined), ) as Record, - ).toString(); - - if (p.length > 0) { - return "?" + p; - } - - return ""; + ); } diff --git a/frontend/src/pages/authorize-page.tsx b/frontend/src/pages/authorize-page.tsx index 0f14a5830..17aa3df7e 100644 --- a/frontend/src/pages/authorize-page.tsx +++ b/frontend/src/pages/authorize-page.tsx @@ -22,7 +22,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { - recompileScreenParams, + searchParamsFromObject, useScreenParams, } from "@/lib/hooks/screen-params"; import { useEffect } from "react"; @@ -89,7 +89,13 @@ export const AuthorizePage = () => { const searchParams = new URLSearchParams(search); const screenParams = useScreenParams(searchParams); const isOidc = screenParams.login_for === "oidc"; - const compiledParams = recompileScreenParams(screenParams); + const compiledParams = (() => { + const params = searchParamsFromObject(screenParams).toString(); + if (params.length > 0) { + return `?${params}`; + } + return ""; + })(); // TODO: maybe a better way to do this const shouldAutoAuthorize = diff --git a/frontend/src/pages/continue-page.tsx b/frontend/src/pages/continue-page.tsx index a4e34fc5c..7b7cdb72f 100644 --- a/frontend/src/pages/continue-page.tsx +++ b/frontend/src/pages/continue-page.tsx @@ -13,7 +13,7 @@ import { Navigate, useLocation, useNavigate } from "react-router"; import { useCallback, useEffect, useRef, useState } from "react"; import { useRedirectUri } from "@/lib/hooks/redirect-uri"; import { - recompileScreenParams, + searchParamsFromObject, useScreenParams, } from "@/lib/hooks/screen-params"; @@ -32,7 +32,13 @@ export const ContinuePage = () => { const screenParams = useScreenParams(searchParams); const redirectUri = screenParams.redirect_uri; const isAppLogin = screenParams.login_for === "app"; - const recompiledParams = recompileScreenParams(screenParams); + const compiledParams = (() => { + const params = searchParamsFromObject(screenParams).toString(); + if (params.length > 0) { + return `?${params}`; + } + return ""; + })(); const { url, valid, trusted, allowedProto, httpsDowngrade } = useRedirectUri( redirectUri, @@ -89,7 +95,7 @@ export const ContinuePage = () => { }, [shouldAutoRedirect, redirectToTarget]); if (!auth.authenticated) { - return ; + return ; } if (!hasValidRedirect || !isAppLogin) { diff --git a/frontend/src/pages/forgot-password-page.tsx b/frontend/src/pages/forgot-password-page.tsx index 58b80a3ab..8052701e0 100644 --- a/frontend/src/pages/forgot-password-page.tsx +++ b/frontend/src/pages/forgot-password-page.tsx @@ -12,8 +12,8 @@ import { useTranslation } from "react-i18next"; import Markdown from "react-markdown"; import { useLocation } from "react-router"; import { - recompileScreenParams, - useScreenParams, + searchParamsFromObject, + useScreenParams, } from "@/lib/hooks/screen-params"; export const ForgotPasswordPage = () => { @@ -22,7 +22,13 @@ export const ForgotPasswordPage = () => { const { search } = useLocation(); const searchParams = new URLSearchParams(search); const screenParams = useScreenParams(searchParams); - const compiledParams = recompileScreenParams(screenParams); + const compiledParams = (() => { + const params = searchParamsFromObject(screenParams).toString(); + if (params.length > 0) { + return `?${params}`; + } + return ""; + })(); return ( diff --git a/frontend/src/pages/login-page.tsx b/frontend/src/pages/login-page.tsx index ab63c72a2..08218e00d 100644 --- a/frontend/src/pages/login-page.tsx +++ b/frontend/src/pages/login-page.tsx @@ -26,7 +26,7 @@ import { useTranslation } from "react-i18next"; import { Navigate, useLocation } from "react-router"; import { toast } from "sonner"; import { - recompileScreenParams, + searchParamsFromObject, useScreenParams, } from "@/lib/hooks/screen-params"; import { useLoginFor } from "@/lib/hooks/login-for"; @@ -63,13 +63,16 @@ export const LoginPage = () => { const searchParams = new URLSearchParams(search); const screenParams = useScreenParams(searchParams); - const compiledParams = recompileScreenParams({ - ...screenParams, - oidc_prompt: undefined, - }); + const compiledParams = (() => { + const params = searchParamsFromObject(screenParams).toString(); + if (params.length > 0) { + return `?${params}`; + } + return ""; + })(); const loginForUrl = useLoginFor({ login_for: screenParams.login_for, - compiledParams, + params: searchParamsFromObject({ ...screenParams, oidc_prompt: undefined}), }); const [isOauthAutoRedirect, setIsOauthAutoRedirect] = useState( diff --git a/frontend/src/pages/logout-page.tsx b/frontend/src/pages/logout-page.tsx index d1a734b99..78ef05554 100644 --- a/frontend/src/pages/logout-page.tsx +++ b/frontend/src/pages/logout-page.tsx @@ -17,8 +17,8 @@ import { type UseMutationResult } from "@tanstack/react-query"; import { type AxiosResponse } from "axios"; import { useLocation } from "react-router"; import { + searchParamsFromObject, useScreenParams, - recompileScreenParams, } from "@/lib/hooks/screen-params"; export const LogoutPage = () => { @@ -29,7 +29,13 @@ export const LogoutPage = () => { const redirectTimer = useRef(null); const searchParams = new URLSearchParams(search); const screenParams = useScreenParams(searchParams); - const compiledParams = recompileScreenParams(screenParams); + const compiledParams = (() => { + const params = searchParamsFromObject(screenParams).toString(); + if (params.length > 0) { + return `?${params}`; + } + return ""; + })(); const logoutMutation = useMutation({ mutationFn: () => axios.post("/api/user/logout"), diff --git a/frontend/src/pages/totp-page.tsx b/frontend/src/pages/totp-page.tsx index e66b47049..3b9a6ef7e 100644 --- a/frontend/src/pages/totp-page.tsx +++ b/frontend/src/pages/totp-page.tsx @@ -17,7 +17,7 @@ import { useTranslation } from "react-i18next"; import { Navigate, useLocation } from "react-router"; import { toast } from "sonner"; import { - recompileScreenParams, + searchParamsFromObject, useScreenParams, } from "@/lib/hooks/screen-params"; import { useLoginFor } from "@/lib/hooks/login-for"; @@ -32,10 +32,16 @@ export const TotpPage = () => { const searchParams = new URLSearchParams(search); const screenParams = useScreenParams(searchParams); - const compiledParams = recompileScreenParams(screenParams); + const compiledParams = (() => { + const params = searchParamsFromObject(screenParams).toString(); + if (params.length > 0) { + return `?${params}`; + } + return ""; + })(); const loginForUrl = useLoginFor({ login_for: screenParams.login_for, - compiledParams, + params: searchParamsFromObject(screenParams), }); const totpMutation = useMutation({ diff --git a/go.mod b/go.mod index b02b23dd6..97b38097e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/docker/docker v28.5.2+incompatible github.com/gin-gonic/gin v1.12.0 github.com/go-jose/go-jose/v4 v4.1.4 - github.com/go-ldap/ldap/v3 v3.4.13 + github.com/go-ldap/ldap/v3 v3.4.14 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/golang-migrate/migrate/v4 v4.19.1 github.com/google/go-querystring v1.2.0 @@ -24,12 +24,13 @@ require ( github.com/weppos/publicsuffix-go v0.50.3 go.uber.org/dig v1.19.0 golang.org/x/crypto v0.54.0 + golang.org/x/net v0.57.0 golang.org/x/oauth2 v0.36.0 golang.org/x/tools v0.48.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.36.2 k8s.io/client-go v0.36.2 - modernc.org/sqlite v1.53.0 + modernc.org/sqlite v1.54.0 ) require ( @@ -72,7 +73,7 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/gin-contrib/sse v1.1.0 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect @@ -135,7 +136,6 @@ require ( golang.org/x/arch v0.22.0 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/mod v0.38.0 // indirect - golang.org/x/net v0.57.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/term v0.45.0 // indirect @@ -147,7 +147,7 @@ require ( k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect - modernc.org/libc v1.73.4 // indirect + modernc.org/libc v1.74.1 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect rsc.io/qr v0.2.0 // indirect diff --git a/go.sum b/go.sum index d3d5905a4..c48378881 100644 --- a/go.sum +++ b/go.sum @@ -113,12 +113,12 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= -github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= -github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ= +github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= -github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ= -github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0= +github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs= +github.com/go-ldap/ldap/v3 v3.4.14/go.mod h1:S4eJUMUNjDkE0ZJtIZdybwyb03sGGLW6gxXT1Hs8VKA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -392,20 +392,20 @@ k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hk k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c= -modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= -modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws= -modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE= +modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc= +modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU= +modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk= modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc= -modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI= +modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA= -modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8= +modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ= +modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= @@ -414,8 +414,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M= -modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s= +modernc.org/sqlite v1.54.0 h1:JCxR4qwkJvOaqAoYcgDoO25Nc+ROg6EJ2LfBVzdrgog= +modernc.org/sqlite v1.54.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/internal/bootstrap/app_bootstrap.go b/internal/bootstrap/app_bootstrap.go index f82f8a12f..5762b97a8 100644 --- a/internal/bootstrap/app_bootstrap.go +++ b/internal/bootstrap/app_bootstrap.go @@ -88,17 +88,13 @@ func (app *BootstrapApp) Setup() error { app.log.App.Info().Msgf("Starting Tinyauth version: %s", model.Version) // get app url - if app.config.AppURL == "" { - return errors.New("app url cannot be empty, perhaps config loading failed") - } - - appUrl, err := url.Parse(app.config.AppURL) + appURL, err := utils.SafeParseAppURL(app.config.AppURL) if err != nil { return fmt.Errorf("failed to parse app url: %w", err) } - app.runtime.AppURL = strings.ToLower(appUrl.Scheme + "://" + appUrl.Host) + app.runtime.AppURL = appURL // validate session config if app.config.Auth.SessionMaxLifetime != 0 && app.config.Auth.SessionMaxLifetime < app.config.Auth.SessionExpiry { @@ -172,7 +168,13 @@ func (app *BootstrapApp) Setup() error { app.runtime.CookieDomain = cookieDomain // cookie names - app.runtime.UUID = utils.GenerateUUID(appUrl.Hostname()) + u, err := url.Parse(app.runtime.AppURL) + + if err != nil { + return fmt.Errorf("failed to parse app url: %w", err) + } + + app.runtime.UUID = utils.GenerateUUID(u.Hostname()) cookieId := strings.Split(app.runtime.UUID, "-")[0] // first 8 characters of the uuid should be good enough diff --git a/internal/controller/oauth_controller.go b/internal/controller/oauth_controller.go index ee0f92fa9..00eae837b 100644 --- a/internal/controller/oauth_controller.go +++ b/internal/controller/oauth_controller.go @@ -313,8 +313,7 @@ func (controller *OAuthController) getCookieDomain() string { func (controller *OAuthController) isRedirectSafe(redirectURI string) bool { v := validators.NewDomainValidator(validators.DomainValidatorOptions{ - WithScheme: true, - WithPort: true, + WithPort: true, }) _, err := v.SafeHostname(controller.runtime.AppURL) @@ -333,7 +332,6 @@ func (controller *OAuthController) isRedirectSafe(redirectURI string) bool { controller.log.App.Debug().Err(err).Msg("Failed to validate redirect URI") if errors.Is(err, validators.ErrInvalidURL) || - errors.Is(err, validators.ErrSchemeMismatch) || errors.Is(err, validators.ErrPortMismatch) { return false } @@ -351,7 +349,8 @@ func (controller *OAuthController) isRedirectSafe(redirectURI string) bool { return false } - if strings.HasSuffix(hostname, "."+strings.ToLower(controller.runtime.CookieDomain)) { + if strings.HasSuffix(hostname, "."+strings.ToLower(controller.runtime.CookieDomain)) || + hostname == controller.runtime.CookieDomain { return true } diff --git a/internal/controller/oauth_controller_test.go b/internal/controller/oauth_controller_test.go index 557eb8586..80939ab2f 100644 --- a/internal/controller/oauth_controller_test.go +++ b/internal/controller/oauth_controller_test.go @@ -81,14 +81,6 @@ func TestOAuthController_isRedirectSafe(t *testing.T) { redirectURI: "https://sub.example.com", expected: false, }, - { - description: "Cookie domain itself is not a subdomain match", - appURL: "https://tinyauth.example.com", - cookieDomain: "example.com", - subdomainsEnabled: true, - redirectURI: "https://example.com", - expected: false, - }, { description: "Different scheme returns false", appURL: "https://tinyauth.example.com", @@ -121,14 +113,6 @@ func TestOAuthController_isRedirectSafe(t *testing.T) { redirectURI: "https:/malicious", expected: false, }, - { - description: "Redirect URI without scheme returns false", - appURL: "https://tinyauth.example.com", - cookieDomain: "example.com", - subdomainsEnabled: true, - redirectURI: "tinyauth.example.com", - expected: false, - }, { description: "Relative redirect URI returns false", appURL: "https://tinyauth.example.com", diff --git a/internal/service/access_controls_service.go b/internal/service/access_controls_service.go index bb76c2a8e..d29f2a973 100644 --- a/internal/service/access_controls_service.go +++ b/internal/service/access_controls_service.go @@ -44,13 +44,17 @@ func (service *AccessControlsService) lookupStaticACLs(domain string) *model.App // First try to find a matching app by domain, then fallback to matching by app name (subdomain) for app, config := range service.config.Apps { - err := v.Validate(config.Config.Domain, domain) - if err == nil { - service.log.App.Debug().Str("name", app).Msg("Found matching container by domain") - return &config - } - if !errors.Is(err, validators.ErrHostnameMismatch) { - service.log.App.Debug().Str("name", app).Err(err).Msg("Domain validation failed") + if config.Config.Domain != "" { + err := v.Validate(config.Config.Domain, domain) + if err == nil { + service.log.App.Debug().Str("name", app).Msg("Found matching container by domain") + return &config + } + if !errors.Is(err, validators.ErrHostnameMismatch) && + !errors.Is(err, validators.ErrPortMismatch) && + !errors.Is(err, validators.ErrSchemeMismatch) { + service.log.App.Debug().Str("name", app).Err(err).Msg("Domain validation failed") + } } if strings.HasPrefix(strings.ToLower(domain), strings.ToLower(app+".")) { service.log.App.Debug().Str("name", app).Msg("Found matching container by app name") diff --git a/internal/utils/app_utils.go b/internal/utils/app_utils.go index 7d30e63ab..3bc3546a0 100644 --- a/internal/utils/app_utils.go +++ b/internal/utils/app_utils.go @@ -7,10 +7,55 @@ import ( "strings" "github.com/weppos/publicsuffix-go/publicsuffix" + "golang.org/x/net/idna" ) -// GetCookieDomain parses the app url and returns the domain value to use for cookies. +var ( + ErrEmptyURL = fmt.Errorf("invalid url") +) + +func SafeParseAppURL(str string) (string, error) { + if strings.TrimSpace(str) == "" { + return "", ErrEmptyURL + } + + u, err := url.Parse(str) + + if err != nil { + return "", fmt.Errorf("invalid url: %w", err) + } + + if u.Host == "" || + (u.Scheme != "http" && + u.Scheme != "https") { + return "", fmt.Errorf("invalid url, must be in format https(s)://host") + } + + hostname := strings.ToLower(u.Hostname()) + hostname = strings.TrimSuffix(hostname, ".") + + if netIP := net.ParseIP(hostname); netIP != nil { + return "", fmt.Errorf("ip addresses not allowed") + } + + hostname, err = idna.Lookup.ToASCII(hostname) + + if err != nil { + return "", fmt.Errorf("failed to convert hostname to ascii: %w", err) + } + + appURL := fmt.Sprintf("%s://%s", u.Scheme, hostname) + + if u.Port() != "" { + appURL += ":" + u.Port() + } + + return appURL, nil +} + +// GetCookieDomain parses the app URL and returns the domain value to use for cookies. // When auth for subdomains is enabled, it strips the leftmost label +// GetCookieDomain assumes the app URL is first parsed with SafeParseAppURL // (e.g. sub1.sub2.domain.com -> sub2.domain.com), otherwise it returns the full hostname. func GetCookieDomain(appUrl string, subdomainsEnabled bool) (string, error) { u, err := url.Parse(appUrl) @@ -21,10 +66,6 @@ func GetCookieDomain(appUrl string, subdomainsEnabled bool) (string, error) { hostname := strings.ToLower(u.Hostname()) - if netIP := net.ParseIP(hostname); netIP != nil { - return "", fmt.Errorf("ip addresses not allowed") - } - parts := strings.Split(hostname, ".") if len(parts) < 2 { diff --git a/internal/utils/app_utils_test.go b/internal/utils/app_utils_test.go index 296c168a0..8c9e9bc5e 100644 --- a/internal/utils/app_utils_test.go +++ b/internal/utils/app_utils_test.go @@ -7,7 +7,86 @@ import ( "github.com/tinyauthapp/tinyauth/internal/utils" ) -func TestGetRootDomain(t *testing.T) { +func TestSafeParseAPPURL(t *testing.T) { + // Normal app url + appURL := "http://sub.tinyauth.app" + expected := "http://sub.tinyauth.app" + result, err := utils.SafeParseAppURL(appURL) + assert.NoError(t, err) + assert.Equal(t, expected, result) + + // Strip path + appURL = "http://sub.tinyauth.app/path" + expected = "http://sub.tinyauth.app" + result, err = utils.SafeParseAppURL(appURL) + assert.NoError(t, err) + assert.Equal(t, expected, result) + + // Preserve port + appURL = "http://sub.tinyauth.app:8080" + expected = "http://sub.tinyauth.app:8080" + result, err = utils.SafeParseAppURL(appURL) + assert.NoError(t, err) + assert.Equal(t, expected, result) + + // Remove trailing dot + appURL = "http://sub.tinyauth.app." + expected = "http://sub.tinyauth.app" + result, err = utils.SafeParseAppURL(appURL) + assert.NoError(t, err) + assert.Equal(t, expected, result) + + // Convert to ascii + appURL = "http://bücher.example.com" + expected = "http://xn--bcher-kva.example.com" + result, err = utils.SafeParseAppURL(appURL) + assert.NoError(t, err) + assert.Equal(t, expected, result) + + // Lowercase + appURL = "HTTP://SUb.tinyAUth.aPP" + expected = "http://sub.tinyauth.app" + result, err = utils.SafeParseAppURL(appURL) + assert.NoError(t, err) + assert.Equal(t, expected, result) + + // Empty string + appURL = "" + _, err = utils.SafeParseAppURL(appURL) + assert.ErrorIs(t, err, utils.ErrEmptyURL) + + // Invalid URL + appURL = "invalidurl" + _, err = utils.SafeParseAppURL(appURL) + assert.ErrorContains(t, err, "invalid url") + + // Non http or https URL + appURL = "ftp://sub.tinyauth.app" + _, err = utils.SafeParseAppURL(appURL) + assert.ErrorContains(t, err, "invalid url") + + // Invalid punycode + appURL = "http://ab--cd.example.com" + _, err = utils.SafeParseAppURL(appURL) + assert.ErrorContains(t, err, "failed to convert hostname to ascii") + + // IP address + appURL = "http://10.10.10.10" + _, err = utils.SafeParseAppURL(appURL) + assert.ErrorContains(t, err, "ip addresses not allowed") + + // IPv6 address + appURL = "http://[::1]:8080" + _, err = utils.SafeParseAppURL(appURL) + assert.ErrorContains(t, err, "ip addresses not allowed") + + // Invalid URL + appURL = "://" + _, err = utils.SafeParseAppURL(appURL) + assert.ErrorContains(t, err, "invalid url") +} + +func TestGetCookieDomain(t *testing.T) { // Normal case domain := "http://sub.tinyauth.app" expected := "tinyauth.app" @@ -27,11 +106,6 @@ func TestGetRootDomain(t *testing.T) { _, err = utils.GetCookieDomain(domain, true) assert.EqualError(t, err, "invalid app url, must be in format subdomain.domain.tld or domain.tld") - // IP address - domain = "http://10.10.10.10" - _, err = utils.GetCookieDomain(domain, true) - assert.ErrorContains(t, err, "ip addresses not allowed") - // Invalid URL domain = "http://[::1]:namedport" _, err = utils.GetCookieDomain(domain, true) @@ -56,6 +130,11 @@ func TestGetRootDomain(t *testing.T) { _, err = utils.GetCookieDomain(domain, true) assert.ErrorContains(t, err, "domain in public suffix list, cannot set cookies") + // Domain managed by ICANN without subdomain + domain = "http://co.uk" + _, err = utils.GetCookieDomain(domain, true) + assert.ErrorContains(t, err, "domain in public suffix list, cannot set cookies") + // Domain without subdomain domain = "http://tinyauth.app" expected = "tinyauth.app" diff --git a/pkg/validators/domain_validator.go b/pkg/validators/domain_validator.go index adeb7a859..c16966f48 100644 --- a/pkg/validators/domain_validator.go +++ b/pkg/validators/domain_validator.go @@ -63,7 +63,7 @@ func (v *DomainValidator) getURL(i string) (*url.URL, error) { return nil, ErrInvalidURL } - if v.opts.WithPort && !v.opts.WithScheme && u.Port() == "" { + if v.opts.WithPort && u.Port() == "" && (u.Scheme != "http" && u.Scheme != "https") { return nil, fmt.Errorf("port validation is enabled but port is missing in input url and schemes are not enabled") } diff --git a/pkg/validators/domain_validator_test.go b/pkg/validators/domain_validator_test.go index a3df87013..9789b139b 100644 --- a/pkg/validators/domain_validator_test.go +++ b/pkg/validators/domain_validator_test.go @@ -104,6 +104,24 @@ func TestDomainValidator_SafeHostname(t *testing.T) { assert.ErrorContains(t, e, "input url is missing scheme") }, }, + { + description: "With port enabled but without scheme and url with https should work", + options: DomainValidatorOptions{WithPort: true}, + input: "https://example.com", + expected: "example.com", + }, + { + description: "With port enabled but without scheme and url with http should work", + options: DomainValidatorOptions{WithPort: true}, + input: "http://example.com", + expected: "example.com", + }, + { + description: "With port enabled but without scheme and url with port should work", + options: DomainValidatorOptions{WithPort: true}, + input: "example.com:8080", + expected: "example.com", + }, } for _, test := range tests { @@ -194,7 +212,7 @@ func TestDomainValidator_Validate(t *testing.T) { expected: "ssh://example.com:22", actual: "ssh://example.com", errorFunc: func(t *testing.T, e error) { - assert.ErrorContains(t, e, "failed to get effective port for url") + assert.ErrorContains(t, e, "port validation is enabled but port is missing in input url and schemes are not enabled") }, }, { @@ -203,9 +221,21 @@ func TestDomainValidator_Validate(t *testing.T) { expected: "ssh://example.com", actual: "ssh://example.com:22", errorFunc: func(t *testing.T, e error) { - assert.ErrorContains(t, e, "failed to get effective port for url") + assert.ErrorContains(t, e, "port validation is enabled but port is missing in input url and schemes are not enabled") }, }, + { + description: "Port enabled but no scheme and https scheme should pass", + options: DomainValidatorOptions{WithPort: true}, + expected: "https://example.com", + actual: "https://example.com", + }, + { + description: "Port enabled but no scheme and http scheme should pass", + options: DomainValidatorOptions{WithPort: true}, + expected: "http://example.com", + actual: "http://example.com", + }, { description: "Port validation with port and no scheme should fail with different port", options: DomainValidatorOptions{WithPort: true},