mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-07-10 20:11:13 +00:00
Compare commits
9 Commits
main
..
5d2ca65ea4
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d2ca65ea4 | |||
| f841095b27 | |||
| eb0a925ea3 | |||
| f9fd457e28 | |||
| e38c4710d4 | |||
| f8b0188776 | |||
| 7b5d882ee8 | |||
| b9abab2f17 | |||
| 3fd56272d2 |
+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,33 +13,21 @@ 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
|
|
||||||
uses: sqlc-dev/setup-sqlc@v5
|
|
||||||
with:
|
|
||||||
sqlc-version: "1.31.1"
|
|
||||||
|
|
||||||
- name: Check codegen is up to date
|
|
||||||
run: |
|
|
||||||
sqlc generate
|
|
||||||
go generate ./...
|
|
||||||
git diff --exit-code
|
|
||||||
git status --porcelain | grep -q . && echo "untracked files code gen files" && exit 1 || true
|
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
run: pnpm ci
|
run: pnpm ci
|
||||||
@@ -62,6 +50,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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 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@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 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@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # 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
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
name: Close stale issues and PRs
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: 0 10 * * *
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10
|
||||||
|
with:
|
||||||
|
days-before-stale: 30
|
||||||
|
stale-pr-message: This PR has been inactive for 30 days and will be marked as stale.
|
||||||
|
stale-issue-message: This issue has been inactive for 30 days and will be marked as stale.
|
||||||
|
close-issue-message: Closed for inactivity.
|
||||||
|
close-pr-message: Closed for inactivity.
|
||||||
|
stale-issue-label: stale
|
||||||
|
stale-pr-label: stale
|
||||||
|
exempt-issue-labels: pinned
|
||||||
|
exempt-pr-labels: pinned
|
||||||
@@ -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.5-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.5-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
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 19 November 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
@@ -7,15 +7,17 @@
|
|||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU Affero General Public License is a free, copyleft license for
|
The GNU General Public License is a free, copyleft license for
|
||||||
software and other kinds of works, specifically designed to ensure
|
software and other kinds of works.
|
||||||
cooperation with the community in the case of network server software.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
our General Public Licenses are intended to guarantee your freedom to
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
share and change all versions of a program--to make sure it remains free
|
share and change all versions of a program--to make sure it remains free
|
||||||
software for all its users.
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
@@ -24,34 +26,44 @@ them if you wish), that you receive source code or can get it if you
|
|||||||
want it, that you can change the software or use pieces of it in new
|
want it, that you can change the software or use pieces of it in new
|
||||||
free programs, and that you know you can do these things.
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
Developers that use our General Public Licenses protect your rights
|
To protect your rights, we need to prevent others from denying you
|
||||||
with two steps: (1) assert copyright on the software, and (2) offer
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
you this License which gives you legal permission to copy, distribute
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
and/or modify the software.
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
A secondary benefit of defending all users' freedom is that
|
For example, if you distribute copies of such a program, whether
|
||||||
improvements made in alternate versions of the program, if they
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
receive widespread use, become available for other developers to
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
incorporate. Many developers of free software are heartened and
|
or can get the source code. And you must show them these terms so they
|
||||||
encouraged by the resulting cooperation. However, in the case of
|
know their rights.
|
||||||
software used on network servers, this result may fail to come about.
|
|
||||||
The GNU General Public License permits making a modified version and
|
|
||||||
letting the public access it on a server without ever releasing its
|
|
||||||
source code to the public.
|
|
||||||
|
|
||||||
The GNU Affero General Public License is designed specifically to
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
ensure that, in such cases, the modified source code becomes available
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
to the community. It requires the operator of a network server to
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
provide the source code of the modified version running there to the
|
|
||||||
users of that server. Therefore, public use of a modified version, on
|
|
||||||
a publicly accessible server, gives the public access to the source
|
|
||||||
code of the modified version.
|
|
||||||
|
|
||||||
An older license, called the Affero General Public License and
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
published by Affero, was designed to accomplish similar goals. This is
|
that there is no warranty for this free software. For both users' and
|
||||||
a different license, not a version of the Affero GPL, but Affero has
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
released a new version of the Affero GPL which permits relicensing under
|
changed, so that their problems will not be attributed erroneously to
|
||||||
this license.
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
@@ -60,7 +72,7 @@ modification follow.
|
|||||||
|
|
||||||
0. Definitions.
|
0. Definitions.
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
works, such as semiconductor masks.
|
||||||
@@ -537,45 +549,35 @@ to collect a royalty for further conveying from those to whom you convey
|
|||||||
the Program, the only way you could satisfy both those terms and this
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, if you modify the
|
|
||||||
Program, your modified version must prominently offer all users
|
|
||||||
interacting with it remotely through a computer network (if your version
|
|
||||||
supports such interaction) an opportunity to receive the Corresponding
|
|
||||||
Source of your version by providing access to the Corresponding Source
|
|
||||||
from a network server at no charge, through some standard or customary
|
|
||||||
means of facilitating copying of software. This Corresponding Source
|
|
||||||
shall include the Corresponding Source for any work covered by version 3
|
|
||||||
of the GNU General Public License that is incorporated pursuant to the
|
|
||||||
following paragraph.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
permission to link or combine any covered work with a work licensed
|
||||||
under version 3 of the GNU General Public License into a single
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
combined work, and to convey the resulting work. The terms of this
|
combined work, and to convey the resulting work. The terms of this
|
||||||
License will continue to apply to the part which is the covered work,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the work with which it is combined will remain governed by version
|
but the special requirements of the GNU Affero General Public License,
|
||||||
3 of the GNU General Public License.
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
the GNU Affero General Public License from time to time. Such new versions
|
the GNU General Public License from time to time. Such new versions will
|
||||||
will be similar in spirit to the present version, but may differ in detail to
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
Each version is given a distinguishing version number. If the
|
||||||
Program specifies that a certain numbered version of the GNU Affero General
|
Program specifies that a certain numbered version of the GNU General
|
||||||
Public License "or any later version" applies to it, you have the
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
Foundation. If the Program does not specify a version number of the
|
||||||
GNU Affero General Public License, you may choose any version ever published
|
GNU General Public License, you may choose any version ever published
|
||||||
by the Free Software Foundation.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
If the Program specifies that a proxy can decide which future
|
||||||
versions of the GNU Affero General Public License can be used, that proxy's
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
public statement of acceptance of a version permanently authorizes you
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
to choose that version for the Program.
|
||||||
|
|
||||||
@@ -633,29 +635,40 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||||||
Copyright (C) <year> <name of author>
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU Affero General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If your software can interact with users remotely through a computer
|
If the program does terminal interaction, make it output a short
|
||||||
network, you should also make sure that it provides a way for users to
|
notice like this when it starts in an interactive mode:
|
||||||
get its source. For example, if your program is a web application, its
|
|
||||||
interface could display a "Source" link that leads users to an archive
|
<program> Copyright (C) <year> <name of author>
|
||||||
of the code. There are many ways you could offer source, and different
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
solutions will be better for different programs; see section 13 for the
|
This is free software, and you are welcome to redistribute it
|
||||||
specific requirements.
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
<https://www.gnu.org/licenses/>.
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
@@ -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,10 @@ 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
|
||||||
|
|
||||||
# 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,9 +28,8 @@ 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).
|
> [!NOTE]
|
||||||
|
> Tinyauth is in the process of migrating to the new [tinyauthapp](https://github.com/tinyauthapp) organization. The organization **is official** and it will host all of the Tinyauth related repositories in the future.
|
||||||
<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
|
||||||
|
|
||||||
@@ -60,22 +59,13 @@ If you like, you can help translate Tinyauth into more languages by visiting the
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
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 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) GPL-licensed code must also be made available under the GPL along with build & install instructions. 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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:v3.6
|
image: traefik:v3.6
|
||||||
command: --api.insecure=true --providers.docker --entrypoints.web.address=:80 --entrypoints.websecure.address=:443
|
command: --api.insecure=true --providers.docker
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
- 443:443
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
@@ -26,8 +25,6 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
traefik.http.routers.tinyauth.rule: Host(`tinyauth.127.0.0.1.sslip.io`)
|
traefik.http.routers.tinyauth.rule: Host(`tinyauth.127.0.0.1.sslip.io`)
|
||||||
traefik.http.routers.tinyauth.entrypoints: websecure
|
|
||||||
traefik.http.routers.tinyauth.tls: true
|
|
||||||
|
|
||||||
tinyauth-backend:
|
tinyauth-backend:
|
||||||
build:
|
build:
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { Navigate } from "react-router";
|
|||||||
import { useUserContext } from "./context/user-context";
|
import { useUserContext } from "./context/user-context";
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const { auth } = useUserContext();
|
const { isLoggedIn } = useUserContext();
|
||||||
|
|
||||||
if (auth.authenticated) {
|
if (isLoggedIn) {
|
||||||
return <Navigate to="/logout" replace />;
|
return <Navigate to="/logout" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,28 +1,29 @@
|
|||||||
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 { backgroundImage, title } = useAppContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = ui.title;
|
document.title = title;
|
||||||
}, [ui.title]);
|
}, [title]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col justify-center items-center min-h-svh px-4"
|
className="flex flex-col justify-center items-center min-h-svh px-4"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${ui.backgroundImage})`,
|
backgroundImage: `url(${backgroundImage})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
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>
|
||||||
@@ -30,7 +31,7 @@ const BaseLayout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Layout = () => {
|
export const Layout = () => {
|
||||||
const { app, ui } = useAppContext();
|
const { appUrl, warningsEnabled } = useAppContext();
|
||||||
const [ignoreDomainWarning, setIgnoreDomainWarning] = useState(() => {
|
const [ignoreDomainWarning, setIgnoreDomainWarning] = useState(() => {
|
||||||
return window.sessionStorage.getItem("ignoreDomainWarning") === "true";
|
return window.sessionStorage.getItem("ignoreDomainWarning") === "true";
|
||||||
});
|
});
|
||||||
@@ -41,22 +42,11 @@ export const Layout = () => {
|
|||||||
setIgnoreDomainWarning(true);
|
setIgnoreDomainWarning(true);
|
||||||
}, [setIgnoreDomainWarning]);
|
}, [setIgnoreDomainWarning]);
|
||||||
|
|
||||||
const isTrusted = (() => {
|
if (!ignoreDomainWarning && warningsEnabled && appUrl !== currentUrl) {
|
||||||
try {
|
|
||||||
const appUrlObj = new URL(app.appUrl);
|
|
||||||
const currentUrlObj = new URL(currentUrl);
|
|
||||||
|
|
||||||
return isTrustedDomain(currentUrlObj, appUrlObj, "", false);
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (!ignoreDomainWarning && ui.warningsEnabled && !isTrusted) {
|
|
||||||
return (
|
return (
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<DomainWarning
|
<DomainWarning
|
||||||
appUrl={app.appUrl}
|
appUrl={appUrl}
|
||||||
currentUrl={currentUrl}
|
currentUrl={currentUrl}
|
||||||
onClick={() => handleIgnore()}
|
onClick={() => handleIgnore()}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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",
|
||||||
@@ -80,26 +80,5 @@
|
|||||||
"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",
|
"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",
|
||||||
@@ -84,22 +84,5 @@
|
|||||||
"phoneScopeName": "Phone",
|
"phoneScopeName": "Phone",
|
||||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
"phoneScopeDescription": "Allows the app to access your phone number.",
|
||||||
"addressScopeName": "Address",
|
"addressScopeName": "Address",
|
||||||
"addressScopeDescription": "Allows the app to access your 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": "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;
|
||||||
@@ -80,32 +77,34 @@ const createScopeMap = (t: TFunction<"translation", undefined>): Scope[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AuthorizePage = () => {
|
export const AuthorizePage = () => {
|
||||||
const { auth } = useUserContext();
|
const { isLoggedIn } = useUserContext();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
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 (!isLoggedIn) {
|
||||||
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,15 +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}
|
loading={authorizeMutation.isPending}
|
||||||
disabled={shouldAutoAuthorize}
|
|
||||||
>
|
>
|
||||||
{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,14 +12,10 @@ 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 { cookieDomain, warningsEnabled } = useAppContext();
|
||||||
const { auth } = useUserContext();
|
const { isLoggedIn } = useUserContext();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -29,31 +25,24 @@ 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,
|
cookieDomain,
|
||||||
app.appUrl,
|
|
||||||
app.subdomainsEnabled,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const urlHref = url?.href;
|
const urlHref = url?.href;
|
||||||
|
|
||||||
const hasValidRedirect = valid && allowedProto;
|
const hasValidRedirect = valid && allowedProto;
|
||||||
const showUntrustedWarning =
|
const showUntrustedWarning = hasValidRedirect && !trusted && warningsEnabled;
|
||||||
hasValidRedirect && !trusted && ui.warningsEnabled;
|
|
||||||
const showInsecureWarning =
|
const showInsecureWarning =
|
||||||
hasValidRedirect && httpsDowngrade && ui.warningsEnabled;
|
hasValidRedirect && httpsDowngrade && warningsEnabled;
|
||||||
const shouldAutoRedirect =
|
const shouldAutoRedirect =
|
||||||
auth.authenticated &&
|
isLoggedIn &&
|
||||||
hasValidRedirect &&
|
hasValidRedirect &&
|
||||||
!showUntrustedWarning &&
|
!showUntrustedWarning &&
|
||||||
!showInsecureWarning &&
|
!showInsecureWarning;
|
||||||
isAppLogin;
|
|
||||||
|
|
||||||
const redirectToTarget = useCallback(() => {
|
const redirectToTarget = useCallback(() => {
|
||||||
if (!urlHref || hasRedirected.current) {
|
if (!urlHref || hasRedirected.current) {
|
||||||
@@ -88,11 +77,16 @@ export const ContinuePage = () => {
|
|||||||
};
|
};
|
||||||
}, [shouldAutoRedirect, redirectToTarget]);
|
}, [shouldAutoRedirect, redirectToTarget]);
|
||||||
|
|
||||||
if (!auth.authenticated) {
|
if (!isLoggedIn) {
|
||||||
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 +104,7 @@ export const ContinuePage = () => {
|
|||||||
components={{
|
components={{
|
||||||
code: <code />,
|
code: <code />,
|
||||||
}}
|
}}
|
||||||
values={{
|
values={{ 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 { forgotPasswordMessage } = 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>
|
||||||
@@ -32,8 +26,8 @@ export const ForgotPasswordPage = () => {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<Markdown>
|
<Markdown>
|
||||||
{ui.forgotPasswordMessage !== ""
|
{forgotPasswordMessage !== ""
|
||||||
? ui.forgotPasswordMessage
|
? forgotPasswordMessage
|
||||||
: t("forgotPasswordMessage")}
|
: t("forgotPasswordMessage")}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
@@ -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 />,
|
||||||
@@ -40,19 +36,12 @@ const iconMap: Record<string, React.ReactNode> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const LoginPage = () => {
|
export const LoginPage = () => {
|
||||||
const { auth, tailscale } = useUserContext();
|
const { isLoggedIn } = useUserContext();
|
||||||
const {
|
const { providers, title, oauthAutoRedirect } = useAppContext();
|
||||||
ui,
|
|
||||||
oauth,
|
|
||||||
auth: { providers },
|
|
||||||
} = useAppContext();
|
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [showRedirectButton, setShowRedirectButton] = useState(false);
|
const [showRedirectButton, setShowRedirectButton] = useState(false);
|
||||||
const [useTailscale, setUseTailscale] = useState(
|
|
||||||
tailscale.nodeName !== undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasAutoRedirectedRef = useRef(false);
|
const hasAutoRedirectedRef = useRef(false);
|
||||||
|
|
||||||
@@ -62,25 +51,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 === oauthAutoRedirect) !==
|
||||||
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 +74,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 +114,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 +129,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) => {
|
||||||
@@ -146,45 +148,23 @@ export const LoginPage = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: tailscaleMutate, isPending: tailscaleIsPending } =
|
|
||||||
useMutation({
|
|
||||||
mutationFn: () => axios.post("/api/user/tailscale"),
|
|
||||||
mutationKey: ["tailscale"],
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success(t("loginSuccessTitle"), {
|
|
||||||
description: t("loginTailscaleSuccess"),
|
|
||||||
});
|
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
|
||||||
window.location.replace(loginForUrl);
|
|
||||||
}, 500);
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
toast.error(t("loginFailTitle"), {
|
|
||||||
description: t("loginTailscaleFail"),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!auth.authenticated &&
|
!isLoggedIn &&
|
||||||
isOauthAutoRedirect &&
|
isOauthAutoRedirect &&
|
||||||
!hasAutoRedirectedRef.current &&
|
!hasAutoRedirectedRef.current &&
|
||||||
screenParams.redirect_uri &&
|
redirectUri !== undefined
|
||||||
screenParams.login_for
|
|
||||||
) {
|
) {
|
||||||
hasAutoRedirectedRef.current = true;
|
hasAutoRedirectedRef.current = true;
|
||||||
oauthMutate(oauth.autoRedirect);
|
oauthMutate(oauthAutoRedirect);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
auth.authenticated,
|
isLoggedIn,
|
||||||
oauthMutate,
|
oauthMutate,
|
||||||
hasAutoRedirectedRef,
|
hasAutoRedirectedRef,
|
||||||
oauth.autoRedirect,
|
oauthAutoRedirect,
|
||||||
isOauthAutoRedirect,
|
isOauthAutoRedirect,
|
||||||
screenParams.login_for,
|
redirectUri,
|
||||||
screenParams.redirect_uri,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -199,8 +179,21 @@ export const LoginPage = () => {
|
|||||||
};
|
};
|
||||||
}, [redirectTimer, redirectButtonTimer]);
|
}, [redirectTimer, redirectButtonTimer]);
|
||||||
|
|
||||||
if (auth.authenticated && screenParams.oidc_prompt !== "login") {
|
if (isLoggedIn && oidcParams.isOidc) {
|
||||||
return <Navigate to={loginForUrl} replace />;
|
return <Navigate to={`/authorize?${oidcParams.compiled}`} replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoggedIn && redirectUri !== undefined) {
|
||||||
|
return (
|
||||||
|
<Navigate
|
||||||
|
to={`/continue${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`}
|
||||||
|
replace
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoggedIn) {
|
||||||
|
return <Navigate to="/logout" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOauthAutoRedirect) {
|
if (isOauthAutoRedirect) {
|
||||||
@@ -235,49 +228,10 @@ export const LoginPage = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useTailscale) {
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="gap-3">
|
|
||||||
<TailscaleIcon className="mx-auto h-8 w-8" />
|
|
||||||
<CardTitle className="text-center text-xl">
|
|
||||||
{t("loginTailscaleTitle")}
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="flex flex-col gap-4">
|
|
||||||
<div className="text-muted-foreground text-sm">
|
|
||||||
{t("loginTailscaleDescription")}
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground text-sm">
|
|
||||||
{t("loginTailscaleDeviceName")} <code>{tailscale.nodeName}</code>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="flex flex-col items-stretch gap-3">
|
|
||||||
<Button
|
|
||||||
className="w-full"
|
|
||||||
onClick={() => tailscaleMutate()}
|
|
||||||
loading={tailscaleIsPending}
|
|
||||||
>
|
|
||||||
{t("loginTailscaleTitle")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
className="w-full"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => setUseTailscale(false)}
|
|
||||||
disabled={tailscaleIsPending}
|
|
||||||
>
|
|
||||||
{t("loginTailscaleOtherMethod")}
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="gap-1.5">
|
<CardHeader className="gap-1.5">
|
||||||
<CardTitle className="text-center text-xl">{ui.title}</CardTitle>
|
<CardTitle className="text-center text-xl">{title}</CardTitle>
|
||||||
{providers.length > 0 && (
|
{providers.length > 0 && (
|
||||||
<CardDescription className="text-center">
|
<CardDescription className="text-center">
|
||||||
{oauthProviders.length !== 0
|
{oauthProviders.length !== 0
|
||||||
|
|||||||
@@ -13,23 +13,12 @@ import { useEffect, useRef } from "react";
|
|||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { Navigate } from "react-router";
|
import { Navigate } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { type UseMutationResult } from "@tanstack/react-query";
|
|
||||||
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 { provider, username, isLoggedIn, email, oauthName } = 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 +29,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: () => {
|
||||||
@@ -58,13 +47,16 @@ export const LogoutPage = () => {
|
|||||||
};
|
};
|
||||||
}, [redirectTimer]);
|
}, [redirectTimer]);
|
||||||
|
|
||||||
if (!auth.authenticated) {
|
if (!isLoggedIn) {
|
||||||
return <Navigate to={`/login${compiledParams}`} replace />;
|
return <Navigate to="/login" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oauth.active) {
|
|
||||||
return (
|
return (
|
||||||
<LogoutLayout logoutMutation={logoutMutation}>
|
<Card>
|
||||||
|
<CardHeader className="gap-1.5">
|
||||||
|
<CardTitle className="text-xl">{t("logoutTitle")}</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
{provider !== "local" && provider !== "ldap" ? (
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="logoutOauthSubtitle"
|
i18nKey="logoutOauthSubtitle"
|
||||||
t={t}
|
t={t}
|
||||||
@@ -72,35 +64,12 @@ export const LogoutPage = () => {
|
|||||||
code: <code />,
|
code: <code />,
|
||||||
}}
|
}}
|
||||||
values={{
|
values={{
|
||||||
username: auth.email,
|
username: email,
|
||||||
provider: oauth.displayName,
|
provider: oauthName,
|
||||||
}}
|
}}
|
||||||
shouldUnescape={true}
|
shouldUnescape={true}
|
||||||
/>
|
/>
|
||||||
</LogoutLayout>
|
) : (
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth.providerId === "tailscale") {
|
|
||||||
return (
|
|
||||||
<LogoutLayout logoutMutation={logoutMutation}>
|
|
||||||
<Trans
|
|
||||||
i18nKey="logoutTailscaleSubtitle"
|
|
||||||
t={t}
|
|
||||||
components={{
|
|
||||||
code: <code />,
|
|
||||||
}}
|
|
||||||
values={{
|
|
||||||
deviceName: tailscale.nodeName,
|
|
||||||
}}
|
|
||||||
shouldUnescape={true}
|
|
||||||
/>
|
|
||||||
</LogoutLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LogoutLayout logoutMutation={logoutMutation}>
|
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="logoutUsernameSubtitle"
|
i18nKey="logoutUsernameSubtitle"
|
||||||
t={t}
|
t={t}
|
||||||
@@ -108,36 +77,16 @@ export const LogoutPage = () => {
|
|||||||
code: <code />,
|
code: <code />,
|
||||||
}}
|
}}
|
||||||
values={{
|
values={{
|
||||||
username: auth.username,
|
username,
|
||||||
}}
|
}}
|
||||||
shouldUnescape={true}
|
shouldUnescape={true}
|
||||||
/>
|
/>
|
||||||
</LogoutLayout>
|
)}
|
||||||
);
|
</CardDescription>
|
||||||
};
|
|
||||||
|
|
||||||
interface LogoutLayoutProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
logoutMutation: UseMutationResult<
|
|
||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-empty-object-type
|
|
||||||
AxiosResponse<any, any, {}>,
|
|
||||||
Error,
|
|
||||||
void,
|
|
||||||
unknown
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function LogoutLayout({ children, logoutMutation }: LogoutLayoutProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="gap-1.5">
|
|
||||||
<CardTitle className="text-xl">{t("logoutTitle")}</CardTitle>
|
|
||||||
<CardDescription>{children}</CardDescription>
|
|
||||||
</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()}
|
||||||
@@ -147,4 +96,4 @@ function LogoutLayout({ children, logoutMutation }: LogoutLayoutProps) {
|
|||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -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 { totpPending } = 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: () => {
|
||||||
@@ -65,11 +64,8 @@ export const TotpPage = () => {
|
|||||||
};
|
};
|
||||||
}, [redirectTimer]);
|
}, [redirectTimer]);
|
||||||
|
|
||||||
if (!totp.pending) {
|
if (!totpPending) {
|
||||||
if (auth.authenticated) {
|
return <Navigate to="/" replace />;
|
||||||
return <Navigate to={loginForUrl} replace />;
|
|
||||||
}
|
|
||||||
return <Navigate to={`/login${compiledParams}`} replace />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,32 +6,15 @@ export const providerSchema = z.object({
|
|||||||
oauth: z.boolean(),
|
oauth: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const authSchema = z.object({
|
export const appContextSchema = z.object({
|
||||||
providers: z.array(providerSchema),
|
providers: z.array(providerSchema),
|
||||||
});
|
|
||||||
|
|
||||||
const oauthSchema = z.object({
|
|
||||||
autoRedirect: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const uiSchema = z.object({
|
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
|
appUrl: z.string(),
|
||||||
|
cookieDomain: z.string(),
|
||||||
forgotPasswordMessage: z.string(),
|
forgotPasswordMessage: z.string(),
|
||||||
backgroundImage: z.string(),
|
backgroundImage: z.string(),
|
||||||
|
oauthAutoRedirect: z.string(),
|
||||||
warningsEnabled: z.boolean(),
|
warningsEnabled: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const appSchema = z.object({
|
|
||||||
appUrl: z.string(),
|
|
||||||
cookieDomain: z.string(),
|
|
||||||
subdomainsEnabled: z.boolean(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const appContextSchema = z.object({
|
|
||||||
auth: authSchema,
|
|
||||||
oauth: oauthSchema,
|
|
||||||
ui: uiSchema,
|
|
||||||
app: appSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type AppContextSchema = z.infer<typeof appContextSchema>;
|
export type AppContextSchema = z.infer<typeof appContextSchema>;
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const getOidcClientInfoSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
});
|
||||||
@@ -1,31 +1,14 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const authSchema = z.object({
|
export const userContextSchema = z.object({
|
||||||
authenticated: z.boolean(),
|
isLoggedIn: z.boolean(),
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
email: z.string(),
|
email: z.string(),
|
||||||
providerId: z.string(),
|
provider: z.string(),
|
||||||
});
|
oauth: z.boolean(),
|
||||||
|
totpPending: z.boolean(),
|
||||||
const oauthSchema = z.object({
|
oauthName: z.string(),
|
||||||
active: z.boolean(),
|
|
||||||
displayName: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const totpSchema = z.object({
|
|
||||||
pending: z.boolean(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const tailscaleSchema = z.object({
|
|
||||||
nodeName: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const userContextSchema = z.object({
|
|
||||||
auth: authSchema,
|
|
||||||
oauth: oauthSchema,
|
|
||||||
totp: totpSchema,
|
|
||||||
tailscale: tailscaleSchema,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type UserContextSchema = z.infer<typeof userContextSchema>;
|
export type UserContextSchema = z.infer<typeof userContextSchema>;
|
||||||
|
|||||||
@@ -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,473 +0,0 @@
|
|||||||
// gen/sqlc_wrapper generates store.go wrapper files for each sqlc driver package under
|
|
||||||
// internal/repository/<driver>/. Run via:
|
|
||||||
//
|
|
||||||
// go generate ./internal/repository/...
|
|
||||||
//
|
|
||||||
// The generator introspects *Queries methods and the model/params types in the
|
|
||||||
// driver package, then emits a store.go that wraps *Queries so it satisfies
|
|
||||||
// repository.Store using the canonical shared types in the parent package.
|
|
||||||
// This generator is specific to sqlc-generated drivers. Non-sqlc drivers should
|
|
||||||
// implement repository.Store directly by hand.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
_ "embed"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"go/format"
|
|
||||||
"go/types"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/packages"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed store.tmpl
|
|
||||||
var storeSrc string
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println("sqlc_wrapper: generating store.go files for sqlc driver packages...")
|
|
||||||
if err := run(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run() error {
|
|
||||||
driverPkg := flag.String("pkg", "", "import path of the driver package")
|
|
||||||
out := flag.String("out", "store.go", "output filename relative to driver package directory")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *driverPkg == "" {
|
|
||||||
return fmt.Errorf("-pkg is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve the driver package directory so we can overlay the output file
|
|
||||||
// with a valid stub. This prevents a stale store.go from poisoning the
|
|
||||||
// type-checker and producing cryptic "undefined" errors.
|
|
||||||
driverDir, err := pkgDir(*driverPkg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("resolve driver dir: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
outPath := filepath.Join(driverDir, *out)
|
|
||||||
if filepath.IsAbs(*out) {
|
|
||||||
outPath = *out
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub replaces the output file during load so stale generated code is ignored.
|
|
||||||
stub := []byte("package " + filepath.Base(driverDir) + "\n")
|
|
||||||
cfg := &packages.Config{
|
|
||||||
Mode: packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedImports,
|
|
||||||
Overlay: map[string][]byte{outPath: stub},
|
|
||||||
}
|
|
||||||
|
|
||||||
driverTypePkg, err := loadOnePkg(cfg, *driverPkg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("load driver package: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
repoPkgPath := parentPkg(*driverPkg)
|
|
||||||
repoTypePkg, err := loadOnePkg(cfg, repoPkgPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("load repo package: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateStructShapes(driverTypePkg, repoTypePkg); err != nil {
|
|
||||||
return fmt.Errorf("struct shape mismatch: %w", err)
|
|
||||||
}
|
|
||||||
if err := validateStoreCoverage(driverTypePkg, repoTypePkg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
methods, err := collectMethods(driverTypePkg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
src, err := render(tmplData{
|
|
||||||
PkgName: driverTypePkg.Name(),
|
|
||||||
RepoPkg: repoPkgPath,
|
|
||||||
Methods: renderMethods(methods),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("render: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.WriteFile(outPath, src, 0644); err != nil {
|
|
||||||
return fmt.Errorf("write %s: %w", outPath, err)
|
|
||||||
}
|
|
||||||
fmt.Printf("wrote %s\n", outPath)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadOnePkg loads a single package via cfg and returns its *types.Package,
|
|
||||||
// or an error if the package fails to load or has type errors.
|
|
||||||
func loadOnePkg(cfg *packages.Config, importPath string) (*types.Package, error) {
|
|
||||||
pkgs, err := packages.Load(cfg, importPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("load %s: %w", importPath, err)
|
|
||||||
}
|
|
||||||
if len(pkgs) != 1 {
|
|
||||||
return nil, fmt.Errorf("expected 1 package for %s, got %d", importPath, len(pkgs))
|
|
||||||
}
|
|
||||||
pkg := pkgs[0]
|
|
||||||
if len(pkg.Errors) > 0 {
|
|
||||||
msgs := make([]string, len(pkg.Errors))
|
|
||||||
for i, e := range pkg.Errors {
|
|
||||||
msgs[i] = e.Error()
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("package %s has errors:\n %s", importPath, strings.Join(msgs, "\n "))
|
|
||||||
}
|
|
||||||
return pkg.Types, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parentPkg returns the parent import path (everything before the last /).
|
|
||||||
// Panics if imp contains no slash — callers are expected to pass driver sub-packages.
|
|
||||||
func parentPkg(imp string) string {
|
|
||||||
i := strings.LastIndex(imp, "/")
|
|
||||||
if i < 0 {
|
|
||||||
panic(fmt.Sprintf("parentPkg: import path %q has no parent", imp))
|
|
||||||
}
|
|
||||||
return imp[:i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// pkgDir returns the on-disk directory for an import path using `go list`.
|
|
||||||
func pkgDir(importPath string) (string, error) {
|
|
||||||
out, err := exec.Command("go", "list", "-f", "{{.Dir}}", importPath).Output()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("go list %s: %w", importPath, err)
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(string(out)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// scopeStructs returns all named struct types in pkg, excluding the internal
|
|
||||||
// sqlc types Queries, DBTX, and Store. Names are returned in sorted order.
|
|
||||||
func scopeStructs(pkg *types.Package) (names []string, byName map[string]*types.Struct) {
|
|
||||||
byName = make(map[string]*types.Struct)
|
|
||||||
for _, name := range pkg.Scope().Names() { // Names() is already sorted
|
|
||||||
switch name {
|
|
||||||
case "Queries", "DBTX", "Store":
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
obj, ok := pkg.Scope().Lookup(name).(*types.TypeName)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
named, ok := obj.Type().(*types.Named)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s, ok := named.Underlying().(*types.Struct)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
names = append(names, name)
|
|
||||||
byName[name] = s
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateStoreCoverage checks that every method declared in repository.Store
|
|
||||||
// exists on *Queries in the driver package. Missing methods are reported by
|
|
||||||
// name so the developer knows exactly which SQL queries need to be added.
|
|
||||||
func validateStoreCoverage(driverPkg, repoPkg *types.Package) error {
|
|
||||||
queriesObj := driverPkg.Scope().Lookup("Queries")
|
|
||||||
if queriesObj == nil {
|
|
||||||
return fmt.Errorf("queries type not found in driver package")
|
|
||||||
}
|
|
||||||
queriesNamed := queriesObj.Type().(*types.Named)
|
|
||||||
queriesMS := types.NewMethodSet(types.NewPointer(queriesNamed))
|
|
||||||
queriesMethods := make(map[string]bool)
|
|
||||||
for m := range queriesMS.Methods() {
|
|
||||||
queriesMethods[m.Obj().Name()] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
storeObj := repoPkg.Scope().Lookup("Store")
|
|
||||||
if storeObj == nil {
|
|
||||||
return fmt.Errorf("store type not found in repository package")
|
|
||||||
}
|
|
||||||
storeIface, ok := storeObj.Type().Underlying().(*types.Interface)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("repository.Store is not an interface")
|
|
||||||
}
|
|
||||||
|
|
||||||
var missing []string
|
|
||||||
for method := range storeIface.Methods() {
|
|
||||||
if name := method.Name(); !queriesMethods[name] {
|
|
||||||
missing = append(missing, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(missing) > 0 {
|
|
||||||
sort.Strings(missing)
|
|
||||||
return fmt.Errorf(
|
|
||||||
"driver *Queries is missing %d method(s) required by repository.Store:\n - %s\n\nRun sqlc generate to regenerate query methods, or add the missing SQL queries",
|
|
||||||
len(missing), strings.Join(missing, "\n - "),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateStructShapes checks that every model/params struct in the driver
|
|
||||||
// package has fields that exactly match the corresponding type in the repo
|
|
||||||
// (parent) package. This catches drift between sqlc-generated types and the
|
|
||||||
// canonical repository types before a broken cast reaches the compiler.
|
|
||||||
func validateStructShapes(driverPkg, repoPkg *types.Package) error {
|
|
||||||
_, repoStructs := scopeStructs(repoPkg)
|
|
||||||
driverNames, driverStructs := scopeStructs(driverPkg)
|
|
||||||
|
|
||||||
var errs []string
|
|
||||||
for _, name := range driverNames {
|
|
||||||
repoStruct, ok := repoStructs[name]
|
|
||||||
if !ok {
|
|
||||||
// Driver has a type not in repo — fine (e.g. internal helpers).
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := compareStructs(name, driverStructs[name], repoStruct); err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
sort.Strings(errs)
|
|
||||||
return fmt.Errorf("%s", strings.Join(errs, "\n "))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func compareStructs(name string, driver, repo *types.Struct) error {
|
|
||||||
if driver.NumFields() != repo.NumFields() {
|
|
||||||
return fmt.Errorf("%s: field count mismatch (driver=%d, repo=%d)",
|
|
||||||
name, driver.NumFields(), repo.NumFields())
|
|
||||||
}
|
|
||||||
for i := range driver.NumFields() {
|
|
||||||
df := driver.Field(i)
|
|
||||||
rf := repo.Field(i)
|
|
||||||
if df.Name() != rf.Name() {
|
|
||||||
return fmt.Errorf("%s: field %d name mismatch (driver=%q, repo=%q)",
|
|
||||||
name, i, df.Name(), rf.Name())
|
|
||||||
}
|
|
||||||
if !types.Identical(df.Type(), rf.Type()) {
|
|
||||||
return fmt.Errorf("%s.%s: type mismatch (driver=%s, repo=%s)",
|
|
||||||
name, df.Name(), df.Type(), rf.Type())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type methodInfo struct {
|
|
||||||
Name string
|
|
||||||
Params []paramInfo
|
|
||||||
Results []resultInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type paramInfo struct {
|
|
||||||
Name string
|
|
||||||
TypeStr string // local (unqualified) type name
|
|
||||||
RepoType string // "repository.X" if this is a driver model/params type; else ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type resultInfo struct {
|
|
||||||
TypeStr string
|
|
||||||
IsSlice bool
|
|
||||||
RepoType string // "repository.X" if driver type; else ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectMethods(pkg *types.Package) ([]methodInfo, error) {
|
|
||||||
obj := pkg.Scope().Lookup("Queries")
|
|
||||||
if obj == nil {
|
|
||||||
return nil, fmt.Errorf("queries type not found in %s", pkg.Path())
|
|
||||||
}
|
|
||||||
named, ok := obj.Type().(*types.Named)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("queries is not a named type")
|
|
||||||
}
|
|
||||||
ms := types.NewMethodSet(types.NewPointer(named))
|
|
||||||
|
|
||||||
var out []methodInfo
|
|
||||||
for method := range ms.Methods() {
|
|
||||||
fn, ok := method.Obj().(*types.Func)
|
|
||||||
if !ok || fn.Name() == "WithTx" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sig := fn.Type().(*types.Signature)
|
|
||||||
mi := methodInfo{Name: fn.Name()}
|
|
||||||
|
|
||||||
// params: skip receiver + first (context.Context)
|
|
||||||
for i := 1; i < sig.Params().Len(); i++ {
|
|
||||||
p := sig.Params().At(i)
|
|
||||||
mi.Params = append(mi.Params, makeParam(p.Name(), p.Type(), pkg.Path()))
|
|
||||||
}
|
|
||||||
// results: skip error
|
|
||||||
for r := range sig.Results().Variables() {
|
|
||||||
if r.Type().String() == "error" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
mi.Results = append(mi.Results, makeResult(r.Type(), pkg.Path()))
|
|
||||||
}
|
|
||||||
out = append(out, mi)
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeParam(name string, t types.Type, driverPath string) paramInfo {
|
|
||||||
return paramInfo{
|
|
||||||
Name: name,
|
|
||||||
TypeStr: localName(t, driverPath),
|
|
||||||
RepoType: repoName(t, driverPath),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeResult(t types.Type, driverPath string) resultInfo {
|
|
||||||
ri := resultInfo{}
|
|
||||||
if sl, ok := t.(*types.Slice); ok {
|
|
||||||
ri.IsSlice = true
|
|
||||||
t = sl.Elem()
|
|
||||||
}
|
|
||||||
ri.TypeStr = localName(t, driverPath)
|
|
||||||
ri.RepoType = repoName(t, driverPath)
|
|
||||||
return ri
|
|
||||||
}
|
|
||||||
|
|
||||||
func localName(t types.Type, driverPath string) string {
|
|
||||||
named, ok := t.(*types.Named)
|
|
||||||
if !ok {
|
|
||||||
return types.TypeString(t, nil)
|
|
||||||
}
|
|
||||||
if named.Obj().Pkg() != nil && named.Obj().Pkg().Path() == driverPath {
|
|
||||||
return named.Obj().Name()
|
|
||||||
}
|
|
||||||
return types.TypeString(t, func(p *types.Package) string { return p.Name() })
|
|
||||||
}
|
|
||||||
|
|
||||||
func repoName(t types.Type, driverPath string) string {
|
|
||||||
named, ok := t.(*types.Named)
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if named.Obj().Pkg() != nil && named.Obj().Pkg().Path() == driverPath {
|
|
||||||
return "repository." + named.Obj().Name()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// renderedMethod holds pre-built signature and body strings passed to the template.
|
|
||||||
type renderedMethod struct {
|
|
||||||
Signature string
|
|
||||||
Body string
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderMethods(methods []methodInfo) []renderedMethod {
|
|
||||||
out := make([]renderedMethod, len(methods))
|
|
||||||
for i, m := range methods {
|
|
||||||
out[i] = renderedMethod{
|
|
||||||
Signature: buildSig(m),
|
|
||||||
Body: buildBody(m),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildSig(m methodInfo) string {
|
|
||||||
var sb strings.Builder
|
|
||||||
sb.WriteString("func (s *Store) ")
|
|
||||||
sb.WriteString(m.Name)
|
|
||||||
sb.WriteString("(ctx context.Context")
|
|
||||||
for _, p := range m.Params {
|
|
||||||
sb.WriteString(", ")
|
|
||||||
sb.WriteString(p.Name)
|
|
||||||
sb.WriteString(" ")
|
|
||||||
if p.RepoType != "" {
|
|
||||||
sb.WriteString(p.RepoType)
|
|
||||||
} else {
|
|
||||||
sb.WriteString(p.TypeStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.WriteString(") (")
|
|
||||||
for _, r := range m.Results {
|
|
||||||
if r.IsSlice {
|
|
||||||
sb.WriteString("[]")
|
|
||||||
}
|
|
||||||
if r.RepoType != "" {
|
|
||||||
sb.WriteString(r.RepoType)
|
|
||||||
} else {
|
|
||||||
sb.WriteString(r.TypeStr)
|
|
||||||
}
|
|
||||||
sb.WriteString(", ")
|
|
||||||
}
|
|
||||||
sb.WriteString("error)")
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func callArgs(m methodInfo) string {
|
|
||||||
args := make([]string, 0, len(m.Params))
|
|
||||||
for _, p := range m.Params {
|
|
||||||
if p.RepoType != "" {
|
|
||||||
// convert repo type → driver type: DriverType(arg)
|
|
||||||
args = append(args, p.TypeStr+"("+p.Name+")")
|
|
||||||
} else {
|
|
||||||
args = append(args, p.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(args) == 0 {
|
|
||||||
return "ctx"
|
|
||||||
}
|
|
||||||
return "ctx, " + strings.Join(args, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
var bodyTmpl = template.Must(template.New("store").Parse(storeSrc))
|
|
||||||
|
|
||||||
type bodyData struct {
|
|
||||||
Call string
|
|
||||||
RepoType string
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBody(m methodInfo) string {
|
|
||||||
call := "s.q." + m.Name + "(" + callArgs(m) + ")"
|
|
||||||
|
|
||||||
var (
|
|
||||||
name string
|
|
||||||
data bodyData
|
|
||||||
)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case len(m.Results) == 0 || m.Results[0].RepoType == "":
|
|
||||||
name = "void"
|
|
||||||
data = bodyData{Call: call}
|
|
||||||
case m.Results[0].IsSlice:
|
|
||||||
name = "slice"
|
|
||||||
data = bodyData{Call: call, RepoType: m.Results[0].RepoType}
|
|
||||||
default:
|
|
||||||
name = "scalar"
|
|
||||||
data = bodyData{Call: call, RepoType: m.Results[0].RepoType}
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := bodyTmpl.ExecuteTemplate(&buf, name, data); err != nil {
|
|
||||||
panic(fmt.Sprintf("buildBody %s: %v", name, err))
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
type tmplData struct {
|
|
||||||
PkgName string
|
|
||||||
RepoPkg string
|
|
||||||
Methods []renderedMethod
|
|
||||||
}
|
|
||||||
|
|
||||||
func render(data tmplData) ([]byte, error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := bodyTmpl.Execute(&buf, data); err != nil {
|
|
||||||
return nil, fmt.Errorf("execute template: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
formatted, err := format.Source(buf.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return buf.Bytes(), fmt.Errorf("format source: %w\nraw:\n%s", err, buf.String())
|
|
||||||
}
|
|
||||||
return formatted, nil
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
// Code generated by cmd/gen/sqlc-wrapper. DO NOT EDIT.
|
|
||||||
package {{.PkgName}}
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"{{.RepoPkg}}"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Store wraps *Queries and implements repository.Store.
|
|
||||||
type Store struct {
|
|
||||||
q *Queries
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStore wraps a *Queries to satisfy repository.Store.
|
|
||||||
func NewStore(q *Queries) repository.Store {
|
|
||||||
return &Store{q: q}
|
|
||||||
}
|
|
||||||
|
|
||||||
var errorMap = map[error]error{
|
|
||||||
sql.ErrNoRows: repository.ErrNotFound,
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapErr(err error) error {
|
|
||||||
for from, to := range errorMap {
|
|
||||||
if errors.Is(err, from) {
|
|
||||||
return to
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
{{range .Methods}}{{.Signature}} {
|
|
||||||
{{.Body}}}
|
|
||||||
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{- define "void"}} return mapErr({{.Call}})
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{- define "scalar"}} r, err := {{.Call}}
|
|
||||||
if err != nil {
|
|
||||||
return {{.RepoType}}{}, mapErr(err)
|
|
||||||
}
|
|
||||||
return {{.RepoType}}(r), nil
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{- define "slice"}} rows, err := {{.Call}}
|
|
||||||
if err != nil {
|
|
||||||
return nil, mapErr(err)
|
|
||||||
}
|
|
||||||
out := make([]{{.RepoType}}, len(rows))
|
|
||||||
for i, row := range rows {
|
|
||||||
out[i] = {{.RepoType}}(row)
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
{{end}}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package tinyauth
|
|
||||||
|
|
||||||
//go:generate go run github.com/tinyauthapp/tinyauth/gen/docs
|
|
||||||
@@ -1,45 +1,39 @@
|
|||||||
module github.com/tinyauthapp/tinyauth
|
module github.com/tinyauthapp/tinyauth
|
||||||
|
|
||||||
go 1.26.4
|
go 1.26.0
|
||||||
|
|
||||||
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.54.0
|
|
||||||
golang.org/x/oauth2 v0.36.0
|
golang.org/x/oauth2 v0.36.0
|
||||||
golang.org/x/tools v0.48.0
|
k8s.io/apimachinery v0.36.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
k8s.io/client-go v0.36.0
|
||||||
k8s.io/apimachinery v0.36.2
|
modernc.org/sqlite v1.50.0
|
||||||
k8s.io/client-go v0.36.2
|
|
||||||
modernc.org/sqlite v1.53.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
||||||
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.3.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/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
@@ -49,9 +43,9 @@ require (
|
|||||||
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
|
||||||
@@ -65,7 +59,7 @@ require (
|
|||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // 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/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
|
||||||
@@ -75,24 +69,19 @@ require (
|
|||||||
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-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/huandu/xstrings v1.5.0 // indirect
|
github.com/huandu/xstrings v1.5.0 // indirect
|
||||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // 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/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||||
@@ -106,25 +95,23 @@ require (
|
|||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
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.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // 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/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/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/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
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||||
@@ -134,20 +121,20 @@ require (
|
|||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // 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.38.0 // indirect
|
golang.org/x/net v0.52.0 // indirect
|
||||||
golang.org/x/net v0.57.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sync v0.22.0 // indirect
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
golang.org/x/sys v0.47.0 // indirect
|
golang.org/x/term v0.42.0 // indirect
|
||||||
golang.org/x/term v0.45.0 // indirect
|
golang.org/x/text v0.36.0 // indirect
|
||||||
golang.org/x/text v0.40.0 // indirect
|
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // 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
|
||||||
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
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ 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=
|
||||||
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=
|
||||||
@@ -18,8 +18,8 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ
|
|||||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||||
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=
|
||||||
@@ -45,12 +45,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=
|
||||||
@@ -87,14 +87,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
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/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=
|
|
||||||
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/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=
|
||||||
@@ -126,8 +124,8 @@ 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-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.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||||
github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4=
|
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
@@ -142,8 +140,6 @@ 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/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
github.com/golang-migrate/migrate/v4 v4.19.1 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/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||||
@@ -166,16 +162,6 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
|
|||||||
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/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/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw=
|
|
||||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
|
||||||
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/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
|
||||||
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
|
|
||||||
github.com/jackc/pgx/v5 v5.10.0/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/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=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||||
@@ -202,16 +188,16 @@ 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/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
|
github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
|
||||||
@@ -246,8 +232,8 @@ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOF
|
|||||||
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/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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -259,8 +245,8 @@ 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/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=
|
||||||
@@ -271,21 +257,18 @@ 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/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=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
@@ -308,8 +291,8 @@ go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF
|
|||||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||||
@@ -326,8 +309,6 @@ 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=
|
||||||
@@ -336,29 +317,29 @@ 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=
|
||||||
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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
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.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||||
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
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.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
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.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
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.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||||
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||||
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 +361,42 @@ 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=
|
k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80=
|
||||||
k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg=
|
k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34=
|
||||||
k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ=
|
k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ=
|
||||||
k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4=
|
k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc=
|
||||||
k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI=
|
k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c=
|
||||||
k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0=
|
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=
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ var FrontendAssets embed.FS
|
|||||||
|
|
||||||
// Migrations
|
// Migrations
|
||||||
//
|
//
|
||||||
//go:embed migrations/sqlite/*.sql migrations/postgres/*.sql
|
//go:embed migrations/*.sql
|
||||||
var Migrations embed.FS
|
var Migrations embed.FS
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user