Compare commits

..

1 Commits

Author SHA1 Message Date
Stavros
1b145fd531 chore: bump version 2025-02-08 12:43:30 +02:00
227 changed files with 5754 additions and 13499 deletions

View File

@@ -1,22 +0,0 @@
PORT=3000
ADDRESS=0.0.0.0
APP_URL=http://localhost:3000
USERS=your_user_password_hash
USERS_FILE=users_file
SECURE_COOKIE=false
OAUTH_WHITELIST=
GENERIC_NAME=My OAuth
SESSION_EXPIRY=7200
LOGIN_TIMEOUT=300
LOGIN_MAX_RETRIES=5
LOG_LEVEL=debug
APP_TITLE=Tinyauth SSO
FORGOT_PASSWORD_MESSAGE=Some message about resetting the password
OAUTH_AUTO_REDIRECT=none
BACKGROUND_IMAGE=some_image_url
GENERIC_SKIP_SSL=false
RESOURCES_DIR=/data/resources
DATABASE_PATH=/data/tinyauth.db
DISABLE_ANALYTICS=false
DISABLE_RESOURCES=false
TRUSTED_PROXIES=

View File

@@ -1,37 +0,0 @@
---
name: Bug report
about: Create a report to help improve Tinyauth
title: "[BUG]"
labels: bug
assignees: steveiliop56
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Logs**
Please include the Tinyauth logs below, make sure to not include sensitive info.
**Device (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Tinyauth [e.g. v2.1.1]
- Docker [e.g. 27.3.1]
**
**Additional context**
Add any other context about the problem here.

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE]"
labels: enhancement
assignees: steveiliop56
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,26 +0,0 @@
version: 2
updates:
- package-ecosystem: "bun"
directory: "/frontend"
groups:
minor-patch:
update-types:
- "patch"
- "minor"
schedule:
interval: "daily"
- package-ecosystem: "gomod"
directory: "/"
groups:
minor-patch:
update-types:
- "patch"
- "minor"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "daily"

58
.github/workflows/alpha-release.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Alpha Release
on:
workflow_dispatch:
inputs:
alpha:
description: "Alpha version (e.g. 1, 2, 3)"
required: true
jobs:
get-tag:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.name }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get tag
id: tag
run: echo "name=$(cat internal/assets/version)-alpha.${{ github.event.inputs.alpha }}" >> $GITHUB_OUTPUT
build-docker:
needs: get-tag
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/arm64, linux/amd64
tags: ghcr.io/${{ github.repository_owner }}/tinyauth:${{ needs.get-tag.outputs.tag }}
alpha-release:
needs: [get-tag, build-docker]
runs-on: ubuntu-latest
steps:
- name: Create alpha release
uses: softprops/action-gh-release@v2
with:
prerelease: true
tag_name: ${{ needs.get-tag.outputs.tag }}

58
.github/workflows/beta-release.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Beta Release
on:
workflow_dispatch:
inputs:
alpha:
description: "Beta version (e.g. 1, 2, 3)"
required: true
jobs:
get-tag:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.name }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get tag
id: tag
run: echo "name=$(cat internal/assets/version)-beta.${{ github.event.inputs.alpha }}" >> $GITHUB_OUTPUT
build-docker:
needs: get-tag
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/arm64, linux/amd64
tags: ghcr.io/${{ github.repository_owner }}/tinyauth:${{ needs.get-tag.outputs.tag }}
beta-release:
needs: [get-tag, build-docker]
runs-on: ubuntu-latest
steps:
- name: Create beta release
uses: softprops/action-gh-release@v2
with:
prerelease: true
tag_name: ${{ needs.get-tag.outputs.tag }}

View File

