Compare commits

..
Author SHA1 Message Date
dependabot[bot]andGitHub 8624bb9562 chore(deps): bump golang from 1.26-alpine3.23 to 1.27-alpine3.23
Bumps golang from 1.26-alpine3.23 to 1.27-alpine3.23.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.27-alpine3.23
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 08:15:52 +00:00
21 changed files with 275 additions and 438 deletions
-2
View File
@@ -235,8 +235,6 @@ TINYAUTH_LDAP_GROUPCACHETTL=900
# Enable the OAuth bridge, uses a new way to format OAuth user information.
TINYAUTH_EXPERIMENTAL_OAUTHBRIDGEENABLED=false
# Disable the fallback to forward_auth modules when auth_request or ext_authz fail.
TINYAUTH_EXPERIMENTAL_DISABLEAUTHMODULEFALLBACK=false
# tailscale config
+3 -3
View File
@@ -1,9 +1,9 @@
name: Run e2e tests
on:
workflow_dispatch:
# pull_request:
# branches:
# - main
pull_request:
branches:
- main
jobs:
test:
+1 -1
View File
@@ -38,6 +38,6 @@ jobs:
retention-days: 5
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
sarif_file: results.sarif
+1 -1
View File
@@ -22,7 +22,7 @@ COPY ./frontend/vite.config.ts ./
RUN pnpm run build
# Builder
FROM golang:1.26-alpine3.23 AS builder
FROM golang:1.27-alpine3.23 AS builder
ARG VERSION
ARG COMMIT_HASH
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.26-alpine3.23
FROM golang:1.27-alpine3.23
WORKDIR /tinyauth
+1 -1
View File
@@ -22,7 +22,7 @@ COPY ./frontend/vite.config.ts ./
RUN pnpm run build
# Builder
FROM golang:1.26-alpine3.23 AS builder
FROM golang:1.27-alpine3.23 AS builder
ARG VERSION
ARG COMMIT_HASH
+1
View File
@@ -81,6 +81,7 @@ A huge thank you to [selfh.st](https://selfh.st) for their generous donation to
- [JetBrains for Open-Source](https://jb.gg/OpenSource)
- [CodeRabbit AI](https://www.coderabbit.ai)
- [Dosu](https://dosu.dev/)
- [InstaPods](https://instapods.com) - [Deploy Tinyauth from 3$/month](https://app.instapods.com/dashboard/pods/create?app=tinyauth&ref=tinyauth)
## Acknowledgements
+8 -8
View File
@@ -12,7 +12,7 @@
},
"packageManager": "pnpm@11.1.2",
"dependencies": {
"@hookform/resolvers": "^5.9.1",
"@hookform/resolvers": "^5.9.0",
"@radix-ui/react-dropdown-menu": "^2.1.24",
"@radix-ui/react-label": "^2.1.15",
"@radix-ui/react-select": "^2.3.7",
@@ -23,16 +23,16 @@
"axios": "^1.19.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"i18next": "^26.4.0",
"i18next": "^26.3.6",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-resources-to-backend": "^1.2.3",
"lucide-react": "^1.33.0",
"lucide-react": "^1.31.0",
"next-themes": "^0.4.6",
"radix-ui": "^1.6.7",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-hook-form": "^7.86.0",
"react-i18next": "^17.0.12",
"react-hook-form": "^7.85.0",
"react-i18next": "^17.0.11",
"react-markdown": "^10.1.0",
"react-router": "^8.3.0",
"sonner": "^2.0.8",
@@ -46,8 +46,8 @@
"@types/node": "^26.2.0",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"@vitejs/plugin-react": "^6.1.0",
"eslint": "^10.9.0",
"@vitejs/plugin-react": "^6.0.5",
"eslint": "^10.8.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.4",
"globals": "^17.11.0",
@@ -55,6 +55,6 @@
"tw-animate-css": "^1.4.0",
"typescript": "~6.0.2",
"typescript-eslint": "^8.67.0",
"vite": "^8.2.2"
"vite": "^8.2.1"
}
}
+152 -165
View File
@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@hookform/resolvers':
specifier: ^5.9.1
version: 5.9.1(react-hook-form@7.86.0(react@19.2.8))(zod@4.4.3)
specifier: ^5.9.0
version: 5.9.0(react-hook-form@7.85.0(react@19.2.8))(zod@4.4.3)
'@radix-ui/react-dropdown-menu':
specifier: ^2.1.24
version: 2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -28,7 +28,7 @@ importers:
version: 1.3.3(@types/react@19.2.18)(react@19.2.8)
'@tailwindcss/vite':
specifier: ^4.3.3
version: 4.3.3(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0))
version: 4.3.3(vite@8.2.1(@types/node@26.2.0)(jiti@2.7.0))
'@tanstack/react-query':
specifier: ^5.101.4
version: 5.101.4(react@19.2.8)
@@ -42,8 +42,8 @@ importers:
specifier: ^2.1.1
version: 2.1.1
i18next:
specifier: ^26.4.0
version: 26.4.0(typescript@6.0.3)
specifier: ^26.3.6
version: 26.3.6(typescript@6.0.3)
i18next-browser-languagedetector:
specifier: ^8.2.1
version: 8.2.1
@@ -51,8 +51,8 @@ importers:
specifier: ^1.2.3
version: 1.2.3
lucide-react:
specifier: ^1.33.0
version: 1.33.0(react@19.2.8)
specifier: ^1.31.0
version: 1.31.0(react@19.2.8)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -66,11 +66,11 @@ importers:
specifier: ^19.2.8
version: 19.2.8(react@19.2.8)
react-hook-form:
specifier: ^7.86.0
version: 7.86.0(react@19.2.8)
specifier: ^7.85.0
version: 7.85.0(react@19.2.8)
react-i18next:
specifier: ^17.0.12
version: 17.0.12(i18next@26.4.0(typescript@6.0.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@6.0.3)
specifier: ^17.0.11
version: 17.0.11(i18next@26.3.6(typescript@6.0.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@6.0.3)
react-markdown:
specifier: ^10.1.0
version: 10.1.0(@types/react@19.2.18)(react@19.2.8)
@@ -92,10 +92,10 @@ importers:
devDependencies:
'@eslint/js':
specifier: ^10.0.1
version: 10.0.1(eslint@10.9.0(jiti@2.7.0))
version: 10.0.1(eslint@10.8.1(jiti@2.7.0))
'@tanstack/eslint-plugin-query':
specifier: ^5.101.4
version: 5.101.4(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)
version: 5.101.4(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)
'@types/node':
specifier: ^26.2.0
version: 26.2.0
@@ -106,23 +106,23 @@ importers:
specifier: ^19.2.4
version: 19.2.4(@types/react@19.2.18)
'@vitejs/plugin-react':
specifier: ^6.1.0
version: 6.1.0(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0))
specifier: ^6.0.5
version: 6.0.5(vite@8.2.1(@types/node@26.2.0)(jiti@2.7.0))
eslint:
specifier: ^10.9.0
version: 10.9.0(jiti@2.7.0)
specifier: ^10.8.1
version: 10.8.1(jiti@2.7.0)
eslint-plugin-react-hooks:
specifier: ^7.0.1
version: 7.1.1(eslint@10.9.0(jiti@2.7.0))
version: 7.1.1(eslint@10.8.1(jiti@2.7.0))
eslint-plugin-react-refresh:
specifier: ^0.5.4
version: 0.5.4(eslint@10.9.0(jiti@2.7.0))
version: 0.5.4(eslint@10.8.1(jiti@2.7.0))
globals:
specifier: ^17.11.0
version: 17.11.0
rollup-plugin-visualizer:
specifier: ^7.1.1
version: 7.1.1(rolldown@1.2.5)
version: 7.1.1(rolldown@1.2.3)
tw-animate-css:
specifier: ^1.4.0
version: 1.4.0
@@ -131,10 +131,10 @@ importers:
version: 6.0.3
typescript-eslint:
specifier: ^8.67.0
version: 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)
version: 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)
vite:
specifier: ^8.2.2
version: 8.2.2(@types/node@26.2.0)(jiti@2.7.0)
specifier: ^8.2.1
version: 8.2.1(@types/node@26.2.0)(jiti@2.7.0)
packages:
@@ -267,8 +267,8 @@ packages:
'@floating-ui/utils@0.2.12':
resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
'@hookform/resolvers@5.9.1':
resolution: {integrity: sha512-7b7vsbraJxKgjVSA1Nur9tLwj539WGJUBLA7QNvXnFoT2pM5Z7G+6rlukk4B2/QrTZy6huRtH6wKeESPKuIr6w==}
'@hookform/resolvers@5.9.0':
resolution: {integrity: sha512-8sNo1IklGaONrsKzXjwZJNsPbccAXLXGX//G+VFEcQOviMtbvR8/fM6HWyA0qrjgpOYauwoodda53CQIWOZ7Ag==}
peerDependencies:
'@sinclair/typebox': '>=0.25.24'
'@standard-schema/spec': ^1.0.0
@@ -381,8 +381,8 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@oxc-project/types@0.146.0':
resolution: {integrity: sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==}
'@oxc-project/types@0.143.0':
resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==}
'@radix-ui/number@1.1.3':
resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==}
@@ -1074,98 +1074,92 @@ packages:
'@radix-ui/rect@1.1.3':
resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==}
'@rolldown/binding-android-arm-eabi@1.2.5':
resolution: {integrity: sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [android]
'@rolldown/binding-android-arm64@1.2.5':
resolution: {integrity: sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==}
'@rolldown/binding-android-arm64@1.2.3':
resolution: {integrity: sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@rolldown/binding-darwin-arm64@1.2.5':
resolution: {integrity: sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==}
'@rolldown/binding-darwin-arm64@1.2.3':
resolution: {integrity: sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@rolldown/binding-darwin-x64@1.2.5':
resolution: {integrity: sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==}
'@rolldown/binding-darwin-x64@1.2.3':
resolution: {integrity: sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@rolldown/binding-freebsd-x64@1.2.5':
resolution: {integrity: sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==}
'@rolldown/binding-freebsd-x64@1.2.3':
resolution: {integrity: sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@rolldown/binding-linux-arm-gnueabihf@1.2.5':
resolution: {integrity: sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==}
'@rolldown/binding-linux-arm-gnueabihf@1.2.3':
resolution: {integrity: sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@rolldown/binding-linux-arm64-gnu@1.2.5':
resolution: {integrity: sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==}
'@rolldown/binding-linux-arm64-gnu@1.2.3':
resolution: {integrity: sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.2.5':
resolution: {integrity: sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==}
'@rolldown/binding-linux-arm64-musl@1.2.3':
resolution: {integrity: sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-ppc64-gnu@1.2.5':
resolution: {integrity: sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==}
'@rolldown/binding-linux-ppc64-gnu@1.2.3':
resolution: {integrity: sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.2.5':
resolution: {integrity: sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==}
'@rolldown/binding-linux-s390x-gnu@1.2.3':
resolution: {integrity: sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.2.5':
resolution: {integrity: sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==}
'@rolldown/binding-linux-x64-gnu@1.2.3':
resolution: {integrity: sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.2.5':
resolution: {integrity: sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==}
'@rolldown/binding-linux-x64-musl@1.2.3':
resolution: {integrity: sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.2.5':
resolution: {integrity: sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==}
'@rolldown/binding-openharmony-arm64@1.2.3':
resolution: {integrity: sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@rolldown/binding-win32-arm64-msvc@1.2.5':
resolution: {integrity: sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==}
'@rolldown/binding-win32-arm64-msvc@1.2.3':
resolution: {integrity: sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.2.5':
resolution: {integrity: sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==}
'@rolldown/binding-win32-x64-msvc@1.2.3':
resolution: {integrity: sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
@@ -1427,21 +1421,18 @@ packages:
'@ungap/structured-clone@1.3.1':
resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==}
'@vitejs/plugin-react@6.1.0':
resolution: {integrity: sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==}
'@vitejs/plugin-react@6.0.5':
resolution: {integrity: sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==}
engines: {node: ^20.19.0 || >=22.12.0}
peerDependencies:
'@rolldown/plugin-babel': ^0.1.7 || ^0.2.0
babel-plugin-react-compiler: ^1.0.0
oxc-transform-react: ^0.145.0
vite: ^8.0.0
peerDependenciesMeta:
'@rolldown/plugin-babel':
optional: true
babel-plugin-react-compiler:
optional: true
oxc-transform-react:
optional: true
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
@@ -1662,8 +1653,8 @@ packages:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
eslint@10.9.0:
resolution: {integrity: sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw==}
eslint@10.8.1:
resolution: {integrity: sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true
peerDependencies:
@@ -1831,8 +1822,8 @@ packages:
i18next-resources-to-backend@1.2.3:
resolution: {integrity: sha512-8Y/LLAm5fqZc2ckQxtTWbu75ndjNLzF7mcYl6rhc4g+WBopIsG3YhdYqF5qhJy64tlYhlU2KSSnEgW3gGHLqwg==}
i18next@26.4.0:
resolution: {integrity: sha512-rsmK5bFqsD1AetSFSIa43wtNR4WpvvH4p0tLEsTxkC7QTrfdFm06nbQ95bh8Og4wwaCnUEcm9DVYL2cgxitiQg==}
i18next@26.3.6:
resolution: {integrity: sha512-Bu5Z2nAXgfVyM8xvW3jk9EKRIuX37PudsrBViThNFx7CR7aaYTpP01cxNB/E4c4UUzTDiAZRstEhsRfPOL/8xA==}
peerDependencies:
typescript: ^5 || ^6 || ^7
peerDependenciesMeta:
@@ -2090,8 +2081,8 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lucide-react@1.33.0:
resolution: {integrity: sha512-MTRwMy0ZlL8Ur/vOAiJ9XGHE+kFPC7brq6MxAm0GiGXEBj0qy0jA/pG4N675oSzciO/UCdX8T+5yUQdmDeTLxg==}
lucide-react@1.31.0:
resolution: {integrity: sha512-G8u2eEtoHUnUa9f8lbvqDhCiORMnYLdUEo06EEG9MQvHQrInKcX3Pa2TH39MM5qyzRcWETxB0+aOwAPI1g1kEg==}
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -2300,14 +2291,14 @@ packages:
peerDependencies:
react: ^19.2.8
react-hook-form@7.86.0:
resolution: {integrity: sha512-4kbWJrh5jPZt1+YqVcXcGKffGcXV/XVbozknLh0Yjh0KhpoAkus21TAQhzRYqNwFkkObmnSvRlZZ3GT+ehoIrA==}
react-hook-form@7.85.0:
resolution: {integrity: sha512-U2MTriFXnclmV4rOE20p2DcRFv5WEg3FIcBFOKcOLFHDVvGIMPvLTkTWefUsonmlaVy23khVDxDWym6uJVGOzw==}
engines: {node: '>=18.0.0'}
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
react-i18next@17.0.12:
resolution: {integrity: sha512-lFWPEGkxQ6RhusdUkysFBD58VHfSSzvHBzqMgN0SvfVpdQGfwtNkStTqdy08/sJd7s807qqutgx93fRpD0DJ3Q==}
react-i18next@17.0.11:
resolution: {integrity: sha512-cDtkXgxjuFTWUH6V+aQn1Ve5vDiUztCNPWW5GtSHDccsgRXO1nE6QFWCEmc1KAutrb3OUv87wFShJL5RhUwPXg==}
peerDependencies:
i18next: '>= 26.2.0'
react: '>= 16.8.0'
@@ -2378,8 +2369,8 @@ packages:
remark-rehype@11.1.2:
resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
rolldown@1.2.5:
resolution: {integrity: sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==}
rolldown@1.2.3:
resolution: {integrity: sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
@@ -2571,13 +2562,13 @@ packages:
vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
vite@8.2.2:
resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==}
vite@8.2.1:
resolution: {integrity: sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@types/node': ^20.19.0 || >=22.12.0
'@vitejs/devtools': ^0.4.0 || ^0.5.0
'@vitejs/devtools': ^0.4.0
esbuild: ^0.27.0 || ^0.28.0
jiti: '>=1.21.0'
less: ^4.0.0
@@ -2768,9 +2759,9 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
'@eslint-community/eslint-utils@4.10.1(eslint@10.9.0(jiti@2.7.0))':
'@eslint-community/eslint-utils@4.10.1(eslint@10.8.1(jiti@2.7.0))':
dependencies:
eslint: 10.9.0(jiti@2.7.0)
eslint: 10.8.1(jiti@2.7.0)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
@@ -2791,9 +2782,9 @@ snapshots:
dependencies:
'@types/json-schema': 7.0.15
'@eslint/js@10.0.1(eslint@10.9.0(jiti@2.7.0))':
'@eslint/js@10.0.1(eslint@10.8.1(jiti@2.7.0))':
optionalDependencies:
eslint: 10.9.0(jiti@2.7.0)
eslint: 10.8.1(jiti@2.7.0)
'@eslint/object-schema@3.0.5': {}
@@ -2819,10 +2810,10 @@ snapshots:
'@floating-ui/utils@0.2.12': {}
'@hookform/resolvers@5.9.1(react-hook-form@7.86.0(react@19.2.8))(zod@4.4.3)':
'@hookform/resolvers@5.9.0(react-hook-form@7.85.0(react@19.2.8))(zod@4.4.3)':
dependencies:
'@standard-schema/utils': 0.3.0
react-hook-form: 7.86.0(react@19.2.8)
react-hook-form: 7.85.0(react@19.2.8)
optionalDependencies:
zod: 4.4.3
@@ -2861,7 +2852,7 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@oxc-project/types@0.146.0': {}
'@oxc-project/types@0.143.0': {}
'@radix-ui/number@1.1.3': {}
@@ -3610,49 +3601,46 @@ snapshots:
'@radix-ui/rect@1.1.3': {}
'@rolldown/binding-android-arm-eabi@1.2.5':
'@rolldown/binding-android-arm64@1.2.3':
optional: true
'@rolldown/binding-android-arm64@1.2.5':
'@rolldown/binding-darwin-arm64@1.2.3':
optional: true
'@rolldown/binding-darwin-arm64@1.2.5':
'@rolldown/binding-darwin-x64@1.2.3':
optional: true
'@rolldown/binding-darwin-x64@1.2.5':
'@rolldown/binding-freebsd-x64@1.2.3':
optional: true
'@rolldown/binding-freebsd-x64@1.2.5':
'@rolldown/binding-linux-arm-gnueabihf@1.2.3':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.2.5':
'@rolldown/binding-linux-arm64-gnu@1.2.3':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.2.5':
'@rolldown/binding-linux-arm64-musl@1.2.3':
optional: true
'@rolldown/binding-linux-arm64-musl@1.2.5':
'@rolldown/binding-linux-ppc64-gnu@1.2.3':
optional: true
'@rolldown/binding-linux-ppc64-gnu@1.2.5':
'@rolldown/binding-linux-s390x-gnu@1.2.3':
optional: true
'@rolldown/binding-linux-s390x-gnu@1.2.5':
'@rolldown/binding-linux-x64-gnu@1.2.3':
optional: true
'@rolldown/binding-linux-x64-gnu@1.2.5':
'@rolldown/binding-linux-x64-musl@1.2.3':
optional: true
'@rolldown/binding-linux-x64-musl@1.2.5':
'@rolldown/binding-openharmony-arm64@1.2.3':
optional: true
'@rolldown/binding-openharmony-arm64@1.2.5':
'@rolldown/binding-win32-arm64-msvc@1.2.3':
optional: true
'@rolldown/binding-win32-arm64-msvc@1.2.5':
optional: true
'@rolldown/binding-win32-x64-msvc@1.2.5':
'@rolldown/binding-win32-x64-msvc@1.2.3':
optional: true
'@rolldown/pluginutils@1.0.1': {}
@@ -3720,17 +3708,17 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.3.3
'@tailwindcss/oxide-win32-x64-msvc': 4.3.3
'@tailwindcss/vite@4.3.3(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0))':
'@tailwindcss/vite@4.3.3(vite@8.2.1(@types/node@26.2.0)(jiti@2.7.0))':
dependencies:
'@tailwindcss/node': 4.3.3
'@tailwindcss/oxide': 4.3.3
tailwindcss: 4.3.3
vite: 8.2.2(@types/node@26.2.0)(jiti@2.7.0)
vite: 8.2.1(@types/node@26.2.0)(jiti@2.7.0)
'@tanstack/eslint-plugin-query@5.101.4(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)':
'@tanstack/eslint-plugin-query@5.101.4(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
'@typescript-eslint/utils': 8.65.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)
eslint: 10.9.0(jiti@2.7.0)
'@typescript-eslint/utils': 8.65.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)
eslint: 10.8.1(jiti@2.7.0)
optionalDependencies:
typescript: 6.0.3
transitivePeerDependencies:
@@ -3783,15 +3771,15 @@ snapshots:
'@types/unist@3.0.3': {}
'@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)':
'@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/parser': 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.67.0
'@typescript-eslint/type-utils': 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/utils': 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/type-utils': 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.67.0
eslint: 10.9.0(jiti@2.7.0)
eslint: 10.8.1(jiti@2.7.0)
ignore: 7.0.6
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@6.0.3)
@@ -3799,14 +3787,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)':
'@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.67.0
'@typescript-eslint/types': 8.67.0
'@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.67.0
debug: 4.4.3
eslint: 10.9.0(jiti@2.7.0)
eslint: 10.8.1(jiti@2.7.0)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -3847,13 +3835,13 @@ snapshots:
dependencies:
typescript: 6.0.3
'@typescript-eslint/type-utils@8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)':
'@typescript-eslint/type-utils@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
'@typescript-eslint/types': 8.67.0
'@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3)
'@typescript-eslint/utils': 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)
debug: 4.4.3
eslint: 10.9.0(jiti@2.7.0)
eslint: 10.8.1(jiti@2.7.0)
ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
@@ -3893,24 +3881,24 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.65.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)':
'@typescript-eslint/utils@8.65.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(jiti@2.7.0))
'@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0))
'@typescript-eslint/scope-manager': 8.65.0
'@typescript-eslint/types': 8.65.0
'@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3)
eslint: 10.9.0(jiti@2.7.0)
eslint: 10.8.1(jiti@2.7.0)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)':
'@typescript-eslint/utils@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(jiti@2.7.0))
'@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0))
'@typescript-eslint/scope-manager': 8.67.0
'@typescript-eslint/types': 8.67.0
'@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3)
eslint: 10.9.0(jiti@2.7.0)
eslint: 10.8.1(jiti@2.7.0)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -3927,10 +3915,10 @@ snapshots:
'@ungap/structured-clone@1.3.1': {}
'@vitejs/plugin-react@6.1.0(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0))':
'@vitejs/plugin-react@6.0.5(vite@8.2.1(@types/node@26.2.0)(jiti@2.7.0))':
dependencies:
'@rolldown/pluginutils': 1.0.1
vite: 8.2.2(@types/node@26.2.0)(jiti@2.7.0)
vite: 8.2.1(@types/node@26.2.0)(jiti@2.7.0)
acorn-jsx@5.3.2(acorn@8.18.0):
dependencies:
@@ -4105,20 +4093,20 @@ snapshots:
escape-string-regexp@4.0.0: {}
eslint-plugin-react-hooks@7.1.1(eslint@10.9.0(jiti@2.7.0)):
eslint-plugin-react-hooks@7.1.1(eslint@10.8.1(jiti@2.7.0)):
dependencies:
'@babel/core': 7.29.0
'@babel/parser': 7.29.3
eslint: 10.9.0(jiti@2.7.0)
eslint: 10.8.1(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.4(eslint@10.9.0(jiti@2.7.0)):
eslint-plugin-react-refresh@0.5.4(eslint@10.8.1(jiti@2.7.0)):
dependencies:
eslint: 10.9.0(jiti@2.7.0)
eslint: 10.8.1(jiti@2.7.0)
eslint-scope@9.1.2:
dependencies:
@@ -4131,9 +4119,9 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
eslint@10.9.0(jiti@2.7.0):
eslint@10.8.1(jiti@2.7.0):
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(jiti@2.7.0))
'@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0))
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.5
'@eslint/config-helpers': 0.7.0
@@ -4326,7 +4314,7 @@ snapshots:
dependencies:
'@babel/runtime': 7.29.7
i18next@26.4.0(typescript@6.0.3):
i18next@26.3.6(typescript@6.0.3):
optionalDependencies:
typescript: 6.0.3
@@ -4502,7 +4490,7 @@ snapshots:
dependencies:
yallist: 3.1.1
lucide-react@1.33.0(react@19.2.8):
lucide-react@1.31.0(react@19.2.8):
dependencies:
react: 19.2.8
@@ -4887,15 +4875,15 @@ snapshots:
react: 19.2.8
scheduler: 0.27.0
react-hook-form@7.86.0(react@19.2.8):
react-hook-form@7.85.0(react@19.2.8):
dependencies:
react: 19.2.8
react-i18next@17.0.12(i18next@26.4.0(typescript@6.0.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@6.0.3):
react-i18next@17.0.11(i18next@26.3.6(typescript@6.0.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@6.0.3):
dependencies:
'@babel/runtime': 7.29.7
html-parse-stringify: 4.0.1
i18next: 26.4.0(typescript@6.0.3)
i18next: 26.3.6(typescript@6.0.3)
react: 19.2.8
use-sync-external-store: 1.6.0(react@19.2.8)
optionalDependencies:
@@ -4973,35 +4961,34 @@ snapshots:
unified: 11.0.5
vfile: 6.0.3
rolldown@1.2.5:
rolldown@1.2.3:
dependencies:
'@oxc-project/types': 0.146.0
'@oxc-project/types': 0.143.0
'@rolldown/pluginutils': 1.0.1
optionalDependencies:
'@rolldown/binding-android-arm-eabi': 1.2.5
'@rolldown/binding-android-arm64': 1.2.5
'@rolldown/binding-darwin-arm64': 1.2.5
'@rolldown/binding-darwin-x64': 1.2.5
'@rolldown/binding-freebsd-x64': 1.2.5
'@rolldown/binding-linux-arm-gnueabihf': 1.2.5
'@rolldown/binding-linux-arm64-gnu': 1.2.5
'@rolldown/binding-linux-arm64-musl': 1.2.5
'@rolldown/binding-linux-ppc64-gnu': 1.2.5
'@rolldown/binding-linux-s390x-gnu': 1.2.5
'@rolldown/binding-linux-x64-gnu': 1.2.5
'@rolldown/binding-linux-x64-musl': 1.2.5
'@rolldown/binding-openharmony-arm64': 1.2.5
'@rolldown/binding-win32-arm64-msvc': 1.2.5
'@rolldown/binding-win32-x64-msvc': 1.2.5
'@rolldown/binding-android-arm64': 1.2.3
'@rolldown/binding-darwin-arm64': 1.2.3
'@rolldown/binding-darwin-x64': 1.2.3
'@rolldown/binding-freebsd-x64': 1.2.3
'@rolldown/binding-linux-arm-gnueabihf': 1.2.3
'@rolldown/binding-linux-arm64-gnu': 1.2.3
'@rolldown/binding-linux-arm64-musl': 1.2.3
'@rolldown/binding-linux-ppc64-gnu': 1.2.3
'@rolldown/binding-linux-s390x-gnu': 1.2.3
'@rolldown/binding-linux-x64-gnu': 1.2.3
'@rolldown/binding-linux-x64-musl': 1.2.3
'@rolldown/binding-openharmony-arm64': 1.2.3
'@rolldown/binding-win32-arm64-msvc': 1.2.3
'@rolldown/binding-win32-x64-msvc': 1.2.3
rollup-plugin-visualizer@7.1.1(rolldown@1.2.5):
rollup-plugin-visualizer@7.1.1(rolldown@1.2.3):
dependencies:
open: 11.0.1
picomatch: 4.0.5
source-map: 0.8.0
yargs: 18.1.0
optionalDependencies:
rolldown: 1.2.5
rolldown: 1.2.3
run-applescript@7.1.0: {}
@@ -5085,13 +5072,13 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
typescript-eslint@8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3):
typescript-eslint@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/parser': 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/parser': 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3)
'@typescript-eslint/utils': 8.67.0(eslint@10.9.0(jiti@2.7.0))(typescript@6.0.3)
eslint: 10.9.0(jiti@2.7.0)
'@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)
eslint: 10.8.1(jiti@2.7.0)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -5172,12 +5159,12 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0):
vite@8.2.1(@types/node@26.2.0)(jiti@2.7.0):
dependencies:
lightningcss: 1.33.0
picomatch: 4.0.5
postcss: 8.5.26
rolldown: 1.2.5
rolldown: 1.2.3
tinyglobby: 0.2.17
optionalDependencies:
'@types/node': 26.2.0
+3 -3
View File
@@ -28,9 +28,9 @@ require (
golang.org/x/oauth2 v0.36.0
golang.org/x/tools v0.49.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.36.4
k8s.io/client-go v0.36.4
modernc.org/sqlite v1.57.0
k8s.io/apimachinery v0.36.3
k8s.io/client-go v0.36.3
modernc.org/sqlite v1.56.0
)
require (
+8 -8
View File
@@ -380,12 +380,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
k8s.io/api v0.36.4 h1:RxrvqCL6vgH5/+UnTeu1IIFqYmGfy0hnyrod1rn35Oo=
k8s.io/api v0.36.4/go.mod h1:S2B3orCFBDhrgyWbLeuKcT2QdHIpQesBkCYSlWtwUOw=
k8s.io/apimachinery v0.36.4 h1:PT2UzkupGuAx/+xT5XjiMJ1WGpY3fn9/hdAvjweRet4=
k8s.io/apimachinery v0.36.4/go.mod h1:p2I2dipt7JHG+quVwQ1d02d28O4GdDi77RByQ13MTpk=
k8s.io/client-go v0.36.4 h1:MDvfDNvMSt0Br94SK8neviVlwL9qifw9B26hJCpD1K0=
k8s.io/client-go v0.36.4/go.mod h1:pNK4WKELbwlEDvtbE8l22lEZL5THYF61H5EealokZmA=
k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w=
k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg=
k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM=
k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE=
k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg=
k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
@@ -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.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg=
modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
modernc.org/sqlite v1.56.0 h1:/D8e2RfFqoy/Zc6PuC76U28zFwmI/sYx1Kjm4yEn9e0=
modernc.org/sqlite v1.56.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
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=
+1 -3
View File
@@ -294,9 +294,7 @@ func (controller *OAuthController) getCookieDomain() string {
func (controller *OAuthController) isRedirectSafe(redirectURI string) bool {
v := validators.NewDomainValidator(validators.DomainValidatorOptions{
WithPort: true,
WithScheme: true,
AllowedSchemes: []string{"https", "http"},
WithPort: true,
})
_, err := v.SafeHostname(controller.runtime.AppURL)
+13 -69
View File
@@ -57,7 +57,6 @@ type ProxyContext struct {
type ProxyController struct {
log *logger.Logger
runtime *model.RuntimeConfig
config *model.Config
acls *service.AccessControlsService
auth *service.AuthService
policyEngine *service.PolicyEngine
@@ -68,7 +67,6 @@ type ProxyControllerInput struct {
Log *logger.Logger
RuntimeConfig *model.RuntimeConfig
Config *model.Config
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
ACLsService *service.AccessControlsService
AuthService *service.AuthService
@@ -79,7 +77,6 @@ func NewProxyController(i ProxyControllerInput) *ProxyController {
controller := &ProxyController{
log: i.Log,
runtime: i.RuntimeConfig,
config: i.Config,
acls: i.ACLsService,
auth: i.AuthService,
policyEngine: i.PolicyEngine,
@@ -468,10 +465,6 @@ func (controller *ProxyController) getExtAuthzContext(c *gin.Context) (ProxyCont
// We get the path from the query string
path := c.Query("path")
if strings.TrimSpace(path) == "" {
return ProxyContext{}, errors.New("path not found")
}
// For envoy we need to support every method
method := c.Request.Method
@@ -484,22 +477,14 @@ func (controller *ProxyController) getExtAuthzContext(c *gin.Context) (ProxyCont
}, nil
}
func (controller *ProxyController) determineAuthModules(proxy ProxyType, fallbacks bool) []AuthModuleType {
func (controller *ProxyController) determineAuthModules(proxy ProxyType) []AuthModuleType {
switch proxy {
case Traefik, Caddy:
return []AuthModuleType{ForwardAuth}
case Envoy:
authModules := []AuthModuleType{ExtAuthz}
if fallbacks {
authModules = append(authModules, ForwardAuth)
}
return authModules
return []AuthModuleType{ExtAuthz, ForwardAuth}
case Nginx:
authModules := []AuthModuleType{AuthRequest}
if fallbacks {
authModules = append(authModules, ForwardAuth)
}
return authModules
return []AuthModuleType{AuthRequest, ForwardAuth}
default:
return []AuthModuleType{}
}
@@ -529,39 +514,6 @@ func (controller *ProxyController) getContextFromAuthModule(c *gin.Context, modu
return ProxyContext{}, fmt.Errorf("unsupported auth module: %v", module)
}
func (controller *ProxyController) authModuleIdentifiersPresent(c *gin.Context, module AuthModuleType) bool {
switch module {
case ForwardAuth:
_, host := controller.getHeader(c, "x-forwarded-host")
_, uri := controller.getHeader(c, "x-forwarded-uri")
return host || uri
case AuthRequest:
_, ok := controller.getHeader(c, "x-original-url")
return ok
case ExtAuthz:
return strings.TrimSpace(c.Query("path")) != ""
default:
return false
}
}
func (controller *ProxyController) ensureNoMultipleAuthModules(c *gin.Context, authModules []AuthModuleType) error {
present := 0
for _, module := range authModules {
if controller.authModuleIdentifiersPresent(c, module) {
present++
}
}
if present > 1 {
controller.log.App.Warn().Msg("Request carries headers for multiple auth modules, possible spoofing attempt, denying")
return fmt.Errorf("conflicting auth module headers")
}
return nil
}
func (controller *ProxyController) getProxyContext(c *gin.Context) (ProxyContext, error) {
var req Proxy
@@ -578,34 +530,26 @@ func (controller *ProxyController) getProxyContext(c *gin.Context) (ProxyContext
controller.log.App.Debug().Msgf("Determined proxy type: %v", proxy)
authModules := controller.determineAuthModules(proxy, !controller.config.Experimental.DisableAuthModuleFallback)
authModules := controller.determineAuthModules(proxy)
if len(authModules) == 0 {
return ProxyContext{}, fmt.Errorf("no auth modules supported for proxy: %v", req.Proxy)
}
err = controller.ensureNoMultipleAuthModules(c, controller.determineAuthModules(proxy, true))
if err != nil {
return ProxyContext{}, err
}
var ctx *ProxyContext
var ctx ProxyContext
for _, module := range authModules {
controller.log.App.Debug().Msgf("Trying to get context from auth module %v", module)
authModuleCtx, err := controller.getContextFromAuthModule(c, module)
if err != nil {
controller.log.App.Debug().Msgf("Failed to get context from auth module %v: %v", module, err)
continue
ctx, err = controller.getContextFromAuthModule(c, module)
if err == nil {
controller.log.App.Debug().Msgf("Successfully got context from auth module %v", module)
break
}
controller.log.App.Debug().Msgf("Successfully got context from auth module %v", module)
ctx = &authModuleCtx
break
controller.log.App.Debug().Msgf("Failed to get context from auth module %v: %v", module, err)
}
if ctx == nil {
return ProxyContext{}, fmt.Errorf("failed to get context from any auth module")
if err != nil {
return ProxyContext{}, err
}
// Parse the raw path to populate the cleaned path used for ACLs
@@ -633,5 +577,5 @@ func (controller *ProxyController) getProxyContext(c *gin.Context) (ProxyContext
ctx.IsBrowser = isBrowser
ctx.ProxyType = proxy
return *ctx, nil
return ctx, nil
}
+2 -30
View File
@@ -213,7 +213,7 @@ func TestProxyController(t *testing.T) {
description: "Ensure forward auth fallback for envoy",
middlewares: []gin.HandlerFunc{},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("HEAD", "/api/auth/envoy", nil)
req := httptest.NewRequest("HEAD", "/api/auth/envoy?path=/hello", nil)
req.Host = ""
req.Header.Set("x-forwarded-host", "test.example.com")
req.Header.Set("x-forwarded-proto", "https")
@@ -261,7 +261,7 @@ func TestProxyController(t *testing.T) {
description: "Ensure extauthz with envoy non browser returns json",
middlewares: []gin.HandlerFunc{},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("HEAD", "/api/auth/envoy", nil)
req := httptest.NewRequest("HEAD", "/api/auth/envoy?path=/hello", nil)
req.Header.Set("x-forwarded-host", "test.example.com")
req.Header.Set("x-forwarded-proto", "https")
req.Header.Set("x-forwarded-uri", "/hello")
@@ -877,32 +877,6 @@ func TestProxyController(t *testing.T) {
assert.Equal(t, "bar", recorder.Header().Get("x-foo"))
},
},
{
description: "Forward auth and auth request headers should fail for nginx",
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("GET", "/api/auth/nginx", nil)
req.Header.Set("x-forwarded-host", "foo.example.com")
req.Header.Set("x-forwarded-proto", "https")
req.Header.Set("x-forwarded-uri", "/foo?bar=foo")
req.Header.Set("x-original-url", "https://foo.example.com/foo?bar=foo")
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusBadRequest, recorder.Code)
},
},
{
description: "Forward auth and ext authz headers should fail for envoy",
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("HEAD", "/api/auth/envoy?path=/hello", nil)
req.Host = "foo.example.com"
req.Header.Set("x-forwarded-host", "foo.example.com")
req.Header.Set("x-forwarded-proto", "https")
req.Header.Set("x-forwarded-uri", "/foo?bar=foo")
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusBadRequest, recorder.Code)
},
},
}
store := memory.New()
@@ -918,7 +892,6 @@ func TestProxyController(t *testing.T) {
aclsService := service.NewAccessControlsService(service.AccessControlServiceInput{
Log: log,
Config: &cfg,
Runtime: &runtime,
LabelProvider: nil,
})
@@ -979,7 +952,6 @@ func TestProxyController(t *testing.T) {
NewProxyController(ProxyControllerInput{
Log: log,
RuntimeConfig: &runtime,
Config: &cfg,
RouterGroup: group,
ACLsService: aclsService,
AuthService: authService,
+1 -2
View File
@@ -239,8 +239,7 @@ type LogStreamConfig struct {
}
type ExperimentalConfig struct {
OAuthBridgeEnabled bool `description:"Enable the OAuth bridge, uses a new way to format OAuth user information." yaml:"oauthBridgeEnabled,omitempty"`
DisableAuthModuleFallback bool `description:"Disable the fallback to forward_auth modules when auth_request or ext_authz fail." yaml:"disableAuthModuleFallback,omitempty"`
OAuthBridgeEnabled bool `description:"Enable the OAuth bridge, uses a new way to format OAuth user information." yaml:"oauthBridgeEnabled,omitempty"`
}
type TailscaleConfig struct {
+8 -40
View File
@@ -2,13 +2,11 @@ package service
import (
"errors"
"fmt"
"net"
"strings"
"unicode"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"github.com/tinyauthapp/tinyauth/pkg/validators"
"go.uber.org/dig"
)
@@ -19,7 +17,6 @@ type LabelProvider interface {
type AccessControlsService struct {
log *logger.Logger
config *model.Config
runtime *model.RuntimeConfig
labelProvider LabelProvider
}
@@ -28,7 +25,6 @@ type AccessControlServiceInput struct {
Log *logger.Logger
Config *model.Config
Runtime *model.RuntimeConfig
LabelProvider LabelProvider `optional:"true"`
}
@@ -37,38 +33,12 @@ func NewAccessControlsService(i AccessControlServiceInput) *AccessControlsServic
return &AccessControlsService{
log: i.Log,
config: i.Config,
runtime: i.Runtime,
labelProvider: i.LabelProvider,
}
}
func (service *AccessControlsService) ensureAscii(str string) bool {
for i := 0; i < len(str); i++ {
if str[i] > unicode.MaxASCII {
return false
}
}
return true
}
func (service *AccessControlsService) normalizeDomain(domain string) string {
if host, _, err := net.SplitHostPort(domain); err == nil {
domain = host
}
domain = strings.TrimRight(domain, ".")
return strings.ToLower(domain)
}
func (service *AccessControlsService) getACLs(domain string, lookup func(locator func(name string, app *model.App) bool) error) (*model.App, error) {
if !service.ensureAscii(domain) {
return nil, errors.New("domain contains non-ascii characters")
}
normalizedDomain := service.normalizeDomain(domain)
if !strings.HasSuffix(normalizedDomain, "."+service.runtime.CookieDomain) && normalizedDomain != service.runtime.CookieDomain {
return nil, fmt.Errorf("domain does not match cookie domain, expected %s (or a subdomain), got %s", service.runtime.CookieDomain, domain)
}
v := validators.NewDomainValidator(validators.DomainValidatorOptions{})
var domainMatch *model.App
var nameMatch *model.App
@@ -76,18 +46,16 @@ func (service *AccessControlsService) getACLs(domain string, lookup func(locator
locatorFunc := func(name string, app *model.App) bool {
if app.Config.Domain != "" {
if !service.ensureAscii(app.Config.Domain) {
service.log.App.Warn().Str("name", name).Str("domain", app.Config.Domain).Msg("Domain contains non-ascii characters, skipping")
return false
}
if normalizedDomain == service.normalizeDomain(app.Config.Domain) {
err := v.Validate(app.Config.Domain, domain)
if err == nil {
service.log.App.Debug().Str("name", name).Msg("Found matching container by domain")
domainMatch = app
return true
} else if !errors.Is(err, validators.ErrHostnameMismatch) {
service.log.App.Debug().Str("name", name).Err(err).Msg("Domain validation failed")
}
return false
}
if strings.HasPrefix(normalizedDomain, strings.ToLower(name+".")) {
if strings.HasPrefix(strings.ToLower(domain), strings.ToLower(name+".")) {
service.log.App.Debug().Str("name", name).Msg("Found matching container by app name")
nameMatch = app
nameMatchedApps = append(nameMatchedApps, name)
@@ -111,7 +79,7 @@ func (service *AccessControlsService) getACLs(domain string, lookup func(locator
}
if len(nameMatchedApps) > 1 {
return nil, fmt.Errorf("domain matched multiple apps by name prefix, use explicit domain config")
service.log.App.Warn().Str("domain", domain).Strs("apps", nameMatchedApps).Msg("Multiple apps matched domain by name, app names must be unique, using last match")
}
service.log.App.Debug().Str("domain", domain).Msg("Found matching app by app name")
@@ -4,10 +4,8 @@ import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/test"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
)
@@ -36,25 +34,14 @@ func TestAccessControlsService(t *testing.T) {
log := logger.NewLogger().WithTestConfig()
log.Init()
_, runtime := test.CreateTestConfigs(t)
tests := []struct {
name string
domain string
acls map[string]model.App
want *model.App
errorFunc func(t *testing.T, e error)
name string
domain string
acls map[string]model.App
want *model.App
}{
{
name: "returns ACLs for domain",
domain: "app.example.com",
acls: map[string]model.App{
"foo": {Config: model.AppConfig{Domain: "app.example.com"}},
},
want: &model.App{Config: model.AppConfig{Domain: "app.example.com"}},
},
{
name: "returns ACLs for root domain",
domain: "example.com",
acls: map[string]model.App{
"foo": {Config: model.AppConfig{Domain: "example.com"}},
@@ -78,11 +65,20 @@ func TestAccessControlsService(t *testing.T) {
want: &model.App{Config: model.AppConfig{Domain: "example.com"}},
},
{
name: "returns error for non-ascii domain",
name: "returns ACLs for non-ascii domain",
domain: "bücher.example.com",
errorFunc: func(t *testing.T, e error) {
assert.ErrorContains(t, e, "domain contains non-ascii characters")
acls: map[string]model.App{
"foo": {Config: model.AppConfig{Domain: "bücher.example.com"}},
},
want: &model.App{Config: model.AppConfig{Domain: "bücher.example.com"}},
},
{
name: "returns ACLs for punycode domain and non-ascii config",
domain: "bücher.example.com",
acls: map[string]model.App{
"foo": {Config: model.AppConfig{Domain: "xn--bcher-kva.example.com"}},
},
want: &model.App{Config: model.AppConfig{Domain: "xn--bcher-kva.example.com"}},
},
{
name: "returns ACLs with case-insensitive matching",
@@ -114,33 +110,6 @@ func TestAccessControlsService(t *testing.T) {
acls: map[string]model.App{},
want: nil,
},
{
name: "App in domain not matching with the cookie domain should return nothing with name matching",
domain: "foo.bad_example.com",
acls: map[string]model.App{
"foo": {
Path: model.AppPath{Allow: "/foo"},
},
},
want: nil,
errorFunc: func(t *testing.T, e error) {
assert.ErrorContains(t, e, "domain does not match cookie domain")
},
},
{
name: "App in domain not matching with the cookie domain should return nothing with domain matching",
domain: "foo.bad_example.com",
acls: map[string]model.App{
"foo": {
Path: model.AppPath{Allow: "/foo"},
Config: model.AppConfig{Domain: "foo.bad_example.com"},
},
},
want: nil,
errorFunc: func(t *testing.T, e error) {
assert.ErrorContains(t, e, "domain does not match cookie domain")
},
},
}
// run once for a mock provider
@@ -149,15 +118,10 @@ func TestAccessControlsService(t *testing.T) {
mock := newMockProvider(test.acls, false)
acls := NewAccessControlsService(AccessControlServiceInput{
Log: log,
Runtime: &runtime,
Config: &model.Config{},
LabelProvider: mock,
})
app, err := acls.getACLs(test.domain, mock.Lookup)
if test.errorFunc != nil {
test.errorFunc(t, err)
return
}
require.NoError(t, err)
require.Equal(t, test.want, app)
})
@@ -167,17 +131,12 @@ func TestAccessControlsService(t *testing.T) {
for _, test := range tests {
t.Run(test.name+"(staticACLs)", func(t *testing.T) {
acls := NewAccessControlsService(AccessControlServiceInput{
Log: log,
Runtime: &runtime,
Log: log,
Config: &model.Config{
Apps: test.acls,
},
})
app, err := acls.lookupStaticACLs(test.domain)
if test.errorFunc != nil {
test.errorFunc(t, err)
return
}
require.NoError(t, err)
require.Equal(t, test.want, app)
})
@@ -186,32 +145,16 @@ func TestAccessControlsService(t *testing.T) {
// get acls should return an error when the provider fails
mock := newMockProvider(map[string]model.App{}, true)
acls := NewAccessControlsService(AccessControlServiceInput{
Log: log,
Runtime: &runtime,
Config: &model.Config{},
Log: log,
Config: &model.Config{},
})
_, err := acls.getACLs("example.com", mock.Lookup)
assert.Error(t, err)
// get acls should return an error when multiple apps with the same domain exist
acls = NewAccessControlsService(AccessControlServiceInput{
Log: log,
Runtime: &runtime,
Config: &model.Config{
Apps: map[string]model.App{
"foo": {Path: model.AppPath{Allow: "/foo"}},
"foo.bar": {Path: model.AppPath{Allow: "/bar"}},
},
},
})
_, err = acls.GetAccessControls("foo.bar.example.com")
assert.ErrorContains(t, err, "domain matched multiple apps by name prefix, use explicit domain config")
require.Error(t, err)
// get access controls should get acls from
// static when static acls are configured
acls = NewAccessControlsService(AccessControlServiceInput{
Log: log,
Runtime: &runtime,
Log: log,
Config: &model.Config{
Apps: map[string]model.App{
"foo": {Config: model.AppConfig{Domain: "foo.example.com"}},
@@ -220,12 +163,12 @@ func TestAccessControlsService(t *testing.T) {
})
app, err := acls.GetAccessControls("foo.example.com")
require.NoError(t, err)
assert.Equal(t, &model.App{Config: model.AppConfig{Domain: "foo.example.com"}}, app)
require.Equal(t, &model.App{Config: model.AppConfig{Domain: "foo.example.com"}}, app)
// should return nil for no apps
app, err = acls.GetAccessControls("bar.example.com")
require.NoError(t, err)
assert.Nil(t, app)
require.Nil(t, app)
// Should use label provider if available
mock = newMockProvider(map[string]model.App{
@@ -235,11 +178,10 @@ func TestAccessControlsService(t *testing.T) {
}, false)
acls = NewAccessControlsService(AccessControlServiceInput{
Log: log,
Runtime: &runtime,
Config: &model.Config{},
LabelProvider: mock,
})
app, err = acls.GetAccessControls("bar.example.com")
require.NoError(t, err)
assert.Equal(t, &model.App{Config: model.AppConfig{Domain: "bar.example.com"}}, app)
require.Equal(t, &model.App{Config: model.AppConfig{Domain: "bar.example.com"}}, app)
}
+1 -10
View File
@@ -38,16 +38,7 @@ func SafeParseAppURL(str string) (string, error) {
return "", fmt.Errorf("ip addresses not allowed")
}
i := idna.New(
idna.MapForLookup(),
idna.Transitional(false),
idna.BidiRule(),
idna.StrictDomainName(false),
idna.CheckHyphens(true),
idna.CheckJoiners(false),
)
hostname, err = i.ToASCII(hostname)
hostname, err = idna.Lookup.ToASCII(hostname)
if err != nil {
return "", fmt.Errorf("failed to convert hostname to ascii: %w", err)
+1 -8
View File
@@ -43,13 +43,6 @@ func TestSafeParseAPPURL(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, expected, result)
// Underscores
appURL = "http://sub_tinyauth.app"
expected = "http://sub_tinyauth.app"
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"
@@ -73,7 +66,7 @@ func TestSafeParseAPPURL(t *testing.T) {
assert.ErrorContains(t, err, "invalid url")
// Invalid punycode
appURL = "http://xn--h-kva.example.com"
appURL = "http://ab--cd.example.com"
_, err = utils.SafeParseAppURL(appURL)
assert.ErrorContains(t, err, "failed to convert hostname to ascii")
+7 -1
View File
@@ -11,6 +11,8 @@ import (
"net"
"net/url"
"strings"
"golang.org/x/net/idna"
)
// Errors
@@ -113,10 +115,14 @@ func (v *DomainValidator) getURL(i string) (*url.URL, error) {
func (v *DomainValidator) getHostname(hostname string) (string, error) {
hostname = strings.ToLower(hostname)
hostname = strings.TrimRight(hostname, ".")
hostname = strings.TrimSuffix(hostname, ".")
if net.ParseIP(hostname) != nil {
return "", fmt.Errorf("ip addresses are not supported")
}
hostname, err := idna.Lookup.ToASCII(hostname)
if err != nil {
return "", fmt.Errorf("failed to convert hostname to ascii: %w", err)
}
return hostname, nil
}
+38
View File
@@ -101,6 +101,18 @@ func TestDomainValidator_SafeHostname(t *testing.T) {
assert.ErrorContains(t, e, "ip addresses are not supported")
},
},
{
description: "Domains with unicode characters should be allowed",
input: "bücher.example.com",
expected: "xn--bcher-kva.example.com",
},
{
description: "Invalid IDNA domain should fail",
input: "ab--cd.example.com",
errorFunc: func(t *testing.T, e error) {
assert.ErrorContains(t, e, "invalid label")
},
},
{
description: "With port enabled without any port should work",
options: DomainValidatorOptions{WithPort: true},
@@ -182,6 +194,22 @@ func TestDomainValidator_Validate(t *testing.T) {
expected: "https://example.com:443",
actual: "https://example.com:443",
},
{
description: "Failure to format expected domain should fail",
expected: "ab--cd.example.com",
actual: "example.com",
errorFunc: func(t *testing.T, e error) {
assert.ErrorContains(t, e, "idna: invalid label")
},
},
{
description: "Failure to format check domain should fail",
expected: "example.com",
actual: "ab--cd.example.com",
errorFunc: func(t *testing.T, e error) {
assert.ErrorContains(t, e, "idna: invalid label")
},
},
{
description: "Valid domains with matching schemes and ports should pass",
options: DomainValidatorOptions{WithScheme: true, AllowedSchemes: []string{"https", "http"}, WithPort: true},
@@ -208,6 +236,16 @@ func TestDomainValidator_Validate(t *testing.T) {
actual: "example.com",
expected: "example.com",
},
{
description: "Unicode valid domains should pass",
expected: "xn--bcher-kva.example.com",
actual: "bücher.example.com",
},
{
description: "Unicode valid domains should pass (reverse)",
expected: "bücher.example.com",
actual: "xn--bcher-kva.example.com",
},
{
description: "Non matching hostnames should fail",
expected: "example.com",