Compare commits
198 Commits
v2.0.2-bet
...
v3.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2db7795eb7 | ||
|
|
dd5a9e2216 | ||
|
|
00d1543f08 | ||
|
|
d1eeb8c7f7 | ||
|
|
a98a91a394 | ||
|
|
5278fbea68 | ||
|
|
773942dc3b | ||
|
|
83483d6374 | ||
|
|
aab01b3195 | ||
|
|
fe5e07139f | ||
|
|
93a75324b8 | ||
|
|
67a01c196f | ||
|
|
483b1de701 | ||
|
|
40ceed6686 | ||
|
|
3878c629c6 | ||
|
|
31e874a34f | ||
|
|
74a346349a | ||
|
|
a9e8bf89a9 | ||
|
|
f824b84787 | ||
|
|
71b0c301f6 | ||
|
|
1c738b718a | ||
|
|
4dc6bc0c98 | ||
|
|
3436466cff | ||
|
|
84f550023a | ||
|
|
9923eb9b8f | ||
|
|
db43f1cb7a | ||
|
|
bedef0bbf2 | ||
|
|
2bfed23db3 | ||
|
|
85ad0d19c7 | ||
|
|
34b1c97db0 | ||
|
|
dc731cff10 | ||
|
|
ab4efdc66c | ||
|
|
4f883260b9 | ||
|
|
0cc85b5d85 | ||
|
|
e11d14fda0 | ||
|
|
7413b3f931 | ||
|
|
b24aab17b1 | ||
|
|
9a7847bc10 | ||
|
|
ef24a760e4 | ||
|
|
8a3fe9fb2c | ||
|
|
04b0a64b18 | ||
|
|
0774012058 | ||
|
|
2253b1bf68 | ||
|
|
10c6f7236d | ||
|
|
45d9d8c1d4 | ||
|
|
ea5b4cefbd | ||
|
|
02faabf688 | ||
|
|
eb36b2211b | ||
|
|
0761c2f5c1 | ||
|
|
476a455329 | ||
|
|
5ec60b8e39 | ||
|
|
5d190fa686 | ||
|
|
1fb9f13c16 | ||
|
|
4a077da11d | ||
|
|
adc8731fb7 | ||
|
|
d9cb738132 | ||
|
|
aeb827265b | ||
|
|
04a0dbb71e | ||
|
|
ed75b6b880 | ||
|
|
f7393da3bb | ||
|
|
529152f70b | ||
|
|
fcc45f6f61 | ||
|
|
8c7856b28c | ||
|
|
77e9fafa32 | ||
|
|
01e2b5e63d | ||
|
|
2d6baef810 | ||
|
|
afad78b7da | ||
|
|
74cd886e9c | ||
|
|
df34b13f25 | ||
|
|
61dfc91110 | ||
|
|
130e6facb7 | ||
|
|
525f4f3041 | ||
|
|
8a21345706 | ||
|
|
1169c633cc | ||
|
|
2242c9c1e6 | ||
|
|
939919df39 | ||
|
|
a579cf37ce | ||
|
|
2647aa07b4 | ||
|
|
f68c580e11 | ||
|
|
9b39a2b856 | ||
|
|
6d17ce699a | ||
|
|
20dbb35d44 | ||
|
|
36d9dd7354 | ||
|
|
5129f9bff8 | ||
|
|
496a56676d | ||
|
|
57e25524c7 | ||
|
|
614a9b468a | ||
|
|
94a5359080 | ||
|
|
38c5cd7b32 | ||
|
|
c664be5cc5 | ||
|
|
bafcb9a867 | ||
|
|
d322c13791 | ||
|
|
8e84e59c2f | ||
|
|
bd7e160e10 | ||
|
|
df849d5a5c | ||
|
|
5cf4e208c6 | ||
|
|
07ddd4f917 | ||
|
|
98abe514e1 | ||
|
|
e43bd651b6 | ||
|
|
6dc461c11e | ||
|
|
2ed90dfa34 | ||
|
|
14ce8ecf98 | ||
|
|
46526b564e | ||
|
|
d82e959734 | ||
|
|
187e46eae5 | ||
|
|
fe5f26aea5 | ||
|
|
69a7972cd6 | ||
|
|
354668b016 | ||
|
|
c7000951fe | ||
|
|
33ea6c17d2 | ||
|
|
90801b77fa | ||
|
|
4076a7e464 | ||
|
|
fd32e737a3 | ||
|
|
3ccc831a1f | ||
|
|
f3471880ee | ||
|
|
52f189563b | ||
|
|
7e39cb0dfe | ||
|
|
be836c296c | ||
|
|
6f6b1f4862 | ||
|
|
ada3531565 | ||
|
|
939ed26fd0 | ||
|
|
da0641c115 | ||
|
|
753b95baff | ||
|
|
9dd9829058 | ||
|
|
ec67ea3807 | ||
|
|
3649d0d84e | ||
|
|
c0ffe3faf4 | ||
|
|
ad718d3ef8 | ||
|
|
38105d0b4e | ||
|
|
e13bd14eb6 | ||
|
|
43dc3f9aa6 | ||
|
|
00bfaa1cbe | ||
|
|
8cc0f8b31b | ||
|
|
631059be69 | ||
|
|
5188089673 | ||
|
|
47fff12bac | ||
|
|
a8c51b649f | ||
|
|
c2e8f1b473 | ||
|
|
bdf327cc9a | ||
|
|
46ec623d74 | ||
|
|
f97c4d7e78 | ||
|
|
d45b148725 | ||
|
|
33904f7f86 | ||
|
|
7e0bc84b0f | ||
|
|
fc3f8b5036 | ||
|
|
3030fc5fcf | ||
|
|
e4379cf3ed | ||
|
|
30aab17f06 | ||
|
|
7ee0b645e6 | ||
|
|
5c34ab96a9 | ||
|
|
cb6f93d879 | ||
|
|
df0c356511 | ||
|
|
d1c6ae1ba1 | ||
|
|
0f8d2e7fde | ||
|
|
0da82ae3fe | ||
|
|
f9ab9a6406 | ||
|
|
6f35923735 | ||
|
|
b1dc5cb4cc | ||
|
|
3c9bc8c67f | ||
|
|
b2f4041e09 | ||
|
|
eb4e157def | ||
|
|
cfe2a1967a | ||
|
|
c4ee269283 | ||
|
|
d18fba1ef3 | ||
|
|
acaee5357f | ||
|
|
38412e1962 | ||
|
|
302d9cf2fd | ||
|
|
caf9cde08f | ||
|
|
2473d3ce34 | ||
|
|
d8d347b45f | ||
|
|
a8da813374 | ||
|
|
6936987c6b | ||
|
|
9a16737a54 | ||
|
|
ddf40e6d63 | ||
|
|
9fd1c81f8b | ||
|
|
567e6f0b5b | ||
|
|
f7e7dee3da | ||
|
|
7a3a463489 | ||
|
|
e09f241364 | ||
|
|
d2ee382f92 | ||
|
|
4e8a2443a6 | ||
|
|
22777a16a1 | ||
|
|
0872556c1a | ||
|
|
daad2abc33 | ||
|
|
ce567ae3de | ||
|
|
87393d3c64 | ||
|
|
97830a309b | ||
|
|
fe594d2755 | ||
|
|
b3aac26644 | ||
|
|
c37f66abb9 | ||
|
|
2c4f086008 | ||
|
|
6e5f882e0b | ||
|
|
99268f80c9 | ||
|
|
dcd816b6c6 | ||
|
|
381f6ef76f | ||
|
|
8a8ba18ded | ||
|
|
29f0a94faf | ||
|
|
6602e8140b |
31
.env.example
Normal file
@@ -0,0 +1,31 @@
|
||||
PORT=3000
|
||||
ADDRESS=0.0.0.0
|
||||
SECRET=app_secret
|
||||
SECRET_FILE=app_secret_file
|
||||
APP_URL=http://localhost:3000
|
||||
USERS=your_user_password_hash
|
||||
USERS_FILE=users_file
|
||||
COOKIE_SECURE=false
|
||||
GITHUB_CLIENT_ID=github_client_id
|
||||
GITHUB_CLIENT_SECRET=github_client_secret
|
||||
GITHUB_CLIENT_SECRET_FILE=github_client_secret_file
|
||||
GOOGLE_CLIENT_ID=google_client_id
|
||||
GOOGLE_CLIENT_SECRET=google_client_secret
|
||||
GOOGLE_CLIENT_SECRET_FILE=google_client_secret_file
|
||||
GENERIC_CLIENT_ID=generic_client_id
|
||||
GENERIC_CLIENT_SECRET=generic_client_secret
|
||||
GENERIC_CLIENT_SECRET_FILE=generic_client_secret_file
|
||||
GENERIC_SCOPES=generic_scopes
|
||||
GENERIC_AUTH_URL=generic_auth_url
|
||||
GENERIC_TOKEN_URL=generic_token_url
|
||||
GENERIC_USER_URL=generic_user_url
|
||||
DISABLE_CONTINUE=false
|
||||
OAUTH_WHITELIST=
|
||||
GENERIC_NAME=My OAuth
|
||||
SESSION_EXPIRY=7200
|
||||
LOGIN_TIMEOUT=300
|
||||
LOGIN_MAX_RETRIES=5
|
||||
LOG_LEVEL=0
|
||||
APP_TITLE=Tinyauth SSO
|
||||
FORGOT_PASSWORD_MESSAGE=Some message about resetting the password
|
||||
OAUTH_AUTO_REDIRECT=none
|
||||
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
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.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
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.
|
||||
14
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "bun"
|
||||
directory: "/frontend"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
58
.github/workflows/alpha-release.yml
vendored
@@ -1,58 +0,0 @@
|
||||
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
@@ -1,58 +0,0 @@
|
||||
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 }}
|
||||
40
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
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 Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.23.2"
|
||||
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: |
|
||||
cd frontend
|
||||
bun install
|
||||
|
||||
- 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 -v ./...
|
||||
234
.github/workflows/release.yml
vendored
@@ -1,32 +1,100 @@
|
||||
name: Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
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)" >> $GITHUB_OUTPUT
|
||||
|
||||
build-docker:
|
||||
needs: get-tag
|
||||
binary-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.23.2"
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: |
|
||||
cd frontend
|
||||
bun install
|
||||
|
||||
- name: Install backend dependencies
|
||||
run: |
|
||||
go mod tidy
|
||||
|
||||
- name: Build frontend
|
||||
run: |
|
||||
cd frontend
|
||||
bun run build
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
CGO_ENABLED=0 go build -ldflags "-s -w" -o tinyauth-amd64
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tinyauth-amd64
|
||||
path: tinyauth-amd64
|
||||
|
||||
binary-build-arm:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.23.2"
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: |
|
||||
cd frontend
|
||||
bun install
|
||||
|
||||
- name: Install backend dependencies
|
||||
run: |
|
||||
go mod tidy
|
||||
|
||||
- name: Build frontend
|
||||
run: |
|
||||
cd frontend
|
||||
bun run build
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
CGO_ENABLED=0 go build -ldflags "-s -w" -o tinyauth-arm64
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tinyauth-arm64
|
||||
path: tinyauth-arm64
|
||||
|
||||
image-build:
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
@@ -35,21 +103,129 @@ 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:
|
||||
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
|
||||
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
|
||||
|
||||
release:
|
||||
needs: [get-tag, build-docker]
|
||||
runs-on: ubuntu-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-arm:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- name: Create release
|
||||
- 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
|
||||
|
||||
- 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-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
|
||||
tags: |
|
||||
type=semver,pattern={{version}},prefix=v
|
||||
type=semver,pattern={{major}},prefix=v
|
||||
type=semver,pattern={{major}}.{{minor}},prefix=v
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: ${{ runner.temp }}/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf 'ghcr.io/${{ github.repository_owner }}/tinyauth@sha256:%s ' *)
|
||||
|
||||
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:
|
||||
prerelease: false
|
||||
make_latest: false
|
||||
tag_name: ${{ needs.get-tag.outputs.tag }}
|
||||
files: binaries/*
|
||||
|
||||
31
.github/workflows/sponsors.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
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> '
|
||||
|
||||
- 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
|
||||
20
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
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
|
||||
98
.github/workflows/translations.yml
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
name: Publish translations
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- i18n_v*
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
get-branches:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
i18n-branches: ${{ steps.get-branches.outputs.result }}
|
||||
steps:
|
||||
- name: Get branches
|
||||
id: get-branches
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { data: repos } = await github.rest.repos.listBranches({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
})
|
||||
|
||||
const i18nBranches = repos.filter((branch) => branch.name.startsWith("i18n_v"))
|
||||
const i18nBranchNames = i18nBranches.map((branch) => branch.name)
|
||||
|
||||
return i18nBranchNames
|
||||
|
||||
get-translations:
|
||||
needs: get-branches
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
branch: ${{ fromJson(needs.get-branches.outputs.i18n-branches) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ matrix.branch }}
|
||||
|
||||
- name: Get translation version
|
||||
id: get-version
|
||||
run: |
|
||||
branch=${{ matrix.branch }}
|
||||
version=${branch#i18n_}
|
||||
echo "version=$version" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload translations
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.get-version.outputs.version }}
|
||||
path: frontend/src/lib/i18n/locales
|
||||
|
||||
build:
|
||||
needs: get-translations
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
|
||||
- name: Prepare output directory
|
||||
run: |
|
||||
mkdir -p dist/i18n/
|
||||
|
||||
- name: Download translations
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist/i18n/
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: dist
|
||||
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
16
.gitignore
vendored
@@ -5,11 +5,23 @@ internal/assets/dist
|
||||
tinyauth
|
||||
|
||||
# test docker compose
|
||||
docker-compose.test.yml
|
||||
docker-compose.test*
|
||||
|
||||
# users file
|
||||
users.txt
|
||||
|
||||
# secret test file
|
||||
secret.txt
|
||||
secret_oauth.txt
|
||||
secret_oauth.txt
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
|
||||
# apple stuff
|
||||
.DS_Store
|
||||
|
||||
# env
|
||||
.env
|
||||
|
||||
# tmp directory
|
||||
tmp
|
||||
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# 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.
|
||||
56
CONTRIBUTING.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Contributing
|
||||
|
||||
Contributing is relatively easy, you just need to follow the steps carefully and you will be up and running with a development server in less than 5 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 inside 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
|
||||
```
|
||||
|
||||
Then you can just make sure the domains are correct in the example docker compose file and run:
|
||||
|
||||
```sh
|
||||
docker compose -f docker-compose.dev.yml up --build
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> I would 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.
|
||||
37
Dockerfile
@@ -1,27 +1,27 @@
|
||||
# Site builder
|
||||
FROM oven/bun:1.1.45-alpine AS site-builder
|
||||
FROM oven/bun:1.2.11-alpine AS frontend-builder
|
||||
|
||||
WORKDIR /site
|
||||
WORKDIR /frontend
|
||||
|
||||
COPY ./site/package.json ./
|
||||
COPY ./site/bun.lockb ./
|
||||
COPY ./frontend/package.json ./
|
||||
COPY ./frontend/bun.lockb ./
|
||||
|
||||
RUN bun install
|
||||
|
||||
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 ./
|
||||
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 ./frontend/postcss.config.cjs ./
|
||||
|
||||
RUN bun run build
|
||||
|
||||
# Builder
|
||||
FROM golang:1.23-alpine3.21 AS builder
|
||||
FROM golang:1.24-alpine3.21 AS builder
|
||||
|
||||
WORKDIR /tinyauth
|
||||
|
||||
@@ -33,17 +33,22 @@ RUN go mod download
|
||||
COPY ./main.go ./
|
||||
COPY ./cmd ./cmd
|
||||
COPY ./internal ./internal
|
||||
COPY --from=site-builder /site/dist ./internal/assets/dist
|
||||
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
||||
|
||||
RUN CGO_ENABLED=0 go build
|
||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w"
|
||||
|
||||
# Runner
|
||||
FROM alpine:3.21 AS runner
|
||||
|
||||
WORKDIR /tinyauth
|
||||
|
||||
RUN apk add --no-cache curl
|
||||
|
||||
COPY --from=builder /tinyauth/tinyauth ./
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=5s \
|
||||
CMD curl -f http://localhost:3000/api/healthcheck || exit 1
|
||||
|
||||
ENTRYPOINT ["./tinyauth"]
|
||||
19
Dockerfile.dev
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM golang:1.24-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"]
|
||||
2
FUNDING.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
github: steveiliop56
|
||||
buy_me_a_coffee: steveiliop56
|
||||
33
README.md
@@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<img alt="Tinyauth" title="Tinyauth" width="256" src="site/public/logo.png">
|
||||
<img alt="Tinyauth" title="Tinyauth" height="256" src="frontend/public/logo.png">
|
||||
<h1>Tinyauth</h1>
|
||||
<p>The easiest way to secure your apps with a login screen.</p>
|
||||
</div>
|
||||
@@ -8,13 +8,16 @@
|
||||
<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 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.
|
||||
Tinyauth is a simple authentication middleware that adds simple username/password login or OAuth with Google, Github and any generic provider to all of your docker apps. It is designed 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.
|
||||
@@ -22,25 +25,43 @@ Tinyauth is a simple authentication middleware that adds simple username/passwor
|
||||
> [!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
|
||||
|
||||
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/eHzVaCzRRd), see you there!
|
||||
|
||||
## Getting Started
|
||||
|
||||
You can easily get started with tinyauth by following the guide on the documentation [here](https://tinyauth.doesmycode.work/docs/getting-started.html). There is also an available docker compose file [here](./docker-compose.example.yml) that has traefik, nginx and tinyauth to demonstrate its capabilities.
|
||||
You can easily get started with tinyauth by following the guide in the [documentation](https://tinyauth.app/docs/getting-started.html). There is also an available [docker compose file](./docker-compose.example.yml) that has traefik, whoami and tinyauth to demonstrate its capabilities.
|
||||
|
||||
## Documentation
|
||||
|
||||
You can find documentation and guides on all available configuration of tinyauth [here](https://tinyauth.doesmycode.work).
|
||||
You can find documentation and guides on all of the available configuration of tinyauth [here](https://tinyauth.app).
|
||||
|
||||
## Contributing
|
||||
|
||||
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!
|
||||
|
||||
## Localization
|
||||
|
||||
If you would like to help translating the project in more languages you can do so by visiting the [Crowdin](https://crowdin.com/project/tinyauth) page.
|
||||
|
||||
## 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
|
||||
|
||||
Thanks a lot to the following people for providing me with more coffee:
|
||||
|
||||
<!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https://github.com/erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a> <a href="https://github.com/nicotsx"><img src="https://github.com/nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a> <a href="https://github.com/SimpleHomelab"><img src="https://github.com/SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a> <a href="https://github.com/jmadden91"><img src="https://github.com/jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a> <a href="https://github.com/tribor"><img src="https://github.com/tribor.png" width="64px" alt="User avatar: tribor" /></a> <!-- sponsors -->
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Credits for the logo of this app go to:
|
||||
|
||||
- **Freepik** for providing the police hat and logo.
|
||||
- **Freepik** for providing the police hat and badge.
|
||||
- **Renee French** for the original gopher logo.
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://www.star-history.com/#steveiliop56/tinyauth&Date)
|
||||
|
||||
9
SECURITY.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please always use the latest available Tinyauth version which can be found [here](https://github.com/steveiliop56/tinyauth/releases/latest). Older versions (especially major) may contain security issues which I cannot go back and fix.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Due to the nature of this app, it needs to be secure. If you find any security issues in the OAuth or login flow of the app please contact me at <steve@doesmycode.work> and include a concise description of the issue. Please do not use the issues section for reporting major security issues.
|
||||
24
air.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
root = "/tinyauth"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
pre_cmd = ["mkdir -p internal/assets/dist", "echo 'backend running' > internal/assets/dist/index.html"]
|
||||
cmd = "go build -o ./tmp/tinyauth ."
|
||||
bin = "tmp/tinyauth"
|
||||
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
|
||||
22
assets/discohook.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"content": null,
|
||||
"embeds": [
|
||||
{
|
||||
"title": "Welcome to Tinyauth Discord!",
|
||||
"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.app>",
|
||||
"url": "https://tinyauth.app",
|
||||
"color": 7002085,
|
||||
"author": {
|
||||
"name": "Tinyauth"
|
||||
},
|
||||
"footer": {
|
||||
"text": "Updated at"
|
||||
},
|
||||
"timestamp": "2025-03-10T19:00:00.000Z",
|
||||
"thumbnail": {
|
||||
"url": "https://github.com/steveiliop56/tinyauth/blob/main/frontend/public/logo.png?raw=true"
|
||||
}
|
||||
}
|
||||
],
|
||||
"attachments": []
|
||||
}
|
||||
BIN
assets/screenshot.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
123
cmd/root.go
@@ -1,13 +1,17 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
cmd "tinyauth/cmd/user"
|
||||
totpCmd "tinyauth/cmd/totp"
|
||||
userCmd "tinyauth/cmd/user"
|
||||
"tinyauth/internal/api"
|
||||
"tinyauth/internal/assets"
|
||||
"tinyauth/internal/auth"
|
||||
"tinyauth/internal/docker"
|
||||
"tinyauth/internal/handlers"
|
||||
"tinyauth/internal/hooks"
|
||||
"tinyauth/internal/providers"
|
||||
"tinyauth/internal/types"
|
||||
@@ -30,8 +34,8 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
// Get config
|
||||
var config types.Config
|
||||
parseErr := viper.Unmarshal(&config)
|
||||
HandleError(parseErr, "Failed to parse config")
|
||||
err := viper.Unmarshal(&config)
|
||||
HandleError(err, "Failed to parse config")
|
||||
|
||||
// Secrets
|
||||
config.Secret = utils.GetSecret(config.Secret, config.SecretFile)
|
||||
@@ -41,8 +45,8 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
// Validate config
|
||||
validator := validator.New()
|
||||
validateErr := validator.Struct(config)
|
||||
HandleError(validateErr, "Failed to validate config")
|
||||
err = validator.Struct(config)
|
||||
HandleError(err, "Failed to validate config")
|
||||
|
||||
// Logger
|
||||
log.Logger = log.Level(zerolog.Level(config.LogLevel))
|
||||
@@ -50,15 +54,18 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
// Users
|
||||
log.Info().Msg("Parsing users")
|
||||
users, usersErr := utils.GetUsers(config.Users, config.UsersFile)
|
||||
users, err := utils.GetUsers(config.Users, config.UsersFile)
|
||||
HandleError(err, "Failed to parse users")
|
||||
|
||||
if (len(users) == 0 || usersErr != nil) && !utils.OAuthConfigured(config) {
|
||||
log.Fatal().Err(usersErr).Msg("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 oauth whitelist
|
||||
oauthWhitelist := strings.Split(config.OAuthWhitelist, ",")
|
||||
log.Debug().Msg("Parsed OAuth whitelist")
|
||||
// Get domain
|
||||
log.Debug().Msg("Getting domain")
|
||||
domain, err := utils.GetUpperDomain(config.AppURL)
|
||||
HandleError(err, "Failed to get upper domain")
|
||||
log.Info().Str("domain", domain).Msg("Using domain for cookie store")
|
||||
|
||||
// Create OAuth config
|
||||
oauthConfig := types.OAuthConfig{
|
||||
@@ -75,10 +82,50 @@ var rootCmd = &cobra.Command{
|
||||
AppURL: config.AppURL,
|
||||
}
|
||||
|
||||
log.Debug().Msg("Parsed OAuth config")
|
||||
// Create handlers config
|
||||
handlersConfig := types.HandlersConfig{
|
||||
AppURL: config.AppURL,
|
||||
DisableContinue: config.DisableContinue,
|
||||
Title: config.Title,
|
||||
GenericName: config.GenericName,
|
||||
CookieSecure: config.CookieSecure,
|
||||
Domain: domain,
|
||||
ForgotPasswordMessage: config.FogotPasswordMessage,
|
||||
OAuthAutoRedirect: config.OAuthAutoRedirect,
|
||||
}
|
||||
|
||||
// Create api config
|
||||
apiConfig := types.APIConfig{
|
||||
Port: config.Port,
|
||||
Address: config.Address,
|
||||
}
|
||||
|
||||
// Create auth config
|
||||
authConfig := types.AuthConfig{
|
||||
Users: users,
|
||||
OauthWhitelist: config.OAuthWhitelist,
|
||||
Secret: config.Secret,
|
||||
CookieSecure: config.CookieSecure,
|
||||
SessionExpiry: config.SessionExpiry,
|
||||
Domain: domain,
|
||||
LoginTimeout: config.LoginTimeout,
|
||||
LoginMaxRetries: config.LoginMaxRetries,
|
||||
}
|
||||
|
||||
// Create hooks config
|
||||
hooksConfig := types.HooksConfig{
|
||||
Domain: domain,
|
||||
}
|
||||
|
||||
// Create docker service
|
||||
docker := docker.NewDocker()
|
||||
|
||||
// Initialize docker
|
||||
err = docker.Init()
|
||||
HandleError(err, "Failed to initialize docker")
|
||||
|
||||
// Create auth service
|
||||
auth := auth.NewAuth(users, oauthWhitelist)
|
||||
auth := auth.NewAuth(authConfig, docker)
|
||||
|
||||
// Create OAuth providers service
|
||||
providers := providers.NewProviders(oauthConfig)
|
||||
@@ -87,18 +134,13 @@ var rootCmd = &cobra.Command{
|
||||
providers.Init()
|
||||
|
||||
// Create hooks service
|
||||
hooks := hooks.NewHooks(auth, providers)
|
||||
hooks := hooks.NewHooks(hooksConfig, auth, providers)
|
||||
|
||||
// Create handlers
|
||||
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
||||
|
||||
// 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.CookieExpiry,
|
||||
}, hooks, auth, providers)
|
||||
api := api.NewAPI(apiConfig, handlers)
|
||||
|
||||
// Setup routes
|
||||
api.Init()
|
||||
@@ -111,20 +153,27 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to execute command")
|
||||
}
|
||||
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 init() {
|
||||
rootCmd.AddCommand(cmd.UserCmd())
|
||||
// Add user command
|
||||
rootCmd.AddCommand(userCmd.UserCmd())
|
||||
|
||||
// Add totp command
|
||||
rootCmd.AddCommand(totpCmd.TotpCmd())
|
||||
|
||||
// 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.")
|
||||
@@ -146,10 +195,18 @@ func init() {
|
||||
rootCmd.Flags().String("generic-auth-url", "", "Generic OAuth auth URL.")
|
||||
rootCmd.Flags().String("generic-token-url", "", "Generic OAuth token URL.")
|
||||
rootCmd.Flags().String("generic-user-url", "", "Generic OAuth user info URL.")
|
||||
rootCmd.Flags().String("generic-name", "Generic", "Generic OAuth provider name.")
|
||||
rootCmd.Flags().Bool("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("cookie-expiry", 86400, "Cookie expiration time in seconds.")
|
||||
rootCmd.Flags().String("oauth-auto-redirect", "none", "Auto redirect to the specified OAuth provider if configured. (available providers: github, google, generic)")
|
||||
rootCmd.Flags().Int("session-expiry", 86400, "Session (cookie) expiration time in seconds.")
|
||||
rootCmd.Flags().Int("login-timeout", 300, "Login timeout in seconds after max retries reached (0 to disable).")
|
||||
rootCmd.Flags().Int("login-max-retries", 5, "Maximum login attempts before timeout (0 to disable).")
|
||||
rootCmd.Flags().Int("log-level", 1, "Log level.")
|
||||
rootCmd.Flags().String("app-title", "Tinyauth", "Title of the app.")
|
||||
rootCmd.Flags().String("forgot-password-message", "You can reset your password by changing the `USERS` environment variable.", "Message to show on the forgot password page.")
|
||||
|
||||
// Bind flags to environment
|
||||
viper.BindEnv("port", "PORT")
|
||||
viper.BindEnv("address", "ADDRESS")
|
||||
viper.BindEnv("secret", "SECRET")
|
||||
@@ -171,9 +228,17 @@ func init() {
|
||||
viper.BindEnv("generic-auth-url", "GENERIC_AUTH_URL")
|
||||
viper.BindEnv("generic-token-url", "GENERIC_TOKEN_URL")
|
||||
viper.BindEnv("generic-user-url", "GENERIC_USER_URL")
|
||||
viper.BindEnv("generic-name", "GENERIC_NAME")
|
||||
viper.BindEnv("disable-continue", "DISABLE_CONTINUE")
|
||||
viper.BindEnv("oauth-whitelist", "OAUTH_WHITELIST")
|
||||
viper.BindEnv("cookie-expiry", "COOKIE_EXPIRY")
|
||||
viper.BindEnv("oauth-auto-redirect", "OAUTH_AUTO_REDIRECT")
|
||||
viper.BindEnv("session-expiry", "SESSION_EXPIRY")
|
||||
viper.BindEnv("log-level", "LOG_LEVEL")
|
||||
viper.BindEnv("app-title", "APP_TITLE")
|
||||
viper.BindEnv("login-timeout", "LOGIN_TIMEOUT")
|
||||
viper.BindEnv("login-max-retries", "LOGIN_MAX_RETRIES")
|
||||
viper.BindEnv("forgot-password-message", "FORGOT_PASSWORD_MESSAGE")
|
||||
|
||||
// Bind flags to viper
|
||||
viper.BindPFlags(rootCmd.Flags())
|
||||
}
|
||||
|
||||
121
cmd/totp/generate/generate.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/mdp/qrterminal/v3"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Interactive flag
|
||||
var interactive bool
|
||||
|
||||
// Input user
|
||||
var iUser string
|
||||
|
||||
var GenerateCmd = &cobra.Command{
|
||||
Use: "generate",
|
||||
Short: "Generate a totp secret",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Setup logger
|
||||
log.Logger = log.Level(zerolog.InfoLevel)
|
||||
|
||||
// Use simple theme
|
||||
var baseTheme *huh.Theme = huh.ThemeBase()
|
||||
|
||||
// Interactive
|
||||
if interactive {
|
||||
// Create huh form
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().Title("Current username:hash").Value(&iUser).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("user cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
),
|
||||
)
|
||||
|
||||
// Run form
|
||||
err := form.WithTheme(baseTheme).Run()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Form failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Parse user
|
||||
user, err := utils.ParseUser(iUser)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to parse user")
|
||||
}
|
||||
|
||||
// Check if user was using docker escape
|
||||
dockerEscape := false
|
||||
|
||||
if strings.Contains(iUser, "$$") {
|
||||
dockerEscape = true
|
||||
}
|
||||
|
||||
// Check it has totp
|
||||
if user.TotpSecret != "" {
|
||||
log.Fatal().Msg("User already has a totp secret")
|
||||
}
|
||||
|
||||
// Generate 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")
|
||||
}
|
||||
|
||||
// Create secret
|
||||
secret := key.Secret()
|
||||
|
||||
// Print secret and image
|
||||
log.Info().Str("secret", secret).Msg("Generated totp secret")
|
||||
|
||||
// Print QR code
|
||||
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)
|
||||
|
||||
// Add the secret to the user
|
||||
user.TotpSecret = secret
|
||||
|
||||
// If using docker escape re-escape it
|
||||
if dockerEscape {
|
||||
user.Password = strings.ReplaceAll(user.Password, "$", "$$")
|
||||
}
|
||||
|
||||
// Print success
|
||||
log.Info().Str("user", fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TotpSecret)).Msg("Add the totp secret to your authenticator app then use the verify command to ensure everything is working correctly.")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Add interactive flag
|
||||
GenerateCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Run in interactive mode")
|
||||
GenerateCmd.Flags().StringVar(&iUser, "user", "", "Your current username:hash")
|
||||
}
|
||||
22
cmd/totp/totp.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"tinyauth/cmd/totp/generate"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TotpCmd() *cobra.Command {
|
||||
// Create the totp command
|
||||
totpCmd := &cobra.Command{
|
||||
Use: "totp",
|
||||
Short: "Totp utilities",
|
||||
Long: `Utilities for creating and verifying totp codes.`,
|
||||
}
|
||||
|
||||
// Add the generate command
|
||||
totpCmd.AddCommand(generate.GenerateCmd)
|
||||
|
||||
// Return the totp command
|
||||
return totpCmd
|
||||
}
|
||||
@@ -12,28 +12,36 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// Interactive flag
|
||||
var interactive bool
|
||||
var username string
|
||||
var password string
|
||||
|
||||
// Docker flag
|
||||
var docker bool
|
||||
|
||||
// i stands for input
|
||||
var iUsername string
|
||||
var iPassword string
|
||||
|
||||
var CreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a user",
|
||||
Long: `Create a user either interactively or by passing flags.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// 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 {
|
||||
huh.NewInput().Title("Username").Value(&iUsername).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("username cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
huh.NewInput().Title("Password").Value(&password).Validate((func(s string) error {
|
||||
huh.NewInput().Title("Password").Value(&iPassword).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("password cannot be empty")
|
||||
}
|
||||
@@ -43,40 +51,47 @@ var CreateCmd = &cobra.Command{
|
||||
),
|
||||
)
|
||||
|
||||
// Use simple theme
|
||||
var baseTheme *huh.Theme = huh.ThemeBase()
|
||||
|
||||
formErr := form.WithTheme(baseTheme).Run()
|
||||
err := form.WithTheme(baseTheme).Run()
|
||||
|
||||
if formErr != nil {
|
||||
log.Fatal().Err(formErr).Msg("Form failed")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Form failed")
|
||||
}
|
||||
}
|
||||
|
||||
if username == "" || password == "" {
|
||||
log.Error().Msg("Username and password cannot be empty")
|
||||
// Do we have username and password?
|
||||
if iUsername == "" || iPassword == "" {
|
||||
log.Fatal().Err(errors.New("error invalid input")).Msg("Username and password cannot be empty")
|
||||
}
|
||||
|
||||
log.Info().Str("username", username).Str("password", password).Bool("docker", docker).Msg("Creating user")
|
||||
log.Info().Str("username", iUsername).Str("password", iPassword).Bool("docker", docker).Msg("Creating user")
|
||||
|
||||
passwordByte, passwordErr := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
// Hash password
|
||||
password, err := bcrypt.GenerateFromPassword([]byte(iPassword), bcrypt.DefaultCost)
|
||||
|
||||
if passwordErr != nil {
|
||||
log.Fatal().Err(passwordErr).Msg("Failed to hash password")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to hash password")
|
||||
}
|
||||
|
||||
passwordString := string(passwordByte)
|
||||
// Convert password to string
|
||||
passwordString := string(password)
|
||||
|
||||
// Escape $ for docker
|
||||
if docker {
|
||||
passwordString = strings.ReplaceAll(passwordString, "$", "$$")
|
||||
}
|
||||
|
||||
log.Info().Str("user", fmt.Sprintf("%s:%s", username, passwordString)).Msg("User created")
|
||||
// Log user created
|
||||
log.Info().Str("user", fmt.Sprintf("%s:%s", iUsername, passwordString)).Msg("User created")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
CreateCmd.Flags().BoolVar(&interactive, "interactive", false, "Create a user interactively")
|
||||
// Flags
|
||||
CreateCmd.Flags().BoolVarP(&interactive, "interactive", "i", 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")
|
||||
CreateCmd.Flags().StringVar(&iUsername, "username", "", "Username")
|
||||
CreateCmd.Flags().StringVar(&iPassword, "password", "", "Password")
|
||||
}
|
||||
|
||||
@@ -2,92 +2,121 @@ package verify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"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"
|
||||
)
|
||||
|
||||
// Interactive flag
|
||||
var interactive bool
|
||||
var username string
|
||||
var password string
|
||||
|
||||
// Docker flag
|
||||
var docker bool
|
||||
var user string
|
||||
|
||||
// i stands for input
|
||||
var iUsername string
|
||||
var iPassword string
|
||||
var iTotp string
|
||||
var iUser string
|
||||
|
||||
var VerifyCmd = &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify a user is set up correctly",
|
||||
Long: `Verify a user is set up correctly meaning that it has a correct username and password.`,
|
||||
Long: `Verify a user is set up correctly meaning that it has a correct username, password and totp code.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Setup logger
|
||||
log.Logger = log.Level(zerolog.InfoLevel)
|
||||
|
||||
// Use simple theme
|
||||
var baseTheme *huh.Theme = huh.ThemeBase()
|
||||
|
||||
// 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 {
|
||||
huh.NewInput().Title("User (username:hash:totp)").Value(&iUser).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("user cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
huh.NewInput().Title("Username").Value(&username).Validate((func(s string) error {
|
||||
huh.NewInput().Title("Username").Value(&iUsername).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("username cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
huh.NewInput().Title("Password").Value(&password).Validate((func(s string) error {
|
||||
huh.NewInput().Title("Password").Value(&iPassword).Validate((func(s string) error {
|
||||
if s == "" {
|
||||
return errors.New("password cannot be empty")
|
||||
}
|
||||
return nil
|
||||
})),
|
||||
huh.NewSelect[bool]().Title("Is the user formatted for docker?").Options(huh.NewOption("Yes", true), huh.NewOption("No", false)).Value(&docker),
|
||||
huh.NewInput().Title("Totp Code (if setup)").Value(&iTotp),
|
||||
),
|
||||
)
|
||||
|
||||
var baseTheme *huh.Theme = huh.ThemeBase()
|
||||
// Run form
|
||||
err := form.WithTheme(baseTheme).Run()
|
||||
|
||||
formErr := form.WithTheme(baseTheme).Run()
|
||||
|
||||
if formErr != nil {
|
||||
log.Fatal().Err(formErr).Msg("Form failed")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Form failed")
|
||||
}
|
||||
}
|
||||
|
||||
if username == "" || password == "" || user == "" {
|
||||
log.Fatal().Msg("Username, password and user cannot be empty")
|
||||
// Parse user
|
||||
user, err := utils.ParseUser(iUser)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to parse user")
|
||||
}
|
||||
|
||||
log.Info().Str("user", user).Str("username", username).Str("password", password).Bool("docker", docker).Msg("Verifying user")
|
||||
|
||||
userSplit := strings.Split(user, ":")
|
||||
|
||||
if userSplit[1] == "" {
|
||||
log.Fatal().Msg("User is not formatted correctly")
|
||||
// Compare username
|
||||
if user.Username != iUsername {
|
||||
log.Fatal().Msg("Username is incorrect")
|
||||
}
|
||||
|
||||
if docker {
|
||||
userSplit[1] = strings.ReplaceAll(userSplit[1], "$$", "$")
|
||||
// Compare password
|
||||
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(iPassword))
|
||||
|
||||
if err != nil {
|
||||
log.Fatal().Msg("Ppassword is incorrect")
|
||||
}
|
||||
|
||||
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")
|
||||
// Check if user has 2fa code
|
||||
if user.TotpSecret == "" {
|
||||
if iTotp != "" {
|
||||
log.Warn().Msg("User does not have 2fa secret")
|
||||
}
|
||||
log.Info().Msg("User verified")
|
||||
return
|
||||
}
|
||||
|
||||
// Check totp code
|
||||
ok := totp.Validate(iTotp, user.TotpSecret)
|
||||
|
||||
if !ok {
|
||||
log.Fatal().Msg("Totp code incorrect")
|
||||
|
||||
}
|
||||
|
||||
// Done
|
||||
log.Info().Msg("User verified")
|
||||
},
|
||||
}
|
||||
|
||||
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)")
|
||||
VerifyCmd.Flags().StringVar(&iUsername, "username", "", "Username")
|
||||
VerifyCmd.Flags().StringVar(&iPassword, "password", "", "Password")
|
||||
VerifyCmd.Flags().StringVar(&iTotp, "totp", "", "Totp code")
|
||||
VerifyCmd.Flags().StringVar(&iUser, "user", "", "Hash (username:hash:totp combination)")
|
||||
}
|
||||
|
||||
12
crowdin.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
"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",
|
||||
},
|
||||
]
|
||||
@@ -7,28 +7,41 @@ services:
|
||||
- 80:80
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
labels:
|
||||
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth
|
||||
|
||||
nginx:
|
||||
container_name: nginx
|
||||
image: nginx:latest
|
||||
whoami:
|
||||
container_name: whoami
|
||||
image: traefik/whoami:latest
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.nginx.rule: Host(`nginx.dev.local`)
|
||||
traefik.http.services.nginx.loadbalancer.server.port: 80
|
||||
traefik.http.routers.nginx.rule: Host(`whoami.example.com`)
|
||||
traefik.http.routers.nginx.middlewares: tinyauth
|
||||
|
||||
tinyauth:
|
||||
container_name: tinyauth
|
||||
tinyauth-frontend:
|
||||
container_name: tinyauth-frontend
|
||||
build:
|
||||
context: .
|
||||
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
|
||||
dockerfile: frontend/Dockerfile.dev
|
||||
volumes:
|
||||
- ./frontend/src:/frontend/src
|
||||
ports:
|
||||
- 5173:5173
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.tinyauth.rule: Host(`tinyauth.dev.local`)
|
||||
traefik.http.services.tinyauth.loadbalancer.server.port: 3000
|
||||
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
|
||||
|
||||
tinyauth-backend:
|
||||
container_name: tinyauth-backend
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
env_file: .env
|
||||
volumes:
|
||||
- ./internal:/tinyauth/internal
|
||||
- ./cmd:/tinyauth/cmd
|
||||
- ./main.go:/tinyauth/main.go
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
ports:
|
||||
- 3000:3000
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth-backend:3000/api/auth/traefik
|
||||
|
||||
@@ -7,21 +7,18 @@ services:
|
||||
- 80:80
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
labels:
|
||||
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth
|
||||
|
||||
nginx:
|
||||
container_name: nginx
|
||||
image: nginx:latest
|
||||
whoami:
|
||||
container_name: whoami
|
||||
image: traefik/whoami:latest
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.nginx.rule: Host(`nginx.example.com`)
|
||||
traefik.http.services.nginx.loadbalancer.server.port: 80
|
||||
traefik.http.routers.nginx.rule: Host(`whoami.example.com`)
|
||||
traefik.http.routers.nginx.middlewares: tinyauth
|
||||
|
||||
tinyauth:
|
||||
container_name: tinyauth
|
||||
image: ghcr.io/steveiliop56/tinyauth:latest
|
||||
image: ghcr.io/steveiliop56/tinyauth:v3
|
||||
environment:
|
||||
- SECRET=some-random-32-chars-string
|
||||
- APP_URL=https://tinyauth.example.com
|
||||
@@ -29,4 +26,4 @@ services:
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
|
||||
traefik.http.services.tinyauth.loadbalancer.server.port: 3000
|
||||
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik
|
||||
|
||||
0
site/.gitignore → frontend/.gitignore
vendored
23
frontend/Dockerfile.dev
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM oven/bun:1.1.45-alpine
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
COPY ./frontend/package.json ./
|
||||
COPY ./frontend/bun.lockb ./
|
||||
|
||||
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 ./frontend/postcss.config.cjs ./
|
||||
|
||||
EXPOSE 5173
|
||||
|
||||
ENTRYPOINT ["bun", "run", "dev"]
|
||||
BIN
frontend/bun.lockb
Executable file
@@ -6,7 +6,7 @@
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="manifest" href="/frontend.webmanifest" />
|
||||
<title>Tinyauth</title>
|
||||
</head>
|
||||
<body>
|
||||
4919
frontend/package-lock.json
generated
Normal file
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "site",
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
@@ -14,28 +14,35 @@
|
||||
"@mantine/form": "^7.16.0",
|
||||
"@mantine/hooks": "^7.16.0",
|
||||
"@mantine/notifications": "^7.16.0",
|
||||
"@tanstack/react-query": "4",
|
||||
"@tanstack/react-query": "5",
|
||||
"axios": "^1.7.9",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router": "^7.1.3",
|
||||
"i18next": "^25.0.0",
|
||||
"i18next-browser-languagedetector": "^8.0.4",
|
||||
"i18next-chained-backend": "^4.6.2",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"i18next-resources-to-backend": "^1.2.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router": "^7.5.2",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/react": "^19.1.1",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.14.0",
|
||||
"globals": "^16.0.0",
|
||||
"postcss": "^8.5.1",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"prettier": "3.4.2",
|
||||
"typescript": "~5.6.2",
|
||||
"prettier": "3.5.3",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.18.2",
|
||||
"vite": "^6.0.5"
|
||||
"vite": "^6.3.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 602 B After Width: | Height: | Size: 602 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
57
frontend/src/components/auth/login-forn.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { TextInput, PasswordInput, Button, Anchor, Group, Text } from "@mantine/core";
|
||||
import { useForm, zodResolver } from "@mantine/form";
|
||||
import { LoginFormValues, loginSchema } from "../../schemas/login-schema";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface LoginFormProps {
|
||||
isPending: boolean;
|
||||
onSubmit: (values: LoginFormValues) => void;
|
||||
}
|
||||
|
||||
export const LoginForm = (props: LoginFormProps) => {
|
||||
const { isPending, onSubmit } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const form = useForm({
|
||||
mode: "uncontrolled",
|
||||
initialValues: {
|
||||
username: "",
|
||||
password: "",
|
||||
},
|
||||
validate: zodResolver(loginSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||
<TextInput
|
||||
label={t("loginUsername")}
|
||||
placeholder="Username"
|
||||
disabled={isPending}
|
||||
required
|
||||
withAsterisk={false}
|
||||
key={form.key("username")}
|
||||
{...form.getInputProps("username")}
|
||||
/>
|
||||
<Group justify="space-between" mb={5} mt="md">
|
||||
<Text component="label" htmlFor=".password-input" size="sm" fw={500}>
|
||||
{t("loginPassword")}
|
||||
</Text>
|
||||
|
||||
<Anchor href="#" onClick={() => window.location.replace("/forgot-password")} pt={2} fw={500} fz="xs">
|
||||
{t('forgotPasswordTitle')}
|
||||
</Anchor>
|
||||
</Group>
|
||||
<PasswordInput
|
||||
className="password-input"
|
||||
placeholder="Password"
|
||||
required
|
||||
disabled={isPending}
|
||||
key={form.key("password")}
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
<Button fullWidth mt="xl" type="submit" loading={isPending}>
|
||||
{t("loginSubmit")}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
58
frontend/src/components/auth/oauth-buttons.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Grid, Button } from "@mantine/core";
|
||||
import { GithubIcon } from "../../icons/github";
|
||||
import { GoogleIcon } from "../../icons/google";
|
||||
import { OAuthIcon } from "../../icons/oauth";
|
||||
|
||||
interface OAuthButtonsProps {
|
||||
oauthProviders: string[];
|
||||
isPending: boolean;
|
||||
mutate: (provider: string) => void;
|
||||
genericName: string;
|
||||
}
|
||||
|
||||
export const OAuthButtons = (props: OAuthButtonsProps) => {
|
||||
const { oauthProviders, isPending, genericName, mutate } = props;
|
||||
return (
|
||||
<Grid mb="md" mt="md" align="center" justify="center">
|
||||
{oauthProviders.includes("google") && (
|
||||
<Grid.Col span="content">
|
||||
<Button
|
||||
radius="xl"
|
||||
leftSection={<GoogleIcon style={{ width: 14, height: 14 }} />}
|
||||
variant="default"
|
||||
onClick={() => mutate("google")}
|
||||
loading={isPending}
|
||||
>
|
||||
Google
|
||||
</Button>
|
||||
</Grid.Col>
|
||||
)}
|
||||
{oauthProviders.includes("github") && (
|
||||
<Grid.Col span="content">
|
||||
<Button
|
||||
radius="xl"
|
||||
leftSection={<GithubIcon style={{ width: 14, height: 14 }} />}
|
||||
variant="default"
|
||||
onClick={() => mutate("github")}
|
||||
loading={isPending}
|
||||
>
|
||||
Github
|
||||
</Button>
|
||||
</Grid.Col>
|
||||
)}
|
||||
{oauthProviders.includes("generic") && (
|
||||
<Grid.Col span="content">
|
||||
<Button
|
||||
radius="xl"
|
||||
leftSection={<OAuthIcon style={{ width: 14, height: 14 }} />}
|
||||
variant="default"
|
||||
onClick={() => mutate("generic")}
|
||||
loading={isPending}
|
||||
>
|
||||
{genericName}
|
||||
</Button>
|
||||
</Grid.Col>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
40
frontend/src/components/auth/totp-form.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Button, PinInput } from "@mantine/core";
|
||||
import { useForm, zodResolver } from "@mantine/form";
|
||||
import { z } from "zod";
|
||||
|
||||
const schema = z.object({
|
||||
code: z.string(),
|
||||
});
|
||||
|
||||
type FormValues = z.infer<typeof schema>;
|
||||
|
||||
interface TotpFormProps {
|
||||
onSubmit: (values: FormValues) => void;
|
||||
isPending: boolean;
|
||||
}
|
||||
|
||||
export const TotpForm = (props: TotpFormProps) => {
|
||||
const { onSubmit, isPending } = props;
|
||||
|
||||
const form = useForm({
|
||||
mode: "uncontrolled",
|
||||
initialValues: {
|
||||
code: "",
|
||||
},
|
||||
validate: zodResolver(schema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||
<PinInput
|
||||
length={6}
|
||||
type={"number"}
|
||||
placeholder=""
|
||||
{...form.getInputProps("code")}
|
||||
/>
|
||||
<Button type="submit" mt="xl" loading={isPending} fullWidth>
|
||||
Verify
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ComboboxItem, Select } from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
import i18n from "../../lib/i18n/i18n";
|
||||
import {
|
||||
SupportedLanguage,
|
||||
getLanguageName,
|
||||
languages,
|
||||
} from "../../lib/i18n/locales";
|
||||
|
||||
export const LanguageSelector = () => {
|
||||
const [language, setLanguage] = useState<ComboboxItem>({
|
||||
value: i18n.language,
|
||||
label: getLanguageName(i18n.language as SupportedLanguage),
|
||||
});
|
||||
|
||||
const languageOptions = Object.entries(languages).map(([code, name]) => ({
|
||||
value: code,
|
||||
label: name,
|
||||
}));
|
||||
|
||||
const handleLanguageChange = (option: string) => {
|
||||
i18n.changeLanguage(option as SupportedLanguage);
|
||||
setLanguage({
|
||||
value: option,
|
||||
label: getLanguageName(option as SupportedLanguage),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
data={languageOptions}
|
||||
value={language ? language.value : null}
|
||||
onChange={(_value, option) => handleLanguageChange(option.value)}
|
||||
allowDeselect={false}
|
||||
pos="absolute"
|
||||
right={10}
|
||||
top={10}
|
||||
/>
|
||||
);
|
||||
};
|
||||
16
frontend/src/components/layouts/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Center, Flex } from "@mantine/core";
|
||||
import { ReactNode } from "react";
|
||||
import { LanguageSelector } from "../language-selector/language-selector";
|
||||
|
||||
export const Layout = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<>
|
||||
<LanguageSelector />
|
||||
<Center style={{ minHeight: "100vh" }}>
|
||||
<Flex direction="column" flex="1" maw={340}>
|
||||
{children}
|
||||
</Flex>
|
||||
</Center>
|
||||
</>
|
||||
);
|
||||
};
|
||||
42
frontend/src/context/app-context.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import React, { createContext, useContext } from "react";
|
||||
import axios from "axios";
|
||||
import { AppContextSchemaType } from "../schemas/app-context-schema";
|
||||
|
||||
const AppContext = createContext<AppContextSchemaType | null>(null);
|
||||
|
||||
export const AppContextProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const {
|
||||
data: userContext,
|
||||
isLoading,
|
||||
error,
|
||||
} = useSuspenseQuery({
|
||||
queryKey: ["appContext"],
|
||||
queryFn: async () => {
|
||||
const res = await axios.get("/api/app");
|
||||
return res.data;
|
||||
},
|
||||
});
|
||||
|
||||
if (error && !isLoading) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={userContext}>{children}</AppContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAppContext = () => {
|
||||
const context = useContext(AppContext);
|
||||
|
||||
if (context === null) {
|
||||
throw new Error("useAppContext must be used within an AppContextProvider");
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import React, { createContext, useContext } from "react";
|
||||
import { UserContextSchemaType } from "../schemas/user-context-schema";
|
||||
import axios from "axios";
|
||||
import { UserContextSchemaType } from "../schemas/user-context-schema";
|
||||
|
||||
const UserContext = createContext<UserContextSchemaType | null>(null);
|
||||
|
||||
@@ -14,10 +14,10 @@ export const UserContextProvider = ({
|
||||
data: userContext,
|
||||
isLoading,
|
||||
error,
|
||||
} = useQuery({
|
||||
queryKey: ["isLoggedIn"],
|
||||
} = useSuspenseQuery({
|
||||
queryKey: ["userContext"],
|
||||
queryFn: async () => {
|
||||
const res = await axios.get("/api/status");
|
||||
const res = await axios.get("/api/user");
|
||||
return res.data;
|
||||
},
|
||||
});
|
||||
4
frontend/src/index.css
Normal file
@@ -0,0 +1,4 @@
|
||||
span,
|
||||
p {
|
||||
word-break: break-word;
|
||||
}
|
||||
26
frontend/src/lib/hooks/use-is-mounted.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
|
||||
/**
|
||||
* Custom hook that determines if the component is currently mounted.
|
||||
* @returns {() => boolean} A function that returns a boolean value indicating whether the component is mounted.
|
||||
* @public
|
||||
* @see [Documentation](https://usehooks-ts.com/react-hook/use-is-mounted)
|
||||
* @example
|
||||
* ```tsx
|
||||
* const isComponentMounted = useIsMounted();
|
||||
* // Use isComponentMounted() to check if the component is currently mounted before performing certain actions.
|
||||
* ```
|
||||
*/
|
||||
export function useIsMounted(): () => boolean {
|
||||
const isMounted = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
isMounted.current = true
|
||||
|
||||
return () => {
|
||||
isMounted.current = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
return useCallback(() => isMounted.current, [])
|
||||
}
|
||||
42
frontend/src/lib/i18n/i18n.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import ChainedBackend from "i18next-chained-backend";
|
||||
import resourcesToBackend from "i18next-resources-to-backend";
|
||||
import HttpBackend from "i18next-http-backend";
|
||||
|
||||
const backends = [
|
||||
HttpBackend,
|
||||
resourcesToBackend(
|
||||
(language: string) => import(`./locales/${language}.json`),
|
||||
),
|
||||
]
|
||||
|
||||
const backendOptions = [
|
||||
{
|
||||
loadPath: "https://cdn.tinyauth.app/i18n/v1/{{lng}}.json",
|
||||
},
|
||||
{}
|
||||
]
|
||||
|
||||
i18n
|
||||
.use(ChainedBackend)
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
fallbackLng: "en",
|
||||
debug: import.meta.env.MODE === "development",
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
|
||||
load: "currentOnly",
|
||||
|
||||
backend: {
|
||||
backends: import.meta.env.MODE !== "development" ? backends : backends.reverse(),
|
||||
backendOptions: import.meta.env.MODE !== "development" ? backendOptions : backendOptions.reverse()
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
36
frontend/src/lib/i18n/locales.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
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];
|
||||
51
frontend/src/lib/i18n/locales/af-ZA.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/ar-SA.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "مرحبا بعودتك، قم بتسجيل الدخول باستخدام",
|
||||
"loginDivider": "أو المتابعة بكلمة المرور",
|
||||
"loginUsername": "اسم المستخدم",
|
||||
"loginPassword": "كلمة المرور",
|
||||
"loginSubmit": "تسجيل الدخول",
|
||||
"loginFailTitle": "فشل تسجيل الدخول",
|
||||
"loginFailSubtitle": "الرجاء التحقق من اسم المستخدم وكلمة المرور",
|
||||
"loginFailRateLimit": "فشلت في تسجيل الدخول عدة مرات، الرجاء المحاولة مرة أخرى لاحقا",
|
||||
"loginSuccessTitle": "تم تسجيل الدخول",
|
||||
"loginSuccessSubtitle": "مرحبا بعودتك!",
|
||||
"loginOauthFailTitle": "خطأ داخلي",
|
||||
"loginOauthFailSubtitle": "فشل في الحصول على رابط OAuth",
|
||||
"loginOauthSuccessTitle": "إعادة توجيه",
|
||||
"loginOauthSuccessSubtitle": "إعادة توجيه إلى مزود OAuth الخاص بك",
|
||||
"continueRedirectingTitle": "إعادة توجيه...",
|
||||
"continueRedirectingSubtitle": "يجب إعادة توجيهك إلى التطبيق قريبا",
|
||||
"continueInvalidRedirectTitle": "إعادة توجيه غير صالحة",
|
||||
"continueInvalidRedirectSubtitle": "رابط إعادة التوجيه غير صالح",
|
||||
"continueInsecureRedirectTitle": "إعادة توجيه غير آمنة",
|
||||
"continueInsecureRedirectSubtitle": "أنت تحاول إعادة التوجيه من <Code>https</Code> إلى <Code>http</Code>، هل أنت متأكد أنك تريد المتابعة؟",
|
||||
"continueTitle": "متابعة",
|
||||
"continueSubtitle": "انقر الزر للمتابعة إلى التطبيق الخاص بك.",
|
||||
"internalErrorTitle": "خطأ داخلي في الخادم",
|
||||
"internalErrorSubtitle": "حدث خطأ على الخادم ولا يمكن حاليا تلبية طلبك.",
|
||||
"internalErrorButton": "حاول مجددا",
|
||||
"logoutFailTitle": "فشل تسجيل الخروج",
|
||||
"logoutFailSubtitle": "يرجى إعادة المحاولة",
|
||||
"logoutSuccessTitle": "تم تسجيل الخروج",
|
||||
"logoutSuccessSubtitle": "تم تسجيل خروجك",
|
||||
"logoutTitle": "تسجيل الخروج",
|
||||
"logoutUsernameSubtitle": "أنت حاليا مسجل الدخول ك <Code>{{username}}</Code>، انقر الزر أدناه لتسجيل الخروج.",
|
||||
"logoutOauthSubtitle": "أنت حاليا مسجل الدخول ك <Code>{{username}}</Code> باستخدام مزود OAuth {{provider}} ، انقر الزر أدناه لتسجيل الخروج.",
|
||||
"notFoundTitle": "الصفحة غير موجودة",
|
||||
"notFoundSubtitle": "الصفحة التي تبحث عنها غير موجودة.",
|
||||
"notFoundButton": "انتقل إلى الرئيسية",
|
||||
"totpFailTitle": "فشل في التحقق من الرمز",
|
||||
"totpFailSubtitle": "الرجاء التحقق من الرمز الخاص بك وحاول مرة أخرى",
|
||||
"totpSuccessTitle": "تم التحقق",
|
||||
"totpSuccessSubtitle": "إعادة توجيه إلى تطبيقك",
|
||||
"totpTitle": "أدخل رمز TOTP الخاص بك",
|
||||
"unauthorizedTitle": "غير مرخص",
|
||||
"unauthorizedResourceSubtitle": "المستخدم الذي يحمل اسم المستخدم <Code>{{username}}</Code> غير مصرح له بالوصول إلى المورد <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>.",
|
||||
"unauthorizedButton": "حاول مجددا",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "إلغاء",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/ca-ES.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/cs-CZ.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/da-DK.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/de-DE.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Willkommen zurück, logge dich ein mit",
|
||||
"loginDivider": "Oder mit Passwort fortfahren",
|
||||
"loginUsername": "Benutzername",
|
||||
"loginPassword": "Passwort",
|
||||
"loginSubmit": "Anmelden",
|
||||
"loginFailTitle": "Login fehlgeschlagen",
|
||||
"loginFailSubtitle": "Bitte überprüfe deinen Benutzernamen und Passwort",
|
||||
"loginFailRateLimit": "Sie konnten sich zu oft nicht einloggen, bitte versuchen Sie es später erneut",
|
||||
"loginSuccessTitle": "Angemeldet",
|
||||
"loginSuccessSubtitle": "Willkommen zurück!",
|
||||
"loginOauthFailTitle": "Interner Fehler",
|
||||
"loginOauthFailSubtitle": "Fehler beim Abrufen der OAuth-URL",
|
||||
"loginOauthSuccessTitle": "Leite weiter",
|
||||
"loginOauthSuccessSubtitle": "Weiterleitung zu Ihrem OAuth-Provider",
|
||||
"continueRedirectingTitle": "Leite weiter...",
|
||||
"continueRedirectingSubtitle": "Sie sollten in Kürze zur App weitergeleitet werden",
|
||||
"continueInvalidRedirectTitle": "Ungültige Weiterleitung",
|
||||
"continueInvalidRedirectSubtitle": "Die Weiterleitungs-URL ist ungültig",
|
||||
"continueInsecureRedirectTitle": "Unsichere Weiterleitung",
|
||||
"continueInsecureRedirectSubtitle": "Sie versuchen von <Code>https</Code> auf <Code>http</Code>weiterzuleiten. Sind Sie sicher, dass Sie fortfahren möchten?",
|
||||
"continueTitle": "Weiter",
|
||||
"continueSubtitle": "Klicken Sie auf den Button, um zur App zu gelangen.",
|
||||
"internalErrorTitle": "Interner Serverfehler",
|
||||
"internalErrorSubtitle": "Ein Error ist auf dem Server aufgetreten, weshalb ihre Anfrage derzeit nicht bearbeitet werden kann.",
|
||||
"internalErrorButton": "Erneut versuchen",
|
||||
"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> mit dem {{provider}} OAuth-Anbieter angemeldet. Klicken Sie auf den Button unten, um sich abzumelden.",
|
||||
"notFoundTitle": "Seite nicht gefunden",
|
||||
"notFoundSubtitle": "Die gesuchte Seite existiert nicht.",
|
||||
"notFoundButton": "Nach Hause",
|
||||
"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",
|
||||
"unauthorizedTitle": "Unautorisiert",
|
||||
"unauthorizedResourceSubtitle": "Der Benutzer mit Benutzername <Code>{{username}}</Code> ist nicht berechtigt auf die Ressource <Code>{{resource}}</Code> zuzugreifen.",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Erneut versuchen",
|
||||
"untrustedRedirectTitle": "Nicht vertrauenswürdige Weiterleitung",
|
||||
"untrustedRedirectSubtitle": "Sie versuchen auf eine Domain umzuleiten, die nicht mit Ihrer konfigurierten Domain übereinstimmt (<Code>{{domain}}</Code>). Sind Sie sicher, dass Sie fortfahren möchten?",
|
||||
"cancelTitle": "Abbrechen",
|
||||
"forgotPasswordTitle": "Passwort vergessen?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/el-GR.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Καλώς ήρθατε, συνδεθείτε με",
|
||||
"loginDivider": "Ή συνεχίστε με κωδικό πρόσβασης",
|
||||
"loginUsername": "Όνομα Χρήστη",
|
||||
"loginPassword": "Κωδικός",
|
||||
"loginSubmit": "Είσοδος",
|
||||
"loginFailTitle": "Αποτυχία σύνδεσης",
|
||||
"loginFailSubtitle": "Παρακαλώ ελέγξτε το όνομα χρήστη και τον κωδικό πρόσβασης",
|
||||
"loginFailRateLimit": "Αποτύχατε να συνδεθείτε πάρα πολλές φορές, παρακαλώ προσπαθήστε ξανά αργότερα",
|
||||
"loginSuccessTitle": "Συνδεδεμένος",
|
||||
"loginSuccessSubtitle": "Καλώς ήρθατε!",
|
||||
"loginOauthFailTitle": "Εσωτερικό σφάλμα",
|
||||
"loginOauthFailSubtitle": "Αποτυχία λήψης OAuth URL",
|
||||
"loginOauthSuccessTitle": "Ανακατεύθυνση",
|
||||
"loginOauthSuccessSubtitle": "Ανακατεύθυνση στον πάροχο OAuth σας",
|
||||
"continueRedirectingTitle": "Ανακατεύθυνση...",
|
||||
"continueRedirectingSubtitle": "Θα πρέπει να μεταφερθείτε σύντομα στην εφαρμογή σας",
|
||||
"continueInvalidRedirectTitle": "Μη έγκυρη ανακατεύθυνση",
|
||||
"continueInvalidRedirectSubtitle": "Το URL ανακατεύθυνσης δεν είναι έγκυρο",
|
||||
"continueInsecureRedirectTitle": "Μη ασφαλής ανακατεύθυνση",
|
||||
"continueInsecureRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε από <Code>https</Code> σε <Code>http</Code>, είστε σίγουροι ότι θέλετε να συνεχίσετε;",
|
||||
"continueTitle": "Συνέχεια",
|
||||
"continueSubtitle": "Κάντε κλικ στο κουμπί για να συνεχίσετε στην εφαρμογή σας.",
|
||||
"internalErrorTitle": "Εσωτερικό Σφάλμα Διακομιστή",
|
||||
"internalErrorSubtitle": "Παρουσιάστηκε σφάλμα στο διακομιστή και δεν μπορεί να εξυπηρετήσει το αίτημά σας.",
|
||||
"internalErrorButton": "Προσπαθήστε ξανά",
|
||||
"logoutFailTitle": "Αποτυχία αποσύνδεσης",
|
||||
"logoutFailSubtitle": "Παρακαλώ δοκιμάστε ξανά",
|
||||
"logoutSuccessTitle": "Αποσυνδεδεμένος",
|
||||
"logoutSuccessSubtitle": "Έχετε αποσυνδεθεί",
|
||||
"logoutTitle": "Αποσύνδεση",
|
||||
"logoutUsernameSubtitle": "Αυτή τη στιγμή είστε συνδεδεμένοι ως <Code>{{username}}</Code>, κάντε κλικ στο παρακάτω κουμπί για να αποσυνδεθείτε.",
|
||||
"logoutOauthSubtitle": "Αυτή τη στιγμή είστε συνδεδεμένοι ως <Code>{{username}}</Code> χρησιμοποιώντας την υπηρεσία παροχής {{provider}} OAuth, κάντε κλικ στο παρακάτω κουμπί για να αποσυνδεθείτε.",
|
||||
"notFoundTitle": "Η σελίδα δε βρέθηκε",
|
||||
"notFoundSubtitle": "Η σελίδα που ψάχνετε δεν υπάρχει.",
|
||||
"notFoundButton": "Μετάβαση στην αρχική",
|
||||
"totpFailTitle": "Αποτυχία επαλήθευσης κωδικού",
|
||||
"totpFailSubtitle": "Παρακαλώ ελέγξτε τον κώδικά σας και προσπαθήστε ξανά",
|
||||
"totpSuccessTitle": "Επαληθεύθηκε",
|
||||
"totpSuccessSubtitle": "Ανακατεύθυνση στην εφαρμογή σας",
|
||||
"totpTitle": "Εισάγετε τον κωδικό TOTP",
|
||||
"unauthorizedTitle": "Μη εξουσιοδοτημένο",
|
||||
"unauthorizedResourceSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν έχει άδεια πρόσβασης στον πόρο <Code>{{resource}}</Code>.",
|
||||
"unauthorizedLoginSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν είναι εξουσιοδοτημένος να συνδεθεί.",
|
||||
"unauthorizedGroupsSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν είναι στις ομάδες που απαιτούνται από τον πόρο <Code>{{resource}}</Code>.",
|
||||
"unauthorizedButton": "Προσπαθήστε ξανά",
|
||||
"untrustedRedirectTitle": "Μη έμπιστη ανακατεύθυνση",
|
||||
"untrustedRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε σε έναν τομέα που δεν ταιριάζει με τον ρυθμισμένο τομέα σας (<Code>{{domain}}</Code>). Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
|
||||
"cancelTitle": "Ακύρωση",
|
||||
"forgotPasswordTitle": "Ξεχάσατε το συνθηματικό σας;"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/en-US.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/en.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/es-ES.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/fi-FI.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/fr-FR.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Bienvenue, connectez-vous avec",
|
||||
"loginDivider": "Ou continuez avec le mot de passe",
|
||||
"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 n'avez pas pu vous connecter trop de fois, veuillez réessayer plus tard",
|
||||
"loginSuccessTitle": "Connecté",
|
||||
"loginSuccessSubtitle": "Bienvenue!",
|
||||
"loginOauthFailTitle": "Erreur interne",
|
||||
"loginOauthFailSubtitle": "Impossible d'obtenir l'URL OAuth",
|
||||
"loginOauthSuccessTitle": "Redirection",
|
||||
"loginOauthSuccessSubtitle": "Redirection vers votre fournisseur OAuth",
|
||||
"continueRedirectingTitle": "Redirection...",
|
||||
"continueRedirectingSubtitle": "Vous devriez être redirigé vers l'application bientôt",
|
||||
"continueInvalidRedirectTitle": "Redirection invalide",
|
||||
"continueInvalidRedirectSubtitle": "L'URL de redirection est invalide",
|
||||
"continueInsecureRedirectTitle": "Redirection non sécurisée",
|
||||
"continueInsecureRedirectSubtitle": "Vous essayez de rediriger de <Code>https</Code> vers <Code>http</Code>, êtes-vous sûr de vouloir continuer ?",
|
||||
"continueTitle": "Continuer",
|
||||
"continueSubtitle": "Cliquez sur le bouton pour continuer vers votre application.",
|
||||
"internalErrorTitle": "Erreur interne du serveur",
|
||||
"internalErrorSubtitle": "Une erreur s'est produite sur le serveur et il ne peut actuellement pas répondre à votre demande.",
|
||||
"internalErrorButton": "Réessayer",
|
||||
"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> en utilisant 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",
|
||||
"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": "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>.",
|
||||
"unauthorizedButton": "Réessayer",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/he-IL.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/hu-HU.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/it-IT.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/ja-JP.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/ko-KR.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/nl-NL.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welkom terug, log in met",
|
||||
"loginDivider": "Of ga door met wachtwoord",
|
||||
"loginUsername": "Gebruikersnaam",
|
||||
"loginPassword": "Wachtwoord",
|
||||
"loginSubmit": "Log in",
|
||||
"loginFailTitle": "Mislukt om in te loggen",
|
||||
"loginFailSubtitle": "Controleer je gebruikersnaam en wachtwoord",
|
||||
"loginFailRateLimit": "Inloggen te vaak mislukt, probeer het later opnieuw",
|
||||
"loginSuccessTitle": "Ingelogd",
|
||||
"loginSuccessSubtitle": "Welkom terug!",
|
||||
"loginOauthFailTitle": "Interne fout",
|
||||
"loginOauthFailSubtitle": "Fout bij het ophalen van OAuth URL",
|
||||
"loginOauthSuccessTitle": "Omleiden",
|
||||
"loginOauthSuccessSubtitle": "Omleiden naar je OAuth provider",
|
||||
"continueRedirectingTitle": "Omleiden...",
|
||||
"continueRedirectingSubtitle": "Je wordt naar de app doorgestuurd",
|
||||
"continueInvalidRedirectTitle": "Ongeldige omleiding",
|
||||
"continueInvalidRedirectSubtitle": "De omleidings-URL is ongeldig",
|
||||
"continueInsecureRedirectTitle": "Onveilige doorverwijzing",
|
||||
"continueInsecureRedirectSubtitle": "Je probeert door te verwijzen van <Code>https</Code> naar <Code>http</Code>, weet je zeker dat je wilt doorgaan?",
|
||||
"continueTitle": "Ga verder",
|
||||
"continueSubtitle": "Klik op de knop om door te gaan naar de app.",
|
||||
"internalErrorTitle": "Interne server fout",
|
||||
"internalErrorSubtitle": "Er is een fout opgetreden op de server en het kan momenteel niet voldoen aan je verzoek.",
|
||||
"internalErrorButton": "Opnieuw proberen",
|
||||
"logoutFailTitle": "Afmelden mislukt",
|
||||
"logoutFailSubtitle": "Probeer het opnieuw",
|
||||
"logoutSuccessTitle": "Afgemeld",
|
||||
"logoutSuccessSubtitle": "Je bent afgemeld",
|
||||
"logoutTitle": "Afmelden",
|
||||
"logoutUsernameSubtitle": "Je bent momenteel ingelogd als <Code>{{username}}</Code>, klik op de knop hieronder om uit te loggen.",
|
||||
"logoutOauthSubtitle": "Je bent momenteel ingelogd als <Code>{{username}}</Code> met behulp van de {{provider}} OAuth provider, klik op de knop hieronder om uit te loggen.",
|
||||
"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",
|
||||
"unauthorizedTitle": "Ongeautoriseerd",
|
||||
"unauthorizedResourceSubtitle": "De gebruiker met gebruikersnaam <Code>{{username}}</Code> heeft geen toegang tot de bron <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>.",
|
||||
"unauthorizedButton": "Opnieuw proberen",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/no-NO.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/pl-PL.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Witaj ponownie, zaloguj się przez",
|
||||
"loginDivider": "Lub kontynuuj z hasłem",
|
||||
"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": "Nie udało się zalogować zbyt wiele razy, spróbuj ponownie później",
|
||||
"loginSuccessTitle": "Zalogowano",
|
||||
"loginSuccessSubtitle": "Witaj ponownie!",
|
||||
"loginOauthFailTitle": "Wewnętrzny błąd",
|
||||
"loginOauthFailSubtitle": "Nie udało się uzyskać adresu URL OAuth",
|
||||
"loginOauthSuccessTitle": "Przekierowywanie",
|
||||
"loginOauthSuccessSubtitle": "Przekierowywanie do Twojego dostawcy OAuth",
|
||||
"continueRedirectingTitle": "Przekierowywanie...",
|
||||
"continueRedirectingSubtitle": "Wkrótce powinieneś zostać przekierowany do aplikacji",
|
||||
"continueInvalidRedirectTitle": "Nieprawidłowe przekierowanie",
|
||||
"continueInvalidRedirectSubtitle": "Adres przekierowania jest nieprawidłowy",
|
||||
"continueInsecureRedirectTitle": "Niezabezpieczone przekierowanie",
|
||||
"continueInsecureRedirectSubtitle": "Próbujesz przekierować z <Code>https</Code> do <Code>http</Code>, czy na pewno chcesz kontynuować?",
|
||||
"continueTitle": "Kontynuuj",
|
||||
"continueSubtitle": "Kliknij przycisk, aby przejść do aplikacji.",
|
||||
"internalErrorTitle": "Wewnętrzny błąd serwera",
|
||||
"internalErrorSubtitle": "Wystąpił błąd na serwerze i obecnie nie można obsłużyć tego żądania.",
|
||||
"internalErrorButton": "Spróbuj ponownie",
|
||||
"logoutFailTitle": "Nie udało się wylogować",
|
||||
"logoutFailSubtitle": "Spróbuj ponownie",
|
||||
"logoutSuccessTitle": "Wylogowano",
|
||||
"logoutSuccessSubtitle": "Zostałeś wylogowany",
|
||||
"logoutTitle": "Wylogowanie",
|
||||
"logoutUsernameSubtitle": "Jesteś aktualnie zalogowany jako <Code>{{username}}</Code>, kliknij przycisk poniżej, aby się wylogować.",
|
||||
"logoutOauthSubtitle": "Jesteś obecnie zalogowany jako <Code>{{username}}</Code> przy użyciu providera OAuth {{provider}}, kliknij przycisk poniżej, aby się wylogować.",
|
||||
"notFoundTitle": "Strona nie znaleziona",
|
||||
"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",
|
||||
"unauthorizedTitle": "Nieautoryzowany",
|
||||
"unauthorizedResourceSubtitle": "Użytkownik o nazwie <Code>{{username}}</Code> nie jest upoważniony do uzyskania dostępu do zasobu <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>.",
|
||||
"unauthorizedButton": "Spróbuj ponownie",
|
||||
"untrustedRedirectTitle": "Niezaufane przekierowanie",
|
||||
"untrustedRedirectSubtitle": "Próbujesz przekierować do domeny, która nie pasuje do skonfigurowanej przez Ciebie domeny (<Code>{{domain}}</Code>). Czy na pewno chcesz kontynuować?",
|
||||
"cancelTitle": "Anuluj",
|
||||
"forgotPasswordTitle": "Nie pamiętasz hasła?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/pt-BR.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Bem-vindo de volta, acesse com",
|
||||
"loginDivider": "Ou continuar com uma senha",
|
||||
"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": "Erro interno",
|
||||
"loginOauthFailSubtitle": "Falha ao obter URL de OAuth",
|
||||
"loginOauthSuccessTitle": "Redirecionando",
|
||||
"loginOauthSuccessSubtitle": "Redirecionando para seu provedor OAuth",
|
||||
"continueRedirectingTitle": "Redirecionando...",
|
||||
"continueRedirectingSubtitle": "Você deve ser redirecionado para o aplicativo em breve",
|
||||
"continueInvalidRedirectTitle": "Redirecionamento inválido",
|
||||
"continueInvalidRedirectSubtitle": "O endereço de redirecionamento é inválido",
|
||||
"continueInsecureRedirectTitle": "Redirecionamento inseguro",
|
||||
"continueInsecureRedirectSubtitle": "Você está tentando redirecionar de <Code>https</Code> para <Code>http</Code>, você tem certeza que deseja continuar?",
|
||||
"continueTitle": "Continuar",
|
||||
"continueSubtitle": "Clique no botão para continuar para o seu aplicativo.",
|
||||
"internalErrorTitle": "Erro interno do servidor",
|
||||
"internalErrorSubtitle": "Ocorreu um erro no servidor e atualmente não pode servir sua solicitação.",
|
||||
"internalErrorButton": "Tentar novamente",
|
||||
"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",
|
||||
"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": "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>.",
|
||||
"unauthorizedButton": "Tentar novamente",
|
||||
"untrustedRedirectTitle": "Redirecionamento não confiável",
|
||||
"untrustedRedirectSubtitle": "Você está tentando redirecionar para um domínio que não corresponde ao seu domínio configurado (<Code>{{domain}}</Code>). Tem certeza que deseja continuar?",
|
||||
"cancelTitle": "Cancelar",
|
||||
"forgotPasswordTitle": "Esqueceu sua senha?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/pt-PT.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/ro-RO.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/ru-RU.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/sr-SP.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/sv-SE.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/tr-TR.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Ya da şifre ile devam edin",
|
||||
"loginUsername": "Kullanıcı Adı",
|
||||
"loginPassword": "Şifre",
|
||||
"loginSubmit": "Giriş Yap",
|
||||
"loginFailTitle": "Giriş yapılamadı",
|
||||
"loginFailSubtitle": "Please check your username and password",
|
||||
"loginFailRateLimit": "You failed to login too many times, please try again later",
|
||||
"loginSuccessTitle": "Giriş yapıldı",
|
||||
"loginSuccessSubtitle": "Tekrar hoş geldiniz!",
|
||||
"loginOauthFailTitle": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Yönlendiriliyor",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Yönlendiriliyor...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Devam et",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "İç Sunucu Hatası",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Tekrar deneyin",
|
||||
"logoutFailTitle": "Failed to log out",
|
||||
"logoutFailSubtitle": "Lütfen tekrar deneyin",
|
||||
"logoutSuccessTitle": "Çıkış yapıldı",
|
||||
"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": "Sayfa bulunamadı",
|
||||
"notFoundSubtitle": "Aradığınız sayfa mevcut değil.",
|
||||
"notFoundButton": "Ana sayfaya git",
|
||||
"totpFailTitle": "Kod doğrulanamadı",
|
||||
"totpFailSubtitle": "Please check your code and try again",
|
||||
"totpSuccessTitle": "Doğrulandı",
|
||||
"totpSuccessSubtitle": "Redirecting to your app",
|
||||
"totpTitle": "Enter your TOTP code",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "İptal",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/uk-UA.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/vi-VN.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/zh-CN.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "欢迎回来,请登录",
|
||||
"loginDivider": "或者继续使用密码",
|
||||
"loginUsername": "用户名",
|
||||
"loginPassword": "密码",
|
||||
"loginSubmit": "登录",
|
||||
"loginFailTitle": "登录失败",
|
||||
"loginFailSubtitle": "请检查您的用户名和密码",
|
||||
"loginFailRateLimit": "您登录次数过多,请稍后再试",
|
||||
"loginSuccessTitle": "已登录",
|
||||
"loginSuccessSubtitle": "欢迎回来!",
|
||||
"loginOauthFailTitle": "内部错误",
|
||||
"loginOauthFailSubtitle": "获取 OAuth URL 失败",
|
||||
"loginOauthSuccessTitle": "重定向中",
|
||||
"loginOauthSuccessSubtitle": "重定向到您的 OAuth 提供商",
|
||||
"continueRedirectingTitle": "正在重定向……",
|
||||
"continueRedirectingSubtitle": "您应该很快被重定向到应用",
|
||||
"continueInvalidRedirectTitle": "无效的重定向",
|
||||
"continueInvalidRedirectSubtitle": "重定向URL无效",
|
||||
"continueInsecureRedirectTitle": "不安全的重定向",
|
||||
"continueInsecureRedirectSubtitle": "您正在尝试将 <Code>https</Code> 重定向到 <Code>http</Code>,您确定要继续吗?",
|
||||
"continueTitle": "继续",
|
||||
"continueSubtitle": "点击按钮以继续您的应用。",
|
||||
"internalErrorTitle": "服务器内部错误",
|
||||
"internalErrorSubtitle": "服务器上发生错误,当前无法满足您的请求。",
|
||||
"internalErrorButton": "重试",
|
||||
"logoutFailTitle": "注销失败",
|
||||
"logoutFailSubtitle": "请重试",
|
||||
"logoutSuccessTitle": "已登出",
|
||||
"logoutSuccessSubtitle": "您已登出",
|
||||
"logoutTitle": "登出",
|
||||
"logoutUsernameSubtitle": "您当前以 <Code>{{username}}</Code> 的身份登录,点击下方按钮退出登录。",
|
||||
"logoutOauthSubtitle": "您当前以 <Code>{{username}}</Code> 的身份登录,使用的是 {{provider}} OAuth 提供商,点击下方按钮退出登录。",
|
||||
"notFoundTitle": "无法找到页面",
|
||||
"notFoundSubtitle": "您正在查找的页面不存在。",
|
||||
"notFoundButton": "回到主页",
|
||||
"totpFailTitle": "无法验证代码",
|
||||
"totpFailSubtitle": "请检查您的代码并重试",
|
||||
"totpSuccessTitle": "已验证",
|
||||
"totpSuccessSubtitle": "重定向到您的应用",
|
||||
"totpTitle": "输入您的 TOTP 代码",
|
||||
"unauthorizedTitle": "未授权",
|
||||
"unauthorizedResourceSubtitle": "用户 <Code>{{username}}</Code> 无权访问资源 <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>.",
|
||||
"unauthorizedButton": "重试",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
51
frontend/src/lib/i18n/locales/zh-TW.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginDivider": "Or continue with password",
|
||||
"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": "Internal error",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
|
||||
"continueTitle": "Continue",
|
||||
"continueSubtitle": "Click the button to continue to your app.",
|
||||
"internalErrorTitle": "Internal Server Error",
|
||||
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Try again",
|
||||
"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",
|
||||
"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>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"untrustedRedirectTitle": "Untrusted redirect",
|
||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?"
|
||||
}
|
||||
54
frontend/src/main.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { App } from "./App.tsx";
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
import { Notifications } from "@mantine/notifications";
|
||||
import "@mantine/core/styles.css";
|
||||
import "@mantine/notifications/styles.css";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { BrowserRouter, Route } from "react-router";
|
||||
import { Routes } from "react-router";
|
||||
import { UserContextProvider } from "./context/user-context.tsx";
|
||||
import { LoginPage } from "./pages/login-page.tsx";
|
||||
import { LogoutPage } from "./pages/logout-page.tsx";
|
||||
import { ContinuePage } from "./pages/continue-page.tsx";
|
||||
import { NotFoundPage } from "./pages/not-found-page.tsx";
|
||||
import { UnauthorizedPage } from "./pages/unauthorized-page.tsx";
|
||||
import { InternalServerError } from "./pages/internal-server-error.tsx";
|
||||
import { TotpPage } from "./pages/totp-page.tsx";
|
||||
import { AppContextProvider } from "./context/app-context.tsx";
|
||||
import "./lib/i18n/i18n.ts";
|
||||
import { ForgotPasswordPage } from "./pages/forgot-password-page.tsx";
|
||||
import "./index.css";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<MantineProvider defaultColorScheme="auto">
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Notifications />
|
||||
<AppContextProvider>
|
||||
<UserContextProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<App />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/totp" element={<TotpPage />} />
|
||||
<Route path="/logout" element={<LogoutPage />} />
|
||||
<Route path="/continue" element={<ContinuePage />} />
|
||||
<Route path="/unauthorized" element={<UnauthorizedPage />} />
|
||||
<Route path="/error" element={<InternalServerError />} />
|
||||
<Route
|
||||
path="/forgot-password"
|
||||
element={<ForgotPasswordPage />}
|
||||
/>
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</UserContextProvider>
|
||||
</AppContextProvider>
|
||||
</QueryClientProvider>
|
||||
</MantineProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
144
frontend/src/pages/continue-page.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import { Button, Code, Paper, Text } from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { Navigate } from "react-router";
|
||||
import { useUserContext } from "../context/user-context";
|
||||
import { Layout } from "../components/layouts/layout";
|
||||
import { ReactNode } from "react";
|
||||
import { escapeRegex, isValidRedirectUri } from "../utils/utils";
|
||||
import { useAppContext } from "../context/app-context";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
export const ContinuePage = () => {
|
||||
const queryString = window.location.search;
|
||||
const params = new URLSearchParams(queryString);
|
||||
const redirectUri = params.get("redirect_uri") ?? "";
|
||||
|
||||
const { isLoggedIn } = useUserContext();
|
||||
const { disableContinue, domain } = useAppContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!isLoggedIn) {
|
||||
return <Navigate to={`/login?redirect_uri=${redirectUri}`} />;
|
||||
}
|
||||
|
||||
if (!isValidRedirectUri(redirectUri)) {
|
||||
return <Navigate to="/" />;
|
||||
}
|
||||
|
||||
const redirect = () => {
|
||||
notifications.show({
|
||||
title: t("continueRedirectingTitle"),
|
||||
message: t("continueRedirectingSubtitle"),
|
||||
color: "blue",
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.href = redirectUri;
|
||||
}, 500);
|
||||
};
|
||||
|
||||
let uri;
|
||||
|
||||
try {
|
||||
uri = new URL(redirectUri);
|
||||
} catch {
|
||||
return (
|
||||
<ContinuePageLayout>
|
||||
<Text size="xl" fw={700}>
|
||||
{t("Invalid redirect")}
|
||||
</Text>
|
||||
<Text>{t("The redirect URL is invalid")}</Text>
|
||||
</ContinuePageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const regex = new RegExp(`^.*${escapeRegex(domain)}$`);
|
||||
|
||||
if (!regex.test(uri.hostname)) {
|
||||
return (
|
||||
<ContinuePageLayout>
|
||||
<Text size="xl" fw={700}>
|
||||
{t("untrustedRedirectTitle")}
|
||||
</Text>
|
||||
<Trans
|
||||
i18nKey="untrustedRedirectSubtitle"
|
||||
t={t}
|
||||
components={{ Code: <Code /> }}
|
||||
values={{ domain: domain }}
|
||||
/>
|
||||
<Button fullWidth mt="xl" color="red" onClick={redirect}>
|
||||
{t("continueTitle")}
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
mt="xs"
|
||||
color="gray"
|
||||
onClick={() => (window.location.href = "/")}
|
||||
>
|
||||
{t("cancelTitle")}
|
||||
</Button>
|
||||
</ContinuePageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
if (disableContinue) {
|
||||
window.location.href = redirectUri;
|
||||
return (
|
||||
<ContinuePageLayout>
|
||||
<Text size="xl" fw={700}>
|
||||
{t("continueRedirectingTitle")}
|
||||
</Text>
|
||||
<Text>{t("continueRedirectingSubtitle")}</Text>
|
||||
</ContinuePageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
if (window.location.protocol === "https:" && uri.protocol === "http:") {
|
||||
return (
|
||||
<ContinuePageLayout>
|
||||
<Text size="xl" fw={700}>
|
||||
{t("continueInsecureRedirectTitle")}
|
||||
</Text>
|
||||
<Text>
|
||||
<Trans
|
||||
i18nKey="continueInsecureRedirectSubtitle"
|
||||
t={t}
|
||||
components={{ Code: <Code /> }}
|
||||
/>
|
||||
</Text>
|
||||
<Button fullWidth mt="xl" color="yellow" onClick={redirect}>
|
||||
{t("continueTitle")}
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
mt="xs"
|
||||
color="gray"
|
||||
onClick={() => (window.location.href = "/")}
|
||||
>
|
||||
{t("cancelTitle")}
|
||||
</Button>
|
||||
</ContinuePageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ContinuePageLayout>
|
||||
<Text size="xl" fw={700}>
|
||||
{t("continueTitle")}
|
||||
</Text>
|
||||
<Text>{t("continueSubtitle")}</Text>
|
||||
<Button fullWidth mt="xl" onClick={redirect}>
|
||||
{t("continueTitle")}
|
||||
</Button>
|
||||
</ContinuePageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const ContinuePageLayout = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<Layout>
|
||||
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
|
||||
{children}
|
||||
</Paper>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
25
frontend/src/pages/forgot-password-page.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Paper, Text, TypographyStylesProvider } from "@mantine/core";
|
||||
import { Layout } from "../components/layouts/layout";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppContext } from "../context/app-context";
|
||||
import Markdown from 'react-markdown'
|
||||
|
||||
export const ForgotPasswordPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const { forgotPasswordMessage } = useAppContext();
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
|
||||
<Text size="xl" fw={700}>
|
||||
{t("forgotPasswordTitle")}
|
||||
</Text>
|
||||
<TypographyStylesProvider>
|
||||
<Markdown>
|
||||
{forgotPasswordMessage}
|
||||
</Markdown>
|
||||
</TypographyStylesProvider>
|
||||
</Paper>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
@@ -1,19 +1,18 @@
|
||||
import { Button, Paper, Text } from "@mantine/core";
|
||||
import { Layout } from "../components/layouts/layout";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const InternalServerError = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Layout>
|
||||
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
|
||||
<Text size="xl" fw={700}>
|
||||
Internal Server Error
|
||||
</Text>
|
||||
<Text>
|
||||
An error occured on the server and it currently cannot serve your
|
||||
request.
|
||||
{t("internalErrorTitle")}
|
||||
</Text>
|
||||
<Text>{t("internalErrorSubtitle")}</Text>
|
||||
<Button fullWidth mt="xl" onClick={() => window.location.replace("/")}>
|
||||
Try again
|
||||
{t("internalErrorButton")}
|
||||
</Button>
|
||||
</Paper>
|
||||
</Layout>
|
||||
181
frontend/src/pages/login-page.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
import { Paper, Title, Text, Divider } from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import axios, { type AxiosError } from "axios";
|
||||
import { useUserContext } from "../context/user-context";
|
||||
import { Navigate } from "react-router";
|
||||
import { Layout } from "../components/layouts/layout";
|
||||
import { OAuthButtons } from "../components/auth/oauth-buttons";
|
||||
import { LoginFormValues } from "../schemas/login-schema";
|
||||
import { LoginForm } from "../components/auth/login-forn";
|
||||
import { useAppContext } from "../context/app-context";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useIsMounted } from "../lib/hooks/use-is-mounted";
|
||||
import { isValidRedirectUri } from "../utils/utils";
|
||||
|
||||
export const LoginPage = () => {
|
||||
const queryString = window.location.search;
|
||||
const params = new URLSearchParams(queryString);
|
||||
const redirectUri = params.get("redirect_uri") ?? "";
|
||||
|
||||
const { isLoggedIn } = useUserContext();
|
||||
|
||||
if (isLoggedIn) {
|
||||
return <Navigate to="/logout" />;
|
||||
}
|
||||
|
||||
const {
|
||||
configuredProviders,
|
||||
title,
|
||||
genericName,
|
||||
oauthAutoRedirect: oauthAutoRedirectContext,
|
||||
} = useAppContext();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [oauthAutoRedirect, setOAuthAutoRedirect] = useState(
|
||||
oauthAutoRedirectContext,
|
||||
);
|
||||
|
||||
const oauthProviders = configuredProviders.filter(
|
||||
(value) => value !== "username",
|
||||
);
|
||||
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
const loginMutation = useMutation({
|
||||
mutationFn: (login: LoginFormValues) => {
|
||||
return axios.post("/api/login", login);
|
||||
},
|
||||
onError: (data: AxiosError) => {
|
||||
if (data.response) {
|
||||
if (data.response.status === 429) {
|
||||
notifications.show({
|
||||
title: t("loginFailTitle"),
|
||||
message: t("loginFailRateLimit"),
|
||||
color: "red",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
notifications.show({
|
||||
title: t("loginFailTitle"),
|
||||
message: t("loginFailSubtitle"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
onSuccess: async (data) => {
|
||||
if (data.data.totpPending) {
|
||||
window.location.replace(`/totp?redirect_uri=${redirectUri}`);
|
||||
return;
|
||||
}
|
||||
|
||||
notifications.show({
|
||||
title: t("loginSuccessTitle"),
|
||||
message: t("loginSuccessSubtitle"),
|
||||
color: "green",
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
if (!isValidRedirectUri(redirectUri)) {
|
||||
window.location.replace("/");
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.replace(`/continue?redirect_uri=${redirectUri}`);
|
||||
}, 500);
|
||||
},
|
||||
});
|
||||
|
||||
const loginOAuthMutation = useMutation({
|
||||
mutationFn: (provider: string) => {
|
||||
return axios.get(
|
||||
`/api/oauth/url/${provider}?redirect_uri=${redirectUri}`,
|
||||
);
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
title: t("loginOauthFailTitle"),
|
||||
message: t("loginOauthFailSubtitle"),
|
||||
color: "red",
|
||||
});
|
||||
setOAuthAutoRedirect("none");
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
notifications.show({
|
||||
title: t("loginOauthSuccessTitle"),
|
||||
message: t("loginOauthSuccessSubtitle"),
|
||||
color: "blue",
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.href = data.data.url;
|
||||
}, 500);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (values: LoginFormValues) => {
|
||||
loginMutation.mutate(values);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isMounted()) {
|
||||
if (
|
||||
oauthProviders.includes(oauthAutoRedirect) &&
|
||||
isValidRedirectUri(redirectUri)
|
||||
) {
|
||||
loginOAuthMutation.mutate(oauthAutoRedirect);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (
|
||||
oauthProviders.includes(oauthAutoRedirect) &&
|
||||
isValidRedirectUri(redirectUri)
|
||||
) {
|
||||
return (
|
||||
<Layout>
|
||||
<Paper shadow="md" p="xl" mt={30} radius="md" withBorder>
|
||||
<Text size="xl" fw={700}>
|
||||
{t("continueRedirectingTitle")}
|
||||
</Text>
|
||||
<Text>{t("loginOauthSuccessSubtitle")}</Text>
|
||||
</Paper>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Title ta="center">{title}</Title>
|
||||
<Paper shadow="md" p="xl" mt={30} radius="md" withBorder>
|
||||
{oauthProviders.length > 0 && (
|
||||
<>
|
||||
<Text size="lg" fw={500} ta="center">
|
||||
{t("loginTitle")}
|
||||
</Text>
|
||||
<OAuthButtons
|
||||
oauthProviders={oauthProviders}
|
||||
isPending={loginOAuthMutation.isPending}
|
||||
mutate={loginOAuthMutation.mutate}
|
||||
genericName={genericName}
|
||||
/>
|
||||
{configuredProviders.includes("username") && (
|
||||
<Divider
|
||||
label={t("loginDivider")}
|
||||
labelPosition="center"
|
||||
my="lg"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{configuredProviders.includes("username") && (
|
||||
<LoginForm
|
||||
isPending={loginMutation.isPending}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
)}
|
||||
</Paper>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
@@ -6,9 +6,13 @@ import { useUserContext } from "../context/user-context";
|
||||
import { Navigate } from "react-router";
|
||||
import { Layout } from "../components/layouts/layout";
|
||||
import { capitalize } from "../utils/utils";
|
||||
import { useAppContext } from "../context/app-context";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
export const LogoutPage = () => {
|
||||
const { isLoggedIn, username, oauth, provider } = useUserContext();
|
||||
const { isLoggedIn, oauth, provider, email, username } = useUserContext();
|
||||
const { genericName } = useAppContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!isLoggedIn) {
|
||||
return <Navigate to="/login" />;
|
||||
@@ -20,15 +24,15 @@ export const LogoutPage = () => {
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
title: "Failed to logout",
|
||||
message: "Please try again",
|
||||
title: t("logoutFailTitle"),
|
||||
message: t("logoutFailSubtitle"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
notifications.show({
|
||||
title: "Logged out",
|
||||
message: "Goodbye!",
|
||||
title: t("logoutSuccessTitle"),
|
||||
message: t("logoutSuccessSubtitle"),
|
||||
color: "green",
|
||||
});
|
||||
setTimeout(() => {
|
||||
@@ -41,20 +45,38 @@ export const LogoutPage = () => {
|
||||
<Layout>
|
||||
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
|
||||
<Text size="xl" fw={700}>
|
||||
Logout
|
||||
{t("logoutTitle")}
|
||||
</Text>
|
||||
<Text>
|
||||
You are currently logged in as <Code>{username}</Code>
|
||||
{oauth && ` using ${capitalize(provider)}`}. Click the button below to
|
||||
log out.
|
||||
{oauth ? (
|
||||
<Trans
|
||||
i18nKey="logoutOauthSubtitle"
|
||||
t={t}
|
||||
components={{ Code: <Code /> }}
|
||||
values={{
|
||||
provider:
|
||||
provider === "generic" ? genericName : capitalize(provider),
|
||||
username: email,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Trans
|
||||
i18nKey="logoutUsernameSubtitle"
|
||||
t={t}
|
||||
components={{ Code: <Code /> }}
|
||||
values={{
|
||||
username: username,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Text>
|
||||
<Button
|
||||
fullWidth
|
||||
mt="xl"
|
||||
onClick={() => logoutMutation.mutate()}
|
||||
loading={logoutMutation.isLoading}
|
||||
loading={logoutMutation.isPending}
|
||||
>
|
||||
Logout
|
||||
{t("logoutTitle")}
|
||||
</Button>
|
||||
</Paper>
|
||||
</Layout>
|
||||