Compare commits

...

10 Commits

Author SHA1 Message Date
Stavros
d348d97003 chore: update contributing instructions 2025-12-30 16:53:41 +02:00
Stavros
3a93800158 refactor: update release workflows to include submodule and patches 2025-12-30 16:31:50 +02:00
Stavros
b37614f458 feat: add paerser as submodule and apply patch for nested maps 2025-12-30 16:08:40 +02:00
Stavros
333b854533 wip 2025-12-29 22:44:23 +02:00
Stavros
986ac88e14 Merge branch 'main' of https://github.com/steveiliop56/tinyauth 2025-12-29 19:46:38 +02:00
Stavros
b159f44729 fix: add missing ldap search filter 2025-12-29 19:46:33 +02:00
Stavros
43487d44f7 feat: forward sub from oidc providers (#543)
* feat: forward sub from oidc providers

* fix: review comments
2025-12-26 19:02:51 +02:00
Stavros
2d8af0510e feat: refresh session cookie when session is active (#540)
* feat: refresh session cookie when session is active

* refactor: use current time to set new expiry
2025-12-26 17:55:54 +02:00
Stavros
a1c3e416b6 refactor: use proper module name (#542)
* chore: reorganize go mod

* refactor: use proper module name
2025-12-26 17:53:24 +02:00
Stavros
7269fa1b95 chore: disable issue enrichment in coderabbit 2025-12-23 23:10:33 +02:00
53 changed files with 518 additions and 287 deletions

3
.coderabbit.yaml Normal file
View File

@@ -0,0 +1,3 @@
issue_enrichment:
auto_enrich:
enabled: false

View File

@@ -18,7 +18,16 @@ jobs:
- name: Setup go - name: Setup go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: 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 - name: Install frontend dependencies
run: | run: |

View File

@@ -61,7 +61,16 @@ jobs:
- name: Install go - name: Install go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: 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 - name: Install frontend dependencies
run: | run: |
@@ -107,7 +116,16 @@ jobs:
- name: Install go - name: Install go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: 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 - name: Install frontend dependencies
run: | run: |
@@ -147,6 +165,15 @@ jobs:
with: with:
ref: nightly 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 - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -205,6 +232,15 @@ jobs:
with: with:
ref: nightly 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 - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -263,6 +299,15 @@ jobs:
with: with:
ref: nightly 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 - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -321,6 +366,15 @@ jobs:
with: with:
ref: nightly 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 - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5

View File

@@ -39,7 +39,16 @@ jobs:
- name: Install go - name: Install go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: 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 - name: Install frontend dependencies
run: | run: |
@@ -82,7 +91,16 @@ jobs:
- name: Install go - name: Install go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: 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 - name: Install frontend dependencies
run: | run: |
@@ -119,6 +137,15 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 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 - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -174,6 +201,15 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 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 - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -229,6 +265,15 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 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 - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -284,6 +329,15 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 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 - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5

5
.gitignore vendored
View File

@@ -33,4 +33,7 @@
# binary out # binary out
/tinyauth.db /tinyauth.db
/resources /resources
# debug files
__debug_*

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "paerser"]
path = paerser
url = https://github.com/traefik/paerser
ignore = all

View File

@@ -5,7 +5,7 @@ Contributing is relatively easy, you just need to follow the steps below and you
## Requirements ## Requirements
- Bun - Bun
- Golang v1.23.2 and above - Golang 1.24.0+
- Git - Git
- Docker - Docker
@@ -18,12 +18,21 @@ git clone https://github.com/steveiliop56/tinyauth
cd tinyauth cd tinyauth
``` ```
## Install requirements ## Initialize submodules
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: The project uses Git submodules for some dependencies, so you need to initialize them with:
```sh ```sh
go mod tidy 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:
```sh
go mod download
``` ```
You also need to download the frontend dependencies, this can be done like so: You also need to download the frontend dependencies, this can be done like so:
@@ -33,13 +42,21 @@ cd frontend/
bun install 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 ## 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. 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 ## 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 *.dev.example.com -> 127.0.0.1
@@ -49,7 +66,7 @@ dev.example.com -> 127.0.0.1
> [!TIP] > [!TIP]
> You can use [sslip.io](https://sslip.io) as a domain if you don't have one to develop with. > 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 ```sh
docker compose -f docker-compose.dev.yml up --build docker compose -f docker-compose.dev.yml up --build

View File

@@ -28,6 +28,8 @@ ARG BUILD_TIMESTAMP
WORKDIR /tinyauth WORKDIR /tinyauth
COPY ./paerser ./paerser
COPY go.mod ./ COPY go.mod ./
COPY go.sum ./ COPY go.sum ./
@@ -38,7 +40,7 @@ COPY ./internal ./internal
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist 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 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 # Runner
FROM alpine:3.23 AS 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"] HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 CMD ["tinyauth", "healthcheck"]
ENTRYPOINT ["tinyauth"] ENTRYPOINT ["tinyauth"]

View File

@@ -2,6 +2,8 @@ FROM golang:1.25-alpine3.21
WORKDIR /tinyauth WORKDIR /tinyauth
COPY ./paerser ./paerser
COPY go.mod ./ COPY go.mod ./
COPY go.sum ./ COPY go.sum ./
@@ -20,4 +22,4 @@ ENV TINYAUTH_DATABASEPATH=/data/tinyauth.db
ENV TINYAUTH_RESOURCESDIR=/data/resources ENV TINYAUTH_RESOURCESDIR=/data/resources
ENTRYPOINT ["air", "-c", "air.toml"] ENTRYPOINT ["air", "-c", "air.toml"]

View File

@@ -28,6 +28,8 @@ ARG BUILD_TIMESTAMP
WORKDIR /tinyauth WORKDIR /tinyauth
COPY ./paerser ./paerser
COPY go.mod ./ COPY go.mod ./
COPY go.sum ./ COPY go.sum ./
@@ -40,7 +42,7 @@ COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
RUN mkdir -p data 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 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 # Runner
FROM gcr.io/distroless/static-debian12:latest AS 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"] HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 CMD ["tinyauth", "healthcheck"]
ENTRYPOINT ["tinyauth"] ENTRYPOINT ["tinyauth"]

View File

@@ -6,7 +6,8 @@ import (
"os" "os"
"strings" "strings"
"time" "time"
"tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/charmbracelet/huh" "github.com/charmbracelet/huh"
"github.com/mdp/qrterminal/v3" "github.com/mdp/qrterminal/v3"

View File

@@ -5,9 +5,10 @@ import (
"os" "os"
"strings" "strings"
"time" "time"
"tinyauth/internal/bootstrap"
"tinyauth/internal/config" "github.com/steveiliop56/tinyauth/internal/bootstrap"
"tinyauth/internal/utils/loaders" "github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/utils/loaders"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@@ -33,6 +34,10 @@ func NewTinyauthCmdConfiguration() *config.Config {
ForgotPasswordMessage: "You can change your password by changing the configuration.", ForgotPasswordMessage: "You can change your password by changing the configuration.",
BackgroundImage: "/background.jpg", BackgroundImage: "/background.jpg",
}, },
Ldap: config.LdapConfig{
Insecure: false,
SearchFilter: "(uid=%s)",
},
Experimental: config.ExperimentalConfig{ Experimental: config.ExperimentalConfig{
ConfigFile: "", ConfigFile: "",
}, },

View File

@@ -5,7 +5,8 @@ import (
"fmt" "fmt"
"os" "os"
"time" "time"
"tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/charmbracelet/huh" "github.com/charmbracelet/huh"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"

View File

@@ -2,7 +2,8 @@ package main
import ( import (
"fmt" "fmt"
"tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/traefik/paerser/cli" "github.com/traefik/paerser/cli"
) )

View File

@@ -42,7 +42,6 @@ services:
volumes: volumes:
- ./internal:/tinyauth/internal - ./internal:/tinyauth/internal
- ./cmd:/tinyauth/cmd - ./cmd:/tinyauth/cmd
- ./main.go:/tinyauth/main.go
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ./data:/data - ./data:/data
ports: ports:

85
go.mod
View File

@@ -1,22 +1,29 @@
module tinyauth module github.com/steveiliop56/tinyauth
go 1.24.0 go 1.24.0
toolchain go1.24.3 toolchain go1.24.3
replace github.com/traefik/paerser v0.2.2 => ./paerser
require ( require (
github.com/cenkalti/backoff/v5 v5.0.3 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/gin-gonic/gin v1.11.0
github.com/glebarez/sqlite 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/golang-migrate/migrate/v4 v4.19.1
github.com/google/go-querystring v1.1.0 github.com/google/go-querystring v1.1.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/mdp/qrterminal/v3 v3.2.1 github.com/mdp/qrterminal/v3 v3.2.1
github.com/pquerna/otp v1.5.0
github.com/rs/zerolog v1.34.0 github.com/rs/zerolog v1.34.0
github.com/traefik/paerser v0.2.2 github.com/traefik/paerser v0.2.2
github.com/weppos/publicsuffix-go v0.50.1 github.com/weppos/publicsuffix-go v0.50.1
golang.org/x/crypto v0.46.0 golang.org/x/crypto v0.46.0
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
golang.org/x/oauth2 v0.34.0
gorm.io/gorm v1.31.1 gorm.io/gorm v1.31.1
gotest.tools/v3 v3.5.2 gotest.tools/v3 v3.5.2
) )
@@ -27,43 +34,6 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // 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/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-playground/validator/v10 v10.28.0 // 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/mattn/go-sqlite3 v1.14.32 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
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/shopspring/decimal v1.4.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
golang.org/x/term v0.38.0 // 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
)
require (
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
@@ -73,14 +43,17 @@ require (
github.com/catppuccin/go v0.3.0 // indirect github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
github.com/charmbracelet/bubbletea v1.3.6 // 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/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // 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/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudwego/base64x v0.1.6 // 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/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-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
@@ -88,12 +61,20 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/gin-contrib/sse v1.1.0 // 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/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.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-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/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
@@ -102,31 +83,49 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // 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/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/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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // 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/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // 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/rivo/uniseg v0.4.7 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.10.0 // indirect github.com/spf13/cast v1.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // 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/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.37.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/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect
golang.org/x/arch v0.20.0 // indirect golang.org/x/arch v0.20.0 // indirect
golang.org/x/net v0.47.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/sync v0.19.0 // indirect
golang.org/x/sys v0.39.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 golang.org/x/text v0.32.0 // indirect
google.golang.org/protobuf v1.36.9 // 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
) )

2
go.sum
View File

@@ -271,8 +271,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.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 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 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= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=

View File

@@ -0,0 +1 @@
ALTER TABLE "sessions" DROP COLUMN "oauth_sub";

View File

@@ -0,0 +1 @@
ALTER TABLE "sessions" ADD COLUMN "oauth_sub" TEXT;

View File

@@ -11,10 +11,11 @@ import (
"sort" "sort"
"strings" "strings"
"time" "time"
"tinyauth/internal/config"
"tinyauth/internal/controller" "github.com/steveiliop56/tinyauth/internal/config"
"tinyauth/internal/model" "github.com/steveiliop56/tinyauth/internal/controller"
"tinyauth/internal/utils" "github.com/steveiliop56/tinyauth/internal/model"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"gorm.io/gorm" "gorm.io/gorm"

View File

@@ -3,8 +3,9 @@ package bootstrap
import ( import (
"fmt" "fmt"
"strings" "strings"
"tinyauth/internal/controller"
"tinyauth/internal/middleware" "github.com/steveiliop56/tinyauth/internal/controller"
"github.com/steveiliop56/tinyauth/internal/middleware"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )

View File

@@ -1,7 +1,7 @@
package bootstrap package bootstrap
import ( import (
"tinyauth/internal/service" "github.com/steveiliop56/tinyauth/internal/service"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -57,7 +57,7 @@ func (app *BootstrapApp) initServices() (Services, error) {
services.dockerService = dockerService services.dockerService = dockerService
accessControlsService := service.NewAccessControlsService(dockerService) accessControlsService := service.NewAccessControlsService(dockerService, app.config.Apps)
err = accessControlsService.Init() err = accessControlsService.Init()

View File

@@ -25,6 +25,7 @@ type Config struct {
LogJSON bool `description:"Enable JSON formatted logs." yaml:"logJSON"` LogJSON bool `description:"Enable JSON formatted logs." yaml:"logJSON"`
Server ServerConfig `description:"Server configuration." yaml:"server"` Server ServerConfig `description:"Server configuration." yaml:"server"`
Auth AuthConfig `description:"Authentication configuration." yaml:"auth"` Auth AuthConfig `description:"Authentication configuration." yaml:"auth"`
Apps map[string]App `description:"Application ACLs configuration." yaml:"apps"`
OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth"` OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth"`
UI UIConfig `description:"UI customization." yaml:"ui"` UI UIConfig `description:"UI customization." yaml:"ui"`
Ldap LdapConfig `description:"LDAP configuration." yaml:"ldap"` Ldap LdapConfig `description:"LDAP configuration." yaml:"ldap"`
@@ -79,6 +80,7 @@ const DefaultNamePrefix = "TINYAUTH_"
// OAuth/OIDC config // OAuth/OIDC config
type Claims struct { type Claims struct {
Sub string `json:"sub"`
Name string `json:"name"` Name string `json:"name"`
Email string `json:"email"` Email string `json:"email"`
PreferredUsername string `json:"preferred_username"` PreferredUsername string `json:"preferred_username"`
@@ -125,6 +127,7 @@ type SessionCookie struct {
TotpPending bool TotpPending bool
OAuthGroups string OAuthGroups string
OAuthName string OAuthName string
OAuthSub string
} }
type UserContext struct { type UserContext struct {
@@ -138,6 +141,7 @@ type UserContext struct {
OAuthGroups string OAuthGroups string
TotpEnabled bool TotpEnabled bool
OAuthName string OAuthName string
OAuthSub string
} }
// API responses and queries // API responses and queries
@@ -153,61 +157,55 @@ type RedirectQuery struct {
RedirectURI string `url:"redirect_uri"` RedirectURI string `url:"redirect_uri"`
} }
// Labels // ACLs
type Apps struct { type Apps struct {
Apps map[string]App Apps map[string]App `description:"App ACLs configuration." yaml:"apps"`
} }
type App struct { type App struct {
Config AppConfig Config AppConfig `description:"App configuration." yaml:"config"`
Users AppUsers Users AppUsers `description:"User access configuration." yaml:"users"`
OAuth AppOAuth OAuth AppOAuth `description:"OAuth access configuration." yaml:"oauth"`
IP AppIP IP AppIP `description:"IP access configuration." yaml:"ip"`
Response AppResponse Response AppResponse `description:"Response customization." yaml:"response"`
Path AppPath Path AppPath `description:"Path access configuration." yaml:"path"`
} }
type AppConfig struct { type AppConfig struct {
Domain string Domain string `description:"The domain of the app." yaml:"domain"`
} }
type AppUsers struct { type AppUsers struct {
Allow string Allow string `description:"Comma-separated list of allowed users." yaml:"allow"`
Block string Block string `description:"Comma-separated list of blocked users." yaml:"block"`
} }
type AppOAuth struct { type AppOAuth struct {
Whitelist string Whitelist string `description:"Comma-separated list of allowed OAuth groups." yaml:"whitelist"`
Groups string Groups string `description:"Comma-separated list of required OAuth groups." yaml:"groups"`
} }
type AppIP struct { type AppIP struct {
Allow []string Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"`
Block []string Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"`
Bypass []string Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication." yaml:"bypass"`
} }
type AppResponse struct { type AppResponse struct {
Headers []string Headers []string `description:"Custom headers to add to the response." yaml:"headers"`
BasicAuth AppBasicAuth BasicAuth AppBasicAuth `description:"Basic authentication for the app." yaml:"basicAuth"`
} }
type AppBasicAuth struct { type AppBasicAuth struct {
Username string Username string `description:"Basic auth username." yaml:"username"`
Password string Password string `description:"Basic auth password." yaml:"password"`
PasswordFile string PasswordFile string `description:"Path to the file containing the basic auth password." yaml:"passwordFile"`
} }
type AppPath struct { type AppPath struct {
Allow string Allow string `description:"Comma-separated list of allowed paths." yaml:"allow"`
Block string Block string `description:"Comma-separated list of blocked paths." yaml:"block"`
}
// Flags
type Providers struct {
Providers map[string]OAuthServiceConfig
} }
// API server // API server

View File

@@ -3,7 +3,8 @@ package controller
import ( import (
"fmt" "fmt"
"net/url" "net/url"
"tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@@ -20,6 +21,7 @@ type UserContextResponse struct {
OAuth bool `json:"oauth"` OAuth bool `json:"oauth"`
TotpPending bool `json:"totpPending"` TotpPending bool `json:"totpPending"`
OAuthName string `json:"oauthName"` OAuthName string `json:"oauthName"`
OAuthSub string `json:"oauthSub"`
} }
type AppContextResponse struct { type AppContextResponse struct {
@@ -88,6 +90,7 @@ func (controller *ContextController) userContextHandler(c *gin.Context) {
OAuth: context.OAuth, OAuth: context.OAuth,
TotpPending: context.TotpPending, TotpPending: context.TotpPending,
OAuthName: context.OAuthName, OAuthName: context.OAuthName,
OAuthSub: context.OAuthSub,
} }
if err != nil { if err != nil {

View File

@@ -4,8 +4,9 @@ import (
"encoding/json" "encoding/json"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"tinyauth/internal/config"
"tinyauth/internal/controller" "github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/controller"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
@@ -43,6 +44,7 @@ var userContext = config.UserContext{
TotpPending: false, TotpPending: false,
OAuthGroups: "", OAuthGroups: "",
TotpEnabled: false, TotpEnabled: false,
OAuthSub: "",
} }
func setupContextController(middlewares *[]gin.HandlerFunc) (*gin.Engine, *httptest.ResponseRecorder) { func setupContextController(middlewares *[]gin.HandlerFunc) (*gin.Engine, *httptest.ResponseRecorder) {

View File

@@ -5,9 +5,10 @@ import (
"net/http" "net/http"
"strings" "strings"
"time" "time"
"tinyauth/internal/config"
"tinyauth/internal/service" "github.com/steveiliop56/tinyauth/internal/config"
"tinyauth/internal/utils" "github.com/steveiliop56/tinyauth/internal/service"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/go-querystring/query" "github.com/google/go-querystring/query"
@@ -196,6 +197,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
Provider: req.Provider, Provider: req.Provider,
OAuthGroups: utils.CoalesceToString(user.Groups), OAuthGroups: utils.CoalesceToString(user.Groups),
OAuthName: service.GetName(), OAuthName: service.GetName(),
OAuthSub: user.Sub,
} }
log.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie") log.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")

View File

@@ -5,9 +5,10 @@ import (
"net/http" "net/http"
"slices" "slices"
"strings" "strings"
"tinyauth/internal/config"
"tinyauth/internal/service" "github.com/steveiliop56/tinyauth/internal/config"
"tinyauth/internal/utils" "github.com/steveiliop56/tinyauth/internal/service"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/go-querystring/query" "github.com/google/go-querystring/query"
@@ -42,7 +43,8 @@ func NewProxyController(config ProxyControllerConfig, router *gin.RouterGroup, a
func (controller *ProxyController) SetupRoutes() { func (controller *ProxyController) SetupRoutes() {
proxyGroup := controller.router.Group("/auth") 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) { func (controller *ProxyController) proxyHandler(c *gin.Context) {
@@ -67,15 +69,6 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return 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") isBrowser := strings.Contains(c.Request.Header.Get("Accept"), "text/html")
if isBrowser { if isBrowser {
@@ -246,6 +239,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
c.Header("Remote-Name", utils.SanitizeHeader(userContext.Name)) c.Header("Remote-Name", utils.SanitizeHeader(userContext.Name))
c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email)) c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email))
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups)) c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
c.Header("Remote-Sub", utils.SanitizeHeader(userContext.OAuthSub))
controller.setHeaders(c, acls) controller.setHeaders(c, acls)

View File

@@ -3,9 +3,10 @@ package controller_test
import ( import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"tinyauth/internal/config"
"tinyauth/internal/controller" "github.com/steveiliop56/tinyauth/internal/config"
"tinyauth/internal/service" "github.com/steveiliop56/tinyauth/internal/controller"
"github.com/steveiliop56/tinyauth/internal/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
@@ -40,7 +41,7 @@ func setupProxyController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.En
assert.NilError(t, dockerService.Init()) assert.NilError(t, dockerService.Init())
// Access controls // Access controls
accessControlsService := service.NewAccessControlsService(dockerService) accessControlsService := service.NewAccessControlsService(dockerService, map[string]config.App{})
assert.NilError(t, accessControlsService.Init()) assert.NilError(t, accessControlsService.Init())
@@ -80,13 +81,6 @@ func TestProxyHandler(t *testing.T) {
assert.Equal(t, 400, recorder.Code) 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) // Test logged out user (traefik/caddy)
recorder = httptest.NewRecorder() recorder = httptest.NewRecorder()
req = httptest.NewRequest("GET", "/api/auth/traefik", nil) req = httptest.NewRequest("GET", "/api/auth/traefik", nil)

View File

@@ -4,7 +4,8 @@ import (
"net/http/httptest" "net/http/httptest"
"os" "os"
"testing" "testing"
"tinyauth/internal/controller"
"github.com/steveiliop56/tinyauth/internal/controller"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"

View File

@@ -3,9 +3,10 @@ package controller
import ( import (
"fmt" "fmt"
"strings" "strings"
"tinyauth/internal/config"
"tinyauth/internal/service" "github.com/steveiliop56/tinyauth/internal/config"
"tinyauth/internal/utils" "github.com/steveiliop56/tinyauth/internal/service"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"

View File

@@ -7,9 +7,10 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"tinyauth/internal/config"
"tinyauth/internal/controller" "github.com/steveiliop56/tinyauth/internal/config"
"tinyauth/internal/service" "github.com/steveiliop56/tinyauth/internal/controller"
"github.com/steveiliop56/tinyauth/internal/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"

View File

@@ -3,9 +3,10 @@ package middleware
import ( import (
"fmt" "fmt"
"strings" "strings"
"tinyauth/internal/config"
"tinyauth/internal/service" "github.com/steveiliop56/tinyauth/internal/config"
"tinyauth/internal/utils" "github.com/steveiliop56/tinyauth/internal/service"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@@ -65,6 +66,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
goto basic goto basic
} }
m.auth.RefreshSessionCookie(c)
c.Set("context", &config.UserContext{ c.Set("context", &config.UserContext{
Username: cookie.Username, Username: cookie.Username,
Name: cookie.Name, Name: cookie.Name,
@@ -89,6 +91,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
goto basic goto basic
} }
m.auth.RefreshSessionCookie(c)
c.Set("context", &config.UserContext{ c.Set("context", &config.UserContext{
Username: cookie.Username, Username: cookie.Username,
Name: cookie.Name, Name: cookie.Name,
@@ -96,6 +99,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
Provider: cookie.Provider, Provider: cookie.Provider,
OAuthGroups: cookie.OAuthGroups, OAuthGroups: cookie.OAuthGroups,
OAuthName: cookie.OAuthName, OAuthName: cookie.OAuthName,
OAuthSub: cookie.OAuthSub,
IsLoggedIn: true, IsLoggedIn: true,
OAuth: true, OAuth: true,
}) })

View File

@@ -7,7 +7,8 @@ import (
"os" "os"
"strings" "strings"
"time" "time"
"tinyauth/internal/assets"
"github.com/steveiliop56/tinyauth/internal/assets"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )

View File

@@ -10,4 +10,5 @@ type Session struct {
OAuthGroups string `gorm:"column:oauth_groups"` OAuthGroups string `gorm:"column:oauth_groups"`
Expiry int64 `gorm:"column:expiry"` Expiry int64 `gorm:"column:expiry"`
OAuthName string `gorm:"column:oauth_name"` OAuthName string `gorm:"column:oauth_name"`
OAuthSub string `gorm:"column:oauth_sub"`
} }

View File

@@ -1,122 +1,54 @@
package service package service
import ( 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 { type AccessControlsService struct {
docker *DockerService 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{ return &AccessControlsService{
docker: docker, docker: docker,
static: static,
} }
} }
func (acls *AccessControlsService) Init() error { func (acls *AccessControlsService) Init() error {
// acls.envACLs = config.Apps{} return nil // No initialization needed
// 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
} }
// func (acls *AccessControlsService) loadEnvACLs(appEnvVars []string) error { func (acls *AccessControlsService) lookupStaticACLs(domain string) (config.App, error) {
// if len(appEnvVars) == 0 { for app, config := range acls.static {
// return nil 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 { func (acls *AccessControlsService) GetAccessControls(domain string) (config.App, error) {
// parts := strings.SplitN(e, "=", 2) // First check in the static config
// if len(parts) != 2 { app, err := acls.lookupStaticACLs(domain)
// continue
// }
// key := parts[0] if err == nil {
// key = strings.ToLower(key) log.Debug().Msg("Using ACls from static configuration")
// key = strings.ReplaceAll(key, "_", ".") return app, nil
// 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
// }
// Fallback to Docker labels // Fallback to Docker labels
return acls.docker.GetLabels(appDomain) log.Debug().Msg("Falling back to Docker labels for ACLs")
return acls.docker.GetLabels(domain)
} }

View File

@@ -1,16 +1,16 @@
package service package service
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
"tinyauth/internal/config"
"tinyauth/internal/model" "github.com/steveiliop56/tinyauth/internal/config"
"tinyauth/internal/utils" "github.com/steveiliop56/tinyauth/internal/model"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
@@ -43,7 +43,6 @@ type AuthService struct {
loginMutex sync.RWMutex loginMutex sync.RWMutex
ldap *LdapService ldap *LdapService
database *gorm.DB database *gorm.DB
ctx context.Context
} }
func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapService, database *gorm.DB) *AuthService { func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapService, database *gorm.DB) *AuthService {
@@ -57,7 +56,6 @@ func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapS
} }
func (auth *AuthService) Init() error { func (auth *AuthService) Init() error {
auth.ctx = context.Background()
return nil return nil
} }
@@ -215,9 +213,10 @@ func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.Sessio
OAuthGroups: data.OAuthGroups, OAuthGroups: data.OAuthGroups,
Expiry: time.Now().Add(time.Duration(expiry) * time.Second).Unix(), Expiry: time.Now().Add(time.Duration(expiry) * time.Second).Unix(),
OAuthName: data.OAuthName, OAuthName: data.OAuthName,
OAuthSub: data.OAuthSub,
} }
err = gorm.G[model.Session](auth.database).Create(auth.ctx, &session) err = gorm.G[model.Session](auth.database).Create(c, &session)
if err != nil { if err != nil {
return err return err
@@ -228,6 +227,40 @@ func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.Sessio
return nil 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 { func (auth *AuthService) DeleteSessionCookie(c *gin.Context) error {
cookie, err := c.Cookie(auth.config.SessionCookieName) cookie, err := c.Cookie(auth.config.SessionCookieName)
@@ -235,7 +268,7 @@ func (auth *AuthService) DeleteSessionCookie(c *gin.Context) error {
return err return err
} }
_, err = gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).Delete(auth.ctx) _, err = gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).Delete(c)
if err != nil { if err != nil {
return err return err
@@ -253,7 +286,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie,
return config.SessionCookie{}, err return config.SessionCookie{}, err
} }
session, err := gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).First(auth.ctx) session, err := gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).First(c)
if err != nil { if err != nil {
return config.SessionCookie{}, err return config.SessionCookie{}, err
@@ -266,7 +299,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie,
currentTime := time.Now().Unix() currentTime := time.Now().Unix()
if currentTime > session.Expiry { if currentTime > session.Expiry {
_, err = gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).Delete(auth.ctx) _, err = gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).Delete(c)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to delete expired session") log.Error().Err(err).Msg("Failed to delete expired session")
} }
@@ -282,6 +315,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie,
TotpPending: session.TOTPPending, TotpPending: session.TOTPPending,
OAuthGroups: session.OAuthGroups, OAuthGroups: session.OAuthGroups,
OAuthName: session.OAuthName, OAuthName: session.OAuthName,
OAuthSub: session.OAuthSub,
}, nil }, nil
} }

View File

@@ -5,7 +5,8 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"tinyauth/internal/assets"
"github.com/steveiliop56/tinyauth/internal/assets"
"github.com/glebarez/sqlite" "github.com/glebarez/sqlite"
"github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4"

View File

@@ -3,8 +3,9 @@ package service
import ( import (
"context" "context"
"strings" "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" container "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client" "github.com/docker/docker/client"

View File

@@ -10,7 +10,8 @@ import (
"io" "io"
"net/http" "net/http"
"time" "time"
"tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"golang.org/x/oauth2" "golang.org/x/oauth2"

View File

@@ -9,8 +9,10 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strconv"
"time" "time"
"tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/config"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/endpoints" "golang.org/x/oauth2/endpoints"
@@ -26,6 +28,7 @@ type GithubEmailResponse []struct {
type GithubUserInfoResponse struct { type GithubUserInfoResponse struct {
Login string `json:"login"` Login string `json:"login"`
Name string `json:"name"` Name string `json:"name"`
ID int `json:"id"`
} }
type GithubOAuthService struct { type GithubOAuthService struct {
@@ -171,6 +174,7 @@ func (github *GithubOAuthService) Userinfo() (config.Claims, error) {
user.PreferredUsername = userInfo.Login user.PreferredUsername = userInfo.Login
user.Name = userInfo.Name user.Name = userInfo.Name
user.Sub = strconv.Itoa(userInfo.ID)
return user, nil return user, nil
} }

View File

@@ -10,18 +10,14 @@ import (
"net/http" "net/http"
"strings" "strings"
"time" "time"
"tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/config"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/endpoints" "golang.org/x/oauth2/endpoints"
) )
var GoogleOAuthScopes = []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"} var GoogleOAuthScopes = []string{"openid", "email", "profile"}
type GoogleUserInfoResponse struct {
Email string `json:"email"`
Name string `json:"name"`
}
type GoogleOAuthService struct { type GoogleOAuthService struct {
config oauth2.Config config oauth2.Config
@@ -90,7 +86,7 @@ func (google *GoogleOAuthService) Userinfo() (config.Claims, error) {
client := google.config.Client(google.context, google.token) 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 { if err != nil {
return config.Claims{}, err return config.Claims{}, err
} }
@@ -105,16 +101,12 @@ func (google *GoogleOAuthService) Userinfo() (config.Claims, error) {
return config.Claims{}, err return config.Claims{}, err
} }
var userInfo GoogleUserInfoResponse err = json.Unmarshal(body, &user)
err = json.Unmarshal(body, &userInfo)
if err != nil { if err != nil {
return config.Claims{}, err return config.Claims{}, err
} }
user.PreferredUsername = strings.Split(userInfo.Email, "@")[0] user.PreferredUsername = strings.SplitN(user.Email, "@", 2)[0]
user.Name = userInfo.Name
user.Email = userInfo.Email
return user, nil return user, nil
} }

View File

@@ -2,7 +2,8 @@ package service
import ( import (
"errors" "errors"
"tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"

View File

@@ -5,7 +5,8 @@ import (
"net" "net"
"net/url" "net/url"
"strings" "strings"
"tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/weppos/publicsuffix-go/publicsuffix" "github.com/weppos/publicsuffix-go/publicsuffix"

View File

@@ -2,8 +2,9 @@ package utils_test
import ( import (
"testing" "testing"
"tinyauth/internal/config"
"tinyauth/internal/utils" "github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"

View File

@@ -2,8 +2,9 @@ package decoders_test
import ( import (
"testing" "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" "gotest.tools/v3/assert"
) )

View File

@@ -2,7 +2,8 @@ package utils_test
import ( import (
"testing" "testing"
"tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
) )

View File

@@ -3,7 +3,8 @@ package loaders
import ( import (
"fmt" "fmt"
"os" "os"
"tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/traefik/paerser/cli" "github.com/traefik/paerser/cli"
"github.com/traefik/paerser/env" "github.com/traefik/paerser/env"

View File

@@ -3,7 +3,8 @@ package utils_test
import ( import (
"os" "os"
"testing" "testing"
"tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
) )

View File

@@ -2,7 +2,8 @@ package utils_test
import ( import (
"testing" "testing"
"tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
) )

View File

@@ -3,7 +3,8 @@ package utils
import ( import (
"errors" "errors"
"strings" "strings"
"tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/config"
) )
func ParseUsers(users string) ([]config.User, error) { func ParseUsers(users string) ([]config.User, error) {

View File

@@ -3,7 +3,8 @@ package utils_test
import ( import (
"os" "os"
"testing" "testing"
"tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
) )

1
paerser Submodule

Submodule paerser added at 7e1b633ba9

95
patches/nested_maps.diff Normal file
View File

@@ -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) {