diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c67bed..6e70165 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,16 @@ jobs: - name: Setup go uses: actions/setup-go@v5 with: - go-version: "^1.23.2" + go-version: "^1.24.0" + + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff - name: Install frontend dependencies run: | diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 5bc3e09..05563b6 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -61,7 +61,16 @@ jobs: - name: Install go uses: actions/setup-go@v5 with: - go-version: "^1.23.2" + go-version: "^1.24.0" + + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff - name: Install frontend dependencies run: | @@ -107,7 +116,16 @@ jobs: - name: Install go uses: actions/setup-go@v5 with: - go-version: "^1.23.2" + go-version: "^1.24.0" + + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff - name: Install frontend dependencies run: | @@ -147,6 +165,15 @@ jobs: with: ref: nightly + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff + - name: Docker meta id: meta uses: docker/metadata-action@v5 @@ -205,6 +232,15 @@ jobs: with: ref: nightly + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff + - name: Docker meta id: meta uses: docker/metadata-action@v5 @@ -263,6 +299,15 @@ jobs: with: ref: nightly + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff + - name: Docker meta id: meta uses: docker/metadata-action@v5 @@ -321,6 +366,15 @@ jobs: with: ref: nightly + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff + - name: Docker meta id: meta uses: docker/metadata-action@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c6896c2..1edf09d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,7 +39,16 @@ jobs: - name: Install go uses: actions/setup-go@v5 with: - go-version: "^1.23.2" + go-version: "^1.24.0" + + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff - name: Install frontend dependencies run: | @@ -82,7 +91,16 @@ jobs: - name: Install go uses: actions/setup-go@v5 with: - go-version: "^1.23.2" + go-version: "^1.24.0" + + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff - name: Install frontend dependencies run: | @@ -119,6 +137,15 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff + - name: Docker meta id: meta uses: docker/metadata-action@v5 @@ -174,6 +201,15 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff + - name: Docker meta id: meta uses: docker/metadata-action@v5 @@ -229,6 +265,15 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff + - name: Docker meta id: meta uses: docker/metadata-action@v5 @@ -284,6 +329,15 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Initialize submodules + run: | + git submodule init + git submodule update + + - name: Apply patches + run: | + git apply --directory paerser/ patches/nested_maps.diff + - name: Docker meta id: meta uses: docker/metadata-action@v5 diff --git a/.gitignore b/.gitignore index 576aeee..0eefed8 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,7 @@ # binary out /tinyauth.db -/resources \ No newline at end of file +/resources + +# debug files +__debug_* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c3cedbc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "paerser"] + path = paerser + url = https://github.com/traefik/paerser + ignore = all diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ace4da..1440bc9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ Contributing is relatively easy, you just need to follow the steps below and you ## Requirements - Bun -- Golang v1.23.2 and above +- Golang 1.24.0+ - Git - Docker @@ -18,9 +18,18 @@ git clone https://github.com/steveiliop56/tinyauth cd tinyauth ``` +## Initialize submodules + +The project uses Git submodules for some dependencies, so you need to initialize them with: + +```sh +git submodule init +git submodule update +``` + ## Install requirements -Although you will not need the requirements in your machine since the development will happen in docker, I still recommend to install them because this way you will not have import errors. To install the go requirements run: +Although you will not need the requirements in your machine since the development will happen in Docker, I still recommend to install them because this way you will not have import errors. To install the Go requirements run: ```sh go mod download @@ -33,13 +42,21 @@ cd frontend/ bun install ``` +## Apply patches + +Some of the dependencies need to be patched in order to work correctly with the project, you can apply the patches by running: + +```sh +git apply --directory paerser/ patches/nested_maps.diff +``` + ## Create your `.env` file In order to configure the app you need to create an environment file, this can be done by copying the `.env.example` file to `.env` and modifying the environment variables to suit your needs. ## Developing -I have designed the development workflow to be entirely in docker, this is because it will directly work with traefik and you will not need to do any building in your host machine. The recommended development setup is to have a subdomain pointing to your machine like this: +I have designed the development workflow to be entirely in Docker, this is because it will directly work with Traefik and you will not need to do any building in your host machine. The recommended development setup is to have a subdomain pointing to your machine like this: ``` *.dev.example.com -> 127.0.0.1 @@ -49,7 +66,7 @@ dev.example.com -> 127.0.0.1 > [!TIP] > You can use [sslip.io](https://sslip.io) as a domain if you don't have one to develop with. -Then you can just make sure the domains are correct in the development docker compose file and run: +Then you can just make sure the domains are correct in the development Docker compose file and run: ```sh docker compose -f docker-compose.dev.yml up --build diff --git a/Dockerfile b/Dockerfile index 41500ed..aae8e0e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,6 +28,8 @@ ARG BUILD_TIMESTAMP WORKDIR /tinyauth +COPY ./paerser ./paerser + COPY go.mod ./ COPY go.sum ./ @@ -38,7 +40,7 @@ COPY ./internal ./internal COPY --from=frontend-builder /frontend/dist ./internal/assets/dist RUN CGO_ENABLED=0 go build -ldflags "-s -w -X tinyauth/internal/config.Version=${VERSION} -X tinyauth/internal/config.CommitHash=${COMMIT_HASH} -X tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth - + # Runner FROM alpine:3.23 AS runner @@ -62,4 +64,4 @@ ENV PATH=$PATH:/tinyauth HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 CMD ["tinyauth", "healthcheck"] -ENTRYPOINT ["tinyauth"] \ No newline at end of file +ENTRYPOINT ["tinyauth"] diff --git a/Dockerfile.dev b/Dockerfile.dev index 96ea9b9..7efe722 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -2,6 +2,8 @@ FROM golang:1.25-alpine3.21 WORKDIR /tinyauth +COPY ./paerser ./paerser + COPY go.mod ./ COPY go.sum ./ @@ -20,4 +22,4 @@ ENV TINYAUTH_DATABASEPATH=/data/tinyauth.db ENV TINYAUTH_RESOURCESDIR=/data/resources -ENTRYPOINT ["air", "-c", "air.toml"] \ No newline at end of file +ENTRYPOINT ["air", "-c", "air.toml"] diff --git a/Dockerfile.distroless b/Dockerfile.distroless index 9806bea..52fb344 100644 --- a/Dockerfile.distroless +++ b/Dockerfile.distroless @@ -28,6 +28,8 @@ ARG BUILD_TIMESTAMP WORKDIR /tinyauth +COPY ./paerser ./paerser + COPY go.mod ./ COPY go.sum ./ @@ -40,7 +42,7 @@ COPY --from=frontend-builder /frontend/dist ./internal/assets/dist RUN mkdir -p data RUN CGO_ENABLED=0 go build -ldflags "-s -w -X tinyauth/internal/config.Version=${VERSION} -X tinyauth/internal/config.CommitHash=${COMMIT_HASH} -X tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth - + # Runner FROM gcr.io/distroless/static-debian12:latest AS runner @@ -65,4 +67,4 @@ ENV PATH=$PATH:/tinyauth HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 CMD ["tinyauth", "healthcheck"] -ENTRYPOINT ["tinyauth"] \ No newline at end of file +ENTRYPOINT ["tinyauth"] diff --git a/cmd/tinyauth/generate.go b/cmd/tinyauth/generate.go index 0c0755b..63eb671 100644 --- a/cmd/tinyauth/generate.go +++ b/cmd/tinyauth/generate.go @@ -6,7 +6,8 @@ import ( "os" "strings" "time" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/utils" "github.com/charmbracelet/huh" "github.com/mdp/qrterminal/v3" diff --git a/cmd/tinyauth/tinyauth.go b/cmd/tinyauth/tinyauth.go index 8642eed..33b4015 100644 --- a/cmd/tinyauth/tinyauth.go +++ b/cmd/tinyauth/tinyauth.go @@ -5,9 +5,10 @@ import ( "os" "strings" "time" - "tinyauth/internal/bootstrap" - "tinyauth/internal/config" - "tinyauth/internal/utils/loaders" + + "github.com/steveiliop56/tinyauth/internal/bootstrap" + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/utils/loaders" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -33,6 +34,10 @@ func NewTinyauthCmdConfiguration() *config.Config { ForgotPasswordMessage: "You can change your password by changing the configuration.", BackgroundImage: "/background.jpg", }, + Ldap: config.LdapConfig{ + Insecure: false, + SearchFilter: "(uid=%s)", + }, Experimental: config.ExperimentalConfig{ ConfigFile: "", }, diff --git a/cmd/tinyauth/verify.go b/cmd/tinyauth/verify.go index ddb114e..9634808 100644 --- a/cmd/tinyauth/verify.go +++ b/cmd/tinyauth/verify.go @@ -5,7 +5,8 @@ import ( "fmt" "os" "time" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/utils" "github.com/charmbracelet/huh" "github.com/pquerna/otp/totp" diff --git a/cmd/tinyauth/version.go b/cmd/tinyauth/version.go index 22aae14..aad6b55 100644 --- a/cmd/tinyauth/version.go +++ b/cmd/tinyauth/version.go @@ -2,7 +2,8 @@ package main import ( "fmt" - "tinyauth/internal/config" + + "github.com/steveiliop56/tinyauth/internal/config" "github.com/traefik/paerser/cli" ) diff --git a/frontend/bun.lock b/frontend/bun.lock index 0a35de7..cfbc8ea 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -12,7 +12,7 @@ "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@tailwindcss/vite": "^4.1.18", - "@tanstack/react-query": "^5.90.12", + "@tanstack/react-query": "^5.90.15", "axios": "^1.13.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -24,7 +24,7 @@ "next-themes": "^0.4.6", "react": "^19.2.3", "react-dom": "^19.2.3", - "react-hook-form": "^7.68.0", + "react-hook-form": "^7.69.0", "react-i18next": "^16.5.0", "react-markdown": "^10.1.0", "react-router": "^7.11.0", @@ -47,7 +47,7 @@ "prettier": "3.7.4", "tw-animate-css": "^1.4.0", "typescript": "~5.9.3", - "typescript-eslint": "^8.50.0", + "typescript-eslint": "^8.51.0", "vite": "^7.3.0", }, }, @@ -339,9 +339,9 @@ "@tanstack/eslint-plugin-query": ["@tanstack/eslint-plugin-query@5.91.2", "", { "dependencies": { "@typescript-eslint/utils": "^8.44.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-UPeWKl/Acu1IuuHJlsN+eITUHqAaa9/04geHHPedY8siVarSaWprY0SVMKrkpKfk5ehRT7+/MZ5QwWuEtkWrFw=="], - "@tanstack/query-core": ["@tanstack/query-core@5.90.12", "", {}, "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg=="], + "@tanstack/query-core": ["@tanstack/query-core@5.90.15", "", {}, "sha512-mInIZNUZftbERE+/Hbtswfse49uUQwch46p+27gP9DWJL927UjnaWEF2t3RMOqBcXbfMdcNkPe06VyUIAZTV1g=="], - "@tanstack/react-query": ["@tanstack/react-query@5.90.12", "", { "dependencies": { "@tanstack/query-core": "5.90.12" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg=="], + "@tanstack/react-query": ["@tanstack/react-query@5.90.15", "", { "dependencies": { "@tanstack/query-core": "5.90.15" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-uQvnDDcTOgJouNtAyrgRej+Azf0U5WDov3PXmHFUBc+t1INnAYhIlpZtCGNBLwCN41b43yO7dPNZu8xWkUFBwQ=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], @@ -373,25 +373,25 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.50.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/type-utils": "8.50.0", "@typescript-eslint/utils": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.50.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.51.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/type-utils": "8.51.0", "@typescript-eslint/utils": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.51.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.50.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.51.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.50.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.50.0", "@typescript-eslint/types": "^8.50.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.51.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.51.0", "@typescript-eslint/types": "^8.51.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ=="], "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.46.1", "", { "dependencies": { "@typescript-eslint/types": "8.46.1", "@typescript-eslint/visitor-keys": "8.46.1" } }, "sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.50.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.51.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.50.0", "", { "dependencies": { "@typescript-eslint/types": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0", "@typescript-eslint/utils": "8.50.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/utils": "8.51.0", "debug": "^4.3.4", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q=="], "@typescript-eslint/types": ["@typescript-eslint/types@8.46.1", "", {}, "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.50.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.50.0", "@typescript-eslint/tsconfig-utils": "8.50.0", "@typescript-eslint/types": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.51.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.51.0", "@typescript-eslint/tsconfig-utils": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng=="], "@typescript-eslint/utils": ["@typescript-eslint/utils@8.46.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/types": "8.46.1", "@typescript-eslint/typescript-estree": "8.46.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.50.0", "", { "dependencies": { "@typescript-eslint/types": "8.50.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], @@ -795,7 +795,7 @@ "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], - "react-hook-form": ["react-hook-form@7.68.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q=="], + "react-hook-form": ["react-hook-form@7.69.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-yt6ZGME9f4F6WHwevrvpAjh42HMvocuSnSIHUGycBqXIJdhqGSPQzTpGF+1NLREk/58IdPxEMfPcFCjlMhclGw=="], "react-i18next": ["react-i18next@16.5.0", "", { "dependencies": { "@babel/runtime": "^7.27.6", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 25.6.2", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-IMpPTyCTKxEj8klCrLKUTIUa8uYTd851+jcu2fJuUB9Agkk9Qq8asw4omyeHVnOXHrLgQJGTm5zTvn8HpaPiqw=="], @@ -863,7 +863,7 @@ "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], - "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + "ts-api-utils": ["ts-api-utils@2.3.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -873,7 +873,7 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "typescript-eslint": ["typescript-eslint@8.50.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.50.0", "@typescript-eslint/parser": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0", "@typescript-eslint/utils": "8.50.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A=="], + "typescript-eslint": ["typescript-eslint@8.51.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.51.0", "@typescript-eslint/parser": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/utils": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], @@ -995,25 +995,25 @@ "@types/estree-jsx/@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.50.0", "", { "dependencies": { "@typescript-eslint/types": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0" } }, "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0" } }, "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.50.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.51.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA=="], "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.4", "", {}, "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A=="], - "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.50.0", "", { "dependencies": { "@typescript-eslint/types": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0" } }, "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A=="], + "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0" } }, "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA=="], - "@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.50.0", "", {}, "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w=="], + "@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], - "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.50.0", "", {}, "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w=="], + "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.46.1", "", { "dependencies": { "@typescript-eslint/types": "8.46.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA=="], - "@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.50.0", "", {}, "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w=="], + "@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], - "@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.50.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg=="], + "@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.51.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA=="], - "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.50.0", "", {}, "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w=="], + "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -1021,7 +1021,7 @@ "@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.46.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.46.1", "@typescript-eslint/tsconfig-utils": "8.46.1", "@typescript-eslint/types": "8.46.1", "@typescript-eslint/visitor-keys": "8.46.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg=="], - "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.50.0", "", {}, "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w=="], + "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], "eslint-plugin-react-hooks/@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], @@ -1039,7 +1039,7 @@ "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], - "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.50.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg=="], + "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.51.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA=="], "@babel/helper-module-imports/@babel/traverse/@babel/generator": ["@babel/generator@7.27.1", "", { "dependencies": { "@babel/parser": "^7.27.1", "@babel/types": "^7.27.1", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w=="], @@ -1057,11 +1057,11 @@ "@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.50.0", "", {}, "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.50.0", "", {}, "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], - "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.50.0", "", { "dependencies": { "@typescript-eslint/types": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0" } }, "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A=="], + "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0" } }, "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], @@ -1075,15 +1075,17 @@ "@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + "eslint-plugin-react-hooks/@babel/core/@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], "eslint-plugin-react-hooks/@babel/core/@babel/traverse": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="], "eslint-plugin-react-hooks/@babel/core/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.50.0", "", { "dependencies": { "@typescript-eslint/types": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0" } }, "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A=="], + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0" } }, "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA=="], - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.50.0", "", {}, "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w=="], + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], "@babel/helper-module-imports/@babel/traverse/@babel/generator/@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], diff --git a/frontend/package.json b/frontend/package.json index 66a817e..59fb72f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,7 +18,7 @@ "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@tailwindcss/vite": "^4.1.18", - "@tanstack/react-query": "^5.90.12", + "@tanstack/react-query": "^5.90.15", "axios": "^1.13.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -30,7 +30,7 @@ "next-themes": "^0.4.6", "react": "^19.2.3", "react-dom": "^19.2.3", - "react-hook-form": "^7.68.0", + "react-hook-form": "^7.69.0", "react-i18next": "^16.5.0", "react-markdown": "^10.1.0", "react-router": "^7.11.0", @@ -53,7 +53,7 @@ "prettier": "3.7.4", "tw-animate-css": "^1.4.0", "typescript": "~5.9.3", - "typescript-eslint": "^8.50.0", + "typescript-eslint": "^8.51.0", "vite": "^7.3.0" } } diff --git a/go.mod b/go.mod index 94c7ac8..c765e03 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,30 @@ -module tinyauth +module github.com/steveiliop56/tinyauth go 1.24.0 toolchain go1.24.3 +replace github.com/traefik/paerser v0.2.2 => ./paerser + require ( github.com/cenkalti/backoff/v5 v5.0.3 + github.com/charmbracelet/huh v0.8.0 + github.com/docker/docker v28.5.2+incompatible github.com/gin-gonic/gin v1.11.0 + github.com/glebarez/sqlite v1.11.0 + github.com/go-ldap/ldap/v3 v3.4.12 github.com/golang-migrate/migrate/v4 v4.19.1 - github.com/google/go-querystring v1.1.0 + github.com/google/go-querystring v1.2.0 github.com/google/uuid v1.6.0 github.com/mdp/qrterminal/v3 v3.2.1 + github.com/pquerna/otp v1.5.0 github.com/rs/zerolog v1.34.0 github.com/traefik/paerser v0.2.2 github.com/weppos/publicsuffix-go v0.50.1 golang.org/x/crypto v0.46.0 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b + golang.org/x/oauth2 v0.34.0 + gorm.io/gorm v1.31.1 gotest.tools/v3 v3.5.2 modernc.org/sqlite v1.38.2 ) @@ -68,14 +77,17 @@ require ( github.com/catppuccin/go v0.3.0 // indirect github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect github.com/charmbracelet/bubbletea v1.3.6 // indirect - github.com/charmbracelet/huh v0.8.0 + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/x/ansi v0.9.3 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/cloudwego/base64x v0.1.6 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v28.5.2+incompatible github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -83,12 +95,20 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/gin-contrib/sse v1.1.0 // indirect - github.com/go-ldap/ldap/v3 v3.4.12 + github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.28.0 // indirect github.com/goccy/go-json v0.10.4 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/imdario/mergo v0.3.11 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -97,31 +117,49 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-sqlite3 v1.14.32 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pquerna/otp v1.5.0 + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.57.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect golang.org/x/arch v0.20.0 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.34.0 golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect golang.org/x/text v0.32.0 // indirect google.golang.org/protobuf v1.36.9 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.66.3 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.38.2 // indirect + rsc.io/qr v0.2.0 // indirect ) diff --git a/go.sum b/go.sum index 520b5f4..b04ac5b 100644 --- a/go.sum +++ b/go.sum @@ -125,11 +125,11 @@ github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7Lk github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= +github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= @@ -263,8 +263,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ= -github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= @@ -351,7 +349,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= diff --git a/internal/assets/migrations/000003_oauth_sub.down.sql b/internal/assets/migrations/000003_oauth_sub.down.sql new file mode 100644 index 0000000..0260a7b --- /dev/null +++ b/internal/assets/migrations/000003_oauth_sub.down.sql @@ -0,0 +1 @@ +ALTER TABLE "sessions" DROP COLUMN "oauth_sub"; \ No newline at end of file diff --git a/internal/assets/migrations/000003_oauth_sub.up.sql b/internal/assets/migrations/000003_oauth_sub.up.sql new file mode 100644 index 0000000..def471e --- /dev/null +++ b/internal/assets/migrations/000003_oauth_sub.up.sql @@ -0,0 +1 @@ +ALTER TABLE "sessions" ADD COLUMN "oauth_sub" TEXT; diff --git a/internal/bootstrap/app_bootstrap.go b/internal/bootstrap/app_bootstrap.go index bfbd0c7..79dc3e4 100644 --- a/internal/bootstrap/app_bootstrap.go +++ b/internal/bootstrap/app_bootstrap.go @@ -11,10 +11,11 @@ import ( "sort" "strings" "time" - "tinyauth/internal/config" - "tinyauth/internal/controller" - "tinyauth/internal/repository" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/controller" + "github.com/steveiliop56/tinyauth/internal/repository" + "github.com/steveiliop56/tinyauth/internal/utils" "github.com/rs/zerolog/log" ) diff --git a/internal/bootstrap/router_bootstrap.go b/internal/bootstrap/router_bootstrap.go index 9318561..531f5e0 100644 --- a/internal/bootstrap/router_bootstrap.go +++ b/internal/bootstrap/router_bootstrap.go @@ -3,8 +3,9 @@ package bootstrap import ( "fmt" "strings" - "tinyauth/internal/controller" - "tinyauth/internal/middleware" + + "github.com/steveiliop56/tinyauth/internal/controller" + "github.com/steveiliop56/tinyauth/internal/middleware" "github.com/gin-gonic/gin" ) diff --git a/internal/bootstrap/service_bootstrap.go b/internal/bootstrap/service_bootstrap.go index 5731616..a921403 100644 --- a/internal/bootstrap/service_bootstrap.go +++ b/internal/bootstrap/service_bootstrap.go @@ -1,8 +1,8 @@ package bootstrap import ( - "tinyauth/internal/repository" - "tinyauth/internal/service" + "github.com/steveiliop56/tinyauth/internal/repository" + "github.com/steveiliop56/tinyauth/internal/service" "github.com/rs/zerolog/log" ) @@ -45,7 +45,7 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er services.dockerService = dockerService - accessControlsService := service.NewAccessControlsService(dockerService) + accessControlsService := service.NewAccessControlsService(dockerService, app.config.Apps) err = accessControlsService.Init() diff --git a/internal/config/config.go b/internal/config/config.go index e664c4d..d3e00de 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,6 +25,7 @@ type Config struct { LogJSON bool `description:"Enable JSON formatted logs." yaml:"logJSON"` Server ServerConfig `description:"Server configuration." yaml:"server"` Auth AuthConfig `description:"Authentication configuration." yaml:"auth"` + Apps map[string]App `description:"Application ACLs configuration." yaml:"apps"` OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth"` UI UIConfig `description:"UI customization." yaml:"ui"` Ldap LdapConfig `description:"LDAP configuration." yaml:"ldap"` @@ -79,6 +80,7 @@ const DefaultNamePrefix = "TINYAUTH_" // OAuth/OIDC config type Claims struct { + Sub string `json:"sub"` Name string `json:"name"` Email string `json:"email"` PreferredUsername string `json:"preferred_username"` @@ -125,6 +127,7 @@ type SessionCookie struct { TotpPending bool OAuthGroups string OAuthName string + OAuthSub string } type UserContext struct { @@ -138,6 +141,7 @@ type UserContext struct { OAuthGroups string TotpEnabled bool OAuthName string + OAuthSub string } // API responses and queries @@ -153,61 +157,55 @@ type RedirectQuery struct { RedirectURI string `url:"redirect_uri"` } -// Labels +// ACLs type Apps struct { - Apps map[string]App + Apps map[string]App `description:"App ACLs configuration." yaml:"apps"` } type App struct { - Config AppConfig - Users AppUsers - OAuth AppOAuth - IP AppIP - Response AppResponse - Path AppPath + Config AppConfig `description:"App configuration." yaml:"config"` + Users AppUsers `description:"User access configuration." yaml:"users"` + OAuth AppOAuth `description:"OAuth access configuration." yaml:"oauth"` + IP AppIP `description:"IP access configuration." yaml:"ip"` + Response AppResponse `description:"Response customization." yaml:"response"` + Path AppPath `description:"Path access configuration." yaml:"path"` } type AppConfig struct { - Domain string + Domain string `description:"The domain of the app." yaml:"domain"` } type AppUsers struct { - Allow string - Block string + Allow string `description:"Comma-separated list of allowed users." yaml:"allow"` + Block string `description:"Comma-separated list of blocked users." yaml:"block"` } type AppOAuth struct { - Whitelist string - Groups string + Whitelist string `description:"Comma-separated list of allowed OAuth groups." yaml:"whitelist"` + Groups string `description:"Comma-separated list of required OAuth groups." yaml:"groups"` } type AppIP struct { - Allow []string - Block []string - Bypass []string + Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"` + Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"` + Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication." yaml:"bypass"` } type AppResponse struct { - Headers []string - BasicAuth AppBasicAuth + Headers []string `description:"Custom headers to add to the response." yaml:"headers"` + BasicAuth AppBasicAuth `description:"Basic authentication for the app." yaml:"basicAuth"` } type AppBasicAuth struct { - Username string - Password string - PasswordFile string + Username string `description:"Basic auth username." yaml:"username"` + Password string `description:"Basic auth password." yaml:"password"` + PasswordFile string `description:"Path to the file containing the basic auth password." yaml:"passwordFile"` } type AppPath struct { - Allow string - Block string -} - -// Flags - -type Providers struct { - Providers map[string]OAuthServiceConfig + Allow string `description:"Comma-separated list of allowed paths." yaml:"allow"` + Block string `description:"Comma-separated list of blocked paths." yaml:"block"` } // API server diff --git a/internal/controller/context_controller.go b/internal/controller/context_controller.go index 5b3b30d..181c2f6 100644 --- a/internal/controller/context_controller.go +++ b/internal/controller/context_controller.go @@ -3,7 +3,8 @@ package controller import ( "fmt" "net/url" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/utils" "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" @@ -20,6 +21,7 @@ type UserContextResponse struct { OAuth bool `json:"oauth"` TotpPending bool `json:"totpPending"` OAuthName string `json:"oauthName"` + OAuthSub string `json:"oauthSub"` } type AppContextResponse struct { @@ -88,6 +90,7 @@ func (controller *ContextController) userContextHandler(c *gin.Context) { OAuth: context.OAuth, TotpPending: context.TotpPending, OAuthName: context.OAuthName, + OAuthSub: context.OAuthSub, } if err != nil { diff --git a/internal/controller/context_controller_test.go b/internal/controller/context_controller_test.go index bfefd13..7dee2c8 100644 --- a/internal/controller/context_controller_test.go +++ b/internal/controller/context_controller_test.go @@ -4,8 +4,9 @@ import ( "encoding/json" "net/http/httptest" "testing" - "tinyauth/internal/config" - "tinyauth/internal/controller" + + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/controller" "github.com/gin-gonic/gin" "gotest.tools/v3/assert" @@ -43,6 +44,7 @@ var userContext = config.UserContext{ TotpPending: false, OAuthGroups: "", TotpEnabled: false, + OAuthSub: "", } func setupContextController(middlewares *[]gin.HandlerFunc) (*gin.Engine, *httptest.ResponseRecorder) { diff --git a/internal/controller/oauth_controller.go b/internal/controller/oauth_controller.go index c58c6c4..3635e85 100644 --- a/internal/controller/oauth_controller.go +++ b/internal/controller/oauth_controller.go @@ -5,9 +5,10 @@ import ( "net/http" "strings" "time" - "tinyauth/internal/config" - "tinyauth/internal/service" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/service" + "github.com/steveiliop56/tinyauth/internal/utils" "github.com/gin-gonic/gin" "github.com/google/go-querystring/query" @@ -196,6 +197,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) { Provider: req.Provider, OAuthGroups: utils.CoalesceToString(user.Groups), OAuthName: service.GetName(), + OAuthSub: user.Sub, } log.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie") diff --git a/internal/controller/proxy_controller.go b/internal/controller/proxy_controller.go index eed127e..40f8374 100644 --- a/internal/controller/proxy_controller.go +++ b/internal/controller/proxy_controller.go @@ -5,9 +5,10 @@ import ( "net/http" "slices" "strings" - "tinyauth/internal/config" - "tinyauth/internal/service" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/service" + "github.com/steveiliop56/tinyauth/internal/utils" "github.com/gin-gonic/gin" "github.com/google/go-querystring/query" @@ -42,7 +43,8 @@ func NewProxyController(config ProxyControllerConfig, router *gin.RouterGroup, a func (controller *ProxyController) SetupRoutes() { proxyGroup := controller.router.Group("/auth") - proxyGroup.Any("/:proxy", controller.proxyHandler) + proxyGroup.GET("/:proxy", controller.proxyHandler) + proxyGroup.POST("/:proxy", controller.proxyHandler) } func (controller *ProxyController) proxyHandler(c *gin.Context) { @@ -67,15 +69,6 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { return } - if req.Proxy != "envoy" && c.Request.Method != http.MethodGet { - log.Warn().Str("method", c.Request.Method).Msg("Invalid method for proxy") - c.JSON(405, gin.H{ - "status": 405, - "message": "Method Not Allowed", - }) - return - } - isBrowser := strings.Contains(c.Request.Header.Get("Accept"), "text/html") if isBrowser { @@ -246,6 +239,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) { c.Header("Remote-Name", utils.SanitizeHeader(userContext.Name)) c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email)) c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups)) + c.Header("Remote-Sub", utils.SanitizeHeader(userContext.OAuthSub)) controller.setHeaders(c, acls) diff --git a/internal/controller/proxy_controller_test.go b/internal/controller/proxy_controller_test.go index 452155f..54bb888 100644 --- a/internal/controller/proxy_controller_test.go +++ b/internal/controller/proxy_controller_test.go @@ -3,9 +3,10 @@ package controller_test import ( "net/http/httptest" "testing" - "tinyauth/internal/config" - "tinyauth/internal/controller" - "tinyauth/internal/service" + + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/controller" + "github.com/steveiliop56/tinyauth/internal/service" "github.com/gin-gonic/gin" "gotest.tools/v3/assert" @@ -40,7 +41,7 @@ func setupProxyController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.En assert.NilError(t, dockerService.Init()) // Access controls - accessControlsService := service.NewAccessControlsService(dockerService) + accessControlsService := service.NewAccessControlsService(dockerService, map[string]config.App{}) assert.NilError(t, accessControlsService.Init()) @@ -80,13 +81,6 @@ func TestProxyHandler(t *testing.T) { assert.Equal(t, 400, recorder.Code) - // Test invalid method - recorder = httptest.NewRecorder() - req = httptest.NewRequest("POST", "/api/auth/traefik", nil) - router.ServeHTTP(recorder, req) - - assert.Equal(t, 405, recorder.Code) - // Test logged out user (traefik/caddy) recorder = httptest.NewRecorder() req = httptest.NewRequest("GET", "/api/auth/traefik", nil) diff --git a/internal/controller/resources_controller_test.go b/internal/controller/resources_controller_test.go index 8e4f843..c27b0a3 100644 --- a/internal/controller/resources_controller_test.go +++ b/internal/controller/resources_controller_test.go @@ -4,7 +4,8 @@ import ( "net/http/httptest" "os" "testing" - "tinyauth/internal/controller" + + "github.com/steveiliop56/tinyauth/internal/controller" "github.com/gin-gonic/gin" "gotest.tools/v3/assert" diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go index ff26de1..a607c4a 100644 --- a/internal/controller/user_controller.go +++ b/internal/controller/user_controller.go @@ -3,9 +3,10 @@ package controller import ( "fmt" "strings" - "tinyauth/internal/config" - "tinyauth/internal/service" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/service" + "github.com/steveiliop56/tinyauth/internal/utils" "github.com/gin-gonic/gin" "github.com/pquerna/otp/totp" diff --git a/internal/controller/user_controller_test.go b/internal/controller/user_controller_test.go index 6065521..1ec7197 100644 --- a/internal/controller/user_controller_test.go +++ b/internal/controller/user_controller_test.go @@ -7,9 +7,10 @@ import ( "strings" "testing" "time" - "tinyauth/internal/config" - "tinyauth/internal/controller" - "tinyauth/internal/service" + + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/controller" + "github.com/steveiliop56/tinyauth/internal/service" "github.com/gin-gonic/gin" "github.com/pquerna/otp/totp" diff --git a/internal/middleware/context_middleware.go b/internal/middleware/context_middleware.go index 2c903be..16f7c05 100644 --- a/internal/middleware/context_middleware.go +++ b/internal/middleware/context_middleware.go @@ -3,9 +3,10 @@ package middleware import ( "fmt" "strings" - "tinyauth/internal/config" - "tinyauth/internal/service" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/service" + "github.com/steveiliop56/tinyauth/internal/utils" "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" @@ -65,6 +66,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc { goto basic } + m.auth.RefreshSessionCookie(c) c.Set("context", &config.UserContext{ Username: cookie.Username, Name: cookie.Name, @@ -89,6 +91,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc { goto basic } + m.auth.RefreshSessionCookie(c) c.Set("context", &config.UserContext{ Username: cookie.Username, Name: cookie.Name, @@ -96,6 +99,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc { Provider: cookie.Provider, OAuthGroups: cookie.OAuthGroups, OAuthName: cookie.OAuthName, + OAuthSub: cookie.OAuthSub, IsLoggedIn: true, OAuth: true, }) diff --git a/internal/middleware/ui_middleware.go b/internal/middleware/ui_middleware.go index adcb784..59a5da9 100644 --- a/internal/middleware/ui_middleware.go +++ b/internal/middleware/ui_middleware.go @@ -7,7 +7,8 @@ import ( "os" "strings" "time" - "tinyauth/internal/assets" + + "github.com/steveiliop56/tinyauth/internal/assets" "github.com/gin-gonic/gin" ) diff --git a/internal/model/session_model.go b/internal/model/session_model.go new file mode 100644 index 0000000..283dd6b --- /dev/null +++ b/internal/model/session_model.go @@ -0,0 +1,14 @@ +package model + +type Session struct { + UUID string `gorm:"column:uuid;primaryKey"` + Username string `gorm:"column:username"` + Email string `gorm:"column:email"` + Name string `gorm:"column:name"` + Provider string `gorm:"column:provider"` + TOTPPending bool `gorm:"column:totp_pending"` + OAuthGroups string `gorm:"column:oauth_groups"` + Expiry int64 `gorm:"column:expiry"` + OAuthName string `gorm:"column:oauth_name"` + OAuthSub string `gorm:"column:oauth_sub"` +} diff --git a/internal/service/access_controls_service.go b/internal/service/access_controls_service.go index f6e4b56..74117ea 100644 --- a/internal/service/access_controls_service.go +++ b/internal/service/access_controls_service.go @@ -1,122 +1,54 @@ package service import ( - "tinyauth/internal/config" + "errors" + "strings" + + "github.com/rs/zerolog/log" + "github.com/steveiliop56/tinyauth/internal/config" ) -/* -Environment variable/flag based ACLs are disabled until v5 due to a technical challenge -with the current parsing logic. - -The current parser works for simple OAuth provider configs like: -- PROVIDERS_MY_AMAZING_PROVIDER_CLIENT_ID - -However, it breaks down when handling nested structs required for ACLs. The custom parsing -solution that worked for v4 OAuth providers is incompatible with the ACL parsing logic, -making the codebase unmaintainable and fragile. - -A solution is being considered for v5 that would standardize the format to something like: -- TINYAUTH_PROVIDERS_GOOGLE_CLIENTSECRET -- TINYAUTH_APPS_MYAPP_CONFIG_DOMAIN - -This would allow the Traefik parser to handle everything consistently, but requires a -config migration. Until this is resolved, environment-based ACLs are disabled and only -Docker label-based ACLs are supported. - -See: https://discord.com/channels/1337450123600465984/1337459086270271538/1434986689935179838 for more information -*/ - type AccessControlsService struct { docker *DockerService - // envACLs config.Apps + static map[string]config.App } -func NewAccessControlsService(docker *DockerService) *AccessControlsService { +func NewAccessControlsService(docker *DockerService, static map[string]config.App) *AccessControlsService { return &AccessControlsService{ docker: docker, + static: static, } } func (acls *AccessControlsService) Init() error { - // acls.envACLs = config.Apps{} - // env := os.Environ() - // appEnvVars := []string{} - - // for _, e := range env { - // if strings.HasPrefix(e, "TINYAUTH_APPS_") { - // appEnvVars = append(appEnvVars, e) - // } - // } - - // err := acls.loadEnvACLs(appEnvVars) - - // if err != nil { - // return err - // } - - // return nil - - return nil - + return nil // No initialization needed } -// func (acls *AccessControlsService) loadEnvACLs(appEnvVars []string) error { -// if len(appEnvVars) == 0 { -// return nil -// } +func (acls *AccessControlsService) lookupStaticACLs(domain string) (config.App, error) { + for app, config := range acls.static { + if config.Config.Domain == domain { + log.Debug().Str("name", app).Msg("Found matching container by domain") + return config, nil + } -// envAcls := map[string]string{} + if strings.SplitN(domain, ".", 2)[0] == app { + log.Debug().Str("name", app).Msg("Found matching container by app name") + return config, nil + } + } + return config.App{}, errors.New("no results") +} -// for _, e := range appEnvVars { -// parts := strings.SplitN(e, "=", 2) -// if len(parts) != 2 { -// continue -// } +func (acls *AccessControlsService) GetAccessControls(domain string) (config.App, error) { + // First check in the static config + app, err := acls.lookupStaticACLs(domain) -// key := parts[0] -// key = strings.ToLower(key) -// key = strings.ReplaceAll(key, "_", ".") -// value := parts[1] -// envAcls[key] = value -// } - -// apps, err := decoders.DecodeLabels(envAcls) - -// if err != nil { -// return err -// } - -// acls.envACLs = apps -// return nil -// } - -// func (acls *AccessControlsService) lookupEnvACLs(appDomain string) *config.App { -// if len(acls.envACLs.Apps) == 0 { -// return nil -// } - -// for appName, appACLs := range acls.envACLs.Apps { -// if appACLs.Config.Domain == appDomain { -// return &appACLs -// } - -// if strings.SplitN(appDomain, ".", 2)[0] == appName { -// return &appACLs -// } -// } - -// return nil -// } - -func (acls *AccessControlsService) GetAccessControls(appDomain string) (config.App, error) { - // First check environment variables - // envACLs := acls.lookupEnvACLs(appDomain) - - // if envACLs != nil { - // log.Debug().Str("domain", appDomain).Msg("Found matching access controls in environment variables") - // return *envACLs, nil - // } + if err == nil { + log.Debug().Msg("Using ACls from static configuration") + return app, nil + } // Fallback to Docker labels - return acls.docker.GetLabels(appDomain) + log.Debug().Msg("Falling back to Docker labels for ACLs") + return acls.docker.GetLabels(domain) } diff --git a/internal/service/auth_service.go b/internal/service/auth_service.go index c09e04d..599c652 100644 --- a/internal/service/auth_service.go +++ b/internal/service/auth_service.go @@ -9,9 +9,10 @@ import ( "strings" "sync" "time" - "tinyauth/internal/config" - "tinyauth/internal/repository" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/model" + "github.com/steveiliop56/tinyauth/internal/utils" "github.com/gin-gonic/gin" "github.com/google/uuid" @@ -57,7 +58,6 @@ func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapS } func (auth *AuthService) Init() error { - auth.ctx = context.Background() return nil } @@ -215,6 +215,7 @@ func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.Sessio OAuthGroups: data.OAuthGroups, Expiry: time.Now().Add(time.Duration(expiry) * time.Second).Unix(), OAuthName: data.OAuthName, + OAuthSub: data.OAuthSub, } _, err = auth.queries.CreateSession(c, session) @@ -228,6 +229,40 @@ func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.Sessio return nil } +func (auth *AuthService) RefreshSessionCookie(c *gin.Context) error { + cookie, err := c.Cookie(auth.config.SessionCookieName) + + if err != nil { + return err + } + + session, err := gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).First(c) + + if err != nil { + return err + } + + currentTime := time.Now().Unix() + + if session.Expiry-currentTime > int64(time.Hour.Seconds()) { + return nil + } + + newExpiry := currentTime + int64(time.Hour.Seconds()) + + _, err = gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).Updates(c, model.Session{ + Expiry: newExpiry, + }) + + if err != nil { + return err + } + + c.SetCookie(auth.config.SessionCookieName, cookie, int(time.Hour.Seconds()), "/", fmt.Sprintf(".%s", auth.config.CookieDomain), auth.config.SecureCookie, true) + + return nil +} + func (auth *AuthService) DeleteSessionCookie(c *gin.Context) error { cookie, err := c.Cookie(auth.config.SessionCookieName) @@ -282,6 +317,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie, TotpPending: session.TotpPending, OAuthGroups: session.OAuthGroups, OAuthName: session.OAuthName, + OAuthSub: session.OAuthSub, }, nil } diff --git a/internal/service/database_service.go b/internal/service/database_service.go new file mode 100644 index 0000000..d3ac9b5 --- /dev/null +++ b/internal/service/database_service.go @@ -0,0 +1,92 @@ +package service + +import ( + "database/sql" + "fmt" + "os" + "path/filepath" + + "github.com/steveiliop56/tinyauth/internal/assets" + + "github.com/glebarez/sqlite" + "github.com/golang-migrate/migrate/v4" + sqliteMigrate "github.com/golang-migrate/migrate/v4/database/sqlite3" + "github.com/golang-migrate/migrate/v4/source/iofs" + "gorm.io/gorm" +) + +type DatabaseServiceConfig struct { + DatabasePath string +} + +type DatabaseService struct { + config DatabaseServiceConfig + database *gorm.DB +} + +func NewDatabaseService(config DatabaseServiceConfig) *DatabaseService { + return &DatabaseService{ + config: config, + } +} + +func (ds *DatabaseService) Init() error { + dbPath := ds.config.DatabasePath + if dbPath == "" { + dbPath = "/data/tinyauth.db" + } + + dir := filepath.Dir(dbPath) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create database directory %s: %w", dir, err) + } + + gormDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) + + if err != nil { + return err + } + + sqlDB, err := gormDB.DB() + + if err != nil { + return err + } + + sqlDB.SetMaxOpenConns(1) + + err = ds.migrateDatabase(sqlDB) + + if err != nil && err != migrate.ErrNoChange { + return err + } + + ds.database = gormDB + return nil +} + +func (ds *DatabaseService) migrateDatabase(sqlDB *sql.DB) error { + data, err := iofs.New(assets.Migrations, "migrations") + + if err != nil { + return err + } + + target, err := sqliteMigrate.WithInstance(sqlDB, &sqliteMigrate.Config{}) + + if err != nil { + return err + } + + migrator, err := migrate.NewWithInstance("iofs", data, "tinyauth", target) + + if err != nil { + return err + } + + return migrator.Up() +} + +func (ds *DatabaseService) GetDatabase() *gorm.DB { + return ds.database +} diff --git a/internal/service/docker_service.go b/internal/service/docker_service.go index 1c39778..a0693b2 100644 --- a/internal/service/docker_service.go +++ b/internal/service/docker_service.go @@ -3,8 +3,9 @@ package service import ( "context" "strings" - "tinyauth/internal/config" - "tinyauth/internal/utils/decoders" + + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/utils/decoders" container "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" diff --git a/internal/service/generic_oauth_service.go b/internal/service/generic_oauth_service.go index d68f8ae..ac3252c 100644 --- a/internal/service/generic_oauth_service.go +++ b/internal/service/generic_oauth_service.go @@ -10,7 +10,8 @@ import ( "io" "net/http" "time" - "tinyauth/internal/config" + + "github.com/steveiliop56/tinyauth/internal/config" "github.com/rs/zerolog/log" "golang.org/x/oauth2" diff --git a/internal/service/github_oauth_service.go b/internal/service/github_oauth_service.go index 11dcf61..35b552a 100644 --- a/internal/service/github_oauth_service.go +++ b/internal/service/github_oauth_service.go @@ -9,8 +9,10 @@ import ( "fmt" "io" "net/http" + "strconv" "time" - "tinyauth/internal/config" + + "github.com/steveiliop56/tinyauth/internal/config" "golang.org/x/oauth2" "golang.org/x/oauth2/endpoints" @@ -26,6 +28,7 @@ type GithubEmailResponse []struct { type GithubUserInfoResponse struct { Login string `json:"login"` Name string `json:"name"` + ID int `json:"id"` } type GithubOAuthService struct { @@ -171,6 +174,7 @@ func (github *GithubOAuthService) Userinfo() (config.Claims, error) { user.PreferredUsername = userInfo.Login user.Name = userInfo.Name + user.Sub = strconv.Itoa(userInfo.ID) return user, nil } diff --git a/internal/service/google_oauth_service.go b/internal/service/google_oauth_service.go index 0ece6f2..6dfbeaf 100644 --- a/internal/service/google_oauth_service.go +++ b/internal/service/google_oauth_service.go @@ -10,18 +10,14 @@ import ( "net/http" "strings" "time" - "tinyauth/internal/config" + + "github.com/steveiliop56/tinyauth/internal/config" "golang.org/x/oauth2" "golang.org/x/oauth2/endpoints" ) -var GoogleOAuthScopes = []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"} - -type GoogleUserInfoResponse struct { - Email string `json:"email"` - Name string `json:"name"` -} +var GoogleOAuthScopes = []string{"openid", "email", "profile"} type GoogleOAuthService struct { config oauth2.Config @@ -90,7 +86,7 @@ func (google *GoogleOAuthService) Userinfo() (config.Claims, error) { client := google.config.Client(google.context, google.token) - res, err := client.Get("https://www.googleapis.com/userinfo/v2/me") + res, err := client.Get("https://openidconnect.googleapis.com/v1/userinfo") if err != nil { return config.Claims{}, err } @@ -105,16 +101,12 @@ func (google *GoogleOAuthService) Userinfo() (config.Claims, error) { return config.Claims{}, err } - var userInfo GoogleUserInfoResponse - - err = json.Unmarshal(body, &userInfo) + err = json.Unmarshal(body, &user) if err != nil { return config.Claims{}, err } - user.PreferredUsername = strings.Split(userInfo.Email, "@")[0] - user.Name = userInfo.Name - user.Email = userInfo.Email + user.PreferredUsername = strings.SplitN(user.Email, "@", 2)[0] return user, nil } diff --git a/internal/service/oauth_broker_service.go b/internal/service/oauth_broker_service.go index e15d9c6..2c7d4cf 100644 --- a/internal/service/oauth_broker_service.go +++ b/internal/service/oauth_broker_service.go @@ -2,7 +2,8 @@ package service import ( "errors" - "tinyauth/internal/config" + + "github.com/steveiliop56/tinyauth/internal/config" "github.com/rs/zerolog/log" "golang.org/x/exp/slices" diff --git a/internal/utils/app_utils.go b/internal/utils/app_utils.go index a2b23c8..28d815d 100644 --- a/internal/utils/app_utils.go +++ b/internal/utils/app_utils.go @@ -5,7 +5,8 @@ import ( "net" "net/url" "strings" - "tinyauth/internal/config" + + "github.com/steveiliop56/tinyauth/internal/config" "github.com/gin-gonic/gin" "github.com/weppos/publicsuffix-go/publicsuffix" diff --git a/internal/utils/app_utils_test.go b/internal/utils/app_utils_test.go index 55fde36..08bb893 100644 --- a/internal/utils/app_utils_test.go +++ b/internal/utils/app_utils_test.go @@ -2,8 +2,9 @@ package utils_test import ( "testing" - "tinyauth/internal/config" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/utils" "github.com/gin-gonic/gin" "gotest.tools/v3/assert" diff --git a/internal/utils/decoders/label_decoder_test.go b/internal/utils/decoders/label_decoder_test.go index c8874f4..fb63552 100644 --- a/internal/utils/decoders/label_decoder_test.go +++ b/internal/utils/decoders/label_decoder_test.go @@ -2,8 +2,9 @@ package decoders_test import ( "testing" - "tinyauth/internal/config" - "tinyauth/internal/utils/decoders" + + "github.com/steveiliop56/tinyauth/internal/config" + "github.com/steveiliop56/tinyauth/internal/utils/decoders" "gotest.tools/v3/assert" ) diff --git a/internal/utils/label_utils_test.go b/internal/utils/label_utils_test.go index f38302d..8edac79 100644 --- a/internal/utils/label_utils_test.go +++ b/internal/utils/label_utils_test.go @@ -2,7 +2,8 @@ package utils_test import ( "testing" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/utils" "gotest.tools/v3/assert" ) diff --git a/internal/utils/loaders/loader_env.go b/internal/utils/loaders/loader_env.go index 9811974..608ae10 100644 --- a/internal/utils/loaders/loader_env.go +++ b/internal/utils/loaders/loader_env.go @@ -3,7 +3,8 @@ package loaders import ( "fmt" "os" - "tinyauth/internal/config" + + "github.com/steveiliop56/tinyauth/internal/config" "github.com/traefik/paerser/cli" "github.com/traefik/paerser/env" diff --git a/internal/utils/security_utils_test.go b/internal/utils/security_utils_test.go index 9adcd7c..3ebd681 100644 --- a/internal/utils/security_utils_test.go +++ b/internal/utils/security_utils_test.go @@ -3,7 +3,8 @@ package utils_test import ( "os" "testing" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/utils" "gotest.tools/v3/assert" ) diff --git a/internal/utils/string_utils_test.go b/internal/utils/string_utils_test.go index 3677eb6..5ea1b47 100644 --- a/internal/utils/string_utils_test.go +++ b/internal/utils/string_utils_test.go @@ -2,7 +2,8 @@ package utils_test import ( "testing" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/utils" "gotest.tools/v3/assert" ) diff --git a/internal/utils/user_utils.go b/internal/utils/user_utils.go index 0044db4..8ee8e94 100644 --- a/internal/utils/user_utils.go +++ b/internal/utils/user_utils.go @@ -3,7 +3,8 @@ package utils import ( "errors" "strings" - "tinyauth/internal/config" + + "github.com/steveiliop56/tinyauth/internal/config" ) func ParseUsers(users string) ([]config.User, error) { diff --git a/internal/utils/user_utils_test.go b/internal/utils/user_utils_test.go index d04636a..3cc23c3 100644 --- a/internal/utils/user_utils_test.go +++ b/internal/utils/user_utils_test.go @@ -3,7 +3,8 @@ package utils_test import ( "os" "testing" - "tinyauth/internal/utils" + + "github.com/steveiliop56/tinyauth/internal/utils" "gotest.tools/v3/assert" ) diff --git a/paerser b/paerser new file mode 160000 index 0000000..7e1b633 --- /dev/null +++ b/paerser @@ -0,0 +1 @@ +Subproject commit 7e1b633ba9fd73f136891ddfec372b2e94d9e9c8 diff --git a/patches/nested_maps.diff b/patches/nested_maps.diff new file mode 100644 index 0000000..91c883f --- /dev/null +++ b/patches/nested_maps.diff @@ -0,0 +1,95 @@ +diff --git a/env/env_test.go b/env/env_test.go +index 7045569..365dc00 100644 +--- a/env/env_test.go ++++ b/env/env_test.go +@@ -166,6 +166,38 @@ func TestDecode(t *testing.T) { + Foo: &struct{ Field string }{}, + }, + }, ++ { ++ desc: "map under the root key", ++ environ: []string{"TRAEFIK_FOO_BAR_FOOBAR_BARFOO=foo"}, ++ element: &struct { ++ Foo map[string]struct { ++ Foobar struct { ++ Barfoo string ++ } ++ } ++ }{}, ++ expected: &struct { ++ Foo map[string]struct { ++ Foobar struct { ++ Barfoo string ++ } ++ } ++ }{ ++ Foo: map[string]struct { ++ Foobar struct { ++ Barfoo string ++ } ++ }{ ++ "bar": { ++ Foobar: struct { ++ Barfoo string ++ }{ ++ Barfoo: "foo", ++ }, ++ }, ++ }, ++ }, ++ }, + } + + for _, test := range testCases { +diff --git a/parser/nodes_metadata.go b/parser/nodes_metadata.go +index 36946c1..0279705 100644 +--- a/parser/nodes_metadata.go ++++ b/parser/nodes_metadata.go +@@ -75,8 +75,13 @@ func (m metadata) add(rootType reflect.Type, node *Node) error { + node.Kind = fType.Kind() + node.Tag = field.Tag + +- if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Pointer && fType.Elem().Kind() == reflect.Struct || +- fType.Kind() == reflect.Map { ++ if node.Kind == reflect.String && len(node.Children) > 0 { ++ fType = reflect.TypeOf(struct{}{}) ++ node.Kind = reflect.Struct ++ } ++ ++ if node.Kind == reflect.Struct || node.Kind == reflect.Pointer && fType.Elem().Kind() == reflect.Struct || ++ node.Kind == reflect.Map { + if len(node.Children) == 0 && !(field.Tag.Get(m.TagName) == TagLabelAllowEmpty || field.Tag.Get(m.TagName) == "-") { + return fmt.Errorf("%s cannot be a standalone element (type %s)", node.Name, fType) + } +@@ -90,11 +95,11 @@ func (m metadata) add(rootType reflect.Type, node *Node) error { + return nil + } + +- if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Pointer && fType.Elem().Kind() == reflect.Struct { ++ if node.Kind == reflect.Struct || node.Kind == reflect.Pointer && fType.Elem().Kind() == reflect.Struct { + return m.browseChildren(fType, node) + } + +- if fType.Kind() == reflect.Map { ++ if node.Kind == reflect.Map { + if fType.Elem().Kind() == reflect.Interface { + addRawValue(node) + return nil +@@ -115,7 +120,7 @@ func (m metadata) add(rootType reflect.Type, node *Node) error { + return nil + } + +- if fType.Kind() == reflect.Slice { ++ if node.Kind == reflect.Slice { + if m.AllowSliceAsStruct && field.Tag.Get(TagLabelSliceAsStruct) != "" { + return m.browseChildren(fType.Elem(), node) + } +@@ -129,7 +134,7 @@ func (m metadata) add(rootType reflect.Type, node *Node) error { + return nil + } + +- return fmt.Errorf("invalid node %s: %v", node.Name, fType.Kind()) ++ return fmt.Errorf("invalid node %s: %v", node.Name, node.Kind) + } + + func (m metadata) findTypedField(rType reflect.Type, node *Node) (reflect.StructField, error) {