mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-05-27 22:50:13 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c7cb94e62c | |||
| faee58ca8e | |||
| e9b8ca3cf8 | |||
| f2c4e7932d | |||
| 4538922caf | |||
| 672db84200 |
+66
-1
@@ -7,7 +7,9 @@ TINYAUTH_APPURL=
|
|||||||
|
|
||||||
# database config
|
# database config
|
||||||
|
|
||||||
# The path to the database, including file name.
|
# 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.
|
||||||
TINYAUTH_DATABASE_PATH="./tinyauth.db"
|
TINYAUTH_DATABASE_PATH="./tinyauth.db"
|
||||||
|
|
||||||
# analytics config
|
# analytics config
|
||||||
@@ -30,6 +32,8 @@ TINYAUTH_SERVER_PORT=3000
|
|||||||
TINYAUTH_SERVER_ADDRESS="0.0.0.0"
|
TINYAUTH_SERVER_ADDRESS="0.0.0.0"
|
||||||
# The path to the Unix socket.
|
# The path to the Unix socket.
|
||||||
TINYAUTH_SERVER_SOCKETPATH=
|
TINYAUTH_SERVER_SOCKETPATH=
|
||||||
|
# Enable listening on both TCP and Unix socket at the same time.
|
||||||
|
TINYAUTH_SERVER_CONCURRENTLISTENERSENABLED=false
|
||||||
|
|
||||||
# auth config
|
# auth config
|
||||||
|
|
||||||
@@ -37,8 +41,52 @@ TINYAUTH_SERVER_SOCKETPATH=
|
|||||||
TINYAUTH_AUTH_IP_ALLOW=
|
TINYAUTH_AUTH_IP_ALLOW=
|
||||||
# List of blocked IPs or CIDR ranges.
|
# List of blocked IPs or CIDR ranges.
|
||||||
TINYAUTH_AUTH_IP_BLOCK=
|
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).
|
# Comma-separated list of users (username:hashed_password).
|
||||||
TINYAUTH_AUTH_USERS=
|
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.
|
# Path to the users file.
|
||||||
TINYAUTH_AUTH_USERSFILE=
|
TINYAUTH_AUTH_USERSFILE=
|
||||||
# Enable secure cookies.
|
# Enable secure cookies.
|
||||||
@@ -53,6 +101,8 @@ TINYAUTH_AUTH_LOGINTIMEOUT=300
|
|||||||
TINYAUTH_AUTH_LOGINMAXRETRIES=3
|
TINYAUTH_AUTH_LOGINMAXRETRIES=3
|
||||||
# Comma-separated list of trusted proxy addresses.
|
# Comma-separated list of trusted proxy addresses.
|
||||||
TINYAUTH_AUTH_TRUSTEDPROXIES=
|
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
|
# apps config
|
||||||
|
|
||||||
@@ -168,6 +218,8 @@ TINYAUTH_LDAP_AUTHCERT=
|
|||||||
TINYAUTH_LDAP_AUTHKEY=
|
TINYAUTH_LDAP_AUTHKEY=
|
||||||
# Cache duration for LDAP group membership in seconds.
|
# Cache duration for LDAP group membership in seconds.
|
||||||
TINYAUTH_LDAP_GROUPCACHETTL=900
|
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
|
# log config
|
||||||
|
|
||||||
@@ -187,3 +239,16 @@ TINYAUTH_LOG_STREAMS_APP_LEVEL=
|
|||||||
TINYAUTH_LOG_STREAMS_AUDIT_ENABLED=false
|
TINYAUTH_LOG_STREAMS_AUDIT_ENABLED=false
|
||||||
# Log level for this stream. Use global if empty.
|
# Log level for this stream. Use global if empty.
|
||||||
TINYAUTH_LOG_STREAMS_AUDIT_LEVEL=
|
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
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cp -r frontend/dist internal/assets/dist
|
cp -r frontend/dist internal/assets/dist
|
||||||
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
|
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
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cp -r frontend/dist internal/assets/dist
|
cp -r frontend/dist internal/assets/dist
|
||||||
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
|
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
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ jobs:
|
|||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
|
LDFLAGS="-s -w"
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
@@ -206,6 +207,7 @@ jobs:
|
|||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
|
LDFLAGS="-s -w"
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
@@ -260,6 +262,7 @@ jobs:
|
|||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
|
LDFLAGS="-s -w"
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
@@ -316,6 +319,7 @@ jobs:
|
|||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
|
LDFLAGS="-s -w"
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
+2
-1
@@ -27,6 +27,7 @@ FROM golang:1.26-alpine3.23 AS builder
|
|||||||
ARG VERSION
|
ARG VERSION
|
||||||
ARG COMMIT_HASH
|
ARG COMMIT_HASH
|
||||||
ARG BUILD_TIMESTAMP
|
ARG BUILD_TIMESTAMP
|
||||||
|
ARG LDFLAGS
|
||||||
|
|
||||||
WORKDIR /tinyauth
|
WORKDIR /tinyauth
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ COPY ./cmd ./cmd
|
|||||||
COPY ./internal ./internal
|
COPY ./internal ./internal
|
||||||
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w \
|
RUN CGO_ENABLED=0 go build -ldflags "${LDFLAGS} \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \
|
-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.CommitHash=${COMMIT_HASH} \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ FROM golang:1.26-alpine3.23 AS builder
|
|||||||
ARG VERSION
|
ARG VERSION
|
||||||
ARG COMMIT_HASH
|
ARG COMMIT_HASH
|
||||||
ARG BUILD_TIMESTAMP
|
ARG BUILD_TIMESTAMP
|
||||||
|
ARG LDFLAGS
|
||||||
|
|
||||||
WORKDIR /tinyauth
|
WORKDIR /tinyauth
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
|||||||
|
|
||||||
RUN mkdir -p data
|
RUN mkdir -p data
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w \
|
RUN CGO_ENABLED=0 go build -ldflags "${LDFLAGS} \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \
|
-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.CommitHash=${COMMIT_HASH} \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ TAG_NAME := $(shell git describe --abbrev=0 --exact-match 2> /dev/null || echo "
|
|||||||
COMMIT_HASH := $(shell git rev-parse HEAD)
|
COMMIT_HASH := $(shell git rev-parse HEAD)
|
||||||
BUILD_TIMESTAMP := $(shell date '+%Y-%m-%dT%H:%M:%S')
|
BUILD_TIMESTAMP := $(shell date '+%Y-%m-%dT%H:%M:%S')
|
||||||
BIN_NAME := tinyauth-$(GOARCH)
|
BIN_NAME := tinyauth-$(GOARCH)
|
||||||
|
LDFLAGS := -s -w
|
||||||
|
|
||||||
# Development vars
|
# Development vars
|
||||||
DEV_COMPOSE := $(shell test -f "docker-compose.test.yml" && echo "docker-compose.test.yml" || echo "docker-compose.dev.yml" )
|
DEV_COMPOSE := $(shell test -f "docker-compose.test.yml" && echo "docker-compose.test.yml" || echo "docker-compose.dev.yml" )
|
||||||
@@ -36,7 +37,7 @@ webui: clean-webui
|
|||||||
|
|
||||||
# Build the binary
|
# Build the binary
|
||||||
binary: webui
|
binary: webui
|
||||||
CGO_ENABLED=$(CGO_ENABLED) go build -ldflags "-s -w \
|
CGO_ENABLED=$(CGO_ENABLED) go build -ldflags "${LDFLAGS} \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${TAG_NAME} \
|
-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.CommitHash=${COMMIT_HASH} \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
||||||
|
|||||||
@@ -12,15 +12,17 @@ require (
|
|||||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||||
github.com/google/go-querystring v1.2.0
|
github.com/google/go-querystring v1.2.0
|
||||||
github.com/google/uuid v1.6.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/mdp/qrterminal/v3 v3.2.1
|
||||||
github.com/pquerna/otp v1.5.0
|
github.com/pquerna/otp v1.5.0
|
||||||
github.com/rs/zerolog v1.35.1
|
github.com/rs/zerolog v1.35.1
|
||||||
|
github.com/steveiliop56/ding v0.2.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298
|
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298
|
||||||
github.com/weppos/publicsuffix-go v0.50.3
|
github.com/weppos/publicsuffix-go v0.50.3
|
||||||
golang.org/x/crypto v0.52.0
|
golang.org/x/crypto v0.52.0
|
||||||
golang.org/x/oauth2 v0.36.0
|
golang.org/x/oauth2 v0.36.0
|
||||||
golang.org/x/tools v0.44.0
|
golang.org/x/tools v0.45.0
|
||||||
k8s.io/apimachinery v0.36.1
|
k8s.io/apimachinery v0.36.1
|
||||||
k8s.io/client-go v0.36.1
|
k8s.io/client-go v0.36.1
|
||||||
modernc.org/sqlite v1.50.1
|
modernc.org/sqlite v1.50.1
|
||||||
@@ -93,7 +95,6 @@ require (
|
|||||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect
|
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // 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/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
@@ -155,7 +156,7 @@ require (
|
|||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.22.0 // indirect
|
golang.org/x/arch v0.22.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/mod v0.35.0 // indirect
|
golang.org/x/mod v0.36.0 // indirect
|
||||||
golang.org/x/net v0.54.0 // indirect
|
golang.org/x/net v0.54.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.45.0 // indirect
|
golang.org/x/sys v0.45.0 // indirect
|
||||||
|
|||||||
@@ -143,6 +143,8 @@ 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/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 h1:8WFBn63wegobsYAX0YjD+8suexZDga5CctH4CCTx2+8=
|
||||||
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
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 h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
||||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
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=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
@@ -400,6 +402,8 @@ 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/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 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -497,8 +501,8 @@ golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9
|
|||||||
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
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 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
||||||
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
||||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
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 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||||
@@ -516,8 +520,8 @@ golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
|||||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
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/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
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/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||||
@@ -26,6 +26,12 @@ import (
|
|||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"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 {
|
type Services struct {
|
||||||
accessControlService *service.AccessControlsService
|
accessControlService *service.AccessControlsService
|
||||||
authService *service.AuthService
|
authService *service.AuthService
|
||||||
@@ -48,7 +54,7 @@ type BootstrapApp struct {
|
|||||||
queries repository.Store
|
queries repository.Store
|
||||||
router *gin.Engine
|
router *gin.Engine
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
wg sync.WaitGroup
|
ding *ding.Ding
|
||||||
listeners []Listener
|
listeners []Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +70,10 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
app.ctx = ctx
|
app.ctx = ctx
|
||||||
app.cancel = cancel
|
app.cancel = cancel
|
||||||
|
|
||||||
|
// Create a ding instance
|
||||||
|
dg := ding.New(ctx)
|
||||||
|
app.ding = dg
|
||||||
|
|
||||||
// setup logger
|
// setup logger
|
||||||
log := logger.NewLogger().WithConfig(app.config.Log)
|
log := logger.NewLogger().WithConfig(app.config.Log)
|
||||||
log.Init()
|
log.Init()
|
||||||
@@ -186,15 +196,17 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
return fmt.Errorf("failed to setup database: %w", err)
|
return fmt.Errorf("failed to setup database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// after this point, we start initializing dependencies so it's a good time to setup a defer
|
app.ding.Go(func(ctx context.Context) {
|
||||||
// to ensure that resources are cleaned up properly in case of an error during initialization
|
<-ctx.Done()
|
||||||
defer func() {
|
app.log.App.Debug().Msg("Shutting down database connection")
|
||||||
app.cancel()
|
if app.db == nil {
|
||||||
app.wg.Wait()
|
// using memory store, no db instance
|
||||||
if app.db != nil {
|
return
|
||||||
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
|
// store
|
||||||
app.queries = store
|
app.queries = store
|
||||||
@@ -261,12 +273,12 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
|
|
||||||
// start db cleanup routine
|
// start db cleanup routine
|
||||||
app.log.App.Debug().Msg("Starting database cleanup routine")
|
app.log.App.Debug().Msg("Starting database cleanup routine")
|
||||||
app.wg.Go(app.dbCleanupRoutine)
|
app.ding.Go(app.dbCleanupRoutine, ding.RingMinor)
|
||||||
|
|
||||||
// if analytics are not disabled, start heartbeat
|
// if analytics are not disabled, start heartbeat
|
||||||
if app.config.Analytics.Enabled {
|
if app.config.Analytics.Enabled {
|
||||||
app.log.App.Debug().Msg("Starting heartbeat routine")
|
app.log.App.Debug().Msg("Starting heartbeat routine")
|
||||||
app.wg.Go(app.heartbeatRoutine)
|
app.ding.Go(app.heartbeatRoutine, ding.RingMinor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup listeners
|
// setup listeners
|
||||||
@@ -287,6 +299,7 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-app.ctx.Done():
|
case <-app.ctx.Done():
|
||||||
|
app.ding.Wait()
|
||||||
app.log.App.Info().Msg("Oh, it's time for me to go, bye!")
|
app.log.App.Info().Msg("Oh, it's time for me to go, bye!")
|
||||||
return nil
|
return nil
|
||||||
case err := <-lec:
|
case err := <-lec:
|
||||||
@@ -297,7 +310,7 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) heartbeatRoutine() {
|
func (app *BootstrapApp) heartbeatRoutine(ctx context.Context) {
|
||||||
ticker := time.NewTicker(time.Duration(12) * time.Hour)
|
ticker := time.NewTicker(time.Duration(12) * time.Hour)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@@ -350,7 +363,7 @@ func (app *BootstrapApp) heartbeatRoutine() {
|
|||||||
if res.StatusCode != 200 && res.StatusCode != 201 {
|
if res.StatusCode != 200 && res.StatusCode != 201 {
|
||||||
app.log.App.Debug().Str("status", res.Status).Msg("Heartbeat returned non-200/201 status")
|
app.log.App.Debug().Str("status", res.Status).Msg("Heartbeat returned non-200/201 status")
|
||||||
}
|
}
|
||||||
case <-app.ctx.Done():
|
case <-ctx.Done():
|
||||||
app.log.App.Debug().Msg("Stopping heartbeat routine")
|
app.log.App.Debug().Msg("Stopping heartbeat routine")
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
return
|
return
|
||||||
@@ -358,7 +371,7 @@ func (app *BootstrapApp) heartbeatRoutine() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) dbCleanupRoutine() {
|
func (app *BootstrapApp) dbCleanupRoutine(ctx context.Context) {
|
||||||
ticker := time.NewTicker(time.Duration(30) * time.Minute)
|
ticker := time.NewTicker(time.Duration(30) * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@@ -367,14 +380,14 @@ func (app *BootstrapApp) dbCleanupRoutine() {
|
|||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
app.log.App.Debug().Msg("Running database cleanup")
|
app.log.App.Debug().Msg("Running database cleanup")
|
||||||
|
|
||||||
err := app.queries.DeleteExpiredSessions(app.ctx, time.Now().Unix())
|
err := app.queries.DeleteExpiredSessions(ctx, time.Now().Unix())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.log.App.Error().Err(err).Msg("Failed to delete expired sessions")
|
app.log.App.Error().Err(err).Msg("Failed to delete expired sessions")
|
||||||
}
|
}
|
||||||
|
|
||||||
app.log.App.Debug().Msg("Database cleanup completed")
|
app.log.App.Debug().Msg("Database cleanup completed")
|
||||||
case <-app.ctx.Done():
|
case <-ctx.Done():
|
||||||
app.log.App.Debug().Msg("Stopping database cleanup routine")
|
app.log.App.Debug().Msg("Stopping database cleanup routine")
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/middleware"
|
"github.com/tinyauthapp/tinyauth/internal/middleware"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
@@ -80,9 +81,9 @@ func (app *BootstrapApp) runListeners() (chan error, error) {
|
|||||||
return nil, fmt.Errorf("failed to get listener function: %w", err)
|
return nil, fmt.Errorf("failed to get listener function: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.wg.Go(func() {
|
app.ding.Go(func(ctx context.Context) {
|
||||||
lec <- listenerFunc()
|
lec <- listenerFunc(ctx)
|
||||||
})
|
}, ding.RingNormal)
|
||||||
}
|
}
|
||||||
|
|
||||||
return lec, nil
|
return lec, nil
|
||||||
@@ -125,7 +126,7 @@ func (app *BootstrapApp) calculateListenerPolicy() []Listener {
|
|||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) listenerFromType(listenerType Listener) (func() error, error) {
|
func (app *BootstrapApp) listenerFromType(listenerType Listener) (func(ctx context.Context) error, error) {
|
||||||
switch listenerType {
|
switch listenerType {
|
||||||
case ListenerHTTP:
|
case ListenerHTTP:
|
||||||
return app.serveHTTP, nil
|
return app.serveHTTP, nil
|
||||||
@@ -138,7 +139,7 @@ func (app *BootstrapApp) listenerFromType(listenerType Listener) (func() error,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) serveHTTP() error {
|
func (app *BootstrapApp) serveHTTP(ctx context.Context) error {
|
||||||
address := fmt.Sprintf("%s:%d", app.config.Server.Address, app.config.Server.Port)
|
address := fmt.Sprintf("%s:%d", app.config.Server.Address, app.config.Server.Port)
|
||||||
|
|
||||||
app.log.App.Info().Msgf("Starting server on %s", address)
|
app.log.App.Info().Msgf("Starting server on %s", address)
|
||||||
@@ -154,10 +155,10 @@ func (app *BootstrapApp) serveHTTP() error {
|
|||||||
Handler: app.router.Handler(),
|
Handler: app.router.Handler(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.serve(listener, server, "http")
|
return app.serve(listener, server, ctx, "http")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) serveUnix() error {
|
func (app *BootstrapApp) serveUnix(ctx context.Context) error {
|
||||||
_, err := os.Stat(app.config.Server.SocketPath)
|
_, err := os.Stat(app.config.Server.SocketPath)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -181,10 +182,10 @@ func (app *BootstrapApp) serveUnix() error {
|
|||||||
Handler: app.router.Handler(),
|
Handler: app.router.Handler(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.serve(listener, server, "unix socket")
|
return app.serve(listener, server, ctx, "unix socket")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) serveTailscale() error {
|
func (app *BootstrapApp) serveTailscale(ctx context.Context) error {
|
||||||
app.log.App.Info().Msgf("Starting Tailscale server on %s", fmt.Sprintf("https://%s", app.services.tailscaleService.GetHostname()))
|
app.log.App.Info().Msgf("Starting Tailscale server on %s", fmt.Sprintf("https://%s", app.services.tailscaleService.GetHostname()))
|
||||||
|
|
||||||
listener, err := app.services.tailscaleService.CreateListener()
|
listener, err := app.services.tailscaleService.CreateListener()
|
||||||
@@ -197,27 +198,23 @@ func (app *BootstrapApp) serveTailscale() error {
|
|||||||
Handler: app.router.Handler(),
|
Handler: app.router.Handler(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.serve(listener, server, "tailscale")
|
return app.serve(listener, server, ctx, "tailscale")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) serve(listener net.Listener, server *http.Server, name string) error {
|
func (app *BootstrapApp) serve(listener net.Listener, server *http.Server, ctx context.Context, name string) error {
|
||||||
shutdown := func() {
|
shutdown := func() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), model.GracefulShutdownTimeout*time.Second)
|
// we use a new context for the shutdown since the main one is cancelled
|
||||||
|
sctx, cancel := context.WithTimeout(context.Background(), model.GracefulShutdownTimeout*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
err := server.Shutdown(ctx)
|
err := server.Shutdown(sctx)
|
||||||
if err != nil &&
|
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)
|
app.log.App.Error().Err(err).Msgf("Failed to shutdown %s listener gracefully", name)
|
||||||
}
|
}
|
||||||
listener.Close()
|
listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-app.ctx.Done()
|
<-ctx.Done()
|
||||||
app.log.App.Debug().Msgf("Shutting down %s listener", name)
|
app.log.App.Debug().Msgf("Shutting down %s listener", name)
|
||||||
shutdown()
|
shutdown()
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (app *BootstrapApp) setupServices() error {
|
func (app *BootstrapApp) setupServices() error {
|
||||||
ldapService, err := service.NewLdapService(app.log, app.config, app.ctx, &app.wg)
|
ldapService, err := service.NewLdapService(app.log, app.config, app.ding)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.log.App.Warn().Err(err).Msg("Failed to initialize LDAP connection, will continue without it")
|
app.log.App.Warn().Err(err).Msg("Failed to initialize LDAP connection, will continue without it")
|
||||||
@@ -22,7 +22,7 @@ func (app *BootstrapApp) setupServices() error {
|
|||||||
return fmt.Errorf("failed to initialize label provider: %w", err)
|
return fmt.Errorf("failed to initialize label provider: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tailscaleService, err := service.NewTailscaleService(app.log, app.config, app.ctx, &app.wg)
|
tailscaleService, err := service.NewTailscaleService(app.log, app.config, app.ctx, app.ding)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.log.App.Warn().Err(err).Msg("Failed to initialize Tailscale connection, will continue without it")
|
app.log.App.Warn().Err(err).Msg("Failed to initialize Tailscale connection, will continue without it")
|
||||||
@@ -42,10 +42,10 @@ func (app *BootstrapApp) setupServices() error {
|
|||||||
oauthBrokerService := service.NewOAuthBrokerService(app.log, app.runtime.OAuthProviders, app.ctx)
|
oauthBrokerService := service.NewOAuthBrokerService(app.log, app.runtime.OAuthProviders, app.ctx)
|
||||||
app.services.oauthBrokerService = oauthBrokerService
|
app.services.oauthBrokerService = oauthBrokerService
|
||||||
|
|
||||||
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.policyEngine)
|
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)
|
||||||
app.services.authService = authService
|
app.services.authService = authService
|
||||||
|
|
||||||
oidcService, err := service.NewOIDCService(app.log, app.config, app.runtime, app.queries, app.ctx, &app.wg)
|
oidcService, err := service.NewOIDCService(app.log, app.config, app.runtime, app.queries, app.ding)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to initialize oidc service: %w", err)
|
return fmt.Errorf("failed to initialize oidc service: %w", err)
|
||||||
@@ -69,7 +69,7 @@ func (app *BootstrapApp) getLabelProvider() (service.LabelProvider, error) {
|
|||||||
if useKubernetes {
|
if useKubernetes {
|
||||||
app.log.App.Debug().Msg("Using Kubernetes label provider")
|
app.log.App.Debug().Msg("Using Kubernetes label provider")
|
||||||
|
|
||||||
kubernetesService, err := service.NewKubernetesService(app.log, app.ctx, &app.wg)
|
kubernetesService, err := service.NewKubernetesService(app.log, app.ctx, app.ding)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize kubernetes service: %w", err)
|
return nil, fmt.Errorf("failed to initialize kubernetes service: %w", err)
|
||||||
@@ -81,7 +81,7 @@ func (app *BootstrapApp) getLabelProvider() (service.LabelProvider, error) {
|
|||||||
|
|
||||||
app.log.App.Debug().Msg("Using Docker label provider")
|
app.log.App.Debug().Msg("Using Docker label provider")
|
||||||
|
|
||||||
dockerService, err := service.NewDockerService(app.log, app.ctx, &app.wg)
|
dockerService, err := service.NewDockerService(app.log, app.ctx, app.ding)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize docker service: %w", err)
|
return nil, fmt.Errorf("failed to initialize docker service: %w", err)
|
||||||
|
|||||||
@@ -16,6 +16,15 @@ import (
|
|||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"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 {
|
type OIDCController struct {
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
oidc *service.OIDCService
|
oidc *service.OIDCService
|
||||||
@@ -119,34 +128,55 @@ func (controller *OIDCController) GetClientInfo(c *gin.Context) {
|
|||||||
|
|
||||||
func (controller *OIDCController) Authorize(c *gin.Context) {
|
func (controller *OIDCController) Authorize(c *gin.Context) {
|
||||||
if controller.oidc == nil {
|
if controller.oidc == nil {
|
||||||
controller.authorizeError(c, errors.New("err_oidc_not_configured"), "OIDC not configured", "This instance is not configured for OIDC", "", "", "")
|
controller.authorizeError(c, authorizeErrorParams{
|
||||||
|
err: errors.New("err_oidc_not_configured"),
|
||||||
|
reason: "OIDC not configured",
|
||||||
|
reasonPublic: "This instance is not configured for OIDC",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userContext, err := new(model.UserContext).NewFromGin(c)
|
userContext, err := new(model.UserContext).NewFromGin(c)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.authorizeError(c, err, "Failed to get user context", "User is not logged in or the session is invalid", "", "", "")
|
controller.authorizeError(c, authorizeErrorParams{
|
||||||
|
err: err,
|
||||||
|
reason: "Failed to get user context",
|
||||||
|
reasonPublic: "User is not logged in or the session is invalid",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !userContext.Authenticated {
|
if !userContext.Authenticated {
|
||||||
controller.authorizeError(c, errors.New("err user not logged in"), "User not logged in", "The user is not logged in", "", "", "")
|
controller.authorizeError(c, authorizeErrorParams{
|
||||||
|
err: errors.New("err user not logged in"),
|
||||||
|
reason: "User not logged in",
|
||||||
|
reasonPublic: "The user is not logged in",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req service.AuthorizeRequest
|
var req service.AuthorizeRequest
|
||||||
|
|
||||||
err = c.BindJSON(&req)
|
err = c.Bind(&req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.authorizeError(c, err, "Failed to bind JSON", "The client provided an invalid authorization request", "", "", "")
|
controller.authorizeError(c, authorizeErrorParams{
|
||||||
|
err: err,
|
||||||
|
reason: "Failed to bind JSON",
|
||||||
|
reasonPublic: "The client provided an invalid authorization request",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
client, ok := controller.oidc.GetClient(req.ClientID)
|
client, ok := controller.oidc.GetClient(req.ClientID)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
controller.authorizeError(c, fmt.Errorf("client not found: %s", req.ClientID), "Client not found", "The client ID is invalid", "", "", "")
|
controller.authorizeError(c, authorizeErrorParams{
|
||||||
|
err: fmt.Errorf("client not found: %s", req.ClientID),
|
||||||
|
reason: "Client not found",
|
||||||
|
reasonPublic: "The client ID is invalid",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,10 +185,21 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Warn().Err(err).Msg("Failed to validate authorize params")
|
controller.log.App.Warn().Err(err).Msg("Failed to validate authorize params")
|
||||||
if err.Error() != "invalid_request_uri" {
|
if err.Error() != "invalid_request_uri" {
|
||||||
controller.authorizeError(c, err, "Failed validate authorize params", "Invalid request parameters", req.RedirectURI, err.Error(), req.State)
|
controller.authorizeError(c, authorizeErrorParams{
|
||||||
|
err: err,
|
||||||
|
reason: "Failed validate authorize params",
|
||||||
|
reasonPublic: "Invalid request parameters",
|
||||||
|
callback: req.RedirectURI,
|
||||||
|
callbackError: err.Error(),
|
||||||
|
state: req.State,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
controller.authorizeError(c, err, "Redirect URI not trusted", "The provided redirect URI is not trusted", "", "", "")
|
controller.authorizeError(c, authorizeErrorParams{
|
||||||
|
err: err,
|
||||||
|
reason: "Redirect URI not trusted",
|
||||||
|
reasonPublic: "The provided redirect URI is not trusted",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,14 +210,28 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
|
|||||||
// Before storing the code, delete old session
|
// Before storing the code, delete old session
|
||||||
err = controller.oidc.DeleteOldSession(c, sub)
|
err = controller.oidc.DeleteOldSession(c, sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.authorizeError(c, err, "Failed to delete old sessions", "Failed to delete old sessions", req.RedirectURI, "server_error", req.State)
|
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,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.oidc.StoreCode(c, sub, code, req)
|
err = controller.oidc.StoreCode(c, sub, code, req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.authorizeError(c, err, "Failed to store code", "Failed to store code", req.RedirectURI, "server_error", req.State)
|
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,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +241,14 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to store user info")
|
controller.log.App.Error().Err(err).Msg("Failed to store user info")
|
||||||
controller.authorizeError(c, err, "Failed to store user info", "Failed to store user info", req.RedirectURI, "server_error", req.State)
|
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,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +259,14 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.authorizeError(c, err, "Failed to build query", "Failed to build query", req.RedirectURI, "server_error", req.State)
|
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,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,20 +547,20 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
|||||||
c.JSON(200, controller.oidc.CompileUserinfo(user, entry.Scope))
|
c.JSON(200, controller.oidc.CompileUserinfo(user, entry.Scope))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (controller *OIDCController) authorizeError(c *gin.Context, err error, reason string, reasonUser string, callback string, callbackError string, state string) {
|
func (controller *OIDCController) authorizeError(c *gin.Context, params authorizeErrorParams) {
|
||||||
controller.log.App.Warn().Err(err).Str("reason", reason).Msg("Authorization error")
|
controller.log.App.Error().Err(params.err).Str("reason", params.reason).Msg("Authorization error")
|
||||||
|
|
||||||
if callback != "" {
|
if params.callback != "" {
|
||||||
errorQueries := CallbackError{
|
errorQueries := CallbackError{
|
||||||
Error: callbackError,
|
Error: params.callbackError,
|
||||||
}
|
}
|
||||||
|
|
||||||
if reasonUser != "" {
|
if params.reasonPublic != "" {
|
||||||
errorQueries.ErrorDescription = reasonUser
|
errorQueries.ErrorDescription = params.reasonPublic
|
||||||
}
|
}
|
||||||
|
|
||||||
if state != "" {
|
if params.state != "" {
|
||||||
errorQueries.State = state
|
errorQueries.State = params.state
|
||||||
}
|
}
|
||||||
|
|
||||||
queries, err := query.Values(errorQueries)
|
queries, err := query.Values(errorQueries)
|
||||||
@@ -503,13 +572,13 @@ func (controller *OIDCController) authorizeError(c *gin.Context, err error, reas
|
|||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"redirect_uri": fmt.Sprintf("%s?%s", callback, queries.Encode()),
|
"redirect_uri": fmt.Sprintf("%s?%s", params.callback, queries.Encode()),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
errorQueries := ErrorScreen{
|
errorQueries := ErrorScreen{
|
||||||
Error: reasonUser,
|
Error: params.reasonPublic,
|
||||||
}
|
}
|
||||||
|
|
||||||
queries, err := query.Values(errorQueries)
|
queries, err := query.Values(errorQueries)
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/go-querystring/query"
|
"github.com/google/go-querystring/query"
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
@@ -840,9 +840,9 @@ func TestOIDCController(t *testing.T) {
|
|||||||
|
|
||||||
store := memory.New()
|
store := memory.New()
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
dg := ding.New(context.TODO())
|
||||||
|
|
||||||
oidcService, err := service.NewOIDCService(log, cfg, runtime, store, context.TODO(), wg)
|
oidcService, err := service.NewOIDCService(log, cfg, runtime, store, dg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package controller_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
@@ -353,8 +353,8 @@ func TestProxyController(t *testing.T) {
|
|||||||
|
|
||||||
store := memory.New()
|
store := memory.New()
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
dg := ding.New(ctx)
|
||||||
|
|
||||||
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
||||||
aclsService := service.NewAccessControlsService(log, cfg, nil)
|
aclsService := service.NewAccessControlsService(log, cfg, nil)
|
||||||
@@ -382,7 +382,7 @@ func TestProxyController(t *testing.T) {
|
|||||||
Log: log,
|
Log: log,
|
||||||
})
|
})
|
||||||
|
|
||||||
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, store, broker, nil, policyEngine)
|
authService := service.NewAuthService(log, cfg, runtime, ctx, dg, nil, store, broker, nil, policyEngine)
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.description, func(t *testing.T) {
|
t.Run(test.description, func(t *testing.T) {
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pquerna/otp/totp"
|
"github.com/pquerna/otp/totp"
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
@@ -412,13 +412,13 @@ func TestUserController(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
wg := &sync.WaitGroup{}
|
dg := ding.New(ctx)
|
||||||
|
|
||||||
policyEngine, err := service.NewPolicyEngine(cfg, log)
|
policyEngine, err := service.NewPolicyEngine(cfg, log)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
||||||
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, store, broker, nil, policyEngine)
|
authService := service.NewAuthService(log, cfg, runtime, ctx, dg, nil, store, broker, nil, policyEngine)
|
||||||
|
|
||||||
beforeEach := func() {
|
beforeEach := func() {
|
||||||
// Clear failed login attempts before each test
|
// Clear failed login attempts before each test
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
@@ -89,11 +89,11 @@ func TestWellKnownController(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
wg := &sync.WaitGroup{}
|
dg := ding.New(ctx)
|
||||||
|
|
||||||
store := memory.New()
|
store := memory.New()
|
||||||
|
|
||||||
oidcService, err := service.NewOIDCService(log, cfg, runtime, store, ctx, wg)
|
oidcService, err := service.NewOIDCService(log, cfg, runtime, store, dg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/middleware"
|
"github.com/tinyauthapp/tinyauth/internal/middleware"
|
||||||
@@ -250,7 +250,7 @@ func TestContextMiddleware(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
wg := &sync.WaitGroup{}
|
dg := ding.New(ctx)
|
||||||
|
|
||||||
store := memory.New()
|
store := memory.New()
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ func TestContextMiddleware(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
||||||
authService := service.NewAuthService(log, cfg, runtime, ctx, wg, nil, store, broker, nil, policyEngine)
|
authService := service.NewAuthService(log, cfg, runtime, ctx, dg, nil, store, broker, nil, policyEngine)
|
||||||
|
|
||||||
contextMiddleware := middleware.NewContextMiddleware(log, runtime, authService, broker, nil)
|
contextMiddleware := middleware.NewContextMiddleware(log, runtime, authService, broker, nil)
|
||||||
|
|
||||||
|
|||||||
@@ -62,9 +62,6 @@ func NewDefaultConfiguration() *Config {
|
|||||||
PrivateKeyPath: "./tinyauth_oidc_key",
|
PrivateKeyPath: "./tinyauth_oidc_key",
|
||||||
PublicKeyPath: "./tinyauth_oidc_key.pub",
|
PublicKeyPath: "./tinyauth_oidc_key.pub",
|
||||||
},
|
},
|
||||||
Experimental: ExperimentalConfig{
|
|
||||||
ConfigFile: "",
|
|
||||||
},
|
|
||||||
Tailscale: TailscaleConfig{
|
Tailscale: TailscaleConfig{
|
||||||
Dir: "./tailscale_state",
|
Dir: "./tailscale_state",
|
||||||
},
|
},
|
||||||
@@ -88,6 +85,7 @@ 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"`
|
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"`
|
Log LogConfig `description:"Logging configuration." yaml:"log"`
|
||||||
Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale"`
|
Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale"`
|
||||||
|
ConfigFile string `description:"Path to config file." yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DatabaseConfig struct {
|
type DatabaseConfig struct {
|
||||||
@@ -208,9 +206,8 @@ type LogStreamConfig struct {
|
|||||||
Level string `description:"Log level for this stream. Use global if empty." yaml:"level"`
|
Level string `description:"Log level for this stream. Use global if empty." yaml:"level"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExperimentalConfig struct {
|
// no experimental features
|
||||||
ConfigFile string `description:"Path to config file." yaml:"-"`
|
type ExperimentalConfig struct{}
|
||||||
}
|
|
||||||
|
|
||||||
type TailscaleConfig struct {
|
type TailscaleConfig struct {
|
||||||
Enabled bool `description:"Enable Tailscale integration." yaml:"enabled"`
|
Enabled bool `description:"Enable Tailscale integration." yaml:"enabled"`
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ import (
|
|||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"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
|
type RuleName string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -25,7 +31,11 @@ type UserAllowedRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rule *UserAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
func (rule *UserAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
||||||
if ctx.ACLs == nil || ctx.UserContext == nil {
|
if ctx.UserContext == nil {
|
||||||
|
return EffectDeny
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.ACLs == nil {
|
||||||
return EffectAbstain
|
return EffectAbstain
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +44,7 @@ func (rule *UserAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
|||||||
match, err := utils.CheckFilter(ctx.ACLs.OAuth.Whitelist, ctx.UserContext.OAuth.Email)
|
match, err := utils.CheckFilter(ctx.ACLs.OAuth.Whitelist, ctx.UserContext.OAuth.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rule.Log.App.Warn().Err(err).Str("item", ctx.UserContext.OAuth.Email).Msg("Invalid entry in OAuth whitelist")
|
rule.Log.App.Warn().Err(err).Str("item", ctx.UserContext.OAuth.Email).Msg("Invalid entry in OAuth whitelist")
|
||||||
return EffectAbstain
|
return EffectDeny
|
||||||
}
|
}
|
||||||
if match {
|
if match {
|
||||||
rule.Log.App.Debug().Str("email", ctx.UserContext.OAuth.Email).Msg("User is in OAuth whitelist, allowing access")
|
rule.Log.App.Debug().Str("email", ctx.UserContext.OAuth.Email).Msg("User is in OAuth whitelist, allowing access")
|
||||||
@@ -48,7 +58,7 @@ func (rule *UserAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
|||||||
match, err := utils.CheckFilter(ctx.ACLs.Users.Block, ctx.UserContext.GetUsername())
|
match, err := utils.CheckFilter(ctx.ACLs.Users.Block, ctx.UserContext.GetUsername())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rule.Log.App.Warn().Err(err).Str("item", ctx.UserContext.GetUsername()).Msg("Invalid entry in users block list")
|
rule.Log.App.Warn().Err(err).Str("item", ctx.UserContext.GetUsername()).Msg("Invalid entry in users block list")
|
||||||
return EffectAbstain
|
return EffectDeny
|
||||||
}
|
}
|
||||||
if match {
|
if match {
|
||||||
rule.Log.App.Debug().Str("username", ctx.UserContext.GetUsername()).Msg("User is in users block list, denying access")
|
rule.Log.App.Debug().Str("username", ctx.UserContext.GetUsername()).Msg("User is in users block list, denying access")
|
||||||
@@ -62,8 +72,11 @@ func (rule *UserAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
|||||||
match, err := utils.CheckFilter(ctx.ACLs.Users.Allow, ctx.UserContext.GetUsername())
|
match, err := utils.CheckFilter(ctx.ACLs.Users.Allow, ctx.UserContext.GetUsername())
|
||||||
|
|
||||||
if err != nil {
|
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")
|
rule.Log.App.Warn().Err(err).Str("item", ctx.UserContext.GetUsername()).Msg("Invalid entry in users allow list")
|
||||||
return EffectAbstain
|
return EffectDeny
|
||||||
}
|
}
|
||||||
|
|
||||||
if match {
|
if match {
|
||||||
@@ -80,13 +93,22 @@ type OAuthGroupRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rule *OAuthGroupRule) Evaluate(ctx *ACLContext) Effect {
|
func (rule *OAuthGroupRule) Evaluate(ctx *ACLContext) Effect {
|
||||||
if ctx.ACLs == nil || ctx.UserContext == nil {
|
if ctx.UserContext == nil {
|
||||||
return EffectAbstain
|
return EffectDeny
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.ACLs == nil {
|
||||||
|
return EffectAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.UserContext.IsOAuth() {
|
if !ctx.UserContext.IsOAuth() {
|
||||||
rule.Log.App.Debug().Msg("User is not an OAuth user, skipping OAuth group check")
|
rule.Log.App.Debug().Msg("User is not an OAuth user, skipping OAuth group check")
|
||||||
return EffectAbstain
|
return EffectAllow
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ctx.ACLs.OAuth.Groups) == 0 {
|
||||||
|
rule.Log.App.Debug().Msg("No OAuth groups specified in ACLs, allowing access")
|
||||||
|
return EffectAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := model.OverrideProviders[ctx.UserContext.OAuth.ID]; ok {
|
if _, ok := model.OverrideProviders[ctx.UserContext.OAuth.ID]; ok {
|
||||||
@@ -97,7 +119,8 @@ func (rule *OAuthGroupRule) Evaluate(ctx *ACLContext) Effect {
|
|||||||
for _, group := range ctx.UserContext.OAuth.Groups {
|
for _, group := range ctx.UserContext.OAuth.Groups {
|
||||||
match, err := utils.CheckFilter(ctx.ACLs.OAuth.Groups, strings.TrimSpace(group))
|
match, err := utils.CheckFilter(ctx.ACLs.OAuth.Groups, strings.TrimSpace(group))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return EffectAbstain
|
rule.Log.App.Warn().Err(err).Str("item", group).Msg("Invalid entry in OAuth groups ACL")
|
||||||
|
return EffectDeny
|
||||||
}
|
}
|
||||||
if match {
|
if match {
|
||||||
rule.Log.App.Trace().Str("group", group).Str("required", ctx.ACLs.OAuth.Groups).Msg("User group matched, allowing access")
|
rule.Log.App.Trace().Str("group", group).Str("required", ctx.ACLs.OAuth.Groups).Msg("User group matched, allowing access")
|
||||||
@@ -114,19 +137,29 @@ type LDAPGroupRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rule *LDAPGroupRule) Evaluate(ctx *ACLContext) Effect {
|
func (rule *LDAPGroupRule) Evaluate(ctx *ACLContext) Effect {
|
||||||
if ctx == nil || ctx.UserContext == nil || ctx.ACLs == nil {
|
if ctx.UserContext == nil {
|
||||||
return EffectAbstain
|
return EffectDeny
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.ACLs == nil {
|
||||||
|
return EffectAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.UserContext.IsLDAP() {
|
if !ctx.UserContext.IsLDAP() {
|
||||||
rule.Log.App.Debug().Msg("User is not an LDAP user, skipping LDAP group check")
|
rule.Log.App.Debug().Msg("User is not an LDAP user, skipping LDAP group check")
|
||||||
return EffectAbstain
|
return EffectAllow
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ctx.ACLs.LDAP.Groups) == 0 {
|
||||||
|
rule.Log.App.Debug().Msg("No LDAP groups specified in ACLs, allowing access")
|
||||||
|
return EffectAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, group := range ctx.UserContext.LDAP.Groups {
|
for _, group := range ctx.UserContext.LDAP.Groups {
|
||||||
match, err := utils.CheckFilter(ctx.ACLs.LDAP.Groups, strings.TrimSpace(group))
|
match, err := utils.CheckFilter(ctx.ACLs.LDAP.Groups, strings.TrimSpace(group))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return EffectAbstain
|
rule.Log.App.Warn().Err(err).Str("item", group).Msg("Invalid entry in LDAP groups ACL")
|
||||||
|
return EffectDeny
|
||||||
}
|
}
|
||||||
if match {
|
if match {
|
||||||
rule.Log.App.Trace().Str("group", group).Str("required", ctx.ACLs.LDAP.Groups).Msg("User group matched, allowing access")
|
rule.Log.App.Trace().Str("group", group).Str("required", ctx.ACLs.LDAP.Groups).Msg("User group matched, allowing access")
|
||||||
|
|||||||
@@ -21,6 +21,16 @@ func TestUserAllowedRule(t *testing.T) {
|
|||||||
ctx *ACLContext
|
ctx *ACLContext
|
||||||
expected Effect
|
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",
|
name: "abstains when ACLs are nil",
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
@@ -34,16 +44,6 @@ func TestUserAllowedRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: EffectAbstain,
|
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",
|
name: "allows OAuth user when email matches whitelist",
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
@@ -78,7 +78,7 @@ func TestUserAllowedRule(t *testing.T) {
|
|||||||
expected: EffectDeny,
|
expected: EffectDeny,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "abstains for OAuth user when whitelist filter is invalid",
|
name: "denies for OAuth user when whitelist filter is invalid",
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: &model.App{
|
ACLs: &model.App{
|
||||||
OAuth: model.AppOAuth{Whitelist: "/[/"},
|
OAuth: model.AppOAuth{Whitelist: "/[/"},
|
||||||
@@ -90,7 +90,7 @@ func TestUserAllowedRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: EffectAbstain,
|
expected: EffectDeny,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "denies local user when username matches block list",
|
name: "denies local user when username matches block list",
|
||||||
@@ -123,7 +123,7 @@ func TestUserAllowedRule(t *testing.T) {
|
|||||||
expected: EffectAllow,
|
expected: EffectAllow,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "abstains when block list filter is invalid",
|
name: "denies when block list filter is invalid",
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: &model.App{
|
ACLs: &model.App{
|
||||||
Users: model.AppUsers{Block: "/[/"},
|
Users: model.AppUsers{Block: "/[/"},
|
||||||
@@ -135,6 +135,21 @@ 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,
|
expected: EffectAbstain,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -168,7 +183,7 @@ func TestUserAllowedRule(t *testing.T) {
|
|||||||
expected: EffectDeny,
|
expected: EffectDeny,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "abstains when allow list filter is invalid",
|
name: "denies when allow list filter is invalid",
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: &model.App{
|
ACLs: &model.App{
|
||||||
Users: model.AppUsers{Allow: "/[/"},
|
Users: model.AppUsers{Allow: "/[/"},
|
||||||
@@ -180,7 +195,7 @@ func TestUserAllowedRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: EffectAbstain,
|
expected: EffectDeny,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +218,17 @@ func TestOAuthGroupRule(t *testing.T) {
|
|||||||
expected Effect
|
expected Effect
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "abstains when ACLs are nil",
|
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",
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: nil,
|
ACLs: nil,
|
||||||
UserContext: &model.UserContext{
|
UserContext: &model.UserContext{
|
||||||
@@ -213,20 +238,10 @@ func TestOAuthGroupRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: EffectAbstain,
|
expected: EffectAllow,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "abstains when user context is nil",
|
name: "allows when user is not OAuth",
|
||||||
ctx: &ACLContext{
|
|
||||||
ACLs: &model.App{
|
|
||||||
OAuth: model.AppOAuth{Whitelist: "alice"},
|
|
||||||
},
|
|
||||||
UserContext: nil,
|
|
||||||
},
|
|
||||||
expected: EffectAbstain,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "abstains when user is not OAuth",
|
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: &model.App{
|
ACLs: &model.App{
|
||||||
OAuth: model.AppOAuth{Groups: "admins"},
|
OAuth: model.AppOAuth{Groups: "admins"},
|
||||||
@@ -238,7 +253,22 @@ func TestOAuthGroupRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: EffectAbstain,
|
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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "allows when provider is an override provider regardless of groups",
|
name: "allows when provider is an override provider regardless of groups",
|
||||||
@@ -305,7 +335,7 @@ func TestOAuthGroupRule(t *testing.T) {
|
|||||||
expected: EffectDeny,
|
expected: EffectDeny,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "abstains when groups filter is invalid",
|
name: "denies when groups filter is invalid",
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: &model.App{
|
ACLs: &model.App{
|
||||||
OAuth: model.AppOAuth{Groups: "/[/"},
|
OAuth: model.AppOAuth{Groups: "/[/"},
|
||||||
@@ -318,7 +348,7 @@ func TestOAuthGroupRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: EffectAbstain,
|
expected: EffectDeny,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,22 +371,30 @@ func TestLDAPGroupRule(t *testing.T) {
|
|||||||
expected Effect
|
expected Effect
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "abstains when context is nil",
|
name: "denies when user context is nil",
|
||||||
ctx: nil,
|
|
||||||
expected: EffectAbstain,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "abstains when user context is nil",
|
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: &model.App{
|
ACLs: &model.App{
|
||||||
OAuth: model.AppOAuth{Whitelist: "alice"},
|
OAuth: model.AppOAuth{Whitelist: "alice"},
|
||||||
},
|
},
|
||||||
UserContext: nil,
|
UserContext: nil,
|
||||||
},
|
},
|
||||||
expected: EffectAbstain,
|
expected: EffectDeny,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "abstains when user is not LDAP",
|
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",
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: &model.App{
|
ACLs: &model.App{
|
||||||
LDAP: model.AppLDAP{Groups: "admins"},
|
LDAP: model.AppLDAP{Groups: "admins"},
|
||||||
@@ -368,7 +406,22 @@ func TestLDAPGroupRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: EffectAbstain,
|
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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "allows LDAP user when a group matches",
|
name: "allows LDAP user when a group matches",
|
||||||
@@ -416,7 +469,7 @@ func TestLDAPGroupRule(t *testing.T) {
|
|||||||
expected: EffectDeny,
|
expected: EffectDeny,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "abstains when groups filter is invalid",
|
name: "denies when groups filter is invalid",
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: &model.App{
|
ACLs: &model.App{
|
||||||
LDAP: model.AppLDAP{Groups: "/[/"},
|
LDAP: model.AppLDAP{Groups: "/[/"},
|
||||||
@@ -428,7 +481,7 @@ func TestLDAPGroupRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: EffectAbstain,
|
expected: EffectDeny,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||||
@@ -97,7 +98,7 @@ func NewAuthService(
|
|||||||
config model.Config,
|
config model.Config,
|
||||||
runtime model.RuntimeConfig,
|
runtime model.RuntimeConfig,
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
wg *sync.WaitGroup,
|
dg *ding.Ding,
|
||||||
ldap *LdapService,
|
ldap *LdapService,
|
||||||
queries repository.Store,
|
queries repository.Store,
|
||||||
oauthBroker *OAuthBrokerService,
|
oauthBroker *OAuthBrokerService,
|
||||||
@@ -119,7 +120,7 @@ func NewAuthService(
|
|||||||
policyEngine: policy,
|
policyEngine: policy,
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Go(service.CleanupOAuthSessionsRoutine)
|
dg.Go(service.cleanupOAuthSessions, ding.RingMinor)
|
||||||
|
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
@@ -601,7 +602,7 @@ func (auth *AuthService) EndOAuthSession(sessionId string) {
|
|||||||
auth.oauthMutex.Unlock()
|
auth.oauthMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *AuthService) CleanupOAuthSessionsRoutine() {
|
func (auth *AuthService) cleanupOAuthSessions(ctx context.Context) {
|
||||||
auth.log.App.Debug().Msg("Starting OAuth session cleanup routine")
|
auth.log.App.Debug().Msg("Starting OAuth session cleanup routine")
|
||||||
|
|
||||||
ticker := time.NewTicker(30 * time.Minute)
|
ticker := time.NewTicker(30 * time.Minute)
|
||||||
@@ -624,7 +625,7 @@ func (auth *AuthService) CleanupOAuthSessionsRoutine() {
|
|||||||
|
|
||||||
auth.oauthMutex.Unlock()
|
auth.oauthMutex.Unlock()
|
||||||
auth.log.App.Debug().Msg("OAuth session cleanup completed")
|
auth.log.App.Debug().Msg("OAuth session cleanup completed")
|
||||||
case <-auth.context.Done():
|
case <-ctx.Done():
|
||||||
auth.log.App.Debug().Msg("Stopping OAuth session cleanup routine")
|
auth.log.App.Debug().Msg("Stopping OAuth session cleanup routine")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
|
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
@@ -24,7 +24,7 @@ type DockerService struct {
|
|||||||
func NewDockerService(
|
func NewDockerService(
|
||||||
log *logger.Logger,
|
log *logger.Logger,
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
wg *sync.WaitGroup,
|
dg *ding.Ding,
|
||||||
) (*DockerService, error) {
|
) (*DockerService, error) {
|
||||||
|
|
||||||
client, err := client.NewClientWithOpts(client.FromEnv)
|
client, err := client.NewClientWithOpts(client.FromEnv)
|
||||||
@@ -50,7 +50,7 @@ func NewDockerService(
|
|||||||
service.isConnected = true
|
service.isConnected = true
|
||||||
service.log.App.Debug().Msg("Docker connected successfully")
|
service.log.App.Debug().Msg("Docker connected successfully")
|
||||||
|
|
||||||
wg.Go(service.watchAndClose)
|
dg.Go(service.watchAndClose, ding.RingMajor)
|
||||||
|
|
||||||
return service, nil
|
return service, nil
|
||||||
}
|
}
|
||||||
@@ -108,8 +108,8 @@ func (docker *DockerService) GetLabels(appDomain string) (*model.App, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (docker *DockerService) watchAndClose() {
|
func (docker *DockerService) watchAndClose(ctx context.Context) {
|
||||||
<-docker.context.Done()
|
<-ctx.Done()
|
||||||
docker.log.App.Debug().Msg("Closing Docker client")
|
docker.log.App.Debug().Msg("Closing Docker client")
|
||||||
if docker.client != nil {
|
if docker.client != nil {
|
||||||
err := docker.client.Close()
|
err := docker.client.Close()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
|
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
@@ -38,7 +39,6 @@ type ingressApp struct {
|
|||||||
|
|
||||||
type KubernetesService struct {
|
type KubernetesService struct {
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
ctx context.Context
|
|
||||||
|
|
||||||
client dynamic.Interface
|
client dynamic.Interface
|
||||||
started bool
|
started bool
|
||||||
@@ -51,7 +51,7 @@ type KubernetesService struct {
|
|||||||
func NewKubernetesService(
|
func NewKubernetesService(
|
||||||
log *logger.Logger,
|
log *logger.Logger,
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
wg *sync.WaitGroup,
|
dg *ding.Ding,
|
||||||
) (*KubernetesService, error) {
|
) (*KubernetesService, error) {
|
||||||
cfg, err := rest.InClusterConfig()
|
cfg, err := rest.InClusterConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -82,16 +82,15 @@ func NewKubernetesService(
|
|||||||
|
|
||||||
service := &KubernetesService{
|
service := &KubernetesService{
|
||||||
log: log,
|
log: log,
|
||||||
ctx: ctx,
|
|
||||||
client: client,
|
client: client,
|
||||||
ingressApps: make(map[ingressKey][]ingressApp),
|
ingressApps: make(map[ingressKey][]ingressApp),
|
||||||
domainIndex: make(map[string]ingressAppKey),
|
domainIndex: make(map[string]ingressAppKey),
|
||||||
appNameIndex: make(map[string]ingressAppKey),
|
appNameIndex: make(map[string]ingressAppKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Go(func() {
|
dg.Go(func(ctx context.Context) {
|
||||||
service.watchGVR(gvr)
|
service.watchGVR(gvr, ctx)
|
||||||
})
|
}, ding.RingMajor)
|
||||||
|
|
||||||
service.started = true
|
service.started = true
|
||||||
log.App.Debug().Msg("Kubernetes label provider started successfully")
|
log.App.Debug().Msg("Kubernetes label provider started successfully")
|
||||||
@@ -271,8 +270,8 @@ func (k *KubernetesService) updateFromItem(item *unstructured.Unstructured) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KubernetesService) resyncGVR(gvr schema.GroupVersionResource) error {
|
func (k *KubernetesService) resyncGVR(gvr schema.GroupVersionResource, ctx context.Context) error {
|
||||||
ctx, cancel := context.WithTimeout(k.ctx, 30*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
list, err := k.client.Resource(gvr).List(ctx, metav1.ListOptions{})
|
list, err := k.client.Resource(gvr).List(ctx, metav1.ListOptions{})
|
||||||
@@ -289,10 +288,10 @@ func (k *KubernetesService) resyncGVR(gvr schema.GroupVersionResource) error {
|
|||||||
|
|
||||||
// runWatcher drains events from an active watcher until it closes or the context is done.
|
// 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.
|
// 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) bool {
|
func (k *KubernetesService) runWatcher(gvr schema.GroupVersionResource, w watch.Interface, resyncTicker *time.Ticker, ctx context.Context) bool {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-k.ctx.Done():
|
case <-ctx.Done():
|
||||||
w.Stop()
|
w.Stop()
|
||||||
return false
|
return false
|
||||||
case event, ok := <-w.ResultChan():
|
case event, ok := <-w.ResultChan():
|
||||||
@@ -314,33 +313,33 @@ func (k *KubernetesService) runWatcher(gvr schema.GroupVersionResource, w watch.
|
|||||||
k.removeIngress(item.GetNamespace(), item.GetName())
|
k.removeIngress(item.GetNamespace(), item.GetName())
|
||||||
}
|
}
|
||||||
case <-resyncTicker.C:
|
case <-resyncTicker.C:
|
||||||
if err := k.resyncGVR(gvr); err != nil {
|
if err := k.resyncGVR(gvr, ctx); err != nil {
|
||||||
k.log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Periodic resync failed during watcher run")
|
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) {
|
func (k *KubernetesService) watchGVR(gvr schema.GroupVersionResource, ctx context.Context) {
|
||||||
resyncTicker := time.NewTicker(5 * time.Minute)
|
resyncTicker := time.NewTicker(5 * time.Minute)
|
||||||
defer resyncTicker.Stop()
|
defer resyncTicker.Stop()
|
||||||
|
|
||||||
if err := k.resyncGVR(gvr); err != nil {
|
if err := k.resyncGVR(gvr, ctx); err != nil {
|
||||||
k.log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Initial resync failed, will retry")
|
k.log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Initial resync failed, will retry")
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-k.ctx.Done():
|
case <-ctx.Done():
|
||||||
k.log.App.Debug().Str("api", gvr.GroupVersion().String()).Msg("Shutting down kubernetes watcher")
|
k.log.App.Debug().Str("api", gvr.GroupVersion().String()).Msg("Shutting down kubernetes watcher")
|
||||||
return
|
return
|
||||||
case <-resyncTicker.C:
|
case <-resyncTicker.C:
|
||||||
if err := k.resyncGVR(gvr); err != nil {
|
if err := k.resyncGVR(gvr, ctx); err != nil {
|
||||||
k.log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Periodic resync failed, will retry")
|
k.log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Periodic resync failed, will retry")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
ctx, cancel := context.WithCancel(k.ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
watcher, err := k.client.Resource(gvr).Watch(ctx, metav1.ListOptions{})
|
watcher, err := k.client.Resource(gvr).Watch(ctx, metav1.ListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
k.log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Failed to start watcher, will retry")
|
k.log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Failed to start watcher, will retry")
|
||||||
@@ -349,7 +348,7 @@ func (k *KubernetesService) watchGVR(gvr schema.GroupVersionResource) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
k.log.App.Debug().Str("api", gvr.GroupVersion().String()).Msg("Watcher started successfully")
|
k.log.App.Debug().Str("api", gvr.GroupVersion().String()).Msg("Watcher started successfully")
|
||||||
if !k.runWatcher(gvr, watcher, resyncTicker) {
|
if !k.runWatcher(gvr, watcher, resyncTicker, ctx) {
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ import (
|
|||||||
|
|
||||||
"github.com/cenkalti/backoff/v5"
|
"github.com/cenkalti/backoff/v5"
|
||||||
ldapgo "github.com/go-ldap/ldap/v3"
|
ldapgo "github.com/go-ldap/ldap/v3"
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LdapService struct {
|
type LdapService struct {
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
config model.Config
|
config model.Config
|
||||||
context context.Context
|
|
||||||
|
|
||||||
conn *ldapgo.Conn
|
conn *ldapgo.Conn
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
@@ -26,17 +26,15 @@ type LdapService struct {
|
|||||||
func NewLdapService(
|
func NewLdapService(
|
||||||
log *logger.Logger,
|
log *logger.Logger,
|
||||||
config model.Config,
|
config model.Config,
|
||||||
ctx context.Context,
|
dg *ding.Ding,
|
||||||
wg *sync.WaitGroup,
|
|
||||||
) (*LdapService, error) {
|
) (*LdapService, error) {
|
||||||
if config.LDAP.Address == "" {
|
if config.LDAP.Address == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ldap := &LdapService{
|
ldap := &LdapService{
|
||||||
log: log,
|
log: log,
|
||||||
config: config,
|
config: config,
|
||||||
context: ctx,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether authentication with client certificate is possible
|
// Check whether authentication with client certificate is possible
|
||||||
@@ -69,7 +67,7 @@ func NewLdapService(
|
|||||||
return nil, fmt.Errorf("failed to connect to ldap server: %w", err)
|
return nil, fmt.Errorf("failed to connect to ldap server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Go(func() {
|
dg.Go(func(ctx context.Context) {
|
||||||
ldap.log.App.Debug().Msg("Starting LDAP connection heartbeat routine")
|
ldap.log.App.Debug().Msg("Starting LDAP connection heartbeat routine")
|
||||||
|
|
||||||
ticker := time.NewTicker(5 * time.Minute)
|
ticker := time.NewTicker(5 * time.Minute)
|
||||||
@@ -87,12 +85,12 @@ func NewLdapService(
|
|||||||
}
|
}
|
||||||
ldap.log.App.Info().Msg("Successfully reconnected to LDAP server")
|
ldap.log.App.Info().Msg("Successfully reconnected to LDAP server")
|
||||||
}
|
}
|
||||||
case <-ldap.context.Done():
|
case <-ctx.Done():
|
||||||
ldap.log.App.Debug().Msg("LDAP service context cancelled, stopping heartbeat")
|
ldap.log.App.Debug().Msg("LDAP service context cancelled, stopping heartbeat")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}, ding.RingMajor)
|
||||||
|
|
||||||
return ldap, nil
|
return ldap, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-jose/go-jose/v4"
|
"github.com/go-jose/go-jose/v4"
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||||
@@ -116,7 +116,6 @@ type OIDCService struct {
|
|||||||
config model.Config
|
config model.Config
|
||||||
runtime model.RuntimeConfig
|
runtime model.RuntimeConfig
|
||||||
queries repository.Store
|
queries repository.Store
|
||||||
context context.Context
|
|
||||||
|
|
||||||
clients map[string]model.OIDCClientConfig
|
clients map[string]model.OIDCClientConfig
|
||||||
privateKey *rsa.PrivateKey
|
privateKey *rsa.PrivateKey
|
||||||
@@ -129,8 +128,7 @@ func NewOIDCService(
|
|||||||
config model.Config,
|
config model.Config,
|
||||||
runtime model.RuntimeConfig,
|
runtime model.RuntimeConfig,
|
||||||
queries repository.Store,
|
queries repository.Store,
|
||||||
ctx context.Context,
|
dg *ding.Ding) (*OIDCService, error) {
|
||||||
wg *sync.WaitGroup) (*OIDCService, error) {
|
|
||||||
// If not configured, skip init
|
// If not configured, skip init
|
||||||
if len(runtime.OIDCClients) == 0 {
|
if len(runtime.OIDCClients) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -276,7 +274,6 @@ func NewOIDCService(
|
|||||||
config: config,
|
config: config,
|
||||||
runtime: runtime,
|
runtime: runtime,
|
||||||
queries: queries,
|
queries: queries,
|
||||||
context: ctx,
|
|
||||||
|
|
||||||
clients: clients,
|
clients: clients,
|
||||||
privateKey: privateKey,
|
privateKey: privateKey,
|
||||||
@@ -285,7 +282,7 @@ func NewOIDCService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start cleanup routine
|
// Start cleanup routine
|
||||||
wg.Go(service.cleanupRoutine)
|
dg.Go(service.cleanupRoutine, ding.RingMinor)
|
||||||
|
|
||||||
return service, nil
|
return service, nil
|
||||||
}
|
}
|
||||||
@@ -759,7 +756,7 @@ func (service *OIDCService) DeleteOldSession(ctx context.Context, sub string) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup routine - Resource heavy due to the linked tables
|
// Cleanup routine - Resource heavy due to the linked tables
|
||||||
func (service *OIDCService) cleanupRoutine() {
|
func (service *OIDCService) cleanupRoutine(ctx context.Context) {
|
||||||
service.log.App.Debug().Msg("Starting OIDC cleanup routine")
|
service.log.App.Debug().Msg("Starting OIDC cleanup routine")
|
||||||
ticker := time.NewTicker(time.Duration(30) * time.Minute)
|
ticker := time.NewTicker(time.Duration(30) * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
@@ -772,7 +769,7 @@ func (service *OIDCService) cleanupRoutine() {
|
|||||||
currentTime := time.Now().Unix()
|
currentTime := time.Now().Unix()
|
||||||
|
|
||||||
// For the OIDC tokens, if they are expired we delete the userinfo and codes
|
// For the OIDC tokens, if they are expired we delete the userinfo and codes
|
||||||
expiredTokens, err := service.queries.DeleteExpiredOidcTokens(service.context, repository.DeleteExpiredOidcTokensParams{
|
expiredTokens, err := service.queries.DeleteExpiredOidcTokens(ctx, repository.DeleteExpiredOidcTokensParams{
|
||||||
TokenExpiresAt: currentTime,
|
TokenExpiresAt: currentTime,
|
||||||
RefreshTokenExpiresAt: currentTime,
|
RefreshTokenExpiresAt: currentTime,
|
||||||
})
|
})
|
||||||
@@ -782,21 +779,21 @@ func (service *OIDCService) cleanupRoutine() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, expiredToken := range expiredTokens {
|
for _, expiredToken := range expiredTokens {
|
||||||
err := service.DeleteOldSession(service.context, expiredToken.Sub)
|
err := service.DeleteOldSession(ctx, expiredToken.Sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
service.log.App.Warn().Err(err).Msg("Failed to delete session for expired token")
|
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
|
// 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(service.context, currentTime)
|
expiredCodes, err := service.queries.DeleteExpiredOidcCodes(ctx, currentTime)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
service.log.App.Warn().Err(err).Msg("Failed to delete expired codes")
|
service.log.App.Warn().Err(err).Msg("Failed to delete expired codes")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, expiredCode := range expiredCodes {
|
for _, expiredCode := range expiredCodes {
|
||||||
token, err := service.queries.GetOidcTokenBySub(service.context, expiredCode.Sub)
|
token, err := service.queries.GetOidcTokenBySub(ctx, expiredCode.Sub)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, repository.ErrNotFound) {
|
if !errors.Is(err, repository.ErrNotFound) {
|
||||||
@@ -806,7 +803,7 @@ func (service *OIDCService) cleanupRoutine() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if token.TokenExpiresAt < currentTime && token.RefreshTokenExpiresAt < currentTime {
|
if token.TokenExpiresAt < currentTime && token.RefreshTokenExpiresAt < currentTime {
|
||||||
err := service.DeleteOldSession(service.context, expiredCode.Sub)
|
err := service.DeleteOldSession(ctx, expiredCode.Sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
service.log.App.Warn().Err(err).Msg("Failed to delete session for expired code")
|
service.log.App.Warn().Err(err).Msg("Failed to delete session for expired code")
|
||||||
}
|
}
|
||||||
@@ -814,7 +811,7 @@ func (service *OIDCService) cleanupRoutine() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
service.log.App.Debug().Msg("Finished OIDC cleanup routine")
|
service.log.App.Debug().Msg("Finished OIDC cleanup routine")
|
||||||
case <-service.context.Done():
|
case <-ctx.Done():
|
||||||
service.log.App.Debug().Msg("Stopping OIDC cleanup routine")
|
service.log.App.Debug().Msg("Stopping OIDC cleanup routine")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package service_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@@ -70,9 +70,9 @@ func TestCompileUserinfo(t *testing.T) {
|
|||||||
log.Init()
|
log.Init()
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
wg := &sync.WaitGroup{}
|
dg := ding.New(ctx)
|
||||||
|
|
||||||
svc, err := service.NewOIDCService(log, cfg, runtime, nil, ctx, wg)
|
svc, err := service.NewOIDCService(log, cfg, runtime, nil, dg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
"tailscale.com/client/local"
|
"tailscale.com/client/local"
|
||||||
@@ -25,7 +26,6 @@ type TailscaleWhoisResponse struct {
|
|||||||
|
|
||||||
type TailscaleService struct {
|
type TailscaleService struct {
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
wg *sync.WaitGroup
|
|
||||||
config model.Config
|
config model.Config
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ type TailscaleService struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTailscaleService(log *logger.Logger, config model.Config, ctx context.Context, wg *sync.WaitGroup) (*TailscaleService, error) {
|
func NewTailscaleService(log *logger.Logger, config model.Config, ctx context.Context, dg *ding.Ding) (*TailscaleService, error) {
|
||||||
if !config.Tailscale.Enabled {
|
if !config.Tailscale.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,6 @@ func NewTailscaleService(log *logger.Logger, config model.Config, ctx context.Co
|
|||||||
|
|
||||||
service := &TailscaleService{
|
service := &TailscaleService{
|
||||||
log: log,
|
log: log,
|
||||||
wg: wg,
|
|
||||||
config: config,
|
config: config,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
srv: srv,
|
srv: srv,
|
||||||
@@ -84,13 +83,13 @@ func NewTailscaleService(log *logger.Logger, config model.Config, ctx context.Co
|
|||||||
return nil, fmt.Errorf("failed to connect to tailscale network: %w", err)
|
return nil, fmt.Errorf("failed to connect to tailscale network: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Go(service.watchAndClose)
|
dg.Go(service.watchAndClose, ding.RingMajor)
|
||||||
|
|
||||||
return service, nil
|
return service, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *TailscaleService) watchAndClose() {
|
func (ts *TailscaleService) watchAndClose(ctx context.Context) {
|
||||||
<-ts.ctx.Done()
|
<-ctx.Done()
|
||||||
ts.log.App.Debug().Msg("Shutting down Tailscale service")
|
ts.log.App.Debug().Msg("Shutting down Tailscale service")
|
||||||
ts.mu.Lock()
|
ts.mu.Lock()
|
||||||
srv := ts.srv
|
srv := ts.srv
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package loaders
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/tinyauthapp/paerser/cli"
|
"github.com/tinyauthapp/paerser/cli"
|
||||||
"github.com/tinyauthapp/paerser/file"
|
"github.com/tinyauthapp/paerser/file"
|
||||||
"github.com/tinyauthapp/paerser/flag"
|
"github.com/tinyauthapp/paerser/flag"
|
||||||
@@ -19,8 +18,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)
|
// I guess we are using traefik as the root name (we can't change it)
|
||||||
configFileFlag := "traefik.experimental.configfile"
|
configFileFlag := "traefik.configfile"
|
||||||
envVar := "TINYAUTH_EXPERIMENTAL_CONFIGFILE"
|
envVar := "TINYAUTH_CONFIGFILE"
|
||||||
|
|
||||||
if _, ok := flags[configFileFlag]; !ok {
|
if _, ok := flags[configFileFlag]; !ok {
|
||||||
if value := os.Getenv(envVar); value != "" {
|
if value := os.Getenv(envVar); value != "" {
|
||||||
@@ -30,8 +29,6 @@ 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)
|
err = file.Decode(flags[configFileFlag], cmd.Configuration)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user