Compare commits

...

16 Commits

Author SHA1 Message Date
Stavros
9a6676b054 feat: add pkce support to oidc server 2026-04-06 23:09:14 +03:00
github-actions[bot]
431cd33053 docs: regenerate readme sponsors list (#765)
Co-authored-by: GitHub <noreply@github.com>
2026-04-06 14:39:15 +03:00
Stavros
3373dcc412 test: extend traefik browser tests 2026-04-02 18:46:38 +03:00
Stavros
9d666dc108 fix: skip browser detection for nginx and envoy 2026-04-02 18:24:38 +03:00
dependabot[bot]
7ad13935a5 chore(deps): bump docker/build-push-action from 6 to 7 (#749)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 15:37:35 +03:00
dependabot[bot]
98e788b1e8 chore(deps): bump docker/metadata-action from 5 to 6 (#750)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 15:37:19 +03:00
dependabot[bot]
a074efb3a3 chore(deps): bump actions/download-artifact from 4 to 8 (#751)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 15:37:02 +03:00
dependabot[bot]
48ef8c0e4c chore(deps): bump actions/stale from 9 to 10 (#752)
Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v9...v10)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: '10'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 15:36:37 +03:00
dependabot[bot]
1313e8767a chore(deps): bump actions/checkout from 4 to 6 (#753)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 15:36:23 +03:00
Stavros
892097dc4d fix: account for proxy type in browser response 2026-04-02 15:35:55 +03:00
dependabot[bot]
6542e1b121 chore(deps): bump codecov/codecov-action from 5 to 6 (#746)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 19:09:28 +03:00
dependabot[bot]
e1d7fa2eb3 chore(deps): bump docker/setup-buildx-action from 3 to 4 (#747)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 19:09:02 +03:00
dependabot[bot]
41244080c0 chore(deps): bump docker/login-action from 3 to 4 (#748)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 19:08:48 +03:00
dependabot[bot]
34f9724866 chore(deps): bump actions/upload-artifact from 4 to 7 (#745)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 19:08:33 +03:00
dependabot[bot]
19a317dd7c chore(deps): bump actions/setup-go from 5 to 6 (#744)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 19:08:01 +03:00
Stavros
8a9ffcf185 chore: add github action updates in dependabot 2026-04-01 19:03:05 +03:00
19 changed files with 346 additions and 134 deletions

View File

@@ -24,3 +24,8 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

View File

@@ -10,13 +10,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup bun - name: Setup bun
uses: oven-sh/setup-bun@v2 uses: oven-sh/setup-bun@v2
- name: Setup go - name: Setup go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: "^1.26.0" go-version: "^1.26.0"
@@ -56,6 +56,6 @@ jobs:
run: go test -coverprofile=coverage.txt -v ./... run: go test -coverprofile=coverage.txt -v ./...
- name: Upload coverage reports to Codecov - name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v6
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Delete old release - name: Delete old release
run: gh release delete --cleanup-tag --yes nightly || echo release not found run: gh release delete --cleanup-tag --yes nightly || echo release not found
@@ -33,7 +33,7 @@ jobs:
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }} BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: nightly ref: nightly
@@ -51,7 +51,7 @@ jobs:
- generate-metadata - generate-metadata
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: nightly ref: nightly
@@ -59,7 +59,7 @@ jobs:
uses: oven-sh/setup-bun@v2 uses: oven-sh/setup-bun@v2
- name: Install go - name: Install go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: "^1.26.0" go-version: "^1.26.0"
@@ -94,7 +94,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: tinyauth-amd64 name: tinyauth-amd64
path: tinyauth-amd64 path: tinyauth-amd64
@@ -106,7 +106,7 @@ jobs:
- generate-metadata - generate-metadata
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: nightly ref: nightly
@@ -114,7 +114,7 @@ jobs:
uses: oven-sh/setup-bun@v2 uses: oven-sh/setup-bun@v2
- name: Install go - name: Install go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: "^1.26.0" go-version: "^1.26.0"
@@ -149,7 +149,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: tinyauth-arm64 name: tinyauth-arm64
path: tinyauth-arm64 path: tinyauth-arm64
@@ -161,7 +161,7 @@ jobs:
- generate-metadata - generate-metadata
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: nightly ref: nightly
@@ -176,22 +176,22 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
id: build id: build
with: with:
platforms: linux/amd64 platforms: linux/amd64
@@ -213,7 +213,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: digests-linux-amd64 name: digests-linux-amd64
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -228,7 +228,7 @@ jobs:
- image-build - image-build
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: nightly ref: nightly
@@ -243,22 +243,22 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
id: build id: build
with: with:
platforms: linux/amd64 platforms: linux/amd64
@@ -281,7 +281,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: digests-distroless-linux-amd64 name: digests-distroless-linux-amd64
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -295,7 +295,7 @@ jobs:
- generate-metadata - generate-metadata
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: nightly ref: nightly
@@ -310,22 +310,22 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
id: build id: build
with: with:
platforms: linux/arm64 platforms: linux/arm64
@@ -347,7 +347,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: digests-linux-arm64 name: digests-linux-arm64
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -362,7 +362,7 @@ jobs:
- image-build-arm - image-build-arm
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
ref: nightly ref: nightly
@@ -377,22 +377,22 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
id: build id: build
with: with:
platforms: linux/arm64 platforms: linux/arm64
@@ -415,7 +415,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: digests-distroless-linux-arm64 name: digests-distroless-linux-arm64
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -429,25 +429,25 @@ jobs:
- image-build-arm - image-build-arm
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: digests-* pattern: digests-*
merge-multiple: true merge-multiple: true
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
flavor: | flavor: |
@@ -468,25 +468,25 @@ jobs:
- image-build-arm-distroless - image-build-arm-distroless
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: digests-distroless-* pattern: digests-distroless-*
merge-multiple: true merge-multiple: true
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
flavor: | flavor: |
@@ -506,7 +506,7 @@ jobs:
- binary-build - binary-build
- binary-build-arm - binary-build-arm
steps: steps:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v8
with: with:
pattern: tinyauth-* pattern: tinyauth-*
path: binaries path: binaries

View File

@@ -14,7 +14,7 @@ jobs:
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }} BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Generate metadata - name: Generate metadata
id: metadata id: metadata
@@ -29,13 +29,13 @@ jobs:
- generate-metadata - generate-metadata
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Install bun - name: Install bun
uses: oven-sh/setup-bun@v2 uses: oven-sh/setup-bun@v2
- name: Install go - name: Install go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: "^1.26.0" go-version: "^1.26.0"
@@ -70,7 +70,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: tinyauth-amd64 name: tinyauth-amd64
path: tinyauth-amd64 path: tinyauth-amd64
@@ -81,13 +81,13 @@ jobs:
- generate-metadata - generate-metadata
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Install bun - name: Install bun
uses: oven-sh/setup-bun@v2 uses: oven-sh/setup-bun@v2
- name: Install go - name: Install go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: "^1.26.0" go-version: "^1.26.0"
@@ -122,7 +122,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: tinyauth-arm64 name: tinyauth-arm64
path: tinyauth-arm64 path: tinyauth-arm64
@@ -133,7 +133,7 @@ jobs:
- generate-metadata - generate-metadata
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Initialize submodules - name: Initialize submodules
run: | run: |
@@ -146,22 +146,22 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
id: build id: build
with: with:
platforms: linux/amd64 platforms: linux/amd64
@@ -183,7 +183,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: digests-linux-amd64 name: digests-linux-amd64
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -197,7 +197,7 @@ jobs:
- image-build - image-build
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Initialize submodules - name: Initialize submodules
run: | run: |
@@ -210,22 +210,22 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
id: build id: build
with: with:
platforms: linux/amd64 platforms: linux/amd64
@@ -248,7 +248,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: digests-distroless-linux-amd64 name: digests-distroless-linux-amd64
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -261,7 +261,7 @@ jobs:
- generate-metadata - generate-metadata
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Initialize submodules - name: Initialize submodules
run: | run: |
@@ -274,22 +274,22 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
id: build id: build
with: with:
platforms: linux/arm64 platforms: linux/arm64
@@ -311,7 +311,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: digests-linux-arm64 name: digests-linux-arm64
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -325,7 +325,7 @@ jobs:
- image-build-arm - image-build-arm
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Initialize submodules - name: Initialize submodules
run: | run: |
@@ -338,22 +338,22 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
id: build id: build
with: with:
platforms: linux/arm64 platforms: linux/arm64
@@ -376,7 +376,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: digests-distroless-linux-arm64 name: digests-distroless-linux-arm64
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -390,25 +390,25 @@ jobs:
- image-build-arm - image-build-arm
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: digests-* pattern: digests-*
merge-multiple: true merge-multiple: true
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
flavor: | flavor: |
@@ -431,25 +431,25 @@ jobs:
- image-build-arm-distroless - image-build-arm-distroless
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: digests-distroless-* pattern: digests-distroless-*
merge-multiple: true merge-multiple: true
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
flavor: | flavor: |
@@ -473,7 +473,7 @@ jobs:
- binary-build - binary-build
- binary-build-arm - binary-build-arm
steps: steps:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v8
with: with:
pattern: tinyauth-* pattern: tinyauth-*
path: binaries path: binaries

View File

@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Generate Sponsors - name: Generate Sponsors
uses: JamesIves/github-sponsors-readme-action@v1 uses: JamesIves/github-sponsors-readme-action@v1

View File

@@ -7,7 +7,7 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v10
with: with:
days-before-stale: 30 days-before-stale: 30
stale-pr-message: This PR has been inactive for 30 days and will be marked as stale. stale-pr-message: This PR has been inactive for 30 days and will be marked as stale.

View File

@@ -58,7 +58,7 @@ Tinyauth is licensed under the GNU General Public License v3.0. TL;DR — You ma
A big thank you to the following people for providing me with more coffee: A big thank you to the following people for providing me with more coffee:
<!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https:&#x2F;&#x2F;github.com&#x2F;erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a>&nbsp;&nbsp;<a href="https://github.com/nicotsx"><img src="https:&#x2F;&#x2F;github.com&#x2F;nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a>&nbsp;&nbsp;<a href="https://github.com/SimpleHomelab"><img src="https:&#x2F;&#x2F;github.com&#x2F;SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a>&nbsp;&nbsp;<a href="https://github.com/jmadden91"><img src="https:&#x2F;&#x2F;github.com&#x2F;jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a>&nbsp;&nbsp;<a href="https://github.com/tribor"><img src="https:&#x2F;&#x2F;github.com&#x2F;tribor.png" width="64px" alt="User avatar: tribor" /></a>&nbsp;&nbsp;<a href="https://github.com/eliasbenb"><img src="https:&#x2F;&#x2F;github.com&#x2F;eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a>&nbsp;&nbsp;<a href="https://github.com/afunworm"><img src="https:&#x2F;&#x2F;github.com&#x2F;afunworm.png" width="64px" alt="User avatar: afunworm" /></a>&nbsp;&nbsp;<a href="https://github.com/chip-well"><img src="https:&#x2F;&#x2F;github.com&#x2F;chip-well.png" width="64px" alt="User avatar: chip-well" /></a>&nbsp;&nbsp;<a href="https://github.com/Lancelot-Enguerrand"><img src="https:&#x2F;&#x2F;github.com&#x2F;Lancelot-Enguerrand.png" width="64px" alt="User avatar: Lancelot-Enguerrand" /></a>&nbsp;&nbsp;<a href="https://github.com/allgoewer"><img src="https:&#x2F;&#x2F;github.com&#x2F;allgoewer.png" width="64px" alt="User avatar: allgoewer" /></a>&nbsp;&nbsp;<a href="https://github.com/NEANC"><img src="https:&#x2F;&#x2F;github.com&#x2F;NEANC.png" width="64px" alt="User avatar: NEANC" /></a>&nbsp;&nbsp;<a href="https://github.com/ax-mad"><img src="https:&#x2F;&#x2F;github.com&#x2F;ax-mad.png" width="64px" alt="User avatar: ax-mad" /></a>&nbsp;&nbsp;<!-- sponsors --> <!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https:&#x2F;&#x2F;github.com&#x2F;erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a>&nbsp;&nbsp;<a href="https://github.com/nicotsx"><img src="https:&#x2F;&#x2F;github.com&#x2F;nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a>&nbsp;&nbsp;<a href="https://github.com/SimpleHomelab"><img src="https:&#x2F;&#x2F;github.com&#x2F;SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a>&nbsp;&nbsp;<a href="https://github.com/jmadden91"><img src="https:&#x2F;&#x2F;github.com&#x2F;jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a>&nbsp;&nbsp;<a href="https://github.com/tribor"><img src="https:&#x2F;&#x2F;github.com&#x2F;tribor.png" width="64px" alt="User avatar: tribor" /></a>&nbsp;&nbsp;<a href="https://github.com/eliasbenb"><img src="https:&#x2F;&#x2F;github.com&#x2F;eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a>&nbsp;&nbsp;<a href="https://github.com/afunworm"><img src="https:&#x2F;&#x2F;github.com&#x2F;afunworm.png" width="64px" alt="User avatar: afunworm" /></a>&nbsp;&nbsp;<a href="https://github.com/chip-well"><img src="https:&#x2F;&#x2F;github.com&#x2F;chip-well.png" width="64px" alt="User avatar: chip-well" /></a>&nbsp;&nbsp;<a href="https://github.com/Lancelot-Enguerrand"><img src="https:&#x2F;&#x2F;github.com&#x2F;Lancelot-Enguerrand.png" width="64px" alt="User avatar: Lancelot-Enguerrand" /></a>&nbsp;&nbsp;<a href="https://github.com/allgoewer"><img src="https:&#x2F;&#x2F;github.com&#x2F;allgoewer.png" width="64px" alt="User avatar: allgoewer" /></a>&nbsp;&nbsp;<a href="https://github.com/NEANC"><img src="https:&#x2F;&#x2F;github.com&#x2F;NEANC.png" width="64px" alt="User avatar: NEANC" /></a>&nbsp;&nbsp;<a href="https://github.com/ax-mad"><img src="https:&#x2F;&#x2F;github.com&#x2F;ax-mad.png" width="64px" alt="User avatar: ax-mad" /></a>&nbsp;&nbsp;<a href="https://github.com/stegratech"><img src="https:&#x2F;&#x2F;github.com&#x2F;stegratech.png" width="64px" alt="User avatar: stegratech" /></a>&nbsp;&nbsp;<!-- sponsors -->
## Acknowledgements ## Acknowledgements

View File

@@ -5,6 +5,8 @@ export type OIDCValues = {
redirect_uri: string; redirect_uri: string;
state: string; state: string;
nonce: string; nonce: string;
code_challenge: string;
code_challenge_method: string;
}; };
interface IuseOIDCParams { interface IuseOIDCParams {
@@ -14,7 +16,12 @@ interface IuseOIDCParams {
missingParams: string[]; missingParams: string[];
} }
const optionalParams: string[] = ["state", "nonce"]; const optionalParams: string[] = [
"state",
"nonce",
"code_challenge",
"code_challenge_method",
];
export function useOIDCParams(params: URLSearchParams): IuseOIDCParams { export function useOIDCParams(params: URLSearchParams): IuseOIDCParams {
let compiled: string = ""; let compiled: string = "";
@@ -28,6 +35,8 @@ export function useOIDCParams(params: URLSearchParams): IuseOIDCParams {
redirect_uri: params.get("redirect_uri") ?? "", redirect_uri: params.get("redirect_uri") ?? "",
state: params.get("state") ?? "", state: params.get("state") ?? "",
nonce: params.get("nonce") ?? "", nonce: params.get("nonce") ?? "",
code_challenge: params.get("code_challenge") ?? "",
code_challenge_method: params.get("code_challenge_method") ?? "",
}; };
for (const key of Object.keys(values)) { for (const key of Object.keys(values)) {

View File

@@ -0,0 +1,2 @@
ALTER TABLE "oidc_codes" DROP COLUMN "code_challenge";
ALTER TABLE "oidc_codes" DROP COLUMN "code_challenge_method";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "oidc_codes" ADD COLUMN "code_challenge" TEXT DEFAULT "";
ALTER TABLE "oidc_codes" ADD COLUMN "code_challenge_method" TEXT DEFAULT "";

View File

@@ -34,6 +34,7 @@ type TokenRequest struct {
RefreshToken string `form:"refresh_token" url:"refresh_token"` RefreshToken string `form:"refresh_token" url:"refresh_token"`
ClientSecret string `form:"client_secret" url:"client_secret"` ClientSecret string `form:"client_secret" url:"client_secret"`
ClientID string `form:"client_id" url:"client_id"` ClientID string `form:"client_id" url:"client_id"`
CodeVerifier string `form:"code_verifier" url:"code_verifier"`
} }
type CallbackError struct { type CallbackError struct {
@@ -308,6 +309,16 @@ func (controller *OIDCController) Token(c *gin.Context) {
return return
} }
ok := controller.oidc.ValidatePKCE(entry.CodeChallenge, entry.CodeChallengeMethod, req.CodeVerifier)
if !ok {
tlog.App.Warn().Msg("PKCE validation failed")
c.JSON(400, gin.H{
"error": "invalid_grant",
})
return
}
tokenRes, err := controller.oidc.GenerateAccessToken(c, client, entry) tokenRes, err := controller.oidc.GenerateAccessToken(c, client, entry)
if err != nil { if err != nil {

View File

@@ -25,6 +25,15 @@ const (
ForwardAuth ForwardAuth
) )
type ProxyType int
const (
Traefik ProxyType = iota
Caddy
Envoy
Nginx
)
var BrowserUserAgentRegex = regexp.MustCompile("Chrome|Gecko|AppleWebKit|Opera|Edge") var BrowserUserAgentRegex = regexp.MustCompile("Chrome|Gecko|AppleWebKit|Opera|Edge")
type Proxy struct { type Proxy struct {
@@ -38,6 +47,7 @@ type ProxyContext struct {
Method string Method string
Type AuthModuleType Type AuthModuleType
IsBrowser bool IsBrowser bool
ProxyType ProxyType
} }
type ProxyControllerConfig struct { type ProxyControllerConfig struct {
@@ -121,7 +131,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
} }
if !controller.auth.CheckIP(acls.IP, clientIP) { if !controller.auth.CheckIP(acls.IP, clientIP) {
if !controller.useFriendlyError(proxyCtx) { if !controller.useBrowserResponse(proxyCtx) {
c.JSON(401, gin.H{ c.JSON(401, gin.H{
"status": 401, "status": 401,
"message": "Unauthorized", "message": "Unauthorized",
@@ -165,7 +175,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
if !userAllowed { if !userAllowed {
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(proxyCtx.Host, ".")[0]).Msg("User not allowed to access resource") tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(proxyCtx.Host, ".")[0]).Msg("User not allowed to access resource")
if !controller.useFriendlyError(proxyCtx) { if !controller.useBrowserResponse(proxyCtx) {
c.JSON(403, gin.H{ c.JSON(403, gin.H{
"status": 403, "status": 403,
"message": "Forbidden", "message": "Forbidden",
@@ -205,7 +215,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
if !groupOK { if !groupOK {
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(proxyCtx.Host, ".")[0]).Msg("User groups do not match resource requirements") tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(proxyCtx.Host, ".")[0]).Msg("User groups do not match resource requirements")
if !controller.useFriendlyError(proxyCtx) { if !controller.useBrowserResponse(proxyCtx) {
c.JSON(403, gin.H{ c.JSON(403, gin.H{
"status": 403, "status": 403,
"message": "Forbidden", "message": "Forbidden",
@@ -256,7 +266,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return return
} }
if !controller.useFriendlyError(proxyCtx) { if !controller.useBrowserResponse(proxyCtx) {
c.JSON(401, gin.H{ c.JSON(401, gin.H{
"status": 401, "status": 401,
"message": "Unauthorized", "message": "Unauthorized",
@@ -296,7 +306,7 @@ func (controller *ProxyController) setHeaders(c *gin.Context, acls config.App) {
} }
func (controller *ProxyController) handleError(c *gin.Context, proxyCtx ProxyContext) { func (controller *ProxyController) handleError(c *gin.Context, proxyCtx ProxyContext) {
if !controller.useFriendlyError(proxyCtx) { if !controller.useBrowserResponse(proxyCtx) {
c.JSON(500, gin.H{ c.JSON(500, gin.H{
"status": 500, "status": 500,
"message": "Internal Server Error", "message": "Internal Server Error",
@@ -312,8 +322,34 @@ func (controller *ProxyController) getHeader(c *gin.Context, header string) (str
return val, strings.TrimSpace(val) != "" return val, strings.TrimSpace(val) != ""
} }
func (controller *ProxyController) useFriendlyError(proxyCtx ProxyContext) bool { func (controller *ProxyController) useBrowserResponse(proxyCtx ProxyContext) bool {
return (proxyCtx.Type == ForwardAuth || proxyCtx.Type == ExtAuthz) && proxyCtx.IsBrowser // If it's nginx or envoy we need non-browser response
if proxyCtx.ProxyType == Nginx || proxyCtx.ProxyType == Envoy {
return false
}
// For other proxies (traefik or caddy) we can check
// the user agent to determine if it's a browser or not
if proxyCtx.IsBrowser {
return true
}
return false
}
func (controller *ProxyController) getProxyType(proxy string) (ProxyType, error) {
switch proxy {
case "traefik":
return Traefik, nil
case "caddy":
return Caddy, nil
case "envoy":
return Envoy, nil
case "nginx":
return Nginx, nil
default:
return 0, fmt.Errorf("unsupported proxy type: %v", proxy)
}
} }
// Code below is inspired from https://github.com/authelia/authelia/blob/master/internal/handlers/handler_authz.go // Code below is inspired from https://github.com/authelia/authelia/blob/master/internal/handlers/handler_authz.go
@@ -417,13 +453,13 @@ func (controller *ProxyController) getExtAuthzContext(c *gin.Context) (ProxyCont
}, nil }, nil
} }
func (controller *ProxyController) determineAuthModules(proxy string) []AuthModuleType { func (controller *ProxyController) determineAuthModules(proxy ProxyType) []AuthModuleType {
switch proxy { switch proxy {
case "traefik", "caddy": case Traefik, Caddy:
return []AuthModuleType{ForwardAuth} return []AuthModuleType{ForwardAuth}
case "envoy": case Envoy:
return []AuthModuleType{ExtAuthz, ForwardAuth} return []AuthModuleType{ExtAuthz, ForwardAuth}
case "nginx": case Nginx:
return []AuthModuleType{AuthRequest, ForwardAuth} return []AuthModuleType{AuthRequest, ForwardAuth}
default: default:
return []AuthModuleType{} return []AuthModuleType{}
@@ -462,9 +498,15 @@ func (controller *ProxyController) getProxyContext(c *gin.Context) (ProxyContext
return ProxyContext{}, err return ProxyContext{}, err
} }
proxy, err := controller.getProxyType(req.Proxy)
if err != nil {
return ProxyContext{}, err
}
tlog.App.Debug().Msgf("Proxy: %v", req.Proxy) tlog.App.Debug().Msgf("Proxy: %v", req.Proxy)
authModules := controller.determineAuthModules(req.Proxy) authModules := controller.determineAuthModules(proxy)
if len(authModules) == 0 { if len(authModules) == 0 {
return ProxyContext{}, fmt.Errorf("no auth modules supported for proxy: %v", req.Proxy) return ProxyContext{}, fmt.Errorf("no auth modules supported for proxy: %v", req.Proxy)
@@ -497,5 +539,6 @@ func (controller *ProxyController) getProxyContext(c *gin.Context) (ProxyContext
} }
ctx.IsBrowser = isBrowser ctx.IsBrowser = isBrowser
ctx.ProxyType = proxy
return ctx, nil return ctx, nil
} }

View File

@@ -164,6 +164,79 @@ func TestProxyController(t *testing.T) {
assert.Equal(t, 401, recorder.Code) assert.Equal(t, 401, recorder.Code)
}, },
}, },
{
description: "Ensure forward auth fallback for nginx with browser user agent",
middlewares: []gin.HandlerFunc{},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("GET", "/api/auth/nginx", nil)
req.Header.Set("x-forwarded-host", "test.example.com")
req.Header.Set("x-forwarded-proto", "https")
req.Header.Set("x-forwarded-uri", "/")
req.Header.Set("user-agent", browserUserAgent)
router.ServeHTTP(recorder, req)
assert.Equal(t, 401, recorder.Code)
},
},
{
description: "Ensure forward auth fallback for envoy with browser user agent",
middlewares: []gin.HandlerFunc{},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("HEAD", "/api/auth/envoy?path=/hello", nil)
req.Header.Set("x-forwarded-host", "test.example.com")
req.Header.Set("x-forwarded-proto", "https")
req.Header.Set("x-forwarded-uri", "/hello")
req.Header.Set("user-agent", browserUserAgent)
router.ServeHTTP(recorder, req)
assert.Equal(t, 401, recorder.Code)
},
},
{
description: "Ensure forward auth with is browser false returns json",
middlewares: []gin.HandlerFunc{},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("GET", "/api/auth/traefik", nil)
req.Header.Set("x-forwarded-host", "test.example.com")
req.Header.Set("x-forwarded-proto", "https")
req.Header.Set("x-forwarded-uri", "/")
router.ServeHTTP(recorder, req)
assert.Equal(t, 401, recorder.Code)
assert.Contains(t, recorder.Body.String(), `"status":401`)
assert.Contains(t, recorder.Body.String(), `"message":"Unauthorized"`)
},
},
{
description: "Ensure forward auth with caddy and browser user agent returns redirect",
middlewares: []gin.HandlerFunc{},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("GET", "/api/auth/traefik", nil)
req.Header.Set("x-forwarded-host", "test.example.com")
req.Header.Set("x-forwarded-proto", "https")
req.Header.Set("x-forwarded-uri", "/")
req.Header.Set("user-agent", browserUserAgent)
router.ServeHTTP(recorder, req)
assert.Equal(t, 307, recorder.Code)
location := recorder.Header().Get("Location")
assert.Contains(t, location, "https://tinyauth.example.com/login?redirect_uri=")
assert.Contains(t, location, "https%3A%2F%2Ftest.example.com%2F")
},
},
{
description: "Ensure forward auth with caddy and non browser user agent returns json",
middlewares: []gin.HandlerFunc{},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("GET", "/api/auth/traefik", nil)
req.Header.Set("x-forwarded-host", "test.example.com")
req.Header.Set("x-forwarded-proto", "https")
req.Header.Set("x-forwarded-uri", "/")
router.ServeHTTP(recorder, req)
assert.Equal(t, 401, recorder.Code)
assert.Contains(t, recorder.Body.String(), `"status":401`)
assert.Contains(t, recorder.Body.String(), `"message":"Unauthorized"`)
},
},
{ {
description: "Ensure normal authentication flow for forward auth", description: "Ensure normal authentication flow for forward auth",
middlewares: []gin.HandlerFunc{ middlewares: []gin.HandlerFunc{

View File

@@ -12,6 +12,8 @@ type OidcCode struct {
ClientID string ClientID string
ExpiresAt int64 ExpiresAt int64
Nonce string Nonce string
CodeChallenge string
CodeChallengeMethod string
} }
type OidcToken struct { type OidcToken struct {

View File

@@ -17,11 +17,13 @@ INSERT INTO "oidc_codes" (
"redirect_uri", "redirect_uri",
"client_id", "client_id",
"expires_at", "expires_at",
"nonce" "nonce",
"code_challenge",
"code_challenge_method"
) VALUES ( ) VALUES (
?, ?, ?, ?, ?, ?, ? ?, ?, ?, ?, ?, ?, ?, ?, ?
) )
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce, code_challenge, code_challenge_method
` `
type CreateOidcCodeParams struct { type CreateOidcCodeParams struct {
@@ -32,6 +34,8 @@ type CreateOidcCodeParams struct {
ClientID string ClientID string
ExpiresAt int64 ExpiresAt int64
Nonce string Nonce string
CodeChallenge string
CodeChallengeMethod string
} }
func (q *Queries) CreateOidcCode(ctx context.Context, arg CreateOidcCodeParams) (OidcCode, error) { func (q *Queries) CreateOidcCode(ctx context.Context, arg CreateOidcCodeParams) (OidcCode, error) {
@@ -43,6 +47,8 @@ func (q *Queries) CreateOidcCode(ctx context.Context, arg CreateOidcCodeParams)
arg.ClientID, arg.ClientID,
arg.ExpiresAt, arg.ExpiresAt,
arg.Nonce, arg.Nonce,
arg.CodeChallenge,
arg.CodeChallengeMethod,
) )
var i OidcCode var i OidcCode
err := row.Scan( err := row.Scan(
@@ -53,6 +59,8 @@ func (q *Queries) CreateOidcCode(ctx context.Context, arg CreateOidcCodeParams)
&i.ClientID, &i.ClientID,
&i.ExpiresAt, &i.ExpiresAt,
&i.Nonce, &i.Nonce,
&i.CodeChallenge,
&i.CodeChallengeMethod,
) )
return i, err return i, err
} }
@@ -156,7 +164,7 @@ func (q *Queries) CreateOidcUserInfo(ctx context.Context, arg CreateOidcUserInfo
const deleteExpiredOidcCodes = `-- name: DeleteExpiredOidcCodes :many const deleteExpiredOidcCodes = `-- name: DeleteExpiredOidcCodes :many
DELETE FROM "oidc_codes" DELETE FROM "oidc_codes"
WHERE "expires_at" < ? WHERE "expires_at" < ?
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce, code_challenge, code_challenge_method
` `
func (q *Queries) DeleteExpiredOidcCodes(ctx context.Context, expiresAt int64) ([]OidcCode, error) { func (q *Queries) DeleteExpiredOidcCodes(ctx context.Context, expiresAt int64) ([]OidcCode, error) {
@@ -176,6 +184,8 @@ func (q *Queries) DeleteExpiredOidcCodes(ctx context.Context, expiresAt int64) (
&i.ClientID, &i.ClientID,
&i.ExpiresAt, &i.ExpiresAt,
&i.Nonce, &i.Nonce,
&i.CodeChallenge,
&i.CodeChallengeMethod,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@@ -286,7 +296,7 @@ func (q *Queries) DeleteOidcUserInfo(ctx context.Context, sub string) error {
const getOidcCode = `-- name: GetOidcCode :one const getOidcCode = `-- name: GetOidcCode :one
DELETE FROM "oidc_codes" DELETE FROM "oidc_codes"
WHERE "code_hash" = ? WHERE "code_hash" = ?
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce, code_challenge, code_challenge_method
` `
func (q *Queries) GetOidcCode(ctx context.Context, codeHash string) (OidcCode, error) { func (q *Queries) GetOidcCode(ctx context.Context, codeHash string) (OidcCode, error) {
@@ -300,6 +310,8 @@ func (q *Queries) GetOidcCode(ctx context.Context, codeHash string) (OidcCode, e
&i.ClientID, &i.ClientID,
&i.ExpiresAt, &i.ExpiresAt,
&i.Nonce, &i.Nonce,
&i.CodeChallenge,
&i.CodeChallengeMethod,
) )
return i, err return i, err
} }
@@ -307,7 +319,7 @@ func (q *Queries) GetOidcCode(ctx context.Context, codeHash string) (OidcCode, e
const getOidcCodeBySub = `-- name: GetOidcCodeBySub :one const getOidcCodeBySub = `-- name: GetOidcCodeBySub :one
DELETE FROM "oidc_codes" DELETE FROM "oidc_codes"
WHERE "sub" = ? WHERE "sub" = ?
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce, code_challenge, code_challenge_method
` `
func (q *Queries) GetOidcCodeBySub(ctx context.Context, sub string) (OidcCode, error) { func (q *Queries) GetOidcCodeBySub(ctx context.Context, sub string) (OidcCode, error) {
@@ -321,12 +333,14 @@ func (q *Queries) GetOidcCodeBySub(ctx context.Context, sub string) (OidcCode, e
&i.ClientID, &i.ClientID,
&i.ExpiresAt, &i.ExpiresAt,
&i.Nonce, &i.Nonce,
&i.CodeChallenge,
&i.CodeChallengeMethod,
) )
return i, err return i, err
} }
const getOidcCodeBySubUnsafe = `-- name: GetOidcCodeBySubUnsafe :one const getOidcCodeBySubUnsafe = `-- name: GetOidcCodeBySubUnsafe :one
SELECT sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce FROM "oidc_codes" SELECT sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce, code_challenge, code_challenge_method FROM "oidc_codes"
WHERE "sub" = ? WHERE "sub" = ?
` `
@@ -341,12 +355,14 @@ func (q *Queries) GetOidcCodeBySubUnsafe(ctx context.Context, sub string) (OidcC
&i.ClientID, &i.ClientID,
&i.ExpiresAt, &i.ExpiresAt,
&i.Nonce, &i.Nonce,
&i.CodeChallenge,
&i.CodeChallengeMethod,
) )
return i, err return i, err
} }
const getOidcCodeUnsafe = `-- name: GetOidcCodeUnsafe :one const getOidcCodeUnsafe = `-- name: GetOidcCodeUnsafe :one
SELECT sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce FROM "oidc_codes" SELECT sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce, code_challenge, code_challenge_method FROM "oidc_codes"
WHERE "code_hash" = ? WHERE "code_hash" = ?
` `
@@ -361,6 +377,8 @@ func (q *Queries) GetOidcCodeUnsafe(ctx context.Context, codeHash string) (OidcC
&i.ClientID, &i.ClientID,
&i.ExpiresAt, &i.ExpiresAt,
&i.Nonce, &i.Nonce,
&i.CodeChallenge,
&i.CodeChallengeMethod,
) )
return i, err return i, err
} }

View File

@@ -81,6 +81,8 @@ type AuthorizeRequest struct {
RedirectURI string `json:"redirect_uri" binding:"required"` RedirectURI string `json:"redirect_uri" binding:"required"`
State string `json:"state"` State string `json:"state"`
Nonce string `json:"nonce"` Nonce string `json:"nonce"`
CodeChallenge string `json:"code_challenge"`
CodeChallengeMethod string `json:"code_challenge_method"`
} }
type OIDCServiceConfig struct { type OIDCServiceConfig struct {
@@ -293,6 +295,13 @@ func (service *OIDCService) ValidateAuthorizeParams(req AuthorizeRequest) error
return errors.New("invalid_request_uri") return errors.New("invalid_request_uri")
} }
// PKCE code challenge method if set
if req.CodeChallenge != "" && req.CodeChallengeMethod != "" {
if req.CodeChallengeMethod != "S256" || req.CodeChallenge == "plain" {
return errors.New("invalid_request")
}
}
return nil return nil
} }
@@ -306,8 +315,7 @@ func (service *OIDCService) StoreCode(c *gin.Context, sub string, code string, r
// Fixed 10 minutes // Fixed 10 minutes
expiresAt := time.Now().Add(time.Minute * time.Duration(10)).Unix() expiresAt := time.Now().Add(time.Minute * time.Duration(10)).Unix()
// Insert the code into the database entry := repository.CreateOidcCodeParams{
_, err := service.queries.CreateOidcCode(c, repository.CreateOidcCodeParams{
Sub: sub, Sub: sub,
CodeHash: service.Hash(code), CodeHash: service.Hash(code),
// Here it's safe to split and trust the output since, we validated the scopes before // Here it's safe to split and trust the output since, we validated the scopes before
@@ -316,7 +324,21 @@ func (service *OIDCService) StoreCode(c *gin.Context, sub string, code string, r
ClientID: req.ClientID, ClientID: req.ClientID,
ExpiresAt: expiresAt, ExpiresAt: expiresAt,
Nonce: req.Nonce, Nonce: req.Nonce,
}) }
if req.CodeChallenge != "" {
if req.CodeChallengeMethod == "S256" {
entry.CodeChallenge = req.CodeChallenge
entry.CodeChallengeMethod = "S256"
} else {
entry.CodeChallenge = service.hashAndEncodePKCE(req.CodeChallenge)
entry.CodeChallengeMethod = "plain"
tlog.App.Warn().Msg("Received plain PKCE code challenge, it's recommended to use S256 for better security")
}
}
// Insert the code into the database
_, err := service.queries.CreateOidcCode(c, entry)
return err return err
} }
@@ -728,3 +750,20 @@ func (service *OIDCService) GetJWK() ([]byte, error) {
return jwk.Public().MarshalJSON() return jwk.Public().MarshalJSON()
} }
func (service *OIDCService) ValidatePKCE(codeChallenge string, codeChallengeMethod string, codeVerifier string) bool {
if codeChallenge == "" {
return true
}
if codeChallengeMethod == "plain" {
// Code challenge is hashed and encoded in the database for security reasons
return codeChallenge == service.hashAndEncodePKCE(codeVerifier)
}
return codeChallenge == codeVerifier
}
func (service *OIDCService) hashAndEncodePKCE(codeVerifier string) string {
hasher := sha256.New()
hasher.Write([]byte(codeVerifier))
return base64.URLEncoding.EncodeToString(hasher.Sum(nil))
}

View File

@@ -6,9 +6,11 @@ INSERT INTO "oidc_codes" (
"redirect_uri", "redirect_uri",
"client_id", "client_id",
"expires_at", "expires_at",
"nonce" "nonce",
"code_challenge",
"code_challenge_method"
) VALUES ( ) VALUES (
?, ?, ?, ?, ?, ?, ? ?, ?, ?, ?, ?, ?, ?, ?, ?
) )
RETURNING *; RETURNING *;

View File

@@ -5,7 +5,9 @@ CREATE TABLE IF NOT EXISTS "oidc_codes" (
"redirect_uri" TEXT NOT NULL, "redirect_uri" TEXT NOT NULL,
"client_id" TEXT NOT NULL, "client_id" TEXT NOT NULL,
"expires_at" INTEGER NOT NULL, "expires_at" INTEGER NOT NULL,
"nonce" TEXT DEFAULT "" "nonce" TEXT DEFAULT "",
"code_challenge" TEXT DEFAULT "",
"code_challenge_method" TEXT DEFAULT ""
); );
CREATE TABLE IF NOT EXISTS "oidc_tokens" ( CREATE TABLE IF NOT EXISTS "oidc_tokens" (

View File

@@ -26,3 +26,7 @@ sql:
go_type: "string" go_type: "string"
- column: "oidc_tokens.nonce" - column: "oidc_tokens.nonce"
go_type: "string" go_type: "string"
- column: "oidc_codes.code_challenge"
go_type: "string"
- column: "oidc_codes.code_challenge_method"
go_type: "string"