mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-02-26 10:52:02 +00:00
Compare commits
102 Commits
feat/optim
...
4e9342fa8b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e9342fa8b | ||
|
|
695e82d8e8 | ||
|
|
d14b1efd1a | ||
|
|
8a2b7a1641 | ||
|
|
97214bc042 | ||
|
|
09061f698f | ||
|
|
bf15538aca | ||
|
|
234e6b7d8b | ||
|
|
8a5d69927a | ||
|
|
b99004607f | ||
|
|
0fa977f01c | ||
|
|
4a6dc8398b | ||
|
|
460eb6031b | ||
|
|
c33ee12b1c | ||
|
|
72389f94b6 | ||
|
|
5a43ff79a5 | ||
|
|
f388336201 | ||
|
|
67cb54453e | ||
|
|
df1fe9a5ad | ||
|
|
0ce3f52688 | ||
|
|
4c11132176 | ||
|
|
2e1be914d4 | ||
|
|
455d253ebc | ||
|
|
17702efd3b | ||
|
|
e89631e199 | ||
|
|
3d058570cc | ||
|
|
9ede29da3f | ||
|
|
13b9b38939 | ||
|
|
678317f802 | ||
|
|
59e1d314c5 | ||
|
|
81944e770e | ||
|
|
c6ab05b151 | ||
|
|
744a33d264 | ||
|
|
aa607eeb37 | ||
|
|
d210a2bb67 | ||
|
|
37b3e74a43 | ||
|
|
b8c7c47547 | ||
|
|
778fb63fed | ||
|
|
6313d05e7f | ||
|
|
f118955cd7 | ||
|
|
c4778cbfc1 | ||
|
|
d200b54c4d | ||
|
|
163fad7c07 | ||
|
|
265042234b | ||
|
|
b85f2daa1e | ||
|
|
85bbd62b5b | ||
|
|
c2100ae939 | ||
|
|
968b6ce5a9 | ||
|
|
d8783bdb5d | ||
|
|
46c6b297c0 | ||
|
|
458c8dd660 | ||
|
|
b8abc852ee | ||
|
|
edf4945f28 | ||
|
|
e4e804cd37 | ||
|
|
8f17b57d11 | ||
|
|
7662737419 | ||
|
|
ac3082aaaa | ||
|
|
cd8513fea1 | ||
|
|
ccbad25015 | ||
|
|
4de8109178 | ||
|
|
0abe2d4055 | ||
|
|
3e7b414022 | ||
|
|
949611f0a0 | ||
|
|
1aa3bc1fec | ||
|
|
0b5e562b73 | ||
|
|
9ea4250b95 | ||
|
|
bafbb3e9a1 | ||
|
|
fd7d997946 | ||
|
|
506e80ce20 | ||
|
|
3285476b23 | ||
|
|
3ee0f4c128 | ||
|
|
3b6ea5de8f | ||
|
|
f1ef8b0c90 | ||
|
|
7c02ec4226 | ||
|
|
aae2c5ea8d | ||
|
|
573f211286 | ||
|
|
3cdd9fdb08 | ||
|
|
0a6ccd8148 | ||
|
|
e214ee617d | ||
|
|
4ffc671663 | ||
|
|
99f7d73eb9 | ||
|
|
a21164108c | ||
|
|
de10d9b232 | ||
|
|
4472815435 | ||
|
|
2cc1339a4b | ||
|
|
77fabf5c92 | ||
|
|
c7b1b62dc2 | ||
|
|
a9414ae42d | ||
|
|
de3cfd4f50 | ||
|
|
7b3d276780 | ||
|
|
21c08897b6 | ||
|
|
7228c4eeb6 | ||
|
|
37b972763b | ||
|
|
911f7c36a3 | ||
|
|
4fd78a0b02 | ||
|
|
f61ef4090c | ||
|
|
1d86424718 | ||
|
|
fc9288ac51 | ||
|
|
9fef4db20b | ||
|
|
dce7e4779d | ||
|
|
6731a741fa | ||
|
|
9667866982 |
@@ -1,3 +0,0 @@
|
|||||||
issue_enrichment:
|
|
||||||
auto_enrich:
|
|
||||||
enabled: false
|
|
||||||
254
.env.example
254
.env.example
@@ -1,221 +1,33 @@
|
|||||||
# Tinyauth example configuration
|
PORT=3000
|
||||||
|
ADDRESS=0.0.0.0
|
||||||
# The base URL where the app is hosted.
|
SECRET=app_secret
|
||||||
TINYAUTH_APPURL=
|
SECRET_FILE=app_secret_file
|
||||||
|
APP_URL=http://localhost:3000
|
||||||
# The directory where resources are stored.
|
USERS=your_user_password_hash
|
||||||
TINYAUTH_RESOURCESDIR="./resources"
|
USERS_FILE=users_file
|
||||||
|
COOKIE_SECURE=false
|
||||||
# The path to the database file.
|
GITHUB_CLIENT_ID=github_client_id
|
||||||
TINYAUTH_DATABASEPATH="./tinyauth.db"
|
GITHUB_CLIENT_SECRET=github_client_secret
|
||||||
|
GITHUB_CLIENT_SECRET_FILE=github_client_secret_file
|
||||||
# Disable analytics.
|
GOOGLE_CLIENT_ID=google_client_id
|
||||||
TINYAUTH_DISABLEANALYTICS=false
|
GOOGLE_CLIENT_SECRET=google_client_secret
|
||||||
|
GOOGLE_CLIENT_SECRET_FILE=google_client_secret_file
|
||||||
# Disable resources server.
|
GENERIC_CLIENT_ID=generic_client_id
|
||||||
TINYAUTH_DISABLERESOURCES=false
|
GENERIC_CLIENT_SECRET=generic_client_secret
|
||||||
|
GENERIC_CLIENT_SECRET_FILE=generic_client_secret_file
|
||||||
# The port on which the server listens.
|
GENERIC_SCOPES=generic_scopes
|
||||||
TINYAUTH_SERVER_PORT=3000
|
GENERIC_AUTH_URL=generic_auth_url
|
||||||
|
GENERIC_TOKEN_URL=generic_token_url
|
||||||
# The address on which the server listens.
|
GENERIC_USER_URL=generic_user_url
|
||||||
TINYAUTH_SERVER_ADDRESS="0.0.0.0"
|
DISABLE_CONTINUE=false
|
||||||
|
OAUTH_WHITELIST=
|
||||||
# The path to the Unix socket.
|
GENERIC_NAME=My OAuth
|
||||||
TINYAUTH_SERVER_SOCKETPATH=
|
SESSION_EXPIRY=7200
|
||||||
|
LOGIN_TIMEOUT=300
|
||||||
# List of allowed IPs or CIDR ranges.
|
LOGIN_MAX_RETRIES=5
|
||||||
TINYAUTH_AUTH_IP_ALLOW=
|
LOG_LEVEL=0
|
||||||
|
APP_TITLE=Tinyauth SSO
|
||||||
# List of blocked IPs or CIDR ranges.
|
FORGOT_PASSWORD_MESSAGE=Some message about resetting the password
|
||||||
TINYAUTH_AUTH_IP_BLOCK=
|
OAUTH_AUTO_REDIRECT=none
|
||||||
|
BACKGROUND_IMAGE=some_image_url
|
||||||
# Comma-separated list of users (username:hashed_password).
|
GENERIC_SKIP_SSL=false
|
||||||
TINYAUTH_AUTH_USERS=
|
|
||||||
|
|
||||||
# Path to the users file.
|
|
||||||
TINYAUTH_AUTH_USERSFILE=
|
|
||||||
|
|
||||||
# Enable secure cookies.
|
|
||||||
TINYAUTH_AUTH_SECURECOOKIE=false
|
|
||||||
|
|
||||||
# Session expiry time in seconds.
|
|
||||||
TINYAUTH_AUTH_SESSIONEXPIRY=86400
|
|
||||||
|
|
||||||
# Maximum session lifetime in seconds.
|
|
||||||
TINYAUTH_AUTH_SESSIONMAXLIFETIME=0
|
|
||||||
|
|
||||||
# Login timeout in seconds.
|
|
||||||
TINYAUTH_AUTH_LOGINTIMEOUT=300
|
|
||||||
|
|
||||||
# Maximum login retries.
|
|
||||||
TINYAUTH_AUTH_LOGINMAXRETRIES=3
|
|
||||||
|
|
||||||
# Comma-separated list of trusted proxy addresses.
|
|
||||||
TINYAUTH_AUTH_TRUSTEDPROXIES=
|
|
||||||
|
|
||||||
# The domain of the app.
|
|
||||||
TINYAUTH_APPS_name_CONFIG_DOMAIN=
|
|
||||||
|
|
||||||
# Comma-separated list of allowed users.
|
|
||||||
TINYAUTH_APPS_name_USERS_ALLOW=
|
|
||||||
|
|
||||||
# Comma-separated list of blocked users.
|
|
||||||
TINYAUTH_APPS_name_USERS_BLOCK=
|
|
||||||
|
|
||||||
# Comma-separated list of allowed OAuth groups.
|
|
||||||
TINYAUTH_APPS_name_OAUTH_WHITELIST=
|
|
||||||
|
|
||||||
# Comma-separated list of required OAuth groups.
|
|
||||||
TINYAUTH_APPS_name_OAUTH_GROUPS=
|
|
||||||
|
|
||||||
# List of allowed IPs or CIDR ranges.
|
|
||||||
TINYAUTH_APPS_name_IP_ALLOW=
|
|
||||||
|
|
||||||
# List of blocked IPs or CIDR ranges.
|
|
||||||
TINYAUTH_APPS_name_IP_BLOCK=
|
|
||||||
|
|
||||||
# List of IPs or CIDR ranges that bypass authentication.
|
|
||||||
TINYAUTH_APPS_name_IP_BYPASS=
|
|
||||||
|
|
||||||
# Custom headers to add to the response.
|
|
||||||
TINYAUTH_APPS_name_RESPONSE_HEADERS=
|
|
||||||
|
|
||||||
# Basic auth username.
|
|
||||||
TINYAUTH_APPS_name_RESPONSE_BASICAUTH_USERNAME=
|
|
||||||
|
|
||||||
# Basic auth password.
|
|
||||||
TINYAUTH_APPS_name_RESPONSE_BASICAUTH_PASSWORD=
|
|
||||||
|
|
||||||
# Path to the file containing the basic auth password.
|
|
||||||
TINYAUTH_APPS_name_RESPONSE_BASICAUTH_PASSWORDFILE=
|
|
||||||
|
|
||||||
# Comma-separated list of allowed paths.
|
|
||||||
TINYAUTH_APPS_name_PATH_ALLOW=
|
|
||||||
|
|
||||||
# Comma-separated list of blocked paths.
|
|
||||||
TINYAUTH_APPS_name_PATH_BLOCK=
|
|
||||||
|
|
||||||
# Comma-separated list of required LDAP groups.
|
|
||||||
TINYAUTH_APPS_name_LDAP_GROUPS=
|
|
||||||
|
|
||||||
# Comma-separated list of allowed OAuth domains.
|
|
||||||
TINYAUTH_OAUTH_WHITELIST=
|
|
||||||
|
|
||||||
# The OAuth provider to use for automatic redirection.
|
|
||||||
TINYAUTH_OAUTH_AUTOREDIRECT=
|
|
||||||
|
|
||||||
# OAuth client ID.
|
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTID=
|
|
||||||
|
|
||||||
# OAuth client secret.
|
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRET=
|
|
||||||
|
|
||||||
# Path to the file containing the OAuth client secret.
|
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRETFILE=
|
|
||||||
|
|
||||||
# OAuth scopes.
|
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_SCOPES=
|
|
||||||
|
|
||||||
# OAuth redirect URL.
|
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_REDIRECTURL=
|
|
||||||
|
|
||||||
# OAuth authorization URL.
|
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_AUTHURL=
|
|
||||||
|
|
||||||
# OAuth token URL.
|
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_TOKENURL=
|
|
||||||
|
|
||||||
# OAuth userinfo URL.
|
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_USERINFOURL=
|
|
||||||
|
|
||||||
# Allow insecure OAuth connections.
|
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_INSECURE=false
|
|
||||||
|
|
||||||
# Provider name in UI.
|
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_NAME=
|
|
||||||
|
|
||||||
# Path to the private key file.
|
|
||||||
TINYAUTH_OIDC_PRIVATEKEYPATH="./tinyauth_oidc_key"
|
|
||||||
|
|
||||||
# Path to the public key file.
|
|
||||||
TINYAUTH_OIDC_PUBLICKEYPATH="./tinyauth_oidc_key.pub"
|
|
||||||
|
|
||||||
# OIDC client ID.
|
|
||||||
TINYAUTH_OIDC_CLIENTS_name_CLIENTID=
|
|
||||||
|
|
||||||
# OIDC client secret.
|
|
||||||
TINYAUTH_OIDC_CLIENTS_name_CLIENTSECRET=
|
|
||||||
|
|
||||||
# Path to the file containing the OIDC client secret.
|
|
||||||
TINYAUTH_OIDC_CLIENTS_name_CLIENTSECRETFILE=
|
|
||||||
|
|
||||||
# List of trusted redirect URIs.
|
|
||||||
TINYAUTH_OIDC_CLIENTS_name_TRUSTEDREDIRECTURIS=
|
|
||||||
|
|
||||||
# Client name in UI.
|
|
||||||
TINYAUTH_OIDC_CLIENTS_name_NAME=
|
|
||||||
|
|
||||||
# The title of the UI.
|
|
||||||
TINYAUTH_UI_TITLE="Tinyauth"
|
|
||||||
|
|
||||||
# Message displayed on the forgot password page.
|
|
||||||
TINYAUTH_UI_FORGOTPASSWORDMESSAGE="You can change your password by changing the configuration."
|
|
||||||
|
|
||||||
# Path to the background image.
|
|
||||||
TINYAUTH_UI_BACKGROUNDIMAGE="/background.jpg"
|
|
||||||
|
|
||||||
# Disable UI warnings.
|
|
||||||
TINYAUTH_UI_DISABLEWARNINGS=false
|
|
||||||
|
|
||||||
# LDAP server address.
|
|
||||||
TINYAUTH_LDAP_ADDRESS=
|
|
||||||
|
|
||||||
# Bind DN for LDAP authentication.
|
|
||||||
TINYAUTH_LDAP_BINDDN=
|
|
||||||
|
|
||||||
# Bind password for LDAP authentication.
|
|
||||||
TINYAUTH_LDAP_BINDPASSWORD=
|
|
||||||
|
|
||||||
# Base DN for LDAP searches.
|
|
||||||
TINYAUTH_LDAP_BASEDN=
|
|
||||||
|
|
||||||
# Allow insecure LDAP connections.
|
|
||||||
TINYAUTH_LDAP_INSECURE=false
|
|
||||||
|
|
||||||
# LDAP search filter.
|
|
||||||
TINYAUTH_LDAP_SEARCHFILTER="(uid=%s)"
|
|
||||||
|
|
||||||
# Certificate for mTLS authentication.
|
|
||||||
TINYAUTH_LDAP_AUTHCERT=
|
|
||||||
|
|
||||||
# Certificate key for mTLS authentication.
|
|
||||||
TINYAUTH_LDAP_AUTHKEY=
|
|
||||||
|
|
||||||
# Cache duration for LDAP group membership in seconds.
|
|
||||||
TINYAUTH_LDAP_GROUPCACHETTL=900
|
|
||||||
|
|
||||||
# Log level (trace, debug, info, warn, error).
|
|
||||||
TINYAUTH_LOG_LEVEL="info"
|
|
||||||
|
|
||||||
# Enable JSON formatted logs.
|
|
||||||
TINYAUTH_LOG_JSON=false
|
|
||||||
|
|
||||||
# Enable this log stream.
|
|
||||||
TINYAUTH_LOG_STREAMS_HTTP_ENABLED=true
|
|
||||||
|
|
||||||
# Log level for this stream. Use global if empty.
|
|
||||||
TINYAUTH_LOG_STREAMS_HTTP_LEVEL=
|
|
||||||
|
|
||||||
# Enable this log stream.
|
|
||||||
TINYAUTH_LOG_STREAMS_APP_ENABLED=true
|
|
||||||
|
|
||||||
# Log level for this stream. Use global if empty.
|
|
||||||
TINYAUTH_LOG_STREAMS_APP_LEVEL=
|
|
||||||
|
|
||||||
# Enable this log stream.
|
|
||||||
TINYAUTH_LOG_STREAMS_AUDIT_ENABLED=false
|
|
||||||
|
|
||||||
# Log level for this stream. Use global if empty.
|
|
||||||
TINYAUTH_LOG_STREAMS_AUDIT_LEVEL=
|
|
||||||
|
|
||||||
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@@ -18,31 +18,17 @@ jobs:
|
|||||||
- name: Setup go
|
- name: Setup go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "^1.24.0"
|
go-version: "^1.23.2"
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
run: |
|
run: |
|
||||||
cd frontend
|
cd frontend
|
||||||
bun install --frozen-lockfile
|
bun install
|
||||||
|
|
||||||
- name: Set version
|
- name: Set version
|
||||||
run: |
|
run: |
|
||||||
echo testing > internal/assets/version
|
echo testing > internal/assets/version
|
||||||
|
|
||||||
- name: Lint frontend
|
|
||||||
run: |
|
|
||||||
cd frontend
|
|
||||||
bun run lint
|
|
||||||
|
|
||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
run: |
|
run: |
|
||||||
cd frontend
|
cd frontend
|
||||||
|
|||||||
235
.github/workflows/nightly.yml
vendored
235
.github/workflows/nightly.yml
vendored
@@ -61,21 +61,12 @@ jobs:
|
|||||||
- name: Install go
|
- name: Install go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "^1.24.0"
|
go-version: "^1.23.2"
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
run: |
|
run: |
|
||||||
cd frontend
|
cd frontend
|
||||||
bun install --frozen-lockfile
|
bun install
|
||||||
|
|
||||||
- name: Install backend dependencies
|
- name: Install backend dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -89,7 +80,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cp -r frontend/dist internal/assets/dist
|
cp -r frontend/dist internal/assets/dist
|
||||||
go build -ldflags "-s -w -X github.com/steveiliop56/tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 ./cmd/tinyauth
|
go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
@@ -116,21 +107,12 @@ jobs:
|
|||||||
- name: Install go
|
- name: Install go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "^1.24.0"
|
go-version: "^1.23.2"
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
run: |
|
run: |
|
||||||
cd frontend
|
cd frontend
|
||||||
bun install --frozen-lockfile
|
bun install
|
||||||
|
|
||||||
- name: Install backend dependencies
|
- name: Install backend dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -144,7 +126,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cp -r frontend/dist internal/assets/dist
|
cp -r frontend/dist internal/assets/dist
|
||||||
go build -ldflags "-s -w -X github.com/steveiliop56/tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 ./cmd/tinyauth
|
go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
@@ -165,15 +147,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: nightly
|
ref: nightly
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
@@ -198,9 +171,6 @@ jobs:
|
|||||||
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
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
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 }}
|
||||||
@@ -220,74 +190,6 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
image-build-distroless:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- create-release
|
|
||||||
- generate-metadata
|
|
||||||
- image-build
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: nightly
|
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
id: build
|
|
||||||
with:
|
|
||||||
platforms: linux/amd64
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
|
||||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
|
||||||
file: Dockerfile.distroless
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
build-args: |
|
|
||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
|
||||||
|
|
||||||
- name: Export digest
|
|
||||||
run: |
|
|
||||||
mkdir -p ${{ runner.temp }}/digests
|
|
||||||
digest="${{ steps.build.outputs.digest }}"
|
|
||||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
|
||||||
|
|
||||||
- name: Upload digest
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: digests-distroless-linux-amd64
|
|
||||||
path: ${{ runner.temp }}/digests/*
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
image-build-arm:
|
image-build-arm:
|
||||||
runs-on: ubuntu-24.04-arm
|
runs-on: ubuntu-24.04-arm
|
||||||
needs:
|
needs:
|
||||||
@@ -299,15 +201,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: nightly
|
ref: nightly
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
@@ -324,6 +217,10 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Set version
|
||||||
|
run: |
|
||||||
|
echo nightly > internal/assets/version
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
id: build
|
id: build
|
||||||
@@ -332,9 +229,6 @@ jobs:
|
|||||||
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
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
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 }}
|
||||||
@@ -354,74 +248,6 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
image-build-arm-distroless:
|
|
||||||
runs-on: ubuntu-24.04-arm
|
|
||||||
needs:
|
|
||||||
- create-release
|
|
||||||
- generate-metadata
|
|
||||||
- image-build-arm
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: nightly
|
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
id: build
|
|
||||||
with:
|
|
||||||
platforms: linux/arm64
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
|
||||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
|
||||||
file: Dockerfile.distroless
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
build-args: |
|
|
||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
|
||||||
|
|
||||||
- name: Export digest
|
|
||||||
run: |
|
|
||||||
mkdir -p ${{ runner.temp }}/digests
|
|
||||||
digest="${{ steps.build.outputs.digest }}"
|
|
||||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
|
||||||
|
|
||||||
- name: Upload digest
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: digests-distroless-linux-arm64
|
|
||||||
path: ${{ runner.temp }}/digests/*
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
image-merge:
|
image-merge:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
@@ -450,8 +276,6 @@ jobs:
|
|||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
flavor: |
|
|
||||||
latest=false
|
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,nightly
|
type=raw,nightly
|
||||||
|
|
||||||
@@ -461,45 +285,6 @@ jobs:
|
|||||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||||
$(printf 'ghcr.io/${{ github.repository_owner }}/tinyauth@sha256:%s ' *)
|
$(printf 'ghcr.io/${{ github.repository_owner }}/tinyauth@sha256:%s ' *)
|
||||||
|
|
||||||
image-merge-distroless:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- image-build-distroless
|
|
||||||
- image-build-arm-distroless
|
|
||||||
steps:
|
|
||||||
- name: Download digests
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
path: ${{ runner.temp }}/digests
|
|
||||||
pattern: digests-distroless-*
|
|
||||||
merge-multiple: true
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
|
||||||
flavor: |
|
|
||||||
latest=false
|
|
||||||
tags: |
|
|
||||||
type=raw,nightly-distroless
|
|
||||||
|
|
||||||
- name: Create manifest list and push
|
|
||||||
working-directory: ${{ runner.temp }}/digests
|
|
||||||
run: |
|
|
||||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
|
||||||
$(printf 'ghcr.io/${{ github.repository_owner }}/tinyauth@sha256:%s ' *)
|
|
||||||
|
|
||||||
update-release:
|
update-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
|
|||||||
235
.github/workflows/release.yml
vendored
235
.github/workflows/release.yml
vendored
@@ -39,21 +39,12 @@ jobs:
|
|||||||
- name: Install go
|
- name: Install go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "^1.24.0"
|
go-version: "^1.23.2"
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
run: |
|
run: |
|
||||||
cd frontend
|
cd frontend
|
||||||
bun install --frozen-lockfile
|
bun install
|
||||||
|
|
||||||
- name: Install backend dependencies
|
- name: Install backend dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -67,7 +58,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cp -r frontend/dist internal/assets/dist
|
cp -r frontend/dist internal/assets/dist
|
||||||
go build -ldflags "-s -w -X github.com/steveiliop56/tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 ./cmd/tinyauth
|
go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
@@ -91,21 +82,12 @@ jobs:
|
|||||||
- name: Install go
|
- name: Install go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "^1.24.0"
|
go-version: "^1.23.2"
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
run: |
|
run: |
|
||||||
cd frontend
|
cd frontend
|
||||||
bun install --frozen-lockfile
|
bun install
|
||||||
|
|
||||||
- name: Install backend dependencies
|
- name: Install backend dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -119,7 +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 -ldflags "-s -w -X github.com/steveiliop56/tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 ./cmd/tinyauth
|
go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
@@ -137,15 +119,6 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
@@ -170,9 +143,6 @@ jobs:
|
|||||||
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
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
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 }}
|
||||||
@@ -192,71 +162,6 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
image-build-distroless:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- generate-metadata
|
|
||||||
- image-build
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
id: build
|
|
||||||
with:
|
|
||||||
platforms: linux/amd64
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
|
||||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
|
||||||
file: Dockerfile.distroless
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
build-args: |
|
|
||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
|
||||||
|
|
||||||
- name: Export digest
|
|
||||||
run: |
|
|
||||||
mkdir -p ${{ runner.temp }}/digests
|
|
||||||
digest="${{ steps.build.outputs.digest }}"
|
|
||||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
|
||||||
|
|
||||||
- name: Upload digest
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: digests-distroless-linux-amd64
|
|
||||||
path: ${{ runner.temp }}/digests/*
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
image-build-arm:
|
image-build-arm:
|
||||||
runs-on: ubuntu-24.04-arm
|
runs-on: ubuntu-24.04-arm
|
||||||
needs:
|
needs:
|
||||||
@@ -265,15 +170,6 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
@@ -298,9 +194,6 @@ jobs:
|
|||||||
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
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
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 }}
|
||||||
@@ -320,71 +213,6 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
image-build-arm-distroless:
|
|
||||||
runs-on: ubuntu-24.04-arm
|
|
||||||
needs:
|
|
||||||
- generate-metadata
|
|
||||||
- image-build-arm
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
- name: Apply patches
|
|
||||||
run: |
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
id: build
|
|
||||||
with:
|
|
||||||
platforms: linux/arm64
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
|
|
||||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
|
||||||
file: Dockerfile.distroless
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
build-args: |
|
|
||||||
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
|
|
||||||
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
|
|
||||||
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
|
|
||||||
|
|
||||||
- name: Export digest
|
|
||||||
run: |
|
|
||||||
mkdir -p ${{ runner.temp }}/digests
|
|
||||||
digest="${{ steps.build.outputs.digest }}"
|
|
||||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
|
||||||
|
|
||||||
- name: Upload digest
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: digests-distroless-linux-arm64
|
|
||||||
path: ${{ runner.temp }}/digests/*
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
image-merge:
|
image-merge:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
@@ -413,55 +241,10 @@ jobs:
|
|||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
flavor: |
|
|
||||||
prefix=v,onlatest=false
|
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}},prefix=v
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}},prefix=v
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}},prefix=v
|
||||||
|
|
||||||
- name: Create manifest list and push
|
|
||||||
working-directory: ${{ runner.temp }}/digests
|
|
||||||
run: |
|
|
||||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
|
||||||
$(printf 'ghcr.io/${{ github.repository_owner }}/tinyauth@sha256:%s ' *)
|
|
||||||
|
|
||||||
image-merge-distroless:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- image-build-distroless
|
|
||||||
- image-build-arm-distroless
|
|
||||||
steps:
|
|
||||||
- name: Download digests
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
path: ${{ runner.temp }}/digests
|
|
||||||
pattern: digests-distroless-*
|
|
||||||
merge-multiple: true
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
|
||||||
flavor: |
|
|
||||||
latest=false
|
|
||||||
prefix=v
|
|
||||||
suffix=-distroless
|
|
||||||
tags: |
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
|
|
||||||
- name: Create manifest list and push
|
- name: Create manifest list and push
|
||||||
working-directory: ${{ runner.temp }}/digests
|
working-directory: ${{ runner.temp }}/digests
|
||||||
|
|||||||
36
.gitignore
vendored
36
.gitignore
vendored
@@ -1,44 +1,26 @@
|
|||||||
# dist
|
# dist
|
||||||
/internal/assets/dist
|
internal/assets/dist
|
||||||
|
|
||||||
# binaries
|
# binaries
|
||||||
/tinyauth
|
tinyauth
|
||||||
/tinyauth-arm64
|
|
||||||
/tinyauth-amd64
|
|
||||||
|
|
||||||
# test docker compose
|
# test docker compose
|
||||||
/docker-compose.test*
|
docker-compose.test*
|
||||||
|
|
||||||
# users file
|
# users file
|
||||||
/users.txt
|
users.txt
|
||||||
|
|
||||||
# secret test file
|
# secret test file
|
||||||
/secret*
|
secret*
|
||||||
|
|
||||||
# apple stuff
|
# apple stuff
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# env
|
# env
|
||||||
/.env
|
.env
|
||||||
|
|
||||||
# tmp directory
|
# tmp directory
|
||||||
/tmp
|
tmp
|
||||||
|
|
||||||
# data directory
|
# version files
|
||||||
/data
|
internal/assets/version
|
||||||
|
|
||||||
# config file
|
|
||||||
/config.yml
|
|
||||||
|
|
||||||
# binary out
|
|
||||||
/tinyauth.db
|
|
||||||
/resources
|
|
||||||
|
|
||||||
# debug files
|
|
||||||
__debug_*
|
|
||||||
|
|
||||||
# infisical
|
|
||||||
/.infisical.json
|
|
||||||
|
|
||||||
# traefik data
|
|
||||||
/traefik
|
|
||||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -1,4 +0,0 @@
|
|||||||
[submodule "paerser"]
|
|
||||||
path = paerser
|
|
||||||
url = https://github.com/traefik/paerser
|
|
||||||
ignore = all
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"label": "Attach to remote Delve",
|
|
||||||
"adapter": "Delve",
|
|
||||||
"mode": "remote",
|
|
||||||
"remotePath": "/tinyauth",
|
|
||||||
"request": "attach",
|
|
||||||
"tcp_connection": {
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"port": 4000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
@@ -5,7 +5,7 @@ Contributing is relatively easy, you just need to follow the steps below and you
|
|||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Bun
|
- Bun
|
||||||
- Golang 1.24.0+
|
- Golang v1.23.2 and above
|
||||||
- Git
|
- Git
|
||||||
- Docker
|
- Docker
|
||||||
|
|
||||||
@@ -18,21 +18,12 @@ git clone https://github.com/steveiliop56/tinyauth
|
|||||||
cd tinyauth
|
cd tinyauth
|
||||||
```
|
```
|
||||||
|
|
||||||
## Initialize submodules
|
|
||||||
|
|
||||||
The project uses Git submodules for some dependencies, so you need to initialize them with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
```
|
|
||||||
|
|
||||||
## Install requirements
|
## Install requirements
|
||||||
|
|
||||||
Although you will not need the requirements in your machine since the development will happen in Docker, I still recommend to install them because this way you will not have import errors. To install the Go requirements run:
|
Although you will not need the requirements in your machine since the development will happen in docker, I still recommend to install them because this way you will not have import errors. To install the go requirements run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go mod download
|
go mod tidy
|
||||||
```
|
```
|
||||||
|
|
||||||
You also need to download the frontend dependencies, this can be done like so:
|
You also need to download the frontend dependencies, this can be done like so:
|
||||||
@@ -42,21 +33,13 @@ cd frontend/
|
|||||||
bun install
|
bun install
|
||||||
```
|
```
|
||||||
|
|
||||||
## Apply patches
|
|
||||||
|
|
||||||
Some of the dependencies need to be patched in order to work correctly with the project, you can apply the patches by running:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git apply --directory paerser/ patches/nested_maps.diff
|
|
||||||
```
|
|
||||||
|
|
||||||
## Create your `.env` file
|
## Create your `.env` file
|
||||||
|
|
||||||
In order to configure the app you need to create an environment file, this can be done by copying the `.env.example` file to `.env` and modifying the environment variables to suit your needs.
|
In order to configure the app you need to create an environment file, this can be done by copying the `.env.example` file to `.env` and modifying the environment variables to suit your needs.
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
I have designed the development workflow to be entirely in Docker, this is because it will directly work with Traefik and you will not need to do any building in your host machine. The recommended development setup is to have a subdomain pointing to your machine like this:
|
I have designed the development workflow to be entirely in docker, this is because it will directly work with traefik and you will not need to do any building in your host machine. The recommended development setup is to have a subdomain pointing to your machine like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
*.dev.example.com -> 127.0.0.1
|
*.dev.example.com -> 127.0.0.1
|
||||||
@@ -66,7 +49,7 @@ dev.example.com -> 127.0.0.1
|
|||||||
> [!TIP]
|
> [!TIP]
|
||||||
> You can use [sslip.io](https://sslip.io) as a domain if you don't have one to develop with.
|
> You can use [sslip.io](https://sslip.io) as a domain if you don't have one to develop with.
|
||||||
|
|
||||||
Then you can just make sure the domains are correct in the development Docker compose file and run:
|
Then you can just make sure the domains are correct in the development docker compose file and run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker compose -f docker-compose.dev.yml up --build
|
docker compose -f docker-compose.dev.yml up --build
|
||||||
|
|||||||
34
Dockerfile
34
Dockerfile
@@ -1,12 +1,12 @@
|
|||||||
# Site builder
|
# Site builder
|
||||||
FROM oven/bun:1.3.9-alpine AS frontend-builder
|
FROM oven/bun:1.2.18-alpine AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /frontend
|
WORKDIR /frontend
|
||||||
|
|
||||||
COPY ./frontend/package.json ./
|
COPY ./frontend/package.json ./
|
||||||
COPY ./frontend/bun.lock ./
|
COPY ./frontend/bun.lock ./
|
||||||
|
|
||||||
RUN bun install --frozen-lockfile
|
RUN bun install
|
||||||
|
|
||||||
COPY ./frontend/public ./public
|
COPY ./frontend/public ./public
|
||||||
COPY ./frontend/src ./src
|
COPY ./frontend/src ./src
|
||||||
@@ -20,7 +20,7 @@ COPY ./frontend/vite.config.ts ./
|
|||||||
RUN bun run build
|
RUN bun run build
|
||||||
|
|
||||||
# Builder
|
# Builder
|
||||||
FROM golang:1.25-alpine3.21 AS builder
|
FROM golang:1.24-alpine3.21 AS builder
|
||||||
|
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
ARG COMMIT_HASH
|
ARG COMMIT_HASH
|
||||||
@@ -28,41 +28,27 @@ ARG BUILD_TIMESTAMP
|
|||||||
|
|
||||||
WORKDIR /tinyauth
|
WORKDIR /tinyauth
|
||||||
|
|
||||||
COPY ./paerser ./paerser
|
|
||||||
|
|
||||||
COPY go.mod ./
|
COPY go.mod ./
|
||||||
COPY go.sum ./
|
COPY go.sum ./
|
||||||
|
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY ./main.go ./
|
||||||
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 -ldflags "-s -w \
|
RUN CGO_ENABLED=0 go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${VERSION} -X tinyauth/internal/constants.CommitHash=${COMMIT_HASH} -X tinyauth/internal/constants.BuildTimestamp=${BUILD_TIMESTAMP}"
|
||||||
-X github.com/steveiliop56/tinyauth/internal/config.Version=${VERSION} \
|
|
||||||
-X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${COMMIT_HASH} \
|
|
||||||
-X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
|
||||||
|
|
||||||
# Runner
|
# Runner
|
||||||
FROM alpine:3.23 AS runner
|
FROM alpine:3.22 AS runner
|
||||||
|
|
||||||
WORKDIR /tinyauth
|
WORKDIR /tinyauth
|
||||||
|
|
||||||
COPY --from=builder /tinyauth/tinyauth ./
|
RUN apk add --no-cache curl
|
||||||
|
|
||||||
RUN mkdir -p /data
|
COPY --from=builder /tinyauth/tinyauth ./
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
VOLUME ["/data"]
|
ENTRYPOINT ["./tinyauth"]
|
||||||
|
|
||||||
ENV TINYAUTH_DATABASEPATH=/data/tinyauth.db
|
|
||||||
|
|
||||||
ENV TINYAUTH_RESOURCESDIR=/data/resources
|
|
||||||
|
|
||||||
ENV PATH=$PATH:/tinyauth
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 CMD ["tinyauth", "healthcheck"]
|
|
||||||
|
|
||||||
ENTRYPOINT ["tinyauth"]
|
|
||||||
@@ -1,25 +1,19 @@
|
|||||||
FROM golang:1.25-alpine3.21
|
FROM golang:1.24-alpine3.21
|
||||||
|
|
||||||
WORKDIR /tinyauth
|
WORKDIR /tinyauth
|
||||||
|
|
||||||
COPY ./paerser ./paerser
|
|
||||||
|
|
||||||
COPY go.mod ./
|
COPY go.mod ./
|
||||||
COPY go.sum ./
|
COPY go.sum ./
|
||||||
|
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
RUN go install github.com/air-verse/air@v1.61.7
|
|
||||||
RUN go install github.com/go-delve/delve/cmd/dlv@latest
|
|
||||||
|
|
||||||
COPY ./cmd ./cmd
|
COPY ./cmd ./cmd
|
||||||
COPY ./internal ./internal
|
COPY ./internal ./internal
|
||||||
|
COPY ./main.go ./
|
||||||
COPY ./air.toml ./
|
COPY ./air.toml ./
|
||||||
|
|
||||||
|
RUN go install github.com/air-verse/air@v1.61.7
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
ENV TINYAUTH_DATABASEPATH=/data/tinyauth.db
|
ENTRYPOINT ["air", "-c", "air.toml"]
|
||||||
|
|
||||||
ENV TINYAUTH_RESOURCESDIR=/data/resources
|
|
||||||
|
|
||||||
ENTRYPOINT ["air", "-c", "air.toml"]
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
# Site builder
|
|
||||||
FROM oven/bun:1.3.9-alpine AS frontend-builder
|
|
||||||
|
|
||||||
WORKDIR /frontend
|
|
||||||
|
|
||||||
COPY ./frontend/package.json ./
|
|
||||||
COPY ./frontend/bun.lock ./
|
|
||||||
|
|
||||||
RUN bun install --frozen-lockfile
|
|
||||||
|
|
||||||
COPY ./frontend/public ./public
|
|
||||||
COPY ./frontend/src ./src
|
|
||||||
COPY ./frontend/eslint.config.js ./
|
|
||||||
COPY ./frontend/index.html ./
|
|
||||||
COPY ./frontend/tsconfig.json ./
|
|
||||||
COPY ./frontend/tsconfig.app.json ./
|
|
||||||
COPY ./frontend/tsconfig.node.json ./
|
|
||||||
COPY ./frontend/vite.config.ts ./
|
|
||||||
|
|
||||||
RUN bun run build
|
|
||||||
|
|
||||||
# Builder
|
|
||||||
FROM golang:1.25-alpine3.21 AS builder
|
|
||||||
|
|
||||||
ARG VERSION
|
|
||||||
ARG COMMIT_HASH
|
|
||||||
ARG BUILD_TIMESTAMP
|
|
||||||
|
|
||||||
WORKDIR /tinyauth
|
|
||||||
|
|
||||||
COPY ./paerser ./paerser
|
|
||||||
|
|
||||||
COPY go.mod ./
|
|
||||||
COPY go.sum ./
|
|
||||||
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
COPY ./cmd ./cmd
|
|
||||||
COPY ./internal ./internal
|
|
||||||
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
|
||||||
|
|
||||||
RUN mkdir -p data
|
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w \
|
|
||||||
-X github.com/steveiliop56/tinyauth/internal/config.Version=${VERSION} \
|
|
||||||
-X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${COMMIT_HASH} \
|
|
||||||
-X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
|
||||||
|
|
||||||
# Runner
|
|
||||||
FROM gcr.io/distroless/static-debian12:latest AS runner
|
|
||||||
|
|
||||||
WORKDIR /tinyauth
|
|
||||||
|
|
||||||
COPY --from=builder /tinyauth/tinyauth ./
|
|
||||||
|
|
||||||
# Since it's distroless, we need to copy the data directory from the builder stage
|
|
||||||
COPY --from=builder /tinyauth/data /data
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
|
||||||
|
|
||||||
ENV TINYAUTH_DATABASEPATH=/data/tinyauth.db
|
|
||||||
|
|
||||||
ENV TINYAUTH_RESOURCESDIR=/data/resources
|
|
||||||
|
|
||||||
ENV PATH=$PATH:/tinyauth
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 CMD ["tinyauth", "healthcheck"]
|
|
||||||
|
|
||||||
ENTRYPOINT ["tinyauth"]
|
|
||||||
85
Makefile
85
Makefile
@@ -1,85 +0,0 @@
|
|||||||
# Go specific stuff
|
|
||||||
CGO_ENABLED := 0
|
|
||||||
GOOS := $(shell go env GOOS)
|
|
||||||
GOARCH := $(shell go env GOARCH)
|
|
||||||
|
|
||||||
# Build out
|
|
||||||
TAG_NAME := $(shell git describe --abbrev=0 --exact-match 2> /dev/null || echo "main")
|
|
||||||
COMMIT_HASH := $(shell git rev-parse HEAD)
|
|
||||||
BUILD_TIMESTAMP := $(shell date '+%Y-%m-%dT%H:%M:%S')
|
|
||||||
BIN_NAME := tinyauth-$(GOARCH)
|
|
||||||
|
|
||||||
# Development vars
|
|
||||||
DEV_COMPOSE := $(shell test -f "docker-compose.test.yml" && echo "docker-compose.test.yml" || echo "docker-compose.dev.yml" )
|
|
||||||
PROD_COMPOSE := $(shell test -f "docker-compose.test.prod.yml" && echo "docker-compose.test.prod.yml" || echo "docker-compose.example.yml" )
|
|
||||||
|
|
||||||
# Deps
|
|
||||||
deps:
|
|
||||||
bun install --cwd frontend
|
|
||||||
go mod download
|
|
||||||
|
|
||||||
# Clean data
|
|
||||||
clean-data:
|
|
||||||
rm -rf data/
|
|
||||||
|
|
||||||
# Clean web UI build
|
|
||||||
clean-webui:
|
|
||||||
rm -rf internal/assets/dist
|
|
||||||
rm -rf frontend/dist
|
|
||||||
|
|
||||||
# Build the web UI
|
|
||||||
webui: clean-webui
|
|
||||||
bun run --cwd frontend build
|
|
||||||
cp -r frontend/dist internal/assets
|
|
||||||
|
|
||||||
# Build the binary
|
|
||||||
binary: webui
|
|
||||||
CGO_ENABLED=$(CGO_ENABLED) go build -ldflags "-s -w \
|
|
||||||
-X github.com/steveiliop56/tinyauth/internal/config.Version=${TAG_NAME} \
|
|
||||||
-X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${COMMIT_HASH} \
|
|
||||||
-X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
|
||||||
-o ${BIN_NAME} ./cmd/tinyauth
|
|
||||||
|
|
||||||
# Build for amd64
|
|
||||||
binary-linux-amd64:
|
|
||||||
export BIN_NAME=tinyauth-amd64
|
|
||||||
export GOARCH=amd64
|
|
||||||
export GOOS=linux
|
|
||||||
$(MAKE) binary
|
|
||||||
|
|
||||||
# Build for arm64
|
|
||||||
binary-linux-arm64:
|
|
||||||
export BIN_NAME=tinyauth-arm64
|
|
||||||
export GOARCH=arm64
|
|
||||||
export GOOS=linux
|
|
||||||
$(MAKE) binary
|
|
||||||
|
|
||||||
# Go test
|
|
||||||
.PHONY: test
|
|
||||||
test:
|
|
||||||
go test -v ./...
|
|
||||||
|
|
||||||
# Development
|
|
||||||
dev:
|
|
||||||
docker compose -f $(DEV_COMPOSE) up --force-recreate --pull=always --remove-orphans --build
|
|
||||||
|
|
||||||
# Development - Infisical
|
|
||||||
dev-infisical:
|
|
||||||
infisical run --env=dev -- docker compose -f $(DEV_COMPOSE) up --force-recreate --pull=always --remove-orphans --build
|
|
||||||
|
|
||||||
# Production
|
|
||||||
prod:
|
|
||||||
docker compose -f $(PROD_COMPOSE) up --force-recreate --pull=always --remove-orphans
|
|
||||||
|
|
||||||
# Production - Infisical
|
|
||||||
prod-infisical:
|
|
||||||
infisical run --env=dev -- docker compose -f $(PROD_COMPOSE) up --force-recreate --pull=always --remove-orphans
|
|
||||||
|
|
||||||
# SQL
|
|
||||||
.PHONY: sql
|
|
||||||
sql:
|
|
||||||
sqlc generate
|
|
||||||
|
|
||||||
# Go gen
|
|
||||||
generate:
|
|
||||||
go run ./gen
|
|
||||||
10
README.md
10
README.md
@@ -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 simplest way to protect your apps with a login screen.</p>
|
<p>The easiest way to secure your apps with a login screen.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
Tinyauth is a simple authentication middleware that adds a simple login screen or OAuth with Google, Github or any other provider to all of your apps. It supports all the popular proxies like Traefik, Nginx and Caddy.
|
Tinyauth is a simple authentication middleware that adds a simple login screen or OAuth with Google, Github and any provider to all of your docker apps. It supports all the popular proxies like Traefik, Nginx and Caddy.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Tinyauth is a simple authentication middleware that adds a simple login screen o
|
|||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
You can easily get started with Tinyauth by following the guide in the [documentation](https://tinyauth.app/docs/getting-started). There is also an available [docker compose](./docker-compose.example.yml) file that has Traefik, Whoami and Tinyauth to demonstrate its capabilities.
|
You can easily get started with Tinyauth by following the guide in the [documentation](https://tinyauth.app/docs/getting-started.html). There is also an available [docker compose](./docker-compose.example.yml) file that has Traefik, Whoami and Tinyauth to demonstrate its capabilities.
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
@@ -33,8 +33,6 @@ If you are still not sure if Tinyauth suits your needs you can try out the [demo
|
|||||||
|
|
||||||
You can find documentation and guides on all of the available configuration of Tinyauth in the [website](https://tinyauth.app).
|
You can find documentation and guides on all of the available configuration of Tinyauth in the [website](https://tinyauth.app).
|
||||||
|
|
||||||
If you wish to contribute to the documentation head over to the [repository](https://github.com/steveiliop56/tinyauth-docs).
|
|
||||||
|
|
||||||
## Discord
|
## Discord
|
||||||
|
|
||||||
Tinyauth has a [discord](https://discord.gg/eHzVaCzRRd) server. Feel free to hop in to chat about self-hosting, homelabs and of course Tinyauth. See you there!
|
Tinyauth has a [discord](https://discord.gg/eHzVaCzRRd) server. Feel free to hop in to chat about self-hosting, homelabs and of course Tinyauth. See you there!
|
||||||
@@ -55,7 +53,7 @@ Tinyauth is licensed under the GNU General Public License v3.0. TL;DR — You ma
|
|||||||
|
|
||||||
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/algorist-ahmad"><img src="https://github.com/algorist-ahmad.png" width="64px" alt="User avatar: algorist-ahmad" /></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> <!-- sponsors -->
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
|
|||||||
7
air.toml
7
air.toml
@@ -2,10 +2,9 @@ root = "/tinyauth"
|
|||||||
tmp_dir = "tmp"
|
tmp_dir = "tmp"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
pre_cmd = ["mkdir -p internal/assets/dist", "mkdir -p /data", "echo 'backend running' > internal/assets/dist/index.html"]
|
pre_cmd = ["mkdir -p internal/assets/dist", "echo 'backend running' > internal/assets/dist/index.html", "go install github.com/go-delve/delve/cmd/dlv@v1.25.0"]
|
||||||
cmd = "CGO_ENABLED=0 go build -gcflags=\"all=-N -l\" -o tmp/tinyauth ./cmd/tinyauth"
|
cmd = "CGO_ENABLED=0 go build -gcflags=\"all=-N -l\" -o tmp/tinyauth ."
|
||||||
bin = "tmp/tinyauth"
|
bin = "/go/bin/dlv --listen :4000 --headless=true --api-version=2 --accept-multiclient --log=true exec tmp/tinyauth --continue"
|
||||||
full_bin = "dlv --listen :4000 --headless=true --api-version=2 --accept-multiclient --log=true exec tmp/tinyauth --continue --check-go-version=false"
|
|
||||||
include_ext = ["go"]
|
include_ext = ["go"]
|
||||||
exclude_dir = ["internal/assets/dist"]
|
exclude_dir = ["internal/assets/dist"]
|
||||||
exclude_regex = [".*_test\\.go"]
|
exclude_regex = [".*_test\\.go"]
|
||||||
|
|||||||
263
cmd/root.go
Normal file
263
cmd/root.go
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
totpCmd "tinyauth/cmd/totp"
|
||||||
|
userCmd "tinyauth/cmd/user"
|
||||||
|
"tinyauth/internal/auth"
|
||||||
|
"tinyauth/internal/constants"
|
||||||
|
"tinyauth/internal/docker"
|
||||||
|
"tinyauth/internal/handlers"
|
||||||
|
"tinyauth/internal/hooks"
|
||||||
|
"tinyauth/internal/ldap"
|
||||||
|
"tinyauth/internal/providers"
|
||||||
|
"tinyauth/internal/server"
|
||||||
|
"tinyauth/internal/types"
|
||||||
|
"tinyauth/internal/utils"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "tinyauth",
|
||||||
|
Short: "The simplest way to protect your apps with a login screen.",
|
||||||
|
Long: `Tinyauth is a simple authentication middleware that adds simple username/password login or OAuth with Google, Github and any generic OAuth provider to all of your docker apps.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
var config types.Config
|
||||||
|
err := viper.Unmarshal(&config)
|
||||||
|
HandleError(err, "Failed to parse config")
|
||||||
|
|
||||||
|
// Check if secrets have a file associated with them
|
||||||
|
config.Secret = utils.GetSecret(config.Secret, config.SecretFile)
|
||||||
|
config.GithubClientSecret = utils.GetSecret(config.GithubClientSecret, config.GithubClientSecretFile)
|
||||||
|
config.GoogleClientSecret = utils.GetSecret(config.GoogleClientSecret, config.GoogleClientSecretFile)
|
||||||
|
config.GenericClientSecret = utils.GetSecret(config.GenericClientSecret, config.GenericClientSecretFile)
|
||||||
|
|
||||||
|
validator := validator.New()
|
||||||
|
err = validator.Struct(config)
|
||||||
|
HandleError(err, "Failed to validate config")
|
||||||
|
|
||||||
|
log.Logger = log.Level(zerolog.Level(config.LogLevel))
|
||||||
|
log.Info().Str("version", strings.TrimSpace(constants.Version)).Msg("Starting tinyauth")
|
||||||
|
|
||||||
|
log.Info().Msg("Parsing users")
|
||||||
|
users, err := utils.GetUsers(config.Users, config.UsersFile)
|
||||||
|
HandleError(err, "Failed to parse users")
|
||||||
|
|
||||||
|
log.Debug().Msg("Getting domain")
|
||||||
|
domain, err := utils.GetUpperDomain(config.AppURL)
|
||||||
|
HandleError(err, "Failed to get upper domain")
|
||||||
|
log.Info().Str("domain", domain).Msg("Using domain for cookie store")
|
||||||
|
|
||||||
|
cookieId := utils.GenerateIdentifier(strings.Split(domain, ".")[0])
|
||||||
|
sessionCookieName := fmt.Sprintf("%s-%s", constants.SessionCookieName, cookieId)
|
||||||
|
csrfCookieName := fmt.Sprintf("%s-%s", constants.CsrfCookieName, cookieId)
|
||||||
|
redirectCookieName := fmt.Sprintf("%s-%s", constants.RedirectCookieName, cookieId)
|
||||||
|
|
||||||
|
log.Debug().Msg("Deriving HMAC and encryption secrets")
|
||||||
|
|
||||||
|
hmacSecret, err := utils.DeriveKey(config.Secret, "hmac")
|
||||||
|
HandleError(err, "Failed to derive HMAC secret")
|
||||||
|
|
||||||
|
encryptionSecret, err := utils.DeriveKey(config.Secret, "encryption")
|
||||||
|
HandleError(err, "Failed to derive encryption secret")
|
||||||
|
|
||||||
|
// Split the config into service-specific sub-configs
|
||||||
|
oauthConfig := types.OAuthConfig{
|
||||||
|
GithubClientId: config.GithubClientId,
|
||||||
|
GithubClientSecret: config.GithubClientSecret,
|
||||||
|
GoogleClientId: config.GoogleClientId,
|
||||||
|
GoogleClientSecret: config.GoogleClientSecret,
|
||||||
|
GenericClientId: config.GenericClientId,
|
||||||
|
GenericClientSecret: config.GenericClientSecret,
|
||||||
|
GenericScopes: strings.Split(config.GenericScopes, ","),
|
||||||
|
GenericAuthURL: config.GenericAuthURL,
|
||||||
|
GenericTokenURL: config.GenericTokenURL,
|
||||||
|
GenericUserURL: config.GenericUserURL,
|
||||||
|
GenericSkipSSL: config.GenericSkipSSL,
|
||||||
|
AppURL: config.AppURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
handlersConfig := types.HandlersConfig{
|
||||||
|
AppURL: config.AppURL,
|
||||||
|
DisableContinue: config.DisableContinue,
|
||||||
|
Title: config.Title,
|
||||||
|
GenericName: config.GenericName,
|
||||||
|
CookieSecure: config.CookieSecure,
|
||||||
|
Domain: domain,
|
||||||
|
ForgotPasswordMessage: config.FogotPasswordMessage,
|
||||||
|
BackgroundImage: config.BackgroundImage,
|
||||||
|
OAuthAutoRedirect: config.OAuthAutoRedirect,
|
||||||
|
CsrfCookieName: csrfCookieName,
|
||||||
|
RedirectCookieName: redirectCookieName,
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConfig := types.ServerConfig{
|
||||||
|
Port: config.Port,
|
||||||
|
Address: config.Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
authConfig := types.AuthConfig{
|
||||||
|
Users: users,
|
||||||
|
OauthWhitelist: config.OAuthWhitelist,
|
||||||
|
CookieSecure: config.CookieSecure,
|
||||||
|
SessionExpiry: config.SessionExpiry,
|
||||||
|
Domain: domain,
|
||||||
|
LoginTimeout: config.LoginTimeout,
|
||||||
|
LoginMaxRetries: config.LoginMaxRetries,
|
||||||
|
SessionCookieName: sessionCookieName,
|
||||||
|
HMACSecret: hmacSecret,
|
||||||
|
EncryptionSecret: encryptionSecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
hooksConfig := types.HooksConfig{
|
||||||
|
Domain: domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
var ldapService *ldap.LDAP
|
||||||
|
|
||||||
|
if config.LdapAddress != "" {
|
||||||
|
log.Info().Msg("Using LDAP for authentication")
|
||||||
|
ldapConfig := types.LdapConfig{
|
||||||
|
Address: config.LdapAddress,
|
||||||
|
BindDN: config.LdapBindDN,
|
||||||
|
BindPassword: config.LdapBindPassword,
|
||||||
|
BaseDN: config.LdapBaseDN,
|
||||||
|
Insecure: config.LdapInsecure,
|
||||||
|
SearchFilter: config.LdapSearchFilter,
|
||||||
|
}
|
||||||
|
ldapService, err = ldap.NewLDAP(ldapConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to initialize LDAP service, disabling LDAP authentication")
|
||||||
|
ldapService = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Info().Msg("LDAP not configured, using local users or OAuth")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have a source of users
|
||||||
|
if len(users) == 0 && !utils.OAuthConfigured(config) && ldapService == nil {
|
||||||
|
HandleError(errors.New("err no users"), "Unable to find a source of users")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the services
|
||||||
|
docker, err := docker.NewDocker()
|
||||||
|
HandleError(err, "Failed to initialize docker")
|
||||||
|
auth := auth.NewAuth(authConfig, docker, ldapService)
|
||||||
|
providers := providers.NewProviders(oauthConfig)
|
||||||
|
hooks := hooks.NewHooks(hooksConfig, auth, providers)
|
||||||
|
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
||||||
|
srv, err := server.NewServer(serverConfig, handlers)
|
||||||
|
HandleError(err, "Failed to create server")
|
||||||
|
|
||||||
|
// Start up
|
||||||
|
err = srv.Start()
|
||||||
|
HandleError(err, "Failed to start server")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
err := rootCmd.Execute()
|
||||||
|
HandleError(err, "Failed to execute root command")
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleError(err error, msg string) {
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(userCmd.UserCmd())
|
||||||
|
rootCmd.AddCommand(totpCmd.TotpCmd())
|
||||||
|
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
|
||||||
|
rootCmd.Flags().Int("port", 3000, "Port to run the server on.")
|
||||||
|
rootCmd.Flags().String("address", "0.0.0.0", "Address to bind the server to.")
|
||||||
|
rootCmd.Flags().String("secret", "", "Secret to use for the cookie.")
|
||||||
|
rootCmd.Flags().String("secret-file", "", "Path to a file containing the secret.")
|
||||||
|
rootCmd.Flags().String("app-url", "", "The tinyauth URL.")
|
||||||
|
rootCmd.Flags().String("users", "", "Comma separated list of users in the format username:hash.")
|
||||||
|
rootCmd.Flags().String("users-file", "", "Path to a file containing users in the format username:hash.")
|
||||||
|
rootCmd.Flags().Bool("cookie-secure", false, "Send cookie over secure connection only.")
|
||||||
|
rootCmd.Flags().String("github-client-id", "", "Github OAuth client ID.")
|
||||||
|
rootCmd.Flags().String("github-client-secret", "", "Github OAuth client secret.")
|
||||||
|
rootCmd.Flags().String("github-client-secret-file", "", "Github OAuth client secret file.")
|
||||||
|
rootCmd.Flags().String("google-client-id", "", "Google OAuth client ID.")
|
||||||
|
rootCmd.Flags().String("google-client-secret", "", "Google OAuth client secret.")
|
||||||
|
rootCmd.Flags().String("google-client-secret-file", "", "Google OAuth client secret file.")
|
||||||
|
rootCmd.Flags().String("generic-client-id", "", "Generic OAuth client ID.")
|
||||||
|
rootCmd.Flags().String("generic-client-secret", "", "Generic OAuth client secret.")
|
||||||
|
rootCmd.Flags().String("generic-client-secret-file", "", "Generic OAuth client secret file.")
|
||||||
|
rootCmd.Flags().String("generic-scopes", "", "Generic OAuth scopes.")
|
||||||
|
rootCmd.Flags().String("generic-auth-url", "", "Generic OAuth auth URL.")
|
||||||
|
rootCmd.Flags().String("generic-token-url", "", "Generic OAuth token URL.")
|
||||||
|
rootCmd.Flags().String("generic-user-url", "", "Generic OAuth user info URL.")
|
||||||
|
rootCmd.Flags().String("generic-name", "Generic", "Generic OAuth provider name.")
|
||||||
|
rootCmd.Flags().Bool("generic-skip-ssl", false, "Skip SSL verification for the generic OAuth provider.")
|
||||||
|
rootCmd.Flags().Bool("disable-continue", false, "Disable continue screen and redirect to app directly.")
|
||||||
|
rootCmd.Flags().String("oauth-whitelist", "", "Comma separated list of email addresses to whitelist when using OAuth.")
|
||||||
|
rootCmd.Flags().String("oauth-auto-redirect", "none", "Auto redirect to the specified OAuth provider if configured. (available providers: github, google, generic)")
|
||||||
|
rootCmd.Flags().Int("session-expiry", 86400, "Session (cookie) expiration time in seconds.")
|
||||||
|
rootCmd.Flags().Int("login-timeout", 300, "Login timeout in seconds after max retries reached (0 to disable).")
|
||||||
|
rootCmd.Flags().Int("login-max-retries", 5, "Maximum login attempts before timeout (0 to disable).")
|
||||||
|
rootCmd.Flags().Int("log-level", 1, "Log level.")
|
||||||
|
rootCmd.Flags().String("app-title", "Tinyauth", "Title of the app.")
|
||||||
|
rootCmd.Flags().String("forgot-password-message", "", "Message to show on the forgot password page.")
|
||||||
|
rootCmd.Flags().String("background-image", "/background.jpg", "Background image URL for the login page.")
|
||||||
|
rootCmd.Flags().String("ldap-address", "", "LDAP server address (e.g. ldap://localhost:389).")
|
||||||
|
rootCmd.Flags().String("ldap-bind-dn", "", "LDAP bind DN (e.g. uid=user,dc=example,dc=com).")
|
||||||
|
rootCmd.Flags().String("ldap-bind-password", "", "LDAP bind password.")
|
||||||
|
rootCmd.Flags().String("ldap-base-dn", "", "LDAP base DN (e.g. dc=example,dc=com).")
|
||||||
|
rootCmd.Flags().Bool("ldap-insecure", false, "Skip certificate verification for the LDAP server.")
|
||||||
|
rootCmd.Flags().String("ldap-search-filter", "(uid=%s)", "LDAP search filter for user lookup.")
|
||||||
|
|
||||||
|
viper.BindEnv("port", "PORT")
|
||||||
|
viper.BindEnv("address", "ADDRESS")
|
||||||
|
viper.BindEnv("secret", "SECRET")
|
||||||
|
viper.BindEnv("secret-file", "SECRET_FILE")
|
||||||
|
viper.BindEnv("app-url", "APP_URL")
|
||||||
|
viper.BindEnv("users", "USERS")
|
||||||
|
viper.BindEnv("users-file", "USERS_FILE")
|
||||||
|
viper.BindEnv("cookie-secure", "COOKIE_SECURE")
|
||||||
|
viper.BindEnv("github-client-id", "GITHUB_CLIENT_ID")
|
||||||
|
viper.BindEnv("github-client-secret", "GITHUB_CLIENT_SECRET")
|
||||||
|
viper.BindEnv("github-client-secret-file", "GITHUB_CLIENT_SECRET_FILE")
|
||||||
|
viper.BindEnv("google-client-id", "GOOGLE_CLIENT_ID")
|
||||||
|
viper.BindEnv("google-client-secret", "GOOGLE_CLIENT_SECRET")
|
||||||
|
viper.BindEnv("google-client-secret-file", "GOOGLE_CLIENT_SECRET_FILE")
|
||||||
|
viper.BindEnv("generic-client-id", "GENERIC_CLIENT_ID")
|
||||||
|
viper.BindEnv("generic-client-secret", "GENERIC_CLIENT_SECRET")
|
||||||
|
viper.BindEnv("generic-client-secret-file", "GENERIC_CLIENT_SECRET_FILE")
|
||||||
|
viper.BindEnv("generic-scopes", "GENERIC_SCOPES")
|
||||||
|
viper.BindEnv("generic-auth-url", "GENERIC_AUTH_URL")
|
||||||
|
viper.BindEnv("generic-token-url", "GENERIC_TOKEN_URL")
|
||||||
|
viper.BindEnv("generic-user-url", "GENERIC_USER_URL")
|
||||||
|
viper.BindEnv("generic-name", "GENERIC_NAME")
|
||||||
|
viper.BindEnv("generic-skip-ssl", "GENERIC_SKIP_SSL")
|
||||||
|
viper.BindEnv("disable-continue", "DISABLE_CONTINUE")
|
||||||
|
viper.BindEnv("oauth-whitelist", "OAUTH_WHITELIST")
|
||||||
|
viper.BindEnv("oauth-auto-redirect", "OAUTH_AUTO_REDIRECT")
|
||||||
|
viper.BindEnv("session-expiry", "SESSION_EXPIRY")
|
||||||
|
viper.BindEnv("log-level", "LOG_LEVEL")
|
||||||
|
viper.BindEnv("app-title", "APP_TITLE")
|
||||||
|
viper.BindEnv("login-timeout", "LOGIN_TIMEOUT")
|
||||||
|
viper.BindEnv("login-max-retries", "LOGIN_MAX_RETRIES")
|
||||||
|
viper.BindEnv("forgot-password-message", "FORGOT_PASSWORD_MESSAGE")
|
||||||
|
viper.BindEnv("background-image", "BACKGROUND_IMAGE")
|
||||||
|
viper.BindEnv("ldap-address", "LDAP_ADDRESS")
|
||||||
|
viper.BindEnv("ldap-bind-dn", "LDAP_BIND_DN")
|
||||||
|
viper.BindEnv("ldap-bind-password", "LDAP_BIND_PASSWORD")
|
||||||
|
viper.BindEnv("ldap-base-dn", "LDAP_BASE_DN")
|
||||||
|
viper.BindEnv("ldap-insecure", "LDAP_INSECURE")
|
||||||
|
viper.BindEnv("ldap-search-filter", "LDAP_SEARCH_FILTER")
|
||||||
|
|
||||||
|
viper.BindPFlags(rootCmd.Flags())
|
||||||
|
}
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/huh"
|
|
||||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
|
||||||
"github.com/traefik/paerser/cli"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CreateUserConfig struct {
|
|
||||||
Interactive bool `description:"Create a user interactively."`
|
|
||||||
Docker bool `description:"Format output for docker."`
|
|
||||||
Username string `description:"Username."`
|
|
||||||
Password string `description:"Password."`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCreateUserConfig() *CreateUserConfig {
|
|
||||||
return &CreateUserConfig{
|
|
||||||
Interactive: false,
|
|
||||||
Docker: false,
|
|
||||||
Username: "",
|
|
||||||
Password: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createUserCmd() *cli.Command {
|
|
||||||
tCfg := NewCreateUserConfig()
|
|
||||||
|
|
||||||
loaders := []cli.ResourceLoader{
|
|
||||||
&cli.FlagLoader{},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Description: "Create a user",
|
|
||||||
Configuration: tCfg,
|
|
||||||
Resources: loaders,
|
|
||||||
Run: func(_ []string) error {
|
|
||||||
tlog.NewSimpleLogger().Init()
|
|
||||||
|
|
||||||
if tCfg.Interactive {
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().Title("Username").Value(&tCfg.Username).Validate((func(s string) error {
|
|
||||||
if s == "" {
|
|
||||||
return errors.New("username cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})),
|
|
||||||
huh.NewInput().Title("Password").Value(&tCfg.Password).Validate((func(s string) error {
|
|
||||||
if s == "" {
|
|
||||||
return errors.New("password cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})),
|
|
||||||
huh.NewSelect[bool]().Title("Format the output for Docker?").Options(huh.NewOption("Yes", true), huh.NewOption("No", false)).Value(&tCfg.Docker),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
var baseTheme *huh.Theme = huh.ThemeBase()
|
|
||||||
|
|
||||||
err := form.WithTheme(baseTheme).Run()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to run interactive prompt: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tCfg.Username == "" || tCfg.Password == "" {
|
|
||||||
return errors.New("username and password cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
tlog.App.Info().Str("username", tCfg.Username).Msg("Creating user")
|
|
||||||
|
|
||||||
passwd, err := bcrypt.GenerateFromPassword([]byte(tCfg.Password), bcrypt.DefaultCost)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to hash password: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If docker format is enabled, escape the dollar sign
|
|
||||||
passwdStr := string(passwd)
|
|
||||||
if tCfg.Docker {
|
|
||||||
passwdStr = strings.ReplaceAll(passwdStr, "$", "$$")
|
|
||||||
}
|
|
||||||
|
|
||||||
tlog.App.Info().Str("user", fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)).Msg("User created")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
|
||||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/huh"
|
|
||||||
"github.com/mdp/qrterminal/v3"
|
|
||||||
"github.com/pquerna/otp/totp"
|
|
||||||
"github.com/traefik/paerser/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GenerateTotpConfig struct {
|
|
||||||
Interactive bool `description:"Generate a TOTP secret interactively."`
|
|
||||||
User string `description:"Your current user (username:hash)."`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGenerateTotpConfig() *GenerateTotpConfig {
|
|
||||||
return &GenerateTotpConfig{
|
|
||||||
Interactive: false,
|
|
||||||
User: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateTotpCmd() *cli.Command {
|
|
||||||
tCfg := NewGenerateTotpConfig()
|
|
||||||
|
|
||||||
loaders := []cli.ResourceLoader{
|
|
||||||
&cli.FlagLoader{},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "generate",
|
|
||||||
Description: "Generate a TOTP secret",
|
|
||||||
Configuration: tCfg,
|
|
||||||
Resources: loaders,
|
|
||||||
Run: func(_ []string) error {
|
|
||||||
tlog.NewSimpleLogger().Init()
|
|
||||||
|
|
||||||
if tCfg.Interactive {
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().Title("Current user (username:hash)").Value(&tCfg.User).Validate((func(s string) error {
|
|
||||||
if s == "" {
|
|
||||||
return errors.New("user cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
var baseTheme *huh.Theme = huh.ThemeBase()
|
|
||||||
|
|
||||||
err := form.WithTheme(baseTheme).Run()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to run interactive prompt: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := utils.ParseUser(tCfg.User)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse user: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
docker := false
|
|
||||||
if strings.Contains(tCfg.User, "$$") {
|
|
||||||
docker = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.TotpSecret != "" {
|
|
||||||
return fmt.Errorf("user already has a TOTP secret")
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := totp.Generate(totp.GenerateOpts{
|
|
||||||
Issuer: "Tinyauth",
|
|
||||||
AccountName: user.Username,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to generate TOTP secret: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
secret := key.Secret()
|
|
||||||
|
|
||||||
tlog.App.Info().Str("secret", secret).Msg("Generated TOTP secret")
|
|
||||||
|
|
||||||
tlog.App.Info().Msg("Generated QR code")
|
|
||||||
|
|
||||||
config := qrterminal.Config{
|
|
||||||
Level: qrterminal.L,
|
|
||||||
Writer: os.Stdout,
|
|
||||||
BlackChar: qrterminal.BLACK,
|
|
||||||
WhiteChar: qrterminal.WHITE,
|
|
||||||
QuietZone: 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
qrterminal.GenerateWithConfig(key.URL(), config)
|
|
||||||
|
|
||||||
user.TotpSecret = secret
|
|
||||||
|
|
||||||
// If using docker escape re-escape it
|
|
||||||
if docker {
|
|
||||||
user.Password = strings.ReplaceAll(user.Password, "$", "$$")
|
|
||||||
}
|
|
||||||
|
|
||||||
tlog.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.")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
|
||||||
"github.com/traefik/paerser/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
type healthzResponse struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func healthcheckCmd() *cli.Command {
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "healthcheck",
|
|
||||||
Description: "Perform a health check",
|
|
||||||
Configuration: nil,
|
|
||||||
Resources: nil,
|
|
||||||
AllowArg: true,
|
|
||||||
Run: func(args []string) error {
|
|
||||||
tlog.NewSimpleLogger().Init()
|
|
||||||
|
|
||||||
appUrl := "http://127.0.0.1:3000"
|
|
||||||
|
|
||||||
srvAddr := os.Getenv("TINYAUTH_SERVER_ADDRESS")
|
|
||||||
srvPort := os.Getenv("TINYAUTH_SERVER_PORT")
|
|
||||||
|
|
||||||
if srvAddr != "" && srvPort != "" {
|
|
||||||
appUrl = fmt.Sprintf("http://%s:%s", srvAddr, srvPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) > 0 {
|
|
||||||
appUrl = args[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if appUrl == "" {
|
|
||||||
return errors.New("Could not determine app URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
tlog.App.Info().Str("app_url", appUrl).Msg("Performing health check")
|
|
||||||
|
|
||||||
client := http.Client{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", appUrl+"/api/healthz", nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to perform request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("service is not healthy, got: %s", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var healthResp healthzResponse
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(body, &healthResp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to decode response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlog.App.Info().Interface("response", healthResp).Msg("Tinyauth is healthy")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/steveiliop56/tinyauth/internal/bootstrap"
|
|
||||||
"github.com/steveiliop56/tinyauth/internal/config"
|
|
||||||
"github.com/steveiliop56/tinyauth/internal/utils/loaders"
|
|
||||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/traefik/paerser/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
tConfig := config.NewDefaultConfiguration()
|
|
||||||
|
|
||||||
loaders := []cli.ResourceLoader{
|
|
||||||
&loaders.FileLoader{},
|
|
||||||
&loaders.FlagLoader{},
|
|
||||||
&loaders.EnvLoader{},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdTinyauth := &cli.Command{
|
|
||||||
Name: "tinyauth",
|
|
||||||
Description: "The simplest way to protect your apps with a login screen.",
|
|
||||||
Configuration: tConfig,
|
|
||||||
Resources: loaders,
|
|
||||||
Run: func(_ []string) error {
|
|
||||||
return runCmd(*tConfig)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdUser := &cli.Command{
|
|
||||||
Name: "user",
|
|
||||||
Description: "Utilities for creating and verifying Tinyauth users.",
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdTotp := &cli.Command{
|
|
||||||
Name: "totp",
|
|
||||||
Description: "Utilities for creating Tinyauth TOTP users.",
|
|
||||||
}
|
|
||||||
|
|
||||||
err := cmdTinyauth.AddCommand(versionCmd())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to add version command")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmdUser.AddCommand(verifyUserCmd())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to add verify command")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmdTinyauth.AddCommand(healthcheckCmd())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to add healthcheck command")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmdTotp.AddCommand(generateTotpCmd())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to add generate command")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmdUser.AddCommand(createUserCmd())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to add create command")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmdTinyauth.AddCommand(cmdUser)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to add user command")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmdTinyauth.AddCommand(cmdTotp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to add totp command")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cli.Execute(cmdTinyauth)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to execute command")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCmd(cfg config.Config) error {
|
|
||||||
logger := tlog.NewLogger(cfg.Log)
|
|
||||||
logger.Init()
|
|
||||||
|
|
||||||
tlog.App.Info().Str("version", config.Version).Msg("Starting tinyauth")
|
|
||||||
|
|
||||||
app := bootstrap.NewBootstrapApp(cfg)
|
|
||||||
|
|
||||||
err := app.Setup()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to bootstrap app: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
|
||||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/huh"
|
|
||||||
"github.com/pquerna/otp/totp"
|
|
||||||
"github.com/traefik/paerser/cli"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VerifyUserConfig struct {
|
|
||||||
Interactive bool `description:"Validate a user interactively."`
|
|
||||||
Username string `description:"Username."`
|
|
||||||
Password string `description:"Password."`
|
|
||||||
Totp string `description:"TOTP code."`
|
|
||||||
User string `description:"Hash (username:hash:totp)."`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVerifyUserConfig() *VerifyUserConfig {
|
|
||||||
return &VerifyUserConfig{
|
|
||||||
Interactive: false,
|
|
||||||
Username: "",
|
|
||||||
Password: "",
|
|
||||||
Totp: "",
|
|
||||||
User: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyUserCmd() *cli.Command {
|
|
||||||
tCfg := NewVerifyUserConfig()
|
|
||||||
|
|
||||||
loaders := []cli.ResourceLoader{
|
|
||||||
&cli.FlagLoader{},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "verify",
|
|
||||||
Description: "Verify a user is set up correctly.",
|
|
||||||
Configuration: tCfg,
|
|
||||||
Resources: loaders,
|
|
||||||
Run: func(_ []string) error {
|
|
||||||
tlog.NewSimpleLogger().Init()
|
|
||||||
|
|
||||||
if tCfg.Interactive {
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().Title("User (username:hash:totp)").Value(&tCfg.User).Validate((func(s string) error {
|
|
||||||
if s == "" {
|
|
||||||
return errors.New("user cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})),
|
|
||||||
huh.NewInput().Title("Username").Value(&tCfg.Username).Validate((func(s string) error {
|
|
||||||
if s == "" {
|
|
||||||
return errors.New("username cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})),
|
|
||||||
huh.NewInput().Title("Password").Value(&tCfg.Password).Validate((func(s string) error {
|
|
||||||
if s == "" {
|
|
||||||
return errors.New("password cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})),
|
|
||||||
huh.NewInput().Title("TOTP Code (optional)").Value(&tCfg.Totp),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
var baseTheme *huh.Theme = huh.ThemeBase()
|
|
||||||
|
|
||||||
err := form.WithTheme(baseTheme).Run()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to run interactive prompt: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := utils.ParseUser(tCfg.User)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse user: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Username != tCfg.Username {
|
|
||||||
return fmt.Errorf("username is incorrect")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(tCfg.Password))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("password is incorrect: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.TotpSecret == "" {
|
|
||||||
if tCfg.Totp != "" {
|
|
||||||
tlog.App.Warn().Msg("User does not have TOTP secret")
|
|
||||||
}
|
|
||||||
tlog.App.Info().Msg("User verified")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ok := totp.Validate(tCfg.Totp, user.TotpSecret)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("TOTP code incorrect")
|
|
||||||
}
|
|
||||||
|
|
||||||
tlog.App.Info().Msg("User verified")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/steveiliop56/tinyauth/internal/config"
|
|
||||||
|
|
||||||
"github.com/traefik/paerser/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func versionCmd() *cli.Command {
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "version",
|
|
||||||
Description: "Print the version number of Tinyauth.",
|
|
||||||
Configuration: nil,
|
|
||||||
Resources: nil,
|
|
||||||
Run: func(_ []string) error {
|
|
||||||
fmt.Printf("Version: %s\n", config.Version)
|
|
||||||
fmt.Printf("Commit Hash: %s\n", config.CommitHash)
|
|
||||||
fmt.Printf("Build Timestamp: %s\n", config.BuildTimestamp)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
99
cmd/totp/generate/generate.go
Normal file
99
cmd/totp/generate/generate.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"tinyauth/internal/utils"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
|
"github.com/mdp/qrterminal/v3"
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var interactive bool
|
||||||
|
|
||||||
|
// Input user
|
||||||
|
var iUser string
|
||||||
|
|
||||||
|
var GenerateCmd = &cobra.Command{
|
||||||
|
Use: "generate",
|
||||||
|
Short: "Generate a totp secret",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
log.Logger = log.Level(zerolog.InfoLevel)
|
||||||
|
|
||||||
|
if interactive {
|
||||||
|
form := huh.NewForm(
|
||||||
|
huh.NewGroup(
|
||||||
|
huh.NewInput().Title("Current username:hash").Value(&iUser).Validate((func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return errors.New("user cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var baseTheme *huh.Theme = huh.ThemeBase()
|
||||||
|
err := form.WithTheme(baseTheme).Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Form failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := utils.ParseUser(iUser)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to parse user")
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerEscape := false
|
||||||
|
if strings.Contains(iUser, "$$") {
|
||||||
|
dockerEscape = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.TotpSecret != "" {
|
||||||
|
log.Fatal().Msg("User already has a totp secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := totp.Generate(totp.GenerateOpts{
|
||||||
|
Issuer: "Tinyauth",
|
||||||
|
AccountName: user.Username,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to generate totp secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := key.Secret()
|
||||||
|
|
||||||
|
log.Info().Str("secret", secret).Msg("Generated totp secret")
|
||||||
|
|
||||||
|
log.Info().Msg("Generated QR code")
|
||||||
|
|
||||||
|
config := qrterminal.Config{
|
||||||
|
Level: qrterminal.L,
|
||||||
|
Writer: os.Stdout,
|
||||||
|
BlackChar: qrterminal.BLACK,
|
||||||
|
WhiteChar: qrterminal.WHITE,
|
||||||
|
QuietZone: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
qrterminal.GenerateWithConfig(key.URL(), config)
|
||||||
|
|
||||||
|
user.TotpSecret = secret
|
||||||
|
|
||||||
|
// If using docker escape re-escape it
|
||||||
|
if dockerEscape {
|
||||||
|
user.Password = strings.ReplaceAll(user.Password, "$", "$$")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.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.")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
GenerateCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Run in interactive mode")
|
||||||
|
GenerateCmd.Flags().StringVar(&iUser, "user", "", "Your current username:hash")
|
||||||
|
}
|
||||||
17
cmd/totp/totp.go
Normal file
17
cmd/totp/totp.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tinyauth/cmd/totp/generate"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TotpCmd() *cobra.Command {
|
||||||
|
totpCmd := &cobra.Command{
|
||||||
|
Use: "totp",
|
||||||
|
Short: "Totp utilities",
|
||||||
|
Long: `Utilities for creating and verifying totp codes.`,
|
||||||
|
}
|
||||||
|
totpCmd.AddCommand(generate.GenerateCmd)
|
||||||
|
return totpCmd
|
||||||
|
}
|
||||||
80
cmd/user/create/create.go
Normal file
80
cmd/user/create/create.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package create
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var interactive bool
|
||||||
|
var docker bool
|
||||||
|
|
||||||
|
// i stands for input
|
||||||
|
var iUsername string
|
||||||
|
var iPassword string
|
||||||
|
|
||||||
|
var CreateCmd = &cobra.Command{
|
||||||
|
Use: "create",
|
||||||
|
Short: "Create a user",
|
||||||
|
Long: `Create a user either interactively or by passing flags.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
log.Logger = log.Level(zerolog.InfoLevel)
|
||||||
|
|
||||||
|
if interactive {
|
||||||
|
form := huh.NewForm(
|
||||||
|
huh.NewGroup(
|
||||||
|
huh.NewInput().Title("Username").Value(&iUsername).Validate((func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return errors.New("username cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})),
|
||||||
|
huh.NewInput().Title("Password").Value(&iPassword).Validate((func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return errors.New("password cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})),
|
||||||
|
huh.NewSelect[bool]().Title("Format the output for docker?").Options(huh.NewOption("Yes", true), huh.NewOption("No", false)).Value(&docker),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var baseTheme *huh.Theme = huh.ThemeBase()
|
||||||
|
err := form.WithTheme(baseTheme).Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Form failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if iUsername == "" || iPassword == "" {
|
||||||
|
log.Fatal().Err(errors.New("error invalid input")).Msg("Username and password cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Str("username", iUsername).Str("password", iPassword).Bool("docker", docker).Msg("Creating user")
|
||||||
|
|
||||||
|
password, err := bcrypt.GenerateFromPassword([]byte(iPassword), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to hash password")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If docker format is enabled, escape the dollar sign
|
||||||
|
passwordString := string(password)
|
||||||
|
if docker {
|
||||||
|
passwordString = strings.ReplaceAll(passwordString, "$", "$$")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Str("user", fmt.Sprintf("%s:%s", iUsername, passwordString)).Msg("User created")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
CreateCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Create a user interactively")
|
||||||
|
CreateCmd.Flags().BoolVar(&docker, "docker", false, "Format output for docker")
|
||||||
|
CreateCmd.Flags().StringVar(&iUsername, "username", "", "Username")
|
||||||
|
CreateCmd.Flags().StringVar(&iPassword, "password", "", "Password")
|
||||||
|
}
|
||||||
19
cmd/user/user.go
Normal file
19
cmd/user/user.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tinyauth/cmd/user/create"
|
||||||
|
"tinyauth/cmd/user/verify"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UserCmd() *cobra.Command {
|
||||||
|
userCmd := &cobra.Command{
|
||||||
|
Use: "user",
|
||||||
|
Short: "User utilities",
|
||||||
|
Long: `Utilities for creating and verifying tinyauth compatible users.`,
|
||||||
|
}
|
||||||
|
userCmd.AddCommand(create.CreateCmd)
|
||||||
|
userCmd.AddCommand(verify.VerifyCmd)
|
||||||
|
return userCmd
|
||||||
|
}
|
||||||
101
cmd/user/verify/verify.go
Normal file
101
cmd/user/verify/verify.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package verify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"tinyauth/internal/utils"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var interactive bool
|
||||||
|
var docker bool
|
||||||
|
|
||||||
|
// i stands for input
|
||||||
|
var iUsername string
|
||||||
|
var iPassword string
|
||||||
|
var iTotp string
|
||||||
|
var iUser string
|
||||||
|
|
||||||
|
var VerifyCmd = &cobra.Command{
|
||||||
|
Use: "verify",
|
||||||
|
Short: "Verify a user is set up correctly",
|
||||||
|
Long: `Verify a user is set up correctly meaning that it has a correct username, password and totp code.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
log.Logger = log.Level(zerolog.InfoLevel)
|
||||||
|
|
||||||
|
if interactive {
|
||||||
|
form := huh.NewForm(
|
||||||
|
huh.NewGroup(
|
||||||
|
huh.NewInput().Title("User (username:hash:totp)").Value(&iUser).Validate((func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return errors.New("user cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})),
|
||||||
|
huh.NewInput().Title("Username").Value(&iUsername).Validate((func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return errors.New("username cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})),
|
||||||
|
huh.NewInput().Title("Password").Value(&iPassword).Validate((func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return errors.New("password cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})),
|
||||||
|
huh.NewInput().Title("Totp Code (if setup)").Value(&iTotp),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var baseTheme *huh.Theme = huh.ThemeBase()
|
||||||
|
err := form.WithTheme(baseTheme).Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Form failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := utils.ParseUser(iUser)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to parse user")
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Username != iUsername {
|
||||||
|
log.Fatal().Msg("Username is incorrect")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(iPassword))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Msg("Ppassword is incorrect")
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.TotpSecret == "" {
|
||||||
|
if iTotp != "" {
|
||||||
|
log.Warn().Msg("User does not have 2fa secret")
|
||||||
|
}
|
||||||
|
log.Info().Msg("User verified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := totp.Validate(iTotp, user.TotpSecret)
|
||||||
|
if !ok {
|
||||||
|
log.Fatal().Msg("Totp code incorrect")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msg("User verified")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
VerifyCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Create a user interactively")
|
||||||
|
VerifyCmd.Flags().BoolVar(&docker, "docker", false, "Is the user formatted for docker?")
|
||||||
|
VerifyCmd.Flags().StringVar(&iUsername, "username", "", "Username")
|
||||||
|
VerifyCmd.Flags().StringVar(&iPassword, "password", "", "Password")
|
||||||
|
VerifyCmd.Flags().StringVar(&iTotp, "totp", "", "Totp code")
|
||||||
|
VerifyCmd.Flags().StringVar(&iUser, "user", "", "Hash (username:hash:totp combination)")
|
||||||
|
}
|
||||||
23
cmd/version.go
Normal file
23
cmd/version.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"tinyauth/internal/constants"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var versionCmd = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "Print the version number of Tinyauth",
|
||||||
|
Long: `All software has versions. This is Tinyauth's`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Printf("Version: %s\n", constants.Version)
|
||||||
|
fmt.Printf("Commit Hash: %s\n", constants.CommitHash)
|
||||||
|
fmt.Printf("Build Timestamp: %s\n", constants.BuildTimestamp)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(versionCmd)
|
||||||
|
}
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
# Tinyauth Example Configuration
|
|
||||||
|
|
||||||
# The base URL where Tinyauth is accessible
|
|
||||||
appUrl: "https://auth.example.com"
|
|
||||||
# Directory for static resources
|
|
||||||
resourcesDir: "./resources"
|
|
||||||
# Path to SQLite database file
|
|
||||||
databasePath: "./tinyauth.db"
|
|
||||||
# Disable usage analytics
|
|
||||||
disableAnalytics: false
|
|
||||||
# Disable static resource serving
|
|
||||||
disableResources: false
|
|
||||||
# Disable UI warning messages
|
|
||||||
disableUIWarnings: false
|
|
||||||
|
|
||||||
# Logging Configuration
|
|
||||||
log:
|
|
||||||
# Log level: trace, debug, info, warn, error
|
|
||||||
level: "info"
|
|
||||||
json: false
|
|
||||||
streams:
|
|
||||||
app:
|
|
||||||
enabled: true
|
|
||||||
level: "warn"
|
|
||||||
http:
|
|
||||||
enabled: true
|
|
||||||
level: "debug"
|
|
||||||
audit:
|
|
||||||
enabled: false
|
|
||||||
level: "info"
|
|
||||||
|
|
||||||
# Server Configuration
|
|
||||||
server:
|
|
||||||
# Port to listen on
|
|
||||||
port: 3000
|
|
||||||
# Interface to bind to (0.0.0.0 for all interfaces)
|
|
||||||
address: "0.0.0.0"
|
|
||||||
# Unix socket path (optional, overrides port/address if set)
|
|
||||||
socketPath: ""
|
|
||||||
# Comma-separated list of trusted proxy IPs/CIDRs
|
|
||||||
trustedProxies: ""
|
|
||||||
|
|
||||||
# Authentication Configuration
|
|
||||||
auth:
|
|
||||||
# Format: username:bcrypt_hash (use bcrypt to generate hash)
|
|
||||||
users: "admin:$2a$10$example_bcrypt_hash_here"
|
|
||||||
# Path to external users file (optional)
|
|
||||||
usersFile: ""
|
|
||||||
# Enable secure cookies (requires HTTPS)
|
|
||||||
secureCookie: false
|
|
||||||
# Session expiry in seconds (3600 = 1 hour)
|
|
||||||
sessionExpiry: 3600
|
|
||||||
# Session maximum lifetime in seconds (0 = unlimited)
|
|
||||||
sessionMaxLifetime: 0
|
|
||||||
# Login timeout in seconds (300 = 5 minutes)
|
|
||||||
loginTimeout: 300
|
|
||||||
# Maximum login retries before lockout
|
|
||||||
loginMaxRetries: 3
|
|
||||||
|
|
||||||
# OAuth Configuration
|
|
||||||
oauth:
|
|
||||||
# Regex pattern for allowed email addresses (e.g., /@example\.com$/)
|
|
||||||
whitelist: ""
|
|
||||||
# Provider ID to auto-redirect to (skips login page)
|
|
||||||
autoRedirect: ""
|
|
||||||
# OAuth Provider Configuration (replace myprovider with your provider name)
|
|
||||||
providers:
|
|
||||||
myprovider:
|
|
||||||
clientId: "your_client_id_here"
|
|
||||||
clientSecret: "your_client_secret_here"
|
|
||||||
authUrl: "https://provider.example.com/oauth/authorize"
|
|
||||||
tokenUrl: "https://provider.example.com/oauth/token"
|
|
||||||
userInfoUrl: "https://provider.example.com/oauth/userinfo"
|
|
||||||
redirectUrl: "https://auth.example.com/api/oauth/callback/myprovider"
|
|
||||||
scopes: "openid email profile"
|
|
||||||
name: "My OAuth Provider"
|
|
||||||
# Allow insecure connections (self-signed certificates)
|
|
||||||
insecure: false
|
|
||||||
|
|
||||||
# UI Customization
|
|
||||||
ui:
|
|
||||||
# Custom title for login page
|
|
||||||
title: "Tinyauth"
|
|
||||||
# Message shown on forgot password page
|
|
||||||
forgotPasswordMessage: "Contact your administrator to reset your password"
|
|
||||||
# Background image URL for login page
|
|
||||||
backgroundImage: ""
|
|
||||||
|
|
||||||
# LDAP Configuration (optional)
|
|
||||||
ldap:
|
|
||||||
# LDAP server address
|
|
||||||
address: "ldap://ldap.example.com:389"
|
|
||||||
# DN for binding to LDAP server
|
|
||||||
bindDn: "cn=readonly,dc=example,dc=com"
|
|
||||||
# Password for bind DN
|
|
||||||
bindPassword: "your_bind_password"
|
|
||||||
# Base DN for user searches
|
|
||||||
baseDn: "dc=example,dc=com"
|
|
||||||
# Search filter (%s will be replaced with username)
|
|
||||||
searchFilter: "(&(uid=%s)(memberOf=cn=users,ou=groups,dc=example,dc=com))"
|
|
||||||
# Allow insecure LDAP connections
|
|
||||||
insecure: false
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
image: traefik:v3.6
|
image: traefik:v3.3
|
||||||
command: --api.insecure=true --providers.docker
|
command: --api.insecure=true --providers.docker
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
@@ -13,8 +13,8 @@ services:
|
|||||||
image: traefik/whoami:latest
|
image: traefik/whoami:latest
|
||||||
labels:
|
labels:
|
||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
traefik.http.routers.whoami.rule: Host(`whoami.127.0.0.1.sslip.io`)
|
traefik.http.routers.nginx.rule: Host(`whoami.example.com`)
|
||||||
traefik.http.routers.whoami.middlewares: tinyauth
|
traefik.http.routers.nginx.middlewares: tinyauth
|
||||||
|
|
||||||
tinyauth-frontend:
|
tinyauth-frontend:
|
||||||
container_name: tinyauth-frontend
|
container_name: tinyauth-frontend
|
||||||
@@ -27,27 +27,22 @@ services:
|
|||||||
- 5173:5173
|
- 5173:5173
|
||||||
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.example.com`)
|
||||||
|
|
||||||
tinyauth-backend:
|
tinyauth-backend:
|
||||||
container_name: tinyauth-backend
|
container_name: tinyauth-backend
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.dev
|
dockerfile: Dockerfile.dev
|
||||||
args:
|
|
||||||
- VERSION=development
|
|
||||||
- COMMIT_HASH=development
|
|
||||||
- BUILD_TIMESTAMP=000-00-00T00:00:00Z
|
|
||||||
env_file: .env
|
env_file: .env
|
||||||
volumes:
|
volumes:
|
||||||
- ./internal:/tinyauth/internal
|
- ./internal:/tinyauth/internal
|
||||||
- ./cmd:/tinyauth/cmd
|
- ./cmd:/tinyauth/cmd
|
||||||
|
- ./main.go:/tinyauth/main.go
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- ./data:/data
|
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
- 4000:4000
|
- 4000:4000
|
||||||
labels:
|
labels:
|
||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth-backend:3000/api/auth/traefik
|
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth-backend:3000/api/auth/traefik
|
||||||
traefik.http.middlewares.tinyauth.forwardauth.authResponseHeaders: remote-user, remote-sub, remote-name, remote-email, remote-groups
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
image: traefik:v3.6
|
image: traefik:v3.3
|
||||||
command: --api.insecure=true --providers.docker
|
command: --api.insecure=true --providers.docker
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
@@ -13,17 +13,16 @@ services:
|
|||||||
image: traefik/whoami:latest
|
image: traefik/whoami:latest
|
||||||
labels:
|
labels:
|
||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
traefik.http.routers.whoami.rule: Host(`whoami.example.com`)
|
traefik.http.routers.nginx.rule: Host(`whoami.example.com`)
|
||||||
traefik.http.routers.whoami.middlewares: tinyauth
|
traefik.http.routers.nginx.middlewares: tinyauth
|
||||||
|
|
||||||
tinyauth:
|
tinyauth:
|
||||||
container_name: tinyauth
|
container_name: tinyauth
|
||||||
image: ghcr.io/steveiliop56/tinyauth:v3
|
image: ghcr.io/steveiliop56/tinyauth:v3
|
||||||
environment:
|
environment:
|
||||||
- TINYAUTH_APPURL=https://tinyauth.example.com
|
- SECRET=some-random-32-chars-string
|
||||||
- TINYAUTH_AUTH_USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u # user:password
|
- APP_URL=https://tinyauth.example.com
|
||||||
volumes:
|
- USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u # user:password
|
||||||
- ./data:/data
|
|
||||||
labels:
|
labels:
|
||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
|
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
|
||||||
|
|||||||
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@@ -22,6 +22,3 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
# Stats out
|
|
||||||
stats.html
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,11 +8,10 @@
|
|||||||
<link rel="shortcut icon" href="/favicon.ico" />
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||||
<meta name="apple-mobile-web-app-title" content="Tinyauth" />
|
<meta name="apple-mobile-web-app-title" content="Tinyauth" />
|
||||||
<meta name="robots" content="nofollow, noindex" />
|
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<title>Tinyauth</title>
|
<title>Tinyauth</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="dark">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "tinyauth",
|
"name": "tinyauth-shadcn",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -7,54 +7,52 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview"
|
||||||
"tsc": "tsc -b"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.1.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-label": "^2.1.8",
|
"@radix-ui/react-select": "^2.2.5",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-separator": "^1.1.8",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tanstack/react-query": "^5.83.0",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"axios": "^1.10.0",
|
||||||
"axios": "^1.13.5",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"i18next": "^25.8.7",
|
"dompurify": "^3.2.6",
|
||||||
"i18next-browser-languagedetector": "^8.2.1",
|
"i18next": "^25.3.2",
|
||||||
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"i18next-resources-to-backend": "^1.2.1",
|
"i18next-resources-to-backend": "^1.2.1",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.564.0",
|
"lucide-react": "^0.525.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.2.4",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.71.1",
|
"react-hook-form": "^7.60.0",
|
||||||
"react-i18next": "^16.5.4",
|
"react-i18next": "^15.6.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-router": "^7.13.0",
|
"react-router": "^7.7.0",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.6",
|
||||||
"tailwind-merge": "^3.4.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.11",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^9.31.0",
|
||||||
"@tanstack/eslint-plugin-query": "^5.91.4",
|
"@tanstack/eslint-plugin-query": "^5.81.2",
|
||||||
"@types/node": "^25.2.3",
|
"@types/node": "^24.0.14",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@vitejs/plugin-react": "^5.1.4",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
"eslint": "^10.0.0",
|
"eslint": "^9.31.0",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.5.0",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^17.3.0",
|
"globals": "^16.3.0",
|
||||||
"prettier": "3.8.1",
|
"prettier": "3.6.2",
|
||||||
"rollup-plugin-visualizer": "^6.0.5",
|
"tw-animate-css": "^1.3.5",
|
||||||
"tw-animate-css": "^1.4.0",
|
"typescript": "~5.8.3",
|
||||||
"typescript": "~5.9.3",
|
"typescript-eslint": "^8.37.0",
|
||||||
"typescript-eslint": "^8.55.0",
|
"vite": "^7.0.5"
|
||||||
"vite": "^7.3.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,8 @@ export const App = () => {
|
|||||||
const { isLoggedIn } = useUserContext();
|
const { isLoggedIn } = useUserContext();
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
return <Navigate to="/logout" replace />;
|
return <Navigate to="/logout" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Navigate to="/login" replace />;
|
return <Navigate to="/login" />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ import z from "zod";
|
|||||||
interface Props {
|
interface Props {
|
||||||
formId: string;
|
formId: string;
|
||||||
onSubmit: (code: TotpSchema) => void;
|
onSubmit: (code: TotpSchema) => void;
|
||||||
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TotpForm = (props: Props) => {
|
export const TotpForm = (props: Props) => {
|
||||||
const { formId, onSubmit } = props;
|
const { formId, onSubmit, loading } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
z.config({
|
z.config({
|
||||||
@@ -29,14 +30,6 @@ export const TotpForm = (props: Props) => {
|
|||||||
resolver: zodResolver(totpSchema),
|
resolver: zodResolver(totpSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleChange = (value: string) => {
|
|
||||||
form.setValue("code", value, { shouldDirty: true, shouldValidate: true });
|
|
||||||
|
|
||||||
if (value.length === 6) {
|
|
||||||
onSubmit({ code: value });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form id={formId} onSubmit={form.handleSubmit(onSubmit)}>
|
<form id={formId} onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
@@ -48,10 +41,9 @@ export const TotpForm = (props: Props) => {
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<InputOTP
|
<InputOTP
|
||||||
maxLength={6}
|
maxLength={6}
|
||||||
|
disabled={loading}
|
||||||
{...field}
|
{...field}
|
||||||
autoComplete="one-time-code"
|
autoComplete="one-time-code"
|
||||||
autoFocus
|
|
||||||
onChange={handleChange}
|
|
||||||
>
|
>
|
||||||
<InputOTPGroup>
|
<InputOTPGroup>
|
||||||
<InputOTPSlot index={0} />
|
<InputOTPSlot index={0} />
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
import {
|
|
||||||
Card,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "../ui/card";
|
|
||||||
import { Button } from "../ui/button";
|
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
|
||||||
import { useLocation } from "react-router";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
onClick: () => void;
|
|
||||||
appUrl: string;
|
|
||||||
currentUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DomainWarning = (props: Props) => {
|
|
||||||
const { onClick, appUrl, currentUrl } = props;
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { search } = useLocation();
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
|
||||||
const redirectUri = searchParams.get("redirect_uri");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card role="alert" aria-live="assertive" className="min-w-xs sm:min-w-sm">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-3xl">{t("domainWarningTitle")}</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
<Trans
|
|
||||||
t={t}
|
|
||||||
i18nKey="domainWarningSubtitle"
|
|
||||||
values={{ appUrl, currentUrl }}
|
|
||||||
components={{ code: <code /> }}
|
|
||||||
shouldUnescape={true}
|
|
||||||
/>
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter className="flex flex-col items-stretch gap-2">
|
|
||||||
<Button onClick={onClick} variant="warning">
|
|
||||||
{t("ignoreTitle")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
window.location.assign(
|
|
||||||
`${appUrl}/login?redirect_uri=${encodeURIComponent(redirectUri || "")}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
{t("goToCorrectDomainTitle")}
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { SVGProps } from "react";
|
import type { SVGProps } from "react";
|
||||||
|
|
||||||
export function OAuthIcon(props: SVGProps<SVGSVGElement>) {
|
export function GenericIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import type { SVGProps } from "react";
|
|
||||||
|
|
||||||
export function MicrosoftIcon(props: SVGProps<SVGSVGElement>) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="2em"
|
|
||||||
height="2em"
|
|
||||||
viewBox="0 0 256 256"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path fill="#f1511b" d="M121.666 121.666H0V0h121.666z"></path>
|
|
||||||
<path fill="#80cc28" d="M256 121.666H134.335V0H256z"></path>
|
|
||||||
<path fill="#00adef" d="M121.663 256.002H0V134.336h121.663z"></path>
|
|
||||||
<path fill="#fbbc09" d="M256 256.002H134.335V134.336H256z"></path>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import type { SVGProps } from "react";
|
|
||||||
|
|
||||||
export function PocketIDIcon(props: SVGProps<SVGSVGElement>) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlSpace="preserve"
|
|
||||||
width={512}
|
|
||||||
height={512}
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<circle cx="256" cy="256" r="256" />
|
|
||||||
<path
|
|
||||||
d="M268.6 102.4c64.4 0 116.8 52.4 116.8 116.7 0 25.3-8 49.4-23 69.6-14.8 19.9-35 34.3-58.4 41.7l-6.5 2-15.5-76.2 4.3-2c14-6.7 23-21.1 23-36.6 0-22.4-18.2-40.6-40.6-40.6S228 195.2 228 217.6c0 15.5 9 29.8 23 36.6l4.2 2-25 153.4h-69.5V102.4z"
|
|
||||||
className="fill-white"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import type { SVGProps } from "react";
|
|
||||||
|
|
||||||
export function TailscaleIcon(props: SVGProps<SVGSVGElement>) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlSpace="preserve"
|
|
||||||
width={512}
|
|
||||||
height={512}
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
className="opacity-80"
|
|
||||||
fill="currentColor"
|
|
||||||
d="M65.6 318.1c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9S1.8 219 1.8 254.2s28.6 63.9 63.8 63.9m191.6 0c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.9 28.6-63.9 63.9 28.6 63.9 63.9 63.9m0 193.9c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.9 28.6-63.9 63.9 28.6 63.9 63.9 63.9m189.2-193.9c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.9 28.6-63.9 63.9 28.6 63.9 63.9 63.9"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<path
|
|
||||||
d="M65.6 127.7c35.3 0 63.9-28.6 63.9-63.9S100.9 0 65.6 0 1.8 28.6 1.8 63.9s28.6 63.8 63.8 63.8m0 384.3c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.8 28.7-63.8 63.9S30.4 512 65.6 512m191.6-384.3c35.3 0 63.9-28.6 63.9-63.9S292.5 0 257.2 0s-63.9 28.6-63.9 63.9 28.6 63.8 63.9 63.8m189.2 0c35.3 0 63.9-28.6 63.9-63.9S481.6 0 446.4 0c-35.3 0-63.9 28.6-63.9 63.9s28.6 63.8 63.9 63.8m0 384.3c35.3 0 63.9-28.6 63.9-63.9s-28.6-63.9-63.9-63.9-63.9 28.6-63.9 63.9 28.6 63.9 63.9 63.9"
|
|
||||||
className="opacity-20"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -18,10 +18,9 @@ export const LanguageSelector = () => {
|
|||||||
setLanguage(option as SupportedLanguage);
|
setLanguage(option as SupportedLanguage);
|
||||||
i18n.changeLanguage(option as SupportedLanguage);
|
i18n.changeLanguage(option as SupportedLanguage);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select onValueChange={handleSelect} value={language}>
|
<Select onValueChange={handleSelect} value={language}>
|
||||||
<SelectTrigger>
|
<SelectTrigger className="absolute top-5 right-5">
|
||||||
<SelectValue placeholder="Select language" />
|
<SelectValue placeholder="Select language" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
import { useAppContext } from "@/context/app-context";
|
import { useAppContext } from "@/context/app-context";
|
||||||
import { LanguageSelector } from "../language/language";
|
import { LanguageSelector } from "../language/language";
|
||||||
import { Outlet } from "react-router";
|
import { Outlet } from "react-router";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
|
||||||
import { DomainWarning } from "../domain-warning/domain-warning";
|
|
||||||
import { ThemeToggle } from "../theme-toggle/theme-toggle";
|
|
||||||
|
|
||||||
const BaseLayout = ({ children }: { children: React.ReactNode }) => {
|
export const Layout = () => {
|
||||||
const { backgroundImage, title } = useAppContext();
|
const { backgroundImage } = useAppContext();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.title = title;
|
|
||||||
}, [title]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -21,42 +14,8 @@ const BaseLayout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="absolute top-5 right-5 flex flex-row gap-2">
|
<LanguageSelector />
|
||||||
<ThemeToggle />
|
<Outlet />
|
||||||
<LanguageSelector />
|
|
||||||
</div>
|
|
||||||
{children}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Layout = () => {
|
|
||||||
const { appUrl, disableUiWarnings } = useAppContext();
|
|
||||||
const [ignoreDomainWarning, setIgnoreDomainWarning] = useState(() => {
|
|
||||||
return window.sessionStorage.getItem("ignoreDomainWarning") === "true";
|
|
||||||
});
|
|
||||||
const currentUrl = window.location.origin;
|
|
||||||
|
|
||||||
const handleIgnore = useCallback(() => {
|
|
||||||
window.sessionStorage.setItem("ignoreDomainWarning", "true");
|
|
||||||
setIgnoreDomainWarning(true);
|
|
||||||
}, [setIgnoreDomainWarning]);
|
|
||||||
|
|
||||||
if (!ignoreDomainWarning && !disableUiWarnings && appUrl !== currentUrl) {
|
|
||||||
return (
|
|
||||||
<BaseLayout>
|
|
||||||
<DomainWarning
|
|
||||||
appUrl={appUrl}
|
|
||||||
currentUrl={currentUrl}
|
|
||||||
onClick={() => handleIgnore()}
|
|
||||||
/>
|
|
||||||
</BaseLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseLayout>
|
|
||||||
<Outlet />
|
|
||||||
</BaseLayout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
import { createContext, useContext, useEffect, useState } from "react";
|
|
||||||
|
|
||||||
type Theme = "dark" | "light" | "system";
|
|
||||||
|
|
||||||
type ThemeProviderProps = {
|
|
||||||
children: React.ReactNode;
|
|
||||||
defaultTheme?: Theme;
|
|
||||||
storageKey?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ThemeProviderState = {
|
|
||||||
theme: Theme;
|
|
||||||
setTheme: (theme: Theme) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialState: ThemeProviderState = {
|
|
||||||
theme: "system",
|
|
||||||
setTheme: () => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
|
|
||||||
|
|
||||||
export function ThemeProvider({
|
|
||||||
children,
|
|
||||||
defaultTheme = "system",
|
|
||||||
storageKey = "vite-ui-theme",
|
|
||||||
...props
|
|
||||||
}: ThemeProviderProps) {
|
|
||||||
const [theme, setTheme] = useState<Theme>(
|
|
||||||
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const root = window.document.documentElement;
|
|
||||||
|
|
||||||
root.classList.remove("light", "dark");
|
|
||||||
|
|
||||||
if (theme === "system") {
|
|
||||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
|
||||||
.matches
|
|
||||||
? "dark"
|
|
||||||
: "light";
|
|
||||||
|
|
||||||
root.classList.add(systemTheme);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
root.classList.add(theme);
|
|
||||||
}, [theme]);
|
|
||||||
|
|
||||||
const value = {
|
|
||||||
theme,
|
|
||||||
setTheme: (theme: Theme) => {
|
|
||||||
localStorage.setItem(storageKey, theme);
|
|
||||||
setTheme(theme);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemeProviderContext.Provider {...props} value={value}>
|
|
||||||
{children}
|
|
||||||
</ThemeProviderContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useTheme = () => {
|
|
||||||
const context = useContext(ThemeProviderContext);
|
|
||||||
|
|
||||||
if (context === undefined)
|
|
||||||
throw new Error("useTheme must be used within a ThemeProvider");
|
|
||||||
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive hover:cursor-pointer",
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
@@ -22,7 +22,7 @@ const buttonVariants = cva(
|
|||||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
warning:
|
warning:
|
||||||
"bg-amber-500 text-white shadow-xs hover:bg-amber-400 focus-visible:ring-amber-200/20 dark:focus-visible:ring-amber-400/40",
|
"bg-amber-500 text-white shadow-xs hover:bg-amber-400 focus-visible:ring-amber-200/20 dark:focus-visible:ring-amber-400/40 dark:bg-amber-600",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
|
|||||||
@@ -1,255 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
function DropdownMenu({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
|
||||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuPortal({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuTrigger({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.Trigger
|
|
||||||
data-slot="dropdown-menu-trigger"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuContent({
|
|
||||||
className,
|
|
||||||
sideOffset = 4,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.Portal>
|
|
||||||
<DropdownMenuPrimitive.Content
|
|
||||||
data-slot="dropdown-menu-content"
|
|
||||||
sideOffset={sideOffset}
|
|
||||||
className={cn(
|
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</DropdownMenuPrimitive.Portal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuGroup({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuItem({
|
|
||||||
className,
|
|
||||||
inset,
|
|
||||||
variant = "default",
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
||||||
inset?: boolean
|
|
||||||
variant?: "default" | "destructive"
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.Item
|
|
||||||
data-slot="dropdown-menu-item"
|
|
||||||
data-inset={inset}
|
|
||||||
data-variant={variant}
|
|
||||||
className={cn(
|
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuCheckboxItem({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
checked,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.CheckboxItem
|
|
||||||
data-slot="dropdown-menu-checkbox-item"
|
|
||||||
className={cn(
|
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
checked={checked}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
||||||
<DropdownMenuPrimitive.ItemIndicator>
|
|
||||||
<CheckIcon className="size-4" />
|
|
||||||
</DropdownMenuPrimitive.ItemIndicator>
|
|
||||||
</span>
|
|
||||||
{children}
|
|
||||||
</DropdownMenuPrimitive.CheckboxItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuRadioGroup({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.RadioGroup
|
|
||||||
data-slot="dropdown-menu-radio-group"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuRadioItem({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.RadioItem
|
|
||||||
data-slot="dropdown-menu-radio-item"
|
|
||||||
className={cn(
|
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
||||||
<DropdownMenuPrimitive.ItemIndicator>
|
|
||||||
<CircleIcon className="size-2 fill-current" />
|
|
||||||
</DropdownMenuPrimitive.ItemIndicator>
|
|
||||||
</span>
|
|
||||||
{children}
|
|
||||||
</DropdownMenuPrimitive.RadioItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuLabel({
|
|
||||||
className,
|
|
||||||
inset,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
|
||||||
inset?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.Label
|
|
||||||
data-slot="dropdown-menu-label"
|
|
||||||
data-inset={inset}
|
|
||||||
className={cn(
|
|
||||||
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuSeparator({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.Separator
|
|
||||||
data-slot="dropdown-menu-separator"
|
|
||||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuShortcut({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"span">) {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
data-slot="dropdown-menu-shortcut"
|
|
||||||
className={cn(
|
|
||||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuSub({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
|
||||||
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuSubTrigger({
|
|
||||||
className,
|
|
||||||
inset,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
||||||
inset?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.SubTrigger
|
|
||||||
data-slot="dropdown-menu-sub-trigger"
|
|
||||||
data-inset={inset}
|
|
||||||
className={cn(
|
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<ChevronRightIcon className="ml-auto size-4" />
|
|
||||||
</DropdownMenuPrimitive.SubTrigger>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownMenuSubContent({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.SubContent
|
|
||||||
data-slot="dropdown-menu-sub-content"
|
|
||||||
className={cn(
|
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuPortal,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuGroup,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuCheckboxItem,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuShortcut,
|
|
||||||
DropdownMenuSub,
|
|
||||||
DropdownMenuSubTrigger,
|
|
||||||
DropdownMenuSubContent,
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,7 @@ function SelectTrigger({
|
|||||||
data-slot="select-trigger"
|
data-slot="select-trigger"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"hover:cursor-pointer border-input data-[placeholder]:text-card-foreground [&_svg:not([class*='text-'])]:text-card-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex w-fit items-center justify-between gap-2 rounded-md border bg-card hover:bg-card/90 px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-card dark:hover:bg-card/90 flex w-fit items-center justify-between gap-2 rounded-md border bg-card hover:bg-card/90 px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useTheme } from "../providers/theme-provider";
|
import { useTheme } from "next-themes";
|
||||||
import { Toaster as Sonner, ToasterProps } from "sonner";
|
import { Toaster as Sonner, ToasterProps } from "sonner";
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme } = useTheme();
|
const { theme = "system" } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sonner
|
<Sonner
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const AppContextProvider = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { isFetching, data, error } = useSuspenseQuery({
|
const { isFetching, data, error } = useSuspenseQuery({
|
||||||
queryKey: ["app"],
|
queryKey: ["app"],
|
||||||
queryFn: () => axios.get("/api/context/app").then((res) => res.data),
|
queryFn: () => axios.get("/api/app").then((res) => res.data),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error && !isFetching) {
|
if (error && !isFetching) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const UserContextProvider = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { isFetching, data, error } = useSuspenseQuery({
|
const { isFetching, data, error } = useSuspenseQuery({
|
||||||
queryKey: ["user"],
|
queryKey: ["user"],
|
||||||
queryFn: () => axios.get("/api/context/user").then((res) => res.data),
|
queryFn: () => axios.get("/api/user").then((res) => res.data),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error && !isFetching) {
|
if (error && !isFetching) {
|
||||||
|
|||||||
@@ -156,11 +156,7 @@ ul {
|
|||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
@apply relative rounded bg-muted px-[0.2rem] py-[0.1rem] font-mono text-sm font-semibold break-all;
|
@apply relative rounded bg-muted px-[0.2rem] py-[0.1rem] font-mono text-sm font-semibold;
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
@apply bg-accent border border-border rounded-md p-2 whitespace-break-spaces;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lead {
|
.lead {
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
export type OIDCValues = {
|
|
||||||
scope: string;
|
|
||||||
response_type: string;
|
|
||||||
client_id: string;
|
|
||||||
redirect_uri: string;
|
|
||||||
state: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IuseOIDCParams {
|
|
||||||
values: OIDCValues;
|
|
||||||
compiled: string;
|
|
||||||
isOidc: boolean;
|
|
||||||
missingParams: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const optionalParams: string[] = ["state"];
|
|
||||||
|
|
||||||
export function useOIDCParams(params: URLSearchParams): IuseOIDCParams {
|
|
||||||
let compiled: string = "";
|
|
||||||
let isOidc = false;
|
|
||||||
const missingParams: string[] = [];
|
|
||||||
|
|
||||||
const values: OIDCValues = {
|
|
||||||
scope: params.get("scope") ?? "",
|
|
||||||
response_type: params.get("response_type") ?? "",
|
|
||||||
client_id: params.get("client_id") ?? "",
|
|
||||||
redirect_uri: params.get("redirect_uri") ?? "",
|
|
||||||
state: params.get("state") ?? "",
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const key of Object.keys(values)) {
|
|
||||||
if (!values[key as keyof OIDCValues]) {
|
|
||||||
if (!optionalParams.includes(key)) {
|
|
||||||
missingParams.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (missingParams.length === 0) {
|
|
||||||
isOidc = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isOidc) {
|
|
||||||
compiled = new URLSearchParams(values).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
values,
|
|
||||||
compiled,
|
|
||||||
isOidc,
|
|
||||||
missingParams,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
type IuseRedirectUri = {
|
|
||||||
url?: URL;
|
|
||||||
valid: boolean;
|
|
||||||
trusted: boolean;
|
|
||||||
allowedProto: boolean;
|
|
||||||
httpsDowngrade: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useRedirectUri = (
|
|
||||||
redirect_uri: string | null,
|
|
||||||
cookieDomain: string,
|
|
||||||
): IuseRedirectUri => {
|
|
||||||
let isValid = false;
|
|
||||||
let isTrusted = false;
|
|
||||||
let isAllowedProto = false;
|
|
||||||
let isHttpsDowngrade = false;
|
|
||||||
|
|
||||||
if (!redirect_uri) {
|
|
||||||
return {
|
|
||||||
valid: isValid,
|
|
||||||
trusted: isTrusted,
|
|
||||||
allowedProto: isAllowedProto,
|
|
||||||
httpsDowngrade: isHttpsDowngrade,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let url: URL;
|
|
||||||
|
|
||||||
try {
|
|
||||||
url = new URL(redirect_uri);
|
|
||||||
} catch {
|
|
||||||
return {
|
|
||||||
valid: isValid,
|
|
||||||
trusted: isTrusted,
|
|
||||||
allowedProto: isAllowedProto,
|
|
||||||
httpsDowngrade: isHttpsDowngrade,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
isValid = true;
|
|
||||||
|
|
||||||
if (
|
|
||||||
url.hostname == cookieDomain ||
|
|
||||||
url.hostname.endsWith(`.${cookieDomain}`)
|
|
||||||
) {
|
|
||||||
isTrusted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.protocol == "http:" || url.protocol == "https:") {
|
|
||||||
isAllowedProto = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.location.protocol == "https:" && url.protocol == "http:") {
|
|
||||||
isHttpsDowngrade = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
valid: isValid,
|
|
||||||
trusted: isTrusted,
|
|
||||||
allowedProto: isAllowedProto,
|
|
||||||
httpsDowngrade: isHttpsDowngrade,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
15
frontend/src/lib/hooks/use-is-mounted.ts
Normal file
15
frontend/src/lib/hooks/use-is-mounted.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
export function useIsMounted(): () => boolean {
|
||||||
|
const isMounted = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isMounted.current = true;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted.current = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return useCallback(() => isMounted.current, []);
|
||||||
|
}
|
||||||
@@ -14,11 +14,12 @@ i18n
|
|||||||
.init({
|
.init({
|
||||||
fallbackLng: "en",
|
fallbackLng: "en",
|
||||||
debug: import.meta.env.MODE === "development",
|
debug: import.meta.env.MODE === "development",
|
||||||
nonExplicitSupportedLngs: true,
|
|
||||||
load: "currentOnly",
|
interpolation: {
|
||||||
detection: {
|
escapeValue: false,
|
||||||
lookupLocalStorage: "tinyauth-lang",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
load: "currentOnly",
|
||||||
});
|
});
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ export const languages = {
|
|||||||
"nl-NL": "Nederlands",
|
"nl-NL": "Nederlands",
|
||||||
"no-NO": "Norsk",
|
"no-NO": "Norsk",
|
||||||
"pl-PL": "Polski",
|
"pl-PL": "Polski",
|
||||||
"pt-BR": "Português (Brasil)",
|
"pt-BR": "Português",
|
||||||
"pt-PT": "Português (Portugal)",
|
"pt-PT": "Português",
|
||||||
"ro-RO": "Română",
|
"ro-RO": "Română",
|
||||||
"ru-RU": "Русский",
|
"ru-RU": "Русский",
|
||||||
"sr-SP": "Српски",
|
"sr-SP": "Српски",
|
||||||
@@ -28,7 +28,7 @@ export const languages = {
|
|||||||
"uk-UA": "Українська",
|
"uk-UA": "Українська",
|
||||||
"vi-VN": "Tiếng Việt",
|
"vi-VN": "Tiếng Việt",
|
||||||
"zh-CN": "简体中文",
|
"zh-CN": "简体中文",
|
||||||
"zh-TW": "繁體中文",
|
"zh-TW": "繁體中文(台灣)",
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SupportedLanguage = keyof typeof languages;
|
export type SupportedLanguage = keyof typeof languages;
|
||||||
|
|||||||
@@ -51,7 +51,6 @@
|
|||||||
"forgotPasswordTitle": "Forgot your password?",
|
"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:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
@@ -59,23 +58,5 @@
|
|||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -51,31 +51,12 @@
|
|||||||
"forgotPasswordTitle": "نسيت كلمة المرور؟",
|
"forgotPasswordTitle": "نسيت كلمة المرور؟",
|
||||||
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||||
"errorTitle": "حدث خطأ",
|
"errorTitle": "حدث خطأ",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
"invalidInput": "Invalid input",
|
"invalidInput": "Invalid input",
|
||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "تجاهل",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"forgotPasswordTitle": "Forgot your password?",
|
"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:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
@@ -59,23 +58,5 @@
|
|||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"loginOauthFailSubtitle": "Nepodařilo se získat OAuth URL",
|
"loginOauthFailSubtitle": "Nepodařilo se získat OAuth URL",
|
||||||
"loginOauthSuccessTitle": "Přesměrování",
|
"loginOauthSuccessTitle": "Přesměrování",
|
||||||
"loginOauthSuccessSubtitle": "Přesměrování k poskytovateli OAuth",
|
"loginOauthSuccessSubtitle": "Přesměrování k poskytovateli OAuth",
|
||||||
"loginOauthAutoRedirectTitle": "Automatické přesměrování OAuth",
|
"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": "Redirect now",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Pokračovat",
|
"continueTitle": "Pokračovat",
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"forgotPasswordTitle": "Zapomněli jste heslo?",
|
"forgotPasswordTitle": "Zapomněli jste heslo?",
|
||||||
"failedToFetchProvidersTitle": "Nepodařilo se načíst poskytovatele ověřování. Zkontrolujte prosím konfiguraci.",
|
"failedToFetchProvidersTitle": "Nepodařilo se načíst poskytovatele ověřování. Zkontrolujte prosím konfiguraci.",
|
||||||
"errorTitle": "Došlo k chybě",
|
"errorTitle": "Došlo k chybě",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "Nastala chyba při pokusu o provedení této akce. Pro více informací prosím zkontrolujte konzolu.",
|
"errorSubtitle": "Nastala chyba při pokusu o provedení této akce. Pro více informací prosím zkontrolujte konzolu.",
|
||||||
"forgotPasswordMessage": "Heslo můžete obnovit změnou proměnné `USERS`.",
|
"forgotPasswordMessage": "Heslo můžete obnovit změnou proměnné `USERS`.",
|
||||||
"fieldRequired": "Toto pole je povinné",
|
"fieldRequired": "Toto pole je povinné",
|
||||||
@@ -59,23 +58,5 @@
|
|||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"forgotPasswordTitle": "Glemt din adgangskode?",
|
"forgotPasswordTitle": "Glemt din adgangskode?",
|
||||||
"failedToFetchProvidersTitle": "Kunne ikke indlæse godkendelsesudbydere. Tjek venligst din konfiguration.",
|
"failedToFetchProvidersTitle": "Kunne ikke indlæse godkendelsesudbydere. Tjek venligst din konfiguration.",
|
||||||
"errorTitle": "Der opstod en fejl",
|
"errorTitle": "Der opstod en fejl",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "Der opstod en fejl under forsøget på at udføre denne handling. Tjek venligst konsollen for mere information.",
|
"errorSubtitle": "Der opstod en fejl under forsøget på at udføre denne handling. Tjek venligst konsollen for mere information.",
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
@@ -59,23 +58,5 @@
|
|||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -14,17 +14,17 @@
|
|||||||
"loginOauthFailSubtitle": "Fehler beim Abrufen der OAuth-URL",
|
"loginOauthFailSubtitle": "Fehler beim Abrufen der OAuth-URL",
|
||||||
"loginOauthSuccessTitle": "Leite weiter",
|
"loginOauthSuccessTitle": "Leite weiter",
|
||||||
"loginOauthSuccessSubtitle": "Weiterleitung zu Ihrem OAuth-Provider",
|
"loginOauthSuccessSubtitle": "Weiterleitung zu Ihrem OAuth-Provider",
|
||||||
"loginOauthAutoRedirectTitle": "Automatische OAuth-Weiterleitung",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "Sie werden automatisch zu Ihrem OAuth-Anbieter weitergeleitet, um sich zu authentifizieren.",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "Jetzt weiterleiten",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Weiter",
|
"continueTitle": "Weiter",
|
||||||
"continueRedirectingTitle": "Leite weiter...",
|
"continueRedirectingTitle": "Leite weiter...",
|
||||||
"continueRedirectingSubtitle": "Sie sollten in Kürze zur App weitergeleitet werden",
|
"continueRedirectingSubtitle": "Sie sollten in Kürze zur App weitergeleitet werden",
|
||||||
"continueRedirectManually": "Manuell weiterleiten",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "Unsichere Weiterleitung",
|
"continueInsecureRedirectTitle": "Unsichere Weiterleitung",
|
||||||
"continueInsecureRedirectSubtitle": "Sie versuchen von <code>https</code> auf <code>http</code> weiterzuleiten, was unsicher ist. Sind Sie sicher, dass Sie fortfahren möchten?",
|
"continueInsecureRedirectSubtitle": "Sie versuchen von <code>https</code> auf <code>http</code> weiterzuleiten, was unsicher ist. Sind Sie sicher, dass Sie fortfahren möchten?",
|
||||||
"continueUntrustedRedirectTitle": "Nicht vertrauenswürdige Weiterleitung",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "Sie versuchen auf eine Domain umzuleiten, die nicht mit Ihrer konfigurierten Domain übereinstimmt (<code>{{cookieDomain}}</code>). Sind Sie sicher, dass Sie fortfahren möchten?",
|
"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": "Abmelden fehlgeschlagen",
|
"logoutFailTitle": "Abmelden fehlgeschlagen",
|
||||||
"logoutFailSubtitle": "Bitte versuchen Sie es erneut",
|
"logoutFailSubtitle": "Bitte versuchen Sie es erneut",
|
||||||
"logoutSuccessTitle": "Abgemeldet",
|
"logoutSuccessTitle": "Abgemeldet",
|
||||||
@@ -51,31 +51,12 @@
|
|||||||
"forgotPasswordTitle": "Passwort vergessen?",
|
"forgotPasswordTitle": "Passwort vergessen?",
|
||||||
"failedToFetchProvidersTitle": "Fehler beim Laden der Authentifizierungsanbieter. Bitte überprüfen Sie Ihre Konfiguration.",
|
"failedToFetchProvidersTitle": "Fehler beim Laden der Authentifizierungsanbieter. Bitte überprüfen Sie Ihre Konfiguration.",
|
||||||
"errorTitle": "Ein Fehler ist aufgetreten",
|
"errorTitle": "Ein Fehler ist aufgetreten",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "Beim Versuch, diese Aktion auszuführen, ist ein Fehler aufgetreten. Bitte überprüfen Sie die Konsole für weitere Informationen.",
|
"errorSubtitle": "Beim Versuch, diese Aktion auszuführen, ist ein Fehler aufgetreten. Bitte überprüfen Sie die Konsole für weitere Informationen.",
|
||||||
"forgotPasswordMessage": "Das Passwort kann durch Änderung der 'USERS' Variable zurückgesetzt werden.",
|
"forgotPasswordMessage": "Das Passwort kann durch Änderung der 'USERS' Variable zurückgesetzt werden.",
|
||||||
"fieldRequired": "Dieses Feld ist notwendig",
|
"fieldRequired": "Dieses Feld ist notwendig",
|
||||||
"invalidInput": "Ungültige Eingabe",
|
"invalidInput": "Ungültige Eingabe",
|
||||||
"domainWarningTitle": "Ungültige Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "Diese Instanz ist so konfiguriert, dass sie von <code>{{appUrl}}</code> aufgerufen werden kann, aber <code>{{currentUrl}}</code> wird verwendet. Wenn Sie fortfahren, können Probleme bei der Authentifizierung auftreten.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignorieren",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Zur korrekten Domain gehen",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -14,17 +14,17 @@
|
|||||||
"loginOauthFailSubtitle": "Αποτυχία λήψης OAuth URL",
|
"loginOauthFailSubtitle": "Αποτυχία λήψης OAuth URL",
|
||||||
"loginOauthSuccessTitle": "Ανακατεύθυνση",
|
"loginOauthSuccessTitle": "Ανακατεύθυνση",
|
||||||
"loginOauthSuccessSubtitle": "Ανακατεύθυνση στον πάροχο OAuth σας",
|
"loginOauthSuccessSubtitle": "Ανακατεύθυνση στον πάροχο OAuth σας",
|
||||||
"loginOauthAutoRedirectTitle": "Αυτόματη Ανακατεύθυνση OAuth",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "Θα ανακατευθυνθείτε αυτόματα στον πάροχο OAuth σας για να επαληθευτείτε.",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "Ανακατεύθυνση τώρα",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Συνέχεια",
|
"continueTitle": "Συνέχεια",
|
||||||
"continueRedirectingTitle": "Ανακατεύθυνση...",
|
"continueRedirectingTitle": "Ανακατεύθυνση...",
|
||||||
"continueRedirectingSubtitle": "Θα πρέπει να μεταφερθείτε σύντομα στην εφαρμογή σας",
|
"continueRedirectingSubtitle": "Θα πρέπει να μεταφερθείτε σύντομα στην εφαρμογή σας",
|
||||||
"continueRedirectManually": "Χειροκίνητη ανακατεύθυνση",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "Μη ασφαλής ανακατεύθυνση",
|
"continueInsecureRedirectTitle": "Μη ασφαλής ανακατεύθυνση",
|
||||||
"continueInsecureRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε από <code>https</code> σε <code>http</code> το οποίο δεν είναι ασφαλές. Είστε σίγουροι ότι θέλετε να συνεχίσετε;",
|
"continueInsecureRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε από <code>https</code> σε <code>http</code> το οποίο δεν είναι ασφαλές. Είστε σίγουροι ότι θέλετε να συνεχίσετε;",
|
||||||
"continueUntrustedRedirectTitle": "Μη έμπιστη ανακατεύθυνση",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε σε ένα domain που δεν ταιριάζει με το ρυθμισμένο domain σας (<code>{{cookieDomain}}</code>). Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
|
"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": "Αποτυχία αποσύνδεσης",
|
||||||
"logoutFailSubtitle": "Παρακαλώ δοκιμάστε ξανά",
|
"logoutFailSubtitle": "Παρακαλώ δοκιμάστε ξανά",
|
||||||
"logoutSuccessTitle": "Αποσυνδεδεμένος",
|
"logoutSuccessTitle": "Αποσυνδεδεμένος",
|
||||||
@@ -51,31 +51,12 @@
|
|||||||
"forgotPasswordTitle": "Ξεχάσατε το συνθηματικό σας;",
|
"forgotPasswordTitle": "Ξεχάσατε το συνθηματικό σας;",
|
||||||
"failedToFetchProvidersTitle": "Αποτυχία φόρτωσης παρόχων πιστοποίησης. Παρακαλώ ελέγξτε τις ρυθμίσεις σας.",
|
"failedToFetchProvidersTitle": "Αποτυχία φόρτωσης παρόχων πιστοποίησης. Παρακαλώ ελέγξτε τις ρυθμίσεις σας.",
|
||||||
"errorTitle": "Παρουσιάστηκε ένα σφάλμα",
|
"errorTitle": "Παρουσιάστηκε ένα σφάλμα",
|
||||||
"errorSubtitleInfo": "Το ακόλουθο σφάλμα προέκυψε κατά την επεξεργασία του αιτήματός σας:",
|
|
||||||
"errorSubtitle": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια εκτέλεσης αυτής της ενέργειας. Ελέγξτε την κονσόλα για περισσότερες πληροφορίες.",
|
"errorSubtitle": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια εκτέλεσης αυτής της ενέργειας. Ελέγξτε την κονσόλα για περισσότερες πληροφορίες.",
|
||||||
"forgotPasswordMessage": "Μπορείτε να επαναφέρετε τον κωδικό πρόσβασής σας αλλάζοντας τη μεταβλητή περιβάλλοντος `USERS`.",
|
"forgotPasswordMessage": "Μπορείτε να επαναφέρετε τον κωδικό πρόσβασής σας αλλάζοντας τη μεταβλητή περιβάλλοντος `USERS`.",
|
||||||
"fieldRequired": "Αυτό το πεδίο είναι υποχρεωτικό",
|
"fieldRequired": "Αυτό το πεδίο είναι υποχρεωτικό",
|
||||||
"invalidInput": "Μη έγκυρη καταχώρηση",
|
"invalidInput": "Μη έγκυρη καταχώρηση",
|
||||||
"domainWarningTitle": "Μη έγκυρο domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "Αυτή η εφαρμογή έχει ρυθμιστεί για πρόσβαση από <code>{{appUrl}}</code>, αλλά <code>{{currentUrl}}</code> χρησιμοποιείται. Αν συνεχίσετε, μπορεί να αντιμετωπίσετε προβλήματα με την ταυτοποίηση.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Παράβλεψη",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Μεταβείτε στο σωστό domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Εξουσιοδότηση",
|
}
|
||||||
"authorizeCardTitle": "Συνέχεια στην εφαρμογή {{app}};",
|
|
||||||
"authorizeSubtitle": "Θα θέλατε να συνεχίσετε σε αυτή την εφαρμογή; Παρακαλώ ελέγξτε προσεκτικά τα δικαιώματα που ζητούνται από την εφαρμογή.",
|
|
||||||
"authorizeSubtitleOAuth": "Θα θέλατε να συνεχίσετε σε αυτή την εφαρμογή;",
|
|
||||||
"authorizeLoadingTitle": "Φόρτωση...",
|
|
||||||
"authorizeLoadingSubtitle": "Παρακαλώ περιμένετε όσο φορτώνουμε τις απαραίτητες πληροφορίες.",
|
|
||||||
"authorizeSuccessTitle": "Εξουσιοδοτημένος",
|
|
||||||
"authorizeSuccessSubtitle": "Θα μεταφερθείτε στην εφαρμογή σε λίγα δευτερόλεπτα.",
|
|
||||||
"authorizeErrorClientInfo": "Παρουσιάστηκε σφάλμα κατά τη φόρτωση των πληροφοριών. Παρακαλώ προσπαθήστε ξανά αργότερα.",
|
|
||||||
"authorizeErrorMissingParams": "Οι παρακάτω απαραίτητες πληροφορίες λείπουν από το αίτημά σας: {{missingParams}}",
|
|
||||||
"openidScopeName": "Σύνδεση OpenID",
|
|
||||||
"openidScopeDescription": "Επιτρέπει στην εφαρμογή την πρόσβαση στις πληροφορίες σύνδεσης OpenID.",
|
|
||||||
"emailScopeName": "Διεύθυνση ηλεκτρονικού ταχυδρομείου",
|
|
||||||
"emailScopeDescription": "Επιτρέπει στην εφαρμογή να έχει πρόσβαση στη διεύθυνση ηλεκτρονικού ταχυδρομείου σας.",
|
|
||||||
"profileScopeName": "Προφίλ",
|
|
||||||
"profileScopeDescription": "Επιτρέπει στην εφαρμογή να έχει πρόσβαση στις πληροφορίες του προφίλ σας.",
|
|
||||||
"groupsScopeName": "Ομάδες",
|
|
||||||
"groupsScopeDescription": "Επιτρέπει στην εφαρμογή την πρόσβαση στις πληροφορίες ομάδας σας."
|
|
||||||
}
|
|
||||||
@@ -51,31 +51,12 @@
|
|||||||
"forgotPasswordTitle": "Forgot your password?",
|
"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:",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check your browser console or the app logs for more information.",
|
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
"invalidInput": "Invalid input",
|
"invalidInput": "Invalid input",
|
||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -14,17 +14,14 @@
|
|||||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||||
"loginOauthSuccessTitle": "Redirecting",
|
"loginOauthSuccessTitle": "Redirecting",
|
||||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||||
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
|
||||||
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
|
||||||
"loginOauthAutoRedirectButton": "Redirect now",
|
|
||||||
"continueTitle": "Continue",
|
|
||||||
"continueRedirectingTitle": "Redirecting...",
|
"continueRedirectingTitle": "Redirecting...",
|
||||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||||
"continueRedirectManually": "Redirect me manually",
|
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||||
|
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
"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",
|
"continueTitle": "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?",
|
"continueSubtitle": "Click the button to continue to your app.",
|
||||||
"logoutFailTitle": "Failed to log out",
|
"logoutFailTitle": "Failed to log out",
|
||||||
"logoutFailSubtitle": "Please try again",
|
"logoutFailSubtitle": "Please try again",
|
||||||
"logoutSuccessTitle": "Logged out",
|
"logoutSuccessTitle": "Logged out",
|
||||||
@@ -47,35 +44,14 @@
|
|||||||
"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",
|
||||||
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{domain}}</code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel",
|
"cancelTitle": "Cancel",
|
||||||
"forgotPasswordTitle": "Forgot your password?",
|
"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:",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check your browser console or the app logs for more information.",
|
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
"invalidInput": "Invalid input",
|
"invalidInput": "Invalid input"
|
||||||
"domainWarningTitle": "Invalid Domain",
|
}
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
|
||||||
"ignoreTitle": "Ignore",
|
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
|
||||||
"authorizeTitle": "Authorize",
|
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"forgotPasswordTitle": "¿Olvidó su contraseña?",
|
"forgotPasswordTitle": "¿Olvidó su contraseña?",
|
||||||
"failedToFetchProvidersTitle": "Error al cargar los proveedores de autenticación. Por favor revise su configuración.",
|
"failedToFetchProvidersTitle": "Error al cargar los proveedores de autenticación. Por favor revise su configuración.",
|
||||||
"errorTitle": "Ha ocurrido un error",
|
"errorTitle": "Ha ocurrido un error",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "Ocurrió un error mientras se trataba de realizar esta acción. Por favor, revise la consola para más información.",
|
"errorSubtitle": "Ocurrió un error mientras se trataba de realizar esta acción. Por favor, revise la consola para más información.",
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
@@ -59,23 +58,5 @@
|
|||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -1,81 +1,62 @@
|
|||||||
{
|
{
|
||||||
"loginTitle": "Tervetuloa takaisin, kirjaudu sisään käyttäen",
|
"loginTitle": "Welcome back, login with",
|
||||||
"loginTitleSimple": "Tervetuloa takaisin, ole hyvä ja kirjaudu",
|
"loginTitleSimple": "Welcome back, please login",
|
||||||
"loginDivider": "Tai",
|
"loginDivider": "Or",
|
||||||
"loginUsername": "Käyttäjätunnus",
|
"loginUsername": "Username",
|
||||||
"loginPassword": "Salasana",
|
"loginPassword": "Password",
|
||||||
"loginSubmit": "Kirjaudu",
|
"loginSubmit": "Login",
|
||||||
"loginFailTitle": "Kirjautuminen epäonnistui",
|
"loginFailTitle": "Failed to log in",
|
||||||
"loginFailSubtitle": "Tarkista käyttäjätunnuksesi ja salasanasi",
|
"loginFailSubtitle": "Please check your username and password",
|
||||||
"loginFailRateLimit": "Kirjautuminen epäonnistui liian monta kertaa. Yritä myöhemmin uudelleen",
|
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||||
"loginSuccessTitle": "Olet kirjautunut sisään",
|
"loginSuccessTitle": "Logged in",
|
||||||
"loginSuccessSubtitle": "Tervetuloa takaisin!",
|
"loginSuccessSubtitle": "Welcome back!",
|
||||||
"loginOauthFailTitle": "Tapahtui virhe",
|
"loginOauthFailTitle": "An error occurred",
|
||||||
"loginOauthFailSubtitle": "OAuthin URL-osoitteen haku epäonnistui",
|
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||||
"loginOauthSuccessTitle": "Uudelleenohjataan",
|
"loginOauthSuccessTitle": "Redirecting",
|
||||||
"loginOauthSuccessSubtitle": "Uudelleenohjaus OAuth -palveluntarjoajallesi",
|
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||||
"loginOauthAutoRedirectTitle": "Automaattinen OAuth -uudelleenohjaus",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "Sinut ohjataan automaattisesti OAuth -palveluntarjoajallesi todentamista varten.",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "Siirry nyt",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Jatka",
|
"continueTitle": "Continue",
|
||||||
"continueRedirectingTitle": "Uudelleenohjataan...",
|
"continueRedirectingTitle": "Redirecting...",
|
||||||
"continueRedirectingSubtitle": "Sinun pitäisi ohjautua sovellukseen pian",
|
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||||
"continueRedirectManually": "Siirrä minut manuaalisesti",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "Turvaton uudelleenohjaus",
|
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||||
"continueInsecureRedirectSubtitle": "Yrität siirtyä suojatusta <code>https</code> -sivusta suojaamattomalle <code>http</code> -sivulle. Oletko varma, että haluat jatkaa?",
|
"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": "Ei-luotettu uudelleenohjaus",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "Yrität uudelleenohjata domainiin, joka ei vastaa määritettyä verkkotunnusta (<code>{{cookieDomain}}</code>). Oletko varma, että haluat jatkaa?",
|
"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": "Uloskirjautuminen epäonnistui",
|
"logoutFailTitle": "Failed to log out",
|
||||||
"logoutFailSubtitle": "Ole hyvä ja yritä uudelleen",
|
"logoutFailSubtitle": "Please try again",
|
||||||
"logoutSuccessTitle": "Kirjauduttu ulos",
|
"logoutSuccessTitle": "Logged out",
|
||||||
"logoutSuccessSubtitle": "Sinut on kirjattu ulos",
|
"logoutSuccessSubtitle": "You have been logged out",
|
||||||
"logoutTitle": "Kirjaudu ulos",
|
"logoutTitle": "Logout",
|
||||||
"logoutUsernameSubtitle": "Olet kirjautuneena sisään tunnuksella <code>{{username}}</code>. Kirjaudu ulos alla olevasta painikkeesta.",
|
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
|
||||||
"logoutOauthSubtitle": "Olet kirjautuneena sisään tunnuksella <code>{{username}}</code> OAuth palvelun {{provider}} kautta. Kirjaudu ulos alla olevasta painikkeesta.",
|
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
|
||||||
"notFoundTitle": "Sivua ei löydy",
|
"notFoundTitle": "Page not found",
|
||||||
"notFoundSubtitle": "Sivua, jota etsit ei ole olemassa.",
|
"notFoundSubtitle": "The page you are looking for does not exist.",
|
||||||
"notFoundButton": "Palaa kotinäkymään",
|
"notFoundButton": "Go home",
|
||||||
"totpFailTitle": "Koodin vahvistus epäonnistui",
|
"totpFailTitle": "Failed to verify code",
|
||||||
"totpFailSubtitle": "Tarkista koodisi ja yritä uudelleen",
|
"totpFailSubtitle": "Please check your code and try again",
|
||||||
"totpSuccessTitle": "Vahvistettu",
|
"totpSuccessTitle": "Verified",
|
||||||
"totpSuccessSubtitle": "Uudelleenohjataan sovelluksellesi",
|
"totpSuccessSubtitle": "Redirecting to your app",
|
||||||
"totpTitle": "Syötä TOTP -koodisi",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"totpSubtitle": "Ole hyvä ja syötä koodi todennussovelluksestasi.",
|
"totpSubtitle": "Please enter the code from your authenticator app.",
|
||||||
"unauthorizedTitle": "Ei sallittu",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "Käyttäjällä <code>{{username}}</code> ei ole pääsyä kohteeseen <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "Käyttäjällä <code>{{username}}</code> ei ole lupaa kirjautua.",
|
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
||||||
"unauthorizedGroupsSubtitle": "Käyttäjä <code>{{username}}</code> ei ole ryhmässä, joka vaaditaan pääsyyn kohteeseen <code>{{resource}}</code>.",
|
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedIpSubtitle": "IP osoitteestasi <code>{{ip}}</code> ei ole pääsyä kohteeseen <code>{{resource}}</code>.",
|
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedButton": "Yritä uudelleen",
|
"unauthorizedButton": "Try again",
|
||||||
"cancelTitle": "Peruuta",
|
"cancelTitle": "Cancel",
|
||||||
"forgotPasswordTitle": "Unohditko salasanasi?",
|
"forgotPasswordTitle": "Forgot your password?",
|
||||||
"failedToFetchProvidersTitle": "Todennuspalvelujen tarjoajien lataaminen epäonnistui. Tarkista määrityksesi.",
|
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||||
"errorTitle": "Tapahtui virhe",
|
"errorTitle": "An error occurred",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"errorSubtitle": "Tapahtui virhe yritettäessä suorittaa tämä toiminto. Ole hyvä ja tarkista konsoli saadaksesi lisätietoja.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"forgotPasswordMessage": "Voit nollata salasanasi vaihtamalla ympäristömuuttujan `USERS`.",
|
"fieldRequired": "This field is required",
|
||||||
"fieldRequired": "Tämä kenttä on pakollinen",
|
"invalidInput": "Invalid input",
|
||||||
"invalidInput": "Virheellinen syöte",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningTitle": "Virheellinen verkkotunnus",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"domainWarningSubtitle": "Tämä instanssi on määritelty käyttämään osoitetta <code>{{appUrl}}</code>, mutta nykyinen osoite on <code>{{currentUrl}}</code>. Jos jatkat, saatat törmätä ongelmiin autentikoinnissa.",
|
"ignoreTitle": "Ignore",
|
||||||
"ignoreTitle": "Jätä huomiotta",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"goToCorrectDomainTitle": "Siirry oikeaan verkkotunnukseen",
|
}
|
||||||
"authorizeTitle": "Authorize",
|
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -14,17 +14,17 @@
|
|||||||
"loginOauthFailSubtitle": "Impossible d'obtenir l'URL OAuth",
|
"loginOauthFailSubtitle": "Impossible d'obtenir l'URL OAuth",
|
||||||
"loginOauthSuccessTitle": "Redirection",
|
"loginOauthSuccessTitle": "Redirection",
|
||||||
"loginOauthSuccessSubtitle": "Redirection vers votre fournisseur OAuth",
|
"loginOauthSuccessSubtitle": "Redirection vers votre fournisseur OAuth",
|
||||||
"loginOauthAutoRedirectTitle": "Redirection automatique OAuth",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "Vous allez être automatiquement redirigé vers votre fournisseur OAuth pour vous authentifier.",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "Rediriger",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Continuer",
|
"continueTitle": "Continuer",
|
||||||
"continueRedirectingTitle": "Redirection...",
|
"continueRedirectingTitle": "Redirection...",
|
||||||
"continueRedirectingSubtitle": "Vous devriez être redirigé vers l'application bientôt",
|
"continueRedirectingSubtitle": "Vous devriez être redirigé vers l'application bientôt",
|
||||||
"continueRedirectManually": "Redirection manuelle",
|
"continueRedirectManually": "Redirection manuelle",
|
||||||
"continueInsecureRedirectTitle": "Redirection non sécurisée",
|
"continueInsecureRedirectTitle": "Redirection non sécurisée",
|
||||||
"continueInsecureRedirectSubtitle": "Vous tentez de rediriger de <code>https</code> vers <code>http</code>, ce qui n'est pas sécurisé. Êtes-vous sûr de vouloir continuer ?",
|
"continueInsecureRedirectSubtitle": "Vous tentez de rediriger de <code>https</code> vers <code>http</code>, ce qui n'est pas sécurisé. Êtes-vous sûr de vouloir continuer ?",
|
||||||
"continueUntrustedRedirectTitle": "Redirection non sécurisée",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "Vous essayez de rediriger vers un domaine qui ne correspond pas à votre domaine configuré (<code>{{cookieDomain}}</code>). Êtes-vous sûr de vouloir continuer ?",
|
"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": "Échec de la déconnexion",
|
"logoutFailTitle": "Échec de la déconnexion",
|
||||||
"logoutFailSubtitle": "Veuillez réessayer",
|
"logoutFailSubtitle": "Veuillez réessayer",
|
||||||
"logoutSuccessTitle": "Déconnecté",
|
"logoutSuccessTitle": "Déconnecté",
|
||||||
@@ -51,31 +51,12 @@
|
|||||||
"forgotPasswordTitle": "Mot de passe oublié ?",
|
"forgotPasswordTitle": "Mot de passe oublié ?",
|
||||||
"failedToFetchProvidersTitle": "Échec du chargement des fournisseurs d'authentification. Veuillez vérifier votre configuration.",
|
"failedToFetchProvidersTitle": "Échec du chargement des fournisseurs d'authentification. Veuillez vérifier votre configuration.",
|
||||||
"errorTitle": "Une erreur est survenue",
|
"errorTitle": "Une erreur est survenue",
|
||||||
"errorSubtitleInfo": "L'erreur suivante s'est produite lors du traitement de votre requête :",
|
|
||||||
"errorSubtitle": "Une erreur est survenue lors de l'exécution de cette action. Veuillez consulter la console pour plus d'informations.",
|
"errorSubtitle": "Une erreur est survenue lors de l'exécution de cette action. Veuillez consulter la console pour plus d'informations.",
|
||||||
"forgotPasswordMessage": "Vous pouvez réinitialiser votre mot de passe en modifiant la variable d'environnement `USERS`.",
|
"forgotPasswordMessage": "Vous pouvez réinitialiser votre mot de passe en modifiant la variable d'environnement `USERS`.",
|
||||||
"fieldRequired": "Ce champ est obligatoire",
|
"fieldRequired": "Ce champ est obligatoire",
|
||||||
"invalidInput": "Saisie non valide",
|
"invalidInput": "Saisie non valide",
|
||||||
"domainWarningTitle": "Domaine invalide",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "Cette instance est configurée pour être accédée depuis <code>{{appUrl}}</code>, mais <code>{{currentUrl}}</code> est utilisé. Si vous continuez, vous pourriez rencontrer des problèmes d'authentification.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignorer",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Aller au bon domaine",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Autoriser",
|
}
|
||||||
"authorizeCardTitle": "Continuer vers {{app}} ?",
|
|
||||||
"authorizeSubtitle": "Voulez-vous continuer vers cette application ? Veuillez examiner attentivement les autorisations demandées par l'application.",
|
|
||||||
"authorizeSubtitleOAuth": "Voulez-vous continuer vers cette application ?",
|
|
||||||
"authorizeLoadingTitle": "Chargement...",
|
|
||||||
"authorizeLoadingSubtitle": "Veuillez patienter pendant que nous chargeons les informations du client.",
|
|
||||||
"authorizeSuccessTitle": "Autorisé",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "Les paramètres suivants sont manquants : {{missingParams}}",
|
|
||||||
"openidScopeName": "Connexion OpenID",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Autorise l'application à accéder à votre adresse e-mail.",
|
|
||||||
"profileScopeName": "Profil",
|
|
||||||
"profileScopeDescription": "Autorise l'application à accéder aux informations de votre profil.",
|
|
||||||
"groupsScopeName": "Groupes",
|
|
||||||
"groupsScopeDescription": "Autorise une application à accéder aux informations de votre groupe."
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"forgotPasswordTitle": "Forgot your password?",
|
"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:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
@@ -59,23 +58,5 @@
|
|||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -1,42 +1,42 @@
|
|||||||
{
|
{
|
||||||
"loginTitle": "Welcome back, login with",
|
"loginTitle": "Welcome back, login with",
|
||||||
"loginTitleSimple": "Üdvözöljük, kérem jelentkezzen be",
|
"loginTitleSimple": "Welcome back, please login",
|
||||||
"loginDivider": "Vagy",
|
"loginDivider": "Or",
|
||||||
"loginUsername": "Felhasználónév",
|
"loginUsername": "Username",
|
||||||
"loginPassword": "Jelszó",
|
"loginPassword": "Password",
|
||||||
"loginSubmit": "Bejelentkezés",
|
"loginSubmit": "Login",
|
||||||
"loginFailTitle": "Sikertelen bejelentkezés",
|
"loginFailTitle": "Failed to log in",
|
||||||
"loginFailSubtitle": "Kérjük, ellenőrizze a felhasználónevét és jelszavát",
|
"loginFailSubtitle": "Please check your username and password",
|
||||||
"loginFailRateLimit": "Túl sokszor próbálkoztál bejelentkezni. Próbáld újra később",
|
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||||
"loginSuccessTitle": "Bejelentkezve",
|
"loginSuccessTitle": "Logged in",
|
||||||
"loginSuccessSubtitle": "Üdvözöljük!",
|
"loginSuccessSubtitle": "Welcome back!",
|
||||||
"loginOauthFailTitle": "An error occurred",
|
"loginOauthFailTitle": "An error occurred",
|
||||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||||
"loginOauthSuccessTitle": "Átirányítás",
|
"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": "Redirect now",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Continue",
|
"continueTitle": "Continue",
|
||||||
"continueRedirectingTitle": "Átirányítás...",
|
"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": "Insecure redirect",
|
"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": "Sikertelen kijelentkezés",
|
"logoutFailTitle": "Failed to log out",
|
||||||
"logoutFailSubtitle": "Próbálja újra",
|
"logoutFailSubtitle": "Please try again",
|
||||||
"logoutSuccessTitle": "Kijelentkezve",
|
"logoutSuccessTitle": "Logged out",
|
||||||
"logoutSuccessSubtitle": "Kijelentkeztél",
|
"logoutSuccessSubtitle": "You have been logged out",
|
||||||
"logoutTitle": "Kijelentkezés",
|
"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": "Page not found",
|
"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": "Ugrás a kezdőlapra",
|
"notFoundButton": "Go home",
|
||||||
"totpFailTitle": "Érvénytelen kód",
|
"totpFailTitle": "Failed to verify code",
|
||||||
"totpFailSubtitle": "Kérjük ellenőrizze a kódot és próbálja újra",
|
"totpFailSubtitle": "Please check your code and try again",
|
||||||
"totpSuccessTitle": "Verified",
|
"totpSuccessTitle": "Verified",
|
||||||
"totpSuccessSubtitle": "Redirecting to your app",
|
"totpSuccessSubtitle": "Redirecting to your app",
|
||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
@@ -46,36 +46,17 @@
|
|||||||
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
||||||
"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": "Próbálja újra",
|
"unauthorizedButton": "Try again",
|
||||||
"cancelTitle": "Mégse",
|
"cancelTitle": "Cancel",
|
||||||
"forgotPasswordTitle": "Elfelejtette jelszavát?",
|
"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": "Hiba történt",
|
"errorTitle": "An error occurred",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "Ez egy kötelező mező",
|
"fieldRequired": "This field is required",
|
||||||
"invalidInput": "Invalid input",
|
"invalidInput": "Invalid input",
|
||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
{
|
{
|
||||||
"loginTitle": "Bentornato, accedi con",
|
"loginTitle": "Welcome back, login with",
|
||||||
"loginTitleSimple": "Bentornato, accedi al tuo account",
|
"loginTitleSimple": "Welcome back, please login",
|
||||||
"loginDivider": "Oppure",
|
"loginDivider": "Or",
|
||||||
"loginUsername": "Nome utente",
|
"loginUsername": "Username",
|
||||||
"loginPassword": "Password",
|
"loginPassword": "Password",
|
||||||
"loginSubmit": "Accesso",
|
"loginSubmit": "Login",
|
||||||
"loginFailTitle": "Accesso non riuscito",
|
"loginFailTitle": "Failed to log in",
|
||||||
"loginFailSubtitle": "Verifica che il nome utente e la password siano corretti",
|
"loginFailSubtitle": "Please check your username and password",
|
||||||
"loginFailRateLimit": "Hai effettuato troppi tentativi errati. Riprova più tardi",
|
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||||
"loginSuccessTitle": "Accesso effettuato",
|
"loginSuccessTitle": "Logged in",
|
||||||
"loginSuccessSubtitle": "Bentornato!",
|
"loginSuccessSubtitle": "Welcome back!",
|
||||||
"loginOauthFailTitle": "Si è verificato un errore",
|
"loginOauthFailTitle": "An error occurred",
|
||||||
"loginOauthFailSubtitle": "Impossibile ottenere l'URL di OAuth",
|
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||||
"loginOauthSuccessTitle": "Redirecting",
|
"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": "Redirect now",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Prosegui",
|
"continueTitle": "Continue",
|
||||||
"continueRedirectingTitle": "Redirecting...",
|
"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",
|
||||||
@@ -34,48 +34,29 @@
|
|||||||
"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": "Page not found",
|
"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": "Vai alla home",
|
"notFoundButton": "Go home",
|
||||||
"totpFailTitle": "Errore nella verifica del codice",
|
"totpFailTitle": "Failed to verify code",
|
||||||
"totpFailSubtitle": "Si prega di controllare il codice e riprovare",
|
"totpFailSubtitle": "Please check your code and try again",
|
||||||
"totpSuccessTitle": "Verificato",
|
"totpSuccessTitle": "Verified",
|
||||||
"totpSuccessSubtitle": "Reindirizzamento alla tua app",
|
"totpSuccessSubtitle": "Redirecting to your app",
|
||||||
"totpTitle": "Inserisci il tuo codice TOTP",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"totpSubtitle": "Inserisci il codice dalla tua app di autenticazione.",
|
"totpSubtitle": "Please enter the code from your authenticator app.",
|
||||||
"unauthorizedTitle": "Non Autorizzato",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "L'utente con username <code>{{username}}</code> non è autorizzato ad accedere alla risorsa <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "L'utente con username <code>{{username}}</code> non è autorizzato a effettuare l'accesso.",
|
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
||||||
"unauthorizedGroupsSubtitle": "L'utente con nome utente <code>{{username}}</code> non fa parte dei gruppi richiesti dalla risorsa <code>{{resource}}</code>.",
|
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedIpSubtitle": "Il tuo indirizzo IP <code>{{ip}}</code> non è autorizzato ad accedere alla risorsa <code>{{resource}}</code>.",
|
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedButton": "Riprova",
|
"unauthorizedButton": "Try again",
|
||||||
"cancelTitle": "Annulla",
|
"cancelTitle": "Cancel",
|
||||||
"forgotPasswordTitle": "Password dimenticata?",
|
"forgotPasswordTitle": "Forgot your password?",
|
||||||
"failedToFetchProvidersTitle": "Impossibile caricare i provider di autenticazione. Si prega di controllare la configurazione.",
|
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||||
"errorTitle": "Si è verificato un errore",
|
"errorTitle": "An error occurred",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "Puoi reimpostare la tua password modificando la variabile d'ambiente `USERS`.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "Questo campo è obbligatorio",
|
"fieldRequired": "This field is required",
|
||||||
"invalidInput": "Input non valido",
|
"invalidInput": "Invalid input",
|
||||||
"domainWarningTitle": "Dominio non valido",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "Questa istanza è configurata per essere accessibile da <code>{{appUrl}}</code>, ma <code>{{currentUrl}}</code> è in uso. Se procedi, potresti incorrere in problemi di autenticazione.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignora",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Vai al dominio corretto",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"forgotPasswordTitle": "Forgot your password?",
|
"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:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
@@ -59,23 +58,5 @@
|
|||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"forgotPasswordTitle": "Forgot your password?",
|
"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:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
@@ -59,23 +58,5 @@
|
|||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -1,37 +1,37 @@
|
|||||||
{
|
{
|
||||||
"loginTitle": "Welkom terug, log in met",
|
"loginTitle": "Welkom terug, log in met",
|
||||||
"loginTitleSimple": "Welkom terug, log in",
|
"loginTitleSimple": "Welcome back, please login",
|
||||||
"loginDivider": "Of",
|
"loginDivider": "Or",
|
||||||
"loginUsername": "Gebruikersnaam",
|
"loginUsername": "Gebruikersnaam",
|
||||||
"loginPassword": "Wachtwoord",
|
"loginPassword": "Wachtwoord",
|
||||||
"loginSubmit": "Log in",
|
"loginSubmit": "Log in",
|
||||||
"loginFailTitle": "Mislukt om in te loggen",
|
"loginFailTitle": "Mislukt om in te loggen",
|
||||||
"loginFailSubtitle": "Controleer je gebruikersnaam en wachtwoord",
|
"loginFailSubtitle": "Controleer je gebruikersnaam en wachtwoord",
|
||||||
"loginFailRateLimit": "Inloggen is te vaak mislukt. Probeer het later opnieuw",
|
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||||
"loginSuccessTitle": "Ingelogd",
|
"loginSuccessTitle": "Ingelogd",
|
||||||
"loginSuccessSubtitle": "Welkom terug!",
|
"loginSuccessSubtitle": "Welkom terug!",
|
||||||
"loginOauthFailTitle": "Er is een fout opgetreden",
|
"loginOauthFailTitle": "An error occurred",
|
||||||
"loginOauthFailSubtitle": "Fout bij het ophalen van OAuth URL",
|
"loginOauthFailSubtitle": "Fout bij het ophalen van OAuth URL",
|
||||||
"loginOauthSuccessTitle": "Omleiden",
|
"loginOauthSuccessTitle": "Omleiden",
|
||||||
"loginOauthSuccessSubtitle": "Omleiden naar je OAuth provider",
|
"loginOauthSuccessSubtitle": "Omleiden naar je OAuth provider",
|
||||||
"loginOauthAutoRedirectTitle": "OAuth automatische omleiding",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "Je wordt automatisch omgeleid naar je OAuth provider om te authenticeren.",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "Nu omleiden",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Ga verder",
|
"continueTitle": "Ga verder",
|
||||||
"continueRedirectingTitle": "Omleiden...",
|
"continueRedirectingTitle": "Omleiden...",
|
||||||
"continueRedirectingSubtitle": "Je wordt naar de app doorgestuurd",
|
"continueRedirectingSubtitle": "Je wordt naar de app doorgestuurd",
|
||||||
"continueRedirectManually": "Stuur mij handmatig door",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "Onveilige doorverwijzing",
|
"continueInsecureRedirectTitle": "Onveilige doorverwijzing",
|
||||||
"continueInsecureRedirectSubtitle": "Je probeert door te verwijzen van <code>https</code> naar <code>http</code> die niet veilig is. Weet je zeker dat je wilt doorgaan?",
|
"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": "Niet-vertrouwde doorverwijzing",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "Je probeert door te sturen naar een domein dat niet overeenkomt met je geconfigureerde domein (<code>{{cookieDomain}}</code>). Weet je zeker dat je wilt doorgaan?",
|
"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": "Afmelden mislukt",
|
"logoutFailTitle": "Afmelden mislukt",
|
||||||
"logoutFailSubtitle": "Probeer het opnieuw",
|
"logoutFailSubtitle": "Probeer het opnieuw",
|
||||||
"logoutSuccessTitle": "Afgemeld",
|
"logoutSuccessTitle": "Afgemeld",
|
||||||
"logoutSuccessSubtitle": "Je bent afgemeld",
|
"logoutSuccessSubtitle": "Je bent afgemeld",
|
||||||
"logoutTitle": "Afmelden",
|
"logoutTitle": "Afmelden",
|
||||||
"logoutUsernameSubtitle": "Je bent momenteel ingelogd als <code>{{username}}</code>. Klik op de onderstaande knop om uit te loggen.",
|
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
|
||||||
"logoutOauthSubtitle": "Je bent momenteel ingelogd als <code>{{username}}</code> met behulp van de {{provider}} OAuth provider. Klik op de onderstaande knop om uit te loggen.",
|
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
|
||||||
"notFoundTitle": "Pagina niet gevonden",
|
"notFoundTitle": "Pagina niet gevonden",
|
||||||
"notFoundSubtitle": "De pagina die je zoekt bestaat niet.",
|
"notFoundSubtitle": "De pagina die je zoekt bestaat niet.",
|
||||||
"notFoundButton": "Naar startpagina",
|
"notFoundButton": "Naar startpagina",
|
||||||
@@ -40,42 +40,23 @@
|
|||||||
"totpSuccessTitle": "Geverifiëerd",
|
"totpSuccessTitle": "Geverifiëerd",
|
||||||
"totpSuccessSubtitle": "Omleiden naar je app",
|
"totpSuccessSubtitle": "Omleiden naar je app",
|
||||||
"totpTitle": "Voer je TOTP-code in",
|
"totpTitle": "Voer je TOTP-code in",
|
||||||
"totpSubtitle": "Voer de code van je authenticator-app in.",
|
"totpSubtitle": "Please enter the code from your authenticator app.",
|
||||||
"unauthorizedTitle": "Ongeautoriseerd",
|
"unauthorizedTitle": "Ongeautoriseerd",
|
||||||
"unauthorizedResourceSubtitle": "De gebruiker met gebruikersnaam <code>{{username}}</code> is niet gemachtigd om de bron <code>{{resource}}</code> te gebruiken.",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "De gebruiker met gebruikersnaam <code>{{username}}</code> is niet gemachtigd om in te loggen.",
|
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
||||||
"unauthorizedGroupsSubtitle": "De gebruiker met gebruikersnaam <code>{{username}}</code> maakt geen deel uit van de groepen die vereist zijn door de bron <code>{{resource}}</code>.",
|
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedIpSubtitle": "Jouw IP-adres <code>{{ip}}</code> is niet gemachtigd om de bron <code>{{resource}}</code> te gebruiken.",
|
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedButton": "Opnieuw proberen",
|
"unauthorizedButton": "Opnieuw proberen",
|
||||||
"cancelTitle": "Annuleren",
|
"cancelTitle": "Cancel",
|
||||||
"forgotPasswordTitle": "Wachtwoord vergeten?",
|
"forgotPasswordTitle": "Forgot your password?",
|
||||||
"failedToFetchProvidersTitle": "Fout bij het laden van de authenticatie-providers. Controleer je configuratie.",
|
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||||
"errorTitle": "Er is een fout opgetreden",
|
"errorTitle": "An error occurred",
|
||||||
"errorSubtitleInfo": "De volgende fout is opgetreden bij het verwerken van het verzoek:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "Je kunt je wachtwoord opnieuw instellen door de `USERS` omgevingsvariabele te wijzigen.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "Dit veld is verplicht",
|
"fieldRequired": "This field is required",
|
||||||
"invalidInput": "Ongeldige invoer",
|
"invalidInput": "Invalid input",
|
||||||
"domainWarningTitle": "Ongeldig domein",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "Deze instantie is geconfigureerd voor toegang tot <code>{{appUrl}}</code>, maar <code>{{currentUrl}}</code> wordt gebruikt. Als je doorgaat, kun je problemen ondervinden met authenticatie.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Negeren",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Ga naar het juiste domein",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Autoriseren",
|
}
|
||||||
"authorizeCardTitle": "Doorgaan naar {{app}}?",
|
|
||||||
"authorizeSubtitle": "Doorgaan naar deze app? Controleer de machtigingen die door de app worden gevraagd.",
|
|
||||||
"authorizeSubtitleOAuth": "Doorgaan naar deze app?",
|
|
||||||
"authorizeLoadingTitle": "Laden...",
|
|
||||||
"authorizeLoadingSubtitle": "Even geduld bij het laden van de cliëntinformatie.",
|
|
||||||
"authorizeSuccessTitle": "Geautoriseerd",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "De volgende parameters ontbreken: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Hiermee kan de app toegang krijgen tot jouw OpenID Connect-informatie.",
|
|
||||||
"emailScopeName": "E-mail",
|
|
||||||
"emailScopeDescription": "Hiermee kan de app toegang krijgen tot jouw e-mailadres.",
|
|
||||||
"profileScopeName": "Profiel",
|
|
||||||
"profileScopeDescription": "Hiermee kan de app toegang krijgen tot je profielinformatie.",
|
|
||||||
"groupsScopeName": "Groepen",
|
|
||||||
"groupsScopeDescription": "Hiermee kan de app toegang krijgen tot jouw groepsinformatie."
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"forgotPasswordTitle": "Forgot your password?",
|
"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:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
@@ -59,23 +58,5 @@
|
|||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -14,17 +14,17 @@
|
|||||||
"loginOauthFailSubtitle": "Nie udało się uzyskać adresu URL OAuth",
|
"loginOauthFailSubtitle": "Nie udało się uzyskać adresu URL OAuth",
|
||||||
"loginOauthSuccessTitle": "Przekierowywanie",
|
"loginOauthSuccessTitle": "Przekierowywanie",
|
||||||
"loginOauthSuccessSubtitle": "Przekierowywanie do Twojego dostawcy OAuth",
|
"loginOauthSuccessSubtitle": "Przekierowywanie do Twojego dostawcy OAuth",
|
||||||
"loginOauthAutoRedirectTitle": "Automatyczne przekierowanie OAuth",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "Nastąpi automatyczne przekierowanie do dostawcy OAuth w celu uwierzytelnienia.",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "Przekieruj teraz",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Kontynuuj",
|
"continueTitle": "Kontynuuj",
|
||||||
"continueRedirectingTitle": "Przekierowywanie...",
|
"continueRedirectingTitle": "Przekierowywanie...",
|
||||||
"continueRedirectingSubtitle": "Wkrótce powinieneś zostać przekierowany do aplikacji",
|
"continueRedirectingSubtitle": "Wkrótce powinieneś zostać przekierowany do aplikacji",
|
||||||
"continueRedirectManually": "Przekieruj mnie ręcznie",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "Niezabezpieczone przekierowanie",
|
"continueInsecureRedirectTitle": "Niezabezpieczone przekierowanie",
|
||||||
"continueInsecureRedirectSubtitle": "Próbujesz przekierować z <code>https</code> do <code>http</code>, co nie jest bezpieczne. Czy na pewno chcesz kontynuować?",
|
"continueInsecureRedirectSubtitle": "Próbujesz przekierować z <code>https</code> do <code>http</code>, co nie jest bezpieczne. Czy na pewno chcesz kontynuować?",
|
||||||
"continueUntrustedRedirectTitle": "Niezaufane przekierowanie",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "Próbujesz przekierować do domeny, która nie pasuje do skonfigurowanej domeny (<code>{{cookieDomain}}</code>). Czy na pewno chcesz kontynuować?",
|
"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": "Nie udało się wylogować",
|
"logoutFailTitle": "Nie udało się wylogować",
|
||||||
"logoutFailSubtitle": "Spróbuj ponownie",
|
"logoutFailSubtitle": "Spróbuj ponownie",
|
||||||
"logoutSuccessTitle": "Wylogowano",
|
"logoutSuccessTitle": "Wylogowano",
|
||||||
@@ -51,31 +51,12 @@
|
|||||||
"forgotPasswordTitle": "Nie pamiętasz hasła?",
|
"forgotPasswordTitle": "Nie pamiętasz hasła?",
|
||||||
"failedToFetchProvidersTitle": "Nie udało się załadować dostawców uwierzytelniania. Sprawdź swoją konfigurację.",
|
"failedToFetchProvidersTitle": "Nie udało się załadować dostawców uwierzytelniania. Sprawdź swoją konfigurację.",
|
||||||
"errorTitle": "Wystąpił błąd",
|
"errorTitle": "Wystąpił błąd",
|
||||||
"errorSubtitleInfo": "Podczas przetwarzania żądania wystąpił następujący błąd:",
|
|
||||||
"errorSubtitle": "Wystąpił błąd podczas próby wykonania tej czynności. Sprawdź konsolę, aby uzyskać więcej informacji.",
|
"errorSubtitle": "Wystąpił błąd podczas próby wykonania tej czynności. Sprawdź konsolę, aby uzyskać więcej informacji.",
|
||||||
"forgotPasswordMessage": "Możesz zresetować hasło, zmieniając zmienną środowiskową `USERS`.",
|
"forgotPasswordMessage": "Możesz zresetować hasło, zmieniając zmienną środowiskową `USERS`.",
|
||||||
"fieldRequired": "To pole jest wymagane",
|
"fieldRequired": "To pole jest wymagane",
|
||||||
"invalidInput": "Nieprawidłowe dane wejściowe",
|
"invalidInput": "Nieprawidłowe dane wejściowe",
|
||||||
"domainWarningTitle": "Nieprawidłowa domena",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "Ta instancja jest skonfigurowana do uzyskania dostępu z <code>{{appUrl}}</code>, ale <code>{{currentUrl}}</code> jest w użyciu. Jeśli będziesz kontynuować, mogą wystąpić problemy z uwierzytelnianiem.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Zignoruj",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Przejdź do prawidłowej domeny",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Autoryzuj",
|
}
|
||||||
"authorizeCardTitle": "Kontynuować do {{app}}?",
|
|
||||||
"authorizeSubtitle": "Czy chcesz kontynuować do tej aplikacji? Uważnie zapoznaj się z uprawnieniami żądanymi przez aplikację.",
|
|
||||||
"authorizeSubtitleOAuth": "Czy chcesz kontynuować do tej aplikacji?",
|
|
||||||
"authorizeLoadingTitle": "Wczytywanie...",
|
|
||||||
"authorizeLoadingSubtitle": "Proszę czekać, aż załadujemy informacje o kliencie.",
|
|
||||||
"authorizeSuccessTitle": "Autoryzowano",
|
|
||||||
"authorizeSuccessSubtitle": "Za kilka sekund nastąpi przekierowanie do aplikacji.",
|
|
||||||
"authorizeErrorClientInfo": "Wystąpił błąd podczas ładowania informacji o kliencie. Spróbuj ponownie później.",
|
|
||||||
"authorizeErrorMissingParams": "Brakuje następujących parametrów: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Zezwala aplikacji na dostęp do informacji o OpenID Connect.",
|
|
||||||
"emailScopeName": "E-mail",
|
|
||||||
"emailScopeDescription": "Zezwala aplikacji na dostęp do adresów e-mail.",
|
|
||||||
"profileScopeName": "Profil",
|
|
||||||
"profileScopeDescription": "Zezwala aplikacji na dostęp do informacji o porfilu.",
|
|
||||||
"groupsScopeName": "Grupy",
|
|
||||||
"groupsScopeDescription": "Zezwala aplikacji na dostęp do informacji o grupie."
|
|
||||||
}
|
|
||||||
@@ -1,37 +1,37 @@
|
|||||||
{
|
{
|
||||||
"loginTitle": "Bem-vindo de volta, acesse com",
|
"loginTitle": "Bem-vindo de volta, acesse com",
|
||||||
"loginTitleSimple": "Bem-vindo de volta, faça o login",
|
"loginTitleSimple": "Welcome back, please login",
|
||||||
"loginDivider": "Ou",
|
"loginDivider": "Or",
|
||||||
"loginUsername": "Nome de usuário",
|
"loginUsername": "Nome de usuário",
|
||||||
"loginPassword": "Senha",
|
"loginPassword": "Senha",
|
||||||
"loginSubmit": "Entrar",
|
"loginSubmit": "Entrar",
|
||||||
"loginFailTitle": "Falha ao iniciar sessão",
|
"loginFailTitle": "Falha ao iniciar sessão",
|
||||||
"loginFailSubtitle": "Por favor, verifique seu usuário e senha",
|
"loginFailSubtitle": "Por favor, verifique seu usuário e senha",
|
||||||
"loginFailRateLimit": "Você falhou em iniciar sessão muitas vezes, por favor tente novamente mais tarde",
|
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||||
"loginSuccessTitle": "Sessão Iniciada",
|
"loginSuccessTitle": "Sessão Iniciada",
|
||||||
"loginSuccessSubtitle": "Bem-vindo de volta!",
|
"loginSuccessSubtitle": "Bem-vindo de volta!",
|
||||||
"loginOauthFailTitle": "Ocorreu um erro",
|
"loginOauthFailTitle": "An error occurred",
|
||||||
"loginOauthFailSubtitle": "Falha ao obter URL de OAuth",
|
"loginOauthFailSubtitle": "Falha ao obter URL de OAuth",
|
||||||
"loginOauthSuccessTitle": "Redirecionando",
|
"loginOauthSuccessTitle": "Redirecionando",
|
||||||
"loginOauthSuccessSubtitle": "Redirecionando para seu provedor OAuth",
|
"loginOauthSuccessSubtitle": "Redirecionando para seu provedor OAuth",
|
||||||
"loginOauthAutoRedirectTitle": "Redirecionamento automático do OAuth",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "Você será automaticamente redirecionado para seu provedor OAuth para autenticar.",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "Redirecionar agora",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Continuar",
|
"continueTitle": "Continuar",
|
||||||
"continueRedirectingTitle": "Redirecionando...",
|
"continueRedirectingTitle": "Redirecionando...",
|
||||||
"continueRedirectingSubtitle": "Você deve ser redirecionado para o aplicativo em breve",
|
"continueRedirectingSubtitle": "Você deve ser redirecionado para o aplicativo em breve",
|
||||||
"continueRedirectManually": "Redirecionar-me manualmente",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "Redirecionamento inseguro",
|
"continueInsecureRedirectTitle": "Redirecionamento inseguro",
|
||||||
"continueInsecureRedirectSubtitle": "Você está tentando redirecionar de <code>https</code> para <code>http</code>, você tem certeza que deseja continuar?",
|
"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": "Redirecionamento não confiável",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "Você está tentando redirecionar para um domínio que não corresponde ao seu domínio configurado (<code>{{cookieDomain}}</code>). Tem certeza que deseja continuar?",
|
"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": "Falha ao encerrar sessão",
|
"logoutFailTitle": "Falha ao encerrar sessão",
|
||||||
"logoutFailSubtitle": "Por favor, tente novamente",
|
"logoutFailSubtitle": "Por favor, tente novamente",
|
||||||
"logoutSuccessTitle": "Sessão encerrada",
|
"logoutSuccessTitle": "Sessão encerrada",
|
||||||
"logoutSuccessSubtitle": "Você foi desconectado",
|
"logoutSuccessSubtitle": "Você foi desconectado",
|
||||||
"logoutTitle": "Sair",
|
"logoutTitle": "Sair",
|
||||||
"logoutUsernameSubtitle": "Você está atualmente logado como <code>{{username}}</code>, clique no botão abaixo para sair.",
|
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
|
||||||
"logoutOauthSubtitle": "Você está atualmente logado como <code>{{username}}</code> usando o provedor {{provider}} OAuth, clique no botão abaixo para sair.",
|
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
|
||||||
"notFoundTitle": "Página não encontrada",
|
"notFoundTitle": "Página não encontrada",
|
||||||
"notFoundSubtitle": "A página que você está procurando não existe.",
|
"notFoundSubtitle": "A página que você está procurando não existe.",
|
||||||
"notFoundButton": "Voltar para a tela inicial",
|
"notFoundButton": "Voltar para a tela inicial",
|
||||||
@@ -40,42 +40,23 @@
|
|||||||
"totpSuccessTitle": "Verificado",
|
"totpSuccessTitle": "Verificado",
|
||||||
"totpSuccessSubtitle": "Redirecionando para o seu aplicativo",
|
"totpSuccessSubtitle": "Redirecionando para o seu aplicativo",
|
||||||
"totpTitle": "Insira o seu código TOTP",
|
"totpTitle": "Insira o seu código TOTP",
|
||||||
"totpSubtitle": "Por favor, insira o código do seu aplicativo de autenticação.",
|
"totpSubtitle": "Please enter the code from your authenticator app.",
|
||||||
"unauthorizedTitle": "Não autorizado",
|
"unauthorizedTitle": "Não autorizado",
|
||||||
"unauthorizedResourceSubtitle": "O usuário com nome de usuário <code>{{username}}</code> não está autorizado a acessar o recurso <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "O usuário com o nome <code>{{username}}</code> não está autorizado a acessar.",
|
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
||||||
"unauthorizedGroupsSubtitle": "O usuário <code>{{username}}</code> não está autorizado a acessar o recurso <code>{{resource}}</code>.",
|
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedIpSubtitle": "Seu endereço IP <code>{{ip}}</code> não está autorizado a acessar o recurso <code>{{resource}}</code>.",
|
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedButton": "Tentar novamente",
|
"unauthorizedButton": "Tentar novamente",
|
||||||
"cancelTitle": "Cancelar",
|
"cancelTitle": "Cancelar",
|
||||||
"forgotPasswordTitle": "Esqueceu sua senha?",
|
"forgotPasswordTitle": "Esqueceu sua senha?",
|
||||||
"failedToFetchProvidersTitle": "Falha ao carregar provedores de autenticação. Verifique sua configuração.",
|
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||||
"errorTitle": "Ocorreu um erro",
|
"errorTitle": "An error occurred",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"errorSubtitle": "Ocorreu um erro ao tentar executar esta ação. Por favor, verifique o console para mais informações.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"forgotPasswordMessage": "Você pode redefinir sua senha alterando a variável de ambiente `USERS`.",
|
"fieldRequired": "This field is required",
|
||||||
"fieldRequired": "Este campo é obrigatório",
|
"invalidInput": "Invalid input",
|
||||||
"invalidInput": "Entrada Inválida",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningTitle": "Domínio inválido",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"domainWarningSubtitle": "Esta instância está configurada para ser acessada de <code>{{appUrl}}</code>, mas <code>{{currentUrl}}</code> está sendo usado. Se você continuar, você pode encontrar problemas com a autenticação.",
|
"ignoreTitle": "Ignore",
|
||||||
"ignoreTitle": "Ignorar",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"goToCorrectDomainTitle": "Ir para o domínio correto",
|
}
|
||||||
"authorizeTitle": "Authorize",
|
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -1,81 +1,62 @@
|
|||||||
{
|
{
|
||||||
"loginTitle": "Bem-vindo de volta, inicia sessão com",
|
"loginTitle": "Welcome back, login with",
|
||||||
"loginTitleSimple": "Bem-vindo de volta, inicia sessão",
|
"loginTitleSimple": "Welcome back, please login",
|
||||||
"loginDivider": "Ou",
|
"loginDivider": "Or",
|
||||||
"loginUsername": "Nome de utilizador",
|
"loginUsername": "Username",
|
||||||
"loginPassword": "Palavra-passe",
|
"loginPassword": "Password",
|
||||||
"loginSubmit": "Iniciar sessão",
|
"loginSubmit": "Login",
|
||||||
"loginFailTitle": "Falha ao iniciar sessão",
|
"loginFailTitle": "Failed to log in",
|
||||||
"loginFailSubtitle": "Verifica o nome de utilizador e a palavra-passe",
|
"loginFailSubtitle": "Please check your username and password",
|
||||||
"loginFailRateLimit": "Falhaste o início de sessão demasiadas vezes. Tenta novamente mais tarde",
|
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||||
"loginSuccessTitle": "Sessão iniciada",
|
"loginSuccessTitle": "Logged in",
|
||||||
"loginSuccessSubtitle": "Bem-vindo de volta!",
|
"loginSuccessSubtitle": "Welcome back!",
|
||||||
"loginOauthFailTitle": "Ocorreu um erro",
|
"loginOauthFailTitle": "An error occurred",
|
||||||
"loginOauthFailSubtitle": "Não foi possível obter o URL OAuth",
|
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||||
"loginOauthSuccessTitle": "A redirecionar",
|
"loginOauthSuccessTitle": "Redirecting",
|
||||||
"loginOauthSuccessSubtitle": "A redirecionar para o teu fornecedor OAuth",
|
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||||
"loginOauthAutoRedirectTitle": "Redirecionamento automático OAuth",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "Vais ser redirecionado automaticamente para o teu fornecedor OAuth para autenticação.",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "Redirecionar agora",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Continuar",
|
"continueTitle": "Continue",
|
||||||
"continueRedirectingTitle": "A redirecionar...",
|
"continueRedirectingTitle": "Redirecting...",
|
||||||
"continueRedirectingSubtitle": "Deverás ser redirecionado para a aplicação em breve",
|
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||||
"continueRedirectManually": "Redirecionar manualmente",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "Redirecionamento inseguro",
|
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||||
"continueInsecureRedirectSubtitle": "Estás a tentar redirecionar de <code>https</code> para <code>http</code>, o que não é seguro. Tens a certeza de que queres continuar?",
|
"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": "Redirecionamento não fidedigno",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "Estás a tentar redirecionar para um domínio que não corresponde ao domínio configurado (<code>{{cookieDomain}}</code>). Tens a certeza de que queres continuar?",
|
"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": "Falha ao terminar sessão",
|
"logoutFailTitle": "Failed to log out",
|
||||||
"logoutFailSubtitle": "Tenta novamente",
|
"logoutFailSubtitle": "Please try again",
|
||||||
"logoutSuccessTitle": "Sessão terminada",
|
"logoutSuccessTitle": "Logged out",
|
||||||
"logoutSuccessSubtitle": "Terminaste a sessão com sucesso",
|
"logoutSuccessSubtitle": "You have been logged out",
|
||||||
"logoutTitle": "Terminar sessão",
|
"logoutTitle": "Logout",
|
||||||
"logoutUsernameSubtitle": "Estás com sessão iniciada como <code>{{username}}</code>. Clica no botão abaixo para terminar sessão.",
|
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
|
||||||
"logoutOauthSubtitle": "Estás com sessão iniciada como <code>{{username}}</code> através do fornecedor OAuth {{provider}}. Clica no botão abaixo para terminar sessão.",
|
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
|
||||||
"notFoundTitle": "Página não encontrada",
|
"notFoundTitle": "Page not found",
|
||||||
"notFoundSubtitle": "A página que procuras não existe.",
|
"notFoundSubtitle": "The page you are looking for does not exist.",
|
||||||
"notFoundButton": "Ir para o início",
|
"notFoundButton": "Go home",
|
||||||
"totpFailTitle": "Falha na verificação do código",
|
"totpFailTitle": "Failed to verify code",
|
||||||
"totpFailSubtitle": "Verifica o código e tenta novamente",
|
"totpFailSubtitle": "Please check your code and try again",
|
||||||
"totpSuccessTitle": "Verificado",
|
"totpSuccessTitle": "Verified",
|
||||||
"totpSuccessSubtitle": "A redirecionar para a tua aplicação",
|
"totpSuccessSubtitle": "Redirecting to your app",
|
||||||
"totpTitle": "Introduz o teu código TOTP",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"totpSubtitle": "Introduz o código da tua aplicação de autenticação.",
|
"totpSubtitle": "Please enter the code from your authenticator app.",
|
||||||
"unauthorizedTitle": "Não autorizado",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "O utilizador com o nome <code>{{username}}</code> não tem autorização para aceder ao recurso <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "O utilizador com o nome <code>{{username}}</code> não tem autorização para iniciar sessão.",
|
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
||||||
"unauthorizedGroupsSubtitle": "O utilizador com o nome <code>{{username}}</code> não pertence aos grupos exigidos pelo recurso <code>{{resource}}</code>.",
|
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedIpSubtitle": "O teu endereço IP <code>{{ip}}</code> não tem autorização para aceder ao recurso <code>{{resource}}</code>.",
|
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedButton": "Tentar novamente",
|
"unauthorizedButton": "Try again",
|
||||||
"cancelTitle": "Cancelar",
|
"cancelTitle": "Cancel",
|
||||||
"forgotPasswordTitle": "Esqueceste-te da palavra-passe?",
|
"forgotPasswordTitle": "Forgot your password?",
|
||||||
"failedToFetchProvidersTitle": "Falha ao carregar os fornecedores de autenticação. Verifica a configuração.",
|
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||||
"errorTitle": "Ocorreu um erro",
|
"errorTitle": "An error occurred",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"errorSubtitle": "Ocorreu um erro ao tentar executar esta ação. Consulta a consola para mais informações.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"forgotPasswordMessage": "Podes redefinir a tua palavra-passe alterando a variável de ambiente `USERS`.",
|
"fieldRequired": "This field is required",
|
||||||
"fieldRequired": "Este campo é obrigatório",
|
"invalidInput": "Invalid input",
|
||||||
"invalidInput": "Entrada inválida",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningTitle": "Domínio inválido",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"domainWarningSubtitle": "Esta instância está configurada para ser acedida a partir de <code>{{appUrl}}</code>, mas está a ser usado <code>{{currentUrl}}</code>. Se continuares, poderás ter problemas de autenticação.",
|
"ignoreTitle": "Ignore",
|
||||||
"ignoreTitle": "Ignorar",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"goToCorrectDomainTitle": "Ir para o domínio correto",
|
}
|
||||||
"authorizeTitle": "Authorize",
|
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"forgotPasswordTitle": "Forgot your password?",
|
"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:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
@@ -59,23 +58,5 @@
|
|||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -11,24 +11,24 @@
|
|||||||
"loginSuccessTitle": "Вход выполнен",
|
"loginSuccessTitle": "Вход выполнен",
|
||||||
"loginSuccessSubtitle": "С возвращением!",
|
"loginSuccessSubtitle": "С возвращением!",
|
||||||
"loginOauthFailTitle": "Произошла ошибка",
|
"loginOauthFailTitle": "Произошла ошибка",
|
||||||
"loginOauthFailSubtitle": "Не удалось получить ссылку OAuth",
|
"loginOauthFailSubtitle": "Не удалось получить OAuth URL",
|
||||||
"loginOauthSuccessTitle": "Перенаправление",
|
"loginOauthSuccessTitle": "Перенаправление",
|
||||||
"loginOauthSuccessSubtitle": "Перенаправление к поставщику OAuth",
|
"loginOauthSuccessSubtitle": "Перенаправление к поставщику OAuth",
|
||||||
"loginOauthAutoRedirectTitle": "OAuth автоматическое перенаправление",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "Вы будете автоматически перенаправлены для авторизации у вашего поставщика OAuth.",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "Перенаправить сейчас",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Продолжить",
|
"continueTitle": "Продолжить",
|
||||||
"continueRedirectingTitle": "Перенаправление...",
|
"continueRedirectingTitle": "Перенаправление...",
|
||||||
"continueRedirectingSubtitle": "Скоро вы будете перенаправлены в приложение",
|
"continueRedirectingSubtitle": "Скоро вы будете перенаправлены в приложение",
|
||||||
"continueRedirectManually": "Перенаправить вручную",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "Небезопасное перенаправление",
|
"continueInsecureRedirectTitle": "Небезопасное перенаправление",
|
||||||
"continueInsecureRedirectSubtitle": "Попытка перенаправления с <code>https</code> на <code>http</code>, уверены, что хотите продолжить?",
|
"continueInsecureRedirectSubtitle": "Попытка перенаправления с <code>https</code> на <code>http</code>, уверены, что хотите продолжить?",
|
||||||
"continueUntrustedRedirectTitle": "Недоверенное перенаправление",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "Вы пытаетесь перенаправить на домен, который не соответствует вашему настроенному домену (<code>{{cookieDomain}}</code>). Вы уверены, что хотите продолжить?",
|
"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": "Не удалось выйти",
|
||||||
"logoutFailSubtitle": "Попробуйте ещё раз",
|
"logoutFailSubtitle": "Попробуйте ещё раз",
|
||||||
"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, нажмите кнопку ниже, чтобы выйти.",
|
||||||
@@ -40,42 +40,23 @@
|
|||||||
"totpSuccessTitle": "Подтверждён",
|
"totpSuccessTitle": "Подтверждён",
|
||||||
"totpSuccessSubtitle": "Перенаправление в приложение",
|
"totpSuccessSubtitle": "Перенаправление в приложение",
|
||||||
"totpTitle": "Введите код TOTP",
|
"totpTitle": "Введите код TOTP",
|
||||||
"totpSubtitle": "Пожалуйста, введите код из вашего приложения авторизации.",
|
"totpSubtitle": "Пожалуйста, введите код из вашего приложения-аутентификатора.",
|
||||||
"unauthorizedTitle": "Доступ запрещён",
|
"unauthorizedTitle": "Доступ запрещен",
|
||||||
"unauthorizedResourceSubtitle": "Пользователю <code>{{username}}</code> не разрешён доступ к <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "Пользователю <code>{{username}}</code> не разрешен доступ к <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "Пользователю <code>{{username}}</code> не разрешён вход.",
|
"unauthorizedLoginSubtitle": "Пользователю <code>{{username}}</code> не разрешен вход.",
|
||||||
"unauthorizedGroupsSubtitle": "Пользователь <code>{{username}}</code> не состоит в группах, которым разрешён доступ к <code>{{resource}}</code>.",
|
"unauthorizedGroupsSubtitle": "Пользователь <code>{{username}}</code> не состоит в группах, которым разрешен доступ к <code>{{resource}}</code>.",
|
||||||
"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": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "Произошла ошибка при попытке выполнить это действие. Проверьте консоль для дополнительной информации.",
|
"errorSubtitle": "Произошла ошибка при попытке выполнить это действие. Проверьте консоль для дополнительной информации.",
|
||||||
"forgotPasswordMessage": "Вы можете сбросить свой пароль, изменив переменную окружения `USERS`.",
|
"forgotPasswordMessage": "Вы можете сбросить свой пароль, изменив переменную окружения `USERS`.",
|
||||||
"fieldRequired": "Это поле является обязательным",
|
"fieldRequired": "Это поле является обязательным",
|
||||||
"invalidInput": "Недопустимый ввод",
|
"invalidInput": "Недопустимый ввод",
|
||||||
"domainWarningTitle": "Неверный домен",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "Этот экземпляр настроен на доступ к нему из <code>{{appUrl}}</code>, но <code>{{currentUrl}}</code> в настоящее время используется. Если вы продолжите, то могут возникнуть проблемы с авторизацией.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Игнорировать",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Перейти к правильному домену",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"forgotPasswordTitle": "Заборавили сте лозинку?",
|
"forgotPasswordTitle": "Заборавили сте лозинку?",
|
||||||
"failedToFetchProvidersTitle": "Није успело учитавање провајдера аутентификације. Молим вас проверите ваша подешавања.",
|
"failedToFetchProvidersTitle": "Није успело учитавање провајдера аутентификације. Молим вас проверите ваша подешавања.",
|
||||||
"errorTitle": "Појавила се грешка",
|
"errorTitle": "Појавила се грешка",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "Појавила се грешка при покушају извршавања ове радње. Молим вас проверите конзолу за додатне информације.",
|
"errorSubtitle": "Појавила се грешка при покушају извршавања ове радње. Молим вас проверите конзолу за додатне информације.",
|
||||||
"forgotPasswordMessage": "Можете поништити вашу лозинку променом `USERS` променљиве окружења.",
|
"forgotPasswordMessage": "Можете поништити вашу лозинку променом `USERS` променљиве окружења.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
@@ -59,23 +58,5 @@
|
|||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"forgotPasswordTitle": "Forgot your password?",
|
"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:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
@@ -59,23 +58,5 @@
|
|||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -1,81 +1,62 @@
|
|||||||
{
|
{
|
||||||
"loginTitle": "Tekrar Hoş Geldiniz, giriş yapın",
|
"loginTitle": "Welcome back, login with",
|
||||||
"loginTitleSimple": "Tekrar hoş geldiniz, lütfen giriş yapın",
|
"loginTitleSimple": "Welcome back, please login",
|
||||||
"loginDivider": "Ya da",
|
"loginDivider": "Or",
|
||||||
"loginUsername": "Kullanıcı Adı",
|
"loginUsername": "Kullanıcı Adı",
|
||||||
"loginPassword": "Şifre",
|
"loginPassword": "Şifre",
|
||||||
"loginSubmit": "Giriş Yap",
|
"loginSubmit": "Giriş Yap",
|
||||||
"loginFailTitle": "Giriş yapılamadı",
|
"loginFailTitle": "Giriş yapılamadı",
|
||||||
"loginFailSubtitle": "Lütfen kullanıcı adınızı ve şifrenizi kontrol edin",
|
"loginFailSubtitle": "Please check your username and password",
|
||||||
"loginFailRateLimit": "Çok fazla kez giriş yapma girişiminde bulundunuz. Lütfen daha sonra tekrar deneyin",
|
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||||
"loginSuccessTitle": "Giriş yapıldı",
|
"loginSuccessTitle": "Giriş yapıldı",
|
||||||
"loginSuccessSubtitle": "Tekrar hoş geldiniz!",
|
"loginSuccessSubtitle": "Tekrar hoş geldiniz!",
|
||||||
"loginOauthFailTitle": "Hata oluştu",
|
"loginOauthFailTitle": "An error occurred",
|
||||||
"loginOauthFailSubtitle": "OAuth URL'si alınamadı",
|
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||||
"loginOauthSuccessTitle": "Yönlendiriliyor",
|
"loginOauthSuccessTitle": "Yönlendiriliyor",
|
||||||
"loginOauthSuccessSubtitle": "OAuth sağlayıcınıza yönlendiriliyor",
|
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||||
"loginOauthAutoRedirectTitle": "OAuth Otomatik Yönlendirme",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "Kimlik doğrulama işlemi için otomatik olarak OAuth sağlayıcınıza yönlendirileceksiniz.",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "Şimdi Yönlendir",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Devam et",
|
"continueTitle": "Devam et",
|
||||||
"continueRedirectingTitle": "Yönlendiriliyor...",
|
"continueRedirectingTitle": "Yönlendiriliyor...",
|
||||||
"continueRedirectingSubtitle": "Kısa süre içinde uygulamaya yönlendirileceksiniz",
|
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||||
"continueRedirectManually": "Beni manuel olarak yönlendir",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "Güvenli olmayan yönlendirme",
|
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||||
"continueInsecureRedirectSubtitle": "<code>http</code> adresinden <code>http</code> adresine yönlendirme yapmaya çalışıyorsunuz, bu güvenli değil. Devam etmek istediğinizden emin misiniz?",
|
"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": "Güvenilmeyen yönlendirme",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "Yapılandırdığınız alan adıyla eşleşmeyen bir alana yönlendirme yapmaya çalışıyorsunuz (<code>{{cookieDomain}}</code>). Devam etmek istediğinize emin misiniz?",
|
"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": "Çıkış Yapılamadı",
|
"logoutFailTitle": "Failed to log out",
|
||||||
"logoutFailSubtitle": "Lütfen tekrar deneyin",
|
"logoutFailSubtitle": "Lütfen tekrar deneyin",
|
||||||
"logoutSuccessTitle": "Çıkış yapıldı",
|
"logoutSuccessTitle": "Çıkış yapıldı",
|
||||||
"logoutSuccessSubtitle": "Çıkış yaptınız",
|
"logoutSuccessSubtitle": "You have been logged out",
|
||||||
"logoutTitle": "Çıkış yap",
|
"logoutTitle": "Logout",
|
||||||
"logoutUsernameSubtitle": "<code>{{username}}</code> olarak giriş yapmış durumdasınız. Çıkış yapmak için aşağıdaki düğmeye tıklayın.",
|
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
|
||||||
"logoutOauthSubtitle": "Şu anda {{provider}} OAuth sağlayıcısını kullanarak <code>{{username}}</code> olarak oturum açmış durumdasınız. Oturumunuzu kapatmak için aşağıdaki düğmeye tıklayın.",
|
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
|
||||||
"notFoundTitle": "Sayfa bulunamadı",
|
"notFoundTitle": "Sayfa bulunamadı",
|
||||||
"notFoundSubtitle": "Aradığınız sayfa mevcut değil.",
|
"notFoundSubtitle": "Aradığınız sayfa mevcut değil.",
|
||||||
"notFoundButton": "Ana sayfaya git",
|
"notFoundButton": "Ana sayfaya git",
|
||||||
"totpFailTitle": "Kod doğrulanamadı",
|
"totpFailTitle": "Kod doğrulanamadı",
|
||||||
"totpFailSubtitle": "Lütfen kodunuzu kontrol edin ve tekrar deneyin",
|
"totpFailSubtitle": "Please check your code and try again",
|
||||||
"totpSuccessTitle": "Doğrulandı",
|
"totpSuccessTitle": "Doğrulandı",
|
||||||
"totpSuccessSubtitle": "Uygulamanıza yönlendiriliyor",
|
"totpSuccessSubtitle": "Redirecting to your app",
|
||||||
"totpTitle": "TOTP kodunuzu girin",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"totpSubtitle": "Lütfen kimlik doğrulama uygulamanızdan aldığınız kodu girin.",
|
"totpSubtitle": "Please enter the code from your authenticator app.",
|
||||||
"unauthorizedTitle": "Yetkisiz",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "Kullanıcı adı <code>{{username}}</code> olan kullanıcının <code>{{resource}}</code> kaynağına erişim yetkisi bulunmamaktadır.",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "Kullanıcı adı <code>{{username}}</code> olan kullanıcının oturum açma yetkisi yok.",
|
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
||||||
"unauthorizedGroupsSubtitle": "Kullanıcı adı <code>{{username}}</code> olan kullanıcı, <code>{{resource}}</code> kaynağının gerektirdiği gruplarda bulunmuyor.",
|
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedIpSubtitle": "IP adresiniz <code>{{ip}}</code>, <code>{{resource}}</code> kaynağına erişim yetkisine sahip değil.",
|
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedButton": "Tekrar deneyin",
|
"unauthorizedButton": "Try again",
|
||||||
"cancelTitle": "İptal",
|
"cancelTitle": "İptal",
|
||||||
"forgotPasswordTitle": "Şifrenizi mi unuttunuz?",
|
"forgotPasswordTitle": "Forgot your password?",
|
||||||
"failedToFetchProvidersTitle": "Kimlik doğrulama sağlayıcıları yüklenemedi. Lütfen yapılandırmanızı kontrol edin.",
|
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||||
"errorTitle": "Bir hata oluştu",
|
"errorTitle": "An error occurred",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "Parolanızı `USERS` ortam değişkenini değiştirerek sıfırlayabilirsiniz.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "Bu alan zorunludur",
|
"fieldRequired": "This field is required",
|
||||||
"invalidInput": "Geçersiz girdi",
|
"invalidInput": "Invalid input",
|
||||||
"domainWarningTitle": "Geçersiz alan adı",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "Bu örnek, <code>{{appUrl}}</code> adresinden erişilecek şekilde yapılandırılmıştır, ancak <code>{{currentUrl}}</code> kullanılmaktadır. Devam ederseniz, kimlik doğrulama ile ilgili sorunlarla karşılaşabilirsiniz.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Yoksay",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Doğru alana gidin",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -1,81 +1,62 @@
|
|||||||
{
|
{
|
||||||
"loginTitle": "З поверненням, увійдіть через",
|
"loginTitle": "З поверненням, увійдіть через",
|
||||||
"loginTitleSimple": "З поверненням, будь ласка, авторизуйтесь",
|
"loginTitleSimple": "Welcome back, please login",
|
||||||
"loginDivider": "Або",
|
"loginDivider": "Or",
|
||||||
"loginUsername": "Ім'я користувача",
|
"loginUsername": "Username",
|
||||||
"loginPassword": "Пароль",
|
"loginPassword": "Password",
|
||||||
"loginSubmit": "Увійти",
|
"loginSubmit": "Login",
|
||||||
"loginFailTitle": "Не вдалося авторизуватися",
|
"loginFailTitle": "Failed to log in",
|
||||||
"loginFailSubtitle": "Перевірте ім'я користувача та пароль",
|
"loginFailSubtitle": "Please check your username and password",
|
||||||
"loginFailRateLimit": "Ви не змогли увійти занадто багато разів. Будь ласка, спробуйте ще раз пізніше",
|
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||||
"loginSuccessTitle": "Вхід здійснено",
|
"loginSuccessTitle": "Logged in",
|
||||||
"loginSuccessSubtitle": "З поверненням!",
|
"loginSuccessSubtitle": "Welcome back!",
|
||||||
"loginOauthFailTitle": "Виникла помилка",
|
"loginOauthFailTitle": "An error occurred",
|
||||||
"loginOauthFailSubtitle": "Не вдалося отримати OAuth URL",
|
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||||
"loginOauthSuccessTitle": "Перенаправляємо",
|
"loginOauthSuccessTitle": "Redirecting",
|
||||||
"loginOauthSuccessSubtitle": "Перенаправляємо до вашого провайдера OAuth",
|
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||||
"loginOauthAutoRedirectTitle": "Автоматичне переспрямування OAuth",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "Ви будете автоматично перенаправлені до вашого провайдера OAuth для автентифікації.",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "Перейти зараз",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "Продовжити",
|
"continueTitle": "Continue",
|
||||||
"continueRedirectingTitle": "Перенаправлення...",
|
"continueRedirectingTitle": "Redirecting...",
|
||||||
"continueRedirectingSubtitle": "Незабаром ви будете перенаправлені в додаток",
|
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||||
"continueRedirectManually": "Перенаправити мене вручну",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "Небезпечне перенаправлення",
|
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||||
"continueInsecureRedirectSubtitle": "Ви намагаєтесь перенаправити з <code>https</code> на <code>http</code> який не є безпечним. Ви впевнені, що хочете продовжити?",
|
"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": "Недовірене перенаправлення",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "Ви намагаєтесь перенаправити на домен, який не збігається з вашим налаштованим доменом (<code>{{cookieDomain}}</code>). Впевнені, що хочете продовжити?",
|
"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": "Будь ласка, спробуйте знову",
|
"logoutFailSubtitle": "Please try again",
|
||||||
"logoutSuccessTitle": "Ви вийшли",
|
"logoutSuccessTitle": "Logged out",
|
||||||
"logoutSuccessSubtitle": "Ви вийшли з системи",
|
"logoutSuccessSubtitle": "You have been logged out",
|
||||||
"logoutTitle": "Вийти",
|
"logoutTitle": "Logout",
|
||||||
"logoutUsernameSubtitle": "Зараз ви увійшли як <code>{{username}}</code>. Натисніть кнопку нижче для виходу.",
|
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
|
||||||
"logoutOauthSubtitle": "Наразі ви увійшли як <code>{{username}}</code> використовуючи провайдера {{provider}} OAuth. Натисніть кнопку нижче, щоб вийти.",
|
"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": "Сторінка, яку ви шукаєте, не існує.",
|
"notFoundSubtitle": "The page you are looking for does not exist.",
|
||||||
"notFoundButton": "На головну",
|
"notFoundButton": "Go home",
|
||||||
"totpFailTitle": "Не вдалося перевірити код",
|
"totpFailTitle": "Failed to verify code",
|
||||||
"totpFailSubtitle": "Перевірте ваш код і спробуйте ще раз",
|
"totpFailSubtitle": "Please check your code and try again",
|
||||||
"totpSuccessTitle": "Перевірено",
|
"totpSuccessTitle": "Verified",
|
||||||
"totpSuccessSubtitle": "Перенаправлення до вашого додатку",
|
"totpSuccessSubtitle": "Redirecting to your app",
|
||||||
"totpTitle": "Введіть ваш TOTP код",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"totpSubtitle": "Будь ласка, введіть код з вашого додатку для автентифікації.",
|
"totpSubtitle": "Please enter the code from your authenticator app.",
|
||||||
"unauthorizedTitle": "Доступ обмежено",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "Користувач з ім'ям користувача <code>{{username}}</code> не має права доступу до ресурсу <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "Користувач з іменем <code>{{username}}</code> не авторизований для входу.",
|
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
||||||
"unauthorizedGroupsSubtitle": "Користувач з іменем <code>{{username}}</code> не входить до груп, що необхідні для ресурсу <code>{{resource}}</code>.",
|
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedIpSubtitle": "Ваша IP-адреса <code>{{ip}}</code> не авторизована для доступу до ресурсу <code>{{resource}}</code>.",
|
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedButton": "Спробуйте ще раз",
|
"unauthorizedButton": "Try again",
|
||||||
"cancelTitle": "Скасовувати",
|
"cancelTitle": "Cancel",
|
||||||
"forgotPasswordTitle": "Забули пароль?",
|
"forgotPasswordTitle": "Forgot your password?",
|
||||||
"failedToFetchProvidersTitle": "Не вдалося завантажити провайдерів автентифікації. Будь ласка, перевірте вашу конфігурацію.",
|
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||||
"errorTitle": "Виникла помилка",
|
"errorTitle": "An error occurred",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"forgotPasswordMessage": "Ви можете скинути пароль, змінивши змінну середовища \"USERS\".",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "Це поле обов'язкове для заповнення",
|
"fieldRequired": "This field is required",
|
||||||
"invalidInput": "Невірне введення",
|
"invalidInput": "Invalid input",
|
||||||
"domainWarningTitle": "Невірний домен",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "Даний ресурс налаштований для доступу з <code>{{appUrl}}</code>, але використовується <code>{{currentUrl}}</code>. Якщо ви продовжите, можуть виникнути проблеми з автентифікацією.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ігнорувати",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Перейти за коректним доменом",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -48,34 +48,15 @@
|
|||||||
"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": "Cancel",
|
"cancelTitle": "Cancel",
|
||||||
"forgotPasswordTitle": "Bạn quên mật khẩu?",
|
"forgotPasswordTitle": "Forgot your password?",
|
||||||
"failedToFetchProvidersTitle": "Không tải được nhà cung cấp xác thực. Vui lòng kiểm tra cấu hình của bạn.",
|
"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:",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||||
"errorSubtitle": "Đã xảy ra lỗi khi thực hiện thao tác này. Vui lòng kiểm tra bảng điều khiển để biết thêm thông tin.",
|
|
||||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "This field is required",
|
"fieldRequired": "This field is required",
|
||||||
"invalidInput": "Invalid input",
|
"invalidInput": "Invalid input",
|
||||||
"domainWarningTitle": "Invalid Domain",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "Ignore",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "Go to correct domain",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -14,17 +14,17 @@
|
|||||||
"loginOauthFailSubtitle": "获取 OAuth URL 失败",
|
"loginOauthFailSubtitle": "获取 OAuth URL 失败",
|
||||||
"loginOauthSuccessTitle": "重定向中",
|
"loginOauthSuccessTitle": "重定向中",
|
||||||
"loginOauthSuccessSubtitle": "重定向到您的 OAuth 提供商",
|
"loginOauthSuccessSubtitle": "重定向到您的 OAuth 提供商",
|
||||||
"loginOauthAutoRedirectTitle": "OAuth自动重定向",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "您将被自动重定向到您的 OAuth 提供商进行身份验证。",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "立即跳转",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "继续",
|
"continueTitle": "继续",
|
||||||
"continueRedirectingTitle": "正在重定向……",
|
"continueRedirectingTitle": "正在重定向……",
|
||||||
"continueRedirectingSubtitle": "您应该很快被重定向到应用",
|
"continueRedirectingSubtitle": "您应该很快被重定向到应用",
|
||||||
"continueRedirectManually": "请手动跳转",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "不安全的重定向",
|
"continueInsecureRedirectTitle": "不安全的重定向",
|
||||||
"continueInsecureRedirectSubtitle": "您正在尝试从<code>https</code>重定向到<code>http</code>可能存在风险。您确定要继续吗?",
|
"continueInsecureRedirectSubtitle": "您正在尝试从<code>https</code>重定向到<code>http</code>可能存在风险。您确定要继续吗?",
|
||||||
"continueUntrustedRedirectTitle": "不可信的重定向",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "您尝试跳转的域名与配置的域名(<code>{{cookieDomain}}</code>)不匹配。是否继续?",
|
"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": "注销失败",
|
||||||
"logoutFailSubtitle": "请重试",
|
"logoutFailSubtitle": "请重试",
|
||||||
"logoutSuccessTitle": "已登出",
|
"logoutSuccessTitle": "已登出",
|
||||||
@@ -51,31 +51,12 @@
|
|||||||
"forgotPasswordTitle": "忘记密码?",
|
"forgotPasswordTitle": "忘记密码?",
|
||||||
"failedToFetchProvidersTitle": "加载身份验证提供程序失败,请检查您的配置。",
|
"failedToFetchProvidersTitle": "加载身份验证提供程序失败,请检查您的配置。",
|
||||||
"errorTitle": "发生了错误",
|
"errorTitle": "发生了错误",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "执行此操作时发生错误,请检查控制台以获取更多信息。",
|
"errorSubtitle": "执行此操作时发生错误,请检查控制台以获取更多信息。",
|
||||||
"forgotPasswordMessage": "您可以通过更改 `USERS ` 环境变量重置您的密码。",
|
"forgotPasswordMessage": "您可以通过更改 `USERS ` 环境变量重置您的密码。",
|
||||||
"fieldRequired": "必添字段",
|
"fieldRequired": "必添字段",
|
||||||
"invalidInput": "无效的输入",
|
"invalidInput": "无效的输入",
|
||||||
"domainWarningTitle": "无效域名",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "当前实例配置的访问地址为 <code>{{appUrl}}</code>,但您正在使用 <code>{{currentUrl}}</code>。若继续操作,可能会遇到身份验证问题。",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "忽略",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "转到正确的域名",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"loginTitle": "歡迎回來,請使用以下方式登入",
|
"loginTitle": "歡迎回來,請用以下方式登入",
|
||||||
"loginTitleSimple": "歡迎回來,請登入",
|
"loginTitleSimple": "歡迎回來,請登入",
|
||||||
"loginDivider": "或",
|
"loginDivider": "或",
|
||||||
"loginUsername": "帳號",
|
"loginUsername": "帳號",
|
||||||
@@ -14,17 +14,17 @@
|
|||||||
"loginOauthFailSubtitle": "無法取得 OAuth 網址",
|
"loginOauthFailSubtitle": "無法取得 OAuth 網址",
|
||||||
"loginOauthSuccessTitle": "重新導向中",
|
"loginOauthSuccessTitle": "重新導向中",
|
||||||
"loginOauthSuccessSubtitle": "正在將您重新導向至 OAuth 供應商",
|
"loginOauthSuccessSubtitle": "正在將您重新導向至 OAuth 供應商",
|
||||||
"loginOauthAutoRedirectTitle": "OAuth 自動跳轉",
|
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||||
"loginOauthAutoRedirectSubtitle": "自動跳轉到 OAuth 供應商進行身份驗證。",
|
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||||
"loginOauthAutoRedirectButton": "立即重新導向",
|
"loginOauthAutoRedirectButton": "Redirect now",
|
||||||
"continueTitle": "繼續",
|
"continueTitle": "繼續",
|
||||||
"continueRedirectingTitle": "重新導向中……",
|
"continueRedirectingTitle": "重新導向中……",
|
||||||
"continueRedirectingSubtitle": "您即將被重新導向至應用程式",
|
"continueRedirectingSubtitle": "您即將被重新導向至應用程式",
|
||||||
"continueRedirectManually": "手動重新導向",
|
"continueRedirectManually": "Redirect me manually",
|
||||||
"continueInsecureRedirectTitle": "不安全的重新導向",
|
"continueInsecureRedirectTitle": "不安全的重新導向",
|
||||||
"continueInsecureRedirectSubtitle": "您正嘗試從安全的 <code>https</code> 重新導向至不安全的 <code>http</code>。您確定要繼續嗎?",
|
"continueInsecureRedirectSubtitle": "您正嘗試從安全的 <code>https</code> 重新導向至不安全的 <code>http</code>。您確定要繼續嗎?",
|
||||||
"continueUntrustedRedirectTitle": "不受信任的重新導向",
|
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||||
"continueUntrustedRedirectSubtitle": "你嘗試重新導向的域名與設定不符(<code>{{cookieDomain}}</code>)。你確定要繼續嗎?",
|
"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": "登出失敗",
|
||||||
"logoutFailSubtitle": "請再試一次",
|
"logoutFailSubtitle": "請再試一次",
|
||||||
"logoutSuccessTitle": "登出成功",
|
"logoutSuccessTitle": "登出成功",
|
||||||
@@ -51,31 +51,12 @@
|
|||||||
"forgotPasswordTitle": "忘記密碼?",
|
"forgotPasswordTitle": "忘記密碼?",
|
||||||
"failedToFetchProvidersTitle": "載入驗證供應商失敗。請檢查您的設定。",
|
"failedToFetchProvidersTitle": "載入驗證供應商失敗。請檢查您的設定。",
|
||||||
"errorTitle": "發生錯誤",
|
"errorTitle": "發生錯誤",
|
||||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
|
||||||
"errorSubtitle": "執行此操作時發生錯誤。請檢查主控台以獲取更多資訊。",
|
"errorSubtitle": "執行此操作時發生錯誤。請檢查主控台以獲取更多資訊。",
|
||||||
"forgotPasswordMessage": "透過修改 `USERS` 環境變數,你可以重設你的密碼。",
|
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||||
"fieldRequired": "此為必填欄位",
|
"fieldRequired": "This field is required",
|
||||||
"invalidInput": "無效的輸入",
|
"invalidInput": "Invalid input",
|
||||||
"domainWarningTitle": "無效的網域",
|
"domainWarningTitle": "Invalid Domain",
|
||||||
"domainWarningSubtitle": "此服務設定為透過 <code>{{appUrl}}</code> 存取,但目前使用的是 <code>{{currentUrl}}</code>。若繼續操作,可能會遇到驗證問題。",
|
"domainWarningSubtitle": "This instance is configured to be accessed from <code>{{appUrl}}</code>, but <code>{{currentUrl}}</code> is being used. If you proceed, you may encounter issues with authentication.",
|
||||||
"ignoreTitle": "忽略",
|
"ignoreTitle": "Ignore",
|
||||||
"goToCorrectDomainTitle": "前往正確域名",
|
"goToCorrectDomainTitle": "Go to correct domain"
|
||||||
"authorizeTitle": "Authorize",
|
}
|
||||||
"authorizeCardTitle": "Continue to {{app}}?",
|
|
||||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
|
||||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
|
||||||
"authorizeLoadingTitle": "Loading...",
|
|
||||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
|
||||||
"authorizeSuccessTitle": "Authorized",
|
|
||||||
"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.",
|
|
||||||
"authorizeErrorMissingParams": "The following parameters are missing: {{missingParams}}",
|
|
||||||
"openidScopeName": "OpenID Connect",
|
|
||||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
|
||||||
"emailScopeName": "Email",
|
|
||||||
"emailScopeDescription": "Allows the app to access your email address.",
|
|
||||||
"profileScopeName": "Profile",
|
|
||||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
|
||||||
"groupsScopeName": "Groups",
|
|
||||||
"groupsScopeDescription": "Allows the app to access your group information."
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,15 @@ export function cn(...inputs: ClassValue[]) {
|
|||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isValidUrl = (url: string) => {
|
||||||
|
try {
|
||||||
|
new URL(url);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const capitalize = (str: string) => {
|
export const capitalize = (str: string) => {
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|||||||
import { AppContextProvider } from "./context/app-context.tsx";
|
import { AppContextProvider } from "./context/app-context.tsx";
|
||||||
import { UserContextProvider } from "./context/user-context.tsx";
|
import { UserContextProvider } from "./context/user-context.tsx";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { ThemeProvider } from "./components/providers/theme-provider.tsx";
|
|
||||||
import { AuthorizePage } from "./pages/authorize-page.tsx";
|
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
@@ -26,28 +24,25 @@ createRoot(document.getElementById("root")!).render(
|
|||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<AppContextProvider>
|
<AppContextProvider>
|
||||||
<UserContextProvider>
|
<UserContextProvider>
|
||||||
<ThemeProvider defaultTheme="system" storageKey="tinyauth-theme">
|
<BrowserRouter>
|
||||||
<BrowserRouter>
|
<Routes>
|
||||||
<Routes>
|
<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 path="/logout" element={<LogoutPage />} />
|
||||||
<Route path="/authorize" element={<AuthorizePage />} />
|
<Route path="/continue" element={<ContinuePage />} />
|
||||||
<Route path="/logout" element={<LogoutPage />} />
|
<Route path="/totp" element={<TotpPage />} />
|
||||||
<Route path="/continue" element={<ContinuePage />} />
|
<Route
|
||||||
<Route path="/totp" element={<TotpPage />} />
|
path="/forgot-password"
|
||||||
<Route
|
element={<ForgotPasswordPage />}
|
||||||
path="/forgot-password"
|
/>
|
||||||
element={<ForgotPasswordPage />}
|
<Route path="/unauthorized" element={<UnauthorizedPage />} />
|
||||||
/>
|
<Route path="/error" element={<ErrorPage />} />
|
||||||
<Route path="/unauthorized" element={<UnauthorizedPage />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
<Route path="/error" element={<ErrorPage />} />
|
</Route>
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
</Routes>
|
||||||
</Route>
|
</BrowserRouter>
|
||||||
</Routes>
|
<Toaster />
|
||||||
</BrowserRouter>
|
|
||||||
<Toaster />
|
|
||||||
</ThemeProvider>
|
|
||||||
</UserContextProvider>
|
</UserContextProvider>
|
||||||
</AppContextProvider>
|
</AppContextProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
|||||||
@@ -1,199 +0,0 @@
|
|||||||
import { useUserContext } from "@/context/user-context";
|
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
||||||
import { Navigate, useNavigate } from "react-router";
|
|
||||||
import { useLocation } from "react-router";
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardContent,
|
|
||||||
} from "@/components/ui/card";
|
|
||||||
import { getOidcClientInfoSchema } from "@/schemas/oidc-schemas";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import axios from "axios";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { useOIDCParams } from "@/lib/hooks/oidc";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { TFunction } from "i18next";
|
|
||||||
import { Mail, Shield, User, Users } from "lucide-react";
|
|
||||||
|
|
||||||
type Scope = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
icon: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const scopeMapIconProps = {
|
|
||||||
className: "stroke-card stroke-2.5",
|
|
||||||
};
|
|
||||||
|
|
||||||
const createScopeMap = (t: TFunction<"translation", undefined>): Scope[] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: "openid",
|
|
||||||
name: t("openidScopeName"),
|
|
||||||
description: t("openidScopeDescription"),
|
|
||||||
icon: <Shield {...scopeMapIconProps} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "email",
|
|
||||||
name: t("emailScopeName"),
|
|
||||||
description: t("emailScopeDescription"),
|
|
||||||
icon: <Mail {...scopeMapIconProps} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "profile",
|
|
||||||
name: t("profileScopeName"),
|
|
||||||
description: t("profileScopeDescription"),
|
|
||||||
icon: <User {...scopeMapIconProps} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "groups",
|
|
||||||
name: t("groupsScopeName"),
|
|
||||||
description: t("groupsScopeDescription"),
|
|
||||||
icon: <Users {...scopeMapIconProps} />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AuthorizePage = () => {
|
|
||||||
const { isLoggedIn } = useUserContext();
|
|
||||||
const { search } = useLocation();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const scopeMap = createScopeMap(t);
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
|
||||||
const {
|
|
||||||
values: props,
|
|
||||||
missingParams,
|
|
||||||
isOidc,
|
|
||||||
compiled: compiledOIDCParams,
|
|
||||||
} = useOIDCParams(searchParams);
|
|
||||||
const scopes = props.scope ? props.scope.split(" ").filter(Boolean) : [];
|
|
||||||
|
|
||||||
const getClientInfo = useQuery({
|
|
||||||
queryKey: ["client", props.client_id],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await fetch(`/api/oidc/clients/${props.client_id}`);
|
|
||||||
const data = await getOidcClientInfoSchema.parseAsync(await res.json());
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
enabled: isOidc,
|
|
||||||
});
|
|
||||||
|
|
||||||
const authorizeMutation = useMutation({
|
|
||||||
mutationFn: () => {
|
|
||||||
return axios.post("/api/oidc/authorize", {
|
|
||||||
scope: props.scope,
|
|
||||||
response_type: props.response_type,
|
|
||||||
client_id: props.client_id,
|
|
||||||
redirect_uri: props.redirect_uri,
|
|
||||||
state: props.state,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
mutationKey: ["authorize", props.client_id],
|
|
||||||
onSuccess: (data) => {
|
|
||||||
toast.info(t("authorizeSuccessTitle"), {
|
|
||||||
description: t("authorizeSuccessSubtitle"),
|
|
||||||
});
|
|
||||||
window.location.replace(data.data.redirect_uri);
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
window.location.replace(
|
|
||||||
`/error?error=${encodeURIComponent(error.message)}`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (missingParams.length > 0) {
|
|
||||||
return (
|
|
||||||
<Navigate
|
|
||||||
to={`/error?error=${encodeURIComponent(t("authorizeErrorMissingParams", { missingParams: missingParams.join(", ") }))}`}
|
|
||||||
replace
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
|
||||||
return <Navigate to={`/login?${compiledOIDCParams}`} replace />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getClientInfo.isLoading) {
|
|
||||||
return (
|
|
||||||
<Card className="min-w-xs sm:min-w-sm">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-3xl">
|
|
||||||
{t("authorizeLoadingTitle")}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>{t("authorizeLoadingSubtitle")}</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getClientInfo.isError) {
|
|
||||||
return (
|
|
||||||
<Navigate
|
|
||||||
to={`/error?error=${encodeURIComponent(t("authorizeErrorClientInfo"))}`}
|
|
||||||
replace
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className="min-w-xs sm:min-w-sm mx-4">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-3xl">
|
|
||||||
{t("authorizeCardTitle", {
|
|
||||||
app: getClientInfo.data?.name || "Unknown",
|
|
||||||
})}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
{scopes.includes("openid")
|
|
||||||
? t("authorizeSubtitle")
|
|
||||||
: t("authorizeSubtitleOAuth")}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
{scopes.includes("openid") && (
|
|
||||||
<CardContent className="flex flex-col gap-4">
|
|
||||||
{scopes.map((id) => {
|
|
||||||
const scope = scopeMap.find((s) => s.id === id);
|
|
||||||
if (!scope) return null;
|
|
||||||
return (
|
|
||||||
<div key={scope.id} className="flex flex-row items-center gap-3">
|
|
||||||
<div className="p-2 flex flex-col items-center justify-center bg-card-foreground rounded-md">
|
|
||||||
{scope.icon}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-0.5">
|
|
||||||
<div className="text-md">{scope.name}</div>
|
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
{scope.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</CardContent>
|
|
||||||
)}
|
|
||||||
<CardFooter className="flex flex-col items-stretch gap-2">
|
|
||||||
<Button
|
|
||||||
onClick={() => authorizeMutation.mutate()}
|
|
||||||
loading={authorizeMutation.isPending}
|
|
||||||
>
|
|
||||||
{t("authorizeTitle")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => navigate("/")}
|
|
||||||
disabled={authorizeMutation.isPending}
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
{t("cancelTitle")}
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -8,121 +8,75 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
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 { isValidUrl } from "@/lib/utils";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
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 DOMPurify from "dompurify";
|
||||||
import { useRedirectUri } from "@/lib/hooks/redirect-uri";
|
import { useState } from "react";
|
||||||
|
|
||||||
export const ContinuePage = () => {
|
export const ContinuePage = () => {
|
||||||
const { cookieDomain, disableUiWarnings } = useAppContext();
|
|
||||||
const { isLoggedIn } = useUserContext();
|
const { isLoggedIn } = useUserContext();
|
||||||
|
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
return <Navigate to="/login" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { domain, disableContinue } = useAppContext();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams(search);
|
||||||
|
const redirectURI = searchParams.get("redirect_uri");
|
||||||
|
|
||||||
|
if (!redirectURI) {
|
||||||
|
return <Navigate to="/logout" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValidUrl(DOMPurify.sanitize(redirectURI))) {
|
||||||
|
return <Navigate to="/logout" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRedirect = () => {
|
||||||
|
setLoading(true);
|
||||||
|
window.location.href = DOMPurify.sanitize(redirectURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disableContinue) {
|
||||||
|
handleRedirect();
|
||||||
|
}
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const url = new URL(redirectURI);
|
||||||
const [showRedirectButton, setShowRedirectButton] = useState(false);
|
|
||||||
const hasRedirected = useRef(false);
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
if (!(url.hostname == domain) && !url.hostname.endsWith(`.${domain}`)) {
|
||||||
const redirectUri = searchParams.get("redirect_uri");
|
|
||||||
|
|
||||||
const { url, valid, trusted, allowedProto, httpsDowngrade } = useRedirectUri(
|
|
||||||
redirectUri,
|
|
||||||
cookieDomain,
|
|
||||||
);
|
|
||||||
|
|
||||||
const urlHref = url?.href;
|
|
||||||
|
|
||||||
const hasValidRedirect = valid && allowedProto;
|
|
||||||
const showUntrustedWarning =
|
|
||||||
hasValidRedirect && !trusted && !disableUiWarnings;
|
|
||||||
const showInsecureWarning =
|
|
||||||
hasValidRedirect && httpsDowngrade && !disableUiWarnings;
|
|
||||||
const shouldAutoRedirect =
|
|
||||||
isLoggedIn &&
|
|
||||||
hasValidRedirect &&
|
|
||||||
!showUntrustedWarning &&
|
|
||||||
!showInsecureWarning;
|
|
||||||
|
|
||||||
const redirectToTarget = useCallback(() => {
|
|
||||||
if (!urlHref || hasRedirected.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasRedirected.current = true;
|
|
||||||
window.location.assign(urlHref);
|
|
||||||
}, [urlHref]);
|
|
||||||
|
|
||||||
const handleRedirect = useCallback(() => {
|
|
||||||
setIsLoading(true);
|
|
||||||
redirectToTarget();
|
|
||||||
}, [redirectToTarget]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!shouldAutoRedirect) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto = setTimeout(() => {
|
|
||||||
redirectToTarget();
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
const reveal = setTimeout(() => {
|
|
||||||
setShowRedirectButton(true);
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(auto);
|
|
||||||
clearTimeout(reveal);
|
|
||||||
};
|
|
||||||
}, [shouldAutoRedirect, redirectToTarget]);
|
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
|
||||||
return (
|
return (
|
||||||
<Navigate
|
<Card className="min-w-xs sm:min-w-sm">
|
||||||
to={`/login${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`}
|
|
||||||
replace
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasValidRedirect) {
|
|
||||||
return <Navigate to="/logout" replace />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showUntrustedWarning) {
|
|
||||||
return (
|
|
||||||
<Card role="alert" aria-live="assertive" className="min-w-xs sm:min-w-sm">
|
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-3xl">
|
<CardTitle className="text-3xl">
|
||||||
{t("continueUntrustedRedirectTitle")}
|
{t("untrustedRedirectTitle")}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="continueUntrustedRedirectSubtitle"
|
i18nKey="untrustedRedirectSubtitle"
|
||||||
t={t}
|
t={t}
|
||||||
components={{
|
components={{
|
||||||
code: <code />,
|
code: <code />,
|
||||||
}}
|
}}
|
||||||
values={{ cookieDomain }}
|
values={{ domain }}
|
||||||
shouldUnescape={true}
|
|
||||||
/>
|
/>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardFooter className="flex flex-col items-stretch gap-2">
|
<CardFooter className="flex flex-col items-stretch gap-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleRedirect}
|
onClick={handleRedirect}
|
||||||
loading={isLoading}
|
loading={loading}
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
>
|
>
|
||||||
{t("continueTitle")}
|
{t("continueTitle")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button onClick={() => navigate("/logout")} variant="outline" disabled={loading}>
|
||||||
onClick={() => navigate("/logout")}
|
|
||||||
variant="outline"
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
{t("cancelTitle")}
|
{t("cancelTitle")}
|
||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
@@ -130,9 +84,9 @@ export const ContinuePage = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showInsecureWarning) {
|
if (url.protocol === "http:" && window.location.protocol === "https:") {
|
||||||
return (
|
return (
|
||||||
<Card role="alert" aria-live="assertive" className="min-w-xs sm:min-w-sm">
|
<Card className="min-w-xs sm:min-w-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-3xl">
|
<CardTitle className="text-3xl">
|
||||||
{t("continueInsecureRedirectTitle")}
|
{t("continueInsecureRedirectTitle")}
|
||||||
@@ -150,16 +104,12 @@ export const ContinuePage = () => {
|
|||||||
<CardFooter className="flex flex-col items-stretch gap-2">
|
<CardFooter className="flex flex-col items-stretch gap-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleRedirect}
|
onClick={handleRedirect}
|
||||||
loading={isLoading}
|
loading={loading}
|
||||||
variant="warning"
|
variant="warning"
|
||||||
>
|
>
|
||||||
{t("continueTitle")}
|
{t("continueTitle")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button onClick={() => navigate("/logout")} variant="outline" disabled={loading}>
|
||||||
onClick={() => navigate("/logout")}
|
|
||||||
variant="outline"
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
{t("cancelTitle")}
|
{t("cancelTitle")}
|
||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
@@ -170,18 +120,17 @@ export const ContinuePage = () => {
|
|||||||
return (
|
return (
|
||||||
<Card className="min-w-xs sm:min-w-sm">
|
<Card className="min-w-xs sm:min-w-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-3xl">
|
<CardTitle className="text-3xl">{t("continueTitle")}</CardTitle>
|
||||||
{t("continueRedirectingTitle")}
|
<CardDescription>{t("continueSubtitle")}</CardDescription>
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>{t("continueRedirectingSubtitle")}</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
{showRedirectButton && (
|
<CardFooter className="flex flex-col items-stretch">
|
||||||
<CardFooter className="flex flex-col items-stretch">
|
<Button
|
||||||
<Button onClick={handleRedirect}>
|
onClick={handleRedirect}
|
||||||
{t("continueRedirectManually")}
|
loading={loading}
|
||||||
</Button>
|
>
|
||||||
</CardFooter>
|
{t("continueTitle")}
|
||||||
)}
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,30 +5,15 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useLocation } from "react-router";
|
|
||||||
|
|
||||||
export const ErrorPage = () => {
|
export const ErrorPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { search } = useLocation();
|
|
||||||
const searchParams = new URLSearchParams(search);
|
|
||||||
const error = searchParams.get("error") ?? "";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="min-w-xs sm:min-w-sm">
|
<Card className="min-w-xs sm:min-w-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-3xl">{t("errorTitle")}</CardTitle>
|
<CardTitle className="text-3xl">{t("errorTitle")}</CardTitle>
|
||||||
<CardDescription className="flex flex-col gap-1.5">
|
<CardDescription>{t("errorSubtitle")}</CardDescription>
|
||||||
{error ? (
|
|
||||||
<>
|
|
||||||
<p>{t("errorSubtitleInfo")}</p>
|
|
||||||
<pre>{error}</pre>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<p>{t("errorSubtitle")}</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,82 +1,51 @@
|
|||||||
import { LoginForm } from "@/components/auth/login-form";
|
import { LoginForm } from "@/components/auth/login-form";
|
||||||
|
import { GenericIcon } from "@/components/icons/generic";
|
||||||
import { GithubIcon } from "@/components/icons/github";
|
import { GithubIcon } from "@/components/icons/github";
|
||||||
import { GoogleIcon } from "@/components/icons/google";
|
import { GoogleIcon } from "@/components/icons/google";
|
||||||
import { MicrosoftIcon } from "@/components/icons/microsoft";
|
|
||||||
import { OAuthIcon } from "@/components/icons/oauth";
|
|
||||||
import { PocketIDIcon } from "@/components/icons/pocket-id";
|
|
||||||
import { TailscaleIcon } from "@/components/icons/tailscale";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardFooter,
|
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { OAuthButton } from "@/components/ui/oauth-button";
|
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 { useIsMounted } from "@/lib/hooks/use-is-mounted";
|
||||||
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";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect } 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";
|
||||||
|
|
||||||
const iconMap: Record<string, React.ReactNode> = {
|
|
||||||
google: <GoogleIcon />,
|
|
||||||
github: <GithubIcon />,
|
|
||||||
tailscale: <TailscaleIcon />,
|
|
||||||
microsoft: <MicrosoftIcon />,
|
|
||||||
pocketid: <PocketIDIcon />,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LoginPage = () => {
|
export const LoginPage = () => {
|
||||||
const { isLoggedIn } = useUserContext();
|
const { isLoggedIn } = useUserContext();
|
||||||
const { providers, title, oauthAutoRedirect } = useAppContext();
|
|
||||||
|
if (isLoggedIn) {
|
||||||
|
return <Navigate to="/logout" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { configuredProviders, title, oauthAutoRedirect, genericName } = useAppContext();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const isMounted = useIsMounted();
|
||||||
const [showRedirectButton, setShowRedirectButton] = useState(false);
|
|
||||||
|
|
||||||
const hasAutoRedirectedRef = useRef(false);
|
|
||||||
|
|
||||||
const redirectTimer = useRef<number | null>(null);
|
|
||||||
const redirectButtonTimer = useRef<number | null>(null);
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
const {
|
const redirectUri = searchParams.get("redirect_uri");
|
||||||
values: props,
|
|
||||||
isOidc,
|
|
||||||
compiled: compiledOIDCParams,
|
|
||||||
} = useOIDCParams(searchParams);
|
|
||||||
|
|
||||||
const [isOauthAutoRedirect, setIsOauthAutoRedirect] = useState(
|
const oauthConfigured =
|
||||||
providers.find((provider) => provider.id === oauthAutoRedirect) !==
|
configuredProviders.filter((provider) => provider !== "username").length >
|
||||||
undefined && props.redirect_uri,
|
0;
|
||||||
);
|
const userAuthConfigured = configuredProviders.includes("username");
|
||||||
|
|
||||||
const oauthProviders = providers.filter(
|
const oauthMutation = useMutation({
|
||||||
(provider) => provider.id !== "local" && provider.id !== "ldap",
|
|
||||||
);
|
|
||||||
const userAuthConfigured =
|
|
||||||
providers.find(
|
|
||||||
(provider) => provider.id === "local" || provider.id === "ldap",
|
|
||||||
) !== undefined;
|
|
||||||
|
|
||||||
const {
|
|
||||||
mutate: oauthMutate,
|
|
||||||
data: oauthData,
|
|
||||||
isPending: oauthIsPending,
|
|
||||||
variables: oauthVariables,
|
|
||||||
} = useMutation({
|
|
||||||
mutationFn: (provider: string) =>
|
mutationFn: (provider: string) =>
|
||||||
axios.get(
|
axios.get(
|
||||||
`/api/oauth/url/${provider}${props.redirect_uri ? `?redirect_uri=${encodeURIComponent(props.redirect_uri)}` : ""}`,
|
`/api/oauth/url/${provider}?redirect_uri=${encodeURIComponent(redirectUri ?? "")}`,
|
||||||
),
|
),
|
||||||
mutationKey: ["oauth"],
|
mutationKey: ["oauth"],
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
@@ -84,31 +53,24 @@ export const LoginPage = () => {
|
|||||||
description: t("loginOauthSuccessSubtitle"),
|
description: t("loginOauthSuccessSubtitle"),
|
||||||
});
|
});
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.replace(data.data.url);
|
window.location.href = data.data.url;
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
if (isOauthAutoRedirect) {
|
|
||||||
redirectButtonTimer.current = window.setTimeout(() => {
|
|
||||||
setShowRedirectButton(true);
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
setIsOauthAutoRedirect(false);
|
|
||||||
toast.error(t("loginOauthFailTitle"), {
|
toast.error(t("loginOauthFailTitle"), {
|
||||||
description: t("loginOauthFailSubtitle"),
|
description: t("loginOauthFailSubtitle"),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: loginMutate, isPending: loginIsPending } = useMutation({
|
const loginMutation = useMutation({
|
||||||
mutationFn: (values: LoginSchema) => axios.post("/api/user/login", values),
|
mutationFn: (values: LoginSchema) => axios.post("/api/login", values),
|
||||||
mutationKey: ["login"],
|
mutationKey: ["login"],
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data.data.totpPending) {
|
if (data.data.totpPending) {
|
||||||
window.location.replace(
|
window.location.replace(
|
||||||
`/totp${props.redirect_uri ? `?redirect_uri=${encodeURIComponent(props.redirect_uri)}` : ""}`,
|
`/totp?redirect_uri=${encodeURIComponent(redirectUri ?? "")}`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -117,13 +79,9 @@ export const LoginPage = () => {
|
|||||||
description: t("loginSuccessSubtitle"),
|
description: t("loginSuccessSubtitle"),
|
||||||
});
|
});
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (isOidc) {
|
|
||||||
window.location.replace(`/authorize?${compiledOIDCParams}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.location.replace(
|
window.location.replace(
|
||||||
`/continue${props.redirect_uri ? `?redirect_uri=${encodeURIComponent(props.redirect_uri)}` : ""}`,
|
`/continue?redirect_uri=${encodeURIComponent(redirectUri ?? "")}`,
|
||||||
);
|
);
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
@@ -138,123 +96,72 @@ export const LoginPage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (isMounted()) {
|
||||||
!isLoggedIn &&
|
if (
|
||||||
isOauthAutoRedirect &&
|
oauthConfigured &&
|
||||||
!hasAutoRedirectedRef.current &&
|
configuredProviders.includes(oauthAutoRedirect) &&
|
||||||
props.redirect_uri
|
redirectUri
|
||||||
) {
|
) {
|
||||||
hasAutoRedirectedRef.current = true;
|
oauthMutation.mutate(oauthAutoRedirect);
|
||||||
oauthMutate(oauthAutoRedirect);
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, []);
|
||||||
isLoggedIn,
|
|
||||||
oauthMutate,
|
|
||||||
hasAutoRedirectedRef,
|
|
||||||
oauthAutoRedirect,
|
|
||||||
isOauthAutoRedirect,
|
|
||||||
props.redirect_uri,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (redirectTimer.current) {
|
|
||||||
clearTimeout(redirectTimer.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redirectButtonTimer.current) {
|
|
||||||
clearTimeout(redirectButtonTimer.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [redirectTimer, redirectButtonTimer]);
|
|
||||||
|
|
||||||
if (isLoggedIn && isOidc) {
|
|
||||||
return <Navigate to={`/authorize?${compiledOIDCParams}`} replace />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoggedIn && props.redirect_uri !== "") {
|
|
||||||
return (
|
|
||||||
<Navigate
|
|
||||||
to={`/continue${props.redirect_uri ? `?redirect_uri=${encodeURIComponent(props.redirect_uri)}` : ""}`}
|
|
||||||
replace
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoggedIn) {
|
|
||||||
return <Navigate to="/logout" replace />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isOauthAutoRedirect) {
|
|
||||||
return (
|
|
||||||
<Card className="min-w-xs sm:min-w-sm">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-3xl">
|
|
||||||
{t("loginOauthAutoRedirectTitle")}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
{t("loginOauthAutoRedirectSubtitle")}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
{showRedirectButton && (
|
|
||||||
<CardFooter className="flex flex-col items-stretch">
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (oauthData?.data.url) {
|
|
||||||
window.location.replace(oauthData.data.url);
|
|
||||||
} else {
|
|
||||||
setIsOauthAutoRedirect(false);
|
|
||||||
toast.error(t("loginOauthFailTitle"), {
|
|
||||||
description: t("loginOauthFailSubtitle"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("loginOauthAutoRedirectButton")}
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Card className="min-w-xs sm:min-w-sm">
|
<Card className="min-w-xs sm:min-w-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-center text-3xl">{title}</CardTitle>
|
<CardTitle className="text-center text-3xl">{title}</CardTitle>
|
||||||
{providers.length > 0 && (
|
{configuredProviders.length > 0 && (
|
||||||
<CardDescription className="text-center">
|
<CardDescription className="text-center">
|
||||||
{oauthProviders.length !== 0
|
{oauthConfigured ? t("loginTitle") : t("loginTitleSimple")}
|
||||||
? t("loginTitle")
|
|
||||||
: t("loginTitleSimple")}
|
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
)}
|
)}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4">
|
<CardContent className="flex flex-col gap-4">
|
||||||
{oauthProviders.length !== 0 && (
|
{oauthConfigured && (
|
||||||
<div className="flex flex-col gap-2 items-center justify-center">
|
<div className="flex flex-col gap-2 items-center justify-center">
|
||||||
{oauthProviders.map((provider) => (
|
{configuredProviders.includes("google") && (
|
||||||
<OAuthButton
|
<OAuthButton
|
||||||
key={provider.id}
|
title="Google"
|
||||||
title={provider.name}
|
icon={<GoogleIcon />}
|
||||||
icon={iconMap[provider.id] ?? <OAuthIcon />}
|
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={() => oauthMutate(provider.id)}
|
onClick={() => oauthMutation.mutate("google")}
|
||||||
loading={oauthIsPending && oauthVariables === provider.id}
|
loading={oauthMutation.isPending && oauthMutation.variables === "google"}
|
||||||
disabled={oauthIsPending || loginIsPending}
|
disabled={oauthMutation.isPending || loginMutation.isPending}
|
||||||
/>
|
/>
|
||||||
))}
|
)}
|
||||||
|
{configuredProviders.includes("github") && (
|
||||||
|
<OAuthButton
|
||||||
|
title="Github"
|
||||||
|
icon={<GithubIcon />}
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => oauthMutation.mutate("github")}
|
||||||
|
loading={oauthMutation.isPending && oauthMutation.variables === "github"}
|
||||||
|
disabled={oauthMutation.isPending || loginMutation.isPending}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{configuredProviders.includes("generic") && (
|
||||||
|
<OAuthButton
|
||||||
|
title={genericName}
|
||||||
|
icon={<GenericIcon />}
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => oauthMutation.mutate("generic")}
|
||||||
|
loading={oauthMutation.isPending && oauthMutation.variables === "generic"}
|
||||||
|
disabled={oauthMutation.isPending || loginMutation.isPending}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{userAuthConfigured && oauthProviders.length !== 0 && (
|
{userAuthConfigured && oauthConfigured && (
|
||||||
<SeperatorWithChildren>{t("loginDivider")}</SeperatorWithChildren>
|
<SeperatorWithChildren>{t("loginDivider")}</SeperatorWithChildren>
|
||||||
)}
|
)}
|
||||||
{userAuthConfigured && (
|
{userAuthConfigured && (
|
||||||
<LoginForm
|
<LoginForm
|
||||||
onSubmit={(values) => loginMutate(values)}
|
onSubmit={(values) => loginMutation.mutate(values)}
|
||||||
loading={loginIsPending || oauthIsPending}
|
loading={loginMutation.isPending || oauthMutation.isPending}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{providers.length == 0 && (
|
{configuredProviders.length == 0 && (
|
||||||
<p className="text-center text-red-600 max-w-sm">
|
<p className="text-center text-red-600 max-w-sm">
|
||||||
{t("failedToFetchProvidersTitle")}
|
{t("failedToFetchProvidersTitle")}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -6,29 +6,34 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
|
import { useAppContext } from "@/context/app-context";
|
||||||
import { useUserContext } from "@/context/user-context";
|
import { useUserContext } from "@/context/user-context";
|
||||||
|
import { capitalize } from "@/lib/utils";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
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";
|
||||||
|
|
||||||
export const LogoutPage = () => {
|
export const LogoutPage = () => {
|
||||||
const { provider, username, isLoggedIn, email, oauthName } = useUserContext();
|
const { provider, username, isLoggedIn, email } = useUserContext();
|
||||||
|
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
return <Navigate to="/login" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { genericName } = useAppContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const redirectTimer = useRef<number | null>(null);
|
|
||||||
|
|
||||||
const logoutMutation = useMutation({
|
const logoutMutation = useMutation({
|
||||||
mutationFn: () => axios.post("/api/user/logout"),
|
mutationFn: () => axios.post("/api/logout"),
|
||||||
mutationKey: ["logout"],
|
mutationKey: ["logout"],
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t("logoutSuccessTitle"), {
|
toast.success(t("logoutSuccessTitle"), {
|
||||||
description: t("logoutSuccessSubtitle"),
|
description: t("logoutSuccessSubtitle"),
|
||||||
});
|
});
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
setTimeout(async () => {
|
||||||
window.location.replace("/login");
|
window.location.replace("/login");
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
@@ -39,24 +44,12 @@ export const LogoutPage = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (redirectTimer.current) {
|
|
||||||
clearTimeout(redirectTimer.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [redirectTimer]);
|
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
|
||||||
return <Navigate to="/login" replace />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="min-w-xs sm:min-w-sm">
|
<Card className="min-w-xs sm:min-w-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-3xl">{t("logoutTitle")}</CardTitle>
|
<CardTitle className="text-3xl">{t("logoutTitle")}</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
{provider !== "local" && provider !== "ldap" ? (
|
{provider !== "username" ? (
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="logoutOauthSubtitle"
|
i18nKey="logoutOauthSubtitle"
|
||||||
t={t}
|
t={t}
|
||||||
@@ -65,9 +58,9 @@ export const LogoutPage = () => {
|
|||||||
}}
|
}}
|
||||||
values={{
|
values={{
|
||||||
username: email,
|
username: email,
|
||||||
provider: oauthName,
|
provider:
|
||||||
|
provider === "generic" ? genericName : capitalize(provider),
|
||||||
}}
|
}}
|
||||||
shouldUnescape={true}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Trans
|
<Trans
|
||||||
@@ -79,7 +72,6 @@ export const LogoutPage = () => {
|
|||||||
values={{
|
values={{
|
||||||
username,
|
username,
|
||||||
}}
|
}}
|
||||||
shouldUnescape={true}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
|||||||
@@ -12,43 +12,36 @@ import { useUserContext } from "@/context/user-context";
|
|||||||
import { TotpSchema } from "@/schemas/totp-schema";
|
import { TotpSchema } from "@/schemas/totp-schema";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useEffect, useId, useRef } from "react";
|
import { useId } 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 { useOIDCParams } from "@/lib/hooks/oidc";
|
|
||||||
|
|
||||||
export const TotpPage = () => {
|
export const TotpPage = () => {
|
||||||
const { totpPending } = useUserContext();
|
const { totpPending } = useUserContext();
|
||||||
|
|
||||||
|
if (!totpPending) {
|
||||||
|
return <Navigate to="/" />;
|
||||||
|
}
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const formId = useId();
|
const formId = useId();
|
||||||
|
|
||||||
const redirectTimer = useRef<number | null>(null);
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
const {
|
const redirectUri = searchParams.get("redirect_uri");
|
||||||
values: props,
|
|
||||||
isOidc,
|
|
||||||
compiled: compiledOIDCParams,
|
|
||||||
} = useOIDCParams(searchParams);
|
|
||||||
|
|
||||||
const totpMutation = useMutation({
|
const totpMutation = useMutation({
|
||||||
mutationFn: (values: TotpSchema) => axios.post("/api/user/totp", values),
|
mutationFn: (values: TotpSchema) => axios.post("/api/totp", values),
|
||||||
mutationKey: ["totp"],
|
mutationKey: ["totp"],
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t("totpSuccessTitle"), {
|
toast.success(t("totpSuccessTitle"), {
|
||||||
description: t("totpSuccessSubtitle"),
|
description: t("totpSuccessSubtitle"),
|
||||||
});
|
});
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (isOidc) {
|
|
||||||
window.location.replace(`/authorize?${compiledOIDCParams}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.replace(
|
window.location.replace(
|
||||||
`/continue${props.redirect_uri ? `?redirect_uri=${encodeURIComponent(props.redirect_uri)}` : ""}`,
|
`/continue?redirect_uri=${encodeURIComponent(redirectUri ?? "")}`,
|
||||||
);
|
);
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
@@ -59,18 +52,6 @@ export const TotpPage = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (redirectTimer.current) {
|
|
||||||
clearTimeout(redirectTimer.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [redirectTimer]);
|
|
||||||
|
|
||||||
if (!totpPending) {
|
|
||||||
return <Navigate to="/" replace />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="min-w-xs sm:min-w-sm">
|
<Card className="min-w-xs sm:min-w-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -81,6 +62,7 @@ export const TotpPage = () => {
|
|||||||
<TotpForm
|
<TotpForm
|
||||||
formId={formId}
|
formId={formId}
|
||||||
onSubmit={(values) => totpMutation.mutate(values)}
|
onSubmit={(values) => totpMutation.mutate(values)}
|
||||||
|
loading={totpMutation.isPending}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className="flex flex-col items-stretch">
|
<CardFooter className="flex flex-col items-stretch">
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ import { Navigate, useLocation, useNavigate } from "react-router";
|
|||||||
|
|
||||||
export const UnauthorizedPage = () => {
|
export const UnauthorizedPage = () => {
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const { t } = useTranslation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
const username = searchParams.get("username");
|
const username = searchParams.get("username");
|
||||||
@@ -23,15 +19,19 @@ export const UnauthorizedPage = () => {
|
|||||||
const groupErr = searchParams.get("groupErr");
|
const groupErr = searchParams.get("groupErr");
|
||||||
const ip = searchParams.get("ip");
|
const ip = searchParams.get("ip");
|
||||||
|
|
||||||
|
if (!username && !ip) {
|
||||||
|
return <Navigate to="/" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const handleRedirect = () => {
|
const handleRedirect = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!username && !ip) {
|
|
||||||
return <Navigate to="/" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
let i18nKey = "unauthorizedLoginSubtitle";
|
let i18nKey = "unauthorizedLoginSubtitle";
|
||||||
|
|
||||||
if (resource) {
|
if (resource) {
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const providerSchema = z.object({
|
|
||||||
id: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
oauth: z.boolean(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const appContextSchema = z.object({
|
export const appContextSchema = z.object({
|
||||||
providers: z.array(providerSchema),
|
configuredProviders: z.array(z.string()),
|
||||||
|
disableContinue: z.boolean(),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
appUrl: z.string(),
|
genericName: z.string(),
|
||||||
cookieDomain: z.string(),
|
domain: z.string(),
|
||||||
forgotPasswordMessage: z.string(),
|
forgotPasswordMessage: z.string(),
|
||||||
|
oauthAutoRedirect: z.enum(["none", "github", "google", "generic"]),
|
||||||
backgroundImage: z.string(),
|
backgroundImage: z.string(),
|
||||||
oauthAutoRedirect: z.string(),
|
|
||||||
disableUiWarnings: z.boolean(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppContextSchema = z.infer<typeof appContextSchema>;
|
export type AppContextSchema = z.infer<typeof appContextSchema>;
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const getOidcClientInfoSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
});
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user