mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-07-08 11:10:16 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba18c1bda0 |
+17
-11
@@ -220,17 +220,6 @@ TINYAUTH_LDAP_AUTHCERT=
|
||||
TINYAUTH_LDAP_AUTHKEY=
|
||||
# Cache duration for LDAP group membership in seconds.
|
||||
TINYAUTH_LDAP_GROUPCACHETTL=900
|
||||
|
||||
# tailscale config
|
||||
|
||||
# Enable Tailscale integration.
|
||||
TINYAUTH_TAILSCALE_ENABLED=false
|
||||
# Tailscale API token.
|
||||
TINYAUTH_TAILSCALE_APITOKEN=
|
||||
# Tailnet name.
|
||||
TINYAUTH_TAILSCALE_TAILNET=
|
||||
# Cache duration for Tailscale device and user lists in seconds.
|
||||
TINYAUTH_TAILSCALE_CACHEDURATION=300
|
||||
# Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment.
|
||||
TINYAUTH_LABELPROVIDER="auto"
|
||||
|
||||
@@ -252,3 +241,20 @@ TINYAUTH_LOG_STREAMS_APP_LEVEL=
|
||||
TINYAUTH_LOG_STREAMS_AUDIT_ENABLED=false
|
||||
# Log level for this stream. Use global if empty.
|
||||
TINYAUTH_LOG_STREAMS_AUDIT_LEVEL=
|
||||
|
||||
# tailscale config
|
||||
|
||||
# Enable Tailscale integration.
|
||||
TINYAUTH_TAILSCALE_ENABLED=false
|
||||
# Tailscale state directory.
|
||||
TINYAUTH_TAILSCALE_DIR="./tailscale_state"
|
||||
# Tailscale hostname.
|
||||
TINYAUTH_TAILSCALE_HOSTNAME=
|
||||
# Tailscale auth key.
|
||||
TINYAUTH_TAILSCALE_AUTHKEY=
|
||||
# Use ephemeral Tailscale node.
|
||||
TINYAUTH_TAILSCALE_EPHEMERAL=false
|
||||
# Enable Tailscale Funnel.
|
||||
TINYAUTH_TAILSCALE_FUNNEL=false
|
||||
# Listen on the Tailscale address instead of standard address.
|
||||
TINYAUTH_TAILSCALE_LISTEN=false
|
||||
|
||||
@@ -36,9 +36,9 @@ jobs:
|
||||
- name: Check codegen is up to date
|
||||
run: |
|
||||
sqlc generate
|
||||
go generate ./...
|
||||
git diff --exit-code
|
||||
git status --porcelain | grep -q . && echo "untracked files code gen files" && exit 1 || true
|
||||
go generate ./internal/repository/...
|
||||
git diff --exit-code -- internal/repository/
|
||||
git status --porcelain -- internal/repository/ | grep -q . && echo "untracked files in internal/repository/" && exit 1 || true
|
||||
|
||||
- name: Install frontend dependencies
|
||||
working-directory: ./frontend
|
||||
|
||||
@@ -35,7 +35,6 @@ jobs:
|
||||
VERSION: ${{ steps.metadata.outputs.VERSION }}
|
||||
COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
|
||||
BUILD_TAGS: ${{ steps.metadata.outputs.BUILD_TAGS }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
@@ -48,8 +47,6 @@ jobs:
|
||||
echo "VERSION=nightly" >> "$GITHUB_OUTPUT"
|
||||
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT"
|
||||
export GIN_BUILD_TAGS=nomsgpack
|
||||
echo "BUILD_TAGS=$GIN_BUILD_TAGS" >> "$GITHUB_OUTPUT"
|
||||
|
||||
binary-build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -86,11 +83,7 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
go build -tags ${{ needs.generate-metadata.outputs.BUILD_TAGS }} \
|
||||
-ldflags "-X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" \
|
||||
-o tinyauth-amd64 ./cmd/tinyauth
|
||||
go build -ldflags "-X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 ./cmd/tinyauth
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
@@ -135,11 +128,7 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
go build -tags ${{ needs.generate-metadata.outputs.BUILD_TAGS }} \
|
||||
-ldflags "-X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" \
|
||||
-o tinyauth-arm64 ./cmd/tinyauth
|
||||
go build -ldflags "-X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 ./cmd/tinyauth
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
@@ -177,7 +166,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
@@ -191,7 +180,6 @@ jobs:
|
||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
@@ -202,7 +190,7 @@ jobs:
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: digests-alpine-linux-amd64
|
||||
name: digests-linux-amd64
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
@@ -236,7 +224,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
@@ -251,7 +239,6 @@ jobs:
|
||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
@@ -295,7 +282,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/arm64
|
||||
@@ -309,7 +296,6 @@ jobs:
|
||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
@@ -320,7 +306,7 @@ jobs:
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: digests-alpine-linux-arm64
|
||||
name: digests-linux-arm64
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
@@ -354,7 +340,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/arm64
|
||||
@@ -369,7 +355,6 @@ jobs:
|
||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
@@ -395,7 +380,7 @@ jobs:
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-alpine-*
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
|
||||
@@ -16,21 +16,16 @@ jobs:
|
||||
VERSION: ${{ steps.metadata.outputs.VERSION }}
|
||||
COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
|
||||
BUILD_TAGS: ${{ steps.metadata.outputs.BUILD_TAGS }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Generate metadata
|
||||
id: metadata
|
||||
env:
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
echo "VERSION=$REF_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "VERSION=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
|
||||
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT"
|
||||
export GIN_BUILD_TAGS=nomsgpack
|
||||
echo "BUILD_TAGS=$GIN_BUILD_TAGS" >> "$GITHUB_OUTPUT"
|
||||
|
||||
binary-build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -64,11 +59,7 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
go build -tags ${{ needs.generate-metadata.outputs.BUILD_TAGS }} \
|
||||
-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 "-s -w -X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 ./cmd/tinyauth
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
@@ -110,11 +101,7 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
go build -tags ${{ needs.generate-metadata.outputs.BUILD_TAGS }} \
|
||||
-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 "-s -w -X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 ./cmd/tinyauth
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
@@ -149,7 +136,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
@@ -164,7 +151,6 @@ jobs:
|
||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||
LDFLAGS=-s -w
|
||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
@@ -175,7 +161,7 @@ jobs:
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: digests-alpine-linux-amd64
|
||||
name: digests-linux-amd64
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
@@ -206,7 +192,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
@@ -222,7 +208,6 @@ jobs:
|
||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||
LDFLAGS=-s -w
|
||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
@@ -263,7 +248,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/arm64
|
||||
@@ -278,7 +263,6 @@ jobs:
|
||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||
LDFLAGS=-s -w
|
||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
@@ -289,7 +273,7 @@ jobs:
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: digests-alpine-linux-arm64
|
||||
name: digests-linux-arm64
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
@@ -320,7 +304,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||
id: build
|
||||
with:
|
||||
platforms: linux/arm64
|
||||
@@ -336,7 +320,6 @@ jobs:
|
||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||
LDFLAGS=-s -w
|
||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
@@ -362,7 +345,7 @@ jobs:
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-alpine-*
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
|
||||
@@ -51,6 +51,3 @@ config.certify.yml
|
||||
|
||||
# deepsec
|
||||
/.deepsec
|
||||
|
||||
# jetbrains
|
||||
/.idea/
|
||||
|
||||
+2
-4
@@ -28,7 +28,6 @@ ARG VERSION
|
||||
ARG COMMIT_HASH
|
||||
ARG BUILD_TIMESTAMP
|
||||
ARG LDFLAGS
|
||||
ARG BUILD_TAGS
|
||||
|
||||
WORKDIR /tinyauth
|
||||
|
||||
@@ -41,11 +40,10 @@ COPY ./cmd ./cmd
|
||||
COPY ./internal ./internal
|
||||
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
||||
|
||||
RUN CGO_ENABLED=0 go build -tags "${BUILD_TAGS}" -ldflags "${LDFLAGS} \
|
||||
RUN CGO_ENABLED=0 go build -ldflags "${LDFLAGS} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
||||
-o tinyauth ./cmd/tinyauth
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
||||
|
||||
# Runner
|
||||
FROM alpine:3.24 AS runner
|
||||
|
||||
@@ -28,7 +28,6 @@ ARG VERSION
|
||||
ARG COMMIT_HASH
|
||||
ARG BUILD_TIMESTAMP
|
||||
ARG LDFLAGS
|
||||
ARG BUILD_TAGS
|
||||
|
||||
WORKDIR /tinyauth
|
||||
|
||||
@@ -37,15 +36,14 @@ COPY go.sum ./
|
||||
|
||||
RUN go mod download
|
||||
|
||||
COPY ./cmd ./cmd/
|
||||
COPY ./cmd ./cmd
|
||||
COPY ./internal ./internal
|
||||
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
||||
|
||||
RUN CGO_ENABLED=0 go build -tags "${BUILD_TAGS}" -ldflags "${LDFLAGS} \
|
||||
RUN CGO_ENABLED=0 go build -ldflags "${LDFLAGS} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
||||
-o tinyauth ./cmd/tinyauth
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
||||
|
||||
# Make the data directory with a non-root user
|
||||
RUN addgroup tinyauth && adduser -DH tinyauth -G tinyauth
|
||||
|
||||
@@ -9,9 +9,6 @@ COMMIT_HASH := $(shell git rev-parse HEAD)
|
||||
BUILD_TIMESTAMP := $(shell date '+%Y-%m-%dT%H:%M:%S')
|
||||
BIN_NAME := tinyauth-$(GOARCH)
|
||||
LDFLAGS := -s -w
|
||||
# Whatever 6MB serialization lib Gin is using
|
||||
GIN_BUILD_TAGS := nomsgpack
|
||||
BUILD_TAGS = $(GIN_BUILD_TAGS)
|
||||
|
||||
# Development vars
|
||||
DEV_COMPOSE := $(shell test -f "docker-compose.test.yml" && echo "docker-compose.test.yml" || echo "docker-compose.dev.yml" )
|
||||
@@ -42,7 +39,7 @@ webui: clean-webui
|
||||
|
||||
# Build the binary
|
||||
binary: webui
|
||||
CGO_ENABLED=$(CGO_ENABLED) go build -tags "${BUILD_TAGS}" -ldflags "${LDFLAGS} \
|
||||
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.CommitHash=${COMMIT_HASH} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
||||
@@ -96,22 +93,13 @@ sql:
|
||||
|
||||
# Go gen
|
||||
generate:
|
||||
go generate ./...
|
||||
go run ./gen
|
||||
go generate ./internal/repository/...
|
||||
|
||||
# Docker image
|
||||
docker:
|
||||
docker buildx build -t tinyauthapp/tinyauth:dev \
|
||||
--build-arg=VERSION=$(TAG_NAME) \
|
||||
--build-arg=COMMIT_HASH=$(COMMIT_HASH) \
|
||||
--build-arg=BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) \
|
||||
--build-arg=BUILD_TAGS=$(BUILD_TAGS) \
|
||||
-f Dockerfile .
|
||||
docker buildx build -t tinyauthapp/tinyauth:dev --build-arg=VERSION=$(TAG_NAME) --build-arg=COMMIT_HASH=$(COMMIT_HASH) --build-arg=BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) -f Dockerfile .
|
||||
|
||||
# Docker image distroless
|
||||
docker-distroless:
|
||||
docker buildx build -t tinyauthapp/tinyauth:dev-distroless \
|
||||
--build-arg=VERSION=$(TAG_NAME) \
|
||||
--build-arg=COMMIT_HASH=$(COMMIT_HASH) \
|
||||
--build-arg=BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) \
|
||||
--build-arg=BUILD_TAGS=$(BUILD_TAGS) \
|
||||
-f Dockerfile.distroless .
|
||||
docker buildx build -t tinyauthapp/tinyauth:dev-distroless --build-arg=VERSION=$(TAG_NAME) --build-arg=COMMIT_HASH=$(COMMIT_HASH) --build-arg=BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) -f Dockerfile.distroless .
|
||||
|
||||
+5
-11
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tinyauthapp/paerser/cli"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
@@ -11,21 +11,15 @@ import (
|
||||
func configCmd(tconfig *model.Config, loaders []cli.ResourceLoader) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "config",
|
||||
Description: "Dump the current configuration in YAML format, useful for debugging",
|
||||
Description: "Print the configuration of Tinyauth",
|
||||
Configuration: tconfig,
|
||||
Resources: loaders,
|
||||
Run: func(_ []string) error {
|
||||
buf := strings.Builder{}
|
||||
|
||||
fmt.Fprint(&buf, "Your current configuration in YAML is:\n\n")
|
||||
|
||||
err := renderYamlToBuf(&buf, tconfig)
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(tconfig, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to render yaml config: %w", err)
|
||||
return fmt.Errorf("failed to marshal configuration: %w", err)
|
||||
}
|
||||
|
||||
fmt.Print(buf.String())
|
||||
fmt.Println(string(jsonBytes))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -7,9 +7,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/tinyauthapp/paerser/cli"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/paerser/cli"
|
||||
)
|
||||
|
||||
func createOidcClientCmd() *cli.Command {
|
||||
@@ -32,84 +31,40 @@ func createOidcClientCmd() *cli.Command {
|
||||
return errors.New("client name can only contain alphanumeric characters and hyphens")
|
||||
}
|
||||
|
||||
u := uuid.New()
|
||||
clientId := u.String()
|
||||
uuid := uuid.New()
|
||||
clientId := uuid.String()
|
||||
clientSecret := "ta-" + utils.GenerateString(61)
|
||||
|
||||
uclientName := strings.ToUpper(clientName)
|
||||
lclientName := strings.ToLower(clientName)
|
||||
|
||||
buf := strings.Builder{}
|
||||
builder := strings.Builder{}
|
||||
|
||||
// header
|
||||
fmt.Fprintf(&buf, "Created '%s' OIDC client.\n\n", clientName)
|
||||
fmt.Fprintf(&builder, "Created credentials for client %s\n\n", clientName)
|
||||
|
||||
// credentials
|
||||
fmt.Fprintf(&buf, "Credentials:\n\n")
|
||||
fmt.Fprintf(&buf, "Client Name: %s\n", clientName)
|
||||
fmt.Fprintf(&buf, "Client ID: %s\n", clientId)
|
||||
fmt.Fprintf(&buf, "Client Secret: %s\n\n", clientSecret)
|
||||
fmt.Fprintf(&builder, "Client Name: %s\n", clientName)
|
||||
fmt.Fprintf(&builder, "Client ID: %s\n", clientId)
|
||||
fmt.Fprintf(&builder, "Client Secret: %s\n\n", clientSecret)
|
||||
|
||||
// end variables
|
||||
fmt.Fprintf(&buf, "Environment variables:\n\n")
|
||||
renderToBuf(&buf, []kv{
|
||||
{
|
||||
k: fmt.Sprintf("TINYAUTH_OIDC_CLIENTS_%s_CLIENTID", uclientName),
|
||||
v: clientId,
|
||||
},
|
||||
{
|
||||
k: fmt.Sprintf("TINYAUTH_OIDC_CLIENTS_%s_CLIENTSECRET", uclientName),
|
||||
v: clientSecret,
|
||||
},
|
||||
{
|
||||
k: fmt.Sprintf("TINYAUTH_OIDC_CLIENTS_%s_NAME", uclientName),
|
||||
v: utils.Capitalize(lclientName),
|
||||
},
|
||||
}, "=")
|
||||
fmt.Fprintf(&buf, "\n")
|
||||
// env variables
|
||||
fmt.Fprint(&builder, "Environment variables:\n\n")
|
||||
fmt.Fprintf(&builder, "TINYAUTH_OIDC_CLIENTS_%s_CLIENTID=%s\n", uclientName, clientId)
|
||||
fmt.Fprintf(&builder, "TINYAUTH_OIDC_CLIENTS_%s_CLIENTSECRET=%s\n", uclientName, clientSecret)
|
||||
fmt.Fprintf(&builder, "TINYAUTH_OIDC_CLIENTS_%s_NAME=%s\n\n", uclientName, utils.Capitalize(lclientName))
|
||||
|
||||
// cli flags
|
||||
fmt.Fprintf(&buf, "CLI flags:\n\n")
|
||||
renderToBuf(&buf, []kv{
|
||||
{
|
||||
k: fmt.Sprintf("--oidc.clients.%s.clientid", lclientName),
|
||||
v: clientId,
|
||||
},
|
||||
{
|
||||
k: fmt.Sprintf("--oidc.clients.%s.clientsecret", lclientName),
|
||||
v: clientSecret,
|
||||
},
|
||||
{
|
||||
k: fmt.Sprintf("--oidc.clients.%s.name", lclientName),
|
||||
v: utils.Capitalize(lclientName),
|
||||
},
|
||||
}, "=")
|
||||
fmt.Fprintf(&buf, "\n")
|
||||
|
||||
// yaml config
|
||||
fmt.Fprintf(&buf, "YAML config:\n\n")
|
||||
|
||||
err = renderYamlToBuf(&buf, &model.OIDCConfig{
|
||||
Clients: map[string]model.OIDCClientConfig{
|
||||
lclientName: {
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
Name: utils.Capitalize(lclientName),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to render yaml config: %w", err)
|
||||
}
|
||||
|
||||
buf.WriteString("\n")
|
||||
fmt.Fprint(&builder, "CLI flags:\n\n")
|
||||
fmt.Fprintf(&builder, "--oidc.clients.%s.clientid=%s\n", lclientName, clientId)
|
||||
fmt.Fprintf(&builder, "--oidc.clients.%s.clientsecret=%s\n", lclientName, clientSecret)
|
||||
fmt.Fprintf(&builder, "--oidc.clients.%s.name=%s\n\n", lclientName, utils.Capitalize(lclientName))
|
||||
|
||||
// footer
|
||||
fmt.Fprintln(&buf, "You can use any of the above options to configure your OIDC client. Make sure to save these credentials as there is no way to regenerate them.")
|
||||
fmt.Fprintln(&builder, "You can use either option to configure your OIDC client. Make sure to save these credentials as there is no way to regenerate them.")
|
||||
|
||||
// print
|
||||
out := buf.String()
|
||||
out := builder.String()
|
||||
fmt.Print(out)
|
||||
return nil
|
||||
},
|
||||
|
||||
+44
-90
@@ -3,12 +3,11 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"charm.land/huh/v2"
|
||||
"github.com/tinyauthapp/paerser/cli"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -35,107 +34,62 @@ func createUserCmd() *cli.Command {
|
||||
&cli.FlagLoader{},
|
||||
}
|
||||
|
||||
cmd := &cli.Command{
|
||||
return &cli.Command{
|
||||
Name: "create",
|
||||
Description: "Create a user",
|
||||
Configuration: tCfg,
|
||||
Resources: loaders,
|
||||
}
|
||||
Run: func(_ []string) error {
|
||||
log := logger.NewLogger().WithSimpleConfig()
|
||||
log.Init()
|
||||
|
||||
cmd.Run = func(_ []string) error {
|
||||
if tCfg.Interactive {
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().Title("Username").Value(&tCfg.Username).Validate(func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("username cannot be empty")
|
||||
}
|
||||
if strings.Contains(s, ":") {
|
||||
return errors.New("username cannot contain ':'")
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
huh.NewInput().Title("Password").Value(&tCfg.Password).Validate(func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("password cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
huh.NewSelect[bool]().Title("Format the output for Docker?").Options(huh.NewOption("Yes", true), huh.NewOption("No", false)).Value(&tCfg.Docker),
|
||||
),
|
||||
)
|
||||
if tCfg.Interactive {
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().Title("Username").Value(&tCfg.Username).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("username cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
huh.NewInput().Title("Password").Value(&tCfg.Password).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("password cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
huh.NewSelect[bool]().Title("Format the output for Docker?").Options(huh.NewOption("Yes", true), huh.NewOption("No", false)).Value(&tCfg.Docker),
|
||||
),
|
||||
)
|
||||
|
||||
theme := new(themeBase)
|
||||
theme := new(themeBase)
|
||||
err := form.WithTheme(theme).Run()
|
||||
|
||||
err := form.WithTheme(theme).Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run interactive prompt: %w", err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run interactive prompt: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tCfg.Username == "" || tCfg.Password == "" {
|
||||
cmd.PrintHelp(os.Stdout)
|
||||
return errors.New("username and password cannot be empty")
|
||||
}
|
||||
if tCfg.Username == "" || tCfg.Password == "" {
|
||||
return errors.New("username and password cannot be empty")
|
||||
}
|
||||
|
||||
if strings.Contains(tCfg.Username, ":") {
|
||||
return errors.New("username cannot contain ':'")
|
||||
}
|
||||
log.App.Info().Str("username", tCfg.Username).Msg("Creating user")
|
||||
|
||||
passwd, err := bcrypt.GenerateFromPassword([]byte(tCfg.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to hash password: %w", err)
|
||||
}
|
||||
passwd, err := bcrypt.GenerateFromPassword([]byte(tCfg.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to hash password: %w", err)
|
||||
}
|
||||
|
||||
// Only the docker compose output needs $ escaped, the raw hash is correct everywhere else
|
||||
passwdStr := string(passwd)
|
||||
outputStr := passwdStr
|
||||
// If docker format is enabled, escape the dollar sign
|
||||
passwdStr := string(passwd)
|
||||
if tCfg.Docker {
|
||||
passwdStr = strings.ReplaceAll(passwdStr, "$", "$$")
|
||||
}
|
||||
|
||||
if tCfg.Docker {
|
||||
outputStr = strings.ReplaceAll(passwdStr, "$", "$$")
|
||||
}
|
||||
log.App.Info().Str("user", fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)).Msg("User created")
|
||||
|
||||
user := fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)
|
||||
escapedUser := fmt.Sprintf("%s:%s", tCfg.Username, outputStr)
|
||||
|
||||
buf := strings.Builder{}
|
||||
|
||||
// header
|
||||
fmt.Fprintf(&buf, "Created user '%s'.\n\n", tCfg.Username)
|
||||
|
||||
// environment variable
|
||||
fmt.Fprint(&buf, "Environment variable:\n\n")
|
||||
renderToBuf(&buf, []kv{
|
||||
{"TINYAUTH_AUTH_USERS", escapedUser},
|
||||
}, "=")
|
||||
|
||||
// cli flags
|
||||
fmt.Fprint(&buf, "\nCLI flags:\n\n")
|
||||
renderToBuf(&buf, []kv{
|
||||
{"--auth.users", user},
|
||||
}, "=")
|
||||
|
||||
// yaml config
|
||||
fmt.Fprint(&buf, "\nYAML config:\n\n")
|
||||
|
||||
err = renderYamlToBuf(&buf, &model.Config{
|
||||
Auth: model.AuthConfig{
|
||||
Users: []string{user},
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to render yaml config: %w", err)
|
||||
}
|
||||
|
||||
buf.WriteString("\n")
|
||||
|
||||
// footer
|
||||
fmt.Fprint(&buf, "Use your config option of choice to add the user to Tinyauth and then restart.")
|
||||
|
||||
fmt.Println(buf.String())
|
||||
return nil
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
|
||||
"charm.land/huh/v2"
|
||||
"github.com/mdp/qrterminal/v3"
|
||||
@@ -33,98 +34,85 @@ func generateTotpCmd() *cli.Command {
|
||||
&cli.FlagLoader{},
|
||||
}
|
||||
|
||||
cmd := &cli.Command{
|
||||
return &cli.Command{
|
||||
Name: "generate",
|
||||
Description: "Generate a TOTP secret",
|
||||
Configuration: tCfg,
|
||||
Resources: loaders,
|
||||
}
|
||||
Run: func(_ []string) error {
|
||||
log := logger.NewLogger().WithSimpleConfig()
|
||||
log.Init()
|
||||
|
||||
cmd.Run = func(_ []string) error {
|
||||
colors := getColors()
|
||||
if tCfg.Interactive {
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().Title("Current user (username:hash)").Value(&tCfg.User).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("user cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
),
|
||||
)
|
||||
|
||||
if tCfg.Interactive {
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().Title("Current user (username:hash)").Value(&tCfg.User).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("user cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
),
|
||||
)
|
||||
theme := new(themeBase)
|
||||
err := form.WithTheme(theme).Run()
|
||||
|
||||
theme := new(themeBase)
|
||||
err := form.WithTheme(theme).Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run interactive prompt: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
user, err := utils.ParseUser(tCfg.User)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run interactive prompt: %w", err)
|
||||
return fmt.Errorf("failed to parse user: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if tCfg.User == "" {
|
||||
cmd.PrintHelp(os.Stdout)
|
||||
return fmt.Errorf("user is required")
|
||||
}
|
||||
docker := false
|
||||
if strings.Contains(tCfg.User, "$$") {
|
||||
docker = true
|
||||
}
|
||||
|
||||
user, err := utils.ParseUser(tCfg.User)
|
||||
if user.TOTPSecret != "" {
|
||||
return fmt.Errorf("user already has a TOTP secret")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse user: %w", err)
|
||||
}
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "Tinyauth",
|
||||
AccountName: user.Username,
|
||||
})
|
||||
|
||||
docker := false
|
||||
if strings.Contains(tCfg.User, "$$") {
|
||||
docker = true
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate TOTP secret: %w", err)
|
||||
}
|
||||
|
||||
if user.TOTPSecret != "" {
|
||||
return fmt.Errorf("user already has a TOTP secret")
|
||||
}
|
||||
secret := key.Secret()
|
||||
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "Tinyauth",
|
||||
AccountName: user.Username,
|
||||
})
|
||||
log.App.Info().Str("secret", secret).Msg("Generated TOTP secret")
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate TOTP secret: %w", err)
|
||||
}
|
||||
log.App.Info().Msg("Generated QR code")
|
||||
|
||||
secret := key.Secret()
|
||||
config := qrterminal.Config{
|
||||
Level: qrterminal.L,
|
||||
Writer: os.Stdout,
|
||||
BlackChar: qrterminal.BLACK,
|
||||
WhiteChar: qrterminal.WHITE,
|
||||
QuietZone: 2,
|
||||
}
|
||||
|
||||
fmt.Printf("Scan the following QR code with your authenticator app (e.g., Google Authenticator, 2fauth, Microsoft Authenticator):\n\n")
|
||||
qrterminal.GenerateWithConfig(key.URL(), config)
|
||||
|
||||
config := qrterminal.Config{
|
||||
Level: qrterminal.L,
|
||||
Writer: os.Stdout,
|
||||
BlackChar: qrterminal.BLACK,
|
||||
WhiteChar: qrterminal.WHITE,
|
||||
QuietZone: 2,
|
||||
}
|
||||
user.TOTPSecret = secret
|
||||
|
||||
qrterminal.GenerateWithConfig(key.URL(), config)
|
||||
// If using docker escape re-escape it
|
||||
if docker {
|
||||
user.Password = strings.ReplaceAll(user.Password, "$", "$$")
|
||||
}
|
||||
|
||||
user.TOTPSecret = secret
|
||||
log.App.Info().Str("user", fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TOTPSecret)).Msg("Add the totp secret to your authenticator app then use the verify command to ensure everything is working correctly.")
|
||||
|
||||
// If using docker escape re-escape it
|
||||
if docker {
|
||||
user.Password = strings.ReplaceAll(user.Password, "$", "$$")
|
||||
}
|
||||
|
||||
userStr := fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TOTPSecret)
|
||||
|
||||
fmt.Print("\nOr add the following TOTP secret to your authenticator app: ")
|
||||
fmt.Print(colors.green.Render(secret))
|
||||
fmt.Print("\n\n")
|
||||
|
||||
fmt.Printf("Finally, add your user '%s' back to your configuration: ", user.Username)
|
||||
fmt.Print(colors.green.Render(userStr))
|
||||
fmt.Print("\n")
|
||||
|
||||
return nil
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+16
-140
@@ -2,17 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"charm.land/huh/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/loaders"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/tinyauthapp/paerser/cli"
|
||||
)
|
||||
|
||||
@@ -32,114 +28,89 @@ func main() {
|
||||
Configuration: tConfig,
|
||||
Resources: loaders,
|
||||
Run: func(_ []string) error {
|
||||
if !reflect.DeepEqual(model.NewDefaultConfiguration(env).Experimental, tConfig.Experimental) {
|
||||
colors := getColors()
|
||||
fmt.Println(colors.yellow.Render("⚠") + " Experimental features are enabled, use with caution. Experimental features may change with each release.")
|
||||
}
|
||||
return runCmd(*tConfig)
|
||||
},
|
||||
}
|
||||
|
||||
cmdUser := &cli.Command{
|
||||
Name: "user",
|
||||
Description: "Manage users",
|
||||
Description: "Manage Tinyauth users",
|
||||
}
|
||||
|
||||
cmdTotp := &cli.Command{
|
||||
Name: "totp",
|
||||
Description: "Manage TOTP users",
|
||||
Description: "Manage Tinyauth TOTP users",
|
||||
}
|
||||
|
||||
cmdOidc := &cli.Command{
|
||||
Name: "oidc",
|
||||
Description: "Manage OIDC clients",
|
||||
Description: "Manage Tinyauth OIDC clients",
|
||||
}
|
||||
|
||||
helpCmd := &cli.Command{
|
||||
Name: "help",
|
||||
Description: "Show the help message",
|
||||
Run: func(_ []string) error {
|
||||
return cmdTinyauth.PrintHelp(os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
err := cmdTinyauth.AddCommand(helpCmd)
|
||||
err := cmdTinyauth.AddCommand(versionCmd())
|
||||
|
||||
if err != nil {
|
||||
fatalf(err, "Failed to add help command")
|
||||
}
|
||||
|
||||
err = cmdTinyauth.AddCommand(versionCmd())
|
||||
|
||||
if err != nil {
|
||||
fatalf(err, "Failed to add version command")
|
||||
log.Fatal().Err(err).Msg("Failed to add version command")
|
||||
}
|
||||
|
||||
err = cmdTinyauth.AddCommand(configCmd(tConfig, loaders))
|
||||
|
||||
if err != nil {
|
||||
fatalf(err, "Failed to add config command")
|
||||
log.Fatal().Err(err).Msg("Failed to add config command")
|
||||
}
|
||||
|
||||
err = cmdUser.AddCommand(verifyUserCmd())
|
||||
|
||||
if err != nil {
|
||||
fatalf(err, "Failed to add user verify command")
|
||||
log.Fatal().Err(err).Msg("Failed to add verify command")
|
||||
}
|
||||
|
||||
err = cmdTinyauth.AddCommand(healthcheckCmd())
|
||||
|
||||
if err != nil {
|
||||
fatalf(err, "Failed to add healthcheck command")
|
||||
log.Fatal().Err(err).Msg("Failed to add healthcheck command")
|
||||
}
|
||||
|
||||
err = cmdTotp.AddCommand(generateTotpCmd())
|
||||
|
||||
if err != nil {
|
||||
fatalf(err, "Failed to add totp generate command")
|
||||
log.Fatal().Err(err).Msg("Failed to add generate command")
|
||||
}
|
||||
|
||||
err = cmdUser.AddCommand(createUserCmd())
|
||||
|
||||
if err != nil {
|
||||
fatalf(err, "Failed to add create user command")
|
||||
log.Fatal().Err(err).Msg("Failed to add create command")
|
||||
}
|
||||
|
||||
err = cmdOidc.AddCommand(createOidcClientCmd())
|
||||
|
||||
if err != nil {
|
||||
fatalf(err, "Failed to add create oidc client command")
|
||||
log.Fatal().Err(err).Msg("Failed to add create command")
|
||||
}
|
||||
|
||||
err = cmdTinyauth.AddCommand(cmdUser)
|
||||
|
||||
if err != nil {
|
||||
fatalf(err, "Failed to add user command")
|
||||
log.Fatal().Err(err).Msg("Failed to add user command")
|
||||
}
|
||||
|
||||
err = cmdTinyauth.AddCommand(cmdTotp)
|
||||
|
||||
if err != nil {
|
||||
fatalf(err, "Failed to add totp command")
|
||||
log.Fatal().Err(err).Msg("Failed to add totp command")
|
||||
}
|
||||
|
||||
err = cmdTinyauth.AddCommand(cmdOidc)
|
||||
|
||||
if err != nil {
|
||||
fatalf(err, "Failed to add oidc command")
|
||||
log.Fatal().Err(err).Msg("Failed to add oidc command")
|
||||
}
|
||||
|
||||
err = cli.Execute(cmdTinyauth)
|
||||
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "command not found") {
|
||||
fmt.Println("Command not found. Use 'tinyauth help' to see available commands.")
|
||||
return
|
||||
}
|
||||
if strings.Contains(err.Error(), "is not runnable") {
|
||||
return
|
||||
}
|
||||
fatalf(err, "Failed to execute command")
|
||||
log.Fatal().Err(err).Msg("Failed to execute command")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,98 +131,3 @@ type themeBase struct{}
|
||||
func (t *themeBase) Theme(isDark bool) *huh.Styles {
|
||||
return huh.ThemeBase(isDark)
|
||||
}
|
||||
|
||||
type colors struct {
|
||||
blue lipgloss.Style
|
||||
gray lipgloss.Style
|
||||
lightGray lipgloss.Style
|
||||
green lipgloss.Style
|
||||
yellow lipgloss.Style
|
||||
}
|
||||
|
||||
func getColors() colors {
|
||||
noColor := os.Getenv("NO_COLOR")
|
||||
forceColor := os.Getenv("FORCE_COLOR")
|
||||
|
||||
colorOut := colors{
|
||||
green: lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(34)),
|
||||
gray: lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(245)),
|
||||
yellow: lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(214)),
|
||||
blue: lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(75)),
|
||||
lightGray: lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(250)),
|
||||
}
|
||||
|
||||
noColorOut := colors{
|
||||
green: lipgloss.NewStyle(),
|
||||
gray: lipgloss.NewStyle(),
|
||||
yellow: lipgloss.NewStyle(),
|
||||
blue: lipgloss.NewStyle(),
|
||||
lightGray: lipgloss.NewStyle(),
|
||||
}
|
||||
|
||||
useColors := true
|
||||
|
||||
if noColor == "true" || noColor == "1" {
|
||||
useColors = false
|
||||
}
|
||||
|
||||
if forceColor == "true" || forceColor == "1" {
|
||||
useColors = true
|
||||
}
|
||||
|
||||
if !useColors {
|
||||
return noColorOut
|
||||
}
|
||||
|
||||
return colorOut
|
||||
}
|
||||
|
||||
func fatalf(err error, msg string) {
|
||||
fmt.Printf("%s: %v\n", msg, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
type kv struct {
|
||||
k string
|
||||
v string
|
||||
}
|
||||
|
||||
func renderToBuf(buf *strings.Builder, kv []kv, sep string) {
|
||||
colors := getColors()
|
||||
for _, i := range kv {
|
||||
buf.WriteString(colors.blue.Render(i.k))
|
||||
buf.WriteString(colors.gray.Render(sep))
|
||||
buf.WriteString(colors.lightGray.Render(i.v))
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func renderYamlToBuf(buf *strings.Builder, i any) error {
|
||||
colors := getColors()
|
||||
|
||||
yout, err := yaml.Marshal(i)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal yaml: %w", err)
|
||||
}
|
||||
|
||||
for l := range strings.SplitSeq(string(yout), "\n") {
|
||||
if l == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(strings.TrimLeft(l, " "), "- ") {
|
||||
buf.WriteString(colors.lightGray.Render(l))
|
||||
buf.WriteString("\n")
|
||||
continue
|
||||
}
|
||||
lp := strings.SplitN(l, ":", 2)
|
||||
buf.WriteString(colors.blue.Render(lp[0]))
|
||||
buf.WriteString(colors.gray.Render(":"))
|
||||
if len(lp) == 2 {
|
||||
buf.WriteString(colors.lightGray.Render(lp[1]))
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+64
-70
@@ -3,9 +3,9 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
|
||||
"charm.land/huh/v2"
|
||||
"github.com/pquerna/otp/totp"
|
||||
@@ -38,87 +38,81 @@ func verifyUserCmd() *cli.Command {
|
||||
&cli.FlagLoader{},
|
||||
}
|
||||
|
||||
cmd := &cli.Command{
|
||||
return &cli.Command{
|
||||
Name: "verify",
|
||||
Description: "Verify a user is set up correctly",
|
||||
Configuration: tCfg,
|
||||
Resources: loaders,
|
||||
}
|
||||
Run: func(_ []string) error {
|
||||
log := logger.NewLogger().WithSimpleConfig()
|
||||
log.Init()
|
||||
|
||||
cmd.Run = func(_ []string) error {
|
||||
colors := getColors()
|
||||
if tCfg.Interactive {
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().Title("User (username:hash:totp)").Value(&tCfg.User).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("user cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
huh.NewInput().Title("Username").Value(&tCfg.Username).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("username cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
huh.NewInput().Title("Password").Value(&tCfg.Password).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("password cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
huh.NewInput().Title("TOTP Code (optional)").Value(&tCfg.Totp),
|
||||
),
|
||||
)
|
||||
|
||||
if tCfg.Interactive {
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().Title("User (username:hash:totp)").Value(&tCfg.User).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("user cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
huh.NewInput().Title("Username").Value(&tCfg.Username).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("username cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
huh.NewInput().Title("Password").Value(&tCfg.Password).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("password cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
huh.NewInput().Title("TOTP Code (optional)").Value(&tCfg.Totp),
|
||||
),
|
||||
)
|
||||
theme := new(themeBase)
|
||||
err := form.WithTheme(theme).Run()
|
||||
|
||||
theme := new(themeBase)
|
||||
err := form.WithTheme(theme).Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run interactive prompt: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
user, err := utils.ParseUser(tCfg.User)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run interactive prompt: %w", err)
|
||||
return fmt.Errorf("failed to parse user: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if tCfg.User == "" || tCfg.Username == "" || tCfg.Password == "" {
|
||||
cmd.PrintHelp(os.Stdout)
|
||||
return fmt.Errorf("user, username, and password are required")
|
||||
}
|
||||
|
||||
user, err := utils.ParseUser(tCfg.User)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse user: %w", err)
|
||||
}
|
||||
|
||||
if user.Username != tCfg.Username {
|
||||
return fmt.Errorf("username is incorrect")
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(tCfg.Password))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("password is incorrect: %w", err)
|
||||
}
|
||||
|
||||
if user.TOTPSecret == "" {
|
||||
if tCfg.Totp != "" {
|
||||
fmt.Println(colors.yellow.Render("⚠") + " TOTP code provided but user does not have TOTP enabled")
|
||||
if user.Username != tCfg.Username {
|
||||
return fmt.Errorf("username is incorrect")
|
||||
}
|
||||
fmt.Println(colors.green.Render("✓") + " User verified")
|
||||
|
||||
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(tCfg.Password))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("password is incorrect: %w", err)
|
||||
}
|
||||
|
||||
if user.TOTPSecret == "" {
|
||||
if tCfg.Totp != "" {
|
||||
log.App.Warn().Msg("User does not have TOTP secret")
|
||||
}
|
||||
log.App.Info().Msg("User verified")
|
||||
return nil
|
||||
}
|
||||
|
||||
ok := totp.Validate(tCfg.Totp, user.TOTPSecret)
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("TOTP code incorrect")
|
||||
}
|
||||
|
||||
log.App.Info().Msg("User verified")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
ok := totp.Validate(tCfg.Totp, user.TOTPSecret)
|
||||
if !ok {
|
||||
return fmt.Errorf("TOTP code incorrect")
|
||||
}
|
||||
|
||||
fmt.Println(colors.green.Render("✓") + " User verified")
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -14,10 +14,9 @@ func versionCmd() *cli.Command {
|
||||
Configuration: nil,
|
||||
Resources: nil,
|
||||
Run: func(_ []string) error {
|
||||
colors := getColors()
|
||||
fmt.Printf("Version: %s\n", colors.blue.Render(model.Version))
|
||||
fmt.Printf("Commit Hash: %s\n", colors.blue.Render(model.CommitHash))
|
||||
fmt.Printf("Build Timestamp: %s\n", colors.blue.Render(model.BuildTimestamp))
|
||||
fmt.Printf("Version: %s\n", model.Version)
|
||||
fmt.Printf("Commit Hash: %s\n", model.CommitHash)
|
||||
fmt.Printf("Build Timestamp: %s\n", model.BuildTimestamp)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
+26
-25
@@ -12,49 +12,50 @@
|
||||
},
|
||||
"packageManager": "pnpm@11.1.2",
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.4.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.19",
|
||||
"@radix-ui/react-label": "^2.1.11",
|
||||
"@radix-ui/react-select": "^2.3.2",
|
||||
"@radix-ui/react-separator": "^1.1.11",
|
||||
"@radix-ui/react-slot": "^1.3.0",
|
||||
"@tailwindcss/vite": "^4.3.2",
|
||||
"@tanstack/react-query": "^5.101.2",
|
||||
"axios": "^1.18.1",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@tanstack/react-query": "^5.99.0",
|
||||
"axios": "^1.15.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^26.3.4",
|
||||
"i18next": "^26.0.4",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"i18next-resources-to-backend": "^1.2.1",
|
||||
"lucide-react": "^1.23.0",
|
||||
"lucide-react": "^1.8.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"radix-ui": "^1.6.1",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"react-hook-form": "^7.81.0",
|
||||
"react-i18next": "^17.0.8",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"react-hook-form": "^7.72.1",
|
||||
"react-i18next": "^17.0.2",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router": "^7.14.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.3.2",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@tanstack/eslint-plugin-query": "^5.101.2",
|
||||
"@tanstack/eslint-plugin-query": "^5.99.0",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.3",
|
||||
"eslint": "^10.6.0",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"eslint": "^10.2.0",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.3",
|
||||
"globals": "^17.7.0",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.5.0",
|
||||
"prettier": "3.8.2",
|
||||
"rollup-plugin-visualizer": "^7.0.1",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.62.1",
|
||||
"vite": "^8.1.3"
|
||||
"typescript-eslint": "^8.58.1",
|
||||
"vite": "^8.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1377
-1289
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
dangerouslyAllowAllBuilds: false
|
||||
blockExoticSubdeps: true
|
||||
minimumReleaseAge: 1440 # 1 day
|
||||
minimumReleaseAge: 1440 # 1 day
|
||||
trustPolicy: no-downgrade
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// gen/sqlc_wrapper generates store.go wrapper files for each sqlc driver package under
|
||||
// gen/sqlc-wrapper generates store.go wrapper files for each sqlc driver package under
|
||||
// internal/repository/<driver>/. Run via:
|
||||
//
|
||||
// go generate ./internal/repository/...
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
var storeSrc string
|
||||
|
||||
func main() {
|
||||
fmt.Println("sqlc_wrapper: generating store.go files for sqlc driver packages...")
|
||||
fmt.Println("sqlc-wrapper: generating store.go files for sqlc driver packages...")
|
||||
if err := run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package tinyauth
|
||||
|
||||
//go:generate go run github.com/tinyauthapp/tinyauth/gen/docs
|
||||
@@ -4,7 +4,6 @@ go 1.26.4
|
||||
|
||||
require (
|
||||
charm.land/huh/v2 v2.0.3
|
||||
charm.land/lipgloss/v2 v2.0.1
|
||||
github.com/cenkalti/backoff/v5 v5.0.3
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
@@ -26,22 +25,26 @@ require (
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/tools v0.47.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/apimachinery v0.36.2
|
||||
k8s.io/client-go v0.36.2
|
||||
modernc.org/sqlite v1.53.0
|
||||
tailscale.com v1.100.0
|
||||
)
|
||||
|
||||
require (
|
||||
charm.land/bubbles/v2 v2.0.0 // indirect
|
||||
charm.land/bubbletea/v2 v2.0.2 // indirect
|
||||
charm.land/lipgloss/v2 v2.0.1 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.1.1 // indirect
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/akutz/memconn v0.1.0 // indirect
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/boombuler/barcode v1.0.2 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
@@ -60,10 +63,12 @@ require (
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/coder/websocket v1.8.12 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/creachadair/msync v0.7.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
@@ -71,22 +76,31 @@ require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/gaissmai/bart v0.26.1 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.4 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/huin/goupnp v1.3.0 // indirect
|
||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
@@ -94,12 +108,14 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
|
||||
github.com/mdlayher/socket v0.5.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
@@ -108,16 +124,22 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/safchain/ethtool v0.3.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d // indirect
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
||||
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd // indirect
|
||||
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
|
||||
github.com/tailscale/wireguard-go v0.0.0-20260527010701-b48af7099cad // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
@@ -132,6 +154,8 @@ require (
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.22.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||
golang.org/x/mod v0.37.0 // indirect
|
||||
@@ -141,9 +165,13 @@ require (
|
||||
golang.org/x/term v0.44.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.5.2 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 // indirect
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f h1:1C7nZuxUMNz7eiQALRfiqNOm04+m3edWlRff/BYHf0Q=
|
||||
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM=
|
||||
charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
|
||||
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
|
||||
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
|
||||
@@ -8,6 +10,10 @@ charm.land/lipgloss/v2 v2.0.1 h1:6Xzrn49+Py1Um5q/wZG1gWgER2+7dUyZ9XMEufqPSys=
|
||||
charm.land/lipgloss/v2 v2.0.1/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
|
||||
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=
|
||||
@@ -24,10 +30,44 @@ github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
||||
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
|
||||
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
|
||||
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02 h1:bXAPYSbdYbS5VTy92NIUbeDI1qyggi+JYh5op9IFlcQ=
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c=
|
||||
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
|
||||
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
@@ -69,28 +109,48 @@ github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2
|
||||
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
|
||||
github.com/charmbracelet/x/xpty v0.1.3 h1:eGSitii4suhzrISYH50ZfufV3v085BXQwIytcOdFSsw=
|
||||
github.com/charmbracelet/x/xpty v0.1.3/go.mod h1:poPYpWuLDBFCKmKLDnhBp51ATa0ooD8FhypRwEFtH3Y=
|
||||
github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
|
||||
github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
|
||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/creachadair/mds v0.25.9 h1:080Hr8laN2h+l3NeVCGMBpXtIPnl9mz8e4HLraGPqtA=
|
||||
github.com/creachadair/mds v0.25.9/go.mod h1:4hatI3hRM+qhzuAmqPRFvaBM8mONkS7nsLxkcuTYUIs=
|
||||
github.com/creachadair/msync v0.7.1 h1:SeZmuEBXQPe5GqV/C94ER7QIZPwtvFbeQiykzt/7uho=
|
||||
github.com/creachadair/msync v0.7.1/go.mod h1:8CcFlLsSujfHE5wWm19uUBLHIPDAUr6LXDwneVMO008=
|
||||
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
|
||||
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
|
||||
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc h1:8WFBn63wegobsYAX0YjD+8suexZDga5CctH4CCTx2+8=
|
||||
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
|
||||
github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
@@ -109,14 +169,20 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gaissmai/bart v0.26.1 h1:+w4rnLGNlA2GDVn382Tfe3jOsK5vOr5n4KmigJ9lbTo=
|
||||
github.com/gaissmai/bart v0.26.1/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao=
|
||||
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -124,6 +190,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU=
|
||||
@@ -138,14 +206,24 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go4org/hashtriemap v0.0.0-20251130024219-545ba229f689 h1:0psnKZ+N2IP43/SZC8SKx6OpFJwLmQb9m9QyV9BC2f8=
|
||||
github.com/go4org/hashtriemap v0.0.0-20251130024219-545ba229f689/go.mod h1:OGmRfY/9QEK2P5zCRtmqfbCF283xPkU2dvVA4MvbvpI=
|
||||
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
|
||||
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
@@ -153,7 +231,11 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
||||
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
||||
github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I=
|
||||
github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
|
||||
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -162,10 +244,19 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
|
||||
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||
github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk=
|
||||
github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw=
|
||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
@@ -188,12 +279,24 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
|
||||
github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
|
||||
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -214,10 +317,22 @@ github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjc
|
||||
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
|
||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=
|
||||
github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c=
|
||||
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
|
||||
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
||||
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||
github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
|
||||
github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
@@ -244,19 +359,31 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
|
||||
github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic=
|
||||
@@ -269,6 +396,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
@@ -292,12 +421,42 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d h1:JcGKBZAL7ePLwOhUdN8qGQZlP5GueEiIZwY7R62pejE=
|
||||
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||
github.com/tailscale/gliderssh v0.3.4-0.20260330083525-c1389c70ff89 h1:glgVc1ZYMjwN1Q/ITWeuSQyl029uayagaR2sjsifehc=
|
||||
github.com/tailscale/gliderssh v0.3.4-0.20260330083525-c1389c70ff89/go.mod h1:wn16Km1EZOX4UEAyaZa3dBwfFGOJ7neck40NcwosJUw=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 h1:SRL6irQkKGQKKLzvQP/ke/2ZuB7Py5+XuqtOgSj+iMM=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
|
||||
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd h1:Rf9uhF1+VJ7ZHqxrG8pJ6YacmHvVCmByDmGbAWCc/gA=
|
||||
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo=
|
||||
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
|
||||
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
|
||||
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=
|
||||
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
|
||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20260527010701-b48af7099cad h1:Ky26FR5yZ5IKEB0xtm5A8xSTb06ImY7kxBFrvgOmJSg=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20260527010701-b48af7099cad/go.mod h1:6SerzcvHWQchKO2BfNdmquA77CHSECZuFl+D9fp4RnI=
|
||||
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
|
||||
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
|
||||
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
|
||||
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298 h1:EYSb5jv8ZL/0/NVFZtY7Ejplk0QG5+3lrdL3mSrjFZQ=
|
||||
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298/go.mod h1:TlUDoCF66hMqFZqoBym9bUdJ0bKAWYMir6hLJeYN5z0=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg=
|
||||
github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/weppos/publicsuffix-go v0.50.3 h1:eT5dcjHQcVDNc0igpFEsGHKIip30feuB2zuuI9eJxiE=
|
||||
github.com/weppos/publicsuffix-go v0.50.3/go.mod h1:/rOa781xBykZhHK/I3QeHo92qdDKVmKZKF7s8qAEM/4=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
@@ -334,20 +493,30 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
|
||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo=
|
||||
golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
@@ -359,6 +528,10 @@ golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
||||
@@ -380,6 +553,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 h1:Zy8IV/+FMLxy6j6p87vk/vQGKcdnbprwjTxc8UiUtsA=
|
||||
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
|
||||
honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=
|
||||
honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY=
|
||||
k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg=
|
||||
k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ=
|
||||
@@ -430,3 +609,7 @@ sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
tailscale.com v1.100.0 h1:nm/M/dEaW9RaRsGUjW2HsSDpsZ60Jwd9k4gNW9tTFiE=
|
||||
tailscale.com v1.100.0/go.mod h1:DQ9YBy85DpNlSyeU2XRIWzbAu3RsGp/frv+Khg57meE=
|
||||
|
||||
@@ -278,6 +278,27 @@ func (app *BootstrapApp) Setup() error {
|
||||
|
||||
app.runtime.ConfiguredProviders = configuredProviders
|
||||
|
||||
// if tailscale is enabled and listening, replace the app url with the tailscale hostname
|
||||
if app.services.tailscaleService != nil && app.config.Tailscale.Listen {
|
||||
tailscaleUrl := "https://" + app.services.tailscaleService.GetHostname()
|
||||
|
||||
// if the tailscale url is different from the app url, replace it
|
||||
if tailscaleUrl != app.runtime.AppURL {
|
||||
app.log.App.Info().Msg("Listening on tailscale, replacing app url with tailscale hostname")
|
||||
|
||||
app.runtime.AppURL = tailscaleUrl
|
||||
|
||||
// also update cookie domain
|
||||
cookieDomain, err := utils.GetCookieDomain(tailscaleUrl, app.config.Auth.SubdomainsEnabled)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get cookie domain: %w", err)
|
||||
}
|
||||
|
||||
app.runtime.CookieDomain = cookieDomain
|
||||
}
|
||||
}
|
||||
|
||||
// force an update of the redirect urls for all oauth providers, if they are empty
|
||||
services := app.services.oauthBrokerService.GetConfiguredServices()
|
||||
|
||||
|
||||
@@ -126,9 +126,17 @@ func (app *BootstrapApp) setupRouter() error {
|
||||
}
|
||||
|
||||
// Top down
|
||||
// 1. Unix socket (if server.socketPath)
|
||||
// 2. HTTP - default
|
||||
// 1. Tailscale (if tailscale.listen)
|
||||
// 2. Unix socket (if server.socketPath)
|
||||
// 3. HTTP - default
|
||||
func (app *BootstrapApp) getListenerFunc() (func(ctx context.Context) error, error) {
|
||||
if app.config.Tailscale.Listen {
|
||||
if app.services.tailscaleService == nil {
|
||||
return nil, fmt.Errorf("tailscale.listen is enabled but tailscale service is not initialized")
|
||||
}
|
||||
return app.serveTailscale, nil
|
||||
}
|
||||
|
||||
if app.config.Server.SocketPath != "" {
|
||||
return app.serveUnix, nil
|
||||
}
|
||||
@@ -182,6 +190,22 @@ func (app *BootstrapApp) serveUnix(ctx context.Context) error {
|
||||
return app.serve(listener, server, ctx, "unix socket")
|
||||
}
|
||||
|
||||
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()))
|
||||
|
||||
listener, err := app.services.tailscaleService.CreateListener()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create tailscale listener: %w", err)
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Handler: app.router.Handler(),
|
||||
}
|
||||
|
||||
return app.serve(listener, server, ctx, "tailscale")
|
||||
}
|
||||
|
||||
func (app *BootstrapApp) serve(listener net.Listener, server *http.Server, ctx context.Context, name string) error {
|
||||
shutdown := func() {
|
||||
// we use a new context for the shutdown since the main one is cancelled
|
||||
@@ -203,6 +227,7 @@ func (app *BootstrapApp) serve(listener net.Listener, server *http.Server, ctx c
|
||||
err := server.Serve(listener)
|
||||
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
shutdown()
|
||||
return fmt.Errorf("failed to start %s listener: %w", name, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -739,7 +739,7 @@ func (controller *OIDCController) resolveRequestObject(c *gin.Context) (*service
|
||||
func (controller *OIDCController) resolveNormalParams(c *gin.Context) (*service.AuthorizeRequest, error) {
|
||||
var req service.AuthorizeRequest
|
||||
|
||||
var bind binding.Binding = binding.Query
|
||||
bind := binding.Query
|
||||
|
||||
if c.Request.Method == http.MethodPost {
|
||||
bind = binding.Form
|
||||
|
||||
@@ -112,7 +112,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
|
||||
// Lastly check if we have a tailscale session to add
|
||||
if m.tailscale != nil {
|
||||
tailscaleContext, err := m.tailscaleWhois(c.ClientIP())
|
||||
tailscaleContext, err := m.tailscaleWhois(c.Request.Context(), c.ClientIP())
|
||||
|
||||
if err != nil {
|
||||
m.log.App.Error().Err(err).Msgf("Error performing tailscale whois for IP %s: %v", c.ClientIP(), err)
|
||||
@@ -167,7 +167,7 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string, ip stri
|
||||
userContext.Local.Attributes.Email = utils.CompileUserEmail(user.Username, m.runtime.CookieDomain)
|
||||
}
|
||||
case model.ProviderTailscale:
|
||||
tailscaleContext, err := m.tailscaleWhois(ip)
|
||||
tailscaleContext, err := m.tailscaleWhois(ctx, ip)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error performing tailscale whois: %w", err)
|
||||
@@ -177,10 +177,6 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string, ip stri
|
||||
return nil, nil, fmt.Errorf("tailscale whois returned no result for IP: %s", ip)
|
||||
}
|
||||
|
||||
if tailscaleContext.Email != userContext.Tailscale.Email {
|
||||
return nil, nil, fmt.Errorf("device owner mismatch, login again")
|
||||
}
|
||||
|
||||
userContext.Tailscale = tailscaleContext
|
||||
case model.ProviderLDAP:
|
||||
search, err := m.auth.SearchUser(userContext.LDAP.Username)
|
||||
@@ -312,12 +308,12 @@ func (m *ContextMiddleware) isIgnorePath(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *ContextMiddleware) tailscaleWhois(ip string) (*model.TailscaleContext, error) {
|
||||
func (m *ContextMiddleware) tailscaleWhois(ctx context.Context, ip string) (*model.TailscaleContext, error) {
|
||||
if m.tailscale == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
whois, err := m.tailscale.Whois(ip)
|
||||
whois, err := m.tailscale.Whois(ctx, ip)
|
||||
|
||||
if err != nil {
|
||||
m.log.App.Error().Err(err).Msgf("Error performing Tailscale whois for IP %s: %v", ip, err)
|
||||
@@ -328,15 +324,13 @@ func (m *ContextMiddleware) tailscaleWhois(ip string) (*model.TailscaleContext,
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
username := strings.Replace(whois.LoginName, "@", "_", 1)
|
||||
|
||||
uctx := model.TailscaleContext{
|
||||
BaseContext: model.BaseContext{
|
||||
Username: username,
|
||||
Username: whois.NodeName,
|
||||
Email: whois.LoginName,
|
||||
Name: whois.DisplayName,
|
||||
},
|
||||
NodeName: whois.NodeName,
|
||||
UserID: whois.UserID,
|
||||
}
|
||||
|
||||
return &uctx, nil
|
||||
|
||||
+143
-141
@@ -1,9 +1,6 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
import "os"
|
||||
|
||||
type RuntimeEnv int
|
||||
|
||||
@@ -85,7 +82,7 @@ func NewDefaultConfiguration(runtimeEnv RuntimeEnv) *Config {
|
||||
PublicKeyPath: "./tinyauth_oidc_key.pub",
|
||||
},
|
||||
Tailscale: TailscaleConfig{
|
||||
CacheDuration: int(time.Duration(5 * time.Minute).Seconds()),
|
||||
Dir: "./tailscale_state",
|
||||
},
|
||||
LabelProvider: "auto",
|
||||
}
|
||||
@@ -96,240 +93,245 @@ func NewDefaultConfiguration(runtimeEnv RuntimeEnv) *Config {
|
||||
cfg.Resources.Path = "/data/resources"
|
||||
cfg.OIDC.PrivateKeyPath = "/data/oidc/key.pem"
|
||||
cfg.OIDC.PublicKeyPath = "/data/oidc/key.pub"
|
||||
cfg.Tailscale.Dir = "/data/tailscale"
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
AppURL string `description:"The base URL where the app is hosted." yaml:"appUrl,omitempty"`
|
||||
Database DatabaseConfig `description:"Database configuration." yaml:"database,omitempty"`
|
||||
Analytics AnalyticsConfig `description:"Analytics configuration." yaml:"analytics,omitempty"`
|
||||
Resources ResourcesConfig `description:"Resources configuration." yaml:"resources,omitempty"`
|
||||
Server ServerConfig `description:"Server configuration." yaml:"server,omitempty"`
|
||||
Auth AuthConfig `description:"Authentication configuration." yaml:"auth,omitempty"`
|
||||
Apps map[string]App `description:"Application ACLs configuration." yaml:"apps,omitempty"`
|
||||
OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth,omitempty"`
|
||||
OIDC OIDCConfig `description:"OIDC configuration." yaml:"oidc,omitempty"`
|
||||
UI UIConfig `description:"UI customization." yaml:"ui,omitempty"`
|
||||
LDAP LDAPConfig `description:"LDAP configuration." yaml:"ldap,omitempty"`
|
||||
Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental,omitempty"`
|
||||
Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale,omitempty"`
|
||||
LabelProvider string `description:"Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment." yaml:"labelProvider,omitempty"`
|
||||
Log LogConfig `description:"Logging configuration." yaml:"log,omitempty"`
|
||||
ConfigFile string `description:"Path to config file." yaml:"-"`
|
||||
AppURL string `description:"The base URL where the app is hosted." yaml:"appUrl"`
|
||||
Database DatabaseConfig `description:"Database configuration." yaml:"database"`
|
||||
Analytics AnalyticsConfig `description:"Analytics configuration." yaml:"analytics"`
|
||||
Resources ResourcesConfig `description:"Resources configuration." yaml:"resources"`
|
||||
Server ServerConfig `description:"Server configuration." yaml:"server"`
|
||||
Auth AuthConfig `description:"Authentication configuration." yaml:"auth"`
|
||||
Apps map[string]App `description:"Application ACLs configuration." yaml:"apps"`
|
||||
OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth"`
|
||||
OIDC OIDCConfig `description:"OIDC configuration." yaml:"oidc"`
|
||||
UI UIConfig `description:"UI customization." yaml:"ui"`
|
||||
LDAP LDAPConfig `description:"LDAP configuration." yaml:"ldap"`
|
||||
// Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental"`
|
||||
LabelProvider string `description:"Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment." yaml:"labelProvider"`
|
||||
Log LogConfig `description:"Logging configuration." yaml:"log"`
|
||||
Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale"`
|
||||
ConfigFile string `description:"Path to config file." yaml:"-"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Driver string `description:"The database driver to use. Valid values: sqlite, postgres, memory." yaml:"driver,omitempty"`
|
||||
Path string `description:"The path to the SQLite database file, or connection URL when driver is postgres." yaml:"path,omitempty"`
|
||||
Driver string `description:"The database driver to use. Valid values: sqlite, postgres, memory." yaml:"driver"`
|
||||
Path string `description:"The path to the SQLite database file, or connection URL when driver is postgres." yaml:"path"`
|
||||
}
|
||||
|
||||
type AnalyticsConfig struct {
|
||||
Enabled bool `description:"Enable periodic version information collection." yaml:"enabled,omitempty"`
|
||||
Enabled bool `description:"Enable periodic version information collection." yaml:"enabled"`
|
||||
}
|
||||
|
||||
type ResourcesConfig struct {
|
||||
Enabled bool `description:"Enable the resources server." yaml:"enabled,omitempty"`
|
||||
Path string `description:"The directory where resources are stored." yaml:"path,omitempty"`
|
||||
Enabled bool `description:"Enable the resources server." yaml:"enabled"`
|
||||
Path string `description:"The directory where resources are stored." yaml:"path"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int `description:"The port on which the server listens." yaml:"port,omitempty"`
|
||||
Address string `description:"The address on which the server listens." yaml:"address,omitempty"`
|
||||
SocketPath string `description:"The path to the Unix socket." yaml:"socketPath,omitempty"`
|
||||
Port int `description:"The port on which the server listens." yaml:"port"`
|
||||
Address string `description:"The address on which the server listens." yaml:"address"`
|
||||
SocketPath string `description:"The path to the Unix socket." yaml:"socketPath"`
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
IP IPConfig `description:"IP whitelisting config options." yaml:"ip,omitempty"`
|
||||
Users []string `description:"Comma-separated list of users (username:hashed_password)." yaml:"users,omitempty"`
|
||||
SubdomainsEnabled bool `description:"Enable subdomains support." yaml:"subdomainsEnabled,omitempty"`
|
||||
UserAttributes map[string]UserAttributes `description:"Map of per-user OIDC attributes (username -> attributes)." yaml:"userAttributes,omitempty"`
|
||||
UsersFile string `description:"Path to the users file." yaml:"usersFile,omitempty"`
|
||||
SecureCookie bool `description:"Enable secure cookies." yaml:"secureCookie,omitempty"`
|
||||
SessionExpiry int `description:"Session expiry time in seconds." yaml:"sessionExpiry,omitempty"`
|
||||
SessionMaxLifetime int `description:"Maximum session lifetime in seconds." yaml:"sessionMaxLifetime,omitempty"`
|
||||
LoginTimeout int `description:"Login timeout in seconds." yaml:"loginTimeout,omitempty"`
|
||||
LoginMaxRetries int `description:"Maximum login retries." yaml:"loginMaxRetries,omitempty"`
|
||||
LockdownEnabled bool `description:"Enable lockdown mode after maximum login retries. Lockdown mode limit is calculated automatically." yaml:"lockdownEnabled,omitempty"`
|
||||
TrustedProxies []string `description:"Comma-separated list of trusted proxy addresses." yaml:"trustedProxies,omitempty"`
|
||||
ACLs ACLsConfig `description:"ACLs configuration." yaml:"acls,omitempty"`
|
||||
IP IPConfig `description:"IP whitelisting config options." yaml:"ip"`
|
||||
Users []string `description:"Comma-separated list of users (username:hashed_password)." yaml:"users"`
|
||||
SubdomainsEnabled bool `description:"Enable subdomains support." yaml:"subdomainsEnabled"`
|
||||
UserAttributes map[string]UserAttributes `description:"Map of per-user OIDC attributes (username -> attributes)." yaml:"userAttributes"`
|
||||
UsersFile string `description:"Path to the users file." yaml:"usersFile"`
|
||||
SecureCookie bool `description:"Enable secure cookies." yaml:"secureCookie"`
|
||||
SessionExpiry int `description:"Session expiry time in seconds." yaml:"sessionExpiry"`
|
||||
SessionMaxLifetime int `description:"Maximum session lifetime in seconds." yaml:"sessionMaxLifetime"`
|
||||
LoginTimeout int `description:"Login timeout in seconds." yaml:"loginTimeout"`
|
||||
LoginMaxRetries int `description:"Maximum login retries." yaml:"loginMaxRetries"`
|
||||
LockdownEnabled bool `description:"Enable lockdown mode after maximum login retries. Lockdown mode limit is calculated automatically." yaml:"lockdownEnabled"`
|
||||
TrustedProxies []string `description:"Comma-separated list of trusted proxy addresses." yaml:"trustedProxies"`
|
||||
ACLs ACLsConfig `description:"ACLs configuration." yaml:"acls"`
|
||||
}
|
||||
|
||||
type UserAttributes struct {
|
||||
Name string `description:"Full name of the user." yaml:"name,omitempty"`
|
||||
GivenName string `description:"Given (first) name of the user." yaml:"givenName,omitempty"`
|
||||
FamilyName string `description:"Family (last) name of the user." yaml:"familyName,omitempty"`
|
||||
MiddleName string `description:"Middle name of the user." yaml:"middleName,omitempty"`
|
||||
Nickname string `description:"Nickname of the user." yaml:"nickname,omitempty"`
|
||||
Profile string `description:"URL of the user's profile page." yaml:"profile,omitempty"`
|
||||
Picture string `description:"URL of the user's profile picture." yaml:"picture,omitempty"`
|
||||
Website string `description:"URL of the user's website." yaml:"website,omitempty"`
|
||||
Email string `description:"Email address of the user." yaml:"email,omitempty"`
|
||||
Gender string `description:"Gender of the user." yaml:"gender,omitempty"`
|
||||
Birthdate string `description:"Birthdate of the user (YYYY-MM-DD)." yaml:"birthdate,omitempty"`
|
||||
Zoneinfo string `description:"Time zone of the user (e.g. Europe/Athens)." yaml:"zoneinfo,omitempty"`
|
||||
Locale string `description:"Locale of the user (e.g. en-US)." yaml:"locale,omitempty"`
|
||||
PhoneNumber string `description:"Phone number of the user." yaml:"phoneNumber,omitempty"`
|
||||
Address AddressClaim `description:"Address of the user." yaml:"address,omitempty"`
|
||||
Name string `description:"Full name of the user." yaml:"name"`
|
||||
GivenName string `description:"Given (first) name of the user." yaml:"givenName"`
|
||||
FamilyName string `description:"Family (last) name of the user." yaml:"familyName"`
|
||||
MiddleName string `description:"Middle name of the user." yaml:"middleName"`
|
||||
Nickname string `description:"Nickname of the user." yaml:"nickname"`
|
||||
Profile string `description:"URL of the user's profile page." yaml:"profile"`
|
||||
Picture string `description:"URL of the user's profile picture." yaml:"picture"`
|
||||
Website string `description:"URL of the user's website." yaml:"website"`
|
||||
Email string `description:"Email address of the user." yaml:"email"`
|
||||
Gender string `description:"Gender of the user." yaml:"gender"`
|
||||
Birthdate string `description:"Birthdate of the user (YYYY-MM-DD)." yaml:"birthdate"`
|
||||
Zoneinfo string `description:"Time zone of the user (e.g. Europe/Athens)." yaml:"zoneinfo"`
|
||||
Locale string `description:"Locale of the user (e.g. en-US)." yaml:"locale"`
|
||||
PhoneNumber string `description:"Phone number of the user." yaml:"phoneNumber"`
|
||||
Address AddressClaim `description:"Address of the user." yaml:"address"`
|
||||
}
|
||||
|
||||
type AddressClaim struct {
|
||||
Formatted string `description:"Full mailing address, formatted for display." yaml:"formatted,omitempty" json:"formatted,omitempty"`
|
||||
StreetAddress string `description:"Street address." yaml:"streetAddress,omitempty" json:"street_address,omitempty"`
|
||||
Locality string `description:"City or locality." yaml:"locality,omitempty" json:"locality,omitempty"`
|
||||
Region string `description:"State, province, or region." yaml:"region,omitempty" json:"region,omitempty"`
|
||||
PostalCode string `description:"Zip or postal code." yaml:"postalCode,omitempty" json:"postal_code,omitempty"`
|
||||
Country string `description:"Country." yaml:"country,omitempty" json:"country,omitempty"`
|
||||
Formatted string `description:"Full mailing address, formatted for display." yaml:"formatted" json:"formatted,omitempty"`
|
||||
StreetAddress string `description:"Street address." yaml:"streetAddress" json:"street_address,omitempty"`
|
||||
Locality string `description:"City or locality." yaml:"locality" json:"locality,omitempty"`
|
||||
Region string `description:"State, province, or region." yaml:"region" json:"region,omitempty"`
|
||||
PostalCode string `description:"Zip or postal code." yaml:"postalCode" json:"postal_code,omitempty"`
|
||||
Country string `description:"Country." yaml:"country" json:"country,omitempty"`
|
||||
}
|
||||
|
||||
type IPConfig struct {
|
||||
Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow,omitempty"`
|
||||
Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block,omitempty"`
|
||||
Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication entirely." yaml:"bypass,omitempty"`
|
||||
Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"`
|
||||
Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"`
|
||||
Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication entirely." yaml:"bypass"`
|
||||
}
|
||||
|
||||
type OAuthConfig struct {
|
||||
Whitelist []string `description:"Comma-separated list of allowed OAuth domains." yaml:"whitelist,omitempty"`
|
||||
WhitelistFile string `description:"Path to the OAuth whitelist file." yaml:"whitelistFile,omitempty"`
|
||||
AutoRedirect string `description:"The OAuth provider to use for automatic redirection." yaml:"autoRedirect,omitempty"`
|
||||
Providers map[string]OAuthServiceConfig `description:"OAuth providers configuration." yaml:"providers,omitempty"`
|
||||
Whitelist []string `description:"Comma-separated list of allowed OAuth domains." yaml:"whitelist"`
|
||||
WhitelistFile string `description:"Path to the OAuth whitelist file." yaml:"whitelistFile"`
|
||||
AutoRedirect string `description:"The OAuth provider to use for automatic redirection." yaml:"autoRedirect"`
|
||||
Providers map[string]OAuthServiceConfig `description:"OAuth providers configuration." yaml:"providers"`
|
||||
}
|
||||
|
||||
type OIDCConfig struct {
|
||||
PrivateKeyPath string `description:"Path to the private key file, including file name." yaml:"privateKeyPath,omitempty"`
|
||||
PublicKeyPath string `description:"Path to the public key file, including file name." yaml:"publicKeyPath,omitempty"`
|
||||
Clients map[string]OIDCClientConfig `description:"OIDC clients configuration." yaml:"clients,omitempty"`
|
||||
PrivateKeyPath string `description:"Path to the private key file, including file name." yaml:"privateKeyPath"`
|
||||
PublicKeyPath string `description:"Path to the public key file, including file name." yaml:"publicKeyPath"`
|
||||
Clients map[string]OIDCClientConfig `description:"OIDC clients configuration." yaml:"clients"`
|
||||
}
|
||||
|
||||
type UIConfig struct {
|
||||
Title string `description:"The title of the UI." yaml:"title,omitempty"`
|
||||
ForgotPasswordMessage string `description:"Message displayed on the forgot password page." yaml:"forgotPasswordMessage,omitempty"`
|
||||
BackgroundImage string `description:"Path to the background image." yaml:"backgroundImage,omitempty"`
|
||||
WarningsEnabled bool `description:"Enable UI warnings." yaml:"warningsEnabled,omitempty"`
|
||||
Title string `description:"The title of the UI." yaml:"title"`
|
||||
ForgotPasswordMessage string `description:"Message displayed on the forgot password page." yaml:"forgotPasswordMessage"`
|
||||
BackgroundImage string `description:"Path to the background image." yaml:"backgroundImage"`
|
||||
WarningsEnabled bool `description:"Enable UI warnings." yaml:"warningsEnabled"`
|
||||
}
|
||||
|
||||
type LDAPConfig struct {
|
||||
Address string `description:"LDAP server address." yaml:"address,omitempty"`
|
||||
BindDN string `description:"Bind DN for LDAP authentication." yaml:"bindDn,omitempty"`
|
||||
BindPassword string `description:"Bind password for LDAP authentication." yaml:"bindPassword,omitempty"`
|
||||
BindPasswordFile string `description:"Path to the Bind password." yaml:"bindPasswordFile,omitempty"`
|
||||
BaseDN string `description:"Base DN for LDAP searches." yaml:"baseDn,omitempty"`
|
||||
Insecure bool `description:"Allow insecure LDAP connections." yaml:"insecure,omitempty"`
|
||||
SearchFilter string `description:"LDAP search filter." yaml:"searchFilter,omitempty"`
|
||||
AuthCert string `description:"Certificate for mTLS authentication." yaml:"authCert,omitempty"`
|
||||
AuthKey string `description:"Certificate key for mTLS authentication." yaml:"authKey,omitempty"`
|
||||
GroupCacheTTL int `description:"Cache duration for LDAP group membership in seconds." yaml:"groupCacheTTL,omitempty"`
|
||||
Address string `description:"LDAP server address." yaml:"address"`
|
||||
BindDN string `description:"Bind DN for LDAP authentication." yaml:"bindDn"`
|
||||
BindPassword string `description:"Bind password for LDAP authentication." yaml:"bindPassword"`
|
||||
BindPasswordFile string `description:"Path to the Bind password." yaml:"bindPasswordFile"`
|
||||
BaseDN string `description:"Base DN for LDAP searches." yaml:"baseDn"`
|
||||
Insecure bool `description:"Allow insecure LDAP connections." yaml:"insecure"`
|
||||
SearchFilter string `description:"LDAP search filter." yaml:"searchFilter"`
|
||||
AuthCert string `description:"Certificate for mTLS authentication." yaml:"authCert"`
|
||||
AuthKey string `description:"Certificate key for mTLS authentication." yaml:"authKey"`
|
||||
GroupCacheTTL int `description:"Cache duration for LDAP group membership in seconds." yaml:"groupCacheTTL"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string `description:"Log level (trace, debug, info, warn, error)." yaml:"level,omitempty"`
|
||||
Json bool `description:"Enable JSON formatted logs." yaml:"json,omitempty"`
|
||||
Streams LogStreams `description:"Configuration for specific log streams." yaml:"streams,omitempty"`
|
||||
Level string `description:"Log level (trace, debug, info, warn, error)." yaml:"level"`
|
||||
Json bool `description:"Enable JSON formatted logs." yaml:"json"`
|
||||
Streams LogStreams `description:"Configuration for specific log streams." yaml:"streams"`
|
||||
}
|
||||
|
||||
type LogStreams struct {
|
||||
HTTP LogStreamConfig `description:"HTTP request logging." yaml:"http,omitempty"`
|
||||
App LogStreamConfig `description:"Application logging." yaml:"app,omitempty"`
|
||||
Audit LogStreamConfig `description:"Audit logging." yaml:"audit,omitempty"`
|
||||
HTTP LogStreamConfig `description:"HTTP request logging." yaml:"http"`
|
||||
App LogStreamConfig `description:"Application logging." yaml:"app"`
|
||||
Audit LogStreamConfig `description:"Audit logging." yaml:"audit"`
|
||||
}
|
||||
|
||||
type LogStreamConfig struct {
|
||||
Enabled bool `description:"Enable this log stream." yaml:"enabled,omitempty"`
|
||||
Level string `description:"Log level for this stream. Use global if empty." yaml:"level,omitempty"`
|
||||
Enabled bool `description:"Enable this log stream." yaml:"enabled"`
|
||||
Level string `description:"Log level for this stream. Use global if empty." yaml:"level"`
|
||||
}
|
||||
|
||||
// no experimental features
|
||||
type ExperimentalConfig struct{}
|
||||
|
||||
type TailscaleConfig struct {
|
||||
Enabled bool `description:"Enable Tailscale integration." yaml:"enabled,omitempty"`
|
||||
APIToken string `description:"Tailscale API token." yaml:"apiToken,omitempty"`
|
||||
Tailnet string `description:"Tailnet name." yaml:"tailnet,omitempty"`
|
||||
CacheDuration int `description:"Cache duration for Tailscale device and user lists in seconds." yaml:"cacheDuration,omitempty"`
|
||||
Enabled bool `description:"Enable Tailscale integration." yaml:"enabled"`
|
||||
Dir string `description:"Tailscale state directory." yaml:"dir"`
|
||||
Hostname string `description:"Tailscale hostname." yaml:"hostname"`
|
||||
AuthKey string `description:"Tailscale auth key." yaml:"authKey"`
|
||||
Ephemeral bool `description:"Use ephemeral Tailscale node." yaml:"ephemeral"`
|
||||
Funnel bool `description:"Enable Tailscale Funnel." yaml:"funnel"`
|
||||
Listen bool `description:"Listen on the Tailscale address instead of standard address." yaml:"listen"`
|
||||
}
|
||||
|
||||
// OAuth/OIDC config
|
||||
|
||||
type OAuthServiceConfig struct {
|
||||
ClientID string `description:"OAuth client ID." yaml:"clientId,omitempty"`
|
||||
ClientSecret string `description:"OAuth client secret." yaml:"clientSecret,omitempty"`
|
||||
ClientSecretFile string `description:"Path to the file containing the OAuth client secret." yaml:"clientSecretFile,omitempty"`
|
||||
Whitelist []string `description:"Comma-separated list of allowed OAuth domains for this provider." yaml:"whitelist,omitempty"`
|
||||
WhitelistFile string `description:"Path to the OAuth whitelist file for this provider." yaml:"whitelistFile,omitempty"`
|
||||
Scopes []string `description:"OAuth scopes." yaml:"scopes,omitempty"`
|
||||
RedirectURL string `description:"OAuth redirect URL." yaml:"redirectUrl,omitempty"`
|
||||
AuthURL string `description:"OAuth authorization URL." yaml:"authUrl,omitempty"`
|
||||
TokenURL string `description:"OAuth token URL." yaml:"tokenUrl,omitempty"`
|
||||
UserinfoURL string `description:"OAuth userinfo URL." yaml:"userinfoUrl,omitempty"`
|
||||
Insecure bool `description:"Allow insecure OAuth connections." yaml:"insecure,omitempty"`
|
||||
Name string `description:"Provider name in UI." yaml:"name,omitempty"`
|
||||
ClientID string `description:"OAuth client ID." yaml:"clientId"`
|
||||
ClientSecret string `description:"OAuth client secret." yaml:"clientSecret"`
|
||||
ClientSecretFile string `description:"Path to the file containing the OAuth client secret." yaml:"clientSecretFile"`
|
||||
Whitelist []string `description:"Comma-separated list of allowed OAuth domains for this provider." yaml:"whitelist"`
|
||||
WhitelistFile string `description:"Path to the OAuth whitelist file for this provider." yaml:"whitelistFile"`
|
||||
Scopes []string `description:"OAuth scopes." yaml:"scopes"`
|
||||
RedirectURL string `description:"OAuth redirect URL." yaml:"redirectUrl"`
|
||||
AuthURL string `description:"OAuth authorization URL." yaml:"authUrl"`
|
||||
TokenURL string `description:"OAuth token URL." yaml:"tokenUrl"`
|
||||
UserinfoURL string `description:"OAuth userinfo URL." yaml:"userinfoUrl"`
|
||||
Insecure bool `description:"Allow insecure OAuth connections." yaml:"insecure"`
|
||||
Name string `description:"Provider name in UI." yaml:"name"`
|
||||
}
|
||||
|
||||
type OIDCClientConfig struct {
|
||||
ID string `description:"OIDC client ID." yaml:"-"`
|
||||
ClientID string `description:"OIDC client ID." yaml:"clientId,omitempty"`
|
||||
ClientSecret string `description:"OIDC client secret." yaml:"clientSecret,omitempty"`
|
||||
ClientSecretFile string `description:"Path to the file containing the OIDC client secret." yaml:"clientSecretFile,omitempty"`
|
||||
TrustedRedirectURIs []string `description:"List of trusted redirect URIs." yaml:"trustedRedirectUris,omitempty"`
|
||||
Name string `description:"Client name in UI." yaml:"name,omitempty"`
|
||||
ClientID string `description:"OIDC client ID." yaml:"clientId"`
|
||||
ClientSecret string `description:"OIDC client secret." yaml:"clientSecret"`
|
||||
ClientSecretFile string `description:"Path to the file containing the OIDC client secret." yaml:"clientSecretFile"`
|
||||
TrustedRedirectURIs []string `description:"List of trusted redirect URIs." yaml:"trustedRedirectUris"`
|
||||
Name string `description:"Client name in UI." yaml:"name"`
|
||||
}
|
||||
|
||||
type ACLsConfig struct {
|
||||
Policy string `description:"ACL policy for allow-by-default or deny-by-default, available options are allow and deny, default is allow." yaml:"policy,omitempty"`
|
||||
Policy string `description:"ACL policy for allow-by-default or deny-by-default, available options are allow and deny, default is allow." yaml:"policy"`
|
||||
}
|
||||
|
||||
// ACLs
|
||||
|
||||
type Apps struct {
|
||||
Apps map[string]App `description:"App ACLs configuration." yaml:"apps,omitempty"`
|
||||
Apps map[string]App `description:"App ACLs configuration." yaml:"apps"`
|
||||
}
|
||||
|
||||
type App struct {
|
||||
Config AppConfig `description:"App configuration." yaml:"config,omitempty"`
|
||||
Users AppUsers `description:"User access configuration." yaml:"users,omitempty"`
|
||||
OAuth AppOAuth `description:"OAuth access configuration." yaml:"oauth,omitempty"`
|
||||
IP AppIP `description:"IP access configuration." yaml:"ip,omitempty"`
|
||||
Response AppResponse `description:"Response customization." yaml:"response,omitempty"`
|
||||
Path AppPath `description:"Path access configuration." yaml:"path,omitempty"`
|
||||
LDAP AppLDAP `description:"LDAP access configuration." yaml:"ldap,omitempty"`
|
||||
Config AppConfig `description:"App configuration." yaml:"config"`
|
||||
Users AppUsers `description:"User access configuration." yaml:"users"`
|
||||
OAuth AppOAuth `description:"OAuth access configuration." yaml:"oauth"`
|
||||
IP AppIP `description:"IP access configuration." yaml:"ip"`
|
||||
Response AppResponse `description:"Response customization." yaml:"response"`
|
||||
Path AppPath `description:"Path access configuration." yaml:"path"`
|
||||
LDAP AppLDAP `description:"LDAP access configuration." yaml:"ldap"`
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Domain string `description:"The domain of the app." yaml:"domain,omitempty"`
|
||||
Domain string `description:"The domain of the app." yaml:"domain"`
|
||||
}
|
||||
|
||||
type AppUsers struct {
|
||||
Allow string `description:"Comma-separated list of allowed users." yaml:"allow,omitempty"`
|
||||
Block string `description:"Comma-separated list of blocked users." yaml:"block,omitempty"`
|
||||
Allow string `description:"Comma-separated list of allowed users." yaml:"allow"`
|
||||
Block string `description:"Comma-separated list of blocked users." yaml:"block"`
|
||||
}
|
||||
|
||||
type AppOAuth struct {
|
||||
Whitelist string `description:"Comma-separated list of allowed OAuth groups." yaml:"whitelist,omitempty"`
|
||||
Groups string `description:"Comma-separated list of required OAuth groups." yaml:"groups,omitempty"`
|
||||
Whitelist string `description:"Comma-separated list of allowed OAuth groups." yaml:"whitelist"`
|
||||
Groups string `description:"Comma-separated list of required OAuth groups." yaml:"groups"`
|
||||
}
|
||||
|
||||
type AppLDAP struct {
|
||||
Groups string `description:"Comma-separated list of required LDAP groups." yaml:"groups,omitempty"`
|
||||
Groups string `description:"Comma-separated list of required LDAP groups." yaml:"groups"`
|
||||
}
|
||||
|
||||
type AppIP struct {
|
||||
Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow,omitempty"`
|
||||
Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block,omitempty"`
|
||||
Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication." yaml:"bypass,omitempty"`
|
||||
Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"`
|
||||
Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"`
|
||||
Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication." yaml:"bypass"`
|
||||
}
|
||||
|
||||
type AppResponse struct {
|
||||
Headers []string `description:"Custom headers to add to the response." yaml:"headers,omitempty"`
|
||||
BasicAuth AppBasicAuth `description:"Basic authentication for the app." yaml:"basicAuth,omitempty"`
|
||||
Headers []string `description:"Custom headers to add to the response." yaml:"headers"`
|
||||
BasicAuth AppBasicAuth `description:"Basic authentication for the app." yaml:"basicAuth"`
|
||||
}
|
||||
|
||||
type AppBasicAuth struct {
|
||||
Username string `description:"Basic auth username." yaml:"username,omitempty"`
|
||||
Password string `description:"Basic auth password." yaml:"password,omitempty"`
|
||||
PasswordFile string `description:"Path to the file containing the basic auth password." yaml:"passwordFile,omitempty"`
|
||||
Username string `description:"Basic auth username." yaml:"username"`
|
||||
Password string `description:"Basic auth password." yaml:"password"`
|
||||
PasswordFile string `description:"Path to the file containing the basic auth password." yaml:"passwordFile"`
|
||||
}
|
||||
|
||||
type AppPath struct {
|
||||
Allow string `description:"Comma-separated list of allowed paths." yaml:"allow,omitempty"`
|
||||
Block string `description:"Comma-separated list of blocked paths." yaml:"block,omitempty"`
|
||||
Allow string `description:"Comma-separated list of allowed paths." yaml:"allow"`
|
||||
Block string `description:"Comma-separated list of blocked paths." yaml:"block"`
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ type LDAPContext struct {
|
||||
|
||||
type TailscaleContext struct {
|
||||
BaseContext
|
||||
NodeName string
|
||||
UserID string
|
||||
}
|
||||
|
||||
func (c *UserContext) IsAuthenticated() bool {
|
||||
@@ -249,7 +249,7 @@ func (c *UserContext) OAuthName() string {
|
||||
|
||||
func (c *UserContext) TailscaleNodeName() string {
|
||||
if c.Tailscale != nil {
|
||||
return c.Tailscale.NodeName
|
||||
return c.Tailscale.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package postgres
|
||||
|
||||
//go:generate go run github.com/tinyauthapp/tinyauth/gen/sqlc_wrapper -pkg github.com/tinyauthapp/tinyauth/internal/repository/postgres
|
||||
//go:generate go run github.com/tinyauthapp/tinyauth/gen/sqlc-wrapper -pkg github.com/tinyauthapp/tinyauth/internal/repository/postgres
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package sqlite
|
||||
|
||||
//go:generate go run github.com/tinyauthapp/tinyauth/gen/sqlc_wrapper -pkg github.com/tinyauthapp/tinyauth/internal/repository/sqlite
|
||||
//go:generate go run github.com/tinyauthapp/tinyauth/gen/sqlc-wrapper -pkg github.com/tinyauthapp/tinyauth/internal/repository/sqlite
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@@ -72,3 +75,38 @@ func githubExtractor(client *http.Client, _ string) (*model.Claims, error) {
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func simpleReq[T any](client *http.Client, url string, headers map[string]string) (*T, error) {
|
||||
var decodedRes T
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("request failed with status: %s", res.Status)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &decodedRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &decodedRes, nil
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func simpleReq[T any](client *http.Client, url string, headers map[string]string) (*T, error) {
|
||||
var decodedRes T
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("request failed with status: %s", res.Status)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &decodedRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &decodedRes, nil
|
||||
}
|
||||
@@ -2,75 +2,46 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
"go.uber.org/dig"
|
||||
"tailscale.com/client/local"
|
||||
"tailscale.com/tsnet"
|
||||
)
|
||||
|
||||
const tailscaleAPIBaseURL = "https://api.tailscale.com/api/v2"
|
||||
|
||||
var (
|
||||
tailscaleAPIDeviceList = func(tailnet string) string {
|
||||
return tailscaleAPIBaseURL + "/tailnet/" + tailnet + "/devices"
|
||||
}
|
||||
tailscaleAPIUserList = func(tailnet string) string {
|
||||
return tailscaleAPIBaseURL + "/tailnet/" + tailnet + "/users"
|
||||
}
|
||||
)
|
||||
|
||||
type tailscaleDevice struct {
|
||||
Addresses []string `json:"addresses"`
|
||||
User string `json:"user"`
|
||||
Name string `json:"name"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
type tailscaleAPIDevices struct {
|
||||
Devices []tailscaleDevice `json:"devices"`
|
||||
}
|
||||
|
||||
type tailscaleUser struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
LoginName string `json:"loginName"`
|
||||
}
|
||||
|
||||
type tailscaleAPIUsers struct {
|
||||
Users []tailscaleUser `json:"users"`
|
||||
}
|
||||
|
||||
type TailscaleWhoisResponse struct {
|
||||
DisplayName string
|
||||
UserID string
|
||||
LoginName string
|
||||
DisplayName string
|
||||
NodeName string
|
||||
}
|
||||
|
||||
type TailscaleService struct {
|
||||
config *model.Config
|
||||
log *logger.Logger
|
||||
client *http.Client
|
||||
config *model.Config
|
||||
ctx context.Context
|
||||
|
||||
caches struct {
|
||||
devices *CacheStore[tailscaleAPIDevices]
|
||||
users *CacheStore[tailscaleAPIUsers]
|
||||
}
|
||||
|
||||
urls struct {
|
||||
devices string
|
||||
users string
|
||||
}
|
||||
srv *tsnet.Server
|
||||
lc *local.Client
|
||||
ln *net.Listener
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
type TailscaleServiceInput struct {
|
||||
dig.In
|
||||
|
||||
Ding *ding.Ding
|
||||
Config *model.Config
|
||||
Log *logger.Logger
|
||||
Config *model.Config
|
||||
Ctx context.Context
|
||||
Ding *ding.Ding
|
||||
}
|
||||
|
||||
func NewTailscaleService(i TailscaleServiceInput) (*TailscaleService, error) {
|
||||
@@ -78,156 +49,150 @@ func NewTailscaleService(i TailscaleServiceInput) (*TailscaleService, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if i.Config.Tailscale.APIToken == "" {
|
||||
return nil, fmt.Errorf("tailscale api token not set")
|
||||
srv := new(tsnet.Server)
|
||||
|
||||
// node options
|
||||
srv.Dir = i.Config.Tailscale.Dir
|
||||
srv.Hostname = i.Config.Tailscale.Hostname
|
||||
srv.AuthKey = i.Config.Tailscale.AuthKey
|
||||
srv.Ephemeral = i.Config.Tailscale.Ephemeral
|
||||
|
||||
// redirect logs to zerolog
|
||||
srv.Logf = i.Log.App.Printf
|
||||
srv.UserLogf = i.Log.App.Printf
|
||||
|
||||
err := srv.Start()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start tailscale server: %w", err)
|
||||
}
|
||||
|
||||
if i.Config.Tailscale.Tailnet == "" {
|
||||
return nil, fmt.Errorf("tailscale tailnet not set")
|
||||
lc, err := srv.LocalClient()
|
||||
|
||||
if err != nil {
|
||||
_ = srv.Close()
|
||||
return nil, fmt.Errorf("failed to get tailscale local client: %w", err)
|
||||
}
|
||||
|
||||
s := &TailscaleService{
|
||||
config: i.Config,
|
||||
service := &TailscaleService{
|
||||
log: i.Log,
|
||||
config: i.Config,
|
||||
ctx: i.Ctx,
|
||||
srv: srv,
|
||||
lc: lc,
|
||||
}
|
||||
|
||||
devicesCache := NewCacheStore[tailscaleAPIDevices](0)
|
||||
usersCache := NewCacheStore[tailscaleAPIUsers](0)
|
||||
connectCtx, cancel := context.WithTimeout(i.Ctx, 2*time.Minute) // large enough timeout to allow for user to manually authenticate with link if needed
|
||||
defer cancel()
|
||||
|
||||
s.caches.devices = devicesCache
|
||||
s.caches.users = usersCache
|
||||
err = service.waitForConn(connectCtx)
|
||||
|
||||
i.Ding.Go(func(ctx context.Context) {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
if err != nil {
|
||||
_ = srv.Close()
|
||||
return nil, fmt.Errorf("failed to connect to tailscale network: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.caches.devices.Sweep()
|
||||
s.caches.users.Sweep()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
i.Ding.Go(service.watchAndClose, ding.RingMajor)
|
||||
|
||||
if i.Config.Tailscale.Funnel && !i.Config.Tailscale.Listen {
|
||||
service.log.App.Warn().Msg("Tailscale Funnel is enabled but listen is disabled. Funnel will not work without listen enabled.")
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (ts *TailscaleService) watchAndClose(ctx context.Context) {
|
||||
<-ctx.Done()
|
||||
ts.log.App.Debug().Msg("Shutting down Tailscale service")
|
||||
ts.mu.Lock()
|
||||
srv := ts.srv
|
||||
ln := ts.ln
|
||||
ts.ln = nil
|
||||
ts.srv = nil
|
||||
ts.mu.Unlock()
|
||||
if ln != nil {
|
||||
(*ln).Close()
|
||||
}
|
||||
if srv != nil {
|
||||
srv.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TailscaleService) Whois(ctx context.Context, addr string) (*TailscaleWhoisResponse, error) {
|
||||
who, err := ts.lc.WhoIs(ctx, addr)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, local.ErrPeerNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
}, ding.RingMinor)
|
||||
|
||||
s.urls.devices = tailscaleAPIDeviceList(i.Config.Tailscale.Tailnet)
|
||||
s.urls.users = tailscaleAPIUserList(i.Config.Tailscale.Tailnet)
|
||||
s.client = &http.Client{}
|
||||
|
||||
_, _, err := s.getDeviceList()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get device list: %w", err)
|
||||
return nil, fmt.Errorf("failed to get client whois: %w", err)
|
||||
}
|
||||
|
||||
_, _, err = s.getUsersList()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user list: %w", err)
|
||||
}
|
||||
|
||||
s.log.App.Info().Msg("Tailscale service initialized")
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *TailscaleService) buildAuthorizationHeader() string {
|
||||
return "Bearer " + s.config.Tailscale.APIToken
|
||||
}
|
||||
|
||||
func (s *TailscaleService) getDeviceList() (*tailscaleAPIDevices, bool, error) {
|
||||
cached, ok := s.caches.devices.Get("devices")
|
||||
|
||||
if ok {
|
||||
return &cached, true, nil
|
||||
}
|
||||
|
||||
devices, err := simpleReq[tailscaleAPIDevices](s.client, s.urls.devices, map[string]string{
|
||||
"Authorization": s.buildAuthorizationHeader(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to get device list: %w", err)
|
||||
}
|
||||
|
||||
s.caches.devices.Set("devices", *devices, time.Duration(s.config.Tailscale.CacheDuration)*time.Second)
|
||||
|
||||
return devices, false, nil
|
||||
}
|
||||
|
||||
func (s *TailscaleService) getUsersList() (*tailscaleAPIUsers, bool, error) {
|
||||
cached, ok := s.caches.users.Get("users")
|
||||
|
||||
if ok {
|
||||
return &cached, true, nil
|
||||
}
|
||||
|
||||
users, err := simpleReq[tailscaleAPIUsers](s.client, s.urls.users, map[string]string{
|
||||
"Authorization": s.buildAuthorizationHeader(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to get user list: %w", err)
|
||||
}
|
||||
|
||||
s.caches.users.Set("users", *users, time.Duration(s.config.Tailscale.CacheDuration)*time.Second)
|
||||
|
||||
return users, false, nil
|
||||
}
|
||||
|
||||
func (s *TailscaleService) Whois(addr string) (*TailscaleWhoisResponse, error) {
|
||||
var device *tailscaleDevice
|
||||
|
||||
devices, dCacheHit, err := s.getDeviceList()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get device list: %w", err)
|
||||
}
|
||||
|
||||
for _, d := range devices.Devices {
|
||||
if len(d.Tags) != 0 {
|
||||
continue
|
||||
}
|
||||
for _, a := range d.Addresses {
|
||||
if a == addr {
|
||||
device = &d
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if device == nil {
|
||||
if who.Node.IsTagged() {
|
||||
ts.log.App.Debug().Msgf("Skipping whois for tagged node %s", who.Node.Name)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
s.log.App.Debug().Str("device", device.Name).Bool("cache_hit", dCacheHit).Msg("Tailscale device found")
|
||||
uid := strings.TrimPrefix(who.UserProfile.ID.String(), "userid:")
|
||||
|
||||
var user *tailscaleUser
|
||||
res := TailscaleWhoisResponse{
|
||||
UserID: uid,
|
||||
LoginName: who.UserProfile.LoginName,
|
||||
DisplayName: who.UserProfile.DisplayName,
|
||||
NodeName: strings.TrimSuffix(who.Node.Name, "."),
|
||||
}
|
||||
|
||||
users, uCacheHit, err := s.getUsersList()
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (ts *TailscaleService) CreateListener() (net.Listener, error) {
|
||||
ts.mu.Lock()
|
||||
defer ts.mu.Unlock()
|
||||
|
||||
if ts.ln != nil {
|
||||
return *ts.ln, nil
|
||||
}
|
||||
|
||||
if ts.config.Tailscale.Funnel {
|
||||
ln, err := ts.srv.ListenFunnel("tcp", ":443")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts.ln = &ln
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
ln, err := ts.srv.ListenTLS("tcp", ":443")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts.ln = &ln
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func (ts *TailscaleService) GetHostname() string {
|
||||
status, err := ts.lc.Status(ts.ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user list: %w", err)
|
||||
ts.log.App.Error().Err(err).Msg("Failed to get Tailscale status")
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, u := range users.Users {
|
||||
if u.LoginName == device.User {
|
||||
user = &u
|
||||
break
|
||||
return strings.TrimSuffix(status.Self.DNSName, ".")
|
||||
}
|
||||
|
||||
func (ts *TailscaleService) waitForConn(ctx context.Context) error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("timed out waiting for tailscale connection")
|
||||
default:
|
||||
ip4, _ := ts.srv.TailscaleIPs()
|
||||
if !ip4.IsValid() {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
s.log.App.Debug().Str("user", user.LoginName).Bool("cache_hit", uCacheHit).Msg("Tailscale user found")
|
||||
|
||||
return &TailscaleWhoisResponse{
|
||||
LoginName: user.LoginName,
|
||||
DisplayName: user.DisplayName,
|
||||
NodeName: device.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user