mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-06-03 01:50:14 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f5b9d83360 | |||
| 40540ce133 |
+1
-70
@@ -7,9 +7,7 @@ TINYAUTH_APPURL=
|
||||
|
||||
# database config
|
||||
|
||||
# The database driver to use. Valid values: sqlite, memory.
|
||||
TINYAUTH_DATABASE_DRIVER="sqlite"
|
||||
# The path to the SQLite database, including file name. Only used when driver is sqlite.
|
||||
# The path to the database, including file name.
|
||||
TINYAUTH_DATABASE_PATH="./tinyauth.db"
|
||||
|
||||
# analytics config
|
||||
@@ -32,8 +30,6 @@ TINYAUTH_SERVER_PORT=3000
|
||||
TINYAUTH_SERVER_ADDRESS="0.0.0.0"
|
||||
# The path to the Unix socket.
|
||||
TINYAUTH_SERVER_SOCKETPATH=
|
||||
# Enable listening on both TCP and Unix socket at the same time.
|
||||
TINYAUTH_SERVER_CONCURRENTLISTENERSENABLED=false
|
||||
|
||||
# auth config
|
||||
|
||||
@@ -41,52 +37,8 @@ TINYAUTH_SERVER_CONCURRENTLISTENERSENABLED=false
|
||||
TINYAUTH_AUTH_IP_ALLOW=
|
||||
# List of blocked IPs or CIDR ranges.
|
||||
TINYAUTH_AUTH_IP_BLOCK=
|
||||
# List of IPs or CIDR ranges that bypass authentication entirely.
|
||||
TINYAUTH_AUTH_IP_BYPASS=
|
||||
# Comma-separated list of users (username:hashed_password).
|
||||
TINYAUTH_AUTH_USERS=
|
||||
# Enable subdomains support.
|
||||
TINYAUTH_AUTH_SUBDOMAINSENABLED=true
|
||||
# Full name of the user.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_NAME=
|
||||
# Given (first) name of the user.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_GIVENNAME=
|
||||
# Family (last) name of the user.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_FAMILYNAME=
|
||||
# Middle name of the user.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_MIDDLENAME=
|
||||
# Nickname of the user.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_NICKNAME=
|
||||
# URL of the user's profile page.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_PROFILE=
|
||||
# URL of the user's profile picture.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_PICTURE=
|
||||
# URL of the user's website.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_WEBSITE=
|
||||
# Email address of the user.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_EMAIL=
|
||||
# Gender of the user.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_GENDER=
|
||||
# Birthdate of the user (YYYY-MM-DD).
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_BIRTHDATE=
|
||||
# Time zone of the user (e.g. Europe/Athens).
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ZONEINFO=
|
||||
# Locale of the user (e.g. en-US).
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_LOCALE=
|
||||
# Phone number of the user.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_PHONENUMBER=
|
||||
# Full mailing address, formatted for display.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_FORMATTED=
|
||||
# Street address.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_STREETADDRESS=
|
||||
# City or locality.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_LOCALITY=
|
||||
# State, province, or region.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_REGION=
|
||||
# Zip or postal code.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_POSTALCODE=
|
||||
# Country.
|
||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_COUNTRY=
|
||||
# Path to the users file.
|
||||
TINYAUTH_AUTH_USERSFILE=
|
||||
# Enable secure cookies.
|
||||
@@ -101,8 +53,6 @@ TINYAUTH_AUTH_LOGINTIMEOUT=300
|
||||
TINYAUTH_AUTH_LOGINMAXRETRIES=3
|
||||
# Comma-separated list of trusted proxy addresses.
|
||||
TINYAUTH_AUTH_TRUSTEDPROXIES=
|
||||
# ACL policy for allow-by-default or deny-by-default, available options are allow and deny, default is allow.
|
||||
TINYAUTH_AUTH_ACLS_POLICY="allow"
|
||||
|
||||
# apps config
|
||||
|
||||
@@ -151,10 +101,6 @@ TINYAUTH_OAUTH_PROVIDERS_name_CLIENTID=
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRET=
|
||||
# Path to the file containing the OAuth client secret.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRETFILE=
|
||||
# Comma-separated list of allowed OAuth domains for this provider.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_WHITELIST=
|
||||
# Path to the OAuth whitelist file for this provider.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_WHITELISTFILE=
|
||||
# OAuth scopes.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_SCOPES=
|
||||
# OAuth redirect URL.
|
||||
@@ -218,8 +164,6 @@ TINYAUTH_LDAP_AUTHCERT=
|
||||
TINYAUTH_LDAP_AUTHKEY=
|
||||
# Cache duration for LDAP group membership in seconds.
|
||||
TINYAUTH_LDAP_GROUPCACHETTL=900
|
||||
# Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment.
|
||||
TINYAUTH_LABELPROVIDER="auto"
|
||||
|
||||
# log config
|
||||
|
||||
@@ -239,16 +183,3 @@ TINYAUTH_LOG_STREAMS_APP_LEVEL=
|
||||
TINYAUTH_LOG_STREAMS_AUDIT_ENABLED=false
|
||||
# Log level for this stream. Use global if empty.
|
||||
TINYAUTH_LOG_STREAMS_AUDIT_LEVEL=
|
||||
|
||||
# tailscale config
|
||||
|
||||
# Enable Tailscale integration.
|
||||
TINYAUTH_TAILSCALE_ENABLED=false
|
||||
# Tailscale state directory.
|
||||
TINYAUTH_TAILSCALE_DIR="./tailscale_state"
|
||||
# Tailscale hostname.
|
||||
TINYAUTH_TAILSCALE_HOSTNAME=
|
||||
# Tailscale auth key.
|
||||
TINYAUTH_TAILSCALE_AUTHKEY=
|
||||
# Use ephemeral Tailscale node.
|
||||
TINYAUTH_TAILSCALE_EPHEMERAL=false
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
run: go mod download
|
||||
|
||||
- name: Setup sqlc
|
||||
uses: sqlc-dev/setup-sqlc@v5
|
||||
uses: sqlc-dev/setup-sqlc@v4
|
||||
with:
|
||||
sqlc-version: "1.31.1"
|
||||
|
||||
@@ -62,6 +62,6 @@ jobs:
|
||||
run: go test -coverprofile=coverage.txt -v ./...
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
go build -ldflags "-X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 ./cmd/tinyauth
|
||||
go build -ldflags "-s -w -X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 ./cmd/tinyauth
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
go build -ldflags "-X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 ./cmd/tinyauth
|
||||
go build -ldflags "-s -w -X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 ./cmd/tinyauth
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
@@ -166,7 +166,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
@@ -224,7 +224,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
@@ -282,7 +282,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/arm64
|
||||
@@ -340,7 +340,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/arm64
|
||||
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
@@ -150,7 +150,6 @@ jobs:
|
||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||
LDFLAGS="-s -w"
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
@@ -192,7 +191,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
@@ -207,7 +206,6 @@ jobs:
|
||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||
LDFLAGS="-s -w"
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
@@ -248,7 +246,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/arm64
|
||||
@@ -262,7 +260,6 @@ jobs:
|
||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||
LDFLAGS="-s -w"
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
@@ -304,7 +301,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/arm64
|
||||
@@ -319,7 +316,6 @@ jobs:
|
||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||
LDFLAGS="-s -w"
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
|
||||
@@ -38,6 +38,6 @@ jobs:
|
||||
retention-days: 5
|
||||
|
||||
- name: Upload to code-scanning
|
||||
uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4
|
||||
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
# Site builder
|
||||
FROM node:26.2-alpine3.23 AS frontend-builder
|
||||
FROM node:26.1-alpine3.23 AS frontend-builder
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
@@ -27,7 +27,6 @@ FROM golang:1.26-alpine3.23 AS builder
|
||||
ARG VERSION
|
||||
ARG COMMIT_HASH
|
||||
ARG BUILD_TIMESTAMP
|
||||
ARG LDFLAGS
|
||||
|
||||
WORKDIR /tinyauth
|
||||
|
||||
@@ -40,7 +39,7 @@ COPY ./cmd ./cmd
|
||||
COPY ./internal ./internal
|
||||
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
||||
|
||||
RUN CGO_ENABLED=0 go build -ldflags "${LDFLAGS} \
|
||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Site builder
|
||||
FROM node:26.2-alpine3.23 AS frontend-builder
|
||||
FROM node:26.1-alpine3.23 AS frontend-builder
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
@@ -27,7 +27,6 @@ FROM golang:1.26-alpine3.23 AS builder
|
||||
ARG VERSION
|
||||
ARG COMMIT_HASH
|
||||
ARG BUILD_TIMESTAMP
|
||||
ARG LDFLAGS
|
||||
|
||||
WORKDIR /tinyauth
|
||||
|
||||
@@ -42,7 +41,7 @@ COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
||||
|
||||
RUN mkdir -p data
|
||||
|
||||
RUN CGO_ENABLED=0 go build -ldflags "${LDFLAGS} \
|
||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
||||
|
||||
@@ -8,7 +8,6 @@ TAG_NAME := $(shell git describe --abbrev=0 --exact-match 2> /dev/null || echo "
|
||||
COMMIT_HASH := $(shell git rev-parse HEAD)
|
||||
BUILD_TIMESTAMP := $(shell date '+%Y-%m-%dT%H:%M:%S')
|
||||
BIN_NAME := tinyauth-$(GOARCH)
|
||||
LDFLAGS := -s -w
|
||||
|
||||
# Development vars
|
||||
DEV_COMPOSE := $(shell test -f "docker-compose.test.yml" && echo "docker-compose.test.yml" || echo "docker-compose.dev.yml" )
|
||||
@@ -37,7 +36,7 @@ webui: clean-webui
|
||||
|
||||
# Build the binary
|
||||
binary: webui
|
||||
CGO_ENABLED=$(CGO_ENABLED) go build -ldflags "${LDFLAGS} \
|
||||
CGO_ENABLED=$(CGO_ENABLED) go build -ldflags "-s -w \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${TAG_NAME} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
||||
|
||||
@@ -58,8 +58,8 @@
|
||||
"invalidInput": "Input non valido",
|
||||
"domainWarningTitle": "Dominio non valido",
|
||||
"domainWarningSubtitle": "Stai accedendo a questa istanza da un dominio errato. Scegliendo di procedere, potresti incontrare problemi con l'autenticazione.",
|
||||
"domainWarningCurrent": "Attuale:",
|
||||
"domainWarningExpected": "Previsto:",
|
||||
"domainWarningCurrent": "Current:",
|
||||
"domainWarningExpected": "Expected:",
|
||||
"ignoreTitle": "Ignora",
|
||||
"goToCorrectDomainTitle": "Vai al dominio corretto",
|
||||
"authorizeTitle": "Autorizza",
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"fieldRequired": "Ово поље је неопходно",
|
||||
"invalidInput": "Неисправан унос",
|
||||
"domainWarningTitle": "Неисправан домен",
|
||||
"domainWarningSubtitle": "Приступате овој инстанци са неисправног домена. Ако наставите, можете наићи на проблеме са аутентификацијом.",
|
||||
"domainWarningSubtitle": "You are accessing this instance from an incorrect domain. If you proceed, you may encounter issues with authentication.",
|
||||
"domainWarningCurrent": "Тренутни:",
|
||||
"domainWarningExpected": "Очекивани:",
|
||||
"ignoreTitle": "Игнориши",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/tinyauthapp/tinyauth
|
||||
|
||||
go 1.26.3
|
||||
go 1.26.1
|
||||
|
||||
require (
|
||||
charm.land/huh/v2 v2.0.3
|
||||
@@ -12,21 +12,19 @@ require (
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||
github.com/google/go-querystring v1.2.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgx/v5 v5.9.2
|
||||
github.com/mdp/qrterminal/v3 v3.2.1
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/rs/zerolog v1.35.1
|
||||
github.com/steveiliop56/ding v0.2.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298
|
||||
github.com/weppos/publicsuffix-go v0.50.3
|
||||
golang.org/x/crypto v0.52.0
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/tools v0.45.0
|
||||
k8s.io/apimachinery v0.36.1
|
||||
k8s.io/client-go v0.36.1
|
||||
modernc.org/sqlite v1.50.1
|
||||
tailscale.com v1.98.3
|
||||
golang.org/x/tools v0.43.0
|
||||
k8s.io/apimachinery v0.36.0
|
||||
k8s.io/client-go v0.36.0
|
||||
modernc.org/sqlite v1.50.0
|
||||
tailscale.com v1.96.5
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -44,6 +42,19 @@ require (
|
||||
github.com/akutz/memconn v0.1.0 // indirect
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.58 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
|
||||
github.com/aws/smithy-go v1.24.0 // indirect
|
||||
github.com/boombuler/barcode v1.0.2 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
@@ -68,7 +79,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@@ -95,10 +106,11 @@ require (
|
||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.9.2 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
@@ -125,6 +137,7 @@ require (
|
||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
@@ -132,12 +145,12 @@ require (
|
||||
github.com/safchain/ethtool v0.3.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d // indirect
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
||||
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd // indirect
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
|
||||
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
|
||||
github.com/tailscale/wireguard-go v0.0.0-20260427181203-e3ac4a0afb4e // indirect
|
||||
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
@@ -156,12 +169,12 @@ require (
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.22.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/net v0.54.0 // indirect
|
||||
golang.org/x/mod v0.34.0 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/term v0.43.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/term v0.42.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||
@@ -173,7 +186,7 @@ require (
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
|
||||
modernc.org/libc v1.72.3 // indirect
|
||||
modernc.org/libc v1.72.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
|
||||
@@ -143,8 +143,6 @@ github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbww
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
|
||||
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc h1:8WFBn63wegobsYAX0YjD+8suexZDga5CctH4CCTx2+8=
|
||||
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
|
||||
github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
@@ -153,8 +151,8 @@ github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
@@ -285,8 +283,8 @@ github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUF
|
||||
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
|
||||
@@ -376,6 +374,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
|
||||
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
@@ -396,14 +396,12 @@ github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP
|
||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/steveiliop56/ding v0.2.0 h1:m/Fj99wBpVVLHlpqb2RDJkWubOc5cWJ11ZYCHya3Sk0=
|
||||
github.com/steveiliop56/ding v0.2.0/go.mod h1:bE2u2XH7CjhPzbb/0Ems+D8YZlf2Ae+eKhj00UR1iAY=
|
||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@@ -417,16 +415,14 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d h1:JcGKBZAL7ePLwOhUdN8qGQZlP5GueEiIZwY7R62pejE=
|
||||
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||
github.com/tailscale/gliderssh v0.3.4-0.20260330083525-c1389c70ff89 h1:glgVc1ZYMjwN1Q/ITWeuSQyl029uayagaR2sjsifehc=
|
||||
github.com/tailscale/gliderssh v0.3.4-0.20260330083525-c1389c70ff89/go.mod h1:wn16Km1EZOX4UEAyaZa3dBwfFGOJ7neck40NcwosJUw=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 h1:SRL6irQkKGQKKLzvQP/ke/2ZuB7Py5+XuqtOgSj+iMM=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
|
||||
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd h1:Rf9uhF1+VJ7ZHqxrG8pJ6YacmHvVCmByDmGbAWCc/gA=
|
||||
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo=
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
|
||||
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
|
||||
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
|
||||
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=
|
||||
@@ -435,8 +431,8 @@ github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:U
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
|
||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20260427181203-e3ac4a0afb4e h1:GexFR7ak1iz26fxg8HWCpOEqAOL8UEZJ7J3JxeCalDs=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20260427181203-e3ac4a0afb4e/go.mod h1:6SerzcvHWQchKO2BfNdmquA77CHSECZuFl+D9fp4RnI=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
||||
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
|
||||
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
|
||||
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||
@@ -493,18 +489,18 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
||||
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -512,16 +508,16 @@ golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||
@@ -549,26 +545,26 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 h1:Zy8IV/+FMLxy6j6p87vk/vQGKcdnbprwjTxc8UiUtsA=
|
||||
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
|
||||
honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=
|
||||
honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=
|
||||
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 h1:5SXjd4ET5dYijLaf0O3aOenC0Z4ZafIWSpjUzsQaNho=
|
||||
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
|
||||
k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
|
||||
k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
|
||||
k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
|
||||
k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=
|
||||
k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=
|
||||
k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80=
|
||||
k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34=
|
||||
k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ=
|
||||
k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc=
|
||||
k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c=
|
||||
k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y=
|
||||
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
|
||||
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
|
||||
modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
|
||||
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
|
||||
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
|
||||
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
|
||||
modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
@@ -577,18 +573,18 @@ modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
|
||||
modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
|
||||
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
|
||||
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w=
|
||||
modernc.org/sqlite v1.50.1/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
||||
modernc.org/sqlite v1.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM=
|
||||
modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
@@ -605,5 +601,5 @@ sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
tailscale.com v1.98.3 h1:caAbG4UfkKfKPE6b1fj5t4ep5qrwEis5AJu91ruvePw=
|
||||
tailscale.com v1.98.3/go.mod h1:U23ZwbZlKJMNU7CScy+lCVVlece/S5n09q0nyudncBI=
|
||||
tailscale.com v1.96.5 h1:gNkfA/KSZAl6jCH9cj8urq00HRWItDDTtGsyATI89jA=
|
||||
tailscale.com v1.96.5/go.mod h1:/3lnZBYb2UEwnN0MNu2SDXUtT06AGd5k0s+OWx3WmcY=
|
||||
|
||||
@@ -13,11 +13,11 @@ import (
|
||||
"os/signal"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/steveiliop56/ding"
|
||||
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
@@ -26,12 +26,6 @@ import (
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
)
|
||||
|
||||
// Shutdown order for go routines
|
||||
// 1. Janitor routines (e.g. database cleanup, heartbeat) - ding.RingMinor
|
||||
// 2. HTTP server listeners - ding.RingNormal
|
||||
// 3. Networking layers, user and label providers (e.g. ailscale service, kubernetes service) - ding.RingMajor
|
||||
// 4. Database connection - ding.RingCritical
|
||||
|
||||
type Services struct {
|
||||
accessControlService *service.AccessControlsService
|
||||
authService *service.AuthService
|
||||
@@ -54,7 +48,7 @@ type BootstrapApp struct {
|
||||
queries repository.Store
|
||||
router *gin.Engine
|
||||
db *sql.DB
|
||||
ding *ding.Ding
|
||||
wg sync.WaitGroup
|
||||
listeners []Listener
|
||||
}
|
||||
|
||||
@@ -70,10 +64,6 @@ func (app *BootstrapApp) Setup() error {
|
||||
app.ctx = ctx
|
||||
app.cancel = cancel
|
||||
|
||||
// Create a ding instance
|
||||
dg := ding.New(ctx)
|
||||
app.ding = dg
|
||||
|
||||
// setup logger
|
||||
log := logger.NewLogger().WithConfig(app.config.Log)
|
||||
log.Init()
|
||||
@@ -107,12 +97,7 @@ func (app *BootstrapApp) Setup() error {
|
||||
return fmt.Errorf("failed to load users: %w", err)
|
||||
}
|
||||
|
||||
if users != nil {
|
||||
app.runtime.LocalUsers = *users
|
||||
} else {
|
||||
log.App.Debug().Msg("No local users found, local authentication will not be available")
|
||||
app.runtime.LocalUsers = []model.LocalUser{}
|
||||
}
|
||||
|
||||
// load oauth whitelist
|
||||
oauthWhitelist, err := utils.GetStringList(app.config.OAuth.Whitelist, app.config.OAuth.WhitelistFile)
|
||||
@@ -127,13 +112,6 @@ func (app *BootstrapApp) Setup() error {
|
||||
app.runtime.OAuthProviders = app.config.OAuth.Providers
|
||||
|
||||
for id, provider := range app.runtime.OAuthProviders {
|
||||
providerWhitelist, err := utils.GetStringList(provider.Whitelist, provider.WhitelistFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load oauth whitelist for provider %s: %w", id, err)
|
||||
}
|
||||
|
||||
provider.Whitelist = providerWhitelist
|
||||
|
||||
secret := utils.GetSecret(provider.ClientSecret, provider.ClientSecretFile)
|
||||
provider.ClientSecret = secret
|
||||
provider.ClientSecretFile = ""
|
||||
@@ -196,17 +174,15 @@ func (app *BootstrapApp) Setup() error {
|
||||
return fmt.Errorf("failed to setup database: %w", err)
|
||||
}
|
||||
|
||||
app.ding.Go(func(ctx context.Context) {
|
||||
<-ctx.Done()
|
||||
app.log.App.Debug().Msg("Shutting down database connection")
|
||||
if app.db == nil {
|
||||
// using memory store, no db instance
|
||||
return
|
||||
// after this point, we start initializing dependencies so it's a good time to setup a defer
|
||||
// to ensure that resources are cleaned up properly in case of an error during initialization
|
||||
defer func() {
|
||||
app.cancel()
|
||||
app.wg.Wait()
|
||||
if app.db != nil {
|
||||
app.db.Close()
|
||||
}
|
||||
if err := app.db.Close(); err != nil {
|
||||
app.log.App.Error().Err(err).Msg("Failed to close database connection")
|
||||
}
|
||||
}, ding.RingCritical)
|
||||
}()
|
||||
|
||||
// store
|
||||
app.queries = store
|
||||
@@ -273,12 +249,12 @@ func (app *BootstrapApp) Setup() error {
|
||||
|
||||
// start db cleanup routine
|
||||
app.log.App.Debug().Msg("Starting database cleanup routine")
|
||||
app.ding.Go(app.dbCleanupRoutine, ding.RingMinor)
|
||||
app.wg.Go(app.dbCleanupRoutine)
|
||||
|
||||
// if analytics are not disabled, start heartbeat
|
||||
if app.config.Analytics.Enabled {
|
||||
app.log.App.Debug().Msg("Starting heartbeat routine")
|
||||
app.ding.Go(app.heartbeatRoutine, ding.RingMinor)
|
||||
app.wg.Go(app.heartbeatRoutine)
|
||||
}
|
||||
|
||||
// setup listeners
|
||||
@@ -299,7 +275,6 @@ func (app *BootstrapApp) Setup() error {
|
||||
for {
|
||||
select {
|
||||
case <-app.ctx.Done():
|
||||
app.ding.Wait()
|
||||
app.log.App.Info().Msg("Oh, it's time for me to go, bye!")
|
||||
return nil
|
||||
case err := <-lec:
|
||||
@@ -310,7 +285,7 @@ func (app *BootstrapApp) Setup() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (app *BootstrapApp) heartbeatRoutine(ctx context.Context) {
|
||||
func (app *BootstrapApp) heartbeatRoutine() {
|
||||
ticker := time.NewTicker(time.Duration(12) * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
@@ -363,7 +338,7 @@ func (app *BootstrapApp) heartbeatRoutine(ctx context.Context) {
|
||||
if res.StatusCode != 200 && res.StatusCode != 201 {
|
||||
app.log.App.Debug().Str("status", res.Status).Msg("Heartbeat returned non-200/201 status")
|
||||
}
|
||||
case <-ctx.Done():
|
||||
case <-app.ctx.Done():
|
||||
app.log.App.Debug().Msg("Stopping heartbeat routine")
|
||||
ticker.Stop()
|
||||
return
|
||||
@@ -371,7 +346,7 @@ func (app *BootstrapApp) heartbeatRoutine(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (app *BootstrapApp) dbCleanupRoutine(ctx context.Context) {
|
||||
func (app *BootstrapApp) dbCleanupRoutine() {
|
||||
ticker := time.NewTicker(time.Duration(30) * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
@@ -380,14 +355,14 @@ func (app *BootstrapApp) dbCleanupRoutine(ctx context.Context) {
|
||||
case <-ticker.C:
|
||||
app.log.App.Debug().Msg("Running database cleanup")
|
||||
|
||||
err := app.queries.DeleteExpiredSessions(ctx, time.Now().Unix())
|
||||
err := app.queries.DeleteExpiredSessions(app.ctx, time.Now().Unix())
|
||||
|
||||
if err != nil {
|
||||
app.log.App.Error().Err(err).Msg("Failed to delete expired sessions")
|
||||
}
|
||||
|
||||
app.log.App.Debug().Msg("Database cleanup completed")
|
||||
case <-ctx.Done():
|
||||
case <-app.ctx.Done():
|
||||
app.log.App.Debug().Msg("Stopping database cleanup routine")
|
||||
ticker.Stop()
|
||||
return
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
"github.com/tinyauthapp/tinyauth/internal/middleware"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
@@ -81,9 +80,9 @@ func (app *BootstrapApp) runListeners() (chan error, error) {
|
||||
return nil, fmt.Errorf("failed to get listener function: %w", err)
|
||||
}
|
||||
|
||||
app.ding.Go(func(ctx context.Context) {
|
||||
lec <- listenerFunc(ctx)
|
||||
}, ding.RingNormal)
|
||||
app.wg.Go(func() {
|
||||
lec <- listenerFunc()
|
||||
})
|
||||
}
|
||||
|
||||
return lec, nil
|
||||
@@ -126,7 +125,7 @@ func (app *BootstrapApp) calculateListenerPolicy() []Listener {
|
||||
return l
|
||||
}
|
||||
|
||||
func (app *BootstrapApp) listenerFromType(listenerType Listener) (func(ctx context.Context) error, error) {
|
||||
func (app *BootstrapApp) listenerFromType(listenerType Listener) (func() error, error) {
|
||||
switch listenerType {
|
||||
case ListenerHTTP:
|
||||
return app.serveHTTP, nil
|
||||
@@ -139,7 +138,7 @@ func (app *BootstrapApp) listenerFromType(listenerType Listener) (func(ctx conte
|
||||
}
|
||||
}
|
||||
|
||||
func (app *BootstrapApp) serveHTTP(ctx context.Context) error {
|
||||
func (app *BootstrapApp) serveHTTP() error {
|
||||
address := fmt.Sprintf("%s:%d", app.config.Server.Address, app.config.Server.Port)
|
||||
|
||||
app.log.App.Info().Msgf("Starting server on %s", address)
|
||||
@@ -155,10 +154,10 @@ func (app *BootstrapApp) serveHTTP(ctx context.Context) error {
|
||||
Handler: app.router.Handler(),
|
||||
}
|
||||
|
||||
return app.serve(listener, server, ctx, "http")
|
||||
return app.serve(listener, server, "http")
|
||||
}
|
||||
|
||||
func (app *BootstrapApp) serveUnix(ctx context.Context) error {
|
||||
func (app *BootstrapApp) serveUnix() error {
|
||||
_, err := os.Stat(app.config.Server.SocketPath)
|
||||
|
||||
if err == nil {
|
||||
@@ -182,10 +181,10 @@ func (app *BootstrapApp) serveUnix(ctx context.Context) error {
|
||||
Handler: app.router.Handler(),
|
||||
}
|
||||
|
||||
return app.serve(listener, server, ctx, "unix socket")
|
||||
return app.serve(listener, server, "unix socket")
|
||||
}
|
||||
|
||||
func (app *BootstrapApp) serveTailscale(ctx context.Context) error {
|
||||
func (app *BootstrapApp) serveTailscale() error {
|
||||
app.log.App.Info().Msgf("Starting Tailscale server on %s", fmt.Sprintf("https://%s", app.services.tailscaleService.GetHostname()))
|
||||
|
||||
listener, err := app.services.tailscaleService.CreateListener()
|
||||
@@ -198,23 +197,27 @@ func (app *BootstrapApp) serveTailscale(ctx context.Context) error {
|
||||
Handler: app.router.Handler(),
|
||||
}
|
||||
|
||||
return app.serve(listener, server, ctx, "tailscale")
|
||||
return app.serve(listener, server, "tailscale")
|
||||
}
|
||||
|
||||
func (app *BootstrapApp) serve(listener net.Listener, server *http.Server, ctx context.Context, name string) error {
|
||||
func (app *BootstrapApp) serve(listener net.Listener, server *http.Server, name string) error {
|
||||
shutdown := func() {
|
||||
// we use a new context for the shutdown since the main one is cancelled
|
||||
sctx, cancel := context.WithTimeout(context.Background(), model.GracefulShutdownTimeout*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), model.GracefulShutdownTimeout*time.Second)
|
||||
defer cancel()
|
||||
err := server.Shutdown(sctx)
|
||||
if err != nil {
|
||||
err := server.Shutdown(ctx)
|
||||
if err != nil &&
|
||||
// With tailscale, the goroutine for shutting down the tailscale connection
|
||||
// runs first and causes the connection the tailscale listener is running on to close
|
||||
// first so, the shutdown fails
|
||||
// TODO: add priority to the goroutine shutdowns
|
||||
!errors.Is(err, net.ErrClosed) {
|
||||
app.log.App.Error().Err(err).Msgf("Failed to shutdown %s listener gracefully", name)
|
||||
}
|
||||
listener.Close()
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
<-app.ctx.Done()
|
||||
app.log.App.Debug().Msgf("Shutting down %s listener", name)
|
||||
shutdown()
|
||||
}()
|
||||
|
||||
@@ -2,13 +2,14 @@ package bootstrap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"os"
|
||||
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
)
|
||||
|
||||
func (app *BootstrapApp) setupServices() error {
|
||||
ldapService, err := service.NewLdapService(app.log, app.config, app.ding)
|
||||
ldapService, err := service.NewLdapService(app.log, app.config, app.ctx, &app.wg)
|
||||
|
||||
if err != nil {
|
||||
app.log.App.Warn().Err(err).Msg("Failed to initialize LDAP connection, will continue without it")
|
||||
@@ -22,7 +23,7 @@ func (app *BootstrapApp) setupServices() error {
|
||||
return fmt.Errorf("failed to initialize label provider: %w", err)
|
||||
}
|
||||
|
||||
tailscaleService, err := service.NewTailscaleService(app.log, app.config, app.ctx, app.ding)
|
||||
tailscaleService, err := service.NewTailscaleService(app.log, app.config, app.ctx, &app.wg)
|
||||
|
||||
if err != nil {
|
||||
app.log.App.Warn().Err(err).Msg("Failed to initialize Tailscale connection, will continue without it")
|
||||
@@ -42,10 +43,10 @@ func (app *BootstrapApp) setupServices() error {
|
||||
oauthBrokerService := service.NewOAuthBrokerService(app.log, app.runtime.OAuthProviders, app.ctx)
|
||||
app.services.oauthBrokerService = oauthBrokerService
|
||||
|
||||
authService := service.NewAuthService(app.log, app.config, app.runtime, app.ctx, app.ding, app.services.ldapService, app.queries, app.services.oauthBrokerService, app.services.tailscaleService, app.services.policyEngine)
|
||||
authService := service.NewAuthService(app.log, app.config, app.runtime, app.ctx, &app.wg, app.services.ldapService, app.queries, app.services.oauthBrokerService, app.services.tailscaleService)
|
||||
app.services.authService = authService
|
||||
|
||||
oidcService, err := service.NewOIDCService(app.log, app.config, app.runtime, app.queries, app.ding)
|
||||
oidcService, err := service.NewOIDCService(app.log, app.config, app.runtime, app.queries, app.ctx, &app.wg)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize oidc service: %w", err)
|
||||
@@ -69,7 +70,7 @@ func (app *BootstrapApp) getLabelProvider() (service.LabelProvider, error) {
|
||||
if useKubernetes {
|
||||
app.log.App.Debug().Msg("Using Kubernetes label provider")
|
||||
|
||||
kubernetesService, err := service.NewKubernetesService(app.log, app.ctx, app.ding)
|
||||
kubernetesService, err := service.NewKubernetesService(app.log, app.ctx, &app.wg)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize kubernetes service: %w", err)
|
||||
@@ -81,7 +82,7 @@ func (app *BootstrapApp) getLabelProvider() (service.LabelProvider, error) {
|
||||
|
||||
app.log.App.Debug().Msg("Using Docker label provider")
|
||||
|
||||
dockerService, err := service.NewDockerService(app.log, app.ctx, app.ding)
|
||||
dockerService, err := service.NewDockerService(app.log, app.ctx, &app.wg)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize docker service: %w", err)
|
||||
@@ -126,7 +127,6 @@ func (app *BootstrapApp) setupPolicyEngine() error {
|
||||
})
|
||||
policyEngine.RegisterRule(service.RuleIPBypassed, &service.IPBypassedRule{
|
||||
Log: app.log,
|
||||
Config: app.config,
|
||||
})
|
||||
|
||||
app.services.policyEngine = policyEngine
|
||||
|
||||
@@ -183,23 +183,9 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
svc, err := controller.auth.GetOAuthService(sessionIdCookie)
|
||||
|
||||
if err != nil {
|
||||
controller.log.App.Error().Err(err).Msg("Failed to get OAuth service for session")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||
return
|
||||
}
|
||||
|
||||
if svc.ID() != req.Provider {
|
||||
controller.log.App.Warn().Msgf("OAuth provider mismatch: expected %s, got %s", req.Provider, svc.ID())
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||
return
|
||||
}
|
||||
|
||||
if !controller.auth.IsEmailWhitelisted(svc.ID(), user.Email) {
|
||||
if !controller.auth.IsEmailWhitelisted(user.Email) {
|
||||
controller.log.App.Warn().Str("email", user.Email).Msg("Email not whitelisted, denying access")
|
||||
controller.log.AuditLoginFailure(user.Email, svc.ID(), c.ClientIP(), "email not whitelisted")
|
||||
controller.log.AuditLoginFailure(user.Email, req.Provider, c.ClientIP(), "email not whitelisted")
|
||||
|
||||
queries, err := query.Values(UnauthorizedQuery{
|
||||
Username: user.Email,
|
||||
@@ -240,6 +226,20 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
username = strings.Replace(user.Email, "@", "_", 1)
|
||||
}
|
||||
|
||||
svc, err := controller.auth.GetOAuthService(sessionIdCookie)
|
||||
|
||||
if err != nil {
|
||||
controller.log.App.Error().Err(err).Msg("Failed to get OAuth service for session")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||
return
|
||||
}
|
||||
|
||||
if svc.ID() != req.Provider {
|
||||
controller.log.App.Warn().Msgf("OAuth provider mismatch: expected %s, got %s", req.Provider, svc.ID())
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||
return
|
||||
}
|
||||
|
||||
sessionCookie := repository.Session{
|
||||
Username: username,
|
||||
Name: name,
|
||||
|
||||
@@ -16,15 +16,6 @@ import (
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
)
|
||||
|
||||
type authorizeErrorParams struct {
|
||||
err error
|
||||
reason string
|
||||
reasonPublic string
|
||||
callback string
|
||||
callbackError string
|
||||
state string
|
||||
}
|
||||
|
||||
type OIDCController struct {
|
||||
log *logger.Logger
|
||||
oidc *service.OIDCService
|
||||
@@ -128,55 +119,34 @@ func (controller *OIDCController) GetClientInfo(c *gin.Context) {
|
||||
|
||||
func (controller *OIDCController) Authorize(c *gin.Context) {
|
||||
if controller.oidc == nil {
|
||||
controller.authorizeError(c, authorizeErrorParams{
|
||||
err: errors.New("err_oidc_not_configured"),
|
||||
reason: "OIDC not configured",
|
||||
reasonPublic: "This instance is not configured for OIDC",
|
||||
})
|
||||
controller.authorizeError(c, errors.New("err_oidc_not_configured"), "OIDC not configured", "This instance is not configured for OIDC", "", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
userContext, err := new(model.UserContext).NewFromGin(c)
|
||||
|
||||
if err != nil {
|
||||
controller.authorizeError(c, authorizeErrorParams{
|
||||
err: err,
|
||||
reason: "Failed to get user context",
|
||||
reasonPublic: "User is not logged in or the session is invalid",
|
||||
})
|
||||
controller.authorizeError(c, err, "Failed to get user context", "User is not logged in or the session is invalid", "", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
if !userContext.Authenticated {
|
||||
controller.authorizeError(c, authorizeErrorParams{
|
||||
err: errors.New("err user not logged in"),
|
||||
reason: "User not logged in",
|
||||
reasonPublic: "The user is not logged in",
|
||||
})
|
||||
controller.authorizeError(c, errors.New("err user not logged in"), "User not logged in", "The user is not logged in", "", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
var req service.AuthorizeRequest
|
||||
|
||||
err = c.Bind(&req)
|
||||
|
||||
err = c.BindJSON(&req)
|
||||
if err != nil {
|
||||
controller.authorizeError(c, authorizeErrorParams{
|
||||
err: err,
|
||||
reason: "Failed to bind JSON",
|
||||
reasonPublic: "The client provided an invalid authorization request",
|
||||
})
|
||||
controller.authorizeError(c, err, "Failed to bind JSON", "The client provided an invalid authorization request", "", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
client, ok := controller.oidc.GetClient(req.ClientID)
|
||||
|
||||
if !ok {
|
||||
controller.authorizeError(c, authorizeErrorParams{
|
||||
err: fmt.Errorf("client not found: %s", req.ClientID),
|
||||
reason: "Client not found",
|
||||
reasonPublic: "The client ID is invalid",
|
||||
})
|
||||
controller.authorizeError(c, fmt.Errorf("client not found: %s", req.ClientID), "Client not found", "The client ID is invalid", "", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -185,21 +155,10 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
|
||||
if err != nil {
|
||||
controller.log.App.Warn().Err(err).Msg("Failed to validate authorize params")
|
||||
if err.Error() != "invalid_request_uri" {
|
||||
controller.authorizeError(c, authorizeErrorParams{
|
||||
err: err,
|
||||
reason: "Failed validate authorize params",
|
||||
reasonPublic: "Invalid request parameters",
|
||||
callback: req.RedirectURI,
|
||||
callbackError: err.Error(),
|
||||
state: req.State,
|
||||
})
|
||||
controller.authorizeError(c, err, "Failed validate authorize params", "Invalid request parameters", req.RedirectURI, err.Error(), req.State)
|
||||
return
|
||||
}
|
||||
controller.authorizeError(c, authorizeErrorParams{
|
||||
err: err,
|
||||
reason: "Redirect URI not trusted",
|
||||
reasonPublic: "The provided redirect URI is not trusted",
|
||||
})
|
||||
controller.authorizeError(c, err, "Redirect URI not trusted", "The provided redirect URI is not trusted", "", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -210,28 +169,14 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
|
||||
// Before storing the code, delete old session
|
||||
err = controller.oidc.DeleteOldSession(c, sub)
|
||||
if err != nil {
|
||||
controller.authorizeError(c, authorizeErrorParams{
|
||||
err: err,
|
||||
reason: "Failed to delete old sessions",
|
||||
reasonPublic: "Failed to delete old sessions",
|
||||
callback: req.RedirectURI,
|
||||
callbackError: "server_error",
|
||||
state: req.State,
|
||||
})
|
||||
controller.authorizeError(c, err, "Failed to delete old sessions", "Failed to delete old sessions", req.RedirectURI, "server_error", req.State)
|
||||
return
|
||||
}
|
||||
|
||||
err = controller.oidc.StoreCode(c, sub, code, req)
|
||||
|
||||
if err != nil {
|
||||
controller.authorizeError(c, authorizeErrorParams{
|
||||
err: err,
|
||||
reason: "Failed to store code",
|
||||
reasonPublic: "Failed to store code",
|
||||
callback: req.RedirectURI,
|
||||
callbackError: "server_error",
|
||||
state: req.State,
|
||||
})
|
||||
controller.authorizeError(c, err, "Failed to store code", "Failed to store code", req.RedirectURI, "server_error", req.State)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -241,14 +186,7 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
|
||||
|
||||
if err != nil {
|
||||
controller.log.App.Error().Err(err).Msg("Failed to store user info")
|
||||
controller.authorizeError(c, authorizeErrorParams{
|
||||
err: err,
|
||||
reason: "Failed to store user info",
|
||||
reasonPublic: "Failed to store user info",
|
||||
callback: req.RedirectURI,
|
||||
callbackError: "server_error",
|
||||
state: req.State,
|
||||
})
|
||||
controller.authorizeError(c, err, "Failed to store user info", "Failed to store user info", req.RedirectURI, "server_error", req.State)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -259,14 +197,7 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
controller.authorizeError(c, authorizeErrorParams{
|
||||
err: err,
|
||||
reason: "Failed to build query",
|
||||
reasonPublic: "Failed to build query",
|
||||
callback: req.RedirectURI,
|
||||
callbackError: "server_error",
|
||||
state: req.State,
|
||||
})
|
||||
controller.authorizeError(c, err, "Failed to build query", "Failed to build query", req.RedirectURI, "server_error", req.State)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -547,20 +478,20 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
||||
c.JSON(200, controller.oidc.CompileUserinfo(user, entry.Scope))
|
||||
}
|
||||
|
||||
func (controller *OIDCController) authorizeError(c *gin.Context, params authorizeErrorParams) {
|
||||
controller.log.App.Error().Err(params.err).Str("reason", params.reason).Msg("Authorization error")
|
||||
func (controller *OIDCController) authorizeError(c *gin.Context, err error, reason string, reasonUser string, callback string, callbackError string, state string) {
|
||||
controller.log.App.Warn().Err(err).Str("reason", reason).Msg("Authorization error")
|
||||
|
||||
if params.callback != "" {
|
||||
if callback != "" {
|
||||
errorQueries := CallbackError{
|
||||
Error: params.callbackError,
|
||||
Error: callbackError,
|
||||
}
|
||||
|
||||
if params.reasonPublic != "" {
|
||||
errorQueries.ErrorDescription = params.reasonPublic
|
||||
if reasonUser != "" {
|
||||
errorQueries.ErrorDescription = reasonUser
|
||||
}
|
||||
|
||||
if params.state != "" {
|
||||
errorQueries.State = params.state
|
||||
if state != "" {
|
||||
errorQueries.State = state
|
||||
}
|
||||
|
||||
queries, err := query.Values(errorQueries)
|
||||
@@ -572,13 +503,13 @@ func (controller *OIDCController) authorizeError(c *gin.Context, params authoriz
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": 200,
|
||||
"redirect_uri": fmt.Sprintf("%s?%s", params.callback, queries.Encode()),
|
||||
"redirect_uri": fmt.Sprintf("%s?%s", callback, queries.Encode()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
errorQueries := ErrorScreen{
|
||||
Error: params.reasonPublic,
|
||||
Error: reasonUser,
|
||||
}
|
||||
|
||||
queries, err := query.Values(errorQueries)
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
@@ -840,9 +840,9 @@ func TestOIDCController(t *testing.T) {
|
||||
|
||||
store := memory.New()
|
||||
|
||||
dg := ding.New(context.TODO())
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
oidcService, err := service.NewOIDCService(log, cfg, runtime, store, dg)
|
||||
oidcService, err := service.NewOIDCService(log, cfg, runtime, store, context.TODO(), wg)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -160,10 +160,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
userContext, err := new(model.UserContext).NewFromGin(c)
|
||||
|
||||
if err != nil {
|
||||
// No user context found is not an issue
|
||||
if !errors.Is(err, model.ErrUserContextNotFound) {
|
||||
controller.log.App.Error().Err(err).Msg("Failed to create user context from request, treating as unauthenticated")
|
||||
}
|
||||
controller.log.App.Debug().Err(err).Msg("Failed to create user context from request, treating as unauthenticated")
|
||||
userContext = &model.UserContext{
|
||||
Authenticated: false,
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package controller_test
|
||||
import (
|
||||
"context"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
@@ -353,10 +353,11 @@ func TestProxyController(t *testing.T) {
|
||||
|
||||
store := memory.New()
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
ctx := context.TODO()
|
||||
dg := ding.New(ctx)
|
||||
|
||||
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
||||
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, store, broker, nil)
|
||||
aclsService := service.NewAccessControlsService(log, cfg, nil)
|
||||
|
||||
policyEngine, err := service.NewPolicyEngine(cfg, log)
|
||||
@@ -382,8 +383,6 @@ func TestProxyController(t *testing.T) {
|
||||
Log: log,
|
||||
})
|
||||
|
||||
authService := service.NewAuthService(log, cfg, runtime, ctx, dg, nil, store, broker, nil, policyEngine)
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
router := gin.Default()
|
||||
|
||||
@@ -6,12 +6,12 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
@@ -412,13 +412,10 @@ func TestUserController(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
dg := ding.New(ctx)
|
||||
|
||||
policyEngine, err := service.NewPolicyEngine(cfg, log)
|
||||
require.NoError(t, err)
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
||||
authService := service.NewAuthService(log, cfg, runtime, ctx, dg, nil, store, broker, nil, policyEngine)
|
||||
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, store, broker, nil)
|
||||
|
||||
beforeEach := func() {
|
||||
// Clear failed login attempts before each test
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
@@ -89,11 +89,11 @@ func TestWellKnownController(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
dg := ding.New(ctx)
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
store := memory.New()
|
||||
|
||||
oidcService, err := service.NewOIDCService(log, cfg, runtime, store, dg)
|
||||
oidcService, err := service.NewOIDCService(log, cfg, runtime, store, ctx, wg)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -205,7 +205,7 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string, ip stri
|
||||
return nil, nil, fmt.Errorf("oauth provider from session cookie not found: %s", userContext.OAuth.ID)
|
||||
}
|
||||
|
||||
if !m.auth.IsEmailWhitelisted(userContext.OAuth.ID, userContext.OAuth.Email) {
|
||||
if !m.auth.IsEmailWhitelisted(userContext.OAuth.Email) {
|
||||
m.auth.DeleteSession(ctx, uuid)
|
||||
return nil, nil, fmt.Errorf("email from session cookie not whitelisted: %s", userContext.OAuth.Email)
|
||||
}
|
||||
@@ -251,10 +251,6 @@ func (m *ContextMiddleware) basicAuth(username string, password string) (*model.
|
||||
case model.UserLocal:
|
||||
user := m.auth.GetLocalUser(username)
|
||||
|
||||
if user == nil {
|
||||
return nil, nil, fmt.Errorf("user not found locally: %s", username)
|
||||
}
|
||||
|
||||
if user.TOTPSecret != "" {
|
||||
return nil, nil, fmt.Errorf("user with totp not allowed to login via basic auth: %s", username)
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tinyauthapp/tinyauth/internal/middleware"
|
||||
@@ -250,15 +250,12 @@ func TestContextMiddleware(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
dg := ding.New(ctx)
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
store := memory.New()
|
||||
|
||||
policyEngine, err := service.NewPolicyEngine(cfg, log)
|
||||
require.NoError(t, err)
|
||||
|
||||
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
||||
authService := service.NewAuthService(log, cfg, runtime, ctx, dg, nil, store, broker, nil, policyEngine)
|
||||
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, store, broker, nil)
|
||||
|
||||
contextMiddleware := middleware.NewContextMiddleware(log, runtime, authService, broker, nil)
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ func NewDefaultConfiguration() *Config {
|
||||
PrivateKeyPath: "./tinyauth_oidc_key",
|
||||
PublicKeyPath: "./tinyauth_oidc_key.pub",
|
||||
},
|
||||
Experimental: ExperimentalConfig{
|
||||
ConfigFile: "",
|
||||
},
|
||||
Tailscale: TailscaleConfig{
|
||||
Dir: "./tailscale_state",
|
||||
},
|
||||
@@ -85,7 +88,6 @@ type Config struct {
|
||||
LabelProvider string `description:"Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment." yaml:"labelProvider"`
|
||||
Log LogConfig `description:"Logging configuration." yaml:"log"`
|
||||
Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale"`
|
||||
ConfigFile string `description:"Path to config file." yaml:"-"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
@@ -154,7 +156,6 @@ type AddressClaim struct {
|
||||
type IPConfig struct {
|
||||
Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"`
|
||||
Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"`
|
||||
Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication entirely." yaml:"bypass"`
|
||||
}
|
||||
|
||||
type OAuthConfig struct {
|
||||
@@ -206,8 +207,9 @@ type LogStreamConfig struct {
|
||||
Level string `description:"Log level for this stream. Use global if empty." yaml:"level"`
|
||||
}
|
||||
|
||||
// no experimental features
|
||||
type ExperimentalConfig struct{}
|
||||
type ExperimentalConfig struct {
|
||||
ConfigFile string `description:"Path to config file." yaml:"-"`
|
||||
}
|
||||
|
||||
type TailscaleConfig struct {
|
||||
Enabled bool `description:"Enable Tailscale integration." yaml:"enabled"`
|
||||
@@ -223,8 +225,6 @@ type OAuthServiceConfig struct {
|
||||
ClientID string `description:"OAuth client ID." yaml:"clientId"`
|
||||
ClientSecret string `description:"OAuth client secret." yaml:"clientSecret"`
|
||||
ClientSecretFile string `description:"Path to the file containing the OAuth client secret." yaml:"clientSecretFile"`
|
||||
Whitelist []string `description:"Comma-separated list of allowed OAuth domains for this provider." yaml:"whitelist"`
|
||||
WhitelistFile string `description:"Path to the OAuth whitelist file for this provider." yaml:"whitelistFile"`
|
||||
Scopes []string `description:"OAuth scopes." yaml:"scopes"`
|
||||
RedirectURL string `description:"OAuth redirect URL." yaml:"redirectUrl"`
|
||||
AuthURL string `description:"OAuth authorization URL." yaml:"authUrl"`
|
||||
|
||||
@@ -16,6 +16,8 @@ type DBTX interface {
|
||||
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
// New returns a *Queries configured to use the provided DBTX for executing database operations.
|
||||
// The returned *Queries will use db as its database handle for all query method calls.
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ type Store struct {
|
||||
q *Queries
|
||||
}
|
||||
|
||||
// NewStore wraps a *Queries to satisfy repository.Store.
|
||||
// NewStore returns a repository.Store backed by the provided *Queries.
|
||||
func NewStore(q *Queries) repository.Store {
|
||||
return &Store{q: q}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ var errorMap = map[error]error{
|
||||
sql.ErrNoRows: repository.ErrNotFound,
|
||||
}
|
||||
|
||||
// mapErr maps known database errors to repository-level errors using the package-level errorMap.
|
||||
// It uses errors.Is to match (so wrapped errors are recognized) and returns the original error if no mapping applies.
|
||||
func mapErr(err error) error {
|
||||
for from, to := range errorMap {
|
||||
if errors.Is(err, from) {
|
||||
|
||||
@@ -9,12 +9,6 @@ import (
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
)
|
||||
|
||||
// For LDAP and OAuth groups and IP allow/deny, we default to allow even with a deny policy.
|
||||
// This is because we can't force the user to use groups in LDAP and OAuth if they would like to use
|
||||
// a deny policy. As for IP checks, we can't reliably get the client IP (most of Tinyauth instances are
|
||||
// behind a Docker bridge network) so to make it easier for users to use a deny policy without
|
||||
// issues with IPs we allow by default.
|
||||
|
||||
type RuleName string
|
||||
|
||||
const (
|
||||
@@ -31,11 +25,7 @@ type UserAllowedRule struct {
|
||||
}
|
||||
|
||||
func (rule *UserAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
||||
if ctx.UserContext == nil {
|
||||
return EffectDeny
|
||||
}
|
||||
|
||||
if ctx.ACLs == nil {
|
||||
if ctx.ACLs == nil || ctx.UserContext == nil {
|
||||
return EffectAbstain
|
||||
}
|
||||
|
||||
@@ -44,7 +34,7 @@ func (rule *UserAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
||||
match, err := utils.CheckFilter(ctx.ACLs.OAuth.Whitelist, ctx.UserContext.OAuth.Email)
|
||||
if err != nil {
|
||||
rule.Log.App.Warn().Err(err).Str("item", ctx.UserContext.OAuth.Email).Msg("Invalid entry in OAuth whitelist")
|
||||
return EffectDeny
|
||||
return EffectAbstain
|
||||
}
|
||||
if match {
|
||||
rule.Log.App.Debug().Str("email", ctx.UserContext.OAuth.Email).Msg("User is in OAuth whitelist, allowing access")
|
||||
@@ -58,7 +48,7 @@ func (rule *UserAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
||||
match, err := utils.CheckFilter(ctx.ACLs.Users.Block, ctx.UserContext.GetUsername())
|
||||
if err != nil {
|
||||
rule.Log.App.Warn().Err(err).Str("item", ctx.UserContext.GetUsername()).Msg("Invalid entry in users block list")
|
||||
return EffectDeny
|
||||
return EffectAbstain
|
||||
}
|
||||
if match {
|
||||
rule.Log.App.Debug().Str("username", ctx.UserContext.GetUsername()).Msg("User is in users block list, denying access")
|
||||
@@ -72,11 +62,8 @@ func (rule *UserAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
||||
match, err := utils.CheckFilter(ctx.ACLs.Users.Allow, ctx.UserContext.GetUsername())
|
||||
|
||||
if err != nil {
|
||||
if err == utils.ErrFilterEmpty {
|
||||
return EffectAbstain
|
||||
}
|
||||
rule.Log.App.Warn().Err(err).Str("item", ctx.UserContext.GetUsername()).Msg("Invalid entry in users allow list")
|
||||
return EffectDeny
|
||||
return EffectAbstain
|
||||
}
|
||||
|
||||
if match {
|
||||
@@ -93,22 +80,13 @@ type OAuthGroupRule struct {
|
||||
}
|
||||
|
||||
func (rule *OAuthGroupRule) Evaluate(ctx *ACLContext) Effect {
|
||||
if ctx.UserContext == nil {
|
||||
return EffectDeny
|
||||
}
|
||||
|
||||
if ctx.ACLs == nil {
|
||||
return EffectAllow
|
||||
if ctx.ACLs == nil || ctx.UserContext == nil {
|
||||
return EffectAbstain
|
||||
}
|
||||
|
||||
if !ctx.UserContext.IsOAuth() {
|
||||
rule.Log.App.Debug().Msg("User is not an OAuth user, skipping OAuth group check")
|
||||
return EffectAllow
|
||||
}
|
||||
|
||||
if len(ctx.ACLs.OAuth.Groups) == 0 {
|
||||
rule.Log.App.Debug().Msg("No OAuth groups specified in ACLs, allowing access")
|
||||
return EffectAllow
|
||||
return EffectAbstain
|
||||
}
|
||||
|
||||
if _, ok := model.OverrideProviders[ctx.UserContext.OAuth.ID]; ok {
|
||||
@@ -119,8 +97,7 @@ func (rule *OAuthGroupRule) Evaluate(ctx *ACLContext) Effect {
|
||||
for _, group := range ctx.UserContext.OAuth.Groups {
|
||||
match, err := utils.CheckFilter(ctx.ACLs.OAuth.Groups, strings.TrimSpace(group))
|
||||
if err != nil {
|
||||
rule.Log.App.Warn().Err(err).Str("item", group).Msg("Invalid entry in OAuth groups ACL")
|
||||
return EffectDeny
|
||||
return EffectAbstain
|
||||
}
|
||||
if match {
|
||||
rule.Log.App.Trace().Str("group", group).Str("required", ctx.ACLs.OAuth.Groups).Msg("User group matched, allowing access")
|
||||
@@ -137,29 +114,19 @@ type LDAPGroupRule struct {
|
||||
}
|
||||
|
||||
func (rule *LDAPGroupRule) Evaluate(ctx *ACLContext) Effect {
|
||||
if ctx.UserContext == nil {
|
||||
return EffectDeny
|
||||
}
|
||||
|
||||
if ctx.ACLs == nil {
|
||||
return EffectAllow
|
||||
if ctx == nil || ctx.UserContext == nil {
|
||||
return EffectAbstain
|
||||
}
|
||||
|
||||
if !ctx.UserContext.IsLDAP() {
|
||||
rule.Log.App.Debug().Msg("User is not an LDAP user, skipping LDAP group check")
|
||||
return EffectAllow
|
||||
}
|
||||
|
||||
if len(ctx.ACLs.LDAP.Groups) == 0 {
|
||||
rule.Log.App.Debug().Msg("No LDAP groups specified in ACLs, allowing access")
|
||||
return EffectAllow
|
||||
return EffectAbstain
|
||||
}
|
||||
|
||||
for _, group := range ctx.UserContext.LDAP.Groups {
|
||||
match, err := utils.CheckFilter(ctx.ACLs.LDAP.Groups, strings.TrimSpace(group))
|
||||
if err != nil {
|
||||
rule.Log.App.Warn().Err(err).Str("item", group).Msg("Invalid entry in LDAP groups ACL")
|
||||
return EffectDeny
|
||||
return EffectAbstain
|
||||
}
|
||||
if match {
|
||||
rule.Log.App.Trace().Str("group", group).Str("required", ctx.ACLs.LDAP.Groups).Msg("User group matched, allowing access")
|
||||
@@ -215,15 +182,14 @@ type IPAllowedRule struct {
|
||||
}
|
||||
|
||||
func (rule *IPAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
||||
// merge global and per-app block/allow lists
|
||||
blockedIps := append([]string{}, rule.Config.Auth.IP.Block...)
|
||||
allowedIPs := append([]string{}, rule.Config.Auth.IP.Allow...)
|
||||
|
||||
if ctx.ACLs != nil {
|
||||
blockedIps = append(blockedIps, ctx.ACLs.IP.Block...)
|
||||
allowedIPs = append(allowedIPs, ctx.ACLs.IP.Allow...)
|
||||
if ctx.ACLs == nil {
|
||||
return EffectAbstain
|
||||
}
|
||||
|
||||
// Merge the global and app IP filter
|
||||
blockedIps := append(ctx.ACLs.IP.Block, rule.Config.Auth.IP.Block...)
|
||||
allowedIPs := append(ctx.ACLs.IP.Allow, rule.Config.Auth.IP.Allow...)
|
||||
|
||||
for _, blocked := range blockedIps {
|
||||
match, err := utils.CheckIPFilter(blocked, ctx.IP.String())
|
||||
if err != nil {
|
||||
@@ -259,17 +225,14 @@ func (rule *IPAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
||||
|
||||
type IPBypassedRule struct {
|
||||
Log *logger.Logger
|
||||
Config model.Config
|
||||
}
|
||||
|
||||
func (rule *IPBypassedRule) Evaluate(ctx *ACLContext) Effect {
|
||||
// merge global and per-app bypass lists
|
||||
bypassList := append([]string{}, rule.Config.Auth.IP.Bypass...)
|
||||
if ctx.ACLs != nil {
|
||||
bypassList = append(bypassList, ctx.ACLs.IP.Bypass...)
|
||||
if ctx.ACLs == nil {
|
||||
return EffectDeny
|
||||
}
|
||||
|
||||
for _, bypassed := range bypassList {
|
||||
for _, bypassed := range ctx.ACLs.IP.Bypass {
|
||||
match, err := utils.CheckIPFilter(bypassed, ctx.IP.String())
|
||||
if err != nil {
|
||||
rule.Log.App.Warn().Err(err).Str("item", bypassed).Msg("Invalid IP/CIDR in bypass list")
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
)
|
||||
@@ -21,16 +20,6 @@ func TestUserAllowedRule(t *testing.T) {
|
||||
ctx *ACLContext
|
||||
expected Effect
|
||||
}{
|
||||
{
|
||||
name: "denies when user context is nil",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
OAuth: model.AppOAuth{Whitelist: "alice"},
|
||||
},
|
||||
UserContext: nil,
|
||||
},
|
||||
expected: EffectDeny,
|
||||
},
|
||||
{
|
||||
name: "abstains when ACLs are nil",
|
||||
ctx: &ACLContext{
|
||||
@@ -44,6 +33,16 @@ func TestUserAllowedRule(t *testing.T) {
|
||||
},
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
{
|
||||
name: "abstains when user context is nil",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
OAuth: model.AppOAuth{Whitelist: "alice"},
|
||||
},
|
||||
UserContext: nil,
|
||||
},
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
{
|
||||
name: "allows OAuth user when email matches whitelist",
|
||||
ctx: &ACLContext{
|
||||
@@ -78,7 +77,7 @@ func TestUserAllowedRule(t *testing.T) {
|
||||
expected: EffectDeny,
|
||||
},
|
||||
{
|
||||
name: "denies for OAuth user when whitelist filter is invalid",
|
||||
name: "abstains for OAuth user when whitelist filter is invalid",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
OAuth: model.AppOAuth{Whitelist: "/[/"},
|
||||
@@ -90,7 +89,7 @@ func TestUserAllowedRule(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: EffectDeny,
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
{
|
||||
name: "denies local user when username matches block list",
|
||||
@@ -123,7 +122,7 @@ func TestUserAllowedRule(t *testing.T) {
|
||||
expected: EffectAllow,
|
||||
},
|
||||
{
|
||||
name: "denies when block list filter is invalid",
|
||||
name: "abstains when block list filter is invalid",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
Users: model.AppUsers{Block: "/[/"},
|
||||
@@ -135,21 +134,6 @@ func TestUserAllowedRule(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: EffectDeny,
|
||||
},
|
||||
{
|
||||
name: "abstains when allow list is empty",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
Users: model.AppUsers{Allow: ""},
|
||||
},
|
||||
UserContext: &model.UserContext{
|
||||
Provider: model.ProviderLocal,
|
||||
Local: &model.LocalContext{
|
||||
BaseContext: model.BaseContext{Username: "alice"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
{
|
||||
@@ -183,7 +167,7 @@ func TestUserAllowedRule(t *testing.T) {
|
||||
expected: EffectDeny,
|
||||
},
|
||||
{
|
||||
name: "denies when allow list filter is invalid",
|
||||
name: "abstains when allow list filter is invalid",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
Users: model.AppUsers{Allow: "/[/"},
|
||||
@@ -195,7 +179,7 @@ func TestUserAllowedRule(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: EffectDeny,
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -218,17 +202,7 @@ func TestOAuthGroupRule(t *testing.T) {
|
||||
expected Effect
|
||||
}{
|
||||
{
|
||||
name: "denies when user context is nil",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
OAuth: model.AppOAuth{Whitelist: "alice"},
|
||||
},
|
||||
UserContext: nil,
|
||||
},
|
||||
expected: EffectDeny,
|
||||
},
|
||||
{
|
||||
name: "allows when ACLs are nil",
|
||||
name: "abstains when ACLs are nil",
|
||||
ctx: &ACLContext{
|
||||
ACLs: nil,
|
||||
UserContext: &model.UserContext{
|
||||
@@ -238,10 +212,20 @@ func TestOAuthGroupRule(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: EffectAllow,
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
{
|
||||
name: "allows when user is not OAuth",
|
||||
name: "abstains when user context is nil",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
OAuth: model.AppOAuth{Whitelist: "alice"},
|
||||
},
|
||||
UserContext: nil,
|
||||
},
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
{
|
||||
name: "abstains when user is not OAuth",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
OAuth: model.AppOAuth{Groups: "admins"},
|
||||
@@ -253,22 +237,7 @@ func TestOAuthGroupRule(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: EffectAllow,
|
||||
},
|
||||
{
|
||||
name: "allows when group filter is empty",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
OAuth: model.AppOAuth{Groups: ""},
|
||||
},
|
||||
UserContext: &model.UserContext{
|
||||
Provider: model.ProviderOAuth,
|
||||
OAuth: &model.OAuthContext{
|
||||
BaseContext: model.BaseContext{Username: "alice"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: EffectAllow,
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
{
|
||||
name: "allows when provider is an override provider regardless of groups",
|
||||
@@ -335,7 +304,7 @@ func TestOAuthGroupRule(t *testing.T) {
|
||||
expected: EffectDeny,
|
||||
},
|
||||
{
|
||||
name: "denies when groups filter is invalid",
|
||||
name: "abstains when groups filter is invalid",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
OAuth: model.AppOAuth{Groups: "/[/"},
|
||||
@@ -348,7 +317,7 @@ func TestOAuthGroupRule(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: EffectDeny,
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -371,30 +340,22 @@ func TestLDAPGroupRule(t *testing.T) {
|
||||
expected Effect
|
||||
}{
|
||||
{
|
||||
name: "denies when user context is nil",
|
||||
name: "abstains when context is nil",
|
||||
ctx: nil,
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
{
|
||||
name: "abstains when user context is nil",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
OAuth: model.AppOAuth{Whitelist: "alice"},
|
||||
},
|
||||
UserContext: nil,
|
||||
},
|
||||
expected: EffectDeny,
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
{
|
||||
name: "allows when acls are nil",
|
||||
ctx: &ACLContext{
|
||||
ACLs: nil,
|
||||
UserContext: &model.UserContext{
|
||||
Provider: model.ProviderLocal,
|
||||
Local: &model.LocalContext{
|
||||
BaseContext: model.BaseContext{Username: "alice"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: EffectAllow,
|
||||
},
|
||||
{
|
||||
name: "allows when user is not LDAP",
|
||||
name: "abstains when user is not LDAP",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
LDAP: model.AppLDAP{Groups: "admins"},
|
||||
@@ -406,22 +367,7 @@ func TestLDAPGroupRule(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: EffectAllow,
|
||||
},
|
||||
{
|
||||
name: "allows when group filter is empty",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
LDAP: model.AppLDAP{Groups: ""},
|
||||
},
|
||||
UserContext: &model.UserContext{
|
||||
Provider: model.ProviderLDAP,
|
||||
LDAP: &model.LDAPContext{
|
||||
BaseContext: model.BaseContext{Username: "alice"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: EffectAllow,
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
{
|
||||
name: "allows LDAP user when a group matches",
|
||||
@@ -469,7 +415,7 @@ func TestLDAPGroupRule(t *testing.T) {
|
||||
expected: EffectDeny,
|
||||
},
|
||||
{
|
||||
name: "denies when groups filter is invalid",
|
||||
name: "abstains when groups filter is invalid",
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
LDAP: model.AppLDAP{Groups: "/[/"},
|
||||
@@ -481,7 +427,7 @@ func TestLDAPGroupRule(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: EffectDeny,
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -612,12 +558,12 @@ func TestIPAllowedRule(t *testing.T) {
|
||||
expected Effect
|
||||
}{
|
||||
{
|
||||
name: "allows when ACLs are nil and no global lists configured",
|
||||
name: "abstains when ACLs are nil",
|
||||
ctx: &ACLContext{
|
||||
ACLs: nil,
|
||||
IP: net.ParseIP("10.0.0.1"),
|
||||
},
|
||||
expected: EffectAllow,
|
||||
expected: EffectAbstain,
|
||||
},
|
||||
{
|
||||
name: "denies when IP matches app block list",
|
||||
@@ -723,70 +669,23 @@ func TestIPBypassedRule(t *testing.T) {
|
||||
log := logger.NewLogger().WithTestConfig()
|
||||
log.Init()
|
||||
|
||||
defaultIPBR := &IPBypassedRule{Log: log}
|
||||
globBypassIPBR := &IPBypassedRule{
|
||||
Log: log,
|
||||
Config: model.Config{Auth: model.AuthConfig{IP: model.IPConfig{Bypass: []string{"10.0.0.0/24"}}}},
|
||||
}
|
||||
rule := &IPBypassedRule{Log: log}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rule *IPBypassedRule
|
||||
ctx *ACLContext
|
||||
expected Effect
|
||||
}{
|
||||
{
|
||||
name: "deny when ACLs are nil and no global bypass",
|
||||
rule: defaultIPBR,
|
||||
name: "deny when ACLs are nil",
|
||||
ctx: &ACLContext{
|
||||
ACLs: nil,
|
||||
IP: net.ParseIP("10.0.0.1"),
|
||||
},
|
||||
expected: EffectDeny,
|
||||
},
|
||||
{
|
||||
name: "allows when ACLs are nil but IP matches global bypass",
|
||||
rule: globBypassIPBR,
|
||||
ctx: &ACLContext{
|
||||
ACLs: nil,
|
||||
IP: net.ParseIP("10.0.0.5"),
|
||||
},
|
||||
expected: EffectAllow,
|
||||
},
|
||||
{
|
||||
name: "denies when ACLs are nil and IP does not match global bypass",
|
||||
rule: globBypassIPBR,
|
||||
ctx: &ACLContext{
|
||||
ACLs: nil,
|
||||
IP: net.ParseIP("192.168.1.1"),
|
||||
},
|
||||
expected: EffectDeny,
|
||||
},
|
||||
{
|
||||
name: "allows when IP matches per-app bypass but not global bypass",
|
||||
rule: defaultIPBR,
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
IP: model.AppIP{Bypass: []string{"10.0.0.0/24"}},
|
||||
},
|
||||
IP: net.ParseIP("10.0.0.5"),
|
||||
},
|
||||
expected: EffectAllow,
|
||||
},
|
||||
{
|
||||
name: "allows when IP matches global bypass but not per-app bypass",
|
||||
rule: globBypassIPBR,
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
IP: model.AppIP{Bypass: []string{"172.16.0.0/24"}},
|
||||
},
|
||||
IP: net.ParseIP("10.0.0.5"),
|
||||
},
|
||||
expected: EffectAllow,
|
||||
},
|
||||
{
|
||||
name: "allows when IP matches bypass list",
|
||||
rule: defaultIPBR,
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
IP: model.AppIP{Bypass: []string{"10.0.0.0/24"}},
|
||||
@@ -797,7 +696,6 @@ func TestIPBypassedRule(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "denies when IP does not match bypass list",
|
||||
rule: defaultIPBR,
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
IP: model.AppIP{Bypass: []string{"10.0.0.0/24"}},
|
||||
@@ -808,7 +706,6 @@ func TestIPBypassedRule(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "denies when bypass list is empty",
|
||||
rule: defaultIPBR,
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{},
|
||||
IP: net.ParseIP("10.0.0.1"),
|
||||
@@ -817,7 +714,6 @@ func TestIPBypassedRule(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "skips invalid bypass entries and allows on later match",
|
||||
rule: defaultIPBR,
|
||||
ctx: &ACLContext{
|
||||
ACLs: &model.App{
|
||||
IP: model.AppIP{Bypass: []string{"not-an-ip", "10.0.0.1"}},
|
||||
@@ -830,7 +726,7 @@ func TestIPBypassedRule(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, tt.rule.Evaluate(tt.ctx))
|
||||
assert.Equal(t, tt.expected, rule.Evaluate(tt.ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
@@ -80,7 +79,6 @@ type AuthService struct {
|
||||
queries repository.Store
|
||||
oauthBroker *OAuthBrokerService
|
||||
tailscale *TailscaleService
|
||||
policyEngine *PolicyEngine
|
||||
|
||||
loginAttempts map[string]*LoginAttempt
|
||||
ldapGroupsCache map[string]*LdapGroupsCache
|
||||
@@ -98,12 +96,11 @@ func NewAuthService(
|
||||
config model.Config,
|
||||
runtime model.RuntimeConfig,
|
||||
ctx context.Context,
|
||||
dg *ding.Ding,
|
||||
wg *sync.WaitGroup,
|
||||
ldap *LdapService,
|
||||
queries repository.Store,
|
||||
oauthBroker *OAuthBrokerService,
|
||||
tailscale *TailscaleService,
|
||||
policy *PolicyEngine,
|
||||
) *AuthService {
|
||||
service := &AuthService{
|
||||
log: log,
|
||||
@@ -117,10 +114,9 @@ func NewAuthService(
|
||||
queries: queries,
|
||||
oauthBroker: oauthBroker,
|
||||
tailscale: tailscale,
|
||||
policyEngine: policy,
|
||||
}
|
||||
|
||||
dg.Go(service.cleanupOAuthSessions, ding.RingMinor)
|
||||
wg.Go(service.CleanupOAuthSessionsRoutine)
|
||||
|
||||
return service
|
||||
}
|
||||
@@ -289,27 +285,13 @@ func (auth *AuthService) RecordLoginAttempt(identifier string, success bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// We could also directly access the policyEngine.effectToAccess but
|
||||
// I believe it's better to use the exported functions instead
|
||||
func (auth *AuthService) IsEmailWhitelisted(provider string, email string) bool {
|
||||
return auth.policyEngine.EvaluateFunc(func() Effect {
|
||||
whitelist := auth.runtime.OAuthWhitelist
|
||||
if providerConfig, ok := auth.runtime.OAuthProviders[provider]; ok && len(providerConfig.Whitelist) > 0 {
|
||||
whitelist = providerConfig.Whitelist
|
||||
}
|
||||
match, err := utils.CheckFilter(strings.Join(whitelist, ","), email)
|
||||
func (auth *AuthService) IsEmailWhitelisted(email string) bool {
|
||||
match, err := utils.CheckFilter(strings.Join(auth.runtime.OAuthWhitelist, ","), email)
|
||||
if err != nil {
|
||||
if err == utils.ErrFilterEmpty {
|
||||
return EffectAbstain
|
||||
auth.log.App.Warn().Err(err).Str("email", email).Msg("Invalid email filter pattern")
|
||||
return false
|
||||
}
|
||||
auth.log.App.Error().Err(err).Str("email", email).Msg("Failed to evaluate email whitelist filter, defaulting to deny")
|
||||
return EffectDeny
|
||||
}
|
||||
if match {
|
||||
return EffectAllow
|
||||
}
|
||||
return EffectDeny
|
||||
})
|
||||
return match
|
||||
}
|
||||
|
||||
func (auth *AuthService) CreateSession(ctx context.Context, data repository.Session) (*http.Cookie, error) {
|
||||
@@ -602,7 +584,7 @@ func (auth *AuthService) EndOAuthSession(sessionId string) {
|
||||
auth.oauthMutex.Unlock()
|
||||
}
|
||||
|
||||
func (auth *AuthService) cleanupOAuthSessions(ctx context.Context) {
|
||||
func (auth *AuthService) CleanupOAuthSessionsRoutine() {
|
||||
auth.log.App.Debug().Msg("Starting OAuth session cleanup routine")
|
||||
|
||||
ticker := time.NewTicker(30 * time.Minute)
|
||||
@@ -625,7 +607,7 @@ func (auth *AuthService) cleanupOAuthSessions(ctx context.Context) {
|
||||
|
||||
auth.oauthMutex.Unlock()
|
||||
auth.log.App.Debug().Msg("OAuth session cleanup completed")
|
||||
case <-ctx.Done():
|
||||
case <-auth.context.Done():
|
||||
auth.log.App.Debug().Msg("Stopping OAuth session cleanup routine")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
)
|
||||
|
||||
func TestIsEmailWhitelistedUsesProviderSpecificList(t *testing.T) {
|
||||
log := logger.NewLogger().WithTestConfig()
|
||||
log.Init()
|
||||
|
||||
auth := &AuthService{
|
||||
log: log,
|
||||
runtime: model.RuntimeConfig{
|
||||
OAuthWhitelist: []string{"global@example.com"},
|
||||
OAuthProviders: map[string]model.OAuthServiceConfig{
|
||||
"github": {
|
||||
Whitelist: []string{"github@example.com"},
|
||||
},
|
||||
"pocketid": {
|
||||
Whitelist: []string{"pocket@example.com"},
|
||||
},
|
||||
"gitlab": {
|
||||
Whitelist: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.True(t, auth.IsEmailWhitelisted("github", "github@example.com"))
|
||||
assert.False(t, auth.IsEmailWhitelisted("github", "pocket@example.com"))
|
||||
assert.True(t, auth.IsEmailWhitelisted("pocketid", "pocket@example.com"))
|
||||
assert.True(t, auth.IsEmailWhitelisted("google", "global@example.com"))
|
||||
assert.True(t, auth.IsEmailWhitelisted("gitlab", "global@example.com"))
|
||||
assert.False(t, auth.IsEmailWhitelisted("gitlab", "unknown@example.com"))
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
@@ -24,7 +24,7 @@ type DockerService struct {
|
||||
func NewDockerService(
|
||||
log *logger.Logger,
|
||||
ctx context.Context,
|
||||
dg *ding.Ding,
|
||||
wg *sync.WaitGroup,
|
||||
) (*DockerService, error) {
|
||||
|
||||
client, err := client.NewClientWithOpts(client.FromEnv)
|
||||
@@ -50,7 +50,7 @@ func NewDockerService(
|
||||
service.isConnected = true
|
||||
service.log.App.Debug().Msg("Docker connected successfully")
|
||||
|
||||
dg.Go(service.watchAndClose, ding.RingMajor)
|
||||
wg.Go(service.watchAndClose)
|
||||
|
||||
return service, nil
|
||||
}
|
||||
@@ -108,8 +108,8 @@ func (docker *DockerService) GetLabels(appDomain string) (*model.App, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (docker *DockerService) watchAndClose(ctx context.Context) {
|
||||
<-ctx.Done()
|
||||
func (docker *DockerService) watchAndClose() {
|
||||
<-docker.context.Done()
|
||||
docker.log.App.Debug().Msg("Closing Docker client")
|
||||
if docker.client != nil {
|
||||
err := docker.client.Close()
|
||||
|
||||
@@ -3,12 +3,10 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
@@ -39,6 +37,7 @@ type ingressApp struct {
|
||||
|
||||
type KubernetesService struct {
|
||||
log *logger.Logger
|
||||
ctx context.Context
|
||||
|
||||
client dynamic.Interface
|
||||
started bool
|
||||
@@ -51,7 +50,7 @@ type KubernetesService struct {
|
||||
func NewKubernetesService(
|
||||
log *logger.Logger,
|
||||
ctx context.Context,
|
||||
dg *ding.Ding,
|
||||
wg *sync.WaitGroup,
|
||||
) (*KubernetesService, error) {
|
||||
cfg, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
@@ -82,15 +81,16 @@ func NewKubernetesService(
|
||||
|
||||
service := &KubernetesService{
|
||||
log: log,
|
||||
ctx: ctx,
|
||||
client: client,
|
||||
ingressApps: make(map[ingressKey][]ingressApp),
|
||||
domainIndex: make(map[string]ingressAppKey),
|
||||
appNameIndex: make(map[string]ingressAppKey),
|
||||
}
|
||||
|
||||
dg.Go(func(ctx context.Context) {
|
||||
service.watchGVR(gvr, ctx)
|
||||
}, ding.RingMajor)
|
||||
wg.Go(func() {
|
||||
service.watchGVR(gvr)
|
||||
})
|
||||
|
||||
service.started = true
|
||||
log.App.Debug().Msg("Kubernetes label provider started successfully")
|
||||
@@ -167,68 +167,6 @@ func (k *KubernetesService) getByAppName(appName string) *model.App {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *KubernetesService) extractPaths(rule map[string]any) ([]string, error) {
|
||||
http, found, err := unstructured.NestedMap(rule, "http")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading http from rule: %w", err)
|
||||
}
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
paths, found, err := unstructured.NestedSlice(http, "paths")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading http.paths: %w", err)
|
||||
}
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
var result []string
|
||||
for _, p := range paths {
|
||||
path, ok := p.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if p, ok := path["path"].(string); ok && p != "" {
|
||||
result = append(result, p)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (k *KubernetesService) extractHosts(item *unstructured.Unstructured) ([]string, error) {
|
||||
rules, found, err := unstructured.NestedSlice(item.Object, "spec", "rules")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading spec.rules: %w", err)
|
||||
}
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
var hosts []string
|
||||
for _, r := range rules {
|
||||
rule, ok := r.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if host, ok := rule["host"].(string); ok && host != "" {
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
paths, err := k.extractPaths(rule)
|
||||
if err != nil {
|
||||
// This is purely to warn users, it doesn't affect our ability to extract hosts so we won't fail the whole operation
|
||||
k.log.App.Warn().Err(err).Str("namespace", item.GetNamespace()).Str("name", item.GetName()).Msg("Failed to extract paths from ingress rule")
|
||||
continue
|
||||
}
|
||||
if len(paths) == 0 {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(paths, "/") {
|
||||
k.log.App.Warn().Str("namespace", item.GetNamespace()).Str("name", item.GetName()).Strs("paths", paths).Msg("Ingress rule does not contain a catch-all path, another ingress may be able to bypass auth checks if it routes the same host with a different path. Consider adding a catch-all path to this rule to ensure auth checks are applied to all paths for this host.")
|
||||
}
|
||||
}
|
||||
k.log.App.Trace().Strs("hosts", hosts).Msg("Extracted hosts from ingress rules")
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
func (k *KubernetesService) updateFromItem(item *unstructured.Unstructured) {
|
||||
namespace := item.GetNamespace()
|
||||
name := item.GetName()
|
||||
@@ -237,11 +175,6 @@ func (k *KubernetesService) updateFromItem(item *unstructured.Unstructured) {
|
||||
k.removeIngress(namespace, name)
|
||||
return
|
||||
}
|
||||
hosts, err := k.extractHosts(item)
|
||||
if err != nil {
|
||||
k.removeIngress(namespace, name)
|
||||
return
|
||||
}
|
||||
labels, err := decoders.DecodeLabels[model.Apps](annotations, "apps")
|
||||
if err != nil {
|
||||
k.log.App.Warn().Err(err).Str("namespace", namespace).Str("name", name).Msg("Failed to decode ingress labels, skipping")
|
||||
@@ -253,10 +186,6 @@ func (k *KubernetesService) updateFromItem(item *unstructured.Unstructured) {
|
||||
if appLabels.Config.Domain == "" {
|
||||
continue
|
||||
}
|
||||
if len(hosts) > 0 && !slices.Contains(hosts, appLabels.Config.Domain) {
|
||||
k.log.App.Warn().Str("namespace", namespace).Str("name", name).Str("appName", appName).Str("domain", appLabels.Config.Domain).Msg("App domain does not match any hosts defined in ingress rules, skipping")
|
||||
continue
|
||||
}
|
||||
apps = append(apps, ingressApp{
|
||||
domain: appLabels.Config.Domain,
|
||||
appName: appName,
|
||||
@@ -270,8 +199,8 @@ func (k *KubernetesService) updateFromItem(item *unstructured.Unstructured) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *KubernetesService) resyncGVR(gvr schema.GroupVersionResource, ctx context.Context) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
func (k *KubernetesService) resyncGVR(gvr schema.GroupVersionResource) error {
|
||||
ctx, cancel := context.WithTimeout(k.ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
list, err := k.client.Resource(gvr).List(ctx, metav1.ListOptions{})
|
||||
@@ -288,10 +217,10 @@ func (k *KubernetesService) resyncGVR(gvr schema.GroupVersionResource, ctx conte
|
||||
|
||||
// runWatcher drains events from an active watcher until it closes or the context is done.
|
||||
// Returns true if the caller should restart the watcher, false if it should exit.
|
||||
func (k *KubernetesService) runWatcher(gvr schema.GroupVersionResource, w watch.Interface, resyncTicker *time.Ticker, ctx context.Context) bool {
|
||||
func (k *KubernetesService) runWatcher(gvr schema.GroupVersionResource, w watch.Interface, resyncTicker *time.Ticker) bool {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-k.ctx.Done():
|
||||
w.Stop()
|
||||
return false
|
||||
case event, ok := <-w.ResultChan():
|
||||
@@ -313,33 +242,33 @@ func (k *KubernetesService) runWatcher(gvr schema.GroupVersionResource, w watch.
|
||||
k.removeIngress(item.GetNamespace(), item.GetName())
|
||||
}
|
||||
case <-resyncTicker.C:
|
||||
if err := k.resyncGVR(gvr, ctx); err != nil {
|
||||
if err := k.resyncGVR(gvr); err != nil {
|
||||
k.log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Periodic resync failed during watcher run")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (k *KubernetesService) watchGVR(gvr schema.GroupVersionResource, ctx context.Context) {
|
||||
func (k *KubernetesService) watchGVR(gvr schema.GroupVersionResource) {
|
||||
resyncTicker := time.NewTicker(5 * time.Minute)
|
||||
defer resyncTicker.Stop()
|
||||
|
||||
if err := k.resyncGVR(gvr, ctx); err != nil {
|
||||
if err := k.resyncGVR(gvr); err != nil {
|
||||
k.log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Initial resync failed, will retry")
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-k.ctx.Done():
|
||||
k.log.App.Debug().Str("api", gvr.GroupVersion().String()).Msg("Shutting down kubernetes watcher")
|
||||
return
|
||||
case <-resyncTicker.C:
|
||||
if err := k.resyncGVR(gvr, ctx); err != nil {
|
||||
if err := k.resyncGVR(gvr); err != nil {
|
||||
k.log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Periodic resync failed, will retry")
|
||||
}
|
||||
default:
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
ctx, cancel := context.WithCancel(k.ctx)
|
||||
watcher, err := k.client.Resource(gvr).Watch(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
k.log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Failed to start watcher, will retry")
|
||||
@@ -348,7 +277,7 @@ func (k *KubernetesService) watchGVR(gvr schema.GroupVersionResource, ctx contex
|
||||
continue
|
||||
}
|
||||
k.log.App.Debug().Str("api", gvr.GroupVersion().String()).Msg("Watcher started successfully")
|
||||
if !k.runWatcher(gvr, watcher, resyncTicker, ctx) {
|
||||
if !k.runWatcher(gvr, watcher, resyncTicker) {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/cenkalti/backoff/v5"
|
||||
ldapgo "github.com/go-ldap/ldap/v3"
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
)
|
||||
@@ -17,6 +16,7 @@ import (
|
||||
type LdapService struct {
|
||||
log *logger.Logger
|
||||
config model.Config
|
||||
context context.Context
|
||||
|
||||
conn *ldapgo.Conn
|
||||
mutex sync.RWMutex
|
||||
@@ -26,7 +26,8 @@ type LdapService struct {
|
||||
func NewLdapService(
|
||||
log *logger.Logger,
|
||||
config model.Config,
|
||||
dg *ding.Ding,
|
||||
ctx context.Context,
|
||||
wg *sync.WaitGroup,
|
||||
) (*LdapService, error) {
|
||||
if config.LDAP.Address == "" {
|
||||
return nil, nil
|
||||
@@ -35,6 +36,7 @@ func NewLdapService(
|
||||
ldap := &LdapService{
|
||||
log: log,
|
||||
config: config,
|
||||
context: ctx,
|
||||
}
|
||||
|
||||
// Check whether authentication with client certificate is possible
|
||||
@@ -67,7 +69,7 @@ func NewLdapService(
|
||||
return nil, fmt.Errorf("failed to connect to ldap server: %w", err)
|
||||
}
|
||||
|
||||
dg.Go(func(ctx context.Context) {
|
||||
wg.Go(func() {
|
||||
ldap.log.App.Debug().Msg("Starting LDAP connection heartbeat routine")
|
||||
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
@@ -85,12 +87,12 @@ func NewLdapService(
|
||||
}
|
||||
ldap.log.App.Info().Msg("Successfully reconnected to LDAP server")
|
||||
}
|
||||
case <-ctx.Done():
|
||||
case <-ldap.context.Done():
|
||||
ldap.log.App.Debug().Msg("LDAP service context cancelled, stopping heartbeat")
|
||||
return
|
||||
}
|
||||
}
|
||||
}, ding.RingMajor)
|
||||
})
|
||||
|
||||
return ldap, nil
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"slices"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
@@ -116,6 +116,7 @@ type OIDCService struct {
|
||||
config model.Config
|
||||
runtime model.RuntimeConfig
|
||||
queries repository.Store
|
||||
context context.Context
|
||||
|
||||
clients map[string]model.OIDCClientConfig
|
||||
privateKey *rsa.PrivateKey
|
||||
@@ -128,7 +129,8 @@ func NewOIDCService(
|
||||
config model.Config,
|
||||
runtime model.RuntimeConfig,
|
||||
queries repository.Store,
|
||||
dg *ding.Ding) (*OIDCService, error) {
|
||||
ctx context.Context,
|
||||
wg *sync.WaitGroup) (*OIDCService, error) {
|
||||
// If not configured, skip init
|
||||
if len(runtime.OIDCClients) == 0 {
|
||||
return nil, nil
|
||||
@@ -274,6 +276,7 @@ func NewOIDCService(
|
||||
config: config,
|
||||
runtime: runtime,
|
||||
queries: queries,
|
||||
context: ctx,
|
||||
|
||||
clients: clients,
|
||||
privateKey: privateKey,
|
||||
@@ -282,7 +285,7 @@ func NewOIDCService(
|
||||
}
|
||||
|
||||
// Start cleanup routine
|
||||
dg.Go(service.cleanupRoutine, ding.RingMinor)
|
||||
wg.Go(service.cleanupRoutine)
|
||||
|
||||
return service, nil
|
||||
}
|
||||
@@ -756,7 +759,7 @@ func (service *OIDCService) DeleteOldSession(ctx context.Context, sub string) er
|
||||
}
|
||||
|
||||
// Cleanup routine - Resource heavy due to the linked tables
|
||||
func (service *OIDCService) cleanupRoutine(ctx context.Context) {
|
||||
func (service *OIDCService) cleanupRoutine() {
|
||||
service.log.App.Debug().Msg("Starting OIDC cleanup routine")
|
||||
ticker := time.NewTicker(time.Duration(30) * time.Minute)
|
||||
defer ticker.Stop()
|
||||
@@ -769,7 +772,7 @@ func (service *OIDCService) cleanupRoutine(ctx context.Context) {
|
||||
currentTime := time.Now().Unix()
|
||||
|
||||
// For the OIDC tokens, if they are expired we delete the userinfo and codes
|
||||
expiredTokens, err := service.queries.DeleteExpiredOidcTokens(ctx, repository.DeleteExpiredOidcTokensParams{
|
||||
expiredTokens, err := service.queries.DeleteExpiredOidcTokens(service.context, repository.DeleteExpiredOidcTokensParams{
|
||||
TokenExpiresAt: currentTime,
|
||||
RefreshTokenExpiresAt: currentTime,
|
||||
})
|
||||
@@ -779,21 +782,21 @@ func (service *OIDCService) cleanupRoutine(ctx context.Context) {
|
||||
}
|
||||
|
||||
for _, expiredToken := range expiredTokens {
|
||||
err := service.DeleteOldSession(ctx, expiredToken.Sub)
|
||||
err := service.DeleteOldSession(service.context, expiredToken.Sub)
|
||||
if err != nil {
|
||||
service.log.App.Warn().Err(err).Msg("Failed to delete session for expired token")
|
||||
}
|
||||
}
|
||||
|
||||
// For expired codes, we need to get the sub, check if tokens are expired and if they are remove everything
|
||||
expiredCodes, err := service.queries.DeleteExpiredOidcCodes(ctx, currentTime)
|
||||
expiredCodes, err := service.queries.DeleteExpiredOidcCodes(service.context, currentTime)
|
||||
|
||||
if err != nil {
|
||||
service.log.App.Warn().Err(err).Msg("Failed to delete expired codes")
|
||||
}
|
||||
|
||||
for _, expiredCode := range expiredCodes {
|
||||
token, err := service.queries.GetOidcTokenBySub(ctx, expiredCode.Sub)
|
||||
token, err := service.queries.GetOidcTokenBySub(service.context, expiredCode.Sub)
|
||||
|
||||
if err != nil {
|
||||
if !errors.Is(err, repository.ErrNotFound) {
|
||||
@@ -803,7 +806,7 @@ func (service *OIDCService) cleanupRoutine(ctx context.Context) {
|
||||
}
|
||||
|
||||
if token.TokenExpiresAt < currentTime && token.RefreshTokenExpiresAt < currentTime {
|
||||
err := service.DeleteOldSession(ctx, expiredCode.Sub)
|
||||
err := service.DeleteOldSession(service.context, expiredCode.Sub)
|
||||
if err != nil {
|
||||
service.log.App.Warn().Err(err).Msg("Failed to delete session for expired code")
|
||||
}
|
||||
@@ -811,7 +814,7 @@ func (service *OIDCService) cleanupRoutine(ctx context.Context) {
|
||||
}
|
||||
|
||||
service.log.App.Debug().Msg("Finished OIDC cleanup routine")
|
||||
case <-ctx.Done():
|
||||
case <-service.context.Done():
|
||||
service.log.App.Debug().Msg("Stopping OIDC cleanup routine")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ package service_test
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -70,9 +70,9 @@ func TestCompileUserinfo(t *testing.T) {
|
||||
log.Init()
|
||||
|
||||
ctx := context.TODO()
|
||||
dg := ding.New(ctx)
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
svc, err := service.NewOIDCService(log, cfg, runtime, nil, dg)
|
||||
svc, err := service.NewOIDCService(log, cfg, runtime, nil, ctx, wg)
|
||||
require.NoError(t, err)
|
||||
|
||||
type testCase struct {
|
||||
|
||||
@@ -108,7 +108,3 @@ func (engine *PolicyEngine) Policy() Policy {
|
||||
func (engine *PolicyEngine) Rules() map[RuleName]Rule {
|
||||
return engine.rules
|
||||
}
|
||||
|
||||
func (engine *PolicyEngine) EvaluateFunc(f func() Effect) bool {
|
||||
return engine.effectToAccess(f())
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
"tailscale.com/client/local"
|
||||
@@ -26,6 +25,7 @@ type TailscaleWhoisResponse struct {
|
||||
|
||||
type TailscaleService struct {
|
||||
log *logger.Logger
|
||||
wg *sync.WaitGroup
|
||||
config model.Config
|
||||
ctx context.Context
|
||||
|
||||
@@ -35,7 +35,7 @@ type TailscaleService struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewTailscaleService(log *logger.Logger, config model.Config, ctx context.Context, dg *ding.Ding) (*TailscaleService, error) {
|
||||
func NewTailscaleService(log *logger.Logger, config model.Config, ctx context.Context, wg *sync.WaitGroup) (*TailscaleService, error) {
|
||||
if !config.Tailscale.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -67,6 +67,7 @@ func NewTailscaleService(log *logger.Logger, config model.Config, ctx context.Co
|
||||
|
||||
service := &TailscaleService{
|
||||
log: log,
|
||||
wg: wg,
|
||||
config: config,
|
||||
ctx: ctx,
|
||||
srv: srv,
|
||||
@@ -83,13 +84,13 @@ func NewTailscaleService(log *logger.Logger, config model.Config, ctx context.Co
|
||||
return nil, fmt.Errorf("failed to connect to tailscale network: %w", err)
|
||||
}
|
||||
|
||||
dg.Go(service.watchAndClose, ding.RingMajor)
|
||||
wg.Go(service.watchAndClose)
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (ts *TailscaleService) watchAndClose(ctx context.Context) {
|
||||
<-ctx.Done()
|
||||
func (ts *TailscaleService) watchAndClose() {
|
||||
<-ts.ctx.Done()
|
||||
ts.log.App.Debug().Msg("Shutting down Tailscale service")
|
||||
ts.mu.Lock()
|
||||
srv := ts.srv
|
||||
|
||||
@@ -3,6 +3,7 @@ package loaders
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/tinyauthapp/paerser/cli"
|
||||
"github.com/tinyauthapp/paerser/file"
|
||||
"github.com/tinyauthapp/paerser/flag"
|
||||
@@ -18,8 +19,8 @@ func (f *FileLoader) Load(args []string, cmd *cli.Command) (bool, error) {
|
||||
}
|
||||
|
||||
// I guess we are using traefik as the root name (we can't change it)
|
||||
configFileFlag := "traefik.configfile"
|
||||
envVar := "TINYAUTH_CONFIGFILE"
|
||||
configFileFlag := "traefik.experimental.configfile"
|
||||
envVar := "TINYAUTH_EXPERIMENTAL_CONFIGFILE"
|
||||
|
||||
if _, ok := flags[configFileFlag]; !ok {
|
||||
if value := os.Getenv(envVar); value != "" {
|
||||
@@ -29,6 +30,8 @@ func (f *FileLoader) Load(args []string, cmd *cli.Command) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
log.Warn().Msg("Using experimental file config loader, this feature is experimental and may change or be removed in future releases")
|
||||
|
||||
err = file.Decode(flags[configFileFlag], cmd.Configuration)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -3,7 +3,6 @@ package utils
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
@@ -12,10 +11,6 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFilterEmpty = errors.New("filter is empty")
|
||||
)
|
||||
|
||||
func GetSecret(conf string, file string) string {
|
||||
if conf == "" && file == "" {
|
||||
return ""
|
||||
@@ -83,7 +78,7 @@ func CheckIPFilter(filter string, ip string) (bool, error) {
|
||||
|
||||
func CheckFilter(filter string, input string) (bool, error) {
|
||||
if len(strings.TrimSpace(filter)) == 0 {
|
||||
return false, ErrFilterEmpty
|
||||
return false, fmt.Errorf("filter is empty")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(filter, "/") && strings.HasSuffix(filter, "/") {
|
||||
|
||||
Reference in New Issue
Block a user