mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-07-11 04:21:12 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f5b9d83360 | |||
| 40540ce133 |
+1
-72
@@ -7,9 +7,7 @@ TINYAUTH_APPURL=
|
|||||||
|
|
||||||
# database config
|
# database config
|
||||||
|
|
||||||
# The database driver to use. Valid values: sqlite, postgres, memory.
|
# The path to the database, including file name.
|
||||||
TINYAUTH_DATABASE_DRIVER="sqlite"
|
|
||||||
# The path to the SQLite database file, or connection URL when driver is postgres.
|
|
||||||
TINYAUTH_DATABASE_PATH="./tinyauth.db"
|
TINYAUTH_DATABASE_PATH="./tinyauth.db"
|
||||||
|
|
||||||
# analytics config
|
# analytics config
|
||||||
@@ -39,52 +37,8 @@ TINYAUTH_SERVER_SOCKETPATH=
|
|||||||
TINYAUTH_AUTH_IP_ALLOW=
|
TINYAUTH_AUTH_IP_ALLOW=
|
||||||
# List of blocked IPs or CIDR ranges.
|
# List of blocked IPs or CIDR ranges.
|
||||||
TINYAUTH_AUTH_IP_BLOCK=
|
TINYAUTH_AUTH_IP_BLOCK=
|
||||||
# List of IPs or CIDR ranges that bypass authentication entirely.
|
|
||||||
TINYAUTH_AUTH_IP_BYPASS=
|
|
||||||
# Comma-separated list of users (username:hashed_password).
|
# Comma-separated list of users (username:hashed_password).
|
||||||
TINYAUTH_AUTH_USERS=
|
TINYAUTH_AUTH_USERS=
|
||||||
# Enable subdomains support.
|
|
||||||
TINYAUTH_AUTH_SUBDOMAINSENABLED=true
|
|
||||||
# Full name of the user.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_NAME=
|
|
||||||
# Given (first) name of the user.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_GIVENNAME=
|
|
||||||
# Family (last) name of the user.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_FAMILYNAME=
|
|
||||||
# Middle name of the user.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_MIDDLENAME=
|
|
||||||
# Nickname of the user.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_NICKNAME=
|
|
||||||
# URL of the user's profile page.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_PROFILE=
|
|
||||||
# URL of the user's profile picture.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_PICTURE=
|
|
||||||
# URL of the user's website.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_WEBSITE=
|
|
||||||
# Email address of the user.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_EMAIL=
|
|
||||||
# Gender of the user.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_GENDER=
|
|
||||||
# Birthdate of the user (YYYY-MM-DD).
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_BIRTHDATE=
|
|
||||||
# Time zone of the user (e.g. Europe/Athens).
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ZONEINFO=
|
|
||||||
# Locale of the user (e.g. en-US).
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_LOCALE=
|
|
||||||
# Phone number of the user.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_PHONENUMBER=
|
|
||||||
# Full mailing address, formatted for display.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_FORMATTED=
|
|
||||||
# Street address.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_STREETADDRESS=
|
|
||||||
# City or locality.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_LOCALITY=
|
|
||||||
# State, province, or region.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_REGION=
|
|
||||||
# Zip or postal code.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_POSTALCODE=
|
|
||||||
# Country.
|
|
||||||
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_COUNTRY=
|
|
||||||
# Path to the users file.
|
# Path to the users file.
|
||||||
TINYAUTH_AUTH_USERSFILE=
|
TINYAUTH_AUTH_USERSFILE=
|
||||||
# Enable secure cookies.
|
# Enable secure cookies.
|
||||||
@@ -97,12 +51,8 @@ TINYAUTH_AUTH_SESSIONMAXLIFETIME=0
|
|||||||
TINYAUTH_AUTH_LOGINTIMEOUT=300
|
TINYAUTH_AUTH_LOGINTIMEOUT=300
|
||||||
# Maximum login retries.
|
# Maximum login retries.
|
||||||
TINYAUTH_AUTH_LOGINMAXRETRIES=3
|
TINYAUTH_AUTH_LOGINMAXRETRIES=3
|
||||||
# Enable lockdown mode after maximum login retries. Lockdown mode limit is calculated automatically.
|
|
||||||
TINYAUTH_AUTH_LOCKDOWNENABLED=true
|
|
||||||
# Comma-separated list of trusted proxy addresses.
|
# Comma-separated list of trusted proxy addresses.
|
||||||
TINYAUTH_AUTH_TRUSTEDPROXIES=
|
TINYAUTH_AUTH_TRUSTEDPROXIES=
|
||||||
# ACL policy for allow-by-default or deny-by-default, available options are allow and deny, default is allow.
|
|
||||||
TINYAUTH_AUTH_ACLS_POLICY="allow"
|
|
||||||
|
|
||||||
# apps config
|
# apps config
|
||||||
|
|
||||||
@@ -151,10 +101,6 @@ TINYAUTH_OAUTH_PROVIDERS_name_CLIENTID=
|
|||||||
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRET=
|
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRET=
|
||||||
# Path to the file containing the OAuth client secret.
|
# Path to the file containing the OAuth client secret.
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRETFILE=
|
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRETFILE=
|
||||||
# Comma-separated list of allowed OAuth domains for this provider.
|
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_WHITELIST=
|
|
||||||
# Path to the OAuth whitelist file for this provider.
|
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_WHITELISTFILE=
|
|
||||||
# OAuth scopes.
|
# OAuth scopes.
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_SCOPES=
|
TINYAUTH_OAUTH_PROVIDERS_name_SCOPES=
|
||||||
# OAuth redirect URL.
|
# OAuth redirect URL.
|
||||||
@@ -206,8 +152,6 @@ TINYAUTH_LDAP_ADDRESS=
|
|||||||
TINYAUTH_LDAP_BINDDN=
|
TINYAUTH_LDAP_BINDDN=
|
||||||
# Bind password for LDAP authentication.
|
# Bind password for LDAP authentication.
|
||||||
TINYAUTH_LDAP_BINDPASSWORD=
|
TINYAUTH_LDAP_BINDPASSWORD=
|
||||||
# Path to the Bind password.
|
|
||||||
TINYAUTH_LDAP_BINDPASSWORDFILE=
|
|
||||||
# Base DN for LDAP searches.
|
# Base DN for LDAP searches.
|
||||||
TINYAUTH_LDAP_BASEDN=
|
TINYAUTH_LDAP_BASEDN=
|
||||||
# Allow insecure LDAP connections.
|
# Allow insecure LDAP connections.
|
||||||
@@ -221,21 +165,6 @@ TINYAUTH_LDAP_AUTHKEY=
|
|||||||
# Cache duration for LDAP group membership in seconds.
|
# Cache duration for LDAP group membership in seconds.
|
||||||
TINYAUTH_LDAP_GROUPCACHETTL=900
|
TINYAUTH_LDAP_GROUPCACHETTL=900
|
||||||
|
|
||||||
# tailscale config
|
|
||||||
|
|
||||||
# Enable Tailscale integration.
|
|
||||||
TINYAUTH_TAILSCALE_ENABLED=false
|
|
||||||
# Tailscale API token.
|
|
||||||
TINYAUTH_TAILSCALE_APITOKEN=
|
|
||||||
# Path to the file containing the Tailscale API token.
|
|
||||||
TINYAUTH_TAILSCALE_APITOKENFILE=
|
|
||||||
# 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"
|
|
||||||
|
|
||||||
# log config
|
# log config
|
||||||
|
|
||||||
# Log level (trace, debug, info, warn, error).
|
# Log level (trace, debug, info, warn, error).
|
||||||
|
|||||||
@@ -13,32 +13,32 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||||
with:
|
with:
|
||||||
package_json_file: ./frontend/package.json
|
package_json_file: ./frontend/package.json
|
||||||
|
|
||||||
- name: Setup go
|
- name: Setup go
|
||||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||||
with:
|
with:
|
||||||
go-version: "^1.26.4"
|
go-version: "^1.26.0"
|
||||||
|
|
||||||
- name: Go dependencies
|
- name: Go dependencies
|
||||||
run: go mod download
|
run: go mod download
|
||||||
|
|
||||||
- name: Setup sqlc
|
- name: Setup sqlc
|
||||||
uses: sqlc-dev/setup-sqlc@v5
|
uses: sqlc-dev/setup-sqlc@v4
|
||||||
with:
|
with:
|
||||||
sqlc-version: "1.31.1"
|
sqlc-version: "1.31.1"
|
||||||
|
|
||||||
- name: Check codegen is up to date
|
- name: Check codegen is up to date
|
||||||
run: |
|
run: |
|
||||||
sqlc generate
|
sqlc generate
|
||||||
go generate ./...
|
go generate ./internal/repository/...
|
||||||
git diff --exit-code
|
git diff --exit-code -- internal/repository/
|
||||||
git status --porcelain | grep -q . && echo "untracked files code gen files" && exit 1 || true
|
git status --porcelain -- internal/repository/ | grep -q . && echo "untracked files in internal/repository/" && exit 1 || true
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
@@ -62,6 +62,6 @@ jobs:
|
|||||||
run: go test -coverprofile=coverage.txt -v ./...
|
run: go test -coverprofile=coverage.txt -v ./...
|
||||||
|
|
||||||
- name: Upload coverage reports to Codecov
|
- name: Upload coverage reports to Codecov
|
||||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Delete old release
|
- name: Delete old release
|
||||||
run: gh release delete --cleanup-tag --yes nightly || echo release not found
|
run: gh release delete --cleanup-tag --yes nightly || echo release not found
|
||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
REPO: ${{ github.event.repository.name }}
|
REPO: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3
|
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
tag_name: nightly
|
tag_name: nightly
|
||||||
@@ -35,10 +35,9 @@ jobs:
|
|||||||
VERSION: ${{ steps.metadata.outputs.VERSION }}
|
VERSION: ${{ steps.metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
BUILD_TAGS: ${{ steps.metadata.outputs.BUILD_TAGS }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
ref: nightly
|
ref: nightly
|
||||||
|
|
||||||
@@ -48,8 +47,6 @@ jobs:
|
|||||||
echo "VERSION=nightly" >> "$GITHUB_OUTPUT"
|
echo "VERSION=nightly" >> "$GITHUB_OUTPUT"
|
||||||
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||||
echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$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:
|
binary-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -58,19 +55,19 @@ jobs:
|
|||||||
- generate-metadata
|
- generate-metadata
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
ref: nightly
|
ref: nightly
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||||
with:
|
with:
|
||||||
package_json_file: ./frontend/package.json
|
package_json_file: ./frontend/package.json
|
||||||
|
|
||||||
- name: Install go
|
- name: Install go
|
||||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||||
with:
|
with:
|
||||||
go-version: "^1.26.4"
|
go-version: "^1.26.0"
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
@@ -86,11 +83,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cp -r frontend/dist internal/assets/dist
|
cp -r frontend/dist internal/assets/dist
|
||||||
go build -tags ${{ needs.generate-metadata.outputs.BUILD_TAGS }} \
|
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
|
||||||
-ldflags "-X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} \
|
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} \
|
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" \
|
|
||||||
-o tinyauth-amd64 ./cmd/tinyauth
|
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
@@ -107,19 +100,19 @@ jobs:
|
|||||||
- generate-metadata
|
- generate-metadata
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
ref: nightly
|
ref: nightly
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||||
with:
|
with:
|
||||||
package_json_file: ./frontend/package.json
|
package_json_file: ./frontend/package.json
|
||||||
|
|
||||||
- name: Install go
|
- name: Install go
|
||||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||||
with:
|
with:
|
||||||
go-version: "^1.26.4"
|
go-version: "^1.26.0"
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
@@ -135,11 +128,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cp -r frontend/dist internal/assets/dist
|
cp -r frontend/dist internal/assets/dist
|
||||||
go build -tags ${{ needs.generate-metadata.outputs.BUILD_TAGS }} \
|
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
|
||||||
-ldflags "-X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} \
|
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} \
|
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" \
|
|
||||||
-o tinyauth-arm64 ./cmd/tinyauth
|
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
@@ -156,42 +145,41 @@ jobs:
|
|||||||
- generate-metadata
|
- generate-metadata
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
ref: nightly
|
ref: nightly
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
cache-from: type=gha,scope=buildkit-amd64
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max,scope=buildkit-amd64
|
cache-to: type=gha,mode=max
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
@@ -202,7 +190,7 @@ jobs:
|
|||||||
- name: Upload digest
|
- name: Upload digest
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: digests-alpine-linux-amd64
|
name: digests-linux-amd64
|
||||||
path: ${{ runner.temp }}/digests/*
|
path: ${{ runner.temp }}/digests/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
@@ -215,28 +203,28 @@ jobs:
|
|||||||
- image-build
|
- image-build
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
ref: nightly
|
ref: nightly
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
@@ -244,14 +232,13 @@ jobs:
|
|||||||
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
file: Dockerfile.distroless
|
file: Dockerfile.distroless
|
||||||
cache-from: type=gha,scope=buildkit-distroless-amd64
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max,scope=buildkit-distroless-amd64
|
cache-to: type=gha,mode=max
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
@@ -274,42 +261,41 @@ jobs:
|
|||||||
- generate-metadata
|
- generate-metadata
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
ref: nightly
|
ref: nightly
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
cache-from: type=gha,scope=buildkit-arm64
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max,scope=buildkit-arm64
|
cache-to: type=gha,mode=max
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
@@ -320,7 +306,7 @@ jobs:
|
|||||||
- name: Upload digest
|
- name: Upload digest
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: digests-alpine-linux-arm64
|
name: digests-linux-arm64
|
||||||
path: ${{ runner.temp }}/digests/*
|
path: ${{ runner.temp }}/digests/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
@@ -333,28 +319,28 @@ jobs:
|
|||||||
- image-build-arm
|
- image-build-arm
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
ref: nightly
|
ref: nightly
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
@@ -362,14 +348,13 @@ jobs:
|
|||||||
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
file: Dockerfile.distroless
|
file: Dockerfile.distroless
|
||||||
cache-from: type=gha,scope=buildkit-distroless-arm64
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max,scope=buildkit-distroless-arm64
|
cache-to: type=gha,mode=max
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
@@ -395,22 +380,22 @@ jobs:
|
|||||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||||
with:
|
with:
|
||||||
path: ${{ runner.temp }}/digests
|
path: ${{ runner.temp }}/digests
|
||||||
pattern: digests-alpine-*
|
pattern: digests-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
flavor: |
|
flavor: |
|
||||||
@@ -438,18 +423,18 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
flavor: |
|
flavor: |
|
||||||
@@ -476,7 +461,7 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3
|
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3
|
||||||
with:
|
with:
|
||||||
files: binaries/*
|
files: binaries/*
|
||||||
tag_name: nightly
|
tag_name: nightly
|
||||||
|
|||||||
@@ -16,21 +16,16 @@ jobs:
|
|||||||
VERSION: ${{ steps.metadata.outputs.VERSION }}
|
VERSION: ${{ steps.metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
BUILD_TAGS: ${{ steps.metadata.outputs.BUILD_TAGS }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Generate metadata
|
- name: Generate metadata
|
||||||
id: metadata
|
id: metadata
|
||||||
env:
|
|
||||||
REF_NAME: ${{ github.ref_name }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$REF_NAME" >> "$GITHUB_OUTPUT"
|
echo "VERSION=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
|
||||||
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||||
echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$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:
|
binary-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -38,17 +33,17 @@ jobs:
|
|||||||
- generate-metadata
|
- generate-metadata
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||||
with:
|
with:
|
||||||
package_json_file: ./frontend/package.json
|
package_json_file: ./frontend/package.json
|
||||||
|
|
||||||
- name: Install go
|
- name: Install go
|
||||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||||
with:
|
with:
|
||||||
go-version: "^1.26.4"
|
go-version: "^1.26.0"
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
@@ -64,11 +59,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cp -r frontend/dist internal/assets/dist
|
cp -r frontend/dist internal/assets/dist
|
||||||
go build -tags ${{ needs.generate-metadata.outputs.BUILD_TAGS }} \
|
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
|
||||||
-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:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
@@ -84,17 +75,17 @@ jobs:
|
|||||||
- generate-metadata
|
- generate-metadata
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
||||||
with:
|
with:
|
||||||
package_json_file: ./frontend/package.json
|
package_json_file: ./frontend/package.json
|
||||||
|
|
||||||
- name: Install go
|
- name: Install go
|
||||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||||
with:
|
with:
|
||||||
go-version: "^1.26.4"
|
go-version: "^1.26.0"
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
@@ -110,11 +101,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cp -r frontend/dist internal/assets/dist
|
cp -r frontend/dist internal/assets/dist
|
||||||
go build -tags ${{ needs.generate-metadata.outputs.BUILD_TAGS }} \
|
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
|
||||||
-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:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
@@ -130,41 +117,39 @@ jobs:
|
|||||||
- generate-metadata
|
- generate-metadata
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
cache-from: type=gha,scope=buildkit-amd64
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max,scope=buildkit-amd64
|
cache-to: type=gha,mode=max
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
LDFLAGS=-s -w
|
|
||||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
@@ -175,7 +160,7 @@ jobs:
|
|||||||
- name: Upload digest
|
- name: Upload digest
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: digests-alpine-linux-amd64
|
name: digests-linux-amd64
|
||||||
path: ${{ runner.temp }}/digests/*
|
path: ${{ runner.temp }}/digests/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
@@ -187,26 +172,26 @@ jobs:
|
|||||||
- image-build
|
- image-build
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
@@ -214,15 +199,13 @@ jobs:
|
|||||||
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
file: Dockerfile.distroless
|
file: Dockerfile.distroless
|
||||||
cache-from: type=gha,scope=buildkit-distroless-amd64
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max,scope=buildkit-distroless-amd64
|
cache-to: type=gha,mode=max
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
LDFLAGS=-s -w
|
|
||||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
@@ -244,41 +227,39 @@ jobs:
|
|||||||
- generate-metadata
|
- generate-metadata
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
cache-from: type=gha,scope=buildkit-arm64
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max,scope=buildkit-arm64
|
cache-to: type=gha,mode=max
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
LDFLAGS=-s -w
|
|
||||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
@@ -289,7 +270,7 @@ jobs:
|
|||||||
- name: Upload digest
|
- name: Upload digest
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: digests-alpine-linux-arm64
|
name: digests-linux-arm64
|
||||||
path: ${{ runner.temp }}/digests/*
|
path: ${{ runner.temp }}/digests/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
@@ -301,26 +282,26 @@ jobs:
|
|||||||
- image-build-arm
|
- image-build-arm
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
@@ -328,15 +309,13 @@ jobs:
|
|||||||
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
file: Dockerfile.distroless
|
file: Dockerfile.distroless
|
||||||
cache-from: type=gha,scope=buildkit-distroless-arm64
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max,scope=buildkit-distroless-arm64
|
cache-to: type=gha,mode=max
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
||||||
LDFLAGS=-s -w
|
|
||||||
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
|
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
@@ -362,22 +341,22 @@ jobs:
|
|||||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||||
with:
|
with:
|
||||||
path: ${{ runner.temp }}/digests
|
path: ${{ runner.temp }}/digests
|
||||||
pattern: digests-alpine-*
|
pattern: digests-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
flavor: |
|
flavor: |
|
||||||
@@ -407,18 +386,18 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
flavor: |
|
flavor: |
|
||||||
@@ -449,6 +428,6 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3
|
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3
|
||||||
with:
|
with:
|
||||||
files: binaries/*
|
files: binaries/*
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -38,6 +38,6 @@ jobs:
|
|||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
- name: Upload to code-scanning
|
- name: Upload to code-scanning
|
||||||
uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
|
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Generate Sponsors
|
- name: Generate Sponsors
|
||||||
uses: JamesIves/github-sponsors-readme-action@2fd9142e765f755780202122261dc85e78459405 # v1
|
uses: JamesIves/github-sponsors-readme-action@2fd9142e765f755780202122261dc85e78459405 # v1
|
||||||
|
|||||||
@@ -51,6 +51,3 @@ config.certify.yml
|
|||||||
|
|
||||||
# deepsec
|
# deepsec
|
||||||
/.deepsec
|
/.deepsec
|
||||||
|
|
||||||
# jetbrains
|
|
||||||
/.idea/
|
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ Contributing to Tinyauth is straightforward. Follow the steps below to set up a
|
|||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- pnpm
|
- pnpm
|
||||||
- Golang v1.26.4 or later
|
- Golang v1.24.0 or later
|
||||||
- Git
|
- Git
|
||||||
- Docker
|
- Docker
|
||||||
- Make
|
- Make
|
||||||
|
|||||||
+9
-14
@@ -1,5 +1,5 @@
|
|||||||
# Site builder
|
# Site builder
|
||||||
FROM node:26.4-alpine3.23 AS frontend-builder
|
FROM node:26.1-alpine3.23 AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /frontend
|
WORKDIR /frontend
|
||||||
|
|
||||||
@@ -27,8 +27,6 @@ FROM golang:1.26-alpine3.23 AS builder
|
|||||||
ARG VERSION
|
ARG VERSION
|
||||||
ARG COMMIT_HASH
|
ARG COMMIT_HASH
|
||||||
ARG BUILD_TIMESTAMP
|
ARG BUILD_TIMESTAMP
|
||||||
ARG LDFLAGS
|
|
||||||
ARG BUILD_TAGS
|
|
||||||
|
|
||||||
WORKDIR /tinyauth
|
WORKDIR /tinyauth
|
||||||
|
|
||||||
@@ -41,30 +39,27 @@ COPY ./cmd ./cmd
|
|||||||
COPY ./internal ./internal
|
COPY ./internal ./internal
|
||||||
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 go build -tags "${BUILD_TAGS}" -ldflags "${LDFLAGS} \
|
RUN CGO_ENABLED=0 go build -ldflags "-s -w \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \
|
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
|
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
||||||
-o tinyauth ./cmd/tinyauth
|
|
||||||
|
|
||||||
# Runner
|
# Runner
|
||||||
FROM alpine:3.24 AS runner
|
FROM alpine:3.23 AS runner
|
||||||
|
|
||||||
WORKDIR /tinyauth
|
WORKDIR /tinyauth
|
||||||
|
|
||||||
COPY --from=builder /tinyauth/tinyauth ./
|
COPY --from=builder /tinyauth/tinyauth ./
|
||||||
|
|
||||||
EXPOSE 3000
|
RUN mkdir -p /data
|
||||||
|
|
||||||
# Make the data directory with a non-root user
|
EXPOSE 3000
|
||||||
RUN addgroup tinyauth && adduser -DH tinyauth -G tinyauth
|
|
||||||
RUN mkdir -p /data/resources /data/oidc /data/tailscale
|
|
||||||
RUN chown -R tinyauth:tinyauth /data
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
# Tell tinyauth that it's running in a container and where to find the data directory
|
ENV TINYAUTH_DATABASE_PATH=/data/tinyauth.db
|
||||||
ENV RUNTIME_ENV=docker
|
|
||||||
|
ENV TINYAUTH_RESOURCES_PATH=/data/resources
|
||||||
|
|
||||||
ENV PATH=$PATH:/tinyauth
|
ENV PATH=$PATH:/tinyauth
|
||||||
|
|
||||||
|
|||||||
+10
-15
@@ -1,5 +1,5 @@
|
|||||||
# Site builder
|
# Site builder
|
||||||
FROM node:26.4-alpine3.23 AS frontend-builder
|
FROM node:26.1-alpine3.23 AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /frontend
|
WORKDIR /frontend
|
||||||
|
|
||||||
@@ -27,8 +27,6 @@ FROM golang:1.26-alpine3.23 AS builder
|
|||||||
ARG VERSION
|
ARG VERSION
|
||||||
ARG COMMIT_HASH
|
ARG COMMIT_HASH
|
||||||
ARG BUILD_TIMESTAMP
|
ARG BUILD_TIMESTAMP
|
||||||
ARG LDFLAGS
|
|
||||||
ARG BUILD_TAGS
|
|
||||||
|
|
||||||
WORKDIR /tinyauth
|
WORKDIR /tinyauth
|
||||||
|
|
||||||
@@ -37,20 +35,16 @@ COPY go.sum ./
|
|||||||
|
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
COPY ./cmd ./cmd/
|
COPY ./cmd ./cmd
|
||||||
COPY ./internal ./internal
|
COPY ./internal ./internal
|
||||||
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 go build -tags "${BUILD_TAGS}" -ldflags "${LDFLAGS} \
|
RUN mkdir -p data
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 go build -ldflags "-s -w \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \
|
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
|
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
||||||
-o tinyauth ./cmd/tinyauth
|
|
||||||
|
|
||||||
# Make the data directory with a non-root user
|
|
||||||
RUN addgroup tinyauth && adduser -DH tinyauth -G tinyauth
|
|
||||||
RUN mkdir -p /data/resources /data/oidc /data/tailscale
|
|
||||||
RUN chown -R tinyauth:tinyauth /data
|
|
||||||
|
|
||||||
# Runner
|
# Runner
|
||||||
FROM gcr.io/distroless/static-debian12:latest AS runner
|
FROM gcr.io/distroless/static-debian12:latest AS runner
|
||||||
@@ -60,14 +54,15 @@ WORKDIR /tinyauth
|
|||||||
COPY --from=builder /tinyauth/tinyauth ./
|
COPY --from=builder /tinyauth/tinyauth ./
|
||||||
|
|
||||||
# Since it's distroless, we need to copy the data directory from the builder stage
|
# Since it's distroless, we need to copy the data directory from the builder stage
|
||||||
COPY --from=builder /data /data
|
COPY --from=builder /tinyauth/data /data
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
# Tell tinyauth that it's running in a container and where to find the data directory
|
ENV TINYAUTH_DATABASE_PATH=/data/tinyauth.db
|
||||||
ENV RUNTIME_ENV=docker
|
|
||||||
|
ENV TINYAUTH_RESOURCES_PATH=/data/resources
|
||||||
|
|
||||||
ENV PATH=$PATH:/tinyauth
|
ENV PATH=$PATH:/tinyauth
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ TAG_NAME := $(shell git describe --abbrev=0 --exact-match 2> /dev/null || echo "
|
|||||||
COMMIT_HASH := $(shell git rev-parse HEAD)
|
COMMIT_HASH := $(shell git rev-parse HEAD)
|
||||||
BUILD_TIMESTAMP := $(shell date '+%Y-%m-%dT%H:%M:%S')
|
BUILD_TIMESTAMP := $(shell date '+%Y-%m-%dT%H:%M:%S')
|
||||||
BIN_NAME := tinyauth-$(GOARCH)
|
BIN_NAME := tinyauth-$(GOARCH)
|
||||||
LDFLAGS := -s -w
|
|
||||||
# Whatever 6MB serialization lib Gin is using
|
|
||||||
GIN_BUILD_TAGS := nomsgpack
|
|
||||||
BUILD_TAGS = $(GIN_BUILD_TAGS)
|
|
||||||
|
|
||||||
# Development vars
|
# Development vars
|
||||||
DEV_COMPOSE := $(shell test -f "docker-compose.test.yml" && echo "docker-compose.test.yml" || echo "docker-compose.dev.yml" )
|
DEV_COMPOSE := $(shell test -f "docker-compose.test.yml" && echo "docker-compose.test.yml" || echo "docker-compose.dev.yml" )
|
||||||
@@ -19,8 +15,6 @@ PROD_COMPOSE := $(shell test -f "docker-compose.test.prod.yml" && echo "docker-c
|
|||||||
|
|
||||||
.DEFAULT_GOAL := binary
|
.DEFAULT_GOAL := binary
|
||||||
|
|
||||||
.PHONY: deps clean-data clean-webui webui binary binary-linux-amd64 binary-linux-arm64 test vet test-race dev dev-infisical prod prod-infisical sql generate docker docker-distroless
|
|
||||||
|
|
||||||
# Deps
|
# Deps
|
||||||
deps:
|
deps:
|
||||||
cd frontend && pnpm ci
|
cd frontend && pnpm ci
|
||||||
@@ -42,7 +36,7 @@ webui: clean-webui
|
|||||||
|
|
||||||
# Build the binary
|
# Build the binary
|
||||||
binary: webui
|
binary: webui
|
||||||
CGO_ENABLED=$(CGO_ENABLED) go build -tags "${BUILD_TAGS}" -ldflags "${LDFLAGS} \
|
CGO_ENABLED=$(CGO_ENABLED) go build -ldflags "-s -w \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${TAG_NAME} \
|
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${TAG_NAME} \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
|
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
|
||||||
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
||||||
@@ -63,17 +57,10 @@ binary-linux-arm64:
|
|||||||
$(MAKE) binary
|
$(MAKE) binary
|
||||||
|
|
||||||
# Go test
|
# Go test
|
||||||
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
go test -v ./...
|
go test -v ./...
|
||||||
|
|
||||||
# Go vet
|
|
||||||
vet:
|
|
||||||
go vet ./...
|
|
||||||
|
|
||||||
# Go race
|
|
||||||
test-race:
|
|
||||||
go test -race ./...
|
|
||||||
|
|
||||||
# Development
|
# Development
|
||||||
dev:
|
dev:
|
||||||
docker compose -f $(DEV_COMPOSE) up --force-recreate --pull=always --remove-orphans --build
|
docker compose -f $(DEV_COMPOSE) up --force-recreate --pull=always --remove-orphans --build
|
||||||
@@ -91,27 +78,11 @@ prod-infisical:
|
|||||||
infisical run --env=dev -- docker compose -f $(PROD_COMPOSE) up --force-recreate --pull=always --remove-orphans
|
infisical run --env=dev -- docker compose -f $(PROD_COMPOSE) up --force-recreate --pull=always --remove-orphans
|
||||||
|
|
||||||
# SQL
|
# SQL
|
||||||
|
.PHONY: sql
|
||||||
sql:
|
sql:
|
||||||
sqlc generate
|
sqlc generate
|
||||||
|
|
||||||
# Go gen
|
# Go gen
|
||||||
generate:
|
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 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 .
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img alt="Tinyauth" title="Tinyauth" width="96" src="assets/logo-rounded.png">
|
<img alt="Tinyauth" title="Tinyauth" width="96" src="assets/logo-rounded.png">
|
||||||
<h1>Tinyauth</h1>
|
<h1>Tinyauth</h1>
|
||||||
<p>The tiniest OpenID Certified™ authorization and authentication server you have ever seen.</p>
|
<p>The tiniest authentication and authorization server you have ever seen.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
@@ -28,10 +28,6 @@ Tinyauth is the simplest and tiniest authentication and authorization server you
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> This is the main development branch. For the latest stable release, see the [documentation](https://tinyauth.app) or the latest stable tag.
|
> This is the main development branch. For the latest stable release, see the [documentation](https://tinyauth.app) or the latest stable tag.
|
||||||
|
|
||||||
As of 2026-06-25, Tinyauth v5.1.0 is OpenID Certified™ for Basic OP. You can find the certification details [here](https://openid.net/certification-old/certified-openid-providers-profiles/), test suite available [here](https://www.certification.openid.net/plan-detail.html?public=true&plan=H0qhpsOcQkxUE).
|
|
||||||
|
|
||||||
<img alt="OpenID Certified" width="200" src="https://openid.net/wordpress-content/uploads/2016/05/oid-l-certification-mark-l-cmyk-150dpi-90mm.jpg" />
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
You can get started with Tinyauth by following the guide in the [documentation](https://tinyauth.app/docs/getting-started). There is also an available [docker-compose](./docker-compose.example.yml) file that has Traefik, Whoami and Tinyauth to demonstrate its capabilities (keep in mind that this file lives in the development branch so it may have updates that are not yet released).
|
You can get started with Tinyauth by following the guide in the [documentation](https://tinyauth.app/docs/getting-started). There is also an available [docker-compose](./docker-compose.example.yml) file that has Traefik, Whoami and Tinyauth to demonstrate its capabilities (keep in mind that this file lives in the development branch so it may have updates that are not yet released).
|
||||||
@@ -62,20 +58,11 @@ If you like, you can help translate Tinyauth into more languages by visiting the
|
|||||||
|
|
||||||
Tinyauth is licensed under the GNU Affero General Public License v3.0. TL;DR — You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) AGPL-licensed code must also be made available under the AGPL along with build & install instructions. If you run a modified version over a network, you must also make the source available to the users of that service. For more information about the license check the [license](LICENSE) file.
|
Tinyauth is licensed under the GNU Affero General Public License v3.0. TL;DR — You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) AGPL-licensed code must also be made available under the AGPL along with build & install instructions. If you run a modified version over a network, you must also make the source available to the users of that service. For more information about the license check the [license](LICENSE) file.
|
||||||
|
|
||||||
|
|
||||||
## Hosting Partners
|
|
||||||
|
|
||||||
If you use one of our partners, you can help support us while getting a great hosting deal.
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<a title="InstaPods" target="_blank" href="https://app.instapods.com/dashboard/pods/create?app=tinyauth&ref=tinyauth"><img src="https://instapods.com/deploy-button.svg"></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
A big thank you to the following people for providing me with more coffee:
|
A big thank you to the following people for providing me with more coffee:
|
||||||
|
|
||||||
<!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https://github.com/erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a> <a href="https://github.com/nicotsx"><img src="https://github.com/nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a> <a href="https://github.com/SimpleHomelab"><img src="https://github.com/SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a> <a href="https://github.com/jmadden91"><img src="https://github.com/jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a> <a href="https://github.com/tribor"><img src="https://github.com/tribor.png" width="64px" alt="User avatar: tribor" /></a> <a href="https://github.com/eliasbenb"><img src="https://github.com/eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a> <a href="https://github.com/afunworm"><img src="https://github.com/afunworm.png" width="64px" alt="User avatar: afunworm" /></a> <a href="https://github.com/chip-well"><img src="https://github.com/chip-well.png" width="64px" alt="User avatar: chip-well" /></a> <a href="https://github.com/Lancelot-Enguerrand"><img src="https://github.com/Lancelot-Enguerrand.png" width="64px" alt="User avatar: Lancelot-Enguerrand" /></a> <a href="https://github.com/allgoewer"><img src="https://github.com/allgoewer.png" width="64px" alt="User avatar: allgoewer" /></a> <a href="https://github.com/NEANC"><img src="https://github.com/NEANC.png" width="64px" alt="User avatar: NEANC" /></a> <a href="https://github.com/axjab"><img src="https://github.com/axjab.png" width="64px" alt="User avatar: axjab" /></a> <a href="https://github.com/stegratech"><img src="https://github.com/stegratech.png" width="64px" alt="User avatar: stegratech" /></a> <a href="https://github.com/apearson"><img src="https://github.com/apearson.png" width="64px" alt="User avatar: apearson" /></a> <a href="https://github.com/Micky5991"><img src="https://github.com/Micky5991.png" width="64px" alt="User avatar: Micky5991" /></a> <!-- sponsors -->
|
<!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https://github.com/erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a> <a href="https://github.com/nicotsx"><img src="https://github.com/nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a> <a href="https://github.com/SimpleHomelab"><img src="https://github.com/SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a> <a href="https://github.com/jmadden91"><img src="https://github.com/jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a> <a href="https://github.com/tribor"><img src="https://github.com/tribor.png" width="64px" alt="User avatar: tribor" /></a> <a href="https://github.com/eliasbenb"><img src="https://github.com/eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a> <a href="https://github.com/afunworm"><img src="https://github.com/afunworm.png" width="64px" alt="User avatar: afunworm" /></a> <a href="https://github.com/chip-well"><img src="https://github.com/chip-well.png" width="64px" alt="User avatar: chip-well" /></a> <a href="https://github.com/Lancelot-Enguerrand"><img src="https://github.com/Lancelot-Enguerrand.png" width="64px" alt="User avatar: Lancelot-Enguerrand" /></a> <a href="https://github.com/allgoewer"><img src="https://github.com/allgoewer.png" width="64px" alt="User avatar: allgoewer" /></a> <a href="https://github.com/NEANC"><img src="https://github.com/NEANC.png" width="64px" alt="User avatar: NEANC" /></a> <a href="https://github.com/ax-mad"><img src="https://github.com/ax-mad.png" width="64px" alt="User avatar: ax-mad" /></a> <a href="https://github.com/stegratech"><img src="https://github.com/stegratech.png" width="64px" alt="User avatar: stegratech" /></a> <a href="https://github.com/apearson"><img src="https://github.com/apearson.png" width="64px" alt="User avatar: apearson" /></a> <!-- sponsors -->
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tinyauthapp/paerser/cli"
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
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",
|
|
||||||
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)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to render yaml config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(buf.String())
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,9 +7,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/tinyauthapp/paerser/cli"
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||||
|
"github.com/tinyauthapp/paerser/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createOidcClientCmd() *cli.Command {
|
func createOidcClientCmd() *cli.Command {
|
||||||
@@ -32,84 +31,40 @@ func createOidcClientCmd() *cli.Command {
|
|||||||
return errors.New("client name can only contain alphanumeric characters and hyphens")
|
return errors.New("client name can only contain alphanumeric characters and hyphens")
|
||||||
}
|
}
|
||||||
|
|
||||||
u := uuid.New()
|
uuid := uuid.New()
|
||||||
clientId := u.String()
|
clientId := uuid.String()
|
||||||
clientSecret := "ta-" + utils.GenerateString(61)
|
clientSecret := "ta-" + utils.GenerateString(61)
|
||||||
|
|
||||||
uclientName := strings.ToUpper(clientName)
|
uclientName := strings.ToUpper(clientName)
|
||||||
lclientName := strings.ToLower(clientName)
|
lclientName := strings.ToLower(clientName)
|
||||||
|
|
||||||
buf := strings.Builder{}
|
builder := strings.Builder{}
|
||||||
|
|
||||||
// header
|
// header
|
||||||
fmt.Fprintf(&buf, "Created '%s' OIDC client.\n\n", clientName)
|
fmt.Fprintf(&builder, "Created credentials for client %s\n\n", clientName)
|
||||||
|
|
||||||
// credentials
|
// credentials
|
||||||
fmt.Fprintf(&buf, "Credentials:\n\n")
|
fmt.Fprintf(&builder, "Client Name: %s\n", clientName)
|
||||||
fmt.Fprintf(&buf, "Client Name: %s\n", clientName)
|
fmt.Fprintf(&builder, "Client ID: %s\n", clientId)
|
||||||
fmt.Fprintf(&buf, "Client ID: %s\n", clientId)
|
fmt.Fprintf(&builder, "Client Secret: %s\n\n", clientSecret)
|
||||||
fmt.Fprintf(&buf, "Client Secret: %s\n\n", clientSecret)
|
|
||||||
|
|
||||||
// end variables
|
// env variables
|
||||||
fmt.Fprintf(&buf, "Environment variables:\n\n")
|
fmt.Fprint(&builder, "Environment variables:\n\n")
|
||||||
renderToBuf(&buf, []kv{
|
fmt.Fprintf(&builder, "TINYAUTH_OIDC_CLIENTS_%s_CLIENTID=%s\n", uclientName, clientId)
|
||||||
{
|
fmt.Fprintf(&builder, "TINYAUTH_OIDC_CLIENTS_%s_CLIENTSECRET=%s\n", uclientName, clientSecret)
|
||||||
k: fmt.Sprintf("TINYAUTH_OIDC_CLIENTS_%s_CLIENTID", uclientName),
|
fmt.Fprintf(&builder, "TINYAUTH_OIDC_CLIENTS_%s_NAME=%s\n\n", uclientName, utils.Capitalize(lclientName))
|
||||||
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")
|
|
||||||
|
|
||||||
// cli flags
|
// cli flags
|
||||||
fmt.Fprintf(&buf, "CLI flags:\n\n")
|
fmt.Fprint(&builder, "CLI flags:\n\n")
|
||||||
renderToBuf(&buf, []kv{
|
fmt.Fprintf(&builder, "--oidc.clients.%s.clientid=%s\n", lclientName, clientId)
|
||||||
{
|
fmt.Fprintf(&builder, "--oidc.clients.%s.clientsecret=%s\n", lclientName, clientSecret)
|
||||||
k: fmt.Sprintf("--oidc.clients.%s.clientid", lclientName),
|
fmt.Fprintf(&builder, "--oidc.clients.%s.name=%s\n\n", lclientName, utils.Capitalize(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")
|
|
||||||
|
|
||||||
// footer
|
// 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
|
// print
|
||||||
out := buf.String()
|
out := builder.String()
|
||||||
fmt.Print(out)
|
fmt.Print(out)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|||||||
+15
-61
@@ -3,12 +3,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"charm.land/huh/v2"
|
"charm.land/huh/v2"
|
||||||
"github.com/tinyauthapp/paerser/cli"
|
"github.com/tinyauthapp/paerser/cli"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,107 +34,62 @@ func createUserCmd() *cli.Command {
|
|||||||
&cli.FlagLoader{},
|
&cli.FlagLoader{},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &cli.Command{
|
return &cli.Command{
|
||||||
Name: "create",
|
Name: "create",
|
||||||
Description: "Create a user",
|
Description: "Create a user",
|
||||||
Configuration: tCfg,
|
Configuration: tCfg,
|
||||||
Resources: loaders,
|
Resources: loaders,
|
||||||
}
|
Run: func(_ []string) error {
|
||||||
|
log := logger.NewLogger().WithSimpleConfig()
|
||||||
|
log.Init()
|
||||||
|
|
||||||
cmd.Run = func(_ []string) error {
|
|
||||||
if tCfg.Interactive {
|
if tCfg.Interactive {
|
||||||
form := huh.NewForm(
|
form := huh.NewForm(
|
||||||
huh.NewGroup(
|
huh.NewGroup(
|
||||||
huh.NewInput().Title("Username").Value(&tCfg.Username).Validate(func(s string) error {
|
huh.NewInput().Title("Username").Value(&tCfg.Username).Validate((func(s string) error {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return errors.New("username cannot be empty")
|
return errors.New("username cannot be empty")
|
||||||
}
|
}
|
||||||
if strings.Contains(s, ":") {
|
|
||||||
return errors.New("username cannot contain ':'")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}),
|
})),
|
||||||
huh.NewInput().Title("Password").Value(&tCfg.Password).Validate(func(s string) error {
|
huh.NewInput().Title("Password").Value(&tCfg.Password).Validate((func(s string) error {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return errors.New("password cannot be empty")
|
return errors.New("password cannot be empty")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}),
|
})),
|
||||||
huh.NewSelect[bool]().Title("Format the output for Docker?").Options(huh.NewOption("Yes", true), huh.NewOption("No", false)).Value(&tCfg.Docker),
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to run interactive prompt: %w", err)
|
return fmt.Errorf("failed to run interactive prompt: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tCfg.Username == "" || tCfg.Password == "" {
|
if tCfg.Username == "" || tCfg.Password == "" {
|
||||||
cmd.PrintHelp(os.Stdout)
|
|
||||||
return errors.New("username and password cannot be empty")
|
return errors.New("username and password cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(tCfg.Username, ":") {
|
log.App.Info().Str("username", tCfg.Username).Msg("Creating user")
|
||||||
return errors.New("username cannot contain ':'")
|
|
||||||
}
|
|
||||||
|
|
||||||
passwd, err := bcrypt.GenerateFromPassword([]byte(tCfg.Password), bcrypt.DefaultCost)
|
passwd, err := bcrypt.GenerateFromPassword([]byte(tCfg.Password), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to hash password: %w", err)
|
return fmt.Errorf("failed to hash password: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only the docker compose output needs $ escaped, the raw hash is correct everywhere else
|
// If docker format is enabled, escape the dollar sign
|
||||||
passwdStr := string(passwd)
|
passwdStr := string(passwd)
|
||||||
outputStr := passwdStr
|
|
||||||
|
|
||||||
if tCfg.Docker {
|
if tCfg.Docker {
|
||||||
outputStr = strings.ReplaceAll(passwdStr, "$", "$$")
|
passwdStr = strings.ReplaceAll(passwdStr, "$", "$$")
|
||||||
}
|
}
|
||||||
|
|
||||||
user := fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)
|
log.App.Info().Str("user", fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)).Msg("User created")
|
||||||
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"
|
"strings"
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
|
|
||||||
"charm.land/huh/v2"
|
"charm.land/huh/v2"
|
||||||
"github.com/mdp/qrterminal/v3"
|
"github.com/mdp/qrterminal/v3"
|
||||||
@@ -33,15 +34,14 @@ func generateTotpCmd() *cli.Command {
|
|||||||
&cli.FlagLoader{},
|
&cli.FlagLoader{},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &cli.Command{
|
return &cli.Command{
|
||||||
Name: "generate",
|
Name: "generate",
|
||||||
Description: "Generate a TOTP secret",
|
Description: "Generate a TOTP secret",
|
||||||
Configuration: tCfg,
|
Configuration: tCfg,
|
||||||
Resources: loaders,
|
Resources: loaders,
|
||||||
}
|
Run: func(_ []string) error {
|
||||||
|
log := logger.NewLogger().WithSimpleConfig()
|
||||||
cmd.Run = func(_ []string) error {
|
log.Init()
|
||||||
colors := getColors()
|
|
||||||
|
|
||||||
if tCfg.Interactive {
|
if tCfg.Interactive {
|
||||||
form := huh.NewForm(
|
form := huh.NewForm(
|
||||||
@@ -63,11 +63,6 @@ func generateTotpCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tCfg.User == "" {
|
|
||||||
cmd.PrintHelp(os.Stdout)
|
|
||||||
return fmt.Errorf("user is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := utils.ParseUser(tCfg.User)
|
user, err := utils.ParseUser(tCfg.User)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -94,7 +89,9 @@ func generateTotpCmd() *cli.Command {
|
|||||||
|
|
||||||
secret := key.Secret()
|
secret := key.Secret()
|
||||||
|
|
||||||
fmt.Printf("Scan the following QR code with your authenticator app (e.g., Google Authenticator, 2fauth, Microsoft Authenticator):\n\n")
|
log.App.Info().Str("secret", secret).Msg("Generated TOTP secret")
|
||||||
|
|
||||||
|
log.App.Info().Msg("Generated QR code")
|
||||||
|
|
||||||
config := qrterminal.Config{
|
config := qrterminal.Config{
|
||||||
Level: qrterminal.L,
|
Level: qrterminal.L,
|
||||||
@@ -113,18 +110,9 @@ func generateTotpCmd() *cli.Command {
|
|||||||
user.Password = strings.ReplaceAll(user.Password, "$", "$$")
|
user.Password = strings.ReplaceAll(user.Password, "$", "$$")
|
||||||
}
|
}
|
||||||
|
|
||||||
userStr := fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TOTPSecret)
|
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.")
|
||||||
|
|
||||||
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
-147
@@ -2,23 +2,18 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"charm.land/huh/v2"
|
"charm.land/huh/v2"
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
|
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/loaders"
|
"github.com/tinyauthapp/tinyauth/internal/utils/loaders"
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/tinyauthapp/paerser/cli"
|
"github.com/tinyauthapp/paerser/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
env := model.DetectRuntimeEnv()
|
tConfig := model.NewDefaultConfiguration()
|
||||||
tConfig := model.NewDefaultConfiguration(env)
|
|
||||||
|
|
||||||
loaders := []cli.ResourceLoader{
|
loaders := []cli.ResourceLoader{
|
||||||
&loaders.FileLoader{},
|
&loaders.FileLoader{},
|
||||||
@@ -32,114 +27,83 @@ func main() {
|
|||||||
Configuration: tConfig,
|
Configuration: tConfig,
|
||||||
Resources: loaders,
|
Resources: loaders,
|
||||||
Run: func(_ []string) error {
|
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)
|
return runCmd(*tConfig)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdUser := &cli.Command{
|
cmdUser := &cli.Command{
|
||||||
Name: "user",
|
Name: "user",
|
||||||
Description: "Manage users",
|
Description: "Manage Tinyauth users",
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdTotp := &cli.Command{
|
cmdTotp := &cli.Command{
|
||||||
Name: "totp",
|
Name: "totp",
|
||||||
Description: "Manage TOTP users",
|
Description: "Manage Tinyauth TOTP users",
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdOidc := &cli.Command{
|
cmdOidc := &cli.Command{
|
||||||
Name: "oidc",
|
Name: "oidc",
|
||||||
Description: "Manage OIDC clients",
|
Description: "Manage Tinyauth OIDC clients",
|
||||||
}
|
}
|
||||||
|
|
||||||
helpCmd := &cli.Command{
|
err := cmdTinyauth.AddCommand(versionCmd())
|
||||||
Name: "help",
|
|
||||||
Description: "Show the help message",
|
|
||||||
Run: func(_ []string) error {
|
|
||||||
return cmdTinyauth.PrintHelp(os.Stdout)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := cmdTinyauth.AddCommand(helpCmd)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err, "Failed to add help command")
|
log.Fatal().Err(err).Msg("Failed to add version command")
|
||||||
}
|
|
||||||
|
|
||||||
err = cmdTinyauth.AddCommand(versionCmd())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err, "Failed to add version command")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmdTinyauth.AddCommand(configCmd(tConfig, loaders))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fatalf(err, "Failed to add config command")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cmdUser.AddCommand(verifyUserCmd())
|
err = cmdUser.AddCommand(verifyUserCmd())
|
||||||
|
|
||||||
if err != nil {
|
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())
|
err = cmdTinyauth.AddCommand(healthcheckCmd())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err, "Failed to add healthcheck command")
|
log.Fatal().Err(err).Msg("Failed to add healthcheck command")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cmdTotp.AddCommand(generateTotpCmd())
|
err = cmdTotp.AddCommand(generateTotpCmd())
|
||||||
|
|
||||||
if err != nil {
|
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())
|
err = cmdUser.AddCommand(createUserCmd())
|
||||||
|
|
||||||
if err != nil {
|
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())
|
err = cmdOidc.AddCommand(createOidcClientCmd())
|
||||||
|
|
||||||
if err != nil {
|
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)
|
err = cmdTinyauth.AddCommand(cmdUser)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err, "Failed to add user command")
|
log.Fatal().Err(err).Msg("Failed to add user command")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cmdTinyauth.AddCommand(cmdTotp)
|
err = cmdTinyauth.AddCommand(cmdTotp)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err, "Failed to add totp command")
|
log.Fatal().Err(err).Msg("Failed to add totp command")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cmdTinyauth.AddCommand(cmdOidc)
|
err = cmdTinyauth.AddCommand(cmdOidc)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err, "Failed to add oidc command")
|
log.Fatal().Err(err).Msg("Failed to add oidc command")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cli.Execute(cmdTinyauth)
|
err = cli.Execute(cmdTinyauth)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "command not found") {
|
log.Fatal().Err(err).Msg("Failed to execute command")
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,98 +124,3 @@ type themeBase struct{}
|
|||||||
func (t *themeBase) Theme(isDark bool) *huh.Styles {
|
func (t *themeBase) Theme(isDark bool) *huh.Styles {
|
||||||
return huh.ThemeBase(isDark)
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
+10
-16
@@ -3,9 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
|
|
||||||
"charm.land/huh/v2"
|
"charm.land/huh/v2"
|
||||||
"github.com/pquerna/otp/totp"
|
"github.com/pquerna/otp/totp"
|
||||||
@@ -38,15 +38,14 @@ func verifyUserCmd() *cli.Command {
|
|||||||
&cli.FlagLoader{},
|
&cli.FlagLoader{},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &cli.Command{
|
return &cli.Command{
|
||||||
Name: "verify",
|
Name: "verify",
|
||||||
Description: "Verify a user is set up correctly",
|
Description: "Verify a user is set up correctly",
|
||||||
Configuration: tCfg,
|
Configuration: tCfg,
|
||||||
Resources: loaders,
|
Resources: loaders,
|
||||||
}
|
Run: func(_ []string) error {
|
||||||
|
log := logger.NewLogger().WithSimpleConfig()
|
||||||
cmd.Run = func(_ []string) error {
|
log.Init()
|
||||||
colors := getColors()
|
|
||||||
|
|
||||||
if tCfg.Interactive {
|
if tCfg.Interactive {
|
||||||
form := huh.NewForm(
|
form := huh.NewForm(
|
||||||
@@ -81,11 +80,6 @@ func verifyUserCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
user, err := utils.ParseUser(tCfg.User)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -104,21 +98,21 @@ func verifyUserCmd() *cli.Command {
|
|||||||
|
|
||||||
if user.TOTPSecret == "" {
|
if user.TOTPSecret == "" {
|
||||||
if tCfg.Totp != "" {
|
if tCfg.Totp != "" {
|
||||||
fmt.Println(colors.yellow.Render("⚠") + " TOTP code provided but user does not have TOTP enabled")
|
log.App.Warn().Msg("User does not have TOTP secret")
|
||||||
}
|
}
|
||||||
fmt.Println(colors.green.Render("✓") + " User verified")
|
log.App.Info().Msg("User verified")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := totp.Validate(tCfg.Totp, user.TOTPSecret)
|
ok := totp.Validate(tCfg.Totp, user.TOTPSecret)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("TOTP code incorrect")
|
return fmt.Errorf("TOTP code incorrect")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(colors.green.Render("✓") + " User verified")
|
log.App.Info().Msg("User verified")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,9 @@ func versionCmd() *cli.Command {
|
|||||||
Configuration: nil,
|
Configuration: nil,
|
||||||
Resources: nil,
|
Resources: nil,
|
||||||
Run: func(_ []string) error {
|
Run: func(_ []string) error {
|
||||||
colors := getColors()
|
fmt.Printf("Version: %s\n", model.Version)
|
||||||
fmt.Printf("Version: %s\n", colors.blue.Render(model.Version))
|
fmt.Printf("Commit Hash: %s\n", model.CommitHash)
|
||||||
fmt.Printf("Commit Hash: %s\n", colors.blue.Render(model.CommitHash))
|
fmt.Printf("Build Timestamp: %s\n", model.BuildTimestamp)
|
||||||
fmt.Printf("Build Timestamp: %s\n", colors.blue.Render(model.BuildTimestamp))
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-27
@@ -12,49 +12,50 @@
|
|||||||
},
|
},
|
||||||
"packageManager": "pnpm@11.1.2",
|
"packageManager": "pnpm@11.1.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.4.0",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.19",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-label": "^2.1.11",
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
"@radix-ui/react-select": "^2.3.2",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-separator": "^1.1.11",
|
"@radix-ui/react-separator": "^1.1.8",
|
||||||
"@radix-ui/react-slot": "^1.3.0",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@tailwindcss/vite": "^4.3.2",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"@tanstack/react-query": "^5.101.2",
|
"@tanstack/react-query": "^5.99.0",
|
||||||
"axios": "^1.18.1",
|
"axios": "^1.15.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"i18next": "^26.3.4",
|
"i18next": "^26.0.4",
|
||||||
"i18next-browser-languagedetector": "^8.2.1",
|
"i18next-browser-languagedetector": "^8.2.1",
|
||||||
"i18next-resources-to-backend": "^1.2.1",
|
"i18next-resources-to-backend": "^1.2.1",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.8.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"radix-ui": "^1.6.1",
|
"radix-ui": "^1.4.3",
|
||||||
"react": "^19.2.7",
|
"react": "^19.2.5",
|
||||||
"react-dom": "^19.2.7",
|
"react-dom": "^19.2.5",
|
||||||
"react-hook-form": "^7.81.0",
|
"react-hook-form": "^7.72.1",
|
||||||
"react-i18next": "^17.0.8",
|
"react-i18next": "^17.0.2",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-router": "^8.1.0",
|
"react-router": "^7.14.0",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^4.3.2",
|
"tailwindcss": "^4.2.2",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@tanstack/eslint-plugin-query": "^5.101.2",
|
"@tanstack/eslint-plugin-query": "^5.99.0",
|
||||||
"@types/node": "^26.1.0",
|
"@types/node": "^25.6.0",
|
||||||
"@types/react": "^19.2.17",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^6.0.3",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"eslint": "^10.6.0",
|
"eslint": "^10.2.0",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.3",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"globals": "^17.7.0",
|
"globals": "^17.5.0",
|
||||||
|
"prettier": "3.8.2",
|
||||||
"rollup-plugin-visualizer": "^7.0.1",
|
"rollup-plugin-visualizer": "^7.0.1",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "~6.0.2",
|
"typescript": "~6.0.2",
|
||||||
"typescript-eslint": "^8.62.1",
|
"typescript-eslint": "^8.58.1",
|
||||||
"vite": "^8.1.3"
|
"vite": "^8.0.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+1404
-1309
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
|||||||
dangerouslyAllowAllBuilds: false
|
dangerouslyAllowAllBuilds: false
|
||||||
blockExoticSubdeps: true
|
blockExoticSubdeps: true
|
||||||
minimumReleaseAge: 1440 # 1 day
|
minimumReleaseAge: 1440 # 1 day
|
||||||
|
trustPolicy: no-downgrade
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export const LoginForm = (props: Props) => {
|
|||||||
`/forgot-password${props.params ? `${props.params}` : ""}`,
|
`/forgot-password${props.params ? `${props.params}` : ""}`,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="text-muted-foreground hover:text-muted-foreground/80 text-sm absolute right-0 bottom-[2.565rem] max-w-40 truncate" // 2.565 is *just* perfect
|
className="text-muted-foreground hover:text-muted-foreground/80 text-sm absolute right-0 bottom-[2.565rem]" // 2.565 is *just* perfect
|
||||||
>
|
>
|
||||||
{t("forgotPasswordTitle")}
|
{t("forgotPasswordTitle")}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import type { SVGProps } from "react";
|
|
||||||
|
|
||||||
export function LocalAuthIcon(props: SVGProps<SVGSVGElement>) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="1em"
|
|
||||||
height="1em"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0-8 0M6 21v-2a4 4 0 0 1 4-4h5m3.5 3.5L15 22l-1.5-1.5m5.054-2.086a2 2 0 1 1 2.828-2.828a2 2 0 0 1-2.828 2.828M16 19l1 1"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { languages, SupportedLanguage } from "@/lib/i18n/locales";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "../ui/select";
|
||||||
|
import { useState } from "react";
|
||||||
|
import i18n from "@/lib/i18n/i18n";
|
||||||
|
|
||||||
|
export const LanguageSelector = () => {
|
||||||
|
const [language, setLanguage] = useState<SupportedLanguage>(
|
||||||
|
i18n.language as SupportedLanguage,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelect = (option: string) => {
|
||||||
|
setLanguage(option as SupportedLanguage);
|
||||||
|
i18n.changeLanguage(option as SupportedLanguage);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select onValueChange={handleSelect} value={language}>
|
||||||
|
<SelectTrigger aria-label="Select language">
|
||||||
|
<SelectValue placeholder="Select language" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{Object.entries(languages).map(([key, value]) => (
|
||||||
|
<SelectItem key={key} value={key}>
|
||||||
|
{value}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useAppContext } from "@/context/app-context";
|
import { useAppContext } from "@/context/app-context";
|
||||||
|
import { LanguageSelector } from "../language/language";
|
||||||
import { Outlet } from "react-router";
|
import { Outlet } from "react-router";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { DomainWarning } from "../domain-warning/domain-warning";
|
import { DomainWarning } from "../domain-warning/domain-warning";
|
||||||
import { QuickActions } from "../quick-actions/quick-actions";
|
import { ThemeToggle } from "../theme-toggle/theme-toggle";
|
||||||
import { isTrustedDomain } from "@/lib/hooks/redirect-uri";
|
|
||||||
|
|
||||||
const BaseLayout = ({ children }: { children: React.ReactNode }) => {
|
const BaseLayout = ({ children }: { children: React.ReactNode }) => {
|
||||||
const { ui } = useAppContext();
|
const { ui } = useAppContext();
|
||||||
@@ -21,8 +21,9 @@ const BaseLayout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="absolute top-4 right-4">
|
<div className="absolute top-4 right-4 flex flex-row gap-2">
|
||||||
<QuickActions />
|
<ThemeToggle />
|
||||||
|
<LanguageSelector />
|
||||||
</div>
|
</div>
|
||||||
<div className="max-w-sm md:min-w-sm min-w-xs">{children}</div>
|
<div className="max-w-sm md:min-w-sm min-w-xs">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,18 +42,11 @@ export const Layout = () => {
|
|||||||
setIgnoreDomainWarning(true);
|
setIgnoreDomainWarning(true);
|
||||||
}, [setIgnoreDomainWarning]);
|
}, [setIgnoreDomainWarning]);
|
||||||
|
|
||||||
const isTrusted = (() => {
|
if (
|
||||||
try {
|
!ignoreDomainWarning &&
|
||||||
const appUrlObj = new URL(app.appUrl);
|
ui.warningsEnabled &&
|
||||||
const currentUrlObj = new URL(currentUrl);
|
!app.trustedDomains.includes(currentUrl)
|
||||||
|
) {
|
||||||
return isTrustedDomain(currentUrlObj, appUrlObj, "", false);
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (!ignoreDomainWarning && ui.warningsEnabled && !isTrusted) {
|
|
||||||
return (
|
return (
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<DomainWarning
|
<DomainWarning
|
||||||
|
|||||||
@@ -1,274 +0,0 @@
|
|||||||
import { languages, SupportedLanguage } from "@/lib/i18n/locales";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuPortal,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuSub,
|
|
||||||
DropdownMenuSubContent,
|
|
||||||
DropdownMenuSubTrigger,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "../ui/dropdown-menu";
|
|
||||||
import { useState } from "react";
|
|
||||||
import i18n from "@/lib/i18n/i18n";
|
|
||||||
import { useUserContext } from "@/context/user-context";
|
|
||||||
import { ScrollArea } from "../ui/scroll-area";
|
|
||||||
import { useTheme } from "../providers/theme-provider";
|
|
||||||
import {
|
|
||||||
Check,
|
|
||||||
DoorOpenIcon,
|
|
||||||
Languages,
|
|
||||||
Monitor,
|
|
||||||
Moon,
|
|
||||||
Palette,
|
|
||||||
Settings,
|
|
||||||
Sun,
|
|
||||||
UserRoundKey,
|
|
||||||
X,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useLocation } from "react-router";
|
|
||||||
import { useRef } from "react";
|
|
||||||
import {
|
|
||||||
useScreenParams,
|
|
||||||
recompileScreenParams,
|
|
||||||
} from "@/lib/hooks/screen-params";
|
|
||||||
import { useMutation } from "@tanstack/react-query";
|
|
||||||
import axios from "axios";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { GoogleIcon } from "../icons/google";
|
|
||||||
import { GithubIcon } from "../icons/github";
|
|
||||||
import { TailscaleIcon } from "../icons/tailscale";
|
|
||||||
import { MicrosoftIcon } from "../icons/microsoft";
|
|
||||||
import { PocketIDIcon } from "../icons/pocket-id";
|
|
||||||
import { OAuthIcon } from "../icons/oauth";
|
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
|
||||||
|
|
||||||
const iconStyles = "size-4";
|
|
||||||
|
|
||||||
const iconMap: Record<string, React.ReactNode> = {
|
|
||||||
google: <GoogleIcon className={iconStyles} />,
|
|
||||||
github: <GithubIcon className={iconStyles} />,
|
|
||||||
tailscale: <TailscaleIcon className={iconStyles} />,
|
|
||||||
microsoft: <MicrosoftIcon className={iconStyles} />,
|
|
||||||
pocketid: <PocketIDIcon className={iconStyles} />,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const QuickActions = () => {
|
|
||||||
const { auth, oauth, tailscale } = useUserContext();
|
|
||||||
const { theme, setTheme } = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { search } = useLocation();
|
|
||||||
|
|
||||||
const [language, setLanguage] = useState<SupportedLanguage>(
|
|
||||||
i18n.language as SupportedLanguage,
|
|
||||||
);
|
|
||||||
|
|
||||||
const redirectTimer = useRef<number | null>(null);
|
|
||||||
const searchParams = new URLSearchParams(search);
|
|
||||||
const screenParams = useScreenParams(searchParams);
|
|
||||||
const compiledParams = recompileScreenParams(screenParams);
|
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
const providerDetails = (():
|
|
||||||
| { name: string; icon: React.ReactNode }
|
|
||||||
| undefined => {
|
|
||||||
if (!auth.authenticated) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth.providerId === "local" || auth.providerId === "ldap") {
|
|
||||||
return {
|
|
||||||
name: t(
|
|
||||||
auth.providerId === "ldap"
|
|
||||||
? "quickActionsProviderLDAP"
|
|
||||||
: "quickActionsProviderLocal",
|
|
||||||
),
|
|
||||||
icon: (
|
|
||||||
<UserRoundKey
|
|
||||||
strokeWidth={1.5}
|
|
||||||
size={16}
|
|
||||||
className="text-muted-foreground ml-0.5"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oauth.active) {
|
|
||||||
return {
|
|
||||||
name: t("quickActionsProviderOAuth", { provider: oauth.displayName }),
|
|
||||||
icon: iconMap[auth.providerId] || <OAuthIcon className={iconStyles} />,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth.providerId === "tailscale") {
|
|
||||||
return {
|
|
||||||
name: `Tailscale (${tailscale.nodeName})`,
|
|
||||||
icon: <TailscaleIcon className={iconStyles} />,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
})();
|
|
||||||
|
|
||||||
const logoutMutation = useMutation({
|
|
||||||
mutationFn: () => axios.post("/api/user/logout"),
|
|
||||||
mutationKey: ["logout"],
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success(t("logoutSuccessTitle"), {
|
|
||||||
description: t("logoutSuccessSubtitle"),
|
|
||||||
});
|
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
|
||||||
window.location.replace(`/login${compiledParams}`);
|
|
||||||
}, 500);
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
toast.error(t("logoutFailTitle"), {
|
|
||||||
description: t("logoutFailSubtitle"),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (redirectTimer.current) {
|
|
||||||
clearTimeout(redirectTimer.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [redirectTimer]);
|
|
||||||
|
|
||||||
const initial = auth.authenticated
|
|
||||||
? (auth.name[0] || "U").toUpperCase()
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const handleSelect = (option: string) => {
|
|
||||||
setLanguage(option as SupportedLanguage);
|
|
||||||
i18n.changeLanguage(option as SupportedLanguage);
|
|
||||||
};
|
|
||||||
|
|
||||||
const themes = [
|
|
||||||
{ key: "light", label: t("quickActionsThemeLight"), icon: Sun },
|
|
||||||
{ key: "dark", label: t("quickActionsThemeDark"), icon: Moon },
|
|
||||||
{ key: "system", label: t("quickActionsThemeSystem"), icon: Monitor },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenu onOpenChange={(open) => setIsOpen(open)} open={isOpen}>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<button
|
|
||||||
aria-label={t("quickActionsTitle")}
|
|
||||||
className="rounded-full transition-transform duration-200 will-change-transform hover:scale-105 hover:cursor-pointer focus:ring-0 focus:outline-3 focus:outline-ring/50"
|
|
||||||
>
|
|
||||||
{auth.authenticated ? (
|
|
||||||
<div className="size-10 flex justify-center items-center p-2 rounded-full bg-card border border-border">
|
|
||||||
{isOpen ? (
|
|
||||||
<X className="size-4 text-primary rotate-0 transition-transform duration-200 starting:rotate-45" />
|
|
||||||
) : (
|
|
||||||
<span className="text-sm text-primary rotate-0 transition-transform duration-200 starting:-rotate-45">
|
|
||||||
{initial}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="bg-card text-primary border-border size-10 flex items-center justify-center rounded-full border shadow-lg">
|
|
||||||
<Settings
|
|
||||||
className={`size-4 transition-transform duration-200 ${
|
|
||||||
isOpen ? "rotate-45" : "rotate-0"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
|
|
||||||
<DropdownMenuContent
|
|
||||||
align="end"
|
|
||||||
sideOffset={8}
|
|
||||||
className="rounded-xl p-1 w-3xs"
|
|
||||||
>
|
|
||||||
{auth.authenticated && (
|
|
||||||
<>
|
|
||||||
<DropdownMenuLabel className="flex items-center gap-3 p-2">
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger className="size-9 rounded-full p-2 bg-muted border-border border flex items-center justify-center">
|
|
||||||
{providerDetails!.icon}
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>{providerDetails!.name}</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
<div className="flex min-w-0 flex-col gap-0.5">
|
|
||||||
<span className="truncate text-sm font-medium">
|
|
||||||
{auth.name}
|
|
||||||
</span>
|
|
||||||
<span className="text-muted-foreground truncate text-xs">
|
|
||||||
{auth.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</DropdownMenuLabel>
|
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DropdownMenuSub>
|
|
||||||
<DropdownMenuSubTrigger>
|
|
||||||
<Languages className="size-4" />
|
|
||||||
{t("quickActionsLanguage")}
|
|
||||||
</DropdownMenuSubTrigger>
|
|
||||||
<DropdownMenuPortal>
|
|
||||||
<DropdownMenuSubContent sideOffset={8} className="rounded-xl p-1">
|
|
||||||
<ScrollArea className="h-80">
|
|
||||||
{Object.entries(languages).map(([key, value]) => (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={key}
|
|
||||||
onSelect={() => handleSelect(key)}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
{language === key && <Check className="size-4" />}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
|
||||||
</ScrollArea>
|
|
||||||
</DropdownMenuSubContent>
|
|
||||||
</DropdownMenuPortal>
|
|
||||||
</DropdownMenuSub>
|
|
||||||
|
|
||||||
<DropdownMenuSub>
|
|
||||||
<DropdownMenuSubTrigger>
|
|
||||||
<Palette className="size-4" />
|
|
||||||
{t("quickActionsTheme")}
|
|
||||||
</DropdownMenuSubTrigger>
|
|
||||||
<DropdownMenuPortal>
|
|
||||||
<DropdownMenuSubContent className="rounded-xl p-1" sideOffset={8}>
|
|
||||||
{themes.map(({ key, label, icon: Icon }) => (
|
|
||||||
<DropdownMenuItem key={key} onClick={() => setTheme(key)}>
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
<Icon className="size-4" />
|
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
{theme === key && <Check className="size-4" />}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuSubContent>
|
|
||||||
</DropdownMenuPortal>
|
|
||||||
</DropdownMenuSub>
|
|
||||||
|
|
||||||
{auth.authenticated && (
|
|
||||||
<>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem
|
|
||||||
onSelect={() => logoutMutation.mutate()}
|
|
||||||
className="text-destructive"
|
|
||||||
>
|
|
||||||
<DoorOpenIcon className="size-4 text-destructive" />
|
|
||||||
{t("quickActionsLogout")}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { Moon, Sun } from "lucide-react";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { useTheme } from "@/components/providers/theme-provider";
|
||||||
|
|
||||||
|
export function ThemeToggle() {
|
||||||
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
className="bg-card text-card-foreground hover:bg-card/90"
|
||||||
|
size="icon"
|
||||||
|
>
|
||||||
|
<Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
|
||||||
|
<Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
|
||||||
|
<span className="sr-only">Toggle theme</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||||
|
Light
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||||
|
Dark
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||||
|
System
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
function ScrollArea({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
|
||||||
return (
|
|
||||||
<ScrollAreaPrimitive.Root
|
|
||||||
data-slot="scroll-area"
|
|
||||||
className={cn("relative", className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<ScrollAreaPrimitive.Viewport
|
|
||||||
data-slot="scroll-area-viewport"
|
|
||||||
className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</ScrollAreaPrimitive.Viewport>
|
|
||||||
<ScrollBar />
|
|
||||||
<ScrollAreaPrimitive.Corner />
|
|
||||||
</ScrollAreaPrimitive.Root>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ScrollBar({
|
|
||||||
className,
|
|
||||||
orientation = "vertical",
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
|
||||||
return (
|
|
||||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
|
||||||
data-slot="scroll-area-scrollbar"
|
|
||||||
orientation={orientation}
|
|
||||||
className={cn(
|
|
||||||
"flex touch-none p-px transition-colors select-none",
|
|
||||||
orientation === "vertical" &&
|
|
||||||
"h-full w-2.5 border-l border-l-transparent",
|
|
||||||
orientation === "horizontal" &&
|
|
||||||
"h-2.5 flex-col border-t border-t-transparent",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<ScrollAreaPrimitive.ScrollAreaThumb
|
|
||||||
data-slot="scroll-area-thumb"
|
|
||||||
className="relative flex-1 rounded-full bg-border"
|
|
||||||
/>
|
|
||||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { ScrollArea, ScrollBar }
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
type UseLoginForProps = {
|
|
||||||
login_for?: "oidc" | "app";
|
|
||||||
compiledParams: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useLoginFor = (props: UseLoginForProps): string => {
|
|
||||||
const { login_for, compiledParams } = props;
|
|
||||||
|
|
||||||
switch (login_for) {
|
|
||||||
case "oidc":
|
|
||||||
return "/oidc/authorize" + compiledParams;
|
|
||||||
case "app":
|
|
||||||
return "/continue" + compiledParams;
|
|
||||||
default:
|
|
||||||
return "/logout";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const oidcParamsSchema = z.object({
|
||||||
|
scope: z.string().min(1),
|
||||||
|
response_type: z.string().min(1),
|
||||||
|
client_id: z.string().min(1),
|
||||||
|
redirect_uri: z.string().min(1),
|
||||||
|
state: z.string().optional(),
|
||||||
|
nonce: z.string().optional(),
|
||||||
|
code_challenge: z.string().optional(),
|
||||||
|
code_challenge_method: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
function b64urlDecode(s: string): string {
|
||||||
|
const base64 = s.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
return atob(base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "="));
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeRequestObject(jwt: string): Record<string, string> {
|
||||||
|
try {
|
||||||
|
// Must have exactly 3 parts: header, payload, signature
|
||||||
|
const parts = jwt.split(".");
|
||||||
|
if (parts.length !== 3) return {};
|
||||||
|
|
||||||
|
// Header must specify "alg": "none" and signature must be empty string
|
||||||
|
const header = JSON.parse(b64urlDecode(parts[0]));
|
||||||
|
if (!header || typeof header !== "object" || header.alg !== "none" || parts[2] !== "") return {};
|
||||||
|
|
||||||
|
const payload = JSON.parse(b64urlDecode(parts[1]));
|
||||||
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return {};
|
||||||
|
const result: Record<string, string> = {};
|
||||||
|
for (const [k, v] of Object.entries(payload)) {
|
||||||
|
if (typeof v === "string") result[k] = v;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useOIDCParams = (
|
||||||
|
params: URLSearchParams,
|
||||||
|
): {
|
||||||
|
values: z.infer<typeof oidcParamsSchema>;
|
||||||
|
issues: string[];
|
||||||
|
isOidc: boolean;
|
||||||
|
compiled: string;
|
||||||
|
} => {
|
||||||
|
const obj = Object.fromEntries(params.entries());
|
||||||
|
|
||||||
|
// RFC 9101 / OIDC Core 6.1: if `request` param present, decode JWT payload
|
||||||
|
// and merge claims over top-level params (JWT claims take precedence)
|
||||||
|
const requestJwt = params.get("request");
|
||||||
|
if (requestJwt) {
|
||||||
|
const claims = decodeRequestObject(requestJwt);
|
||||||
|
Object.assign(obj, claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = oidcParamsSchema.safeParse(obj);
|
||||||
|
|
||||||
|
if (parsed.success) {
|
||||||
|
return {
|
||||||
|
values: parsed.data,
|
||||||
|
issues: [],
|
||||||
|
isOidc: true,
|
||||||
|
compiled: new URLSearchParams(parsed.data).toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
issues: parsed.error.issues.map((issue) => issue.path.toString()),
|
||||||
|
values: {} as z.infer<typeof oidcParamsSchema>,
|
||||||
|
isOidc: false,
|
||||||
|
compiled: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -7,29 +7,14 @@ type IuseRedirectUri = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useRedirectUri = (
|
export const useRedirectUri = (
|
||||||
redirect_uri: string | undefined,
|
redirect_uri: string | null,
|
||||||
cookieDomain: string,
|
cookieDomain: string,
|
||||||
appUrl: string,
|
|
||||||
subdomainsEnabled: boolean,
|
|
||||||
): IuseRedirectUri => {
|
): IuseRedirectUri => {
|
||||||
let isValid = false;
|
let isValid = false;
|
||||||
let isTrusted = false;
|
let isTrusted = false;
|
||||||
let isAllowedProto = false;
|
let isAllowedProto = false;
|
||||||
let isHttpsDowngrade = false;
|
let isHttpsDowngrade = false;
|
||||||
|
|
||||||
let appUrlObj: URL;
|
|
||||||
|
|
||||||
try {
|
|
||||||
appUrlObj = new URL(appUrl);
|
|
||||||
} catch {
|
|
||||||
return {
|
|
||||||
valid: isValid,
|
|
||||||
trusted: isTrusted,
|
|
||||||
allowedProto: isAllowedProto,
|
|
||||||
httpsDowngrade: isHttpsDowngrade,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!redirect_uri) {
|
if (!redirect_uri) {
|
||||||
return {
|
return {
|
||||||
valid: isValid,
|
valid: isValid,
|
||||||
@@ -54,7 +39,10 @@ export const useRedirectUri = (
|
|||||||
|
|
||||||
isValid = true;
|
isValid = true;
|
||||||
|
|
||||||
if (isTrustedDomain(url, appUrlObj, cookieDomain, subdomainsEnabled)) {
|
if (
|
||||||
|
url.hostname == cookieDomain ||
|
||||||
|
url.hostname.endsWith(`.${cookieDomain}`)
|
||||||
|
) {
|
||||||
isTrusted = true;
|
isTrusted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,45 +62,3 @@ export const useRedirectUri = (
|
|||||||
httpsDowngrade: isHttpsDowngrade,
|
httpsDowngrade: isHttpsDowngrade,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ported from internal/controller/oauth_controller.go
|
|
||||||
const getEffectivePort = (url: URL): string => {
|
|
||||||
if (url.port) {
|
|
||||||
return url.port;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.protocol == "https:") {
|
|
||||||
return "443";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "80";
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isTrustedDomain = (
|
|
||||||
url: URL,
|
|
||||||
appUrl: URL,
|
|
||||||
cookieDomain: string,
|
|
||||||
subdomainsEnabled: boolean,
|
|
||||||
): boolean => {
|
|
||||||
if (url.protocol != appUrl.protocol) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getEffectivePort(url) != getEffectivePort(appUrl)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.hostname == appUrl.hostname) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!subdomainsEnabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.hostname.endsWith("." + cookieDomain.toLowerCase())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
type ScreenParams = {
|
|
||||||
login_for?: "oidc" | "app";
|
|
||||||
redirect_uri?: string;
|
|
||||||
oidc_ticket?: string;
|
|
||||||
oidc_scope?: string;
|
|
||||||
oidc_name?: string;
|
|
||||||
oidc_prompt?: "none" | "login";
|
|
||||||
};
|
|
||||||
|
|
||||||
const zodScreenParams = z.object({
|
|
||||||
login_for: z.enum(["oidc", "app"]).optional(),
|
|
||||||
redirect_uri: z.string().optional(),
|
|
||||||
oidc_ticket: z.string().optional(),
|
|
||||||
oidc_scope: z.string().optional(),
|
|
||||||
oidc_name: z.string().optional(),
|
|
||||||
oidc_prompt: z.enum(["none", "login"]).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function useScreenParams(params: URLSearchParams): ScreenParams {
|
|
||||||
const paramsObj = Object.fromEntries(params.entries());
|
|
||||||
const parsed = zodScreenParams.safeParse(paramsObj);
|
|
||||||
if (!parsed.success) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return parsed.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function recompileScreenParams(params: ScreenParams): string {
|
|
||||||
const p = new URLSearchParams(
|
|
||||||
Object.fromEntries(
|
|
||||||
Object.entries(params).filter(([, v]) => v !== undefined),
|
|
||||||
) as Record<string, string>,
|
|
||||||
).toString();
|
|
||||||
|
|
||||||
if (p.length > 0) {
|
|
||||||
return "?" + p;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Ermöglicht der App, auf Ihre Benutzergruppen-Informationen zuzugreifen.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Zurück zum Login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
"unauthorizedIpSubtitle": "Η διεύθυνση IP σας <code>{{ip}}</code> δεν είναι εξουσιοδοτημένη να έχει πρόσβαση στον πόρο <code>{{resource}}</code>.",
|
"unauthorizedIpSubtitle": "Η διεύθυνση IP σας <code>{{ip}}</code> δεν είναι εξουσιοδοτημένη να έχει πρόσβαση στον πόρο <code>{{resource}}</code>.",
|
||||||
"unauthorizedButton": "Προσπαθήστε ξανά",
|
"unauthorizedButton": "Προσπαθήστε ξανά",
|
||||||
"cancelTitle": "Ακύρωση",
|
"cancelTitle": "Ακύρωση",
|
||||||
"forgotPasswordTitle": "Ξεχάσατε;",
|
"forgotPasswordTitle": "Ξεχάσατε το συνθηματικό σας;",
|
||||||
"failedToFetchProvidersTitle": "Αποτυχία φόρτωσης παρόχων πιστοποίησης. Παρακαλώ ελέγξτε τις ρυθμίσεις σας.",
|
"failedToFetchProvidersTitle": "Αποτυχία φόρτωσης παρόχων πιστοποίησης. Παρακαλώ ελέγξτε τις ρυθμίσεις σας.",
|
||||||
"errorTitle": "Παρουσιάστηκε ένα σφάλμα",
|
"errorTitle": "Παρουσιάστηκε ένα σφάλμα",
|
||||||
"errorSubtitleInfo": "Το ακόλουθο σφάλμα προέκυψε κατά την επεξεργασία του αιτήματός σας:",
|
"errorSubtitleInfo": "Το ακόλουθο σφάλμα προέκυψε κατά την επεξεργασία του αιτήματός σας:",
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Εξουσιοδοτημένος",
|
"authorizeSuccessTitle": "Εξουσιοδοτημένος",
|
||||||
"authorizeSuccessSubtitle": "Θα μεταφερθείτε στην εφαρμογή σε λίγα δευτερόλεπτα.",
|
"authorizeSuccessSubtitle": "Θα μεταφερθείτε στην εφαρμογή σε λίγα δευτερόλεπτα.",
|
||||||
"authorizeErrorClientInfo": "Παρουσιάστηκε σφάλμα κατά τη φόρτωση των πληροφοριών. Παρακαλώ προσπαθήστε ξανά αργότερα.",
|
"authorizeErrorClientInfo": "Παρουσιάστηκε σφάλμα κατά τη φόρτωση των πληροφοριών. Παρακαλώ προσπαθήστε ξανά αργότερα.",
|
||||||
"authorizeErrorInvalidParams": "Το αίτημα δεν περιέχει τις απαιτούμενες παραμέτρους ή έχει μη έγκυρες παραμέτρους. Ελέγξτε τη διεύθυνση URL και προσπαθήστε ξανά.",
|
"authorizeErrorMissingParams": "Οι παρακάτω απαραίτητες πληροφορίες λείπουν από το αίτημά σας: {{missingParams}}",
|
||||||
"openidScopeName": "Σύνδεση OpenID",
|
"openidScopeName": "Σύνδεση OpenID",
|
||||||
"openidScopeDescription": "Επιτρέπει στην εφαρμογή την πρόσβαση στις πληροφορίες σύνδεσης OpenID.",
|
"openidScopeDescription": "Επιτρέπει στην εφαρμογή την πρόσβαση στις πληροφορίες σύνδεσης OpenID.",
|
||||||
"emailScopeName": "Ηλεκτρονικό ταχυδρομείο",
|
"emailScopeName": "Ηλεκτρονικό ταχυδρομείο",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Προφίλ",
|
"profileScopeName": "Προφίλ",
|
||||||
"profileScopeDescription": "Επιτρέπει στην εφαρμογή να έχει πρόσβαση στις πληροφορίες του προφίλ σας.",
|
"profileScopeDescription": "Επιτρέπει στην εφαρμογή να έχει πρόσβαση στις πληροφορίες του προφίλ σας.",
|
||||||
"groupsScopeName": "Ομάδες",
|
"groupsScopeName": "Ομάδες",
|
||||||
"groupsScopeDescription": "Επιτρέπει στην εφαρμογή την πρόσβαση στις πληροφορίες ομάδας σας.",
|
"groupsScopeDescription": "Επιτρέπει στην εφαρμογή την πρόσβαση στις πληροφορίες ομάδας σας."
|
||||||
"backToLoginButton": "Επιστροφή στη σελίδα εισόδου",
|
|
||||||
"phoneScopeName": "Τηλέφωνο",
|
|
||||||
"phoneScopeDescription": "Επιτρέπει στην εφαρμογή να έχει πρόσβαση στον αριθμό τηλεφώνου σας.",
|
|
||||||
"addressScopeName": "Διεύθυνση",
|
|
||||||
"addressScopeDescription": "Επιτρέπει στην εφαρμογή να έχει πρόσβαση στη διεύθυνσή σας.",
|
|
||||||
"loginTailscaleTitle": "Συνέχεια με Tailscale",
|
|
||||||
"loginTailscaleDescription": "Φαίνεται ότι έχετε πρόσβαση στο Tinyauth από μια εξουσιοδοτημένη συσκευή Tailscale. Θα θέλατε να συνεχίσετε με τη σύνδεση Tailscale σας;",
|
|
||||||
"loginTailscaleDeviceName": "Όνομα συσκευής:",
|
|
||||||
"loginTailscaleOtherMethod": "Σύνδεση με άλλη μέθοδο",
|
|
||||||
"loginTailscaleSuccess": "Επιτυχής ταυτοποίηση με Tailscale.",
|
|
||||||
"loginTailscaleFail": "Αποτυχία ταυτοποίησης με Tailscale. Δοκιμάστε ξανά ή χρησιμοποιήστε μια άλλη μέθοδο σύνδεσης.",
|
|
||||||
"logoutTailscaleSubtitle": "Αυτή τη στιγμή είστε συνδεδεμένοι με Tailscale με τη συσκευή σας <code>{{deviceName}}</code>. Κάντε κλικ στο παρακάτω κουμπί για να αποσυνδεθείτε.",
|
|
||||||
"quickActionsLanguage": "Γλώσσα",
|
|
||||||
"quickActionsTheme": "Θέμα",
|
|
||||||
"quickActionsThemeLight": "Φωτεινό",
|
|
||||||
"quickActionsThemeDark": "Σκούρο",
|
|
||||||
"quickActionsThemeSystem": "Σύστημα",
|
|
||||||
"quickActionsLogout": "Αποσύνδεση",
|
|
||||||
"quickActionsTitle": "Γρήγορες ενέργειες",
|
|
||||||
"quickActionsProviderLocal": "Τοπικό",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -88,18 +88,9 @@
|
|||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
"loginTailscaleTitle": "Continue with Tailscale",
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
"loginTailscaleDeviceName": "Device name:",
|
||||||
|
"loginTailscaleSubmit": "Continue with Tailscale",
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
"loginTailscaleOtherMethod": "Login with another method",
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout."
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -88,18 +88,9 @@
|
|||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
"loginTailscaleTitle": "Continue with Tailscale",
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
"loginTailscaleDeviceName": "Device name:",
|
||||||
|
"loginTailscaleSubmit": "Continue with Tailscale",
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
"loginTailscaleOtherMethod": "Login with another method",
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout."
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Autorisé",
|
"authorizeSuccessTitle": "Autorisé",
|
||||||
"authorizeSuccessSubtitle": "Vous allez être redirigé vers l'application dans quelques secondes.",
|
"authorizeSuccessSubtitle": "Vous allez être redirigé vers l'application dans quelques secondes.",
|
||||||
"authorizeErrorClientInfo": "Une erreur est survenue lors du chargement des informations du client. Veuillez réessayer plus tard.",
|
"authorizeErrorClientInfo": "Une erreur est survenue lors du chargement des informations du client. Veuillez réessayer plus tard.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "Les paramètres suivants sont manquants : {{missingParams}}",
|
||||||
"openidScopeName": "Connexion OpenID",
|
"openidScopeName": "Connexion OpenID",
|
||||||
"openidScopeDescription": "Autorise l'application à accéder à vos informations \"OpenID Connect\".",
|
"openidScopeDescription": "Autorise l'application à accéder à vos informations \"OpenID Connect\".",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profil",
|
"profileScopeName": "Profil",
|
||||||
"profileScopeDescription": "Autorise l'application à accéder aux informations de votre profil.",
|
"profileScopeDescription": "Autorise l'application à accéder aux informations de votre profil.",
|
||||||
"groupsScopeName": "Groupes",
|
"groupsScopeName": "Groupes",
|
||||||
"groupsScopeDescription": "Autorise une application à accéder aux informations de votre groupe.",
|
"groupsScopeDescription": "Autorise une application à accéder aux informations de votre groupe."
|
||||||
"backToLoginButton": "Revenir à la page de connexion",
|
|
||||||
"phoneScopeName": "Téléphone",
|
|
||||||
"phoneScopeDescription": "Autorise l'application à accéder à votre numéro de téléphone.",
|
|
||||||
"addressScopeName": "Adresse",
|
|
||||||
"addressScopeDescription": "Autorise l'application d'accéder à votre adresse.",
|
|
||||||
"loginTailscaleTitle": "Continuer avec Tailscale",
|
|
||||||
"loginTailscaleDescription": "Vous semblez accéder à Tinyauth à partir d'un appareil Tailscale autorisé. Voulez-vous continuer avec votre connexion Tailscale ?",
|
|
||||||
"loginTailscaleDeviceName": "Nom du périphérique:",
|
|
||||||
"loginTailscaleOtherMethod": "Se connecter avec une autre méthode",
|
|
||||||
"loginTailscaleSuccess": "Authentifié avec Tailscale.",
|
|
||||||
"loginTailscaleFail": "Échec de l'authentification Tailscale. Veuillez réessayer ou utiliser une autre méthode de connexion.",
|
|
||||||
"logoutTailscaleSubtitle": "Vous êtes actuellement connecté en tant que <code>{{deviceName}}</code>, cliquez sur le bouton ci-dessous pour vous déconnecter.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,8 +58,8 @@
|
|||||||
"invalidInput": "Input non valido",
|
"invalidInput": "Input non valido",
|
||||||
"domainWarningTitle": "Dominio non valido",
|
"domainWarningTitle": "Dominio non valido",
|
||||||
"domainWarningSubtitle": "Stai accedendo a questa istanza da un dominio errato. Scegliendo di procedere, potresti incontrare problemi con l'autenticazione.",
|
"domainWarningSubtitle": "Stai accedendo a questa istanza da un dominio errato. Scegliendo di procedere, potresti incontrare problemi con l'autenticazione.",
|
||||||
"domainWarningCurrent": "Attuale:",
|
"domainWarningCurrent": "Current:",
|
||||||
"domainWarningExpected": "Previsto:",
|
"domainWarningExpected": "Expected:",
|
||||||
"ignoreTitle": "Ignora",
|
"ignoreTitle": "Ignora",
|
||||||
"goToCorrectDomainTitle": "Vai al dominio corretto",
|
"goToCorrectDomainTitle": "Vai al dominio corretto",
|
||||||
"authorizeTitle": "Autorizza",
|
"authorizeTitle": "Autorizza",
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Autorizzato",
|
"authorizeSuccessTitle": "Autorizzato",
|
||||||
"authorizeSuccessSubtitle": "Verrai reindirizzato all'app in pochi secondi.",
|
"authorizeSuccessSubtitle": "Verrai reindirizzato all'app in pochi secondi.",
|
||||||
"authorizeErrorClientInfo": "Si è verificato un errore durante il caricamento delle informazioni del client. Riprova.",
|
"authorizeErrorClientInfo": "Si è verificato un errore durante il caricamento delle informazioni del client. Riprova.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "I seguenti parametri sono mancanti: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Permetti all'app di accedere alle tue informazioni OpenID Connect.",
|
"openidScopeDescription": "Permetti all'app di accedere alle tue informazioni OpenID Connect.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profilo",
|
"profileScopeName": "Profilo",
|
||||||
"profileScopeDescription": "Consenti all'app di accedere alle informazioni del tuo profilo.",
|
"profileScopeDescription": "Consenti all'app di accedere alle informazioni del tuo profilo.",
|
||||||
"groupsScopeName": "Gruppi",
|
"groupsScopeName": "Gruppi",
|
||||||
"groupsScopeDescription": "Consenti all'app di accedere alle informazioni sui tuoi gruppi.",
|
"groupsScopeDescription": "Consenti all'app di accedere alle informazioni sui tuoi gruppi."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,44 +2,44 @@
|
|||||||
"loginTitle": "Welcome back, login with",
|
"loginTitle": "Welcome back, login with",
|
||||||
"loginTitleSimple": "Welcome back, please login",
|
"loginTitleSimple": "Welcome back, please login",
|
||||||
"loginDivider": "Or",
|
"loginDivider": "Or",
|
||||||
"loginUsername": "ユーザーネーム",
|
"loginUsername": "Username",
|
||||||
"loginPassword": "パスワード",
|
"loginPassword": "Password",
|
||||||
"loginSubmit": "ログイン",
|
"loginSubmit": "Login",
|
||||||
"loginFailTitle": "ログイン出来ない",
|
"loginFailTitle": "Failed to log in",
|
||||||
"loginFailSubtitle": "Please check your username and password",
|
"loginFailSubtitle": "Please check your username and password",
|
||||||
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||||
"loginSuccessTitle": "ログイン済み",
|
"loginSuccessTitle": "Logged in",
|
||||||
"loginSuccessSubtitle": "Welcome back!",
|
"loginSuccessSubtitle": "Welcome back!",
|
||||||
"loginOauthFailTitle": "An error occurred",
|
"loginOauthFailTitle": "An error occurred",
|
||||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||||
"loginOauthSuccessTitle": "リダイレクトしています",
|
"loginOauthSuccessTitle": "Redirecting",
|
||||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||||
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "今すぐリダイレクト",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "続行",
|
"continueTitle": "Continue",
|
||||||
"continueRedirectingTitle": "リダイレクトしています…",
|
"continueRedirectingTitle": "Redirecting...",
|
||||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||||
"continueRedirectManually": "Redirect me manually",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "安全でないリダイレク",
|
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <code>https</code> to <code>http</code> which is not secure. Are you sure you want to continue?",
|
"continueInsecureRedirectSubtitle": "You are trying to redirect from <code>https</code> to <code>http</code> which is not secure. Are you sure you want to continue?",
|
||||||
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{cookieDomain}}</code>). Are you sure you want to continue?",
|
"continueUntrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{cookieDomain}}</code>). Are you sure you want to continue?",
|
||||||
"logoutFailTitle": "ログアウト出来ません",
|
"logoutFailTitle": "Failed to log out",
|
||||||
"logoutFailSubtitle": "Please try again",
|
"logoutFailSubtitle": "Please try again",
|
||||||
"logoutSuccessTitle": "Logged out",
|
"logoutSuccessTitle": "Logged out",
|
||||||
"logoutSuccessSubtitle": "ログアウトしました",
|
"logoutSuccessSubtitle": "You have been logged out",
|
||||||
"logoutTitle": "ログアウト",
|
"logoutTitle": "Logout",
|
||||||
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
|
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
|
||||||
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
|
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
|
||||||
"notFoundTitle": "ページが見つかりません",
|
"notFoundTitle": "Page not found",
|
||||||
"notFoundSubtitle": "The page you are looking for does not exist.",
|
"notFoundSubtitle": "The page you are looking for does not exist.",
|
||||||
"notFoundButton": "ホームに戻る",
|
"notFoundButton": "Go home",
|
||||||
"totpFailTitle": "Failed to verify code",
|
"totpFailTitle": "Failed to verify code",
|
||||||
"totpFailSubtitle": "Please check your code and try again",
|
"totpFailSubtitle": "Please check your code and try again",
|
||||||
"totpSuccessTitle": "Verified",
|
"totpSuccessTitle": "Verified",
|
||||||
"totpSuccessSubtitle": "アプリにリダイレクトしています",
|
"totpSuccessSubtitle": "Redirecting to your app",
|
||||||
"totpTitle": "TOTPコード入れて",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"totpSubtitle": "Please enter the code from your authenticator app.",
|
"totpSubtitle": "Please enter the code from your authenticator app.",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
@@ -47,8 +47,8 @@
|
|||||||
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
|
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"cancelTitle": "キャンセル",
|
"cancelTitle": "Cancel",
|
||||||
"forgotPasswordTitle": "パスワード忘れた?",
|
"forgotPasswordTitle": "Forgot your password?",
|
||||||
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||||
"errorTitle": "An error occurred",
|
"errorTitle": "An error occurred",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
||||||
@@ -71,35 +71,13 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Eメール",
|
"emailScopeName": "Email",
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
"emailScopeDescription": "Allows the app to access your email address.",
|
||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "권한 부여 완료",
|
"authorizeSuccessTitle": "권한 부여 완료",
|
||||||
"authorizeSuccessSubtitle": "몇 초 후에 앱으로 리디렉션됩니다.",
|
"authorizeSuccessSubtitle": "몇 초 후에 앱으로 리디렉션됩니다.",
|
||||||
"authorizeErrorClientInfo": "클라이언트 정보를 불러오는 중 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
|
"authorizeErrorClientInfo": "클라이언트 정보를 불러오는 중 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
|
||||||
"authorizeErrorInvalidParams": "요청에 필수 매개변수가 누락되었거나 유효하지 않은 매개변수가 포함되어 있습니다. URL을 확인한 후 다시 시도해 주세요.",
|
"authorizeErrorMissingParams": "다음 매개변수가 누락되었습니다: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "앱이 OpenID Connect 정보에 접근할 수 있도록 허용합니다.",
|
"openidScopeDescription": "앱이 OpenID Connect 정보에 접근할 수 있도록 허용합니다.",
|
||||||
"emailScopeName": "이메일",
|
"emailScopeName": "이메일",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "프로필",
|
"profileScopeName": "프로필",
|
||||||
"profileScopeDescription": "앱이 프로필 정보에 접근할 수 있도록 허용합니다.",
|
"profileScopeDescription": "앱이 프로필 정보에 접근할 수 있도록 허용합니다.",
|
||||||
"groupsScopeName": "그룹",
|
"groupsScopeName": "그룹",
|
||||||
"groupsScopeDescription": "앱이 그룹 정보에 접근할 수 있도록 허용합니다.",
|
"groupsScopeDescription": "앱이 그룹 정보에 접근할 수 있도록 허용합니다."
|
||||||
"backToLoginButton": "로그인 페이지로 돌아가기",
|
|
||||||
"phoneScopeName": "전화번호",
|
|
||||||
"phoneScopeDescription": "앱이 전화번호에 접근할 수 있도록 허용합니다.",
|
|
||||||
"addressScopeName": "주소",
|
|
||||||
"addressScopeDescription": "앱이 주소에 접근할 수 있도록 허용합니다.",
|
|
||||||
"loginTailscaleTitle": "Tailscale로 계속",
|
|
||||||
"loginTailscaleDescription": "사용자님은 인증된 Tailscale 기기에서 Tinyauth에 접속 중인 것으로 보입니다. Tailscale 연결을 계속하시겠습니까?",
|
|
||||||
"loginTailscaleDeviceName": "기기 이름:",
|
|
||||||
"loginTailscaleOtherMethod": "다른 방법으로 로그인",
|
|
||||||
"loginTailscaleSuccess": "Tailscale을 통해 성공적으로 인증되었습니다.",
|
|
||||||
"loginTailscaleFail": "Tailscale을 통한 인증에 실패했습니다. 다른 방식을 사용해 주세요.",
|
|
||||||
"logoutTailscaleSubtitle": "현재 <code>{{deviceName}}</code>가 Tailscale을 통해 로그인 되어 있습니다. 로그아웃 하시려면 아래 버튼을 클릭하세요.",
|
|
||||||
"quickActionsLanguage": "언어",
|
|
||||||
"quickActionsTheme": "테마",
|
|
||||||
"quickActionsThemeLight": "밝은 테마",
|
|
||||||
"quickActionsThemeDark": "어두운 테마",
|
|
||||||
"quickActionsThemeSystem": "시스템",
|
|
||||||
"quickActionsLogout": "로그아웃",
|
|
||||||
"quickActionsTitle": "빠른 동작",
|
|
||||||
"quickActionsProviderLocal": "로컬",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Geautoriseerd",
|
"authorizeSuccessTitle": "Geautoriseerd",
|
||||||
"authorizeSuccessSubtitle": "Je wordt binnen enkele seconden doorgestuurd naar de app.",
|
"authorizeSuccessSubtitle": "Je wordt binnen enkele seconden doorgestuurd naar de app.",
|
||||||
"authorizeErrorClientInfo": "Er is een fout opgetreden tijdens het laden van de cliëntinformatie. Probeer het later opnieuw.",
|
"authorizeErrorClientInfo": "Er is een fout opgetreden tijdens het laden van de cliëntinformatie. Probeer het later opnieuw.",
|
||||||
"authorizeErrorInvalidParams": "Er ontbreken verplichte parameters voor het verzoek of er zijn ongeldige parameters. Controleer de URL en probeer het opnieuw.",
|
"authorizeErrorMissingParams": "De volgende parameters ontbreken: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Hiermee kan de app toegang krijgen tot jouw OpenID Connect-informatie.",
|
"openidScopeDescription": "Hiermee kan de app toegang krijgen tot jouw OpenID Connect-informatie.",
|
||||||
"emailScopeName": "E-mail",
|
"emailScopeName": "E-mail",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profiel",
|
"profileScopeName": "Profiel",
|
||||||
"profileScopeDescription": "Hiermee kan de app toegang krijgen tot je profielinformatie.",
|
"profileScopeDescription": "Hiermee kan de app toegang krijgen tot je profielinformatie.",
|
||||||
"groupsScopeName": "Groepen",
|
"groupsScopeName": "Groepen",
|
||||||
"groupsScopeDescription": "Hiermee kan de app toegang krijgen tot jouw groepsinformatie.",
|
"groupsScopeDescription": "Hiermee kan de app toegang krijgen tot jouw groepsinformatie."
|
||||||
"backToLoginButton": "Terug naar Inloggen",
|
|
||||||
"phoneScopeName": "Telefoon",
|
|
||||||
"phoneScopeDescription": "Geeft de app toegang tot je telefoonnummer.",
|
|
||||||
"addressScopeName": "Adres",
|
|
||||||
"addressScopeDescription": "Geeft de app toegang tot je adres.",
|
|
||||||
"loginTailscaleTitle": "Doorgaan met Tailscale",
|
|
||||||
"loginTailscaleDescription": "Je lijkt toegang te hebben tot Tinyauth vanaf een geautoriseerd Tailscale-apparaat. Wil je doorgaan met je Tailscale-verbinding?",
|
|
||||||
"loginTailscaleDeviceName": "Apparaatnaam:",
|
|
||||||
"loginTailscaleOtherMethod": "Op een andere manier inloggen",
|
|
||||||
"loginTailscaleSuccess": "Succesvol geauthenticeerd met Tailscale.",
|
|
||||||
"loginTailscaleFail": "Authenticatie met Tailscale mislukt. Probeer het opnieuw of gebruik een andere inlogmethode.",
|
|
||||||
"logoutTailscaleSubtitle": "Je bent momenteel ingelogd met Tailscale op je apparaat <code>{{deviceName}}</code>. Klik op de knop hieronder om uit te loggen.",
|
|
||||||
"quickActionsLanguage": "Taal",
|
|
||||||
"quickActionsTheme": "Thema",
|
|
||||||
"quickActionsThemeLight": "Licht",
|
|
||||||
"quickActionsThemeDark": "Donker",
|
|
||||||
"quickActionsThemeSystem": "Systeem",
|
|
||||||
"quickActionsLogout": "Uitloggen",
|
|
||||||
"quickActionsTitle": "Snelle acties",
|
|
||||||
"quickActionsProviderLocal": "Lokaal",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,35 +71,13 @@
|
|||||||
"authorizeSuccessTitle": "Autoryzowano",
|
"authorizeSuccessTitle": "Autoryzowano",
|
||||||
"authorizeSuccessSubtitle": "Za kilka sekund nastąpi przekierowanie do aplikacji.",
|
"authorizeSuccessSubtitle": "Za kilka sekund nastąpi przekierowanie do aplikacji.",
|
||||||
"authorizeErrorClientInfo": "Wystąpił błąd podczas ładowania informacji o kliencie. Spróbuj ponownie później.",
|
"authorizeErrorClientInfo": "Wystąpił błąd podczas ładowania informacji o kliencie. Spróbuj ponownie później.",
|
||||||
"authorizeErrorInvalidParams": "Żądanie nie zawiera wymaganych parametrów lub zawiera nieprawidłowe parametry. Sprawdź adres URL i spróbuj ponownie.",
|
"authorizeErrorMissingParams": "Brakuje następujących parametrów: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Zezwala aplikacji na dostęp do informacji o OpenID Connect.",
|
"openidScopeDescription": "Zezwala aplikacji na dostęp do informacji o OpenID Connect.",
|
||||||
"emailScopeName": "E-mail",
|
"emailScopeName": "E-mail",
|
||||||
"emailScopeDescription": "Zezwala aplikacji na dostęp do adresów e-mail.",
|
"emailScopeDescription": "Zezwala aplikacji na dostęp do adresów e-mail.",
|
||||||
"profileScopeName": "Profil",
|
"profileScopeName": "Profil",
|
||||||
"profileScopeDescription": "Zezwala aplikacji na dostęp do informacji o profilu.",
|
"profileScopeDescription": "Zezwala aplikacji na dostęp do informacji o porfilu.",
|
||||||
"groupsScopeName": "Grupy",
|
"groupsScopeName": "Grupy",
|
||||||
"groupsScopeDescription": "Zezwala aplikacji na dostęp do informacji o grupie.",
|
"groupsScopeDescription": "Zezwala aplikacji na dostęp do informacji o grupie."
|
||||||
"backToLoginButton": "Wróć do logowania",
|
|
||||||
"phoneScopeName": "Telefon",
|
|
||||||
"phoneScopeDescription": "Pozwala aplikacji na dostęp do Twojego numeru telefonu.",
|
|
||||||
"addressScopeName": "Adres",
|
|
||||||
"addressScopeDescription": "Pozwala aplikacji na dostęp do Twojego adresu.",
|
|
||||||
"loginTailscaleTitle": "Kontynuuj poprzez Tailscale",
|
|
||||||
"loginTailscaleDescription": "Wygląda na to, że uzyskujesz dostęp do Tinyauth z autoryzowanego urządzenia Tailscale. Czy chcesz kontynuować połączenie poprzez Tailscale?",
|
|
||||||
"loginTailscaleDeviceName": "Nazwa urządzenia:",
|
|
||||||
"loginTailscaleOtherMethod": "Zaloguj się inną metodą",
|
|
||||||
"loginTailscaleSuccess": "Pomyślnie uwierzytelniono poprzez Tailscale.",
|
|
||||||
"loginTailscaleFail": "Nie udało się uwierzytelnić poprzez Tailscale. Spróbuj ponownie lub użyj innej metody logowania.",
|
|
||||||
"logoutTailscaleSubtitle": "Jesteś obecnie zalogowany jako <code>{{deviceName}}</code>. Kliknij poniższy przycisk, aby się wylogować.",
|
|
||||||
"quickActionsLanguage": "Język",
|
|
||||||
"quickActionsTheme": "Motyw",
|
|
||||||
"quickActionsThemeLight": "Jasny",
|
|
||||||
"quickActionsThemeDark": "Ciemny",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Wyloguj się",
|
|
||||||
"quickActionsTitle": "Szybkie czynności",
|
|
||||||
"quickActionsProviderLocal": "Lokalny",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"fieldRequired": "Это поле является обязательным",
|
"fieldRequired": "Это поле является обязательным",
|
||||||
"invalidInput": "Недопустимый ввод",
|
"invalidInput": "Недопустимый ввод",
|
||||||
"domainWarningTitle": "Неверный домен",
|
"domainWarningTitle": "Неверный домен",
|
||||||
"domainWarningSubtitle": "Вы получаете доступ к этому экземпляру приложения через некорректный домен. Если вы продолжите, вы можете столкнуться с проблемами аутентификации.",
|
"domainWarningSubtitle": "You are accessing this instance from an incorrect domain. If you proceed, you may encounter issues with authentication.",
|
||||||
"domainWarningCurrent": "Текущий:",
|
"domainWarningCurrent": "Текущий:",
|
||||||
"domainWarningExpected": "Ожидается:",
|
"domainWarningExpected": "Ожидается:",
|
||||||
"ignoreTitle": "Игнорировать",
|
"ignoreTitle": "Игнорировать",
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Разрешено",
|
"authorizeSuccessTitle": "Разрешено",
|
||||||
"authorizeSuccessSubtitle": "Вы будете перенаправлены в приложение через несколько секунд.",
|
"authorizeSuccessSubtitle": "Вы будете перенаправлены в приложение через несколько секунд.",
|
||||||
"authorizeErrorClientInfo": "Произошла ошибка при загрузке информации о клиенте. Пожалуйста, повторите попытку позже.",
|
"authorizeErrorClientInfo": "Произошла ошибка при загрузке информации о клиенте. Пожалуйста, повторите попытку позже.",
|
||||||
"authorizeErrorInvalidParams": "В запросе отсутствуют необходимые параметры или указаны недопустимые параметры. Проверьте URL-адрес и попробуйте снова.",
|
"authorizeErrorMissingParams": "Отсутствуют следующие параметры: {{missingParams}}",
|
||||||
"openidScopeName": "Подключение OpenID",
|
"openidScopeName": "Подключение OpenID",
|
||||||
"openidScopeDescription": "Приложение сможет получить доступ к информации подключённого OpenID.",
|
"openidScopeDescription": "Приложение сможет получить доступ к информации подключённого OpenID.",
|
||||||
"emailScopeName": "Эл. Почта",
|
"emailScopeName": "Эл. Почта",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Профиль",
|
"profileScopeName": "Профиль",
|
||||||
"profileScopeDescription": "Приложение сможет получить доступ к информации вашего профиля.",
|
"profileScopeDescription": "Приложение сможет получить доступ к информации вашего профиля.",
|
||||||
"groupsScopeName": "Группы",
|
"groupsScopeName": "Группы",
|
||||||
"groupsScopeDescription": "Приложение сможет получать доступ к информации о вашей группе.",
|
"groupsScopeDescription": "Приложение сможет получать доступ к информации о вашей группе."
|
||||||
"backToLoginButton": "Вернуться к форме входа",
|
|
||||||
"phoneScopeName": "Телефон",
|
|
||||||
"phoneScopeDescription": "Приложение сможет получить доступ к вашему номеру телефона.",
|
|
||||||
"addressScopeName": "Адрес",
|
|
||||||
"addressScopeDescription": "Приложение сможет получить доступ к вашему адресу.",
|
|
||||||
"loginTailscaleTitle": "Продолжить через Tailscale",
|
|
||||||
"loginTailscaleDescription": "Похоже, вы используете авторизованное устройство Tailscale для доступа к Tinyauth. Хотите продолжить через подключение Tailscale?",
|
|
||||||
"loginTailscaleDeviceName": "Имя устройства:",
|
|
||||||
"loginTailscaleOtherMethod": "Войти другим способом",
|
|
||||||
"loginTailscaleSuccess": "Успешная авторизация через Tailscale.",
|
|
||||||
"loginTailscaleFail": "Не удалось авторизоваться через Tailscale. Пожалуйста, попробуйте ещё раз, или используйте другой способ.",
|
|
||||||
"logoutTailscaleSubtitle": "Вы вошли через Tailscale на устройстве <code>{{deviceName}}</code>. Нажмите кнопку ниже, чтобы выйти.",
|
|
||||||
"quickActionsLanguage": "Язык",
|
|
||||||
"quickActionsTheme": "Тема",
|
|
||||||
"quickActionsThemeLight": "Светлая",
|
|
||||||
"quickActionsThemeDark": "Тёмная",
|
|
||||||
"quickActionsThemeSystem": "Система",
|
|
||||||
"quickActionsLogout": "Выйти",
|
|
||||||
"quickActionsTitle": "Быстрые действия",
|
|
||||||
"quickActionsProviderLocal": "Локальный",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"fieldRequired": "Ово поље је неопходно",
|
"fieldRequired": "Ово поље је неопходно",
|
||||||
"invalidInput": "Неисправан унос",
|
"invalidInput": "Неисправан унос",
|
||||||
"domainWarningTitle": "Неисправан домен",
|
"domainWarningTitle": "Неисправан домен",
|
||||||
"domainWarningSubtitle": "Приступате овој инстанци са неисправног домена. Ако наставите, можете наићи на проблеме са аутентификацијом.",
|
"domainWarningSubtitle": "You are accessing this instance from an incorrect domain. If you proceed, you may encounter issues with authentication.",
|
||||||
"domainWarningCurrent": "Тренутни:",
|
"domainWarningCurrent": "Тренутни:",
|
||||||
"domainWarningExpected": "Очекивани:",
|
"domainWarningExpected": "Очекивани:",
|
||||||
"ignoreTitle": "Игнориши",
|
"ignoreTitle": "Игнориши",
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Ауторизован",
|
"authorizeSuccessTitle": "Ауторизован",
|
||||||
"authorizeSuccessSubtitle": "Бићете преусмерени на апликацију за неколико секунди.",
|
"authorizeSuccessSubtitle": "Бићете преусмерени на апликацију за неколико секунди.",
|
||||||
"authorizeErrorClientInfo": "Појавила се грешка током учитавања информација о клијенту. Молим вас покушајте поново касније.",
|
"authorizeErrorClientInfo": "Појавила се грешка током учитавања информација о клијенту. Молим вас покушајте поново касније.",
|
||||||
"authorizeErrorInvalidParams": "Захтеву недостају неопходни параметри или су они неисправни. Молим вас проверите адресу и покушајте поново.",
|
"authorizeErrorMissingParams": "Следећи параметри недостају: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID повезивање",
|
"openidScopeName": "OpenID повезивање",
|
||||||
"openidScopeDescription": "Омогућава апликацији да приступа информацији о вашој OpenID вези.",
|
"openidScopeDescription": "Омогућава апликацији да приступа информацији о вашој OpenID вези.",
|
||||||
"emailScopeName": "Е-пошта",
|
"emailScopeName": "Е-пошта",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Профил",
|
"profileScopeName": "Профил",
|
||||||
"profileScopeDescription": "Омогућава апликацији да приступа информацијама о вашем профилу.",
|
"profileScopeDescription": "Омогућава апликацији да приступа информацијама о вашем профилу.",
|
||||||
"groupsScopeName": "Групе",
|
"groupsScopeName": "Групе",
|
||||||
"groupsScopeDescription": "Омогућава апликацији да приступа информацијама о вашој групи.",
|
"groupsScopeDescription": "Омогућава апликацији да приступа информацијама о вашој групи."
|
||||||
"backToLoginButton": "Назад на пријаву",
|
|
||||||
"phoneScopeName": "Телефон",
|
|
||||||
"phoneScopeDescription": "Дозволите овој апликацији да приступи вашем броју телефона.",
|
|
||||||
"addressScopeName": "Адреса",
|
|
||||||
"addressScopeDescription": "Омогућава апликацији да приступи вашој адреси.",
|
|
||||||
"loginTailscaleTitle": "Настави са Tailscale-ом",
|
|
||||||
"loginTailscaleDescription": "Изгледа да приступате Tinyauth-у са овлашћеног Tailscale уређаја. Да ли желите да наставите са Tailscale везом?",
|
|
||||||
"loginTailscaleDeviceName": "Назив уређаја:",
|
|
||||||
"loginTailscaleOtherMethod": "Пријави се на други начин",
|
|
||||||
"loginTailscaleSuccess": "Успешно аутентификован са Tailscale-ом.",
|
|
||||||
"loginTailscaleFail": "Неуспела аутентификација са Tailscale-ом. Молим вас покушајте поново или користите други начин пријаве.",
|
|
||||||
"logoutTailscaleSubtitle": "Тренутно сте пријављени са Tailscale-ом на вашем уређају <code>{{deviceName}}</code>. Кликните на дугме испод за одјаву.",
|
|
||||||
"quickActionsLanguage": "Језик",
|
|
||||||
"quickActionsTheme": "Тема",
|
|
||||||
"quickActionsThemeLight": "Светло",
|
|
||||||
"quickActionsThemeDark": "Тамна",
|
|
||||||
"quickActionsThemeSystem": "Систем",
|
|
||||||
"quickActionsLogout": "Одјава",
|
|
||||||
"quickActionsTitle": "Брзе радње",
|
|
||||||
"quickActionsProviderLocal": "Локално",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"logoutSuccessTitle": "Ви вийшли",
|
"logoutSuccessTitle": "Ви вийшли",
|
||||||
"logoutSuccessSubtitle": "Ви вийшли з системи",
|
"logoutSuccessSubtitle": "Ви вийшли з системи",
|
||||||
"logoutTitle": "Вийти",
|
"logoutTitle": "Вийти",
|
||||||
"logoutUsernameSubtitle": "Ви увійшли як <code>{{username}}</code>. Натисніть кнопку нижче для виходу.",
|
"logoutUsernameSubtitle": "Зараз ви увійшли як <code>{{username}}</code>. Натисніть кнопку нижче для виходу.",
|
||||||
"logoutOauthSubtitle": "Наразі ви увійшли як <code>{{username}}</code> використовуючи провайдера {{provider}} OAuth. Натисніть кнопку нижче, щоб вийти.",
|
"logoutOauthSubtitle": "Наразі ви увійшли як <code>{{username}}</code> використовуючи провайдера {{provider}} OAuth. Натисніть кнопку нижче, щоб вийти.",
|
||||||
"notFoundTitle": "Сторінку не знайдено",
|
"notFoundTitle": "Сторінку не знайдено",
|
||||||
"notFoundSubtitle": "Сторінка, яку ви шукаєте, не існує.",
|
"notFoundSubtitle": "Сторінка, яку ви шукаєте, не існує.",
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Авторизовано",
|
"authorizeSuccessTitle": "Авторизовано",
|
||||||
"authorizeSuccessSubtitle": "Вас буде перенаправлено до програми за декілька секунд.",
|
"authorizeSuccessSubtitle": "Вас буде перенаправлено до програми за декілька секунд.",
|
||||||
"authorizeErrorClientInfo": "Під час завантаження даних клієнта сталася помилка. Будь ласка, спробуйте ще раз пізніше.",
|
"authorizeErrorClientInfo": "Під час завантаження даних клієнта сталася помилка. Будь ласка, спробуйте ще раз пізніше.",
|
||||||
"authorizeErrorInvalidParams": "У запиті відсутні обов'язкові або містяться некоректні параметри. Будь ласка, перевірте URL-адресу та спробуйте знову.",
|
"authorizeErrorMissingParams": "Відсутні наступні параметри: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Дозволяє програмі отримувати доступ до вашої інформації OpenID Connect.",
|
"openidScopeDescription": "Дозволяє програмі отримувати доступ до вашої інформації OpenID Connect.",
|
||||||
"emailScopeName": "Електронна пошта",
|
"emailScopeName": "Електронна пошта",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Профіль",
|
"profileScopeName": "Профіль",
|
||||||
"profileScopeDescription": "Дозволяє програмі отримувати доступ до інформації вашого профілю.",
|
"profileScopeDescription": "Дозволяє програмі отримувати доступ до інформації вашого профілю.",
|
||||||
"groupsScopeName": "Групи",
|
"groupsScopeName": "Групи",
|
||||||
"groupsScopeDescription": "Дозволяє програмі отримувати доступ до інформації про групу.",
|
"groupsScopeDescription": "Дозволяє програмі отримувати доступ до інформації про групу."
|
||||||
"backToLoginButton": "Повернутися на сторінку входу",
|
|
||||||
"phoneScopeName": "Номер телефону",
|
|
||||||
"phoneScopeDescription": "Дозволяє програмі отримувати доступ до вашого номера телефону.",
|
|
||||||
"addressScopeName": "Адреса",
|
|
||||||
"addressScopeDescription": "Дозволяє програмі отримувати доступ до вашої адреси.",
|
|
||||||
"loginTailscaleTitle": "Продовжити з Tailscale",
|
|
||||||
"loginTailscaleDescription": "Здається, Ви отримали доступ до Tinyauth з авторизованого пристрою Tailscale. Продовжити з'єднання з Tailscale?",
|
|
||||||
"loginTailscaleDeviceName": "Назва пристрою:",
|
|
||||||
"loginTailscaleOtherMethod": "Увійти за допомогою іншого методу",
|
|
||||||
"loginTailscaleSuccess": "Успішно авторизовано з Tailscale.",
|
|
||||||
"loginTailscaleFail": "Не вдалося увійти з Tailscale. Будь ласка, спробуйте ще раз або скористайтеся іншим методом авторизації.",
|
|
||||||
"logoutTailscaleSubtitle": "Наразі ви ввійшли за допомогою Tailscale на пристрої <code>{{deviceName}}</code>. Натисніть на кнопку нижче, щоб вийти.",
|
|
||||||
"quickActionsLanguage": "Мова",
|
|
||||||
"quickActionsTheme": "Тема",
|
|
||||||
"quickActionsThemeLight": "Світла",
|
|
||||||
"quickActionsThemeDark": "Темна",
|
|
||||||
"quickActionsThemeSystem": "Система",
|
|
||||||
"quickActionsLogout": "Вийти",
|
|
||||||
"quickActionsTitle": "Швидкі дії",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "Authorized",
|
"authorizeSuccessTitle": "Authorized",
|
||||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
||||||
"openidScopeName": "OpenID Connect",
|
"openidScopeName": "OpenID Connect",
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||||
"emailScopeName": "Email",
|
"emailScopeName": "Email",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "Profile",
|
"profileScopeName": "Profile",
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||||
"groupsScopeName": "Groups",
|
"groupsScopeName": "Groups",
|
||||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
"groupsScopeDescription": "Allows the app to access your group information."
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "已授权",
|
"authorizeSuccessTitle": "已授权",
|
||||||
"authorizeSuccessSubtitle": "您将在几秒钟内被重定向到应用程序。",
|
"authorizeSuccessSubtitle": "您将在几秒钟内被重定向到应用程序。",
|
||||||
"authorizeErrorClientInfo": "加载客户端信息时发生错误。请稍后再试。",
|
"authorizeErrorClientInfo": "加载客户端信息时发生错误。请稍后再试。",
|
||||||
"authorizeErrorInvalidParams": "请求缺少所需参数或参数无效。请检查 URL 并重试。",
|
"authorizeErrorMissingParams": "参数缺失:{{missingParams}}",
|
||||||
"openidScopeName": "OpenID 连接",
|
"openidScopeName": "OpenID 连接",
|
||||||
"openidScopeDescription": "允许应用访问您的 OpenID 连接信息。",
|
"openidScopeDescription": "允许应用访问您的 OpenID 连接信息。",
|
||||||
"emailScopeName": "邮箱",
|
"emailScopeName": "邮箱",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "个人资料",
|
"profileScopeName": "个人资料",
|
||||||
"profileScopeDescription": "允许应用访问您的个人信息。",
|
"profileScopeDescription": "允许应用访问您的个人信息。",
|
||||||
"groupsScopeName": "分组",
|
"groupsScopeName": "分组",
|
||||||
"groupsScopeDescription": "允许应用程序访问您的群组信息。",
|
"groupsScopeDescription": "允许应用程序访问您的群组信息。"
|
||||||
"backToLoginButton": "返回登录界面",
|
|
||||||
"phoneScopeName": "手机",
|
|
||||||
"phoneScopeDescription": "允许此应用程序访问您的手机号码。",
|
|
||||||
"addressScopeName": "地址",
|
|
||||||
"addressScopeDescription": "允许此应用程序访问您的地址。",
|
|
||||||
"loginTailscaleTitle": "使用 Tailscale 继续",
|
|
||||||
"loginTailscaleDescription": "您似乎正在使用已授权的 Tailscale 设备访问 Tinyauth 。是否要继续使用您的 Tailscale 连接?",
|
|
||||||
"loginTailscaleDeviceName": "设备名称:",
|
|
||||||
"loginTailscaleOtherMethod": "使用其他方式登录",
|
|
||||||
"loginTailscaleSuccess": "已成功使用 Tailscale 进行身份验证。",
|
|
||||||
"loginTailscaleFail": "使用 Tailscale 进行身份验证失败。请重试或使用其他登录方式。",
|
|
||||||
"logoutTailscaleSubtitle": "您当前已通过 Tailscale 在名为<code>{{deviceName}}</code>的设备上登录。点击下方按钮注销。",
|
|
||||||
"quickActionsLanguage": "语言",
|
|
||||||
"quickActionsTheme": "主题",
|
|
||||||
"quickActionsThemeLight": "亮色主题",
|
|
||||||
"quickActionsThemeDark": "暗色主题",
|
|
||||||
"quickActionsThemeSystem": "跟随系统",
|
|
||||||
"quickActionsLogout": "登出",
|
|
||||||
"quickActionsTitle": "快速操作",
|
|
||||||
"quickActionsProviderLocal": "本地",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"authorizeSuccessTitle": "已授權",
|
"authorizeSuccessTitle": "已授權",
|
||||||
"authorizeSuccessSubtitle": "幾秒鐘內您將會被重新導向至應用程式。",
|
"authorizeSuccessSubtitle": "幾秒鐘內您將會被重新導向至應用程式。",
|
||||||
"authorizeErrorClientInfo": "載入用戶端資訊時發生錯誤。請稍後再試。",
|
"authorizeErrorClientInfo": "載入用戶端資訊時發生錯誤。請稍後再試。",
|
||||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
"authorizeErrorMissingParams": "下列參數遺失:{{missingParams}}",
|
||||||
"openidScopeName": "OpenID 連接",
|
"openidScopeName": "OpenID 連接",
|
||||||
"openidScopeDescription": "允許該應用程式存取您的 OpenID Connect 資訊。",
|
"openidScopeDescription": "允許該應用程式存取您的 OpenID Connect 資訊。",
|
||||||
"emailScopeName": "電子郵件",
|
"emailScopeName": "電子郵件",
|
||||||
@@ -79,27 +79,5 @@
|
|||||||
"profileScopeName": "個人檔案",
|
"profileScopeName": "個人檔案",
|
||||||
"profileScopeDescription": "允許該應用程式存取您的個人資料。",
|
"profileScopeDescription": "允許該應用程式存取您的個人資料。",
|
||||||
"groupsScopeName": "群組",
|
"groupsScopeName": "群組",
|
||||||
"groupsScopeDescription": "允許該應用程式存取您的群組資訊。",
|
"groupsScopeDescription": "允許該應用程式存取您的群組資訊。"
|
||||||
"backToLoginButton": "Back to login",
|
|
||||||
"phoneScopeName": "Phone",
|
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
|
||||||
"addressScopeName": "Address",
|
|
||||||
"addressScopeDescription": "Allows the app to access your address.",
|
|
||||||
"loginTailscaleTitle": "Continue with Tailscale",
|
|
||||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
|
||||||
"loginTailscaleDeviceName": "Device name:",
|
|
||||||
"loginTailscaleOtherMethod": "Login with another method",
|
|
||||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
|
||||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
|
||||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
|
||||||
"quickActionsLanguage": "Language",
|
|
||||||
"quickActionsTheme": "Theme",
|
|
||||||
"quickActionsThemeLight": "Light",
|
|
||||||
"quickActionsThemeDark": "Dark",
|
|
||||||
"quickActionsThemeSystem": "System",
|
|
||||||
"quickActionsLogout": "Logout",
|
|
||||||
"quickActionsTitle": "Quick Actions",
|
|
||||||
"quickActionsProviderLocal": "Local",
|
|
||||||
"quickActionsProviderLDAP": "LDAP",
|
|
||||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,7 @@ createRoot(document.getElementById("root")!).render(
|
|||||||
<Route element={<Layout />} errorElement={<ErrorPage />}>
|
<Route element={<Layout />} errorElement={<ErrorPage />}>
|
||||||
<Route path="/" element={<App />} />
|
<Route path="/" element={<App />} />
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
<Route
|
<Route path="/authorize" element={<AuthorizePage />} />
|
||||||
path="/oidc/authorize"
|
|
||||||
element={<AuthorizePage />}
|
|
||||||
/>
|
|
||||||
<Route path="/logout" element={<LogoutPage />} />
|
<Route path="/logout" element={<LogoutPage />} />
|
||||||
<Route path="/continue" element={<ContinuePage />} />
|
<Route path="/continue" element={<ContinuePage />} />
|
||||||
<Route path="/totp" element={<TotpPage />} />
|
<Route path="/totp" element={<TotpPage />} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useUserContext } from "@/context/user-context";
|
import { useUserContext } from "@/context/user-context";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import { Navigate, useNavigate } from "react-router";
|
import { Navigate, useNavigate } from "react-router";
|
||||||
import { useLocation } from "react-router";
|
import { useLocation } from "react-router";
|
||||||
import {
|
import {
|
||||||
@@ -10,9 +10,11 @@ import {
|
|||||||
CardFooter,
|
CardFooter,
|
||||||
CardContent,
|
CardContent,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
|
import { getOidcClientInfoSchema } from "@/schemas/oidc-schemas";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { useOIDCParams } from "@/lib/hooks/oidc";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { TFunction } from "i18next";
|
import { TFunction } from "i18next";
|
||||||
import { Mail, MapPin, Phone, Shield, User, Users } from "lucide-react";
|
import { Mail, MapPin, Phone, Shield, User, Users } from "lucide-react";
|
||||||
@@ -21,11 +23,6 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import {
|
|
||||||
recompileScreenParams,
|
|
||||||
useScreenParams,
|
|
||||||
} from "@/lib/hooks/screen-params";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
type Scope = {
|
type Scope = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -87,25 +84,27 @@ export const AuthorizePage = () => {
|
|||||||
const scopeMap = createScopeMap(t);
|
const scopeMap = createScopeMap(t);
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
const screenParams = useScreenParams(searchParams);
|
const oidcParams = useOIDCParams(searchParams);
|
||||||
const isOidc = screenParams.login_for === "oidc";
|
|
||||||
const compiledParams = recompileScreenParams(screenParams);
|
|
||||||
|
|
||||||
// TODO: maybe a better way to do this
|
const getClientInfo = useQuery({
|
||||||
const shouldAutoAuthorize =
|
queryKey: ["client", oidcParams.values.client_id],
|
||||||
auth.authenticated &&
|
queryFn: async () => {
|
||||||
isOidc &&
|
const res = await fetch(
|
||||||
screenParams.oidc_ticket !== undefined &&
|
`/api/oidc/clients/${encodeURIComponent(oidcParams.values.client_id)}`,
|
||||||
screenParams.oidc_scope !== undefined &&
|
);
|
||||||
screenParams.oidc_prompt === "none";
|
const data = await getOidcClientInfoSchema.parseAsync(await res.json());
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: oidcParams.isOidc,
|
||||||
|
});
|
||||||
|
|
||||||
const { mutate: authorizeMutate, isPending: authorizePending } = useMutation({
|
const authorizeMutation = useMutation({
|
||||||
mutationFn: () => {
|
mutationFn: () => {
|
||||||
return axios.post("/api/oidc/authorize-complete", {
|
return axios.post("/api/oidc/authorize", {
|
||||||
ticket: screenParams.oidc_ticket,
|
...oidcParams.values,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
mutationKey: ["authorize", screenParams.oidc_ticket],
|
mutationKey: ["authorize", oidcParams.values.client_id],
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
toast.info(t("authorizeSuccessTitle"), {
|
toast.info(t("authorizeSuccessTitle"), {
|
||||||
description: t("authorizeSuccessSubtitle"),
|
description: t("authorizeSuccessSubtitle"),
|
||||||
@@ -119,38 +118,56 @@ export const AuthorizePage = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
if (oidcParams.issues.length > 0) {
|
||||||
if (shouldAutoAuthorize) {
|
|
||||||
authorizeMutate();
|
|
||||||
}
|
|
||||||
}, [shouldAutoAuthorize, authorizeMutate]);
|
|
||||||
|
|
||||||
if (!isOidc || !screenParams.oidc_ticket || !screenParams.oidc_scope) {
|
|
||||||
return (
|
return (
|
||||||
<Navigate
|
<Navigate
|
||||||
to={`/error?error=${encodeURIComponent(t("authorizeErrorInvalidParams"))}`}
|
to={`/error?error=${encodeURIComponent(t("authorizeErrorMissingParams", { missingParams: oidcParams.issues.join(", ") }))}`}
|
||||||
replace
|
replace
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!auth.authenticated || screenParams.oidc_prompt === "login") {
|
if (!auth.authenticated) {
|
||||||
return <Navigate to={`/login${compiledParams}`} replace />;
|
return <Navigate to={`/login?${oidcParams.compiled}`} replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getClientInfo.isLoading) {
|
||||||
|
return (
|
||||||
|
<Card className="gap-0">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-xl">
|
||||||
|
{t("authorizeLoadingTitle")}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<CardDescription>{t("authorizeLoadingSubtitle")}</CardDescription>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getClientInfo.isError) {
|
||||||
|
return (
|
||||||
|
<Navigate
|
||||||
|
to={`/error?error=${encodeURIComponent(t("authorizeErrorClientInfo"))}`}
|
||||||
|
replace
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const scopes =
|
const scopes =
|
||||||
screenParams.oidc_scope.split(" ").filter((s) => s.trim() !== "") || [];
|
oidcParams.values.scope.split(" ").filter((s) => s.trim() !== "") || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="mb-2">
|
<CardHeader className="mb-2">
|
||||||
<div className="flex flex-col gap-3 items-center justify-center text-center">
|
<div className="flex flex-col gap-3 items-center justify-center text-center">
|
||||||
<div className="bg-accent-foreground box-content text-muted text-xl font-bold font-sans rounded-lg size-8 p-2 flex items-center justify-center">
|
<div className="bg-accent-foreground box-content text-muted text-xl font-bold font-sans rounded-lg size-8 p-2 flex items-center justify-center">
|
||||||
{screenParams.oidc_name ? screenParams.oidc_name.slice(0, 1) : "U"}
|
{getClientInfo.data?.name.slice(0, 1) || "U"}
|
||||||
</div>
|
</div>
|
||||||
<CardTitle className="text-xl">
|
<CardTitle className="text-xl">
|
||||||
{t("authorizeCardTitle", {
|
{t("authorizeCardTitle", {
|
||||||
app: screenParams.oidc_name || "Unknown",
|
app: getClientInfo.data?.name || "Unknown",
|
||||||
})}
|
})}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-sm max-w-sm">
|
<CardDescription className="text-sm max-w-sm">
|
||||||
@@ -183,14 +200,14 @@ export const AuthorizePage = () => {
|
|||||||
)}
|
)}
|
||||||
<CardFooter className="flex flex-col items-stretch gap-3">
|
<CardFooter className="flex flex-col items-stretch gap-3">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => authorizeMutate()}
|
onClick={() => authorizeMutation.mutate()}
|
||||||
loading={authorizePending || shouldAutoAuthorize}
|
loading={authorizeMutation.isPending}
|
||||||
>
|
>
|
||||||
{t("authorizeTitle")}
|
{t("authorizeTitle")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate(`/logout${compiledParams}`)}
|
onClick={() => navigate("/")}
|
||||||
disabled={authorizePending || shouldAutoAuthorize}
|
disabled={authorizeMutation.isPending}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
{t("cancelTitle")}
|
{t("cancelTitle")}
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ import { Trans, useTranslation } from "react-i18next";
|
|||||||
import { Navigate, useLocation, useNavigate } from "react-router";
|
import { Navigate, useLocation, useNavigate } from "react-router";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useRedirectUri } from "@/lib/hooks/redirect-uri";
|
import { useRedirectUri } from "@/lib/hooks/redirect-uri";
|
||||||
import {
|
|
||||||
recompileScreenParams,
|
|
||||||
useScreenParams,
|
|
||||||
} from "@/lib/hooks/screen-params";
|
|
||||||
|
|
||||||
export const ContinuePage = () => {
|
export const ContinuePage = () => {
|
||||||
const { app, ui } = useAppContext();
|
const { app, ui } = useAppContext();
|
||||||
@@ -29,16 +25,11 @@ export const ContinuePage = () => {
|
|||||||
const hasRedirected = useRef(false);
|
const hasRedirected = useRef(false);
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
const screenParams = useScreenParams(searchParams);
|
const redirectUri = searchParams.get("redirect_uri");
|
||||||
const redirectUri = screenParams.redirect_uri;
|
|
||||||
const isAppLogin = screenParams.login_for === "app";
|
|
||||||
const recompiledParams = recompileScreenParams(screenParams);
|
|
||||||
|
|
||||||
const { url, valid, trusted, allowedProto, httpsDowngrade } = useRedirectUri(
|
const { url, valid, trusted, allowedProto, httpsDowngrade } = useRedirectUri(
|
||||||
redirectUri,
|
redirectUri,
|
||||||
app.cookieDomain,
|
app.cookieDomain,
|
||||||
app.appUrl,
|
|
||||||
app.subdomainsEnabled,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const urlHref = url?.href;
|
const urlHref = url?.href;
|
||||||
@@ -52,8 +43,7 @@ export const ContinuePage = () => {
|
|||||||
auth.authenticated &&
|
auth.authenticated &&
|
||||||
hasValidRedirect &&
|
hasValidRedirect &&
|
||||||
!showUntrustedWarning &&
|
!showUntrustedWarning &&
|
||||||
!showInsecureWarning &&
|
!showInsecureWarning;
|
||||||
isAppLogin;
|
|
||||||
|
|
||||||
const redirectToTarget = useCallback(() => {
|
const redirectToTarget = useCallback(() => {
|
||||||
if (!urlHref || hasRedirected.current) {
|
if (!urlHref || hasRedirected.current) {
|
||||||
@@ -89,10 +79,15 @@ export const ContinuePage = () => {
|
|||||||
}, [shouldAutoRedirect, redirectToTarget]);
|
}, [shouldAutoRedirect, redirectToTarget]);
|
||||||
|
|
||||||
if (!auth.authenticated) {
|
if (!auth.authenticated) {
|
||||||
return <Navigate to={`/login${recompiledParams}`} replace />;
|
return (
|
||||||
|
<Navigate
|
||||||
|
to={`/login${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`}
|
||||||
|
replace
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasValidRedirect || !isAppLogin) {
|
if (!hasValidRedirect) {
|
||||||
return <Navigate to="/logout" replace />;
|
return <Navigate to="/logout" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,11 +105,7 @@ export const ContinuePage = () => {
|
|||||||
components={{
|
components={{
|
||||||
code: <code />,
|
code: <code />,
|
||||||
}}
|
}}
|
||||||
values={{
|
values={{ cookieDomain: app.cookieDomain }}
|
||||||
cookieDomain: app.subdomainsEnabled
|
|
||||||
? `.${app.cookieDomain}`
|
|
||||||
: app.cookieDomain,
|
|
||||||
}}
|
|
||||||
shouldUnescape={true}
|
shouldUnescape={true}
|
||||||
/>
|
/>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const ErrorPage = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
const error = searchParams.get("error") || "";
|
const error = searchParams.get("error") ?? "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
|
|||||||
@@ -11,18 +11,12 @@ import { useAppContext } from "@/context/app-context";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import { useLocation } from "react-router";
|
import { useLocation } from "react-router";
|
||||||
import {
|
|
||||||
recompileScreenParams,
|
|
||||||
useScreenParams,
|
|
||||||
} from "@/lib/hooks/screen-params";
|
|
||||||
|
|
||||||
export const ForgotPasswordPage = () => {
|
export const ForgotPasswordPage = () => {
|
||||||
const { ui } = useAppContext();
|
const { ui } = useAppContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
const screenParams = useScreenParams(searchParams);
|
|
||||||
const compiledParams = recompileScreenParams(screenParams);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -43,7 +37,10 @@ export const ForgotPasswordPage = () => {
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.location.replace(`/login${compiledParams}`);
|
const eparams = searchParams.toString();
|
||||||
|
window.location.replace(
|
||||||
|
`/login${eparams.length > 0 ? `?${eparams}` : ""}`,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("backToLoginButton")}
|
{t("backToLoginButton")}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { OAuthButton } from "@/components/ui/oauth-button";
|
|||||||
import { SeperatorWithChildren } from "@/components/ui/separator";
|
import { SeperatorWithChildren } from "@/components/ui/separator";
|
||||||
import { useAppContext } from "@/context/app-context";
|
import { useAppContext } from "@/context/app-context";
|
||||||
import { useUserContext } from "@/context/user-context";
|
import { useUserContext } from "@/context/user-context";
|
||||||
|
import { useOIDCParams } from "@/lib/hooks/oidc";
|
||||||
import { LoginSchema } from "@/schemas/login-schema";
|
import { LoginSchema } from "@/schemas/login-schema";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import axios, { AxiosError } from "axios";
|
import axios, { AxiosError } from "axios";
|
||||||
@@ -25,11 +26,6 @@ import { useEffect, useId, useRef, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Navigate, useLocation } from "react-router";
|
import { Navigate, useLocation } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
|
||||||
recompileScreenParams,
|
|
||||||
useScreenParams,
|
|
||||||
} from "@/lib/hooks/screen-params";
|
|
||||||
import { useLoginFor } from "@/lib/hooks/login-for";
|
|
||||||
|
|
||||||
const iconMap: Record<string, React.ReactNode> = {
|
const iconMap: Record<string, React.ReactNode> = {
|
||||||
google: <GoogleIcon />,
|
google: <GoogleIcon />,
|
||||||
@@ -50,9 +46,7 @@ export const LoginPage = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [showRedirectButton, setShowRedirectButton] = useState(false);
|
const [showRedirectButton, setShowRedirectButton] = useState(false);
|
||||||
const [useTailscale, setUseTailscale] = useState(
|
const [useTailscale, setUseTailscale] = useState(tailscale.nodeName !== undefined);
|
||||||
tailscale.nodeName !== undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasAutoRedirectedRef = useRef(false);
|
const hasAutoRedirectedRef = useRef(false);
|
||||||
|
|
||||||
@@ -62,25 +56,17 @@ export const LoginPage = () => {
|
|||||||
const formId = useId();
|
const formId = useId();
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
const screenParams = useScreenParams(searchParams);
|
const redirectUri = searchParams.get("redirect_uri") || undefined;
|
||||||
const compiledParams = recompileScreenParams({
|
const oidcParams = useOIDCParams(searchParams);
|
||||||
...screenParams,
|
|
||||||
oidc_prompt: undefined,
|
|
||||||
});
|
|
||||||
const loginForUrl = useLoginFor({
|
|
||||||
login_for: screenParams.login_for,
|
|
||||||
compiledParams,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [isOauthAutoRedirect, setIsOauthAutoRedirect] = useState(
|
const [isOauthAutoRedirect, setIsOauthAutoRedirect] = useState(
|
||||||
providers.find((provider) => provider.id === oauth.autoRedirect) !==
|
providers.find((provider) => provider.id === oauth.autoRedirect) !==
|
||||||
undefined && screenParams.redirect_uri !== undefined,
|
undefined && redirectUri !== undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
const oauthProviders = providers.filter(
|
const oauthProviders = providers.filter(
|
||||||
(provider) => provider.id !== "local" && provider.id !== "ldap",
|
(provider) => provider.id !== "local" && provider.id !== "ldap",
|
||||||
);
|
);
|
||||||
|
|
||||||
const userAuthConfigured =
|
const userAuthConfigured =
|
||||||
providers.find(
|
providers.find(
|
||||||
(provider) => provider.id === "local" || provider.id === "ldap",
|
(provider) => provider.id === "local" || provider.id === "ldap",
|
||||||
@@ -93,7 +79,16 @@ export const LoginPage = () => {
|
|||||||
variables: oauthVariables,
|
variables: oauthVariables,
|
||||||
} = useMutation({
|
} = useMutation({
|
||||||
mutationFn: (provider: string) => {
|
mutationFn: (provider: string) => {
|
||||||
return axios.get(`/api/oauth/url/${provider}${compiledParams}`);
|
const getParams = function (): string {
|
||||||
|
if (oidcParams.isOidc) {
|
||||||
|
return `?${oidcParams.compiled}`;
|
||||||
|
}
|
||||||
|
if (redirectUri) {
|
||||||
|
return `?redirect_uri=${encodeURIComponent(redirectUri)}`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
return axios.get(`/api/oauth/url/${provider}${getParams()}`);
|
||||||
},
|
},
|
||||||
mutationKey: ["oauth"],
|
mutationKey: ["oauth"],
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
@@ -124,7 +119,13 @@ export const LoginPage = () => {
|
|||||||
mutationKey: ["login"],
|
mutationKey: ["login"],
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data.data.totpPending) {
|
if (data.data.totpPending) {
|
||||||
window.location.replace(`/totp${compiledParams}`);
|
if (oidcParams.isOidc) {
|
||||||
|
window.location.replace(`/totp?${oidcParams.compiled}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.location.replace(
|
||||||
|
`/totp${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +134,13 @@ export const LoginPage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
redirectTimer.current = window.setTimeout(() => {
|
||||||
window.location.replace(loginForUrl);
|
if (oidcParams.isOidc) {
|
||||||
|
window.location.replace(`/authorize?${oidcParams.compiled}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.location.replace(
|
||||||
|
`/continue${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`,
|
||||||
|
);
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
onError: (error: AxiosError) => {
|
onError: (error: AxiosError) => {
|
||||||
@@ -156,7 +163,13 @@ export const LoginPage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
redirectTimer.current = window.setTimeout(() => {
|
||||||
window.location.replace(loginForUrl);
|
if (oidcParams.isOidc) {
|
||||||
|
window.location.replace(`/authorize?${oidcParams.compiled}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.location.replace(
|
||||||
|
`/continue${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`,
|
||||||
|
);
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
@@ -171,8 +184,7 @@ export const LoginPage = () => {
|
|||||||
!auth.authenticated &&
|
!auth.authenticated &&
|
||||||
isOauthAutoRedirect &&
|
isOauthAutoRedirect &&
|
||||||
!hasAutoRedirectedRef.current &&
|
!hasAutoRedirectedRef.current &&
|
||||||
screenParams.redirect_uri &&
|
redirectUri !== undefined
|
||||||
screenParams.login_for
|
|
||||||
) {
|
) {
|
||||||
hasAutoRedirectedRef.current = true;
|
hasAutoRedirectedRef.current = true;
|
||||||
oauthMutate(oauth.autoRedirect);
|
oauthMutate(oauth.autoRedirect);
|
||||||
@@ -183,8 +195,7 @@ export const LoginPage = () => {
|
|||||||
hasAutoRedirectedRef,
|
hasAutoRedirectedRef,
|
||||||
oauth.autoRedirect,
|
oauth.autoRedirect,
|
||||||
isOauthAutoRedirect,
|
isOauthAutoRedirect,
|
||||||
screenParams.login_for,
|
redirectUri,
|
||||||
screenParams.redirect_uri,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -199,8 +210,21 @@ export const LoginPage = () => {
|
|||||||
};
|
};
|
||||||
}, [redirectTimer, redirectButtonTimer]);
|
}, [redirectTimer, redirectButtonTimer]);
|
||||||
|
|
||||||
if (auth.authenticated && screenParams.oidc_prompt !== "login") {
|
if (auth.authenticated && oidcParams.isOidc) {
|
||||||
return <Navigate to={loginForUrl} replace />;
|
return <Navigate to={`/authorize?${oidcParams.compiled}`} replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth.authenticated && redirectUri !== undefined) {
|
||||||
|
return (
|
||||||
|
<Navigate
|
||||||
|
to={`/continue${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`}
|
||||||
|
replace
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth.authenticated) {
|
||||||
|
return <Navigate to="/logout" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOauthAutoRedirect) {
|
if (isOauthAutoRedirect) {
|
||||||
@@ -259,7 +283,7 @@ export const LoginPage = () => {
|
|||||||
onClick={() => tailscaleMutate()}
|
onClick={() => tailscaleMutate()}
|
||||||
loading={tailscaleIsPending}
|
loading={tailscaleIsPending}
|
||||||
>
|
>
|
||||||
{t("loginTailscaleTitle")}
|
{t("loginTailscaleSubmit")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="w-full"
|
className="w-full"
|
||||||
|
|||||||
@@ -15,21 +15,12 @@ import { Navigate } from "react-router";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { type UseMutationResult } from "@tanstack/react-query";
|
import { type UseMutationResult } from "@tanstack/react-query";
|
||||||
import { type AxiosResponse } from "axios";
|
import { type AxiosResponse } from "axios";
|
||||||
import { useLocation } from "react-router";
|
|
||||||
import {
|
|
||||||
useScreenParams,
|
|
||||||
recompileScreenParams,
|
|
||||||
} from "@/lib/hooks/screen-params";
|
|
||||||
|
|
||||||
export const LogoutPage = () => {
|
export const LogoutPage = () => {
|
||||||
const { auth, oauth, tailscale } = useUserContext();
|
const { auth, oauth, tailscale } = useUserContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { search } = useLocation();
|
|
||||||
|
|
||||||
const redirectTimer = useRef<number | null>(null);
|
const redirectTimer = useRef<number | null>(null);
|
||||||
const searchParams = new URLSearchParams(search);
|
|
||||||
const screenParams = useScreenParams(searchParams);
|
|
||||||
const compiledParams = recompileScreenParams(screenParams);
|
|
||||||
|
|
||||||
const logoutMutation = useMutation({
|
const logoutMutation = useMutation({
|
||||||
mutationFn: () => axios.post("/api/user/logout"),
|
mutationFn: () => axios.post("/api/user/logout"),
|
||||||
@@ -40,7 +31,7 @@ export const LogoutPage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
redirectTimer.current = window.setTimeout(() => {
|
||||||
window.location.replace(`/login${compiledParams}`);
|
window.location.replace("/login");
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
@@ -59,7 +50,7 @@ export const LogoutPage = () => {
|
|||||||
}, [redirectTimer]);
|
}, [redirectTimer]);
|
||||||
|
|
||||||
if (!auth.authenticated) {
|
if (!auth.authenticated) {
|
||||||
return <Navigate to={`/login${compiledParams}`} replace />;
|
return <Navigate to="/login" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oauth.active) {
|
if (oauth.active) {
|
||||||
@@ -137,7 +128,7 @@ function LogoutLayout({ children, logoutMutation }: LogoutLayoutProps) {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardFooter>
|
<CardFooter>
|
||||||
<Button
|
<Button
|
||||||
className="w-full text-destructive"
|
className="w-full"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
loading={logoutMutation.isPending}
|
loading={logoutMutation.isPending}
|
||||||
onClick={() => logoutMutation.mutate()}
|
onClick={() => logoutMutation.mutate()}
|
||||||
|
|||||||
@@ -16,14 +16,10 @@ import { useEffect, useId, useRef } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Navigate, useLocation } from "react-router";
|
import { Navigate, useLocation } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import { useOIDCParams } from "@/lib/hooks/oidc";
|
||||||
recompileScreenParams,
|
|
||||||
useScreenParams,
|
|
||||||
} from "@/lib/hooks/screen-params";
|
|
||||||
import { useLoginFor } from "@/lib/hooks/login-for";
|
|
||||||
|
|
||||||
export const TotpPage = () => {
|
export const TotpPage = () => {
|
||||||
const { totp, auth } = useUserContext();
|
const { totp } = useUserContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const formId = useId();
|
const formId = useId();
|
||||||
@@ -31,12 +27,8 @@ export const TotpPage = () => {
|
|||||||
const redirectTimer = useRef<number | null>(null);
|
const redirectTimer = useRef<number | null>(null);
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
const screenParams = useScreenParams(searchParams);
|
const redirectUri = searchParams.get("redirect_uri") || undefined;
|
||||||
const compiledParams = recompileScreenParams(screenParams);
|
const oidcParams = useOIDCParams(searchParams);
|
||||||
const loginForUrl = useLoginFor({
|
|
||||||
login_for: screenParams.login_for,
|
|
||||||
compiledParams,
|
|
||||||
});
|
|
||||||
|
|
||||||
const totpMutation = useMutation({
|
const totpMutation = useMutation({
|
||||||
mutationFn: (values: TotpSchema) => axios.post("/api/user/totp", values),
|
mutationFn: (values: TotpSchema) => axios.post("/api/user/totp", values),
|
||||||
@@ -47,7 +39,14 @@ export const TotpPage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
redirectTimer.current = window.setTimeout(() => {
|
||||||
window.location.replace(loginForUrl);
|
if (oidcParams.isOidc) {
|
||||||
|
window.location.replace(`/authorize?${oidcParams.compiled}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.replace(
|
||||||
|
`/continue${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`,
|
||||||
|
);
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
@@ -66,10 +65,7 @@ export const TotpPage = () => {
|
|||||||
}, [redirectTimer]);
|
}, [redirectTimer]);
|
||||||
|
|
||||||
if (!totp.pending) {
|
if (!totp.pending) {
|
||||||
if (auth.authenticated) {
|
return <Navigate to="/" replace />;
|
||||||
return <Navigate to={loginForUrl} replace />;
|
|
||||||
}
|
|
||||||
return <Navigate to={`/login${compiledParams}`} replace />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const uiSchema = z.object({
|
|||||||
const appSchema = z.object({
|
const appSchema = z.object({
|
||||||
appUrl: z.string(),
|
appUrl: z.string(),
|
||||||
cookieDomain: z.string(),
|
cookieDomain: z.string(),
|
||||||
subdomainsEnabled: z.boolean(),
|
trustedDomains: z.array(z.string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const appContextSchema = z.object({
|
export const appContextSchema = z.object({
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const getOidcClientInfoSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
});
|
||||||
@@ -57,11 +57,6 @@ export default defineConfig({
|
|||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/robots.txt/, ""),
|
rewrite: (path) => path.replace(/^\/robots.txt/, ""),
|
||||||
},
|
},
|
||||||
"/authorize": {
|
|
||||||
target: "http://tinyauth-backend:3000/authorize",
|
|
||||||
changeOrigin: true,
|
|
||||||
rewrite: (path) => path.replace(/^\/authorize/, ""),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
allowedHosts: true,
|
allowedHosts: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type EnvEntry struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateExampleEnv() {
|
func generateExampleEnv() {
|
||||||
cfg := model.NewDefaultConfiguration(model.RuntimeEnvUnknown)
|
cfg := model.NewDefaultConfiguration()
|
||||||
entries := make([]EnvEntry, 0)
|
entries := make([]EnvEntry, 0)
|
||||||
|
|
||||||
root := reflect.TypeOf(cfg).Elem()
|
root := reflect.TypeOf(cfg).Elem()
|
||||||
@@ -21,7 +21,7 @@ type MarkdownEntry struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateMarkdown() {
|
func generateMarkdown() {
|
||||||
cfg := model.NewDefaultConfiguration(model.RuntimeEnvUnknown)
|
cfg := model.NewDefaultConfiguration()
|
||||||
entries := make([]MarkdownEntry, 0)
|
entries := make([]MarkdownEntry, 0)
|
||||||
|
|
||||||
root := reflect.TypeOf(cfg).Elem()
|
root := reflect.TypeOf(cfg).Elem()
|
||||||
@@ -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:
|
// internal/repository/<driver>/. Run via:
|
||||||
//
|
//
|
||||||
// go generate ./internal/repository/...
|
// go generate ./internal/repository/...
|
||||||
@@ -32,7 +32,7 @@ import (
|
|||||||
var storeSrc string
|
var storeSrc string
|
||||||
|
|
||||||
func main() {
|
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 {
|
if err := run(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package tinyauth
|
|
||||||
|
|
||||||
//go:generate go run github.com/tinyauthapp/tinyauth/gen/docs
|
|
||||||
@@ -1,57 +1,69 @@
|
|||||||
module github.com/tinyauthapp/tinyauth
|
module github.com/tinyauthapp/tinyauth
|
||||||
|
|
||||||
go 1.26.4
|
go 1.26.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
charm.land/huh/v2 v2.0.3
|
charm.land/huh/v2 v2.0.3
|
||||||
charm.land/lipgloss/v2 v2.0.5
|
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3
|
github.com/cenkalti/backoff/v5 v5.0.3
|
||||||
github.com/docker/docker v28.5.2+incompatible
|
github.com/docker/docker v28.5.2+incompatible
|
||||||
github.com/gin-gonic/gin v1.12.0
|
github.com/gin-gonic/gin v1.12.0
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4
|
github.com/go-jose/go-jose/v4 v4.1.4
|
||||||
github.com/go-ldap/ldap/v3 v3.4.13
|
github.com/go-ldap/ldap/v3 v3.4.13
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||||
github.com/google/go-querystring v1.2.0
|
github.com/google/go-querystring v1.2.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/jackc/pgx/v5 v5.10.0
|
|
||||||
github.com/mdp/qrterminal/v3 v3.2.1
|
github.com/mdp/qrterminal/v3 v3.2.1
|
||||||
github.com/pquerna/otp v1.5.0
|
github.com/pquerna/otp v1.5.0
|
||||||
github.com/rs/zerolog v1.35.1
|
github.com/rs/zerolog v1.35.1
|
||||||
github.com/steveiliop56/ding v0.2.0
|
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298
|
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298
|
||||||
github.com/weppos/publicsuffix-go v0.50.3
|
github.com/weppos/publicsuffix-go v0.50.3
|
||||||
go.uber.org/dig v1.19.0
|
golang.org/x/crypto v0.50.0
|
||||||
golang.org/x/crypto v0.53.0
|
|
||||||
golang.org/x/oauth2 v0.36.0
|
golang.org/x/oauth2 v0.36.0
|
||||||
golang.org/x/tools v0.47.0
|
golang.org/x/tools v0.43.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
k8s.io/apimachinery v0.36.0
|
||||||
k8s.io/apimachinery v0.36.2
|
k8s.io/client-go v0.36.0
|
||||||
k8s.io/client-go v0.36.2
|
modernc.org/sqlite v1.50.0
|
||||||
modernc.org/sqlite v1.53.0
|
tailscale.com v1.96.5
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
charm.land/bubbles/v2 v2.0.0 // indirect
|
charm.land/bubbles/v2 v2.0.0 // indirect
|
||||||
charm.land/bubbletea/v2 v2.0.2 // indirect
|
charm.land/bubbletea/v2 v2.0.2 // indirect
|
||||||
|
charm.land/lipgloss/v2 v2.0.1 // indirect
|
||||||
dario.cat/mergo v1.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/Azure/go-ntlmssp v0.1.1 // indirect
|
||||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // 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/atotto/clipboard v0.1.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.29.5 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.58 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
|
||||||
|
github.com/aws/smithy-go v1.24.0 // indirect
|
||||||
github.com/boombuler/barcode v1.0.2 // indirect
|
github.com/boombuler/barcode v1.0.2 // indirect
|
||||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/bytedance/sonic v1.15.0 // indirect
|
github.com/bytedance/sonic v1.15.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||||
github.com/catppuccin/go v0.3.0 // indirect
|
github.com/catppuccin/go v0.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
github.com/charmbracelet/colorprofile v0.4.2 // indirect
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
|
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.11.7 // indirect
|
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
||||||
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
|
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||||
@@ -60,46 +72,60 @@ require (
|
|||||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // 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 v1.0.0 // indirect
|
||||||
github.com/containerd/errdefs/pkg v0.3.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/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/distribution/reference v0.6.0 // indirect
|
||||||
github.com/docker/go-connections v0.6.0 // indirect
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12 // 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/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.4 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.30.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-json v0.10.5 // indirect
|
||||||
github.com/goccy/go-yaml v1.19.2 // 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/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/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.9.2 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.2 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.23 // indirect
|
github.com/mattn/go-runewidth v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.32 // 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/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/hashstructure/v2 v2.0.2 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||||
github.com/moby/term v0.5.2 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
@@ -108,16 +134,23 @@ require (
|
|||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // 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/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.59.1 // indirect
|
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/safchain/ethtool v0.3.0 // indirect
|
||||||
github.com/shopspring/decimal v1.4.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/cast v1.10.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
||||||
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
||||||
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
|
||||||
|
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
|
||||||
|
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
|
||||||
|
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
@@ -132,22 +165,28 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // 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/arch v0.22.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/mod v0.37.0 // indirect
|
golang.org/x/mod v0.34.0 // indirect
|
||||||
golang.org/x/net v0.56.0 // indirect
|
golang.org/x/net v0.52.0 // indirect
|
||||||
golang.org/x/sync v0.21.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.46.0 // indirect
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
golang.org/x/term v0.44.0 // indirect
|
golang.org/x/term v0.42.0 // indirect
|
||||||
golang.org/x/text v0.38.0 // indirect
|
golang.org/x/text v0.36.0 // indirect
|
||||||
golang.org/x/time v0.14.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
|
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gotest.tools/v3 v3.5.2 // 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/klog/v2 v2.140.0 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
|
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
|
||||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
|
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
|
||||||
modernc.org/libc v1.73.4 // indirect
|
modernc.org/libc v1.72.0 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
rsc.io/qr v0.2.0 // indirect
|
rsc.io/qr v0.2.0 // indirect
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
|
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 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
|
||||||
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
|
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
|
||||||
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
|
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
|
||||||
charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
|
charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
|
||||||
charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU=
|
charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU=
|
||||||
charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc=
|
charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc=
|
||||||
charm.land/lipgloss/v2 v2.0.5 h1:kbNxgeeUOYv5J0YdpxFjfvf3dFvqH8Aci4zB6xqFtrY=
|
charm.land/lipgloss/v2 v2.0.1 h1:6Xzrn49+Py1Um5q/wZG1gWgER2+7dUyZ9XMEufqPSys=
|
||||||
charm.land/lipgloss/v2 v2.0.5/go.mod h1:9oqhxt4yxIMe6q5A4kHr44DremZk7J9UNh74GlWa5nc=
|
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 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
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 h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
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=
|
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/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 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
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 h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
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 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
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 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
|
||||||
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
|
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=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
@@ -45,12 +85,12 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x
|
|||||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
|
||||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
|
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
|
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
|
||||||
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
|
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
||||||
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
|
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||||
github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=
|
github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=
|
||||||
github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk=
|
github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk=
|
||||||
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
|
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
|
||||||
@@ -69,32 +109,50 @@ 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/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 h1:eGSitii4suhzrISYH50ZfufV3v085BXQwIytcOdFSsw=
|
||||||
github.com/charmbracelet/x/xpty v0.1.3/go.mod h1:poPYpWuLDBFCKmKLDnhBp51ATa0ooD8FhypRwEFtH3Y=
|
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 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
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 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
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 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
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 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
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 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
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 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
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 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
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.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.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 h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
|
||||||
github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=
|
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/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 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
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 h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
@@ -109,14 +167,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/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 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
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 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
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 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
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 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-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 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
|
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-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
@@ -124,6 +188,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/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 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
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 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
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=
|
github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU=
|
||||||
@@ -138,14 +204,20 @@ 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/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 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||||
|
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 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
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 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
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-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 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
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=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
@@ -153,7 +225,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-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 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
||||||
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
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/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 h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
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=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -162,18 +238,27 @@ 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/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 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
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 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
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 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
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 h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw=
|
||||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
|
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
|
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||||
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||||
@@ -188,12 +273,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/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 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
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 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||||
|
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
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 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@@ -202,22 +299,34 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
|
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
|
||||||
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
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 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
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 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
|
||||||
github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU=
|
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 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
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 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
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=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
@@ -244,23 +353,37 @@ 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/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 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
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 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
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 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
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 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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.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 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
|
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
|
||||||
|
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
||||||
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
|
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||||
|
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 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic=
|
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||||
github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
@@ -269,16 +392,16 @@ 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/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 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
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 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/steveiliop56/ding v0.2.0 h1:m/Fj99wBpVVLHlpqb2RDJkWubOc5cWJ11ZYCHya3Sk0=
|
|
||||||
github.com/steveiliop56/ding v0.2.0/go.mod h1:bE2u2XH7CjhPzbb/0Ems+D8YZlf2Ae+eKhj00UR1iAY=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -292,12 +415,40 @@ 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.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 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
||||||
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||||
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||||
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
||||||
|
github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 h1:SRL6irQkKGQKKLzvQP/ke/2ZuB7Py5+XuqtOgSj+iMM=
|
||||||
|
github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
|
||||||
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
|
||||||
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
|
||||||
|
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
|
||||||
|
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
|
||||||
|
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=
|
||||||
|
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-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw=
|
||||||
|
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
||||||
|
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
|
||||||
|
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
|
||||||
|
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||||
|
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 h1:EYSb5jv8ZL/0/NVFZtY7Ejplk0QG5+3lrdL3mSrjFZQ=
|
||||||
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298/go.mod h1:TlUDoCF66hMqFZqoBym9bUdJ0bKAWYMir6hLJeYN5z0=
|
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 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/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 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
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 h1:eT5dcjHQcVDNc0igpFEsGHKIip30feuB2zuuI9eJxiE=
|
||||||
github.com/weppos/publicsuffix-go v0.50.3/go.mod h1:/rOa781xBykZhHK/I3QeHo92qdDKVmKZKF7s8qAEM/4=
|
github.com/weppos/publicsuffix-go v0.50.3/go.mod h1:/rOa781xBykZhHK/I3QeHo92qdDKVmKZKF7s8qAEM/4=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
@@ -326,39 +477,51 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09
|
|||||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||||
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
|
|
||||||
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
|
||||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
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/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 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
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 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=
|
||||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
||||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
||||||
|
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||||
|
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||||
|
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||||
|
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||||
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
|
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||||
|
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 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 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
||||||
@@ -380,42 +543,48 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY=
|
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 h1:Zy8IV/+FMLxy6j6p87vk/vQGKcdnbprwjTxc8UiUtsA=
|
||||||
k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg=
|
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
|
||||||
k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ=
|
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 h1:5SXjd4ET5dYijLaf0O3aOenC0Z4ZafIWSpjUzsQaNho=
|
||||||
k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4=
|
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ=
|
||||||
k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI=
|
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||||
k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0=
|
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||||
|
k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80=
|
||||||
|
k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34=
|
||||||
|
k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ=
|
||||||
|
k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc=
|
||||||
|
k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c=
|
||||||
|
k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y=
|
||||||
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
|
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
|
||||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
|
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
|
||||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
||||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||||
modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c=
|
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
|
||||||
modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
|
||||||
modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws=
|
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
|
||||||
modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE=
|
modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
|
||||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc=
|
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||||
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA=
|
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
|
||||||
modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8=
|
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M=
|
modernc.org/sqlite v1.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM=
|
||||||
modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s=
|
modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
@@ -430,3 +599,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/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 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
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.96.5 h1:gNkfA/KSZAl6jCH9cj8urq00HRWItDDTtGsyATI89jA=
|
||||||
|
tailscale.com v1.96.5/go.mod h1:/3lnZBYb2UEwnN0MNu2SDXUtT06AGd5k0s+OWx3WmcY=
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS "oidc_sessions";
|
|
||||||
|
|
||||||
CREATE TABLE "oidc_codes" (
|
|
||||||
"sub" TEXT NOT NULL UNIQUE,
|
|
||||||
"code_hash" TEXT NOT NULL PRIMARY KEY,
|
|
||||||
"scope" TEXT NOT NULL,
|
|
||||||
"redirect_uri" TEXT NOT NULL,
|
|
||||||
"client_id" TEXT NOT NULL,
|
|
||||||
"expires_at" BIGINT NOT NULL,
|
|
||||||
"nonce" TEXT NOT NULL DEFAULT '',
|
|
||||||
"code_challenge" TEXT NOT NULL DEFAULT ''
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "oidc_tokens" (
|
|
||||||
"sub" TEXT NOT NULL UNIQUE,
|
|
||||||
"access_token_hash" TEXT NOT NULL PRIMARY KEY,
|
|
||||||
"refresh_token_hash" TEXT NOT NULL,
|
|
||||||
"code_hash" TEXT NOT NULL,
|
|
||||||
"scope" TEXT NOT NULL,
|
|
||||||
"client_id" TEXT NOT NULL,
|
|
||||||
"token_expires_at" BIGINT NOT NULL,
|
|
||||||
"refresh_token_expires_at" BIGINT NOT NULL,
|
|
||||||
"nonce" TEXT NOT NULL DEFAULT ''
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "oidc_userinfo" (
|
|
||||||
"sub" TEXT NOT NULL PRIMARY KEY,
|
|
||||||
"name" TEXT NOT NULL,
|
|
||||||
"preferred_username" TEXT NOT NULL,
|
|
||||||
"email" TEXT NOT NULL,
|
|
||||||
"groups" TEXT NOT NULL,
|
|
||||||
"updated_at" BIGINT NOT NULL,
|
|
||||||
"given_name" TEXT NOT NULL,
|
|
||||||
"family_name" TEXT NOT NULL,
|
|
||||||
"middle_name" TEXT NOT NULL,
|
|
||||||
"nickname" TEXT NOT NULL,
|
|
||||||
"profile" TEXT NOT NULL,
|
|
||||||
"picture" TEXT NOT NULL,
|
|
||||||
"website" TEXT NOT NULL,
|
|
||||||
"gender" TEXT NOT NULL,
|
|
||||||
"birthdate" TEXT NOT NULL,
|
|
||||||
"zoneinfo" TEXT NOT NULL,
|
|
||||||
"locale" TEXT NOT NULL,
|
|
||||||
"phone_number" TEXT NOT NULL,
|
|
||||||
"address" TEXT NOT NULL
|
|
||||||
);
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
This migration will nuke the entire setup of OIDC sessions and merge everything
|
|
||||||
into one table.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Drop all the old tables. Yes, we will log out all OIDC users, but not really a big deal
|
|
||||||
*/
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS "oidc_tokens";
|
|
||||||
DROP TABLE IF EXISTS "oidc_userinfo";
|
|
||||||
DROP TABLE IF EXISTS "oidc_codes";
|
|
||||||
|
|
||||||
/*
|
|
||||||
Create a new simple OIDC sessions table that will hold tokens + userinfo.
|
|
||||||
*/
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "oidc_sessions" (
|
|
||||||
"sub" TEXT NOT NULL UNIQUE PRIMARY KEY,
|
|
||||||
"access_token_hash" TEXT NOT NULL UNIQUE,
|
|
||||||
"refresh_token_hash" TEXT NOT NULL UNIQUE,
|
|
||||||
"scope" TEXT NOT NULL,
|
|
||||||
"client_id" TEXT NOT NULL,
|
|
||||||
"token_expires_at" BIGINT NOT NULL,
|
|
||||||
"refresh_token_expires_at" BIGINT NOT NULL,
|
|
||||||
"nonce" TEXT NOT NULL DEFAULT '',
|
|
||||||
"userinfo_json" TEXT NOT NULL
|
|
||||||
);
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS "oidc_sessions";
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "oidc_codes" (
|
|
||||||
"sub" TEXT NOT NULL UNIQUE,
|
|
||||||
"code_hash" TEXT NOT NULL PRIMARY KEY UNIQUE,
|
|
||||||
"scope" TEXT NOT NULL,
|
|
||||||
"redirect_uri" TEXT NOT NULL,
|
|
||||||
"client_id" TEXT NOT NULL,
|
|
||||||
"expires_at" INTEGER NOT NULL,
|
|
||||||
"nonce" TEXT DEFAULT "",
|
|
||||||
"code_challenge" TEXT DEFAULT ""
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "oidc_tokens" (
|
|
||||||
"sub" TEXT NOT NULL UNIQUE,
|
|
||||||
"access_token_hash" TEXT NOT NULL PRIMARY KEY UNIQUE,
|
|
||||||
"refresh_token_hash" TEXT NOT NULL,
|
|
||||||
"code_hash" TEXT NOT NULL,
|
|
||||||
"scope" TEXT NOT NULL,
|
|
||||||
"client_id" TEXT NOT NULL,
|
|
||||||
"token_expires_at" INTEGER NOT NULL,
|
|
||||||
"refresh_token_expires_at" INTEGER NOT NULL,
|
|
||||||
"nonce" TEXT DEFAULT ""
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "oidc_userinfo" (
|
|
||||||
"sub" TEXT NOT NULL UNIQUE PRIMARY KEY,
|
|
||||||
"name" TEXT NOT NULL,
|
|
||||||
"preferred_username" TEXT NOT NULL,
|
|
||||||
"email" TEXT NOT NULL,
|
|
||||||
"groups" TEXT NOT NULL,
|
|
||||||
"updated_at" INTEGER NOT NULL,
|
|
||||||
"given_name" TEXT NOT NULL,
|
|
||||||
"family_name" TEXT NOT NULL,
|
|
||||||
"middle_name" TEXT NOT NULL,
|
|
||||||
"nickname" TEXT NOT NULL,
|
|
||||||
"profile" TEXT NOT NULL,
|
|
||||||
"picture" TEXT NOT NULL,
|
|
||||||
"website" TEXT NOT NULL,
|
|
||||||
"gender" TEXT NOT NULL,
|
|
||||||
"birthdate" TEXT NOT NULL,
|
|
||||||
"zoneinfo" TEXT NOT NULL,
|
|
||||||
"locale" TEXT NOT NULL,
|
|
||||||
"phone_number" TEXT NOT NULL,
|
|
||||||
"address" TEXT NOT NULL
|
|
||||||
);
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
This migration will nuke the entire setup of OIDC sessions and merge everything
|
|
||||||
into one table.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Drop all the old tables. Yes, we will log out all OIDC users, but not really a big deal
|
|
||||||
*/
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS "oidc_tokens";
|
|
||||||
DROP TABLE IF EXISTS "oidc_userinfo";
|
|
||||||
DROP TABLE IF EXISTS "oidc_codes";
|
|
||||||
|
|
||||||
/*
|
|
||||||
Create a new simple OIDC sessions table that will hold tokens + userinfo.
|
|
||||||
*/
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "oidc_sessions" (
|
|
||||||
"sub" TEXT NOT NULL UNIQUE PRIMARY KEY,
|
|
||||||
"access_token_hash" TEXT NOT NULL UNIQUE,
|
|
||||||
"refresh_token_hash" TEXT NOT NULL UNIQUE,
|
|
||||||
"scope" TEXT NOT NULL,
|
|
||||||
"client_id" TEXT NOT NULL,
|
|
||||||
"token_expires_at" INTEGER NOT NULL,
|
|
||||||
"refresh_token_expires_at" INTEGER NOT NULL,
|
|
||||||
"nonce" TEXT DEFAULT "",
|
|
||||||
"userinfo_json" TEXT NOT NULL
|
|
||||||
);
|
|
||||||
@@ -11,15 +11,13 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"slices"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/steveiliop56/ding"
|
|
||||||
"go.uber.org/dig"
|
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||||
@@ -28,12 +26,6 @@ import (
|
|||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Shutdown order for go routines
|
|
||||||
// 1. Janitor routines (e.g. database cleanup, heartbeat) - ding.RingMinor
|
|
||||||
// 2. HTTP server listeners - ding.RingNormal
|
|
||||||
// 3. Networking layers, user and label providers (e.g. ailscale service, kubernetes service) - ding.RingMajor
|
|
||||||
// 4. Database connection - ding.RingCritical
|
|
||||||
|
|
||||||
type Services struct {
|
type Services struct {
|
||||||
accessControlService *service.AccessControlsService
|
accessControlService *service.AccessControlsService
|
||||||
authService *service.AuthService
|
authService *service.AuthService
|
||||||
@@ -56,8 +48,8 @@ type BootstrapApp struct {
|
|||||||
queries repository.Store
|
queries repository.Store
|
||||||
router *gin.Engine
|
router *gin.Engine
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
ding *ding.Ding
|
wg sync.WaitGroup
|
||||||
dig *dig.Container
|
listeners []Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBootstrapApp(config model.Config) *BootstrapApp {
|
func NewBootstrapApp(config model.Config) *BootstrapApp {
|
||||||
@@ -72,14 +64,6 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
app.ctx = ctx
|
app.ctx = ctx
|
||||||
app.cancel = cancel
|
app.cancel = cancel
|
||||||
|
|
||||||
// create the dig container
|
|
||||||
c := dig.New()
|
|
||||||
app.dig = c
|
|
||||||
|
|
||||||
// create a ding instance
|
|
||||||
dg := ding.New(ctx)
|
|
||||||
app.ding = dg
|
|
||||||
|
|
||||||
// setup logger
|
// setup logger
|
||||||
log := logger.NewLogger().WithConfig(app.config.Log)
|
log := logger.NewLogger().WithConfig(app.config.Log)
|
||||||
log.Init()
|
log.Init()
|
||||||
@@ -98,7 +82,8 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
return fmt.Errorf("failed to parse app url: %w", err)
|
return fmt.Errorf("failed to parse app url: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.runtime.AppURL = strings.ToLower(appUrl.Scheme + "://" + appUrl.Host)
|
app.runtime.AppURL = appUrl.Scheme + "://" + appUrl.Host
|
||||||
|
app.runtime.TrustedDomains = append(app.runtime.TrustedDomains, app.runtime.AppURL)
|
||||||
|
|
||||||
// validate session config
|
// validate session config
|
||||||
if app.config.Auth.SessionMaxLifetime != 0 && app.config.Auth.SessionMaxLifetime < app.config.Auth.SessionExpiry {
|
if app.config.Auth.SessionMaxLifetime != 0 && app.config.Auth.SessionMaxLifetime < app.config.Auth.SessionExpiry {
|
||||||
@@ -112,12 +97,7 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
return fmt.Errorf("failed to load users: %w", err)
|
return fmt.Errorf("failed to load users: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if users != nil {
|
|
||||||
app.runtime.LocalUsers = *users
|
app.runtime.LocalUsers = *users
|
||||||
} else {
|
|
||||||
log.App.Debug().Msg("No local users found, local authentication will not be available")
|
|
||||||
app.runtime.LocalUsers = []model.LocalUser{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// load oauth whitelist
|
// load oauth whitelist
|
||||||
oauthWhitelist, err := utils.GetStringList(app.config.OAuth.Whitelist, app.config.OAuth.WhitelistFile)
|
oauthWhitelist, err := utils.GetStringList(app.config.OAuth.Whitelist, app.config.OAuth.WhitelistFile)
|
||||||
@@ -132,21 +112,19 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
app.runtime.OAuthProviders = app.config.OAuth.Providers
|
app.runtime.OAuthProviders = app.config.OAuth.Providers
|
||||||
|
|
||||||
for id, provider := range app.runtime.OAuthProviders {
|
for id, provider := range app.runtime.OAuthProviders {
|
||||||
if slices.Contains(model.ReservedProviderNames, id) {
|
|
||||||
return fmt.Errorf("provider id %s is reserved and cannot be used", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
providerWhitelist, err := utils.GetStringList(provider.Whitelist, provider.WhitelistFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to load oauth whitelist for provider %s: %w", id, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
provider.Whitelist = providerWhitelist
|
|
||||||
|
|
||||||
secret := utils.GetSecret(provider.ClientSecret, provider.ClientSecretFile)
|
secret := utils.GetSecret(provider.ClientSecret, provider.ClientSecretFile)
|
||||||
provider.ClientSecret = secret
|
provider.ClientSecret = secret
|
||||||
provider.ClientSecretFile = ""
|
provider.ClientSecretFile = ""
|
||||||
|
|
||||||
|
if provider.RedirectURL == "" {
|
||||||
|
provider.RedirectURL = app.runtime.AppURL + "/api/oauth/callback/" + id
|
||||||
|
}
|
||||||
|
|
||||||
|
app.runtime.OAuthProviders[id] = provider
|
||||||
|
}
|
||||||
|
|
||||||
|
// set presets for built-in providers
|
||||||
|
for id, provider := range app.runtime.OAuthProviders {
|
||||||
if provider.Name == "" {
|
if provider.Name == "" {
|
||||||
if name, ok := model.OverrideProviders[id]; ok {
|
if name, ok := model.OverrideProviders[id]; ok {
|
||||||
provider.Name = name
|
provider.Name = name
|
||||||
@@ -154,16 +132,24 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
provider.Name = utils.Capitalize(id)
|
provider.Name = utils.Capitalize(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.runtime.OAuthProviders[id] = provider
|
app.runtime.OAuthProviders[id] = provider
|
||||||
}
|
}
|
||||||
|
|
||||||
// cookie domain
|
// setup oidc clients
|
||||||
if !app.config.Auth.SubdomainsEnabled {
|
for id, client := range app.config.OIDC.Clients {
|
||||||
app.log.App.Warn().Msg("Subdomains are disabled, cookies will be set for the current domain only")
|
client.ID = id
|
||||||
|
app.runtime.OIDCClients = append(app.runtime.OIDCClients, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
cookieDomain, err := utils.GetCookieDomain(app.runtime.AppURL, app.config.Auth.SubdomainsEnabled)
|
// cookie domain
|
||||||
|
cookieDomainResolver := utils.GetCookieDomain
|
||||||
|
|
||||||
|
if !app.config.Auth.SubdomainsEnabled {
|
||||||
|
app.log.App.Warn().Msg("Subdomains are disabled, using standalone cookie domain resolver which will not work with subdomains")
|
||||||
|
cookieDomainResolver = utils.GetStandaloneCookieDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieDomain, err := cookieDomainResolver(app.runtime.AppURL)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get cookie domain: %w", err)
|
return fmt.Errorf("failed to get cookie domain: %w", err)
|
||||||
@@ -177,7 +163,8 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
cookieId := strings.Split(app.runtime.UUID, "-")[0] // first 8 characters of the uuid should be good enough
|
cookieId := strings.Split(app.runtime.UUID, "-")[0] // first 8 characters of the uuid should be good enough
|
||||||
|
|
||||||
app.runtime.SessionCookieName = fmt.Sprintf("%s-%s", model.SessionCookieName, cookieId)
|
app.runtime.SessionCookieName = fmt.Sprintf("%s-%s", model.SessionCookieName, cookieId)
|
||||||
app.runtime.ScopeCookieName = fmt.Sprintf("%s-%s", model.OIDCScopeCookieName, cookieId)
|
app.runtime.CSRFCookieName = fmt.Sprintf("%s-%s", model.CSRFCookieName, cookieId)
|
||||||
|
app.runtime.RedirectCookieName = fmt.Sprintf("%s-%s", model.RedirectCookieName, cookieId)
|
||||||
app.runtime.OAuthSessionCookieName = fmt.Sprintf("%s-%s", model.OAuthSessionCookieName, cookieId)
|
app.runtime.OAuthSessionCookieName = fmt.Sprintf("%s-%s", model.OAuthSessionCookieName, cookieId)
|
||||||
|
|
||||||
// database
|
// database
|
||||||
@@ -187,48 +174,19 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
return fmt.Errorf("failed to setup database: %w", err)
|
return fmt.Errorf("failed to setup database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.ding.Go(func(ctx context.Context) {
|
// after this point, we start initializing dependencies so it's a good time to setup a defer
|
||||||
<-ctx.Done()
|
// to ensure that resources are cleaned up properly in case of an error during initialization
|
||||||
app.log.App.Debug().Msg("Shutting down database connection")
|
defer func() {
|
||||||
if app.db == nil {
|
app.cancel()
|
||||||
// using memory store, no db instance
|
app.wg.Wait()
|
||||||
return
|
if app.db != nil {
|
||||||
|
app.db.Close()
|
||||||
}
|
}
|
||||||
if err := app.db.Close(); err != nil {
|
}()
|
||||||
app.log.App.Error().Err(err).Msg("Failed to close database connection")
|
|
||||||
}
|
|
||||||
}, ding.RingCritical)
|
|
||||||
|
|
||||||
// store
|
// store
|
||||||
app.queries = store
|
app.queries = store
|
||||||
|
|
||||||
// provide basic utilities to container
|
|
||||||
type utilityProvider struct {
|
|
||||||
dig.Out
|
|
||||||
|
|
||||||
Log *logger.Logger
|
|
||||||
Config *model.Config
|
|
||||||
Runtime *model.RuntimeConfig
|
|
||||||
Ding *ding.Ding
|
|
||||||
Ctx context.Context
|
|
||||||
Queries repository.Store
|
|
||||||
}
|
|
||||||
|
|
||||||
err = app.dig.Provide(func() utilityProvider {
|
|
||||||
return utilityProvider{
|
|
||||||
Log: app.log,
|
|
||||||
Config: &app.config,
|
|
||||||
Runtime: &app.runtime,
|
|
||||||
Ding: app.ding,
|
|
||||||
Ctx: app.ctx,
|
|
||||||
Queries: app.queries,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to provide utilities to container: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// services
|
// services
|
||||||
err = app.setupServices()
|
err = app.setupServices()
|
||||||
|
|
||||||
@@ -277,22 +235,9 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
|
|
||||||
app.runtime.ConfiguredProviders = configuredProviders
|
app.runtime.ConfiguredProviders = configuredProviders
|
||||||
|
|
||||||
// force an update of the redirect urls for all oauth providers, if they are empty
|
// throw in tailscale if it's configured just before setting up the controllers
|
||||||
services := app.services.oauthBrokerService.GetConfiguredServices()
|
if app.services.tailscaleService != nil {
|
||||||
|
app.runtime.TrustedDomains = append(app.runtime.TrustedDomains, "https://"+app.services.tailscaleService.GetHostname())
|
||||||
for _, service := range services {
|
|
||||||
oauthService, ok := app.services.oauthBrokerService.GetService(service)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("failed to get oauth service for provider %s", service)
|
|
||||||
}
|
|
||||||
|
|
||||||
providerConfig := oauthService.GetConfig()
|
|
||||||
|
|
||||||
if providerConfig.RedirectURL == "" {
|
|
||||||
providerConfig.RedirectURL = app.runtime.AppURL + "/api/oauth/callback/" + service
|
|
||||||
oauthService.UpdateConfig(providerConfig)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup router
|
// setup router
|
||||||
@@ -304,33 +249,32 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
|
|
||||||
// start db cleanup routine
|
// start db cleanup routine
|
||||||
app.log.App.Debug().Msg("Starting database cleanup routine")
|
app.log.App.Debug().Msg("Starting database cleanup routine")
|
||||||
app.ding.Go(app.dbCleanupRoutine, ding.RingMinor)
|
app.wg.Go(app.dbCleanupRoutine)
|
||||||
|
|
||||||
// if analytics are not disabled, start heartbeat
|
// if analytics are not disabled, start heartbeat
|
||||||
if app.config.Analytics.Enabled {
|
if app.config.Analytics.Enabled {
|
||||||
app.log.App.Debug().Msg("Starting heartbeat routine")
|
app.log.App.Debug().Msg("Starting heartbeat routine")
|
||||||
app.ding.Go(app.heartbeatRoutine, ding.RingMinor)
|
app.wg.Go(app.heartbeatRoutine)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get listener
|
// setup listeners
|
||||||
listenerFunc, err := app.getListenerFunc()
|
app.listeners = app.calculateListenerPolicy()
|
||||||
|
|
||||||
|
if app.config.Server.ConcurrentListenersEnabled {
|
||||||
|
app.log.App.Info().Msg("Concurrent listeners enabled, will run on all available listeners")
|
||||||
|
}
|
||||||
|
|
||||||
|
// run listeners
|
||||||
|
lec, err := app.runListeners()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get listener function: %w", err)
|
return fmt.Errorf("failed to run listeners: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// run listener
|
|
||||||
lec := make(chan error, 1)
|
|
||||||
|
|
||||||
app.ding.Go(func(ctx context.Context) {
|
|
||||||
lec <- listenerFunc(ctx)
|
|
||||||
}, ding.RingNormal)
|
|
||||||
|
|
||||||
// monitor cancellation and server errors
|
// monitor cancellation and server errors
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-app.ctx.Done():
|
case <-app.ctx.Done():
|
||||||
app.ding.Wait()
|
|
||||||
app.log.App.Info().Msg("Oh, it's time for me to go, bye!")
|
app.log.App.Info().Msg("Oh, it's time for me to go, bye!")
|
||||||
return nil
|
return nil
|
||||||
case err := <-lec:
|
case err := <-lec:
|
||||||
@@ -341,7 +285,7 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) heartbeatRoutine(ctx context.Context) {
|
func (app *BootstrapApp) heartbeatRoutine() {
|
||||||
ticker := time.NewTicker(time.Duration(12) * time.Hour)
|
ticker := time.NewTicker(time.Duration(12) * time.Hour)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@@ -394,7 +338,7 @@ func (app *BootstrapApp) heartbeatRoutine(ctx context.Context) {
|
|||||||
if res.StatusCode != 200 && res.StatusCode != 201 {
|
if res.StatusCode != 200 && res.StatusCode != 201 {
|
||||||
app.log.App.Debug().Str("status", res.Status).Msg("Heartbeat returned non-200/201 status")
|
app.log.App.Debug().Str("status", res.Status).Msg("Heartbeat returned non-200/201 status")
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-app.ctx.Done():
|
||||||
app.log.App.Debug().Msg("Stopping heartbeat routine")
|
app.log.App.Debug().Msg("Stopping heartbeat routine")
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
return
|
return
|
||||||
@@ -402,7 +346,7 @@ func (app *BootstrapApp) heartbeatRoutine(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) dbCleanupRoutine(ctx context.Context) {
|
func (app *BootstrapApp) dbCleanupRoutine() {
|
||||||
ticker := time.NewTicker(time.Duration(30) * time.Minute)
|
ticker := time.NewTicker(time.Duration(30) * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@@ -411,14 +355,14 @@ func (app *BootstrapApp) dbCleanupRoutine(ctx context.Context) {
|
|||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
app.log.App.Debug().Msg("Running database cleanup")
|
app.log.App.Debug().Msg("Running database cleanup")
|
||||||
|
|
||||||
err := app.queries.DeleteExpiredSessions(ctx, time.Now().Unix())
|
err := app.queries.DeleteExpiredSessions(app.ctx, time.Now().Unix())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.log.App.Error().Err(err).Msg("Failed to delete expired sessions")
|
app.log.App.Error().Err(err).Msg("Failed to delete expired sessions")
|
||||||
}
|
}
|
||||||
|
|
||||||
app.log.App.Debug().Msg("Database cleanup completed")
|
app.log.App.Debug().Msg("Database cleanup completed")
|
||||||
case <-ctx.Done():
|
case <-app.ctx.Done():
|
||||||
app.log.App.Debug().Msg("Stopping database cleanup routine")
|
app.log.App.Debug().Msg("Stopping database cleanup routine")
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -12,11 +12,18 @@ import (
|
|||||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/middleware"
|
"github.com/tinyauthapp/tinyauth/internal/middleware"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"go.uber.org/dig"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Listener int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ListenerHTTP Listener = iota
|
||||||
|
ListenerUnix
|
||||||
|
ListenerTailscale
|
||||||
|
)
|
||||||
|
|
||||||
func (app *BootstrapApp) setupRouter() error {
|
func (app *BootstrapApp) setupRouter() error {
|
||||||
// we don't want gin debug mode
|
// we don't want gin debug mode
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
@@ -32,114 +39,109 @@ func (app *BootstrapApp) setupRouter() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
middlewareProvideFor := []any{
|
contextMiddleware := middleware.NewContextMiddleware(app.log, app.runtime, app.services.authService, app.services.oauthBrokerService, app.services.tailscaleService)
|
||||||
middleware.NewContextMiddleware,
|
engine.Use(contextMiddleware.Middleware())
|
||||||
middleware.NewUIMiddleware,
|
|
||||||
middleware.NewZerologMiddleware,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, provider := range middlewareProvideFor {
|
uiMiddleware, err := middleware.NewUIMiddleware()
|
||||||
err := app.dig.Provide(provider)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to provide middleware: %w", err)
|
return fmt.Errorf("failed to initialize UI middleware: %w", err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type middlewareInput struct {
|
engine.Use(uiMiddleware.Middleware())
|
||||||
dig.In
|
|
||||||
|
|
||||||
ContextMiddleware *middleware.ContextMiddleware
|
zerologMiddleware := middleware.NewZerologMiddleware(app.log)
|
||||||
UIMiddleware *middleware.UIMiddleware
|
|
||||||
ZerologMiddleware *middleware.ZerologMiddleware
|
|
||||||
}
|
|
||||||
|
|
||||||
err := app.dig.Invoke(func(mi middlewareInput) {
|
engine.Use(zerologMiddleware.Middleware())
|
||||||
engine.Use(mi.ContextMiddleware.Middleware())
|
|
||||||
engine.Use(mi.UIMiddleware.Middleware())
|
|
||||||
engine.Use(mi.ZerologMiddleware.Middleware())
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
apiRouter := engine.Group("/api")
|
||||||
return fmt.Errorf("failed to invoke middleware: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = app.dig.Provide(func() *gin.RouterGroup {
|
controller.NewContextController(app.log, app.config, app.runtime, apiRouter)
|
||||||
return &engine.RouterGroup
|
controller.NewOAuthController(app.log, app.config, app.runtime, apiRouter, app.services.authService)
|
||||||
}, dig.Name("mainRouterGroup"))
|
controller.NewOIDCController(app.log, app.services.oidcService, app.runtime, apiRouter)
|
||||||
|
controller.NewProxyController(app.log, app.runtime, apiRouter, app.services.accessControlService, app.services.authService, app.services.policyEngine)
|
||||||
if err != nil {
|
controller.NewUserController(app.log, app.runtime, apiRouter, app.services.authService)
|
||||||
return fmt.Errorf("failed to provide main router group: %w", err)
|
controller.NewResourcesController(app.config, &engine.RouterGroup)
|
||||||
}
|
controller.NewHealthController(apiRouter)
|
||||||
|
controller.NewWellKnownController(app.services.oidcService, &engine.RouterGroup)
|
||||||
err = app.dig.Provide(func() *gin.RouterGroup {
|
|
||||||
return engine.Group("/api")
|
|
||||||
}, dig.Name("apiRouterGroup"))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to provide api router group: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
controllerProvideFor := []any{
|
|
||||||
controller.NewContextController,
|
|
||||||
controller.NewOAuthController,
|
|
||||||
controller.NewOIDCController,
|
|
||||||
controller.NewProxyController,
|
|
||||||
controller.NewUserController,
|
|
||||||
controller.NewResourcesController,
|
|
||||||
controller.NewHealthController,
|
|
||||||
controller.NewWellKnownController,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, provider := range controllerProvideFor {
|
|
||||||
err := app.dig.Provide(provider)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to provide controller: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type controllerInput struct {
|
|
||||||
dig.In
|
|
||||||
|
|
||||||
ContextController *controller.ContextController
|
|
||||||
OAuthController *controller.OAuthController
|
|
||||||
OIDCController *controller.OIDCController
|
|
||||||
ProxyController *controller.ProxyController
|
|
||||||
UserController *controller.UserController
|
|
||||||
ResourcesController *controller.ResourcesController
|
|
||||||
HealthController *controller.HealthController
|
|
||||||
WellKnownController *controller.WellKnownController
|
|
||||||
}
|
|
||||||
|
|
||||||
// force dig to build all controllers and register their routes
|
|
||||||
err = app.dig.Invoke(func(ci controllerInput) error {
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to invoke controllers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.router = engine
|
app.router = engine
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Top down
|
func (app *BootstrapApp) runListeners() (chan error, error) {
|
||||||
// 1. Unix socket (if server.socketPath)
|
// lec -> listener error channel
|
||||||
// 2. HTTP - default
|
lec := make(chan error, len(app.listeners))
|
||||||
func (app *BootstrapApp) getListenerFunc() (func(ctx context.Context) error, error) {
|
|
||||||
|
for _, listenerType := range app.listeners {
|
||||||
|
listenerFunc, err := app.listenerFromType(listenerType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get listener function: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.wg.Go(func() {
|
||||||
|
lec <- listenerFunc()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return lec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The way we calculate listeners is as follows:
|
||||||
|
// If concurrent listeners are disabled, we pick the first available listener, so:
|
||||||
|
// 1. If tailscale is enabled, we use tailscale
|
||||||
|
// 2. If socket path is configured, we use unix socket
|
||||||
|
// 3. Finally if none is configured we use http
|
||||||
|
// If concurrent listeners are enabled, we add all available listeners in the following order
|
||||||
|
func (app *BootstrapApp) calculateListenerPolicy() []Listener {
|
||||||
|
l := []Listener{}
|
||||||
|
|
||||||
|
if !app.config.Server.ConcurrentListenersEnabled {
|
||||||
|
if app.services.tailscaleService != nil {
|
||||||
|
l = append(l, ListenerTailscale)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
if app.config.Server.SocketPath != "" {
|
if app.config.Server.SocketPath != "" {
|
||||||
return app.serveUnix, nil
|
l = append(l, ListenerUnix)
|
||||||
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l = append(l, ListenerHTTP)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
if app.config.Server.SocketPath != "" {
|
||||||
|
l = append(l, ListenerUnix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if app.services.tailscaleService != nil {
|
||||||
|
l = append(l, ListenerTailscale)
|
||||||
|
}
|
||||||
|
|
||||||
|
l = append(l, ListenerHTTP)
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *BootstrapApp) listenerFromType(listenerType Listener) (func() error, error) {
|
||||||
|
switch listenerType {
|
||||||
|
case ListenerHTTP:
|
||||||
return app.serveHTTP, nil
|
return app.serveHTTP, nil
|
||||||
|
case ListenerUnix:
|
||||||
|
return app.serveUnix, nil
|
||||||
|
case ListenerTailscale:
|
||||||
|
return app.serveTailscale, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid listener type: %d", listenerType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) serveHTTP(ctx context.Context) error {
|
func (app *BootstrapApp) serveHTTP() error {
|
||||||
address := fmt.Sprintf("%s:%d", app.config.Server.Address, app.config.Server.Port)
|
address := fmt.Sprintf("%s:%d", app.config.Server.Address, app.config.Server.Port)
|
||||||
|
|
||||||
app.log.App.Info().Msgf("Starting server on http://%s", address)
|
app.log.App.Info().Msgf("Starting server on %s", address)
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", address)
|
listener, err := net.Listen("tcp", address)
|
||||||
|
|
||||||
@@ -152,10 +154,10 @@ func (app *BootstrapApp) serveHTTP(ctx context.Context) error {
|
|||||||
Handler: app.router.Handler(),
|
Handler: app.router.Handler(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.serve(listener, server, ctx, "http")
|
return app.serve(listener, server, "http")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) serveUnix(ctx context.Context) error {
|
func (app *BootstrapApp) serveUnix() error {
|
||||||
_, err := os.Stat(app.config.Server.SocketPath)
|
_, err := os.Stat(app.config.Server.SocketPath)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -179,23 +181,43 @@ func (app *BootstrapApp) serveUnix(ctx context.Context) error {
|
|||||||
Handler: app.router.Handler(),
|
Handler: app.router.Handler(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.serve(listener, server, ctx, "unix socket")
|
return app.serve(listener, server, "unix socket")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) serve(listener net.Listener, server *http.Server, ctx context.Context, name string) error {
|
func (app *BootstrapApp) serveTailscale() error {
|
||||||
shutdown := func() {
|
app.log.App.Info().Msgf("Starting Tailscale server on %s", fmt.Sprintf("https://%s", app.services.tailscaleService.GetHostname()))
|
||||||
// we use a new context for the shutdown since the main one is cancelled
|
|
||||||
sctx, cancel := context.WithTimeout(context.Background(), model.GracefulShutdownTimeout*time.Second)
|
listener, err := app.services.tailscaleService.CreateListener()
|
||||||
defer cancel()
|
|
||||||
err := server.Shutdown(sctx)
|
|
||||||
if err != nil {
|
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, "tailscale")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *BootstrapApp) serve(listener net.Listener, server *http.Server, name string) error {
|
||||||
|
shutdown := func() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), model.GracefulShutdownTimeout*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
err := server.Shutdown(ctx)
|
||||||
|
if err != nil &&
|
||||||
|
// With tailscale, the goroutine for shutting down the tailscale connection
|
||||||
|
// runs first and causes the connection the tailscale listener is running on to close
|
||||||
|
// first so, the shutdown fails
|
||||||
|
// TODO: add priority to the goroutine shutdowns
|
||||||
|
!errors.Is(err, net.ErrClosed) {
|
||||||
app.log.App.Error().Err(err).Msgf("Failed to shutdown %s listener gracefully", name)
|
app.log.App.Error().Err(err).Msgf("Failed to shutdown %s listener gracefully", name)
|
||||||
}
|
}
|
||||||
listener.Close()
|
listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-app.ctx.Done()
|
||||||
app.log.App.Debug().Msgf("Shutting down %s listener", name)
|
app.log.App.Debug().Msgf("Shutting down %s listener", name)
|
||||||
shutdown()
|
shutdown()
|
||||||
}()
|
}()
|
||||||
@@ -203,6 +225,7 @@ func (app *BootstrapApp) serve(listener net.Listener, server *http.Server, ctx c
|
|||||||
err := server.Serve(listener)
|
err := server.Serve(listener)
|
||||||
|
|
||||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
shutdown()
|
||||||
return fmt.Errorf("failed to start %s listener: %w", name, err)
|
return fmt.Errorf("failed to start %s listener: %w", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,70 +2,58 @@ package bootstrap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||||
"go.uber.org/dig"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (app *BootstrapApp) setupServices() error {
|
func (app *BootstrapApp) setupServices() error {
|
||||||
err := app.setupPolicyEngine()
|
ldapService, err := service.NewLdapService(app.log, app.config, app.ctx, &app.wg)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to setup policy engine: %w", err)
|
app.log.App.Warn().Err(err).Msg("Failed to initialize LDAP connection, will continue without it")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.services.ldapService = ldapService
|
||||||
|
|
||||||
labelProvider, err := app.getLabelProvider()
|
labelProvider, err := app.getLabelProvider()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get label provider: %w", err)
|
return fmt.Errorf("failed to initialize label provider: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceProvideFor := []any{
|
tailscaleService, err := service.NewTailscaleService(app.log, app.config, app.ctx, &app.wg)
|
||||||
func() service.LabelProvider {
|
|
||||||
return labelProvider
|
|
||||||
},
|
|
||||||
service.NewLdapService,
|
|
||||||
service.NewTailscaleService,
|
|
||||||
service.NewAccessControlsService,
|
|
||||||
service.NewOAuthBrokerService,
|
|
||||||
service.NewAuthService,
|
|
||||||
service.NewOIDCService,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, provider := range serviceProvideFor {
|
|
||||||
err = app.dig.Provide(provider)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to provide service: %w", err)
|
app.log.App.Warn().Err(err).Msg("Failed to initialize Tailscale connection, will continue without it")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type svcInput struct {
|
app.services.tailscaleService = tailscaleService
|
||||||
dig.In
|
|
||||||
|
|
||||||
AccessControlService *service.AccessControlsService
|
accessControlsService := service.NewAccessControlsService(app.log, app.config, &labelProvider)
|
||||||
AuthService *service.AuthService
|
app.services.accessControlService = accessControlsService
|
||||||
LDAPService *service.LdapService
|
|
||||||
OAuthBrokerService *service.OAuthBrokerService
|
|
||||||
OIDCService *service.OIDCService
|
|
||||||
TailscaleService *service.TailscaleService
|
|
||||||
}
|
|
||||||
|
|
||||||
err = app.dig.Invoke(func(i svcInput) error {
|
err = app.setupPolicyEngine()
|
||||||
app.services.accessControlService = i.AccessControlService
|
|
||||||
app.services.authService = i.AuthService
|
|
||||||
app.services.ldapService = i.LDAPService
|
|
||||||
app.services.oauthBrokerService = i.OAuthBrokerService
|
|
||||||
app.services.oidcService = i.OIDCService
|
|
||||||
app.services.tailscaleService = i.TailscaleService
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to invoke services: %w", err)
|
return fmt.Errorf("failed to initialize policy engine: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oauthBrokerService := service.NewOAuthBrokerService(app.log, app.runtime.OAuthProviders, app.ctx)
|
||||||
|
app.services.oauthBrokerService = oauthBrokerService
|
||||||
|
|
||||||
|
authService := service.NewAuthService(app.log, app.config, app.runtime, app.ctx, &app.wg, app.services.ldapService, app.queries, app.services.oauthBrokerService, app.services.tailscaleService)
|
||||||
|
app.services.authService = authService
|
||||||
|
|
||||||
|
oidcService, err := service.NewOIDCService(app.log, app.config, app.runtime, app.queries, app.ctx, &app.wg)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to initialize oidc service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.services.oidcService = oidcService
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,71 +70,45 @@ func (app *BootstrapApp) getLabelProvider() (service.LabelProvider, error) {
|
|||||||
if useKubernetes {
|
if useKubernetes {
|
||||||
app.log.App.Debug().Msg("Using Kubernetes label provider")
|
app.log.App.Debug().Msg("Using Kubernetes label provider")
|
||||||
|
|
||||||
err := app.dig.Provide(service.NewKubernetesService)
|
kubernetesService, err := service.NewKubernetesService(app.log, app.ctx, &app.wg)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to provide kubernetes service: %w", err)
|
return nil, fmt.Errorf("failed to initialize kubernetes service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = app.dig.Invoke(func(k *service.KubernetesService) error {
|
app.services.kubernetesService = kubernetesService
|
||||||
app.services.kubernetesService = k
|
return kubernetesService, nil
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to invoke kubernetes service: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kubernetes will fail to initialize with an error if it cannot connect to the cluster
|
|
||||||
// but just to be safe, we check if the service is nil and log a warning if it is
|
|
||||||
if app.services.kubernetesService == nil {
|
|
||||||
if app.config.LabelProvider == "kubernetes" {
|
|
||||||
app.log.App.Warn().Msg("Kubernetes label provider selected but Kubernetes is not available, will continue without it")
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return app.services.kubernetesService, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.log.App.Debug().Msg("Using Docker label provider")
|
app.log.App.Debug().Msg("Using Docker label provider")
|
||||||
|
|
||||||
err := app.dig.Provide(service.NewDockerService)
|
dockerService, err := service.NewDockerService(app.log, app.ctx, &app.wg)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to provide docker service: %w", err)
|
return nil, fmt.Errorf("failed to initialize docker service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = app.dig.Invoke(func(d *service.DockerService) error {
|
if dockerService == nil {
|
||||||
app.services.dockerService = d
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to invoke docker service: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if app.services.dockerService == nil {
|
|
||||||
if app.config.LabelProvider == "docker" {
|
if app.config.LabelProvider == "docker" {
|
||||||
app.log.App.Warn().Msg("Docker label provider selected but Docker is not available, will continue without it")
|
app.log.App.Warn().Msg("Docker label provider selected but Docker is not available, will continue without it")
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.services.dockerService, nil
|
app.services.dockerService = dockerService
|
||||||
|
return dockerService, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid label provider: %s", app.config.LabelProvider)
|
return nil, fmt.Errorf("invalid label provider: %s", app.config.LabelProvider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *BootstrapApp) setupPolicyEngine() error {
|
func (app *BootstrapApp) setupPolicyEngine() error {
|
||||||
err := app.dig.Provide(service.NewPolicyEngine)
|
policyEngine, err := service.NewPolicyEngine(app.config, app.log)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create policy engine: %w", err)
|
return fmt.Errorf("failed to initialize policy engine: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = app.dig.Invoke(func(policyEngine *service.PolicyEngine) error {
|
|
||||||
policyEngine.RegisterRule(service.RuleUserAllowed, &service.UserAllowedRule{
|
policyEngine.RegisterRule(service.RuleUserAllowed, &service.UserAllowedRule{
|
||||||
Log: app.log,
|
Log: app.log,
|
||||||
})
|
})
|
||||||
@@ -165,10 +127,8 @@ func (app *BootstrapApp) setupPolicyEngine() error {
|
|||||||
})
|
})
|
||||||
policyEngine.RegisterRule(service.RuleIPBypassed, &service.IPBypassedRule{
|
policyEngine.RegisterRule(service.RuleIPBypassed, &service.IPBypassedRule{
|
||||||
Log: app.log,
|
Log: app.log,
|
||||||
Config: app.config,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return err
|
app.services.policyEngine = policyEngine
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
"go.uber.org/dig"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -62,7 +59,7 @@ type ACRUI struct {
|
|||||||
type ACRApp struct {
|
type ACRApp struct {
|
||||||
AppURL string `json:"appUrl"`
|
AppURL string `json:"appUrl"`
|
||||||
CookieDomain string `json:"cookieDomain"`
|
CookieDomain string `json:"cookieDomain"`
|
||||||
SubdomainsEnabled bool `json:"subdomainsEnabled"`
|
TrustedDomains []string `json:"trustedDomains"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppContextResponse struct {
|
type AppContextResponse struct {
|
||||||
@@ -74,33 +71,29 @@ type AppContextResponse struct {
|
|||||||
App ACRApp `json:"app"`
|
App ACRApp `json:"app"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContextControllerInput struct {
|
|
||||||
dig.In
|
|
||||||
|
|
||||||
Log *logger.Logger
|
|
||||||
Config *model.Config
|
|
||||||
Runtime *model.RuntimeConfig
|
|
||||||
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContextController struct {
|
type ContextController struct {
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
config *model.Config
|
config model.Config
|
||||||
runtime *model.RuntimeConfig
|
runtime model.RuntimeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContextController(i ContextControllerInput) *ContextController {
|
func NewContextController(
|
||||||
|
log *logger.Logger,
|
||||||
|
config model.Config,
|
||||||
|
runtimeConfig model.RuntimeConfig,
|
||||||
|
router *gin.RouterGroup,
|
||||||
|
) *ContextController {
|
||||||
controller := &ContextController{
|
controller := &ContextController{
|
||||||
log: i.Log,
|
log: log,
|
||||||
config: i.Config,
|
config: config,
|
||||||
runtime: i.Runtime,
|
runtime: runtimeConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !i.Config.UI.WarningsEnabled {
|
if !config.UI.WarningsEnabled {
|
||||||
i.Log.App.Warn().Msg("UI warnings are disabled. This may lead to security issues if you are not careful. Make sure to enable warnings in production environments.")
|
log.App.Warn().Msg("UI warnings are disabled. This may lead to security issues if you are not careful. Make sure to enable warnings in production environments.")
|
||||||
}
|
}
|
||||||
|
|
||||||
contextGroup := i.RouterGroup.Group("/context")
|
contextGroup := router.Group("/context")
|
||||||
contextGroup.GET("/user", controller.userContextHandler)
|
contextGroup.GET("/user", controller.userContextHandler)
|
||||||
contextGroup.GET("/app", controller.appContextHandler)
|
contextGroup.GET("/app", controller.appContextHandler)
|
||||||
|
|
||||||
@@ -111,9 +104,7 @@ func (controller *ContextController) userContextHandler(c *gin.Context) {
|
|||||||
context, err := new(model.UserContext).NewFromGin(c)
|
context, err := new(model.UserContext).NewFromGin(c)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, model.ErrUserContextNotFound) {
|
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to create user context from request")
|
controller.log.App.Error().Err(err).Msg("Failed to create user context from request")
|
||||||
}
|
|
||||||
c.JSON(200, UserContextResponse{
|
c.JSON(200, UserContextResponse{
|
||||||
Status: 401,
|
Status: 401,
|
||||||
Message: "Unauthorized",
|
Message: "Unauthorized",
|
||||||
@@ -166,7 +157,7 @@ func (controller *ContextController) appContextHandler(c *gin.Context) {
|
|||||||
App: ACRApp{
|
App: ACRApp{
|
||||||
AppURL: controller.runtime.AppURL,
|
AppURL: controller.runtime.AppURL,
|
||||||
CookieDomain: controller.runtime.CookieDomain,
|
CookieDomain: controller.runtime.CookieDomain,
|
||||||
SubdomainsEnabled: controller.config.Auth.SubdomainsEnabled,
|
TrustedDomains: controller.runtime.TrustedDomains,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package controller
|
package controller_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/test"
|
"github.com/tinyauthapp/tinyauth/internal/test"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||||
@@ -32,25 +33,25 @@ func TestContextController(t *testing.T) {
|
|||||||
middlewares: []gin.HandlerFunc{},
|
middlewares: []gin.HandlerFunc{},
|
||||||
path: "/api/context/app",
|
path: "/api/context/app",
|
||||||
expected: func() string {
|
expected: func() string {
|
||||||
expectedAppContextResponse := AppContextResponse{
|
expectedAppContextResponse := controller.AppContextResponse{
|
||||||
Status: 200,
|
Status: 200,
|
||||||
Message: "Success",
|
Message: "Success",
|
||||||
Auth: ACRAuth{
|
Auth: controller.ACRAuth{
|
||||||
Providers: runtime.ConfiguredProviders,
|
Providers: runtime.ConfiguredProviders,
|
||||||
},
|
},
|
||||||
OAuth: ACROAuth{
|
OAuth: controller.ACROAuth{
|
||||||
AutoRedirect: cfg.OAuth.AutoRedirect,
|
AutoRedirect: cfg.OAuth.AutoRedirect,
|
||||||
},
|
},
|
||||||
UI: ACRUI{
|
UI: controller.ACRUI{
|
||||||
Title: cfg.UI.Title,
|
Title: cfg.UI.Title,
|
||||||
ForgotPasswordMessage: cfg.UI.ForgotPasswordMessage,
|
ForgotPasswordMessage: cfg.UI.ForgotPasswordMessage,
|
||||||
BackgroundImage: cfg.UI.BackgroundImage,
|
BackgroundImage: cfg.UI.BackgroundImage,
|
||||||
WarningsEnabled: cfg.UI.WarningsEnabled,
|
WarningsEnabled: cfg.UI.WarningsEnabled,
|
||||||
},
|
},
|
||||||
App: ACRApp{
|
App: controller.ACRApp{
|
||||||
AppURL: runtime.AppURL,
|
AppURL: runtime.AppURL,
|
||||||
CookieDomain: runtime.CookieDomain,
|
CookieDomain: runtime.CookieDomain,
|
||||||
SubdomainsEnabled: cfg.Auth.SubdomainsEnabled,
|
TrustedDomains: runtime.TrustedDomains,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
bytes, err := json.Marshal(expectedAppContextResponse)
|
bytes, err := json.Marshal(expectedAppContextResponse)
|
||||||
@@ -63,7 +64,7 @@ func TestContextController(t *testing.T) {
|
|||||||
middlewares: []gin.HandlerFunc{},
|
middlewares: []gin.HandlerFunc{},
|
||||||
path: "/api/context/user",
|
path: "/api/context/user",
|
||||||
expected: func() string {
|
expected: func() string {
|
||||||
expectedUserContextResponse := UserContextResponse{
|
expectedUserContextResponse := controller.UserContextResponse{
|
||||||
Status: 401,
|
Status: 401,
|
||||||
Message: "Unauthorized",
|
Message: "Unauthorized",
|
||||||
}
|
}
|
||||||
@@ -91,10 +92,10 @@ func TestContextController(t *testing.T) {
|
|||||||
},
|
},
|
||||||
path: "/api/context/user",
|
path: "/api/context/user",
|
||||||
expected: func() string {
|
expected: func() string {
|
||||||
expectedUserContextResponse := UserContextResponse{
|
expectedUserContextResponse := controller.UserContextResponse{
|
||||||
Status: 200,
|
Status: 200,
|
||||||
Message: "Success",
|
Message: "Success",
|
||||||
Auth: UCRAuth{
|
Auth: controller.UCRAuth{
|
||||||
Authenticated: true,
|
Authenticated: true,
|
||||||
Username: "johndoe",
|
Username: "johndoe",
|
||||||
Name: "John Doe",
|
Name: "John Doe",
|
||||||
@@ -120,12 +121,7 @@ func TestContextController(t *testing.T) {
|
|||||||
group := router.Group("/api")
|
group := router.Group("/api")
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
|
|
||||||
NewContextController(ContextControllerInput{
|
controller.NewContextController(log, cfg, runtime, group)
|
||||||
Log: log,
|
|
||||||
Config: &cfg,
|
|
||||||
Runtime: &runtime,
|
|
||||||
RouterGroup: group,
|
|
||||||
})
|
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
type FrontendLoginFor string
|
|
||||||
|
|
||||||
const (
|
|
||||||
FrontendLoginForOIDC FrontendLoginFor = "oidc"
|
|
||||||
FrontendLoginForApp FrontendLoginFor = "app"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UnauthorizedQuery struct {
|
type UnauthorizedQuery struct {
|
||||||
Username string `url:"username"`
|
Username string `url:"username"`
|
||||||
Resource string `url:"resource"`
|
Resource string `url:"resource"`
|
||||||
@@ -16,5 +9,4 @@ type UnauthorizedQuery struct {
|
|||||||
|
|
||||||
type RedirectQuery struct {
|
type RedirectQuery struct {
|
||||||
RedirectURI string `url:"redirect_uri"`
|
RedirectURI string `url:"redirect_uri"`
|
||||||
LoginFor FrontendLoginFor `url:"login_for"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,15 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import "github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"go.uber.org/dig"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HealthController struct {
|
type HealthController struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type HealthControllerInput struct {
|
func NewHealthController(router *gin.RouterGroup) *HealthController {
|
||||||
dig.In
|
|
||||||
|
|
||||||
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHealthController(i HealthControllerInput) *HealthController {
|
|
||||||
controller := &HealthController{}
|
controller := &HealthController{}
|
||||||
|
|
||||||
i.RouterGroup.GET("/healthz", controller.healthHandler)
|
router.GET("/healthz", controller.healthHandler)
|
||||||
i.RouterGroup.HEAD("/healthz", controller.healthHandler)
|
router.HEAD("/healthz", controller.healthHandler)
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package controller
|
package controller_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHealthController(t *testing.T) {
|
func TestHealthController(t *testing.T) {
|
||||||
@@ -54,9 +55,7 @@ func TestHealthController(t *testing.T) {
|
|||||||
group := router.Group("/api")
|
group := router.Group("/api")
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
|
|
||||||
NewHealthController(HealthControllerInput{
|
controller.NewHealthController(group)
|
||||||
RouterGroup: group,
|
|
||||||
})
|
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -12,7 +11,6 @@ import (
|
|||||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
"go.uber.org/dig"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/go-querystring/query"
|
"github.com/google/go-querystring/query"
|
||||||
@@ -24,30 +22,26 @@ type OAuthRequest struct {
|
|||||||
|
|
||||||
type OAuthController struct {
|
type OAuthController struct {
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
config *model.Config
|
config model.Config
|
||||||
runtime *model.RuntimeConfig
|
runtime model.RuntimeConfig
|
||||||
auth *service.AuthService
|
auth *service.AuthService
|
||||||
}
|
}
|
||||||
|
|
||||||
type OAuthControllerInput struct {
|
func NewOAuthController(
|
||||||
dig.In
|
log *logger.Logger,
|
||||||
|
config model.Config,
|
||||||
Log *logger.Logger
|
runtimeConfig model.RuntimeConfig,
|
||||||
Config *model.Config
|
router *gin.RouterGroup,
|
||||||
RuntimeConfig *model.RuntimeConfig
|
auth *service.AuthService,
|
||||||
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
|
) *OAuthController {
|
||||||
AuthService *service.AuthService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOAuthController(i OAuthControllerInput) *OAuthController {
|
|
||||||
controller := &OAuthController{
|
controller := &OAuthController{
|
||||||
log: i.Log,
|
log: log,
|
||||||
config: i.Config,
|
config: config,
|
||||||
runtime: i.RuntimeConfig,
|
runtime: runtimeConfig,
|
||||||
auth: i.AuthService,
|
auth: auth,
|
||||||
}
|
}
|
||||||
|
|
||||||
oauthGroup := i.RouterGroup.Group("/oauth")
|
oauthGroup := router.Group("/oauth")
|
||||||
oauthGroup.GET("/url/:provider", controller.oauthURLHandler)
|
oauthGroup.GET("/url/:provider", controller.oauthURLHandler)
|
||||||
oauthGroup.GET("/callback/:provider", controller.oauthCallbackHandler)
|
oauthGroup.GET("/callback/:provider", controller.oauthCallbackHandler)
|
||||||
|
|
||||||
@@ -67,7 +61,7 @@ func (controller *OAuthController) oauthURLHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var reqParams service.OAuthCallbackParams
|
var reqParams service.OAuthURLParams
|
||||||
|
|
||||||
err = c.BindQuery(&reqParams)
|
err = c.BindQuery(&reqParams)
|
||||||
|
|
||||||
@@ -81,13 +75,15 @@ func (controller *OAuthController) oauthURLHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !controller.isOidcRequest(reqParams) {
|
if !controller.isOidcRequest(reqParams) {
|
||||||
if !controller.isRedirectSafe(reqParams.RedirectURI) {
|
isRedirectSafe := utils.IsRedirectSafe(reqParams.RedirectURI, controller.runtime.CookieDomain)
|
||||||
|
|
||||||
|
if !isRedirectSafe {
|
||||||
controller.log.App.Warn().Str("redirectUri", reqParams.RedirectURI).Msg("Unsafe redirect URI, ignoring")
|
controller.log.App.Warn().Str("redirectUri", reqParams.RedirectURI).Msg("Unsafe redirect URI, ignoring")
|
||||||
reqParams.RedirectURI = ""
|
reqParams.RedirectURI = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionId, err := controller.auth.NewOAuthSession(req.Provider, reqParams)
|
sessionId, _, err := controller.auth.NewOAuthSession(req.Provider, reqParams)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to create new OAuth session")
|
controller.log.App.Error().Err(err).Msg("Failed to create new OAuth session")
|
||||||
@@ -187,23 +183,9 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
svc, err := controller.auth.GetOAuthService(sessionIdCookie)
|
if !controller.auth.IsEmailWhitelisted(user.Email) {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to get OAuth service for session")
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if svc.ID() != req.Provider {
|
|
||||||
controller.log.App.Warn().Msgf("OAuth provider mismatch: expected %s, got %s", req.Provider, svc.ID())
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !controller.auth.IsEmailWhitelisted(svc.ID(), user.Email) {
|
|
||||||
controller.log.App.Warn().Str("email", user.Email).Msg("Email not whitelisted, denying access")
|
controller.log.App.Warn().Str("email", user.Email).Msg("Email not whitelisted, denying access")
|
||||||
controller.log.AuditLoginFailure(user.Email, svc.ID(), c.ClientIP(), "email not whitelisted")
|
controller.log.AuditLoginFailure(user.Email, req.Provider, c.ClientIP(), "email not whitelisted")
|
||||||
|
|
||||||
queries, err := query.Values(UnauthorizedQuery{
|
queries, err := query.Values(UnauthorizedQuery{
|
||||||
Username: user.Email,
|
Username: user.Email,
|
||||||
@@ -244,6 +226,20 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
username = strings.Replace(user.Email, "@", "_", 1)
|
username = strings.Replace(user.Email, "@", "_", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svc, err := controller.auth.GetOAuthService(sessionIdCookie)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
controller.log.App.Error().Err(err).Msg("Failed to get OAuth service for session")
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc.ID() != req.Provider {
|
||||||
|
controller.log.App.Warn().Msgf("OAuth provider mismatch: expected %s, got %s", req.Provider, svc.ID())
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sessionCookie := repository.Session{
|
sessionCookie := repository.Session{
|
||||||
Username: username,
|
Username: username,
|
||||||
Name: name,
|
Name: name,
|
||||||
@@ -276,14 +272,13 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/oidc/authorize?%s", controller.runtime.AppURL, queries.Encode()))
|
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/authorize?%s", controller.runtime.AppURL, queries.Encode()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if oauthPendingSession.CallbackParams.RedirectURI != "" {
|
if oauthPendingSession.CallbackParams.RedirectURI != "" {
|
||||||
queries, err := query.Values(RedirectQuery{
|
queries, err := query.Values(RedirectQuery{
|
||||||
RedirectURI: oauthPendingSession.CallbackParams.RedirectURI,
|
RedirectURI: oauthPendingSession.CallbackParams.RedirectURI,
|
||||||
LoginFor: FrontendLoginForApp,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -299,68 +294,16 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
c.Redirect(http.StatusTemporaryRedirect, controller.runtime.AppURL)
|
c.Redirect(http.StatusTemporaryRedirect, controller.runtime.AppURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (controller *OAuthController) isOidcRequest(params service.OAuthCallbackParams) bool {
|
func (controller *OAuthController) isOidcRequest(params service.OAuthURLParams) bool {
|
||||||
return params.LoginFor == string(FrontendLoginForOIDC)
|
return params.Scope != "" &&
|
||||||
|
params.ResponseType != "" &&
|
||||||
|
params.ClientID != "" &&
|
||||||
|
params.RedirectURI != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (controller *OAuthController) getCookieDomain() string {
|
func (controller *OAuthController) getCookieDomain() string {
|
||||||
if !controller.config.Auth.SubdomainsEnabled {
|
if controller.config.Auth.SubdomainsEnabled {
|
||||||
return ""
|
return "." + controller.runtime.CookieDomain
|
||||||
}
|
}
|
||||||
return controller.runtime.CookieDomain
|
return controller.runtime.CookieDomain
|
||||||
}
|
}
|
||||||
|
|
||||||
func (controller *OAuthController) isRedirectSafe(redirectURI string) bool {
|
|
||||||
u, err := url.Parse(redirectURI)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to parse redirect URI")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Scheme == "" || u.Host == "" {
|
|
||||||
controller.log.App.Warn().Msg("Redirect URI has invalid scheme or host")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
au, err := url.Parse(controller.runtime.AppURL)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to parse app URL")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Scheme != au.Scheme {
|
|
||||||
controller.log.App.Warn().Msg("Redirect URI scheme does not match app URL scheme")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
getEffectivePort := func(u *url.URL) string {
|
|
||||||
if u.Port() != "" {
|
|
||||||
return u.Port()
|
|
||||||
}
|
|
||||||
if u.Scheme == "https" {
|
|
||||||
return "443"
|
|
||||||
}
|
|
||||||
return "80"
|
|
||||||
}
|
|
||||||
|
|
||||||
if getEffectivePort(u) != getEffectivePort(au) {
|
|
||||||
controller.log.App.Warn().Msg("Redirect URI port does not match app URL port")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.EqualFold(u.Hostname(), au.Hostname()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !controller.config.Auth.SubdomainsEnabled {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasSuffix(strings.ToLower(u.Hostname()), "."+strings.ToLower(controller.runtime.CookieDomain)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,187 +0,0 @@
|
|||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/test"
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOAuthControllerIsRedirectSafe(t *testing.T) {
|
|
||||||
log := logger.NewLogger().WithTestConfig()
|
|
||||||
log.Init()
|
|
||||||
|
|
||||||
cfg, runtime := test.CreateTestConfigs(t)
|
|
||||||
|
|
||||||
type testCase struct {
|
|
||||||
description string
|
|
||||||
appURL string
|
|
||||||
cookieDomain string
|
|
||||||
subdomainsEnabled bool
|
|
||||||
redirectURI string
|
|
||||||
expected bool
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []testCase{
|
|
||||||
{
|
|
||||||
description: "Exact host match returns true",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "https://tinyauth.example.com",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Exact host match is case insensitive",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "https://TinyAuth.Example.com",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Exact host match with subdomains disabled returns true",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: false,
|
|
||||||
redirectURI: "https://tinyauth.example.com",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Subdomain of cookie domain returns true when subdomains enabled",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "https://sub.example.com",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Subdomain of cookie domain is case insensitive",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "Example.COM",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "https://SUB.example.com",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Subdomain not matching cookie domain returns false",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "https://sub.evil.com",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Subdomain returns false when subdomains disabled",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: false,
|
|
||||||
redirectURI: "https://sub.example.com",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Cookie domain itself is not a subdomain match",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "https://example.com",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Different scheme returns false",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "http://tinyauth.example.com",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Different port returns false",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "https://tinyauth.example.com:8080",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Empty redirect URI returns false",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Redirect URI without host returns false",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "https:/malicious",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Redirect URI without scheme returns false",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "tinyauth.example.com",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Relative redirect URI returns false",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "/some/path",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Userinfo trick with malicious host returns false",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "https://malicious.example.com@evil.com",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Unparseable redirect URI returns false",
|
|
||||||
appURL: "https://tinyauth.example.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "https://exa\x7fmple.com",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Unparseable app URL returns false",
|
|
||||||
appURL: "https://tinyauth.\x7fexample.com",
|
|
||||||
cookieDomain: "example.com",
|
|
||||||
subdomainsEnabled: true,
|
|
||||||
redirectURI: "https://tinyauth.example.com",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
router := gin.Default()
|
|
||||||
group := router.Group("/api")
|
|
||||||
gin.SetMode(gin.TestMode)
|
|
||||||
|
|
||||||
// Overwrite the app URL, cookie domain and subdomain setting for each test case
|
|
||||||
runtime.AppURL = tc.appURL
|
|
||||||
runtime.CookieDomain = tc.cookieDomain
|
|
||||||
cfg.Auth.SubdomainsEnabled = tc.subdomainsEnabled
|
|
||||||
|
|
||||||
ctrl := NewOAuthController(OAuthControllerInput{
|
|
||||||
Log: log,
|
|
||||||
Config: &cfg,
|
|
||||||
RuntimeConfig: &runtime,
|
|
||||||
RouterGroup: group,
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Equal(t, tc.expected, ctrl.isRedirectSafe(tc.redirectURI))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +1,25 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin/binding"
|
|
||||||
"github.com/google/go-querystring/query"
|
"github.com/google/go-querystring/query"
|
||||||
"go.uber.org/dig"
|
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type authorizeErrorParams struct {
|
|
||||||
err error
|
|
||||||
reason string
|
|
||||||
reasonPublic string
|
|
||||||
callback string
|
|
||||||
callbackError string
|
|
||||||
state string
|
|
||||||
json bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type OIDCController struct {
|
type OIDCController struct {
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
oidc *service.OIDCService
|
oidc *service.OIDCService
|
||||||
runtime *model.RuntimeConfig
|
runtime model.RuntimeConfig
|
||||||
config *model.Config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthorizeCallback struct {
|
type AuthorizeCallback struct {
|
||||||
@@ -71,42 +56,20 @@ type ClientCredentials struct {
|
|||||||
ClientSecret string
|
ClientSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthorizeScreenParams struct {
|
func NewOIDCController(
|
||||||
LoginFor FrontendLoginFor `url:"login_for"`
|
log *logger.Logger,
|
||||||
OIDCTicket string `url:"oidc_ticket"`
|
oidcService *service.OIDCService,
|
||||||
OIDCScope string `url:"oidc_scope"`
|
runtimeConfig model.RuntimeConfig,
|
||||||
OIDCName string `url:"oidc_name"`
|
router *gin.RouterGroup) *OIDCController {
|
||||||
OIDCPrompt service.OIDCPrompt `url:"oidc_prompt,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthorizeCompleteRequest struct {
|
|
||||||
Ticket string `json:"ticket" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OIDCControllerInput struct {
|
|
||||||
dig.In
|
|
||||||
|
|
||||||
Log *logger.Logger
|
|
||||||
OIDCService *service.OIDCService
|
|
||||||
Config *model.Config
|
|
||||||
RuntimeConfig *model.RuntimeConfig
|
|
||||||
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
|
|
||||||
MainRouter *gin.RouterGroup `name:"mainRouterGroup"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOIDCController(i OIDCControllerInput) *OIDCController {
|
|
||||||
controller := &OIDCController{
|
controller := &OIDCController{
|
||||||
log: i.Log,
|
log: log,
|
||||||
oidc: i.OIDCService,
|
oidc: oidcService,
|
||||||
runtime: i.RuntimeConfig,
|
runtime: runtimeConfig,
|
||||||
config: i.Config,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
i.MainRouter.POST("/authorize", controller.authorize)
|
oidcGroup := router.Group("/oidc")
|
||||||
i.MainRouter.GET("/authorize", controller.authorize)
|
oidcGroup.GET("/clients/:id", controller.GetClientInfo)
|
||||||
|
oidcGroup.POST("/authorize", controller.Authorize)
|
||||||
oidcGroup := i.RouterGroup.Group("/oidc")
|
|
||||||
oidcGroup.POST("/authorize-complete", controller.authorizeComplete)
|
|
||||||
oidcGroup.POST("/token", controller.Token)
|
oidcGroup.POST("/token", controller.Token)
|
||||||
oidcGroup.GET("/userinfo", controller.Userinfo)
|
oidcGroup.GET("/userinfo", controller.Userinfo)
|
||||||
oidcGroup.POST("/userinfo", controller.Userinfo)
|
oidcGroup.POST("/userinfo", controller.Userinfo)
|
||||||
@@ -114,27 +77,24 @@ func NewOIDCController(i OIDCControllerInput) *OIDCController {
|
|||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// This endpoint does **not** return a code, it handles param validation, ticket creation
|
func (controller *OIDCController) GetClientInfo(c *gin.Context) {
|
||||||
// and then redirects to the frontend to handle the consent screen. It performs no destructive
|
|
||||||
// actions (like logging out an existing session)
|
|
||||||
func (controller *OIDCController) authorize(c *gin.Context) {
|
|
||||||
if controller.oidc == nil {
|
if controller.oidc == nil {
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
controller.log.App.Warn().Msg("Received OIDC client info request but OIDC server is not configured")
|
||||||
err: errors.New("err_oidc_not_configured"),
|
c.JSON(500, gin.H{
|
||||||
reason: "OIDC not configured",
|
"status": 500,
|
||||||
reasonPublic: "This instance is not configured for OIDC",
|
"message": "OIDC not configured",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := controller.resolveAuthorizeRequest(c)
|
var req ClientRequest
|
||||||
|
|
||||||
|
err := c.BindUri(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Warn().Err(err).Msg("Failed to resolve authorize request")
|
controller.log.App.Error().Err(err).Msg("Failed to bind URI")
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
c.JSON(400, gin.H{
|
||||||
err: err,
|
"status": 400,
|
||||||
reason: "Failed to resolve authorize request",
|
"message": "Bad Request",
|
||||||
reasonPublic: "The authorization request is invalid",
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -142,273 +102,108 @@ func (controller *OIDCController) authorize(c *gin.Context) {
|
|||||||
client, ok := controller.oidc.GetClient(req.ClientID)
|
client, ok := controller.oidc.GetClient(req.ClientID)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
controller.log.App.Warn().Str("clientId", req.ClientID).Msg("Client not found")
|
||||||
err: fmt.Errorf("client not found: %s", req.ClientID),
|
c.JSON(404, gin.H{
|
||||||
reason: "Client not found",
|
"status": 404,
|
||||||
reasonPublic: "The client ID is invalid",
|
"message": "Client not found",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.oidc.ValidateAuthorizeParams(*req)
|
c.JSON(200, gin.H{
|
||||||
|
"status": 200,
|
||||||
|
"client": client.ClientID,
|
||||||
|
"name": client.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (controller *OIDCController) Authorize(c *gin.Context) {
|
||||||
|
if controller.oidc == nil {
|
||||||
|
controller.authorizeError(c, errors.New("err_oidc_not_configured"), "OIDC not configured", "This instance is not configured for OIDC", "", "", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userContext, err := new(model.UserContext).NewFromGin(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
controller.authorizeError(c, err, "Failed to get user context", "User is not logged in or the session is invalid", "", "", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !userContext.Authenticated {
|
||||||
|
controller.authorizeError(c, errors.New("err user not logged in"), "User not logged in", "The user is not logged in", "", "", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req service.AuthorizeRequest
|
||||||
|
|
||||||
|
err = c.BindJSON(&req)
|
||||||
|
if err != nil {
|
||||||
|
controller.authorizeError(c, err, "Failed to bind JSON", "The client provided an invalid authorization request", "", "", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client, ok := controller.oidc.GetClient(req.ClientID)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
controller.authorizeError(c, fmt.Errorf("client not found: %s", req.ClientID), "Client not found", "The client ID is invalid", "", "", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = controller.oidc.ValidateAuthorizeParams(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Warn().Err(err).Msg("Failed to validate authorize params")
|
controller.log.App.Warn().Err(err).Msg("Failed to validate authorize params")
|
||||||
if err.Error() != "invalid_request_uri" {
|
if err.Error() != "invalid_request_uri" {
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
controller.authorizeError(c, err, "Failed validate authorize params", "Invalid request parameters", req.RedirectURI, err.Error(), req.State)
|
||||||
err: err,
|
|
||||||
reason: "Failed validate authorize params",
|
|
||||||
reasonPublic: "Invalid request parameters",
|
|
||||||
callback: req.RedirectURI,
|
|
||||||
callbackError: err.Error(),
|
|
||||||
state: req.State,
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
controller.authorizeError(c, err, "Redirect URI not trusted", "The provided redirect URI is not trusted", "", "", "")
|
||||||
err: err,
|
|
||||||
reason: "Redirect URI not trusted",
|
|
||||||
reasonPublic: "The provided redirect URI is not trusted",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
prompts := controller.oidc.GetPrompt(req.Prompt)
|
// WARNING: Since Tinyauth is stateless, we cannot have a sub that never changes. We will just create a uuid out of the username and client name which remains stable, but if username or client name changes then sub changes too.
|
||||||
|
sub := utils.GenerateUUID(fmt.Sprintf("%s:%s", userContext.GetUsername(), client.ID))
|
||||||
if slices.Contains(prompts, service.OIDCPromptNone) && len(prompts) > 1 {
|
code := utils.GenerateString(32)
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
|
||||||
err: errors.New("invalid prompt"),
|
|
||||||
reason: "Invalid prompt",
|
|
||||||
reasonPublic: "The prompt parameters are invalid",
|
|
||||||
callback: req.RedirectURI,
|
|
||||||
callbackError: "invalid_request",
|
|
||||||
state: req.State,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userContext, err := new(model.UserContext).NewFromGin(c)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, model.ErrUserContextNotFound) {
|
|
||||||
controller.log.App.Warn().Err(err).Msg("Failed to get user context")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err != nil || !userContext.Authenticated) && slices.Contains(prompts, service.OIDCPromptNone) {
|
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
|
||||||
err: errors.New("user not logged in"),
|
|
||||||
reason: "User not logged in",
|
|
||||||
reasonPublic: "The user is not logged in",
|
|
||||||
callback: req.RedirectURI,
|
|
||||||
callbackError: "login_required",
|
|
||||||
state: req.State,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ticket := controller.oidc.CreateAuthorizeRequestTicket(*req)
|
|
||||||
|
|
||||||
values := AuthorizeScreenParams{
|
|
||||||
LoginFor: FrontendLoginForOIDC,
|
|
||||||
OIDCTicket: ticket,
|
|
||||||
OIDCScope: req.Scope,
|
|
||||||
OIDCName: client.Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
if slices.Contains(prompts, service.OIDCPromptLogin) {
|
|
||||||
values.OIDCPrompt = service.OIDCPromptLogin
|
|
||||||
} else if slices.Contains(prompts, service.OIDCPromptNone) {
|
|
||||||
values.OIDCPrompt = service.OIDCPromptNone
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.MaxAge != "" && userContext != nil {
|
|
||||||
maxAge, err := strconv.Atoi(req.MaxAge)
|
|
||||||
if err != nil {
|
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
|
||||||
err: err,
|
|
||||||
reason: "Invalid max_age",
|
|
||||||
reasonPublic: "The max_age parameter is invalid",
|
|
||||||
callback: req.RedirectURI,
|
|
||||||
callbackError: "invalid_request",
|
|
||||||
state: req.State,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if userContext.Authenticated {
|
|
||||||
authTime := time.Unix(userContext.AuthTime, 0)
|
|
||||||
if authTime.Add(time.Duration(maxAge) * time.Second).Before(time.Now()) {
|
|
||||||
values.OIDCPrompt = service.OIDCPromptLogin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cookieId := strings.SplitN(client.ClientID, "-", 2)[0]
|
|
||||||
cookieName := fmt.Sprintf("%s-%s", controller.runtime.ScopeCookieName, cookieId)
|
|
||||||
scopeCookie, err := c.Cookie(cookieName)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
scopes := fmt.Sprintf("scopes=%s;", req.Scope)
|
|
||||||
if controller.oidc.VerifySignedValue(client.ClientSecret, []byte(scopes), scopeCookie) {
|
|
||||||
if values.OIDCPrompt != service.OIDCPromptLogin {
|
|
||||||
values.OIDCPrompt = service.OIDCPromptNone
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
queries, err := query.Values(values)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
|
||||||
err: err,
|
|
||||||
reason: "Failed to compile authorize queries",
|
|
||||||
reasonPublic: "An internal error occured while processing your request",
|
|
||||||
callback: req.RedirectURI,
|
|
||||||
callbackError: "server_error",
|
|
||||||
state: req.State,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectUrl := fmt.Sprintf("%s/oidc/authorize?%s", controller.oidc.GetIssuer(), queries.Encode())
|
|
||||||
c.Redirect(http.StatusFound, redirectUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The actual **internal** endpoint that actually creates the code and session.
|
|
||||||
// It is called by the frontend after the user has logged in and given consent.
|
|
||||||
func (controller *OIDCController) authorizeComplete(c *gin.Context) {
|
|
||||||
if controller.oidc == nil {
|
|
||||||
// For this endpoint we return JSON errors since it's called
|
|
||||||
// by the frontend and not an external client, so there's
|
|
||||||
// no redirect_uri to send the user to in case of error
|
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
|
||||||
err: errors.New("err_oidc_not_configured"),
|
|
||||||
reason: "OIDC not configured",
|
|
||||||
reasonPublic: "This instance is not configured for OIDC",
|
|
||||||
json: true,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userContext, err := new(model.UserContext).NewFromGin(c)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, model.ErrUserContextNotFound) {
|
|
||||||
controller.log.App.Warn().Err(err).Msg("Failed to get user context")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil || !userContext.Authenticated {
|
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
|
||||||
err: errors.New("err user not logged in"),
|
|
||||||
reason: "User not logged in",
|
|
||||||
reasonPublic: "The user is not logged in",
|
|
||||||
json: true,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var req AuthorizeCompleteRequest
|
|
||||||
|
|
||||||
err = c.BindJSON(&req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
|
||||||
err: err,
|
|
||||||
reason: "Failed to bind JSON",
|
|
||||||
reasonPublic: "The client provided an invalid authorization request",
|
|
||||||
json: true,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
authorizeReq, ok := controller.oidc.GetAuthorizeRequestByTicket(req.Ticket)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
|
||||||
err: errors.New("authorize request not found for ticket"),
|
|
||||||
reason: "Invalid or expired ticket",
|
|
||||||
reasonPublic: "The authorization request has expired or is invalid",
|
|
||||||
json: true,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the client
|
|
||||||
client, ok := controller.oidc.GetClient(authorizeReq.ClientID)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
|
||||||
err: errors.New("client not found"),
|
|
||||||
reason: "Client not found",
|
|
||||||
reasonPublic: "The client is not configured",
|
|
||||||
json: true,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We no longer need the ticket
|
|
||||||
controller.oidc.DeleteAuthorizeRequestTicket(req.Ticket)
|
|
||||||
|
|
||||||
// Create the sub to find and delete old sessions
|
|
||||||
sub := controller.oidc.CreateSub(*userContext, authorizeReq.ClientID)
|
|
||||||
|
|
||||||
// Before storing the code, delete old session
|
// Before storing the code, delete old session
|
||||||
err = controller.oidc.DeleteOldSession(c, sub)
|
err = controller.oidc.DeleteOldSession(c, sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
controller.authorizeError(c, err, "Failed to delete old sessions", "Failed to delete old sessions", req.RedirectURI, "server_error", req.State)
|
||||||
err: err,
|
|
||||||
reason: "Failed to delete old sessions",
|
|
||||||
reasonPublic: "Failed to delete old sessions",
|
|
||||||
callback: authorizeReq.RedirectURI,
|
|
||||||
callbackError: "server_error",
|
|
||||||
state: authorizeReq.State,
|
|
||||||
json: true,
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the authorization code
|
err = controller.oidc.StoreCode(c, sub, code, req)
|
||||||
code := controller.oidc.CreateCode(*authorizeReq, *userContext)
|
|
||||||
|
if err != nil {
|
||||||
|
controller.authorizeError(c, err, "Failed to store code", "Failed to store code", req.RedirectURI, "server_error", req.State)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We also need a snapshot of the user that authorized this (skip if no openid scope)
|
||||||
|
if slices.Contains(strings.Fields(req.Scope), "openid") {
|
||||||
|
err = controller.oidc.StoreUserinfo(c, sub, *userContext, req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
controller.log.App.Error().Err(err).Msg("Failed to store user info")
|
||||||
|
controller.authorizeError(c, err, "Failed to store user info", "Failed to store user info", req.RedirectURI, "server_error", req.State)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
queries, err := query.Values(AuthorizeCallback{
|
queries, err := query.Values(AuthorizeCallback{
|
||||||
Code: code,
|
Code: code,
|
||||||
State: authorizeReq.State,
|
State: req.State,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.authorizeError(c, authorizeErrorParams{
|
controller.authorizeError(c, err, "Failed to build query", "Failed to build query", req.RedirectURI, "server_error", req.State)
|
||||||
err: err,
|
|
||||||
reason: "Failed to build query",
|
|
||||||
reasonPublic: "Failed to build query",
|
|
||||||
callback: authorizeReq.RedirectURI,
|
|
||||||
callbackError: "server_error",
|
|
||||||
state: authorizeReq.State,
|
|
||||||
json: true,
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a cookie for the consent screen (approved scopes)
|
|
||||||
cookieId := strings.SplitN(client.ClientID, "-", 2)[0]
|
|
||||||
cookieName := fmt.Sprintf("%s-%s", controller.runtime.ScopeCookieName, cookieId)
|
|
||||||
scopes := fmt.Sprintf("scopes=%s;", authorizeReq.Scope)
|
|
||||||
|
|
||||||
cookie := &http.Cookie{
|
|
||||||
Name: cookieName,
|
|
||||||
Value: controller.oidc.CreateSignedValue(client.ClientSecret, []byte(scopes)),
|
|
||||||
Path: "/",
|
|
||||||
Secure: controller.config.Auth.SecureCookie,
|
|
||||||
HttpOnly: true,
|
|
||||||
SameSite: http.SameSiteLaxMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
http.SetCookie(c.Writer, cookie)
|
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"redirect_uri": fmt.Sprintf("%s?%s", authorizeReq.RedirectURI, queries.Encode()),
|
"redirect_uri": fmt.Sprintf("%s?%s", req.RedirectURI, queries.Encode()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,33 +285,38 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
|
|
||||||
switch req.GrantType {
|
switch req.GrantType {
|
||||||
case "authorization_code":
|
case "authorization_code":
|
||||||
entry, ok := controller.oidc.GetCodeEntry(controller.oidc.Hash(req.Code), client.ClientID)
|
entry, err := controller.oidc.GetCodeEntry(c, controller.oidc.Hash(req.Code), client.ClientID)
|
||||||
|
|
||||||
if !ok {
|
|
||||||
// ensure no code reuse
|
|
||||||
usedCodeSub, ok := controller.oidc.IsCodeUsed(controller.oidc.Hash(req.Code))
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
controller.log.App.Warn().Msg("Code reuse detected")
|
|
||||||
err := controller.oidc.DeleteSessionBySub(c, usedCodeSub)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to delete session for reused code")
|
if err := controller.oidc.DeleteTokenByCodeHash(c, controller.oidc.Hash(req.Code)); err != nil {
|
||||||
|
controller.log.App.Error().Err(err).Msg("Failed to revoke tokens for replayed code")
|
||||||
}
|
}
|
||||||
c.JSON(400, gin.H{
|
if errors.Is(err, service.ErrCodeNotFound) {
|
||||||
"error": "invalid_grant",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.log.App.Warn().Msg("Code not found")
|
controller.log.App.Warn().Msg("Code not found")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, gin.H{
|
||||||
"error": "invalid_grant",
|
"error": "invalid_grant",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if errors.Is(err, service.ErrCodeExpired) {
|
||||||
// mark code as used to prevent reuse
|
controller.log.App.Warn().Msg("Code expired")
|
||||||
controller.oidc.MarkCodeAsUsed(controller.oidc.Hash(req.Code), entry.Userinfo.Sub)
|
c.JSON(400, gin.H{
|
||||||
|
"error": "invalid_grant",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.Is(err, service.ErrInvalidClient) {
|
||||||
|
controller.log.App.Warn().Msg("Code does not belong to client")
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": "invalid_client",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.log.App.Error().Err(err).Msg("Failed to get code entry")
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": "server_error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if entry.RedirectURI != req.RedirectURI {
|
if entry.RedirectURI != req.RedirectURI {
|
||||||
controller.log.App.Warn().Msg("Redirect URI does not match")
|
controller.log.App.Warn().Msg("Redirect URI does not match")
|
||||||
@@ -526,7 +326,7 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = controller.oidc.ValidatePKCE(entry.CodeChallenge, req.CodeVerifier)
|
ok := controller.oidc.ValidatePKCE(entry.CodeChallenge, req.CodeVerifier)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
controller.log.App.Warn().Msg("PKCE validation failed")
|
controller.log.App.Warn().Msg("PKCE validation failed")
|
||||||
@@ -536,7 +336,7 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenRes, err := controller.oidc.GenerateAccessToken(c, client, *entry, entry.AuthTime)
|
tokenRes, err := controller.oidc.GenerateAccessToken(c, client, entry)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to generate access token")
|
controller.log.App.Error().Err(err).Msg("Failed to generate access token")
|
||||||
@@ -546,7 +346,7 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenResponse = *tokenRes
|
tokenResponse = tokenRes
|
||||||
case "refresh_token":
|
case "refresh_token":
|
||||||
tokenRes, err := controller.oidc.RefreshAccessToken(c, req.RefreshToken, creds.ClientID)
|
tokenRes, err := controller.oidc.RefreshAccessToken(c, req.RefreshToken, creds.ClientID)
|
||||||
|
|
||||||
@@ -574,7 +374,7 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenResponse = *tokenRes
|
tokenResponse = tokenRes
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Header("cache-control", "no-store")
|
c.Header("cache-control", "no-store")
|
||||||
@@ -638,7 +438,7 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
entry, err := controller.oidc.GetSessionByToken(c, controller.oidc.Hash(token))
|
entry, err := controller.oidc.GetAccessToken(c, controller.oidc.Hash(token))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, service.ErrTokenNotFound) {
|
if errors.Is(err, service.ErrTokenNotFound) {
|
||||||
@@ -657,17 +457,15 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we don't have the openid scope, return an error
|
// If we don't have the openid scope, return an error
|
||||||
if !slices.Contains(strings.Split(entry.Scope, " "), "openid") {
|
if !slices.Contains(strings.Split(entry.Scope, ","), "openid") {
|
||||||
controller.log.App.Warn().Msg("OIDC userinfo accessed with missing openid scope")
|
controller.log.App.Warn().Msg("OIDC userinfo accessed with token missing openid scope")
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, gin.H{
|
||||||
"error": "invalid_scope",
|
"error": "invalid_scope",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var userinfo service.UserinfoResponse
|
user, err := controller.oidc.GetUserinfo(c, entry.Sub)
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(entry.UserinfoJson), &userinfo)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to get user info")
|
controller.log.App.Error().Err(err).Msg("Failed to get user info")
|
||||||
@@ -677,55 +475,46 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, controller.oidc.CompileUserinfo(userinfo, entry.Scope))
|
c.JSON(200, controller.oidc.CompileUserinfo(user, entry.Scope))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (controller *OIDCController) authorizeError(c *gin.Context, params authorizeErrorParams) {
|
func (controller *OIDCController) authorizeError(c *gin.Context, err error, reason string, reasonUser string, callback string, callbackError string, state string) {
|
||||||
controller.log.App.Error().Err(params.err).Str("reason", params.reason).Msg("Authorization error")
|
controller.log.App.Warn().Err(err).Str("reason", reason).Msg("Authorization error")
|
||||||
|
|
||||||
if params.callback != "" {
|
if callback != "" {
|
||||||
errorQueries := CallbackError{
|
errorQueries := CallbackError{
|
||||||
Error: params.callbackError,
|
Error: callbackError,
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.reasonPublic != "" {
|
if reasonUser != "" {
|
||||||
errorQueries.ErrorDescription = params.reasonPublic
|
errorQueries.ErrorDescription = reasonUser
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.state != "" {
|
if state != "" {
|
||||||
errorQueries.State = params.state
|
errorQueries.State = state
|
||||||
}
|
}
|
||||||
|
|
||||||
queries, err := query.Values(errorQueries)
|
queries, err := query.Values(errorQueries)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to build callback error query")
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectUrl := fmt.Sprintf("%s?%s", params.callback, queries.Encode())
|
|
||||||
|
|
||||||
if params.json {
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"redirect_uri": redirectUrl,
|
"redirect_uri": fmt.Sprintf("%s?%s", callback, queries.Encode()),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, redirectUrl)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
errorQueries := ErrorScreen{
|
errorQueries := ErrorScreen{
|
||||||
Error: params.reasonPublic,
|
Error: reasonUser,
|
||||||
}
|
}
|
||||||
|
|
||||||
queries, err := query.Values(errorQueries)
|
queries, err := query.Values(errorQueries)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to build error query")
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -738,61 +527,8 @@ func (controller *OIDCController) authorizeError(c *gin.Context, params authoriz
|
|||||||
redirectUrl = fmt.Sprintf("%s/error?%s", controller.runtime.AppURL, queries.Encode())
|
redirectUrl = fmt.Sprintf("%s/error?%s", controller.runtime.AppURL, queries.Encode())
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.json {
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"redirect_uri": redirectUrl,
|
"redirect_uri": redirectUrl,
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, redirectUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (controller *OIDCController) resolveAuthorizeRequest(c *gin.Context) (*service.AuthorizeRequest, error) {
|
|
||||||
// step 1: if we have a request object, decode it and ignore other params. If not, bind the params as usual
|
|
||||||
// we check both query and form parameters for the request object since this endpoint can be called with both GET and POST
|
|
||||||
requestObject, err := controller.resolveRequestObject(c)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if requestObject != nil {
|
|
||||||
return requestObject, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// step 2: by default we assume normal GET query parameters
|
|
||||||
// step 3: if it's a POST request, we try form parameters
|
|
||||||
return controller.resolveNormalParams(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (controller *OIDCController) resolveRequestObject(c *gin.Context) (*service.AuthorizeRequest, error) {
|
|
||||||
raw := c.Query("request")
|
|
||||||
|
|
||||||
if raw == "" && c.Request.Method == http.MethodPost {
|
|
||||||
raw = c.PostForm("request")
|
|
||||||
}
|
|
||||||
|
|
||||||
if raw == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return controller.oidc.DecodeAuthorizeJWT(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (controller *OIDCController) resolveNormalParams(c *gin.Context) (*service.AuthorizeRequest, error) {
|
|
||||||
var req service.AuthorizeRequest
|
|
||||||
|
|
||||||
var bind binding.Binding = binding.Query
|
|
||||||
|
|
||||||
if c.Request.Method == http.MethodPost {
|
|
||||||
bind = binding.Form
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.ShouldBindWith(&req, bind); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &req, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
"go.uber.org/dig"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/go-querystring/query"
|
"github.com/google/go-querystring/query"
|
||||||
@@ -54,33 +53,29 @@ type ProxyContext struct {
|
|||||||
|
|
||||||
type ProxyController struct {
|
type ProxyController struct {
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
runtime *model.RuntimeConfig
|
runtime model.RuntimeConfig
|
||||||
acls *service.AccessControlsService
|
acls *service.AccessControlsService
|
||||||
auth *service.AuthService
|
auth *service.AuthService
|
||||||
policyEngine *service.PolicyEngine
|
policyEngine *service.PolicyEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyControllerInput struct {
|
func NewProxyController(
|
||||||
dig.In
|
log *logger.Logger,
|
||||||
|
runtime model.RuntimeConfig,
|
||||||
Log *logger.Logger
|
router *gin.RouterGroup,
|
||||||
RuntimeConfig *model.RuntimeConfig
|
acls *service.AccessControlsService,
|
||||||
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
|
auth *service.AuthService,
|
||||||
ACLsService *service.AccessControlsService
|
policyEngine *service.PolicyEngine,
|
||||||
AuthService *service.AuthService
|
) *ProxyController {
|
||||||
PolicyEngine *service.PolicyEngine
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProxyController(i ProxyControllerInput) *ProxyController {
|
|
||||||
controller := &ProxyController{
|
controller := &ProxyController{
|
||||||
log: i.Log,
|
log: log,
|
||||||
runtime: i.RuntimeConfig,
|
runtime: runtime,
|
||||||
acls: i.ACLsService,
|
acls: acls,
|
||||||
auth: i.AuthService,
|
auth: auth,
|
||||||
policyEngine: i.PolicyEngine,
|
policyEngine: policyEngine,
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyGroup := i.RouterGroup.Group("/auth")
|
proxyGroup := router.Group("/auth")
|
||||||
proxyGroup.Any("/:proxy", controller.proxyHandler)
|
proxyGroup.Any("/:proxy", controller.proxyHandler)
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
@@ -158,17 +153,14 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, redirectURL)
|
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userContext, err := new(model.UserContext).NewFromGin(c)
|
userContext, err := new(model.UserContext).NewFromGin(c)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// No user context found is not an issue
|
controller.log.App.Debug().Err(err).Msg("Failed to create user context from request, treating as unauthenticated")
|
||||||
if !errors.Is(err, model.ErrUserContextNotFound) {
|
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to create user context from request, treating as unauthenticated")
|
|
||||||
}
|
|
||||||
userContext = &model.UserContext{
|
userContext = &model.UserContext{
|
||||||
Authenticated: false,
|
Authenticated: false,
|
||||||
}
|
}
|
||||||
@@ -207,7 +199,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, redirectURL)
|
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +243,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, redirectURL)
|
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,7 +272,6 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
|
|
||||||
queries, err := query.Values(RedirectQuery{
|
queries, err := query.Values(RedirectQuery{
|
||||||
RedirectURI: fmt.Sprintf("%s://%s%s", proxyCtx.Proto, proxyCtx.Host, proxyCtx.Path),
|
RedirectURI: fmt.Sprintf("%s://%s%s", proxyCtx.Proto, proxyCtx.Host, proxyCtx.Path),
|
||||||
LoginFor: FrontendLoginForApp,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -300,7 +291,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, redirectURL)
|
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (controller *ProxyController) setHeaders(c *gin.Context, acls *model.App) {
|
func (controller *ProxyController) setHeaders(c *gin.Context, acls *model.App) {
|
||||||
@@ -336,7 +327,7 @@ func (controller *ProxyController) handleError(c *gin.Context, proxyCtx ProxyCon
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, redirectURL)
|
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (controller *ProxyController) getHeader(c *gin.Context, header string) (string, bool) {
|
func (controller *ProxyController) getHeader(c *gin.Context, header string) (string, bool) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user