@@ -1,47 +0,0 @@
name: Tinyauth CI
on:
push:
branches:
- main
pull_request:
jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup bun
uses: oven-sh/setup-bun@v2
- name: Setup go
uses: actions/setup-go@v5
with:
go-version: "^1.23.2"
- name: Install frontend dependencies
run: |
cd frontend
bun install --frozen-lockfile
- name: Set version
run: |
echo testing > internal/assets/version
- name: Build frontend
run: |
cd frontend
bun run build
- name: Copy frontend
run: |
cp -r frontend/dist internal/assets/dist
- name: Run tests
run: go test -coverprofile=coverage.txt -v ./...
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -1,465 +0,0 @@
name: Nightly Release
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
create-release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Delete old release
run: gh release delete --cleanup-tag --yes nightly || echo release not found
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OWNER: ${{ github.repository_owner }}
REPO: ${{ github.event.repository.name }}
- name: Create release
uses: softprops/action-gh-release@v2
with:
prerelease: true
tag_name: nightly
generate-metadata:
runs-on: ubuntu-latest
needs: create-release
outputs:
VERSION: ${{ steps.metadata.outputs.VERSION }}
COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: nightly
- name: Generate metadata
id: metadata
run: |
echo "VERSION=nightly" >> "$GITHUB_OUTPUT"
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT"
binary-build:
runs-on: ubuntu-latest
needs:
- create-release
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: nightly
- name: Install bun
uses: oven-sh/setup-bun@v2
- name: Install go
uses: actions/setup-go@v5
with:
go-version: "^1.23.2"
- name: Install frontend dependencies
run: |
cd frontend
bun install --frozen-lockfile
- name: Install backend dependencies
run: |
go mod download
- name: Build frontend
run: |
cd frontend
bun run build
- name: Build
run: |
cp -r frontend/dist internal/assets/dist
go build -ldflags "-s -w -X tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64
env:
CGO_ENABLED: 0
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tinyauth-amd64
path: tinyauth-amd64
binary-build-arm:
runs-on: ubuntu-24.04-arm
needs:
- create-release
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: nightly
- name: Install bun
uses: oven-sh/setup-bun@v2
- name: Install go
uses: actions/setup-go@v5
with:
go-version: "^1.23.2"
- name: Install frontend dependencies
run: |
cd frontend
bun install --frozen-lockfile
- name: Install backend dependencies
run: |
go mod download
- name: Build frontend
run: |
cd frontend
bun run build
- name: Build
run: |
cp -r frontend/dist internal/assets/dist
go build -ldflags "-s -w -X tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64
env:
CGO_ENABLED: 0
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tinyauth-arm64
path: tinyauth-arm64
image-build:
runs-on: ubuntu-latest
needs:
- create-release
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: nightly
- 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
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-linux-amd64
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
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: 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:
runs-on: ubuntu-24.04-arm
needs:
- create-release
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: nightly
- 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
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-linux-arm64
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
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: 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:
runs-on: ubuntu-latest
needs:
- image-build
- image-build-arm
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
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
- 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
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:
runs-on: ubuntu-latest
needs:
- binary-build
- binary-build-arm
steps:
- uses: actions/download-artifact@v4
with:
pattern: tinyauth-*
path: binaries
merge-multiple: true
- name: Release
uses: softprops/action-gh-release@v2
with:
files: binaries/*
tag_name: nightly

View File

@@ -1,129 +1,32 @@
name: Release
on:
workflow_dispatch:
push:
tags:
- "v*"
jobs:
generate-metadata:
get-tag:
runs-on: ubuntu-latest
outputs:
VERSION: ${{ steps.metadata.outputs.VERSION }}
COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
tag: ${{ steps.tag.outputs.name }}
steps:
- name: Checkout
- name: Checkout code
uses: actions/checkout@v4
with:
ref: nightly
- name: Generate metadata
id: metadata
run: |
echo "VERSION=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT"
- name: Get tag
id: tag
run: echo "name=$(cat internal/assets/version)" >> $GITHUB_OUTPUT
binary-build:
build-docker:
needs: get-tag
runs-on: ubuntu-latest
needs:
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install bun
uses: oven-sh/setup-bun@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Install go
uses: actions/setup-go@v5
with:
go-version: "^1.23.2"
- name: Install frontend dependencies
run: |
cd frontend
bun install --frozen-lockfile
- name: Install backend dependencies
run: |
go mod download
- name: Build frontend
run: |
cd frontend
bun run build
- name: Build
run: |
cp -r frontend/dist internal/assets/dist
go build -ldflags "-s -w -X tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64
env:
CGO_ENABLED: 0
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tinyauth-amd64
path: tinyauth-amd64
binary-build-arm:
runs-on: ubuntu-24.04-arm
needs:
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install bun
uses: oven-sh/setup-bun@v2
- name: Install go
uses: actions/setup-go@v5
with:
go-version: "^1.23.2"
- name: Install frontend dependencies
run: |
cd frontend
bun install --frozen-lockfile
- name: Install backend dependencies
run: |
go mod download
- name: Build frontend
run: |
cd frontend
bun run build
- name: Build
run: |
cp -r frontend/dist internal/assets/dist
go build -ldflags "-s -w -X tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64
env:
CGO_ENABLED: 0
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tinyauth-arm64
path: tinyauth-arm64
image-build:
runs-on: ubuntu-latest
needs:
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
@@ -132,302 +35,21 @@ jobs:
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
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 }}
context: .
push: true
platforms: linux/arm64, linux/amd64
tags: ghcr.io/${{ github.repository_owner }}/tinyauth:${{ needs.get-tag.outputs.tag }}, ghcr.io/${{ github.repository_owner }}/tinyauth:latest
- 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-linux-amd64
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
image-build-distroless:
release:
needs: [get-tag, build-docker]
runs-on: ubuntu-latest
needs:
- generate-metadata
- image-build
steps:
- name: Checkout
uses: actions/checkout@v4
- 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:
runs-on: ubuntu-24.04-arm
needs:
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
- 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
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-linux-arm64
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
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: 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:
runs-on: ubuntu-latest
needs:
- image-build
- image-build-arm
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
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: |
prefix=v,onlatest=false
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
- 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
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:
runs-on: ubuntu-latest
needs:
- binary-build
- binary-build-arm
steps:
- uses: actions/download-artifact@v4
with:
pattern: tinyauth-*
path: binaries
merge-multiple: true
- name: Release
- name: Create release
uses: softprops/action-gh-release@v2
with:
files: binaries/*
prerelease: false
make_latest: false
tag_name: ${{ needs.get-tag.outputs.tag }}

View File

@@ -1,31 +0,0 @@
name: Generate Sponsors List
on:
workflow_dispatch:
jobs:
generate-sponsors:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Generate Sponsors
uses: JamesIves/github-sponsors-readme-action@v1
with:
token: ${{ secrets.SPONSORS_GENERATOR_PAT }}
active-only: false
file: "README.md"
template: '<a href="https://github.com/{{{ login }}}"><img src="{{{ avatarUrl }}}" width="64px" alt="User avatar: {{{ login }}}" /></a>&nbsp;&nbsp;'
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: |
docs: regenerate readme sponsors list
committer: GitHub <noreply@github.com>
author: GitHub <noreply@github.com>
branch: docs/update-readme
title: |
docs: regenerate readme sponsors list
labels: bot

View File

@@ -1,20 +0,0 @@
name: Close stale issues and PRs
on:
schedule:
- cron: 0 10 * * *
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
days-before-stale: 30
stale-pr-message: This PR has been inactive for 30 days and will be marked as stale.
stale-issue-message: This issue has been inactive for 30 days and will be marked as stale.
close-issue-message: Closed for inactivity.
close-pr-message: Closed for inactivity.
stale-issue-label: stale
stale-pr-label: stale
exempt-issue-labels: pinned
exempt-pr-labels: pinned

20
.gitignore vendored
View File

@@ -11,19 +11,11 @@ docker-compose.test*
users.txt
# secret test file
secret*
secret.txt
secret_oauth.txt
# vscode
.vscode
# apple stuff
.DS_Store
# env
.env
# tmp directory
tmp
# version files
internal/assets/version
# data directory
data
.DS_Store

15
.vscode/launch.json vendored
View File

@@ -1,15 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Connect to server",
"type": "go",
"request": "attach",
"mode": "remote",
"remotePath": "/tinyauth",
"port": 4000,
"host": "127.0.0.1",
"debugAdapter": "legacy"
}
]
}

View File

@@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -1,59 +0,0 @@
# Contributing
Contributing is relatively easy, you just need to follow the steps below and you will be up and running with a development server in less than five minutes.
## Requirements
- Bun
- Golang v1.23.2 and above
- Git
- Docker
## Cloning the repository
You firstly need to clone the repository with:
```sh
git clone https://github.com/steveiliop56/tinyauth
cd tinyauth
```
## 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:
```sh
go mod tidy
```
You also need to download the frontend dependencies, this can be done like so:
```sh
cd frontend/
bun install
```
## 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.
## 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:
```
*.dev.example.com -> 127.0.0.1
dev.example.com -> 127.0.0.1
```
> [!TIP]
> 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:
```sh
docker compose -f docker-compose.dev.yml up --build
```
> [!NOTE]
> I recommend copying the example `docker-compose.dev.yml` into a `docker-compose.test.yml` file, so as you don't accidentally commit any sensitive information.

View File

@@ -1,30 +1,27 @@
# Site builder
FROM oven/bun:1.3.0-alpine AS frontend-builder
FROM oven/bun:1.1.45-alpine AS site-builder
WORKDIR /frontend
WORKDIR /site
COPY ./frontend/package.json ./
COPY ./frontend/bun.lock ./
COPY ./site/package.json ./
COPY ./site/bun.lockb ./
RUN bun install --frozen-lockfile
RUN bun install
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 ./
COPY ./site/public ./public
COPY ./site/src ./src
COPY ./site/eslint.config.js ./
COPY ./site/index.html ./
COPY ./site/tsconfig.json ./
COPY ./site/tsconfig.app.json ./
COPY ./site/tsconfig.node.json ./
COPY ./site/vite.config.ts ./
COPY ./site/postcss.config.cjs ./
RUN bun run build
# Builder
FROM golang:1.25-alpine3.21 AS builder
ARG VERSION
ARG COMMIT_HASH
ARG BUILD_TIMESTAMP
FROM golang:1.23-alpine3.21 AS builder
WORKDIR /tinyauth
@@ -36,27 +33,17 @@ RUN go mod download
COPY ./main.go ./
COPY ./cmd ./cmd
COPY ./internal ./internal
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
COPY --from=site-builder /site/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/config.Version=${VERSION} -X tinyauth/internal/config.CommitHash=${COMMIT_HASH} -X tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}"
# Runner
FROM alpine:3.22 AS runner
FROM alpine:3.21 AS runner
WORKDIR /tinyauth
COPY --from=builder /tinyauth/tinyauth ./
RUN mkdir -p /data
EXPOSE 3000
VOLUME ["/data"]
ENV GIN_MODE=release
ENV PATH=$PATH:/tinyauth
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 CMD ["tinyauth", "healthcheck"]
ENTRYPOINT ["tinyauth"]
ENTRYPOINT ["./tinyauth"]

View File

@@ -1,19 +0,0 @@
FROM golang:1.25-alpine3.21
WORKDIR /tinyauth
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY ./cmd ./cmd
COPY ./internal ./internal
COPY ./main.go ./
COPY ./air.toml ./
RUN go install github.com/air-verse/air@v1.61.7
EXPOSE 3000
ENTRYPOINT ["air", "-c", "air.toml"]

View File

@@ -1,65 +0,0 @@
# Site builder
FROM oven/bun:1.3.0-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 go.mod ./
COPY go.sum ./
RUN go mod download
COPY ./main.go ./
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 tinyauth/internal/config.Version=${VERSION} -X tinyauth/internal/config.CommitHash=${COMMIT_HASH} -X tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}"
# 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 GIN_MODE=release
ENV PATH=$PATH:/tinyauth
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 CMD ["tinyauth", "healthcheck"]
ENTRYPOINT ["tinyauth"]

View File

@@ -1,2 +0,0 @@
github: steveiliop56
buy_me_a_coffee: steveiliop56

View File

@@ -1,67 +1,50 @@
<div align="center">
<img alt="Tinyauth" title="Tinyauth" width="96" src="assets/logo-rounded.png">
<img alt="Tinyauth" title="Tinyauth" width="256" src="site/public/logo.png">
<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 align="center">
<img alt="License" src="https://img.shields.io/github/license/steveiliop56/tinyauth">
<img alt="Release" src="https://img.shields.io/github/v/release/steveiliop56/tinyauth">
<img alt="Commit activity" src="https://img.shields.io/github/commit-activity/w/steveiliop56/tinyauth">
<img alt="Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/steveiliop56/tinyauth/release.yml">
<img alt="Issues" src="https://img.shields.io/github/issues/steveiliop56/tinyauth">
<img alt="Tinyauth CI" src="https://github.com/steveiliop56/tinyauth/actions/workflows/ci.yml/badge.svg">
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/tinyauth"><img src="https://badges.crowdin.net/tinyauth/localized.svg"></a>
</div>
<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.
![Screenshot](assets/screenshot.png)
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. It is made for traefik but it can be extended to work with all reverse proxies like caddy and nginx.
> [!WARNING]
> Tinyauth is in active development and configuration may change often. Please make sure to carefully read the release notes before updating.
## 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.
## Demo
If you are still not sure if Tinyauth suits your needs you can try out the [demo](https://demo.tinyauth.app). The default username is `user` and the default password is `password`.
## Documentation
You can find documentation and guides on all of the available configuration of Tinyauth in the [website](https://tinyauth.app).
> [!NOTE]
> Tinyauth is intended for homelab use and it is not made for production use cases. If you are looking for something production ready please use [authentik](https://goauthentik.io).
## 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!
I just made a Discord server for Tinyauth! It is not only for Tinyauth but general self-hosting because I just like chatting with people! The link is [here](https://discord.gg/gWpzrksk), see you there!
## Getting Started
You can easily get started with tinyauth by following the guide on the [documentation](https://tinyauth.doesmycode.work/docs/getting-started.html). There is also an available [docker compose file](./docker-compose.example.yml) that has traefik, nginx and tinyauth to demonstrate its capabilities.
## Documentation
You can find documentation and guides on all available configuration of tinyauth [here](https://tinyauth.doesmycode.work).
## Contributing
All contributions to the codebase are welcome! If you have any free time feel free to pick up an [issue](https://github.com/steveiliop56/tinyauth/issues) or add your own missing features. Make sure to check out the [contributing guide](./CONTRIBUTING.md) for instructions on how to get the development server up and running.
## Localization
If you would like to help translate Tinyauth into more languages, visit the [Crowdin](https://crowdin.com/project/tinyauth) page.
All contributions to the codebase are welcome! If you have any recommendations on how to improve security or find a security issue in tinyauth please open an issue or pull request so it can be fixed as soon as possible!
## License
Tinyauth is licensed under the GNU General Public License v3.0. TL;DR — You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions. For more information about the license check the [license](./LICENSE) file.
## Sponsors
A big thank you to the following people for providing me with more coffee:
<!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https:&#x2F;&#x2F;github.com&#x2F;erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a>&nbsp;&nbsp;<a href="https://github.com/nicotsx"><img src="https:&#x2F;&#x2F;github.com&#x2F;nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a>&nbsp;&nbsp;<a href="https://github.com/SimpleHomelab"><img src="https:&#x2F;&#x2F;github.com&#x2F;SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a>&nbsp;&nbsp;<a href="https://github.com/jmadden91"><img src="https:&#x2F;&#x2F;github.com&#x2F;jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a>&nbsp;&nbsp;<a href="https://github.com/tribor"><img src="https:&#x2F;&#x2F;github.com&#x2F;tribor.png" width="64px" alt="User avatar: tribor" /></a>&nbsp;&nbsp;<a href="https://github.com/eliasbenb"><img src="https:&#x2F;&#x2F;github.com&#x2F;eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a>&nbsp;&nbsp;<a href="https://github.com/afunworm"><img src="https:&#x2F;&#x2F;github.com&#x2F;afunworm.png" width="64px" alt="User avatar: afunworm" /></a>&nbsp;&nbsp;<a href="https://github.com/chip-well"><img src="https:&#x2F;&#x2F;github.com&#x2F;chip-well.png" width="64px" alt="User avatar: chip-well" /></a>&nbsp;&nbsp;<a href="https://github.com/Lancelot-Enguerrand"><img src="https:&#x2F;&#x2F;github.com&#x2F;Lancelot-Enguerrand.png" width="64px" alt="User avatar: Lancelot-Enguerrand" /></a>&nbsp;&nbsp;<!-- sponsors -->
## Acknowledgements
- **Freepik** for providing the police hat and badge.
Credits for the logo of this app go to:
- **Freepik** for providing the police hat and logo.
- **Renee French** for the original gopher logo.
- **Coderabbit AI** for providing free AI code reviews.
- **Syrhu** for providing the background image of the app.
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=steveiliop56/tinyauth&type=Date)](https://www.star-history.com/#steveiliop56/tinyauth&Date)

View File

@@ -1,9 +0,0 @@
# Security Policy
## Supported Versions
It is recommended to use the [latest](https://github.com/steveiliop56/tinyauth/releases/latest) available version of tinyauth. This is because it includes security fixes, new features and dependency updates. Older versions, especially major ones, are not supported and won't receive security or patch updates.
## Reporting a Vulnerability
Due to the nature of this app, it needs to be secure. If you discover any security issues or vulnerabilities in the app please contact me as soon as possible at <steve@doesmycode.work>. Please do not use the issues section to report security issues as I won't be able to patch them in time and they may get exploited by malicious actors.

View File

@@ -1,24 +0,0 @@
root = "/tinyauth"
tmp_dir = "tmp"
[build]
pre_cmd = ["mkdir -p internal/assets/dist", "mkdir -p /data", "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 ."
bin = "/go/bin/dlv --listen :4000 --headless=true --api-version=2 --accept-multiclient --log=true exec tmp/tinyauth --continue --check-go-version=false"
include_ext = ["go"]
exclude_dir = ["internal/assets/dist"]
exclude_regex = [".*_test\\.go"]
stop_on_error = true
[color]
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"
[misc]
clean_on_exit = true
[screen]
clear_on_rebuild = false
keep_scroll = true

View File

@@ -3,8 +3,8 @@
"embeds": [
{
"title": "Welcome to Tinyauth Discord!",
"description": "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.\n\n**Information**\n\n• Github: <https://github.com/steveiliop56/tinyauth>\n• Website: <https://tinyauth.app>",
"url": "https://tinyauth.app",
"description": "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.\n\n**Information**\n\n• Github: <https://github.com/steveiliop56/tinyauth>\n• Website: <https://tinyauth.doesmycode.work>",
"url": "https://tinyauth.doesmycode.work",
"color": 7002085,
"author": {
"name": "Tinyauth"
@@ -12,11 +12,11 @@
"footer": {
"text": "Updated at"
},
"timestamp": "2025-06-06T12:25:27.629Z",
"timestamp": "2025-02-06T22:00:00.000Z",
"thumbnail": {
"url": "https://github.com/steveiliop56/tinyauth/blob/main/assets/logo.png?raw=true"
"url": "https://github.com/steveiliop56/tinyauth/blob/main/site/public/logo.png?raw=true"
}
}
],
"attachments": []
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 MiB

View File

@@ -1,99 +0,0 @@
package cmd
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"
)
type createUserCmd struct {
root *cobra.Command
cmd *cobra.Command
interactive bool
docker bool
username string
password string
}
func newCreateUserCmd(root *cobra.Command) *createUserCmd {
return &createUserCmd{
root: root,
}
}
func (c *createUserCmd) Register() {
c.cmd = &cobra.Command{
Use: "create",
Short: "Create a user",
Long: `Create a user either interactively or by passing flags.`,
Run: c.run,
}
c.cmd.Flags().BoolVarP(&c.interactive, "interactive", "i", false, "Create a user interactively")
c.cmd.Flags().BoolVar(&c.docker, "docker", false, "Format output for docker")
c.cmd.Flags().StringVar(&c.username, "username", "", "Username")
c.cmd.Flags().StringVar(&c.password, "password", "", "Password")
if c.root != nil {
c.root.AddCommand(c.cmd)
}
}
func (c *createUserCmd) GetCmd() *cobra.Command {
return c.cmd
}
func (c *createUserCmd) run(cmd *cobra.Command, args []string) {
log.Logger = log.Level(zerolog.InfoLevel)
if c.interactive {
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().Title("Username").Value(&c.username).Validate((func(s string) error {
if s == "" {
return errors.New("username cannot be empty")
}
return nil
})),
huh.NewInput().Title("Password").Value(&c.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(&c.docker),
),
)
var baseTheme *huh.Theme = huh.ThemeBase()
err := form.WithTheme(baseTheme).Run()
if err != nil {
log.Fatal().Err(err).Msg("Form failed")
}
}
if c.username == "" || c.password == "" {
log.Fatal().Err(errors.New("error invalid input")).Msg("Username and password cannot be empty")
}
log.Info().Str("username", c.username).Msg("Creating user")
passwd, err := bcrypt.GenerateFromPassword([]byte(c.password), bcrypt.DefaultCost)
if err != nil {
log.Fatal().Err(err).Msg("Failed to hash password")
}
// If docker format is enabled, escape the dollar sign
passwdStr := string(passwd)
if c.docker {
passwdStr = strings.ReplaceAll(passwdStr, "$", "$$")
}
log.Info().Str("user", fmt.Sprintf("%s:%s", c.username, passwdStr)).Msg("User created")
}

View File

@@ -1,120 +0,0 @@
package cmd
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"
)
type generateTotpCmd struct {
root *cobra.Command
cmd *cobra.Command
interactive bool
user string
}
func newGenerateTotpCmd(root *cobra.Command) *generateTotpCmd {
return &generateTotpCmd{
root: root,
}
}
func (c *generateTotpCmd) Register() {
c.cmd = &cobra.Command{
Use: "generate",
Short: "Generate a totp secret",
Long: `Generate a totp secret for a user either interactively or by passing flags.`,
Run: c.run,
}
c.cmd.Flags().BoolVarP(&c.interactive, "interactive", "i", false, "Run in interactive mode")
c.cmd.Flags().StringVar(&c.user, "user", "", "Your current user (username:hash)")
if c.root != nil {
c.root.AddCommand(c.cmd)
}
}
func (c *generateTotpCmd) GetCmd() *cobra.Command {
return c.cmd
}
func (c *generateTotpCmd) run(cmd *cobra.Command, args []string) {
log.Logger = log.Level(zerolog.InfoLevel)
if c.interactive {
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().Title("Current user (username:hash)").Value(&c.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 {
log.Fatal().Err(err).Msg("Form failed")
}
}
user, err := utils.ParseUser(c.user)
if err != nil {
log.Fatal().Err(err).Msg("Failed to parse user")
}
docker := false
if strings.Contains(c.user, "$$") {
docker = 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 docker {
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.")
}

View File

@@ -1,112 +0,0 @@
package cmd
import (
"encoding/json"
"errors"
"io"
"net/http"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
type healthzResponse struct {
Status string `json:"status"`
Message string `json:"message"`
}
type healthcheckCmd struct {
root *cobra.Command
cmd *cobra.Command
viper *viper.Viper
}
func newHealthcheckCmd(root *cobra.Command) *healthcheckCmd {
return &healthcheckCmd{
root: root,
viper: viper.New(),
}
}
func (c *healthcheckCmd) Register() {
c.cmd = &cobra.Command{
Use: "healthcheck [app-url]",
Short: "Perform a health check",
Long: `Use the health check endpoint to verify that Tinyauth is running and it's healthy.`,
Run: c.run,
}
c.viper.AutomaticEnv()
if c.root != nil {
c.root.AddCommand(c.cmd)
}
}
func (c *healthcheckCmd) GetCmd() *cobra.Command {
return c.cmd
}
func (c *healthcheckCmd) run(cmd *cobra.Command, args []string) {
log.Logger = log.Level(zerolog.InfoLevel)
var appUrl string
port := c.viper.GetString("PORT")
address := c.viper.GetString("ADDRESS")
if port == "" {
port = "3000"
}
if address == "" {
address = "127.0.0.1"
}
appUrl = "http://" + address + ":" + port
if len(args) > 0 {
appUrl = args[0]
}
log.Info().Str("app_url", appUrl).Msg("Performing health check")
client := http.Client{}
req, err := http.NewRequest("GET", appUrl+"/api/healthz", nil)
if err != nil {
log.Fatal().Err(err).Msg("Failed to create request")
}
resp, err := client.Do(req)
if err != nil {
log.Fatal().Err(err).Msg("Failed to perform request")
}
if resp.StatusCode != http.StatusOK {
log.Fatal().Err(errors.New("service is not healthy")).Msgf("Service is not healthy. Status code: %d", resp.StatusCode)
}
defer resp.Body.Close()
var healthResp healthzResponse
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal().Err(err).Msg("Failed to read response")
}
err = json.Unmarshal(body, &healthResp)
if err != nil {
log.Fatal().Err(err).Msg("Failed to decode response")
}
log.Info().Interface("response", healthResp).Msg("Tinyauth is healthy")
}

View File

@@ -1,9 +1,18 @@
package cmd
import (
"errors"
"os"
"strings"
"tinyauth/internal/bootstrap"
"tinyauth/internal/config"
"time"
cmd "tinyauth/cmd/user"
"tinyauth/internal/api"
"tinyauth/internal/assets"
"tinyauth/internal/auth"
"tinyauth/internal/docker"
"tinyauth/internal/hooks"
"tinyauth/internal/providers"
"tinyauth/internal/types"
"tinyauth/internal/utils"
"github.com/go-playground/validator/v10"
@@ -13,145 +22,186 @@ import (
"github.com/spf13/viper"
)
type rootCmd struct {
root *cobra.Command
cmd *cobra.Command
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) {
// Logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Timestamp().Logger().Level(zerolog.FatalLevel)
viper *viper.Viper
}
// Get config
var config types.Config
parseErr := viper.Unmarshal(&config)
HandleError(parseErr, "Failed to parse config")
func newRootCmd() *rootCmd {
return &rootCmd{
viper: viper.New(),
}
}
// Secrets
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)
config.TailscaleClientSecret = utils.GetSecret(config.TailscaleClientSecret, config.TailscaleClientSecretFile)
func (c *rootCmd) Register() {
c.cmd = &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 a simple login screen or OAuth with Google, Github or any other provider to all of your docker apps.`,
Run: c.run,
}
// Validate config
validator := validator.New()
validateErr := validator.Struct(config)
HandleError(validateErr, "Failed to validate config")
c.viper.AutomaticEnv()
// Logger
log.Logger = log.Level(zerolog.Level(config.LogLevel))
log.Info().Str("version", assets.Version).Msg("Starting tinyauth")
configOptions := []struct {
name string
defaultVal any
description string
}{
{"port", 3000, "Port to run the server on."},
{"address", "0.0.0.0", "Address to bind the server to."},
{"app-url", "", "The Tinyauth URL."},
{"users", "", "Comma separated list of users in the format username:hash."},
{"users-file", "", "Path to a file containing users in the format username:hash."},
{"secure-cookie", false, "Send cookie over secure connection only."},
{"oauth-whitelist", "", "Comma separated list of email addresses to whitelist when using OAuth."},
{"oauth-auto-redirect", "none", "Auto redirect to the specified OAuth provider if configured. (available providers: github, google, generic)"},
{"session-expiry", 86400, "Session (cookie) expiration time in seconds."},
{"login-timeout", 300, "Login timeout in seconds after max retries reached (0 to disable)."},
{"login-max-retries", 5, "Maximum login attempts before timeout (0 to disable)."},
{"log-level", "info", "Log level."},
{"app-title", "Tinyauth", "Title of the app."},
{"forgot-password-message", "", "Message to show on the forgot password page."},
{"background-image", "/background.jpg", "Background image URL for the login page."},
{"ldap-address", "", "LDAP server address (e.g. ldap://localhost:389)."},
{"ldap-bind-dn", "", "LDAP bind DN (e.g. uid=user,dc=example,dc=com)."},
{"ldap-bind-password", "", "LDAP bind password."},
{"ldap-base-dn", "", "LDAP base DN (e.g. dc=example,dc=com)."},
{"ldap-insecure", false, "Skip certificate verification for the LDAP server."},
{"ldap-search-filter", "(uid=%s)", "LDAP search filter for user lookup."},
{"resources-dir", "/data/resources", "Path to a directory containing custom resources (e.g. background image)."},
{"database-path", "/data/tinyauth.db", "Path to the Sqlite database file."},
{"trusted-proxies", "", "Comma separated list of trusted proxies (IP addresses or CIDRs) for correct client IP detection."},
{"disable-analytics", false, "Disable anonymous version collection."},
{"disable-resources", false, "Disable the resources server."},
}
// Users
log.Info().Msg("Parsing users")
users, usersErr := utils.GetUsers(config.Users, config.UsersFile)
for _, opt := range configOptions {
switch v := opt.defaultVal.(type) {
case bool:
c.cmd.Flags().Bool(opt.name, v, opt.description)
case int:
c.cmd.Flags().Int(opt.name, v, opt.description)
case string:
c.cmd.Flags().String(opt.name, v, opt.description)
HandleError(usersErr, "Failed to parse users")
if len(users) == 0 && !utils.OAuthConfigured(config) {
HandleError(errors.New("no users or OAuth configured"), "No users or OAuth configured")
}
// Create uppercase env var name
envVar := strings.ReplaceAll(strings.ToUpper(opt.name), "-", "_")
c.viper.BindEnv(opt.name, envVar)
}
// Create oauth whitelist
oauthWhitelist := strings.Split(config.OAuthWhitelist, ",")
log.Debug().Msg("Parsed OAuth whitelist")
c.viper.BindPFlags(c.cmd.Flags())
// Create OAuth config
oauthConfig := types.OAuthConfig{
GithubClientId: config.GithubClientId,
GithubClientSecret: config.GithubClientSecret,
GoogleClientId: config.GoogleClientId,
GoogleClientSecret: config.GoogleClientSecret,
TailscaleClientId: config.TailscaleClientId,
TailscaleClientSecret: config.TailscaleClientSecret,
GenericClientId: config.GenericClientId,
GenericClientSecret: config.GenericClientSecret,
GenericScopes: strings.Split(config.GenericScopes, ","),
GenericAuthURL: config.GenericAuthURL,
GenericTokenURL: config.GenericTokenURL,
GenericUserURL: config.GenericUserURL,
AppURL: config.AppURL,
}
if c.root != nil {
c.root.AddCommand(c.cmd)
log.Debug().Msg("Parsed OAuth config")
// Create docker service
docker := docker.NewDocker()
// Initialize docker
dockerErr := docker.Init()
HandleError(dockerErr, "Failed to initialize docker")
// Create auth service
auth := auth.NewAuth(docker, users, oauthWhitelist, config.SessionExpiry)
// Create OAuth providers service
providers := providers.NewProviders(oauthConfig)
// Initialize providers
providers.Init()
// Create hooks service
hooks := hooks.NewHooks(auth, providers)
// Create API
api := api.NewAPI(types.APIConfig{
Port: config.Port,
Address: config.Address,
Secret: config.Secret,
AppURL: config.AppURL,
CookieSecure: config.CookieSecure,
DisableContinue: config.DisableContinue,
CookieExpiry: config.SessionExpiry,
}, hooks, auth, providers)
// Setup routes
api.Init()
api.SetupRoutes()
// Start
api.Run()
},
}
func Execute() {
err := rootCmd.Execute()
HandleError(err, "Failed to execute root command")
}
func HandleError(err error, msg string) {
// If error log it and exit
if err != nil {
log.Fatal().Err(err).Msg(msg)
}
}
func (c *rootCmd) GetCmd() *cobra.Command {
return c.cmd
}
func (c *rootCmd) run(cmd *cobra.Command, args []string) {
var conf config.Config
err := c.viper.Unmarshal(&conf)
if err != nil {
log.Fatal().Err(err).Msg("Failed to parse config")
}
v := validator.New()
err = v.Struct(conf)
if err != nil {
log.Fatal().Err(err).Msg("Invalid config")
}
log.Logger = log.Level(zerolog.Level(utils.GetLogLevel(conf.LogLevel)))
log.Info().Str("version", strings.TrimSpace(config.Version)).Msg("Starting Tinyauth")
if log.Logger.GetLevel() == zerolog.TraceLevel {
log.Warn().Msg("Log level set to trace, this will log sensitive information!")
}
app := bootstrap.NewBootstrapApp(conf)
err = app.Setup()
if err != nil {
log.Fatal().Err(err).Msg("Failed to setup app")
}
}
func Run() {
rootCmd := newRootCmd()
rootCmd.Register()
root := rootCmd.GetCmd()
userCmd := &cobra.Command{
Use: "user",
Short: "User utilities",
Long: `Utilities for creating and verifying tinyauth compatible users.`,
}
totpCmd := &cobra.Command{
Use: "totp",
Short: "Totp utilities",
Long: `Utilities for creating and verifying totp codes.`,
}
newCreateUserCmd(userCmd).Register()
newVerifyUserCmd(userCmd).Register()
newGenerateTotpCmd(totpCmd).Register()
newVersionCmd(root).Register()
newHealthcheckCmd(root).Register()
root.AddCommand(userCmd)
root.AddCommand(totpCmd)
err := root.Execute()
if err != nil {
log.Fatal().Err(err).Msg("Failed to execute root command")
}
func init() {
// Add user command
rootCmd.AddCommand(cmd.UserCmd())
// Read environment variables
viper.AutomaticEnv()
// Flags
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("tailscale-client-id", "", "Tailscale OAuth client ID.")
rootCmd.Flags().String("tailscale-client-secret", "", "Tailscale OAuth client secret.")
rootCmd.Flags().String("tailscale-client-secret-file", "", "Tailscale 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().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().Int("session-expiry", 86400, "Session (cookie) expiration time in seconds.")
rootCmd.Flags().Int("log-level", 1, "Log level.")
// Bind flags to environment
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("tailscale-client-id", "TAILSCALE_CLIENT_ID")
viper.BindEnv("tailscale-client-secret", "TAILSCALE_CLIENT_SECRET")
viper.BindEnv("tailscale-client-secret-file", "TAILSCALE_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("disable-continue", "DISABLE_CONTINUE")
viper.BindEnv("oauth-whitelist", "OAUTH_WHITELIST")
viper.BindEnv("session-expiry", "SESSION_EXPIRY")
viper.BindEnv("log-level", "LOG_LEVEL")
// Bind flags to viper
viper.BindPFlags(rootCmd.Flags())
}

91
cmd/user/create/create.go Normal file
View File

@@ -0,0 +1,91 @@
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 username string
var password string
var docker bool
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) {
// Setup logger
log.Logger = log.Level(zerolog.InfoLevel)
// Check if interactive
if interactive {
// Create huh form
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().Title("Username").Value(&username).Validate((func(s string) error {
if s == "" {
return errors.New("username cannot be empty")
}
return nil
})),
huh.NewInput().Title("Password").Value(&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(&docker),
),
)
// Use simple theme
var baseTheme *huh.Theme = huh.ThemeBase()
formErr := form.WithTheme(baseTheme).Run()
if formErr != nil {
log.Fatal().Err(formErr).Msg("Form failed")
}
}
// Do we have username and password?
if username == "" || password == "" {
log.Error().Msg("Username and password cannot be empty")
}
log.Info().Str("username", username).Str("password", password).Bool("docker", docker).Msg("Creating user")
// Hash password
passwordByte, passwordErr := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if passwordErr != nil {
log.Fatal().Err(passwordErr).Msg("Failed to hash password")
}
passwordString := string(passwordByte)
// Escape $ for docker
if docker {
passwordString = strings.ReplaceAll(passwordString, "$", "$$")
}
// Log user created
log.Info().Str("user", fmt.Sprintf("%s:%s", username, passwordString)).Msg("User created")
},
}
func init() {
// Flags
CreateCmd.Flags().BoolVar(&interactive, "interactive", false, "Create a user interactively")
CreateCmd.Flags().BoolVar(&docker, "docker", false, "Format output for docker")
CreateCmd.Flags().StringVar(&username, "username", "", "Username")
CreateCmd.Flags().StringVar(&password, "password", "", "Password")
}

24
cmd/user/user.go Normal file
View File

@@ -0,0 +1,24 @@
package cmd
import (
"tinyauth/cmd/user/create"
"tinyauth/cmd/user/verify"
"github.com/spf13/cobra"
)
func UserCmd() *cobra.Command {
// Create the user command
userCmd := &cobra.Command{
Use: "user",
Short: "User utilities",
Long: `Utilities for creating and verifying tinyauth compatible users.`,
}
// Add subcommands
userCmd.AddCommand(create.CreateCmd)
userCmd.AddCommand(verify.VerifyCmd)
// Return the user command
return userCmd
}

102
cmd/user/verify/verify.go Normal file
View File

@@ -0,0 +1,102 @@
package verify
import (
"errors"
"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 username string
var password string
var docker bool
var user 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 and password.`,
Run: func(cmd *cobra.Command, args []string) {
// Setup logger
log.Logger = log.Level(zerolog.InfoLevel)
// Check if interactive
if interactive {
// Create huh form
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().Title("User (username:hash)").Value(&user).Validate((func(s string) error {
if s == "" {
return errors.New("user cannot be empty")
}
return nil
})),
huh.NewInput().Title("Username").Value(&username).Validate((func(s string) error {
if s == "" {
return errors.New("username cannot be empty")
}
return nil
})),
huh.NewInput().Title("Password").Value(&password).Validate((func(s string) error {
if s == "" {
return errors.New("password cannot be empty")
}
return nil
})),
huh.NewSelect[bool]().Title("Is the user formatted for docker?").Options(huh.NewOption("Yes", true), huh.NewOption("No", false)).Value(&docker),
),
)
// Use simple theme
var baseTheme *huh.Theme = huh.ThemeBase()
formErr := form.WithTheme(baseTheme).Run()
if formErr != nil {
log.Fatal().Err(formErr).Msg("Form failed")
}
}
// Do we have username, password and user?
if username == "" || password == "" || user == "" {
log.Fatal().Msg("Username, password and user cannot be empty")
}
log.Info().Str("user", user).Str("username", username).Str("password", password).Bool("docker", docker).Msg("Verifying user")
// Split username and password
userSplit := strings.Split(user, ":")
if userSplit[1] == "" {
log.Fatal().Msg("User is not formatted correctly")
}
// Replace $$ with $ if formatted for docker
if docker {
userSplit[1] = strings.ReplaceAll(userSplit[1], "$$", "$")
}
// Compare username and password
verifyErr := bcrypt.CompareHashAndPassword([]byte(userSplit[1]), []byte(password))
if verifyErr != nil || username != userSplit[0] {
log.Fatal().Msg("Username or password incorrect")
} else {
log.Info().Msg("Verification successful")
}
},
}
func init() {
// Flags
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(&username, "username", "", "Username")
VerifyCmd.Flags().StringVar(&password, "password", "", "Password")
VerifyCmd.Flags().StringVar(&user, "user", "", "Hash (username:hash combination)")
}

View File

@@ -1,118 +0,0 @@
package cmd
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"
)
type verifyUserCmd struct {
root *cobra.Command
cmd *cobra.Command
interactive bool
username string
password string
totp string
user string
}
func newVerifyUserCmd(root *cobra.Command) *verifyUserCmd {
return &verifyUserCmd{
root: root,
}
}
func (c *verifyUserCmd) Register() {
c.cmd = &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: c.run,
}
c.cmd.Flags().BoolVarP(&c.interactive, "interactive", "i", false, "Validate a user interactively")
c.cmd.Flags().StringVar(&c.username, "username", "", "Username")
c.cmd.Flags().StringVar(&c.password, "password", "", "Password")
c.cmd.Flags().StringVar(&c.totp, "totp", "", "TOTP code")
c.cmd.Flags().StringVar(&c.user, "user", "", "Hash (username:hash:totp)")
if c.root != nil {
c.root.AddCommand(c.cmd)
}
}
func (c *verifyUserCmd) GetCmd() *cobra.Command {
return c.cmd
}
func (c *verifyUserCmd) run(cmd *cobra.Command, args []string) {
log.Logger = log.Level(zerolog.InfoLevel)
if c.interactive {
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().Title("User (username:hash:totp)").Value(&c.user).Validate((func(s string) error {
if s == "" {
return errors.New("user cannot be empty")
}
return nil
})),
huh.NewInput().Title("Username").Value(&c.username).Validate((func(s string) error {
if s == "" {
return errors.New("username cannot be empty")
}
return nil
})),
huh.NewInput().Title("Password").Value(&c.password).Validate((func(s string) error {
if s == "" {
return errors.New("password cannot be empty")
}
return nil
})),
huh.NewInput().Title("TOTP Code (optional)").Value(&c.totp),
),
)
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(c.user)
if err != nil {
log.Fatal().Err(err).Msg("Failed to parse user")
}
if user.Username != c.username {
log.Fatal().Msg("Username is incorrect")
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(c.password))
if err != nil {
log.Fatal().Msg("Password is incorrect")
}
if user.TotpSecret == "" {
if c.totp != "" {
log.Warn().Msg("User does not have TOTP secret")
}
log.Info().Msg("User verified")
return
}
ok := totp.Validate(c.totp, user.TotpSecret)
if !ok {
log.Fatal().Msg("TOTP code incorrect")
}
log.Info().Msg("User verified")
}

View File

@@ -1,42 +0,0 @@
package cmd
import (
"fmt"
"tinyauth/internal/config"
"github.com/spf13/cobra"
)
type versionCmd struct {
root *cobra.Command
cmd *cobra.Command
}
func newVersionCmd(root *cobra.Command) *versionCmd {
return &versionCmd{
root: root,
}
}
func (c *versionCmd) Register() {
c.cmd = &cobra.Command{
Use: "version",
Short: "Print the version number of Tinyauth",
Long: `All software has versions. This is Tinyauth's.`,
Run: c.run,
}
if c.root != nil {
c.root.AddCommand(c.cmd)
}
}
func (c *versionCmd) GetCmd() *cobra.Command {
return c.cmd
}
func (c *versionCmd) run(cmd *cobra.Command, args []string) {
fmt.Printf("Version: %s\n", config.Version)
fmt.Printf("Commit Hash: %s\n", config.CommitHash)
fmt.Printf("Build Timestamp: %s\n", config.BuildTimestamp)
}

View File

@@ -1,8 +0,0 @@
coverage:
status:
project:
default:
informational: true
patch:
default:
informational: true

View File

@@ -1,12 +0,0 @@
"base_path": "."
"base_url": "https://api.crowdin.com"
"preserve_hierarchy": true
files:
[
{
"source": "/frontend/src/lib/i18n/locales/en.json",
"translation": "/frontend/src/lib/i18n/locales/%locale%.json",
},
]

View File

@@ -7,47 +7,28 @@ services:
- 80:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock
labels:
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth
whoami:
container_name: whoami
image: traefik/whoami:latest
nginx:
container_name: nginx
image: nginx:latest
labels:
traefik.enable: true
traefik.http.routers.whoami.rule: Host(`whoami.example.com`)
traefik.http.routers.whoami.middlewares: tinyauth
traefik.http.routers.nginx.rule: Host(`nginx.dev.local`)
traefik.http.services.nginx.loadbalancer.server.port: 80
traefik.http.routers.nginx.middlewares: tinyauth
tinyauth-frontend:
container_name: tinyauth-frontend
tinyauth:
container_name: tinyauth
build:
context: .
dockerfile: frontend/Dockerfile.dev
volumes:
- ./frontend/src:/frontend/src
ports:
- 5173:5173
dockerfile: Dockerfile
environment:
- SECRET=some-random-32-chars-string
- APP_URL=http://tinyauth.dev.local
- USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u # user:password
labels:
traefik.enable: true
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
tinyauth-backend:
container_name: tinyauth-backend
build:
context: .
dockerfile: Dockerfile.dev
args:
- VERSION=development
- COMMIT_HASH=development
- BUILD_TIMESTAMP=000-00-00T00:00:00Z
env_file: .env
volumes:
- ./internal:/tinyauth/internal
- ./cmd:/tinyauth/cmd
- ./main.go:/tinyauth/main.go
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data
ports:
- 3000:3000
- 4000:4000
labels:
traefik.enable: true
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth-backend:3000/api/auth/traefik
traefik.http.routers.tinyauth.rule: Host(`tinyauth.dev.local`)
traefik.http.services.tinyauth.loadbalancer.server.port: 3000

View File

@@ -7,24 +7,26 @@ services:
- 80:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock
labels:
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth
whoami:
container_name: whoami
image: traefik/whoami:latest
nginx:
container_name: nginx
image: nginx:latest
labels:
traefik.enable: true
traefik.http.routers.whoami.rule: Host(`whoami.example.com`)
traefik.http.routers.whoami.middlewares: tinyauth
traefik.http.routers.nginx.rule: Host(`nginx.example.com`)
traefik.http.services.nginx.loadbalancer.server.port: 80
traefik.http.routers.nginx.middlewares: tinyauth
tinyauth:
container_name: tinyauth
image: ghcr.io/steveiliop56/tinyauth:v3
image: ghcr.io/steveiliop56/tinyauth:latest
environment:
- SECRET=some-random-32-chars-string
- APP_URL=https://tinyauth.example.com
- USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u # user:password
volumes:
- ./data:/data
labels:
traefik.enable: true
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik
traefik.http.services.tinyauth.loadbalancer.server.port: 3000

View File

@@ -1,6 +0,0 @@
# Ignore artifacts:
dist
node_modules
bun.lock
package.json
src/lib/i18n/locales

View File

@@ -1,22 +0,0 @@
FROM oven/bun:1.2.16-alpine
WORKDIR /frontend
COPY ./frontend/package.json ./
COPY ./frontend/bun.lock ./
RUN bun install
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 ./
EXPOSE 5173
ENTRYPOINT ["bun", "run", "dev"]

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

View File

@@ -1,57 +0,0 @@
{
"name": "tinyauth-shadcn",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/vite": "^4.1.15",
"@tanstack/react-query": "^5.90.5",
"axios": "^1.12.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"i18next": "^25.6.0",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-resources-to-backend": "^1.2.1",
"input-otp": "^1.4.2",
"lucide-react": "^0.546.0",
"next-themes": "^0.4.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-hook-form": "^7.65.0",
"react-i18next": "^16.1.3",
"react-markdown": "^10.1.0",
"react-router": "^7.9.4",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.15",
"zod": "^4.1.12"
},
"devDependencies": {
"@eslint/js": "^9.38.0",
"@tanstack/eslint-plugin-query": "^5.91.2",
"@types/node": "^24.9.1",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@vitejs/plugin-react": "^5.0.4",
"eslint": "^9.38.0",
"eslint-plugin-react-hooks": "^7.0.0",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.4.0",
"prettier": "3.6.2",
"tw-animate-css": "^1.4.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.2",
"vite": "^7.1.11"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -1,21 +0,0 @@
{
"name": "Tinyauth",
"short_name": "Tinyauth",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#171717",
"background_color": "#171717",
"display": "standalone"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -1,12 +0,0 @@
import { Navigate } from "react-router";
import { useUserContext } from "./context/user-context";
export const App = () => {
const { isLoggedIn } = useUserContext();
if (isLoggedIn) {
return <Navigate to="/logout" replace />;
}
return <Navigate to="/login" replace />;
};

View File

@@ -1,89 +0,0 @@
import { useTranslation } from "react-i18next";
import { Input } from "../ui/input";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "../ui/form";
import { Button } from "../ui/button";
import { loginSchema, LoginSchema } from "@/schemas/login-schema";
import z from "zod";
interface Props {
onSubmit: (data: LoginSchema) => void;
loading?: boolean;
}
export const LoginForm = (props: Props) => {
const { onSubmit, loading } = props;
const { t } = useTranslation();
z.config({
customError: (iss) =>
iss.input === undefined ? t("fieldRequired") : t("invalidInput"),
});
const form = useForm<LoginSchema>({
resolver: zodResolver(loginSchema),
});
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem className="mb-4 gap-0">
<FormLabel className="mb-2">{t("loginUsername")}</FormLabel>
<FormControl className="mb-1">
<Input
placeholder={t("loginUsername")}
disabled={loading}
autoComplete="username"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem className="mb-4 gap-0">
<div className="relative mb-1">
<FormLabel className="mb-2">{t("loginPassword")}</FormLabel>
<FormControl>
<Input
placeholder={t("loginPassword")}
type="password"
disabled={loading}
autoComplete="current-password"
{...field}
/>
</FormControl>
<a
href="/forgot-password"
className="text-muted-foreground text-sm absolute right-0 bottom-[2.565rem]" // 2.565 is *just* perfect
>
{t("forgotPasswordTitle")}
</a>
</div>
<FormMessage />
</FormItem>
)}
/>
<Button className="w-full" type="submit" loading={loading}>
{t("loginSubmit")}
</Button>
</form>
</Form>
);
};

View File

@@ -1,68 +0,0 @@
import { Form, FormControl, FormField, FormItem } from "../ui/form";
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "../ui/input-otp";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { totpSchema, TotpSchema } from "@/schemas/totp-schema";
import { useTranslation } from "react-i18next";
import z from "zod";
interface Props {
formId: string;
onSubmit: (code: TotpSchema) => void;
loading?: boolean;
}
export const TotpForm = (props: Props) => {
const { formId, onSubmit, loading } = props;
const { t } = useTranslation();
z.config({
customError: (iss) =>
iss.input === undefined ? t("fieldRequired") : t("invalidInput"),
});
const form = useForm<TotpSchema>({
resolver: zodResolver(totpSchema),
});
return (
<Form {...form}>
<form id={formId} onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="code"
render={({ field }) => (
<FormItem>
<FormControl>
<InputOTP
maxLength={6}
disabled={loading}
{...field}
autoComplete="one-time-code"
autoFocus
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</FormControl>
</FormItem>
)}
/>
</form>
</Form>
);
};

View File

@@ -1,56 +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 /> }}
/>
</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>
);
};

View File

@@ -1,30 +0,0 @@
import type { SVGProps } from "react";
export function GoogleIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={256}
height={262}
viewBox="0 0 256 262"
{...props}
>
<path
fill="#4285f4"
d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
></path>
<path
fill="#34a853"
d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
></path>
<path
fill="#fbbc05"
d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z"
></path>
<path
fill="#eb4335"
d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
></path>
</svg>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -1,35 +0,0 @@
import { languages, SupportedLanguage } from "@/lib/i18n/locales";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/select";
import { useState } from "react";
import i18n from "@/lib/i18n/i18n";
export const LanguageSelector = () => {
const [language, setLanguage] = useState<SupportedLanguage>(
i18n.language as SupportedLanguage,
);
const handleSelect = (option: string) => {
setLanguage(option as SupportedLanguage);
i18n.changeLanguage(option as SupportedLanguage);
};
return (
<Select onValueChange={handleSelect} value={language}>
<SelectTrigger className="absolute top-5 right-5">
<SelectValue placeholder="Select language" />
</SelectTrigger>
<SelectContent>
{Object.entries(languages).map(([key, value]) => (
<SelectItem key={key} value={key}>
{value}
</SelectItem>
))}
</SelectContent>
</Select>
);
};

View File

@@ -1,58 +0,0 @@
import { useAppContext } from "@/context/app-context";
import { LanguageSelector } from "../language/language";
import { Outlet } from "react-router";
import { useCallback, useEffect, useState } from "react";
import { DomainWarning } from "../domain-warning/domain-warning";
const BaseLayout = ({ children }: { children: React.ReactNode }) => {
const { backgroundImage, title } = useAppContext();
useEffect(() => {
document.title = title;
}, [title]);
return (
<div
className="relative flex flex-col justify-center items-center min-h-svh"
style={{
backgroundImage: `url(${backgroundImage})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
>
<LanguageSelector />
{children}
</div>
);
};
export const Layout = () => {
const { appUrl } = 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 && appUrl !== currentUrl) {
return (
<BaseLayout>
<DomainWarning
appUrl={appUrl}
currentUrl={currentUrl}
onClick={() => handleIgnore()}
/>
</BaseLayout>
);
}
return (
<BaseLayout>
<Outlet />
</BaseLayout>
);
};

View File

@@ -1,77 +0,0 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { Loader2 } from "lucide-react";
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",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
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",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
function Button({
className,
variant,
size,
asChild = false,
loading = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
loading?: boolean;
}) {
const Comp = asChild ? Slot : "button";
if (loading) {
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
disabled
{...props}
>
<Loader2 className="animate-spin" />
</Comp>
);
}
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
export { Button, buttonVariants };

View File

@@ -1,92 +0,0 @@
import * as React from "react";
import { cn } from "@/lib/utils";
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className,
)}
{...props}
/>
);
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className,
)}
{...props}
/>
);
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
);
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className,
)}
{...props}
/>
);
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
);
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
);
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
};

View File

@@ -1,166 +0,0 @@
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import {
Controller,
FormProvider,
useFormContext,
useFormState,
type ControllerProps,
type FieldPath,
type FieldValues,
} from "react-hook-form";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label";
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue,
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState } = useFormContext();
const formState = useFormState({ name: fieldContext.name });
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
type FormItemContextValue = {
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue,
);
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div
data-slot="form-item"
className={cn("grid gap-2", className)}
{...props}
/>
</FormItemContext.Provider>
);
}
function FormLabel({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
const { error, formItemId } = useFormField();
return (
<Label
data-slot="form-label"
data-error={!!error}
className={cn("data-[error=true]:text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
);
}
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return (
<Slot
data-slot="form-control"
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
);
}
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
const { formDescriptionId } = useFormField();
return (
<p
data-slot="form-description"
id={formDescriptionId}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
}
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message ?? "") : props.children;
if (!body) {
return null;
}
return (
<p
data-slot="form-message"
id={formMessageId}
className={cn("text-destructive text-sm", className)}
{...props}
>
{body}
</p>
);
}
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
};

View File

@@ -1,75 +0,0 @@
import * as React from "react";
import { OTPInput, OTPInputContext } from "input-otp";
import { MinusIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function InputOTP({
className,
containerClassName,
...props
}: React.ComponentProps<typeof OTPInput> & {
containerClassName?: string;
}) {
return (
<OTPInput
data-slot="input-otp"
containerClassName={cn(
"flex items-center gap-2 has-disabled:opacity-50",
containerClassName,
)}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
);
}
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="input-otp-group"
className={cn("flex items-center", className)}
{...props}
/>
);
}
function InputOTPSlot({
index,
className,
...props
}: React.ComponentProps<"div"> & {
index: number;
}) {
const inputOTPContext = React.useContext(OTPInputContext);
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
return (
<div
data-slot="input-otp-slot"
data-active={isActive}
className={cn(
"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
className,
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
</div>
)}
</div>
);
}
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
return (
<div data-slot="input-otp-separator" role="separator" {...props}>
<MinusIcon />
</div>
);
}
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };

View File

@@ -1,21 +0,0 @@
import * as React from "react";
import { cn } from "@/lib/utils";
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"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",
className,
)}
{...props}
/>
);
}
export { Input };

View File

@@ -1,22 +0,0 @@
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cn } from "@/lib/utils";
function Label({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className,
)}
{...props}
/>
);
}
export { Label };

View File

@@ -1,33 +0,0 @@
import { Loader2 } from "lucide-react";
import { Button } from "./button";
import React from "react";
import { twMerge } from "tailwind-merge";
interface Props extends React.ComponentProps<typeof Button> {
title: string;
icon: React.ReactNode;
onClick?: () => void;
loading?: boolean;
}
export const OAuthButton = (props: Props) => {
const { title, icon, onClick, loading, className, ...rest } = props;
return (
<Button
onClick={onClick}
className={twMerge("rounded-md", className)}
variant="outline"
{...rest}
>
{loading ? (
<Loader2 className="animate-spin" />
) : (
<>
{icon}
{title}
</>
)}
</Button>
);
};

View File

@@ -1,183 +0,0 @@
import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />;
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default";
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"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,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
}
function SelectContent({
className,
children,
position = "popper",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
);
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
{...props}
/>
);
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 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 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
);
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props}
/>
);
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
);
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
);
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
};

View File

@@ -1,38 +0,0 @@
"use client";
import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/lib/utils";
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator-root"
decorative={decorative}
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className,
)}
{...props}
/>
);
}
function SeperatorWithChildren({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-4">
<Separator className="flex-1" />
<span className="text-sm text-muted-foreground">{children}</span>
<Separator className="flex-1" />
</div>
);
}
export { Separator, SeperatorWithChildren };

View File

@@ -1,23 +0,0 @@
import { useTheme } from "next-themes";
import { Toaster as Sonner, ToasterProps } from "sonner";
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme();
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
} as React.CSSProperties
}
{...props}
/>
);
};
export { Toaster };

View File

@@ -1,44 +0,0 @@
import {
appContextSchema,
AppContextSchema,
} from "@/schemas/app-context-schema";
import { createContext, useContext } from "react";
import { useSuspenseQuery } from "@tanstack/react-query";
import axios from "axios";
const AppContext = createContext<AppContextSchema | null>(null);
export const AppContextProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const { isFetching, data, error } = useSuspenseQuery({
queryKey: ["app"],
queryFn: () => axios.get("/api/context/app").then((res) => res.data),
});
if (error && !isFetching) {
throw error;
}
const validated = appContextSchema.safeParse(data);
if (validated.success === false) {
throw validated.error;
}
return (
<AppContext.Provider value={validated.data}>{children}</AppContext.Provider>
);
};
export const useAppContext = () => {
const context = useContext(AppContext);
if (!context) {
throw new Error("useAppContext must be used within an AppContextProvider");
}
return context;
};

View File

@@ -1,48 +0,0 @@
import {
userContextSchema,
UserContextSchema,
} from "@/schemas/user-context-schema";
import { createContext, useContext } from "react";
import { useSuspenseQuery } from "@tanstack/react-query";
import axios from "axios";
const UserContext = createContext<UserContextSchema | null>(null);
export const UserContextProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const { isFetching, data, error } = useSuspenseQuery({
queryKey: ["user"],
queryFn: () => axios.get("/api/context/user").then((res) => res.data),
});
if (error && !isFetching) {
throw error;
}
const validated = userContextSchema.safeParse(data);
if (validated.success === false) {
throw validated.error;
}
return (
<UserContext.Provider value={validated.data}>
{children}
</UserContext.Provider>
);
};
export const useUserContext = () => {
const context = useContext(UserContext);
if (!context) {
throw new Error(
"useUserContext must be used within an UserContextProvider",
);
}
return context;
};

View File

@@ -1,176 +0,0 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
h1 {
@apply scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl;
}
h2 {
@apply scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0;
}
h3 {
@apply scroll-m-20 text-2xl font-semibold tracking-tight;
}
h4 {
@apply scroll-m-20 text-xl font-semibold tracking-tight;
}
p {
@apply leading-6;
}
blockquote {
@apply mt-6 border-l-2 pl-6 italic;
}
tr {
@apply m-0 border-t p-0 even:bg-muted;
}
th {
@apply border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right;
}
ul {
@apply my-6 ml-6 list-disc [&>li]:mt-2;
}
code {
@apply relative rounded bg-muted px-[0.2rem] py-[0.1rem] font-mono text-sm font-semibold break-all;
}
.lead {
@apply text-xl text-muted-foreground;
}
.large {
@apply text-lg font-semibold;
}
small {
@apply text-sm font-medium leading-none;
}
.muted {
@apply text-sm text-muted-foreground;
}

View File

@@ -1,15 +0,0 @@
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, []);
}

View File

@@ -1,25 +0,0 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import resourcesToBackend from "i18next-resources-to-backend";
i18n
.use(LanguageDetector)
.use(initReactI18next)
.use(
resourcesToBackend(
(language: string) => import(`./locales/${language}.json`),
),
)
.init({
fallbackLng: "en",
debug: import.meta.env.MODE === "development",
interpolation: {
escapeValue: false,
},
load: "currentOnly",
});
export default i18n;

View File

@@ -1,37 +0,0 @@
export const languages = {
"af-ZA": "Afrikaans",
"ar-SA": "العربية",
"ca-ES": "Català",
"cs-CZ": "Čeština",
"da-DK": "Dansk",
"de-DE": "Deutsch",
"el-GR": "Ελληνικά",
"en-US": "English",
"es-ES": "Español",
"fi-FI": "Suomi",
"fr-FR": "Français",
"he-IL": "עברית",
"hu-HU": "Magyar",
"it-IT": "Italiano",
"ja-JP": "日本語",
"ko-KR": "한국어",
"nl-NL": "Nederlands",
"no-NO": "Norsk",
"pl-PL": "Polski",
"pt-BR": "Português",
"pt-PT": "Português",
"ro-RO": "Română",
"ru-RU": "Русский",
"sr-SP": "Српски",
"sv-SE": "Svenska",
"tr-TR": "Türkçe",
"uk-UA": "Українська",
"vi-VN": "Tiếng Việt",
"zh-CN": "简体中文",
"zh-TW": "繁體中文(台灣)",
};
export type SupportedLanguage = keyof typeof languages;
export const getLanguageName = (language: SupportedLanguage): string =>
languages[language];

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"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...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "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.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "مرحبا بعودتك، ادخل باستخدام",
"loginTitleSimple": "مرحبا بعودتك، سجل دخولك",
"loginDivider": "أو",
"loginUsername": "اسم المستخدم",
"loginPassword": "كلمة المرور",
"loginSubmit": "تسجيل الدخول",
"loginFailTitle": "فشل تسجيل الدخول",
"loginFailSubtitle": "الرجاء التحقق من اسم المستخدم وكلمة المرور",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "تم تسجيل الدخول",
"loginSuccessSubtitle": "مرحبا بعودتك!",
"loginOauthFailTitle": "حدث خطأ",
"loginOauthFailSubtitle": "أخفق الحصول على رابط OAuth",
"loginOauthSuccessTitle": "إعادة توجيه",
"loginOauthSuccessSubtitle": "إعادة توجيه إلى مزود OAuth الخاص بك",
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
"loginOauthAutoRedirectButton": "Redirect now",
"continueTitle": "متابعة",
"continueRedirectingTitle": "إعادة توجيه...",
"continueRedirectingSubtitle": "يجب إعادة توجيهك إلى التطبيق قريبا",
"continueRedirectManually": "Redirect me manually",
"continueInsecureRedirectTitle": "إعادة توجيه غير آمنة",
"continueInsecureRedirectSubtitle": "أنت تحاول إعادة التوجيه من <code>https</code> إلى <code>http</code>، هل أنت متأكد أنك تريد المتابعة؟",
"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?",
"logoutFailTitle": "فشل تسجيل الخروج",
"logoutFailSubtitle": "يرجى إعادة المحاولة",
"logoutSuccessTitle": "تم تسجيل الخروج",
"logoutSuccessSubtitle": "تم تسجيل خروجك",
"logoutTitle": "تسجيل الخروج",
"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.",
"notFoundTitle": "الصفحة غير موجودة",
"notFoundSubtitle": "الصفحة التي تبحث عنها غير موجودة.",
"notFoundButton": "انتقل إلى الرئيسية",
"totpFailTitle": "أخفق التحقق من الرمز",
"totpFailSubtitle": "الرجاء التحقق من الرمز الخاص بك وحاول مرة أخرى",
"totpSuccessTitle": "تم التحقق",
"totpSuccessSubtitle": "إعادة توجيه إلى تطبيقك",
"totpTitle": "أدخل رمز TOTP الخاص بك",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "غير مرخص",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "حاول مجددا",
"cancelTitle": "إلغاء",
"forgotPasswordTitle": "نسيت كلمة المرور؟",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "حدث خطأ",
"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.",
"fieldRequired": "This field is required",
"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": "تجاهل",
"goToCorrectDomainTitle": "Go to correct domain"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"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...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "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.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Vítejte zpět, přihlaste se pomocí",
"loginTitleSimple": "Vítejte zpět, přihlaste se prosím",
"loginDivider": "Nebo",
"loginUsername": "Uživatelské jméno",
"loginPassword": "Heslo",
"loginSubmit": "Přihlásit",
"loginFailTitle": "Přihlášení se nezdařilo",
"loginFailSubtitle": "Zkontrolujte prosím své uživatelské jméno a heslo",
"loginFailRateLimit": "Přiliš mnoho neúspěšných pokusů přihlášení. Zkuste to prosím později",
"loginSuccessTitle": "Přihlášen",
"loginSuccessSubtitle": "Vítejte zpět!",
"loginOauthFailTitle": "Došlo k chybě",
"loginOauthFailSubtitle": "Nepodařilo se získat OAuth URL",
"loginOauthSuccessTitle": "Přesměrování",
"loginOauthSuccessSubtitle": "Přesměrování k poskytovateli OAuth",
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
"loginOauthAutoRedirectButton": "Redirect now",
"continueTitle": "Pokračovat",
"continueRedirectingTitle": "Přesměrování...",
"continueRedirectingSubtitle": "Brzy budete přesměrováni do aplikace",
"continueRedirectManually": "Redirect me manually",
"continueInsecureRedirectTitle": "Nezabezpečené přesměrování",
"continueInsecureRedirectSubtitle": "Pokoušíte se přesměrovat z <code>https</code> na <code>http</code>, které není bezpečné. Opravdu chcete pokračovat?",
"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?",
"logoutFailTitle": "Odhlášení se nezdařilo",
"logoutFailSubtitle": "Zkuste to prosím znovu",
"logoutSuccessTitle": "Odhlášen",
"logoutSuccessSubtitle": "Byl jste odhlášen",
"logoutTitle": "Odhlásit",
"logoutUsernameSubtitle": "Jste přihlášen jako <code>{{username}}</code>. Pro odhlášení klikněte na tlačítko níže.",
"logoutOauthSubtitle": "Jste přihlášen jako <code>{{username}}</code> pomocí {{provider}} poskytovatele OAuth. Pro odhlášení klikněte na tlačítko níže.",
"notFoundTitle": "Stránka nenalezena",
"notFoundSubtitle": "Stránka, kterou hledáte, neexistuje.",
"notFoundButton": "Jít domů",
"totpFailTitle": "Nepodařilo se ověřit kód",
"totpFailSubtitle": "Zkontrolujte prosím kód a zkuste to znovu",
"totpSuccessTitle": "Ověřeno",
"totpSuccessSubtitle": "Přesměrování do aplikace",
"totpTitle": "Zadejte TOTP kód",
"totpSubtitle": "Zadejte prosím kód z ověřovací aplikace.",
"unauthorizedTitle": "Nepovoleno",
"unauthorizedResourceSubtitle": "Uživatel s uživatelským jménem <code>{{username}}</code> není oprávněn k přístupu ke zdroji <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "Uživatel s uživatelským jménem <code>{{username}}</code> není oprávněn k přihlášení.",
"unauthorizedGroupsSubtitle": "Uživatel s uživatelským jménem <code>{{username}}</code> není ve skupině potřebné k přístupu ke zdroji <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Vaše IP adresa <code>{{ip}}</code> není oprávněna k přístupu ke zdroji <code>{{resource}}</code>.",
"unauthorizedButton": "Zkusit znovu",
"cancelTitle": "Zrušit",
"forgotPasswordTitle": "Zapomněli jste heslo?",
"failedToFetchProvidersTitle": "Nepodařilo se načíst poskytovatele ověřování. Zkontrolujte prosím konfiguraci.",
"errorTitle": "Došlo k chybě",
"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`.",
"fieldRequired": "Toto pole je povinné",
"invalidInput": "Neplatný údaj",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Velkommen tilbage, log ind med",
"loginTitleSimple": "Velkommen tilbage, log venligst ind",
"loginDivider": "Eller",
"loginUsername": "Brugernavn",
"loginPassword": "Adgangskode",
"loginSubmit": "Log ind",
"loginFailTitle": "Login mislykkedes",
"loginFailSubtitle": "Tjek venligst dit brugernavn og adgangskode",
"loginFailRateLimit": "Du har forsøgt at logge ind for mange gange, prøv igen senere",
"loginSuccessTitle": "Logget ind",
"loginSuccessSubtitle": "Velkommen tilbage!",
"loginOauthFailTitle": "Der opstod en fejl",
"loginOauthFailSubtitle": "Kunne ikke hente OAuth-URL",
"loginOauthSuccessTitle": "Omdirigerer",
"loginOauthSuccessSubtitle": "Omdirigerer til din OAuth-udbyder",
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
"loginOauthAutoRedirectButton": "Redirect now",
"continueTitle": "Fortsæt",
"continueRedirectingTitle": "Omdirigerer...",
"continueRedirectingSubtitle": "Du bør blive omdirigeret til appen snart",
"continueRedirectManually": "Redirect me manually",
"continueInsecureRedirectTitle": "Usikker omdirigering",
"continueInsecureRedirectSubtitle": "Du forsøger at omdirigere fra <code>https</code> til <code>http</code>, som ikke er sikker. Er du sikker på, at du vil fortsætte?",
"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?",
"logoutFailTitle": "Log ud mislykkedes",
"logoutFailSubtitle": "Prøv venligst igen",
"logoutSuccessTitle": "Logget ud",
"logoutSuccessSubtitle": "Du er blevet logget ud",
"logoutTitle": "Log ud",
"logoutUsernameSubtitle": "Du er i øjeblikket logget ind som <code>{{username}}</code>. Klik på knappen nedenfor for at logge ud.",
"logoutOauthSubtitle": "Du er i øjeblikket logget ind som <code>{{username}}</code> via {{provider}} OAuth-udbyderen. Klik på knappen nedenfor for at logge ud.",
"notFoundTitle": "Siden blev ikke fundet",
"notFoundSubtitle": "Siden du leder efter, findes ikke.",
"notFoundButton": "Gå til forsiden",
"totpFailTitle": "Verificering af kode mislykkedes",
"totpFailSubtitle": "Tjek venligst din kode og prøv igen",
"totpSuccessTitle": "Verificeret",
"totpSuccessSubtitle": "Omdirigerer til din app",
"totpTitle": "Indtast din TOTP-kode",
"totpSubtitle": "Indtast venligst koden fra din to-faktor-godkendelsesapp.",
"unauthorizedTitle": "Uautoriseret",
"unauthorizedResourceSubtitle": "Brugeren med brugernavnet <code>{{username}}</code> har ikke tilladelse til at tilgå ressourcen <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "Brugeren med brugernavnet <code>{{username}}</code> har ikke tilladelse til at logge ind.",
"unauthorizedGroupsSubtitle": "Brugeren med brugernavnet <code>{{username}}</code> er ikke i de grupper, som ressourcen <code>{{resource}}</code> kræver.",
"unauthorizedIpSubtitle": "Din IP adresse <code>{{ip}}</code> er ikke autoriseret til at tilgå ressourcen <code>{{resource}}</code>.",
"unauthorizedButton": "Prøv igen",
"cancelTitle": "Annuller",
"forgotPasswordTitle": "Glemt din adgangskode?",
"failedToFetchProvidersTitle": "Kunne ikke indlæse godkendelsesudbydere. Tjek venligst din konfiguration.",
"errorTitle": "Der opstod en fejl",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Willkommen zurück, logge dich ein mit",
"loginTitleSimple": "Willkommen zurück, bitte anmelden",
"loginDivider": "Oder",
"loginUsername": "Benutzername",
"loginPassword": "Passwort",
"loginSubmit": "Anmelden",
"loginFailTitle": "Login fehlgeschlagen",
"loginFailSubtitle": "Bitte überprüfe deinen Benutzernamen und Passwort",
"loginFailRateLimit": "Zu viele fehlgeschlagene Loginversuche. Versuche es später erneut",
"loginSuccessTitle": "Angemeldet",
"loginSuccessSubtitle": "Willkommen zurück!",
"loginOauthFailTitle": "Ein Fehler ist aufgetreten",
"loginOauthFailSubtitle": "Fehler beim Abrufen der OAuth-URL",
"loginOauthSuccessTitle": "Leite weiter",
"loginOauthSuccessSubtitle": "Weiterleitung zu Ihrem OAuth-Provider",
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
"loginOauthAutoRedirectButton": "Redirect now",
"continueTitle": "Weiter",
"continueRedirectingTitle": "Leite weiter...",
"continueRedirectingSubtitle": "Sie sollten in Kürze zur App weitergeleitet werden",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Abmelden fehlgeschlagen",
"logoutFailSubtitle": "Bitte versuchen Sie es erneut",
"logoutSuccessTitle": "Abgemeldet",
"logoutSuccessSubtitle": "Sie wurden abgemeldet",
"logoutTitle": "Abmelden",
"logoutUsernameSubtitle": "Sie sind derzeit als <code>{{username}}</code> angemeldet. Klicken Sie auf den Button unten, um sich abzumelden.",
"logoutOauthSubtitle": "Sie sind derzeit als <code>{{username}}</code> über den OAuth-Anbieter {{provider}} angemeldet. Klicken Sie auf den Button unten, um sich abzumelden.",
"notFoundTitle": "Seite nicht gefunden",
"notFoundSubtitle": "Die gesuchte Seite existiert nicht.",
"notFoundButton": "Zurück",
"totpFailTitle": "Fehler beim Verifizieren des Codes",
"totpFailSubtitle": "Bitte überprüfen Sie Ihren Code und versuchen Sie es erneut",
"totpSuccessTitle": "Verifiziert",
"totpSuccessSubtitle": "Leite zur App weiter",
"totpTitle": "Geben Sie Ihren TOTP Code ein",
"totpSubtitle": "Bitte geben Sie den Code aus Ihrer Authenticator-App ein.",
"unauthorizedTitle": "Unautorisiert",
"unauthorizedResourceSubtitle": "Der Benutzer mit Benutzername <code>{{username}}</code> ist nicht berechtigt, auf die Ressource <code>{{resource}}</code> zuzugreifen.",
"unauthorizedLoginSubtitle": "Der Benutzer mit Benutzername <code>{{username}}</code> ist nicht berechtigt, sich anzumelden.",
"unauthorizedGroupsSubtitle": "Der Benutzer mit Benutzername <code>{{username}}</code> ist nicht in den Gruppen, die von der Ressource <code>{{resource}}</code> benötigt werden.",
"unauthorizedIpSubtitle": "Ihre IP-Adresse <code>{{ip}}</code> ist nicht berechtigt, auf die Ressource <code>{{resource}}</code> zuzugreifen.",
"unauthorizedButton": "Erneut versuchen",
"cancelTitle": "Abbrechen",
"forgotPasswordTitle": "Passwort vergessen?",
"failedToFetchProvidersTitle": "Fehler beim Laden der Authentifizierungsanbieter. Bitte überprüfen Sie Ihre Konfiguration.",
"errorTitle": "Ein Fehler ist aufgetreten",
"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.",
"fieldRequired": "Dieses Feld ist notwendig",
"invalidInput": "Ungültige Eingabe",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Καλώς ήρθατε, συνδεθείτε με",
"loginTitleSimple": "Καλώς ορίσατε, παρακαλώ συνδεθείτε",
"loginDivider": "Ή",
"loginUsername": "Όνομα Χρήστη",
"loginPassword": "Κωδικός",
"loginSubmit": "Είσοδος",
"loginFailTitle": "Αποτυχία σύνδεσης",
"loginFailSubtitle": "Παρακαλώ ελέγξτε το όνομα χρήστη και τον κωδικό πρόσβασης",
"loginFailRateLimit": "Αποτύχατε να συνδεθείτε πάρα πολλές φορές. Παρακαλώ προσπαθήστε ξανά αργότερα",
"loginSuccessTitle": "Συνδεδεμένος",
"loginSuccessSubtitle": "Καλώς ήρθατε!",
"loginOauthFailTitle": "Παρουσιάστηκε ένα σφάλμα",
"loginOauthFailSubtitle": "Αποτυχία λήψης OAuth URL",
"loginOauthSuccessTitle": "Ανακατεύθυνση",
"loginOauthSuccessSubtitle": "Ανακατεύθυνση στον πάροχο OAuth σας",
"loginOauthAutoRedirectTitle": "Αυτόματη Ανακατεύθυνση OAuth",
"loginOauthAutoRedirectSubtitle": "Θα ανακατευθυνθείτε αυτόματα στον πάροχο OAuth σας για να επαληθευτείτε.",
"loginOauthAutoRedirectButton": "Ανακατεύθυνση τώρα",
"continueTitle": "Συνέχεια",
"continueRedirectingTitle": "Ανακατεύθυνση...",
"continueRedirectingSubtitle": "Θα πρέπει να μεταφερθείτε σύντομα στην εφαρμογή σας",
"continueRedirectManually": "Χειροκίνητη ανακατεύθυνση",
"continueInsecureRedirectTitle": "Μη ασφαλής ανακατεύθυνση",
"continueInsecureRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε από <code>https</code> σε <code>http</code> το οποίο δεν είναι ασφαλές. Είστε σίγουροι ότι θέλετε να συνεχίσετε;",
"continueUntrustedRedirectTitle": "Μη έμπιστη ανακατεύθυνση",
"continueUntrustedRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε σε ένα domain που δεν ταιριάζει με το ρυθμισμένο domain σας (<code>{{cookieDomain}}</code>). Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
"logoutFailTitle": "Αποτυχία αποσύνδεσης",
"logoutFailSubtitle": "Παρακαλώ δοκιμάστε ξανά",
"logoutSuccessTitle": "Αποσυνδεδεμένος",
"logoutSuccessSubtitle": "Έχετε αποσυνδεθεί",
"logoutTitle": "Αποσύνδεση",
"logoutUsernameSubtitle": "Αυτή τη στιγμή είστε συνδεδεμένοι ως <code>{{username}}</code>. Κάντε κλικ στο παρακάτω κουμπί για να αποσυνδεθείτε.",
"logoutOauthSubtitle": "Αυτή τη στιγμή είστε συνδεδεμένοι ως <code>{{username}}</code> χρησιμοποιώντας την υπηρεσία παροχής {{provider}} OAuth. Κάντε κλικ στο παρακάτω κουμπί για να αποσυνδεθείτε.",
"notFoundTitle": "Η σελίδα δε βρέθηκε",
"notFoundSubtitle": "Η σελίδα που ψάχνετε δεν υπάρχει.",
"notFoundButton": "Μετάβαση στην αρχική",
"totpFailTitle": "Αποτυχία επαλήθευσης κωδικού",
"totpFailSubtitle": "Παρακαλώ ελέγξτε τον κώδικά σας και προσπαθήστε ξανά",
"totpSuccessTitle": "Επαληθεύθηκε",
"totpSuccessSubtitle": "Ανακατεύθυνση στην εφαρμογή σας",
"totpTitle": "Εισάγετε τον κωδικό TOTP",
"totpSubtitle": "Παρακαλώ εισάγετε τον κωδικό από την εφαρμογή ελέγχου ταυτότητας.",
"unauthorizedTitle": "Μη εξουσιοδοτημένο",
"unauthorizedResourceSubtitle": "Ο χρήστης με όνομα χρήστη <code>{{username}}</code> δεν έχει άδεια πρόσβασης στον πόρο <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "Ο χρήστης με όνομα χρήστη <code>{{username}}</code> δεν είναι εξουσιοδοτημένος να συνδεθεί.",
"unauthorizedGroupsSubtitle": "Ο χρήστης με όνομα χρήστη <code>{{username}}</code> δεν είναι στις ομάδες που απαιτούνται από τον πόρο <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Η διεύθυνση IP σας <code>{{ip}}</code> δεν είναι εξουσιοδοτημένη να έχει πρόσβαση στον πόρο <code>{{resource}}</code>.",
"unauthorizedButton": "Προσπαθήστε ξανά",
"cancelTitle": "Ακύρωση",
"forgotPasswordTitle": "Ξεχάσατε το συνθηματικό σας;",
"failedToFetchProvidersTitle": "Αποτυχία φόρτωσης παρόχων πιστοποίησης. Παρακαλώ ελέγξτε τις ρυθμίσεις σας.",
"errorTitle": "Παρουσιάστηκε ένα σφάλμα",
"errorSubtitle": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια εκτέλεσης αυτής της ενέργειας. Ελέγξτε την κονσόλα για περισσότερες πληροφορίες.",
"forgotPasswordMessage": "Μπορείτε να επαναφέρετε τον κωδικό πρόσβασής σας αλλάζοντας τη μεταβλητή περιβάλλοντος `USERS`.",
"fieldRequired": "Αυτό το πεδίο είναι υποχρεωτικό",
"invalidInput": "Μη έγκυρη καταχώρηση",
"domainWarningTitle": "Μη έγκυρο domain",
"domainWarningSubtitle": "Αυτή η εφαρμογή έχει ρυθμιστεί για πρόσβαση από <code>{{appUrl}}</code>, αλλά <code>{{currentUrl}}</code> χρησιμοποιείται. Αν συνεχίσετε, μπορεί να αντιμετωπίσετε προβλήματα με την ταυτοποίηση.",
"ignoreTitle": "Παράβλεψη",
"goToCorrectDomainTitle": "Μεταβείτε στο σωστό domain"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"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...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "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.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"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...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "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.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Bienvenido de vuelta, inicie sesión con",
"loginTitleSimple": "Bienvenido de vuelta, por favor inicie sesión",
"loginDivider": "O",
"loginUsername": "Usuario",
"loginPassword": "Contraseña",
"loginSubmit": "Iniciar sesión",
"loginFailTitle": "Fallo al iniciar sesión",
"loginFailSubtitle": "Por favor revise su usuario y contraseña",
"loginFailRateLimit": "Muchos inicios de sesión consecutivos fallidos. Por favor inténtelo más tarde",
"loginSuccessTitle": "Sesión iniciada",
"loginSuccessSubtitle": "¡Bienvenido de vuelta!",
"loginOauthFailTitle": "Ocurrió un error",
"loginOauthFailSubtitle": "Error al obtener la URL de OAuth",
"loginOauthSuccessTitle": "Redireccionando",
"loginOauthSuccessSubtitle": "Redireccionando a tu proveedor de OAuth",
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
"loginOauthAutoRedirectButton": "Redirect now",
"continueTitle": "Continuar",
"continueRedirectingTitle": "Redireccionando...",
"continueRedirectingSubtitle": "Pronto será redirigido a la aplicación",
"continueRedirectManually": "Redirect me manually",
"continueInsecureRedirectTitle": "Redirección insegura",
"continueInsecureRedirectSubtitle": "Está intentando redirigir desde <code>https</code> a <code>http</code> lo cual no es seguro. ¿Está seguro que desea continuar?",
"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?",
"logoutFailTitle": "Fallo al cerrar sesión",
"logoutFailSubtitle": "Por favor intente nuevamente",
"logoutSuccessTitle": "Sesión cerrada",
"logoutSuccessSubtitle": "Su sesión ha sido cerrada",
"logoutTitle": "Cerrar sesión",
"logoutUsernameSubtitle": "Actualmente está conectado como <code>{{username}}</code>. Haga clic en el botón de abajo para cerrar sesión.",
"logoutOauthSubtitle": "Actualmente está conectado como <code>{{username}}</code> usando {{provider}} como su proveedor de OAuth. Haga clic en el botón de abajo para cerrar sesión.",
"notFoundTitle": "Página no encontrada",
"notFoundSubtitle": "La página que está buscando no existe.",
"notFoundButton": "Volver al inicio",
"totpFailTitle": "Error al verificar código",
"totpFailSubtitle": "Por favor compruebe su código e inténtelo de nuevo",
"totpSuccessTitle": "Verificado",
"totpSuccessSubtitle": "Redirigiendo a su aplicación",
"totpTitle": "Ingrese su código TOTP",
"totpSubtitle": "Por favor introduzca el código de su aplicación de autenticación.",
"unauthorizedTitle": "No autorizado",
"unauthorizedResourceSubtitle": "El usuario con nombre de usuario <code>{{username}}</code> no está autorizado para acceder al recurso <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "El usuario con nombre de usuario <code>{{username}}</code> no está autorizado a iniciar sesión.",
"unauthorizedGroupsSubtitle": "El usuario con nombre de usuario <code>{{username}}</code> no está en los grupos requeridos por el recurso <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Inténtelo de nuevo",
"cancelTitle": "Cancelar",
"forgotPasswordTitle": "¿Olvidó su contraseña?",
"failedToFetchProvidersTitle": "Error al cargar los proveedores de autenticación. Por favor revise su configuración.",
"errorTitle": "Ha ocurrido un error",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"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...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "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.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Bienvenue, connectez-vous avec",
"loginTitleSimple": "De retour parmi nous, veuillez vous connecter",
"loginDivider": "Ou",
"loginUsername": "Nom d'utilisateur",
"loginPassword": "Mot de passe",
"loginSubmit": "Se connecter",
"loginFailTitle": "Échec de la connexion",
"loginFailSubtitle": "Veuillez vérifier votre nom d'utilisateur et votre mot de passe",
"loginFailRateLimit": "Vous avez échoué trop de fois à vous connecter. Veuillez réessayer ultérieurement",
"loginSuccessTitle": "Connecté",
"loginSuccessSubtitle": "Bienvenue!",
"loginOauthFailTitle": "Une erreur s'est produite",
"loginOauthFailSubtitle": "Impossible d'obtenir l'URL OAuth",
"loginOauthSuccessTitle": "Redirection",
"loginOauthSuccessSubtitle": "Redirection vers votre fournisseur OAuth",
"loginOauthAutoRedirectTitle": "Redirection automatique OAuth",
"loginOauthAutoRedirectSubtitle": "Vous allez être automatiquement redirigé vers votre fournisseur OAuth pour vous authentifier.",
"loginOauthAutoRedirectButton": "Rediriger",
"continueTitle": "Continuer",
"continueRedirectingTitle": "Redirection...",
"continueRedirectingSubtitle": "Vous devriez être redirigé vers l'application bientôt",
"continueRedirectManually": "Redirection manuelle",
"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 ?",
"continueUntrustedRedirectTitle": "Redirection non sécurisée",
"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 ?",
"logoutFailTitle": "Échec de la déconnexion",
"logoutFailSubtitle": "Veuillez réessayer",
"logoutSuccessTitle": "Déconnecté",
"logoutSuccessSubtitle": "Vous avez été déconnecté",
"logoutTitle": "Déconnexion",
"logoutUsernameSubtitle": "Vous êtes actuellement connecté en tant que <code>{{username}}</code>. Cliquez sur le bouton ci-dessous pour vous déconnecter.",
"logoutOauthSubtitle": "Vous êtes actuellement connecté en tant que <code>{{username}}</code> via le fournisseur OAuth {{provider}}. Cliquez sur le bouton ci-dessous pour vous déconnecter.",
"notFoundTitle": "Page introuvable",
"notFoundSubtitle": "La page recherchée n'existe pas.",
"notFoundButton": "Retour à la page d'accueil",
"totpFailTitle": "Échec de la vérification du code",
"totpFailSubtitle": "Veuillez vérifier votre code et réessayer",
"totpSuccessTitle": "Vérifié",
"totpSuccessSubtitle": "Redirection vers votre application",
"totpTitle": "Saisissez votre code TOTP",
"totpSubtitle": "Veuillez saisir le code de votre application d'authentification.",
"unauthorizedTitle": "Non autorisé",
"unauthorizedResourceSubtitle": "L'utilisateur avec le nom d'utilisateur <code>{{username}}</code> n'est pas autorisé à accéder à la ressource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "L'utilisateur avec le nom d'utilisateur <code>{{username}}</code> n'est pas autorisé à se connecter.",
"unauthorizedGroupsSubtitle": "L'utilisateur avec le nom d'utilisateur <code>{{username}}</code> n'appartient pas aux groupes requis par la ressource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Votre adresse IP <code>{{ip}}</code> n'est pas autorisée à accéder à la ressource <code>{{resource}}</code>.",
"unauthorizedButton": "Réessayer",
"cancelTitle": "Annuler",
"forgotPasswordTitle": "Mot de passe oublié ?",
"failedToFetchProvidersTitle": "Échec du chargement des fournisseurs d'authentification. Veuillez vérifier votre configuration.",
"errorTitle": "Une erreur est survenue",
"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`.",
"fieldRequired": "Ce champ est obligatoire",
"invalidInput": "Saisie non valide",
"domainWarningTitle": "Domaine invalide",
"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.",
"ignoreTitle": "Ignorer",
"goToCorrectDomainTitle": "Aller au bon domaine"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"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...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "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.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"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...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "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.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"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...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "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.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"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...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "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.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"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...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "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.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welkom terug, log in met",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Gebruikersnaam",
"loginPassword": "Wachtwoord",
"loginSubmit": "Log in",
"loginFailTitle": "Mislukt om in te loggen",
"loginFailSubtitle": "Controleer je gebruikersnaam en wachtwoord",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Ingelogd",
"loginSuccessSubtitle": "Welkom terug!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Fout bij het ophalen van OAuth URL",
"loginOauthSuccessTitle": "Omleiden",
"loginOauthSuccessSubtitle": "Omleiden naar je OAuth provider",
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
"loginOauthAutoRedirectButton": "Redirect now",
"continueTitle": "Ga verder",
"continueRedirectingTitle": "Omleiden...",
"continueRedirectingSubtitle": "Je wordt naar de app doorgestuurd",
"continueRedirectManually": "Redirect me manually",
"continueInsecureRedirectTitle": "Onveilige doorverwijzing",
"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",
"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",
"logoutFailSubtitle": "Probeer het opnieuw",
"logoutSuccessTitle": "Afgemeld",
"logoutSuccessSubtitle": "Je bent afgemeld",
"logoutTitle": "Afmelden",
"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.",
"notFoundTitle": "Pagina niet gevonden",
"notFoundSubtitle": "De pagina die je zoekt bestaat niet.",
"notFoundButton": "Naar startpagina",
"totpFailTitle": "Verifiëren van code mislukt",
"totpFailSubtitle": "Controleer je code en probeer het opnieuw",
"totpSuccessTitle": "Geverifiëerd",
"totpSuccessSubtitle": "Omleiden naar je app",
"totpTitle": "Voer je TOTP-code in",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Ongeautoriseerd",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Opnieuw proberen",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"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...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "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.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Witaj ponownie, zaloguj się przez",
"loginTitleSimple": "Witaj ponownie, zaloguj się",
"loginDivider": "Lub",
"loginUsername": "Nazwa użytkownika",
"loginPassword": "Hasło",
"loginSubmit": "Zaloguj się",
"loginFailTitle": "Nie udało się zalogować",
"loginFailSubtitle": "Sprawdź swoją nazwę użytkownika i hasło",
"loginFailRateLimit": "Zbyt wiele razy nie udało Ci się zalogować. Spróbuj ponownie później",
"loginSuccessTitle": "Zalogowano",
"loginSuccessSubtitle": "Witaj ponownie!",
"loginOauthFailTitle": "Wystąpił błąd",
"loginOauthFailSubtitle": "Nie udało się uzyskać adresu URL OAuth",
"loginOauthSuccessTitle": "Przekierowywanie",
"loginOauthSuccessSubtitle": "Przekierowywanie do Twojego dostawcy OAuth",
"loginOauthAutoRedirectTitle": "Automatyczne przekierowanie OAuth",
"loginOauthAutoRedirectSubtitle": "Nastąpi automatyczne przekierowanie do dostawcy OAuth w celu uwierzytelnienia.",
"loginOauthAutoRedirectButton": "Przekieruj teraz",
"continueTitle": "Kontynuuj",
"continueRedirectingTitle": "Przekierowywanie...",
"continueRedirectingSubtitle": "Wkrótce powinieneś zostać przekierowany do aplikacji",
"continueRedirectManually": "Przekieruj mnie ręcznie",
"continueInsecureRedirectTitle": "Niezabezpieczone przekierowanie",
"continueInsecureRedirectSubtitle": "Próbujesz przekierować z <code>https</code> do <code>http</code>, co nie jest bezpieczne. Czy na pewno chcesz kontynuować?",
"continueUntrustedRedirectTitle": "Niezaufane przekierowanie",
"continueUntrustedRedirectSubtitle": "Próbujesz przekierować do domeny, która nie pasuje do skonfigurowanej domeny (<code>{{cookieDomain}}</code>). Czy na pewno chcesz kontynuować?",
"logoutFailTitle": "Nie udało się wylogować",
"logoutFailSubtitle": "Spróbuj ponownie",
"logoutSuccessTitle": "Wylogowano",
"logoutSuccessSubtitle": "Zostałeś wylogowany",
"logoutTitle": "Wyloguj się",
"logoutUsernameSubtitle": "Jesteś obecnie zalogowany jako <code>{{username}}</code>. Kliknij poniższy przycisk, aby się wylogować.",
"logoutOauthSubtitle": "Obecnie jesteś zalogowany jako <code>{{username}}</code> przy użyciu dostawcy {{provider}} OAuth. Kliknij poniższy przycisk, aby się wylogować.",
"notFoundTitle": "Nie znaleziono strony",
"notFoundSubtitle": "Strona, której szukasz nie istnieje.",
"notFoundButton": "Wróć do strony głównej",
"totpFailTitle": "Nie udało się zweryfikować kodu",
"totpFailSubtitle": "Sprawdź swój kod i spróbuj ponownie",
"totpSuccessTitle": "Zweryfikowano",
"totpSuccessSubtitle": "Przekierowywanie do aplikacji",
"totpTitle": "Wprowadź kod TOTP",
"totpSubtitle": "Wpisz kod z aplikacji uwierzytelniającej.",
"unauthorizedTitle": "Nieautoryzowany",
"unauthorizedResourceSubtitle": "Użytkownik o nazwie użytkownika <code>{{username}}</code> nie ma uprawnień dostępu do zasobu <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "Użytkownik o nazwie <code>{{username}}</code> nie jest upoważniony do zalogowania się.",
"unauthorizedGroupsSubtitle": "Użytkownik o nazwie <code>{{username}}</code> nie należy do grup wymaganych przez zasób <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Twój adres IP <code>{{ip}}</code> nie ma autoryzacji do dostępu do zasobu <code>{{resource}}</code>.",
"unauthorizedButton": "Spróbuj ponownie",
"cancelTitle": "Anuluj",
"forgotPasswordTitle": "Nie pamiętasz hasła?",
"failedToFetchProvidersTitle": "Nie udało się załadować dostawców uwierzytelniania. Sprawdź swoją konfigurację.",
"errorTitle": "Wystąpił błąd",
"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`.",
"fieldRequired": "To pole jest wymagane",
"invalidInput": "Nieprawidłowe dane wejściowe",
"domainWarningTitle": "Nieprawidłowa domena",
"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.",
"ignoreTitle": "Zignoruj",
"goToCorrectDomainTitle": "Przejdź do prawidłowej domeny"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Bem-vindo de volta, acesse com",
"loginTitleSimple": "Bem-vindo de volta, faça o login",
"loginDivider": "Ou",
"loginUsername": "Nome de usuário",
"loginPassword": "Senha",
"loginSubmit": "Entrar",
"loginFailTitle": "Falha ao iniciar sessão",
"loginFailSubtitle": "Por favor, verifique seu usuário e senha",
"loginFailRateLimit": "Você falhou em iniciar sessão muitas vezes, por favor tente novamente mais tarde",
"loginSuccessTitle": "Sessão Iniciada",
"loginSuccessSubtitle": "Bem-vindo de volta!",
"loginOauthFailTitle": "Ocorreu um erro",
"loginOauthFailSubtitle": "Falha ao obter URL de OAuth",
"loginOauthSuccessTitle": "Redirecionando",
"loginOauthSuccessSubtitle": "Redirecionando para seu provedor OAuth",
"loginOauthAutoRedirectTitle": "Redirecionamento automático do OAuth",
"loginOauthAutoRedirectSubtitle": "Você será automaticamente redirecionado para seu provedor OAuth para autenticar.",
"loginOauthAutoRedirectButton": "Redirecionar agora",
"continueTitle": "Continuar",
"continueRedirectingTitle": "Redirecionando...",
"continueRedirectingSubtitle": "Você deve ser redirecionado para o aplicativo em breve",
"continueRedirectManually": "Redirecionar-me manualmente",
"continueInsecureRedirectTitle": "Redirecionamento inseguro",
"continueInsecureRedirectSubtitle": "Você está tentando redirecionar de <Code>https</Code> para <Code>http</Code>, você tem certeza que deseja continuar?",
"continueUntrustedRedirectTitle": "Redirecionamento não confiável",
"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?",
"logoutFailTitle": "Falha ao encerrar sessão",
"logoutFailSubtitle": "Por favor, tente novamente",
"logoutSuccessTitle": "Sessão encerrada",
"logoutSuccessSubtitle": "Você foi desconectado",
"logoutTitle": "Sair",
"logoutUsernameSubtitle": "Você está atualmente logado como <Code>{{username}}</Code>, clique no botão abaixo para sair.",
"logoutOauthSubtitle": "Você está atualmente logado como <Code>{{username}}</Code> usando o provedor {{provider}} OAuth, clique no botão abaixo para sair.",
"notFoundTitle": "Página não encontrada",
"notFoundSubtitle": "A página que você está procurando não existe.",
"notFoundButton": "Voltar para a tela inicial",
"totpFailTitle": "Falha ao verificar código",
"totpFailSubtitle": "Por favor, verifique seu código e tente novamente",
"totpSuccessTitle": "Verificado",
"totpSuccessSubtitle": "Redirecionando para o seu aplicativo",
"totpTitle": "Insira o seu código TOTP",
"totpSubtitle": "Por favor, insira o código do seu aplicativo de autenticação.",
"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>.",
"unauthorizedLoginSubtitle": "O usuário com o nome <code>{{username}}</code> não está autorizado a acessar.",
"unauthorizedGroupsSubtitle": "O usuário <code>{{username}}</code> não está autorizado a acessar o recurso <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Seu endereço IP <code>{{ip}}</code> não está autorizado a acessar o recurso <code>{{resource}}</code>.",
"unauthorizedButton": "Tentar novamente",
"cancelTitle": "Cancelar",
"forgotPasswordTitle": "Esqueceu sua senha?",
"failedToFetchProvidersTitle": "Falha ao carregar provedores de autenticação. Verifique sua configuração.",
"errorTitle": "Ocorreu um erro",
"errorSubtitle": "Ocorreu um erro ao tentar executar esta ação. Por favor, verifique o console para mais informações.",
"forgotPasswordMessage": "Você pode redefinir sua senha alterando a variável de ambiente `USERS`.",
"fieldRequired": "Este campo é obrigatório",
"invalidInput": "Entrada Inválida",
"domainWarningTitle": "Domínio inválido",
"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": "Ignorar",
"goToCorrectDomainTitle": "Ir para o domínio correto"
}

View File

@@ -1,62 +0,0 @@
{
"loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"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...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectManually": "Redirect me manually",
"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?",
"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?",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "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.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"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>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"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.",
"fieldRequired": "This field is required",
"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"
}

Some files were not shown because too many files have changed in this diff Show More