Compare commits
24 Commits
refactor/e
...
v3.2.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bafcb9a867 | ||
|
|
d322c13791 | ||
|
|
8e84e59c2f | ||
|
|
bd7e160e10 | ||
|
|
df849d5a5c | ||
|
|
5cf4e208c6 | ||
|
|
07ddd4f917 | ||
|
|
98abe514e1 | ||
|
|
e43bd651b6 | ||
|
|
6dc461c11e | ||
|
|
2ed90dfa34 | ||
|
|
14ce8ecf98 | ||
|
|
46526b564e | ||
|
|
d82e959734 | ||
|
|
187e46eae5 | ||
|
|
fe5f26aea5 | ||
|
|
69a7972cd6 | ||
|
|
354668b016 | ||
|
|
c7000951fe | ||
|
|
33ea6c17d2 | ||
|
|
90801b77fa | ||
|
|
4076a7e464 | ||
|
|
fd32e737a3 | ||
|
|
3ccc831a1f |
@@ -26,5 +26,7 @@ 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
|
||||
6
.github/workflows/ci.yml
vendored
@@ -26,17 +26,17 @@ jobs:
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: |
|
||||
cd site
|
||||
cd frontend
|
||||
bun install
|
||||
|
||||
- name: Build frontend
|
||||
run: |
|
||||
cd site
|
||||
cd frontend
|
||||
bun run build
|
||||
|
||||
- name: Copy frontend
|
||||
run: |
|
||||
cp -r site/dist internal/assets/dist
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
|
||||
- name: Run tests
|
||||
run: go test -v ./...
|
||||
|
||||
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
|
||||
@@ -20,22 +20,22 @@ 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 errors, to install the go requirements, run:
|
||||
Although you will not need the requirements in your machine since the development will happen in docker, I still recommend to install them because this way you will not have import errors, to install the go requirements, run:
|
||||
|
||||
```sh
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
You also need to download the frontend requirements, this can be done like so:
|
||||
You also need to download the frontend dependencies, this can be done like so:
|
||||
|
||||
```sh
|
||||
cd site/
|
||||
cd frontend/
|
||||
bun install
|
||||
```
|
||||
|
||||
## Create your `.env` file
|
||||
|
||||
In order to ocnfigure 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.
|
||||
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
|
||||
|
||||
|
||||
33
Dockerfile
@@ -1,22 +1,22 @@
|
||||
# Site builder
|
||||
FROM oven/bun:1.1.45-alpine AS site-builder
|
||||
FROM oven/bun:1.1.45-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
|
||||
|
||||
@@ -33,7 +33,7 @@ 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 -ldflags "-s -w"
|
||||
|
||||
@@ -42,8 +42,13 @@ 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"]
|
||||
11
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" width="256" src="frontend/public/logo.png">
|
||||
<h1>Tinyauth</h1>
|
||||
<p>The easiest way to secure your apps with a login screen.</p>
|
||||
</div>
|
||||
@@ -10,6 +10,7 @@
|
||||
<img alt="Commit activity" src="https://img.shields.io/github/commit-activity/w/steveiliop56/tinyauth">
|
||||
<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 />
|
||||
@@ -38,6 +39,10 @@ You can find documentation and guides on all available configuration of tinyauth
|
||||
|
||||
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.
|
||||
@@ -56,3 +61,7 @@ Credits for the logo of this app go to:
|
||||
|
||||
- **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)
|
||||
1
air.toml
@@ -2,7 +2,6 @@ root = "/tinyauth"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
pre_cmd = ["go mod tidy"]
|
||||
cmd = "go build -o ./tmp/tinyauth ."
|
||||
bin = "tmp/tinyauth"
|
||||
include_ext = ["go"]
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"timestamp": "2025-03-10T19:00:00.000Z",
|
||||
"thumbnail": {
|
||||
"url": "https://github.com/steveiliop56/tinyauth/blob/main/site/public/logo.png?raw=true"
|
||||
"url": "https://github.com/steveiliop56/tinyauth/blob/main/frontend/public/logo.png?raw=true"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
33
cmd/root.go
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -94,10 +93,8 @@ var rootCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
// Create handlers config
|
||||
serverConfig := types.HandlersConfig{
|
||||
handlersConfig := types.HandlersConfig{
|
||||
AppURL: config.AppURL,
|
||||
Domain: fmt.Sprintf(".%s", domain),
|
||||
CookieSecure: config.CookieSecure,
|
||||
DisableContinue: config.DisableContinue,
|
||||
Title: config.Title,
|
||||
GenericName: config.GenericName,
|
||||
@@ -105,12 +102,20 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
// Create api config
|
||||
apiConfig := types.APIConfig{
|
||||
Port: config.Port,
|
||||
Address: config.Address,
|
||||
Secret: config.Secret,
|
||||
CookieSecure: config.CookieSecure,
|
||||
SessionExpiry: config.SessionExpiry,
|
||||
Domain: domain,
|
||||
Port: config.Port,
|
||||
Address: config.Address,
|
||||
}
|
||||
|
||||
// Create auth config
|
||||
authConfig := types.AuthConfig{
|
||||
Users: users,
|
||||
OauthWhitelist: oauthWhitelist,
|
||||
Secret: config.Secret,
|
||||
CookieSecure: config.CookieSecure,
|
||||
SessionExpiry: config.SessionExpiry,
|
||||
Domain: domain,
|
||||
LoginTimeout: config.LoginTimeout,
|
||||
LoginMaxRetries: config.LoginMaxRetries,
|
||||
}
|
||||
|
||||
// Create docker service
|
||||
@@ -121,7 +126,7 @@ var rootCmd = &cobra.Command{
|
||||
HandleError(err, "Failed to initialize docker")
|
||||
|
||||
// Create auth service
|
||||
auth := auth.NewAuth(docker, users, oauthWhitelist, config.SessionExpiry)
|
||||
auth := auth.NewAuth(authConfig, docker)
|
||||
|
||||
// Create OAuth providers service
|
||||
providers := providers.NewProviders(oauthConfig)
|
||||
@@ -133,7 +138,7 @@ var rootCmd = &cobra.Command{
|
||||
hooks := hooks.NewHooks(auth, providers)
|
||||
|
||||
// Create handlers
|
||||
handlers := handlers.NewHandlers(serverConfig, auth, hooks, providers)
|
||||
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
||||
|
||||
// Create API
|
||||
api := api.NewAPI(apiConfig, handlers)
|
||||
@@ -198,6 +203,8 @@ func init() {
|
||||
rootCmd.Flags().Bool("disable-continue", false, "Disable continue screen and redirect to app directly.")
|
||||
rootCmd.Flags().String("oauth-whitelist", "", "Comma separated list of email addresses to whitelist when using OAuth.")
|
||||
rootCmd.Flags().Int("session-expiry", 86400, "Session (cookie) expiration time in seconds.")
|
||||
rootCmd.Flags().Int("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.")
|
||||
|
||||
@@ -232,6 +239,8 @@ func init() {
|
||||
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")
|
||||
|
||||
// Bind flags to viper
|
||||
viper.BindPFlags(rootCmd.Flags())
|
||||
|
||||
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",
|
||||
},
|
||||
]
|
||||
@@ -14,22 +14,20 @@ services:
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.nginx.rule: Host(`whoami.example.com`)
|
||||
traefik.http.services.nginx.loadbalancer.server.port: 80
|
||||
traefik.http.routers.nginx.middlewares: tinyauth
|
||||
|
||||
tinyauth-frontend:
|
||||
container_name: tinyauth-frontend
|
||||
build:
|
||||
context: .
|
||||
dockerfile: site/Dockerfile.dev
|
||||
dockerfile: frontend/Dockerfile.dev
|
||||
volumes:
|
||||
- ./site/src:/site/src
|
||||
- ./frontend/src:/frontend/src
|
||||
ports:
|
||||
- 5173:5173
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
|
||||
traefik.http.services.tinyauth.loadbalancer.server.port: 5173
|
||||
|
||||
tinyauth-backend:
|
||||
container_name: tinyauth-backend
|
||||
@@ -41,6 +39,7 @@ services:
|
||||
- ./internal:/tinyauth/internal
|
||||
- ./cmd:/tinyauth/cmd
|
||||
- ./main.go:/tinyauth/main.go
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
ports:
|
||||
- 3000:3000
|
||||
labels:
|
||||
|
||||
@@ -14,7 +14,6 @@ services:
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.nginx.rule: Host(`whoami.example.com`)
|
||||
traefik.http.services.nginx.loadbalancer.server.port: 80
|
||||
traefik.http.routers.nginx.middlewares: tinyauth
|
||||
|
||||
tinyauth:
|
||||
@@ -27,5 +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>
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "site",
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "site",
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@mantine/core": "^7.16.0",
|
||||
@@ -2246,4 +2246,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "site",
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
@@ -16,8 +16,14 @@
|
||||
"@mantine/notifications": "^7.16.0",
|
||||
"@tanstack/react-query": "4",
|
||||
"axios": "^1.7.9",
|
||||
"i18next": "^24.2.3",
|
||||
"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": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-router": "^7.1.3",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
@@ -38,4 +44,4 @@
|
||||
"typescript-eslint": "^8.18.2",
|
||||
"vite": "^6.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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 |
@@ -1,6 +1,7 @@
|
||||
import { TextInput, PasswordInput, Button } from "@mantine/core";
|
||||
import { useForm, zodResolver } from "@mantine/form";
|
||||
import { LoginFormValues, loginSchema } from "../../schemas/login-schema";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface LoginFormProps {
|
||||
isLoading: boolean;
|
||||
@@ -9,6 +10,7 @@ interface LoginFormProps {
|
||||
|
||||
export const LoginForm = (props: LoginFormProps) => {
|
||||
const { isLoading, onSubmit } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const form = useForm({
|
||||
mode: "uncontrolled",
|
||||
@@ -22,7 +24,7 @@ export const LoginForm = (props: LoginFormProps) => {
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||
<TextInput
|
||||
label="Username"
|
||||
label={t("loginUsername")}
|
||||
placeholder="user@example.com"
|
||||
required
|
||||
disabled={isLoading}
|
||||
@@ -30,7 +32,7 @@ export const LoginForm = (props: LoginFormProps) => {
|
||||
{...form.getInputProps("username")}
|
||||
/>
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
label={t("loginPassword")}
|
||||
placeholder="password"
|
||||
required
|
||||
mt="md"
|
||||
@@ -39,7 +41,7 @@ export const LoginForm = (props: LoginFormProps) => {
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
<Button fullWidth mt="xl" type="submit" loading={isLoading}>
|
||||
Login
|
||||
{t("loginSubmit")}
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
37
frontend/src/lib/i18n/i18n.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
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";
|
||||
|
||||
i18n
|
||||
.use(ChainedBackend)
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
fallbackLng: "en",
|
||||
debug: import.meta.env.MODE === "development",
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
|
||||
load: "currentOnly",
|
||||
|
||||
backend: {
|
||||
backends: [
|
||||
HttpBackend,
|
||||
resourcesToBackend(
|
||||
(language: string) => import(`./locales/${language}.json`),
|
||||
),
|
||||
],
|
||||
backendOptions: [
|
||||
{
|
||||
loadPath: "https://cdn.tinyauth.app/i18n/v1/{{lng}}.json",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
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];
|
||||
45
frontend/src/lib/i18n/locales/af-ZA.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/ar-SA.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/ca-ES.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/cs-CZ.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/da-DK.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/de-DE.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/el-GR.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"loginTitle": "Καλώς ήρθατε, συνδεθείτε με",
|
||||
"loginDivider": "Ή συνεχίστε με κωδικό πρόσβασης",
|
||||
"loginUsername": "Όνομα Χρήστη",
|
||||
"loginPassword": "Κωδικός",
|
||||
"loginSubmit": "Είσοδος",
|
||||
"loginFailTitle": "Αποτυχία σύνδεσης",
|
||||
"loginFailSubtitle": "Παρακαλώ ελέγξτε το όνομα χρήστη και τον κωδικό πρόσβασης",
|
||||
"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": "Ο χρήστης με όνομα χρήστη {{username}} δεν είναι εξουσιοδοτημένος να έχει πρόσβαση στον πόρο <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "Ο χρήστης με όνομα χρήστη {{username}} δεν είναι εξουσιοδοτημένος να συνδεθεί.",
|
||||
"unauthorizedButton": "Προσπαθήστε ξανά"
|
||||
}
|
||||
46
frontend/src/lib/i18n/locales/en-US.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
46
frontend/src/lib/i18n/locales/en.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/es-ES.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/fi-FI.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/fr-FR.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} n'est pas autorisé à accéder à la ressource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "L'utilisateur avec le nom d'utilisateur {{username}} n'est pas autorisé à se connecter.",
|
||||
"unauthorizedButton": "Réessayer"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/he-IL.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/hu-HU.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/it-IT.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/ja-JP.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/ko-KR.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/nl-NL.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/no-NO.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/pl-PL.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"loginTitle": "Witaj ponownie, zaloguj się przez",
|
||||
"loginDivider": "Lub kontynuuj z hasłem",
|
||||
"loginUsername": "Nazwa użytkownika",
|
||||
"loginPassword": "Hasło",
|
||||
"loginSubmit": "Login",
|
||||
"loginFailTitle": "Failed to log in",
|
||||
"loginFailSubtitle": "Sprawdź swoją nazwę użytkownika i hasło",
|
||||
"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": "An error occurred on the server and it currently cannot serve your request.",
|
||||
"internalErrorButton": "Spróbuj ponownie",
|
||||
"logoutFailTitle": "Failed to log out",
|
||||
"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 {{username}} nie jest upoważniony do uzyskania dostępu do zasobu <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "Użytkownik o nazwie {{username}} nie jest upoważniony do logowania.",
|
||||
"unauthorizedButton": "Spróbuj ponownie"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/pt-BR.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/pt-PT.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/ro-RO.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/ru-RU.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/sr-SP.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/sv-SE.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/tr-TR.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/uk-UA.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/vi-VN.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/zh-CN.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
45
frontend/src/lib/i18n/locales/zh-TW.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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",
|
||||
"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 {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
|
||||
"unauthorizedButton": "Try again"
|
||||
}
|
||||
@@ -17,6 +17,7 @@ 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";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -6,6 +6,7 @@ import { Layout } from "../components/layouts/layout";
|
||||
import { ReactNode } from "react";
|
||||
import { isQueryValid } from "../utils/utils";
|
||||
import { useAppContext } from "../context/app-context";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
export const ContinuePage = () => {
|
||||
const queryString = window.location.search;
|
||||
@@ -14,6 +15,7 @@ export const ContinuePage = () => {
|
||||
|
||||
const { isLoggedIn } = useUserContext();
|
||||
const { disableContinue } = useAppContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!isLoggedIn) {
|
||||
return <Navigate to={`/login?redirect_uri=${redirectUri}`} />;
|
||||
@@ -25,8 +27,8 @@ export const ContinuePage = () => {
|
||||
|
||||
const redirect = () => {
|
||||
notifications.show({
|
||||
title: "Redirecting",
|
||||
message: "You should be redirected to the app soon",
|
||||
title: t("continueRedirectingTitle"),
|
||||
message: t("continueRedirectingSubtitle"),
|
||||
color: "blue",
|
||||
});
|
||||
setTimeout(() => {
|
||||
@@ -42,12 +44,9 @@ export const ContinuePage = () => {
|
||||
return (
|
||||
<ContinuePageLayout>
|
||||
<Text size="xl" fw={700}>
|
||||
Invalid Redirect
|
||||
</Text>
|
||||
<Text>
|
||||
The redirect URL is invalid, please contact the app owner to fix the
|
||||
issue.
|
||||
{t("Invalid redirect")}
|
||||
</Text>
|
||||
<Text>{t("The redirect URL is invalid")}</Text>
|
||||
</ContinuePageLayout>
|
||||
);
|
||||
}
|
||||
@@ -57,9 +56,9 @@ export const ContinuePage = () => {
|
||||
return (
|
||||
<ContinuePageLayout>
|
||||
<Text size="xl" fw={700}>
|
||||
Redirecting
|
||||
{t("continueRedirectingTitle")}
|
||||
</Text>
|
||||
<Text>You should be redirected to your app soon.</Text>
|
||||
<Text>{t("continueRedirectingSubtitle")}</Text>
|
||||
</ContinuePageLayout>
|
||||
);
|
||||
}
|
||||
@@ -68,14 +67,17 @@ export const ContinuePage = () => {
|
||||
return (
|
||||
<ContinuePageLayout>
|
||||
<Text size="xl" fw={700}>
|
||||
Insecure Redirect
|
||||
{t("continueInsecureRedirectTitle")}
|
||||
</Text>
|
||||
<Text>
|
||||
Your are trying to redirect from <Code>https</Code> to{" "}
|
||||
<Code>http</Code>, are you sure you want to continue?
|
||||
<Trans
|
||||
i18nKey="continueInsecureRedirectSubtitle"
|
||||
t={t}
|
||||
components={{ Code: <Code /> }}
|
||||
/>
|
||||
</Text>
|
||||
<Button fullWidth mt="xl" color="yellow" onClick={redirect}>
|
||||
Continue
|
||||
{t("continueTitle")}
|
||||
</Button>
|
||||
</ContinuePageLayout>
|
||||
);
|
||||
@@ -84,11 +86,11 @@ export const ContinuePage = () => {
|
||||
return (
|
||||
<ContinuePageLayout>
|
||||
<Text size="xl" fw={700}>
|
||||
Continue
|
||||
{t("continueTitle")}
|
||||
</Text>
|
||||
<Text>Click the button to continue to your app.</Text>
|
||||
<Text>{t("continueSubtitle")}</Text>
|
||||
<Button fullWidth mt="xl" onClick={redirect}>
|
||||
Continue
|
||||
{t("continueTitle")}
|
||||
</Button>
|
||||
</ContinuePageLayout>
|
||||
);
|
||||
@@ -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>
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Paper, Title, Text, Divider } from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
import axios, { type AxiosError } from "axios";
|
||||
import { useUserContext } from "../context/user-context";
|
||||
import { Navigate } from "react-router";
|
||||
import { Layout } from "../components/layouts/layout";
|
||||
@@ -10,6 +10,7 @@ import { LoginFormValues } from "../schemas/login-schema";
|
||||
import { LoginForm } from "../components/auth/login-forn";
|
||||
import { isQueryValid } from "../utils/utils";
|
||||
import { useAppContext } from "../context/app-context";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const LoginPage = () => {
|
||||
const queryString = window.location.search;
|
||||
@@ -18,6 +19,7 @@ export const LoginPage = () => {
|
||||
|
||||
const { isLoggedIn } = useUserContext();
|
||||
const { configuredProviders, title, genericName } = useAppContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const oauthProviders = configuredProviders.filter(
|
||||
(value) => value !== "username",
|
||||
@@ -31,10 +33,20 @@ export const LoginPage = () => {
|
||||
mutationFn: (login: LoginFormValues) => {
|
||||
return axios.post("/api/login", login);
|
||||
},
|
||||
onError: () => {
|
||||
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: "Failed to login",
|
||||
message: "Check your username and password",
|
||||
title: t("loginFailTitle"),
|
||||
message: t("loginFailSubtitle"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
@@ -45,8 +57,8 @@ export const LoginPage = () => {
|
||||
}
|
||||
|
||||
notifications.show({
|
||||
title: "Logged in",
|
||||
message: "Welcome back!",
|
||||
title: t("loginSuccessTitle"),
|
||||
message: t("loginSuccessSubtitle"),
|
||||
color: "green",
|
||||
});
|
||||
|
||||
@@ -69,15 +81,15 @@ export const LoginPage = () => {
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
title: "Internal error",
|
||||
message: "Failed to get OAuth URL",
|
||||
title: t("loginOauthFailTitle"),
|
||||
message: t("loginOauthFailSubtitle"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
notifications.show({
|
||||
title: "Redirecting",
|
||||
message: "Redirecting to your OAuth provider",
|
||||
title: t("loginOauthSuccessTitle"),
|
||||
message: t("loginOauthSuccessSubtitle"),
|
||||
color: "blue",
|
||||
});
|
||||
setTimeout(() => {
|
||||
@@ -97,7 +109,7 @@ export const LoginPage = () => {
|
||||
{oauthProviders.length > 0 && (
|
||||
<>
|
||||
<Text size="lg" fw={500} ta="center">
|
||||
Welcome back, login with
|
||||
{t("loginTitle")}
|
||||
</Text>
|
||||
<OAuthButtons
|
||||
oauthProviders={oauthProviders}
|
||||
@@ -107,7 +119,7 @@ export const LoginPage = () => {
|
||||
/>
|
||||
{configuredProviders.includes("username") && (
|
||||
<Divider
|
||||
label="Or continue with password"
|
||||
label={t("loginDivider")}
|
||||
labelPosition="center"
|
||||
my="lg"
|
||||
/>
|
||||
@@ -7,10 +7,12 @@ 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 { genericName } = useAppContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!isLoggedIn) {
|
||||
return <Navigate to="/login" />;
|
||||
@@ -22,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(() => {
|
||||
@@ -43,13 +45,30 @@ 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 === "generic" ? genericName : provider)} OAuth`}
|
||||
. Click the button below to log out.
|
||||
{oauth ? (
|
||||
<Trans
|
||||
i18nKey="logoutOauthSubtitle"
|
||||
t={t}
|
||||
components={{ Code: <Code /> }}
|
||||
values={{
|
||||
provider:
|
||||
provider === "generic" ? genericName : capitalize(provider),
|
||||
username: username,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Trans
|
||||
i18nKey="logoutUsernameSubtitle"
|
||||
t={t}
|
||||
components={{ Code: <Code /> }}
|
||||
values={{
|
||||
username: username,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Text>
|
||||
<Button
|
||||
fullWidth
|
||||
@@ -57,7 +76,7 @@ export const LogoutPage = () => {
|
||||
onClick={() => logoutMutation.mutate()}
|
||||
loading={logoutMutation.isLoading}
|
||||
>
|
||||
Logout
|
||||
{t("logoutTitle")}
|
||||
</Button>
|
||||
</Paper>
|
||||
</Layout>
|
||||
@@ -1,16 +1,18 @@
|
||||
import { Button, Paper, Text } from "@mantine/core";
|
||||
import { Layout } from "../components/layouts/layout";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const NotFoundPage = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Layout>
|
||||
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
|
||||
<Text size="xl" fw={700}>
|
||||
Not found
|
||||
{t("notFoundTitle")}
|
||||
</Text>
|
||||
<Text>The page you are looking for does not exist.</Text>
|
||||
<Text>{t("notFoundSubtitle")}</Text>
|
||||
<Button fullWidth mt="xl" onClick={() => window.location.replace("/")}>
|
||||
Go home
|
||||
{t("notFoundButton")}
|
||||
</Button>
|
||||
</Paper>
|
||||
</Layout>
|
||||
@@ -7,6 +7,7 @@ import { useMutation } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useAppContext } from "../context/app-context";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const TotpPage = () => {
|
||||
const queryString = window.location.search;
|
||||
@@ -15,6 +16,7 @@ export const TotpPage = () => {
|
||||
|
||||
const { totpPending, isLoggedIn } = useUserContext();
|
||||
const { title } = useAppContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (isLoggedIn) {
|
||||
return <Navigate to={`/logout`} />;
|
||||
@@ -30,15 +32,15 @@ export const TotpPage = () => {
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
title: "Failed to verify code",
|
||||
message: "Please try again",
|
||||
title: t("totpFailTitle"),
|
||||
message: t("totpFailSubtitle"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
notifications.show({
|
||||
title: "Verified",
|
||||
message: "Redirecting to your app",
|
||||
title: t("totpSuccessTitle"),
|
||||
message: t("totpSuccessSubtitle"),
|
||||
color: "green",
|
||||
});
|
||||
setTimeout(() => {
|
||||
@@ -52,7 +54,7 @@ export const TotpPage = () => {
|
||||
<Title ta="center">{title}</Title>
|
||||
<Paper shadow="md" p="xl" mt={30} radius="md" withBorder>
|
||||
<Text size="lg" fw={500} mb="md" ta="center">
|
||||
Enter your TOTP code
|
||||
{t("totpTitle")}
|
||||
</Text>
|
||||
<TotpForm
|
||||
isLoading={totpMutation.isLoading}
|
||||
@@ -2,6 +2,7 @@ import { Button, Code, Paper, Text } from "@mantine/core";
|
||||
import { Layout } from "../components/layouts/layout";
|
||||
import { Navigate } from "react-router";
|
||||
import { isQueryValid } from "../utils/utils";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
export const UnauthorizedPage = () => {
|
||||
const queryString = window.location.search;
|
||||
@@ -9,6 +10,8 @@ export const UnauthorizedPage = () => {
|
||||
const username = params.get("username") ?? "";
|
||||
const resource = params.get("resource") ?? "";
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!isQueryValid(username)) {
|
||||
return <Navigate to="/" />;
|
||||
}
|
||||
@@ -17,16 +20,26 @@ export const UnauthorizedPage = () => {
|
||||
<Layout>
|
||||
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
|
||||
<Text size="xl" fw={700}>
|
||||
Unauthorized
|
||||
{t("Unauthorized")}
|
||||
</Text>
|
||||
<Text>
|
||||
The user with username <Code>{username}</Code> is not authorized to{" "}
|
||||
{isQueryValid(resource) ? (
|
||||
<span>
|
||||
access the <Code>{resource}</Code> resource.
|
||||
</span>
|
||||
<Text>
|
||||
<Trans
|
||||
i18nKey="unauthorizedResourceSubtitle"
|
||||
t={t}
|
||||
components={{ Code: <Code /> }}
|
||||
values={{ resource, username }}
|
||||
/>
|
||||
</Text>
|
||||
) : (
|
||||
"login."
|
||||
<Text>
|
||||
<Trans
|
||||
i18nKey="unauthorizedLoginSubtitle"
|
||||
t={t}
|
||||
values={{ username }}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
<Button
|
||||
@@ -34,7 +47,7 @@ export const UnauthorizedPage = () => {
|
||||
mt="xl"
|
||||
onClick={() => window.location.replace("/login")}
|
||||
>
|
||||
Try again
|
||||
{t("unauthorizedButton")}
|
||||
</Button>
|
||||
</Paper>
|
||||
</Layout>
|
||||
4
go.mod
@@ -3,7 +3,6 @@ module tinyauth
|
||||
go 1.23.2
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/sessions v1.0.2
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/go-playground/validator/v10 v10.24.0
|
||||
github.com/google/go-querystring v1.1.0
|
||||
@@ -58,9 +57,8 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/goccy/go-json v0.10.4 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.2.2 // indirect
|
||||
github.com/gorilla/sessions v1.2.2
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
|
||||
4
go.sum
@@ -65,8 +65,6 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA=
|
||||
github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
@@ -99,8 +97,6 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||
|
||||
@@ -4,14 +4,13 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"tinyauth/internal/assets"
|
||||
"tinyauth/internal/handlers"
|
||||
"tinyauth/internal/types"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -50,26 +49,22 @@ func (api *API) Init() {
|
||||
log.Debug().Msg("Setting up file server")
|
||||
fileServer := http.FileServer(http.FS(dist))
|
||||
|
||||
// Setup cookie store
|
||||
log.Debug().Msg("Setting up cookie store")
|
||||
store := cookie.NewStore([]byte(api.Config.Secret))
|
||||
|
||||
// Use session middleware
|
||||
store.Options(sessions.Options{
|
||||
Domain: api.Config.Domain,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: api.Config.CookieSecure,
|
||||
MaxAge: api.Config.SessionExpiry,
|
||||
})
|
||||
|
||||
router.Use(sessions.Sessions("tinyauth", store))
|
||||
|
||||
// UI middleware
|
||||
router.Use(func(c *gin.Context) {
|
||||
// If not an API request, serve the UI
|
||||
if !strings.HasPrefix(c.Request.URL.Path, "/api") {
|
||||
// Check if the file exists
|
||||
_, err := fs.Stat(dist, strings.TrimPrefix(c.Request.URL.Path, "/"))
|
||||
|
||||
// If the file doesn't exist, serve the index.html
|
||||
if os.IsNotExist(err) {
|
||||
c.Request.URL.Path = "/"
|
||||
}
|
||||
|
||||
// Serve the file
|
||||
fileServer.ServeHTTP(c.Writer, c.Request)
|
||||
|
||||
// Stop further processing
|
||||
c.Abort()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -21,23 +21,29 @@ import (
|
||||
|
||||
// Simple API config for tests
|
||||
var apiConfig = types.APIConfig{
|
||||
Port: 8080,
|
||||
Address: "0.0.0.0",
|
||||
Secret: "super-secret-api-thing-for-tests", // It is 32 chars long
|
||||
CookieSecure: false,
|
||||
SessionExpiry: 3600,
|
||||
Port: 8080,
|
||||
Address: "0.0.0.0",
|
||||
}
|
||||
|
||||
// Simple handlers config for tests
|
||||
var handlersConfig = types.HandlersConfig{
|
||||
AppURL: "http://localhost:8080",
|
||||
Domain: ".localhost",
|
||||
CookieSecure: false,
|
||||
DisableContinue: false,
|
||||
Title: "Tinyauth",
|
||||
GenericName: "Generic",
|
||||
}
|
||||
|
||||
// Simple auth config for tests
|
||||
var authConfig = types.AuthConfig{
|
||||
Users: types.Users{},
|
||||
OauthWhitelist: []string{},
|
||||
Secret: "super-secret-api-thing-for-tests", // It is 32 chars long
|
||||
CookieSecure: false,
|
||||
SessionExpiry: 3600,
|
||||
LoginTimeout: 0,
|
||||
LoginMaxRetries: 0,
|
||||
}
|
||||
|
||||
// Cookie
|
||||
var cookie string
|
||||
|
||||
@@ -61,12 +67,13 @@ func getAPI(t *testing.T) *api.API {
|
||||
}
|
||||
|
||||
// Create auth service
|
||||
auth := auth.NewAuth(docker, types.Users{
|
||||
authConfig.Users = types.Users{
|
||||
{
|
||||
Username: user.Username,
|
||||
Password: user.Password,
|
||||
},
|
||||
}, nil, apiConfig.SessionExpiry)
|
||||
}
|
||||
auth := auth.NewAuth(authConfig, docker)
|
||||
|
||||
// Create providers service
|
||||
providers := providers.NewProviders(types.OAuthConfig{})
|
||||
@@ -78,7 +85,7 @@ func getAPI(t *testing.T) *api.API {
|
||||
hooks := hooks.NewHooks(auth, providers)
|
||||
|
||||
// Create handlers service
|
||||
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers)
|
||||
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
||||
|
||||
// Create API
|
||||
api := api.NewAPI(apiConfig, handlers)
|
||||
|
||||
@@ -1 +1 @@
|
||||
v3.1.0
|
||||
v3.2.0
|
||||
@@ -1,38 +1,64 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"tinyauth/internal/docker"
|
||||
"tinyauth/internal/types"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func NewAuth(docker *docker.Docker, userList types.Users, oauthWhitelist []string, sessionExpiry int) *Auth {
|
||||
func NewAuth(config types.AuthConfig, docker *docker.Docker) *Auth {
|
||||
return &Auth{
|
||||
Docker: docker,
|
||||
Users: userList,
|
||||
OAuthWhitelist: oauthWhitelist,
|
||||
SessionExpiry: sessionExpiry,
|
||||
Config: config,
|
||||
Docker: docker,
|
||||
LoginAttempts: make(map[string]*types.LoginAttempt),
|
||||
}
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
Users types.Users
|
||||
Docker *docker.Docker
|
||||
OAuthWhitelist []string
|
||||
SessionExpiry int
|
||||
Config types.AuthConfig
|
||||
Docker *docker.Docker
|
||||
LoginAttempts map[string]*types.LoginAttempt
|
||||
LoginMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) {
|
||||
// Create cookie store
|
||||
store := sessions.NewCookieStore([]byte(auth.Config.Secret))
|
||||
|
||||
// Configure cookie store
|
||||
store.Options = &sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: auth.Config.SessionExpiry,
|
||||
Secure: auth.Config.CookieSecure,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteDefaultMode,
|
||||
Domain: fmt.Sprintf(".%s", auth.Config.Domain),
|
||||
}
|
||||
|
||||
// Get session
|
||||
session, err := store.Get(c.Request, "tinyauth")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get session")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (auth *Auth) GetUser(username string) *types.User {
|
||||
// Loop through users and return the user if the username matches
|
||||
for _, user := range auth.Users {
|
||||
for _, user := range auth.Config.Users {
|
||||
if user.Username == username {
|
||||
return &user
|
||||
}
|
||||
@@ -45,14 +71,78 @@ func (auth *Auth) CheckPassword(user types.User, password string) bool {
|
||||
return bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) == nil
|
||||
}
|
||||
|
||||
// IsAccountLocked checks if a username or IP is locked due to too many failed login attempts
|
||||
func (auth *Auth) IsAccountLocked(identifier string) (bool, int) {
|
||||
auth.LoginMutex.RLock()
|
||||
defer auth.LoginMutex.RUnlock()
|
||||
|
||||
// Return false if rate limiting is not configured
|
||||
if auth.Config.LoginMaxRetries <= 0 || auth.Config.LoginTimeout <= 0 {
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// Check if the identifier exists in the map
|
||||
attempt, exists := auth.LoginAttempts[identifier]
|
||||
if !exists {
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// If account is locked, check if lock time has expired
|
||||
if attempt.LockedUntil.After(time.Now()) {
|
||||
// Calculate remaining lockout time in seconds
|
||||
remaining := int(time.Until(attempt.LockedUntil).Seconds())
|
||||
return true, remaining
|
||||
}
|
||||
|
||||
// Lock has expired
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// RecordLoginAttempt records a login attempt for rate limiting
|
||||
func (auth *Auth) RecordLoginAttempt(identifier string, success bool) {
|
||||
// Skip if rate limiting is not configured
|
||||
if auth.Config.LoginMaxRetries <= 0 || auth.Config.LoginTimeout <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
auth.LoginMutex.Lock()
|
||||
defer auth.LoginMutex.Unlock()
|
||||
|
||||
// Get current attempt record or create a new one
|
||||
attempt, exists := auth.LoginAttempts[identifier]
|
||||
if !exists {
|
||||
attempt = &types.LoginAttempt{}
|
||||
auth.LoginAttempts[identifier] = attempt
|
||||
}
|
||||
|
||||
// Update last attempt time
|
||||
attempt.LastAttempt = time.Now()
|
||||
|
||||
// If successful login, reset failed attempts
|
||||
if success {
|
||||
attempt.FailedAttempts = 0
|
||||
attempt.LockedUntil = time.Time{} // Reset lock time
|
||||
return
|
||||
}
|
||||
|
||||
// Increment failed attempts
|
||||
attempt.FailedAttempts++
|
||||
|
||||
// If max retries reached, lock the account
|
||||
if attempt.FailedAttempts >= auth.Config.LoginMaxRetries {
|
||||
attempt.LockedUntil = time.Now().Add(time.Duration(auth.Config.LoginTimeout) * time.Second)
|
||||
log.Warn().Str("identifier", identifier).Int("timeout", auth.Config.LoginTimeout).Msg("Account locked due to too many failed login attempts")
|
||||
}
|
||||
}
|
||||
|
||||
func (auth *Auth) EmailWhitelisted(emailSrc string) bool {
|
||||
// If the whitelist is empty, allow all emails
|
||||
if len(auth.OAuthWhitelist) == 0 {
|
||||
if len(auth.Config.OauthWhitelist) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Loop through the whitelist and return true if the email matches
|
||||
for _, email := range auth.OAuthWhitelist {
|
||||
for _, email := range auth.Config.OauthWhitelist {
|
||||
if email == emailSrc {
|
||||
return true
|
||||
}
|
||||
@@ -62,11 +152,15 @@ func (auth *Auth) EmailWhitelisted(emailSrc string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (auth *Auth) CreateSessionCookie(c *gin.Context, data *types.SessionCookie) {
|
||||
func (auth *Auth) CreateSessionCookie(c *gin.Context, data *types.SessionCookie) error {
|
||||
log.Debug().Msg("Creating session cookie")
|
||||
|
||||
// Get session
|
||||
sessions := sessions.Default(c)
|
||||
session, err := auth.GetSession(c)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get session")
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug().Msg("Setting session cookie")
|
||||
|
||||
@@ -76,54 +170,73 @@ func (auth *Auth) CreateSessionCookie(c *gin.Context, data *types.SessionCookie)
|
||||
if data.TotpPending {
|
||||
sessionExpiry = 3600
|
||||
} else {
|
||||
sessionExpiry = auth.SessionExpiry
|
||||
sessionExpiry = auth.Config.SessionExpiry
|
||||
}
|
||||
|
||||
// Set data
|
||||
sessions.Set("username", data.Username)
|
||||
sessions.Set("provider", data.Provider)
|
||||
sessions.Set("expiry", time.Now().Add(time.Duration(sessionExpiry)*time.Second).Unix())
|
||||
sessions.Set("totpPending", data.TotpPending)
|
||||
session.Values["username"] = data.Username
|
||||
session.Values["provider"] = data.Provider
|
||||
session.Values["expiry"] = time.Now().Add(time.Duration(sessionExpiry) * time.Second).Unix()
|
||||
session.Values["totpPending"] = data.TotpPending
|
||||
session.Values["redirectURI"] = data.RedirectURI
|
||||
|
||||
// Save session
|
||||
sessions.Save()
|
||||
err = session.Save(c.Request, c.Writer)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to save session")
|
||||
return err
|
||||
}
|
||||
|
||||
// Return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *Auth) DeleteSessionCookie(c *gin.Context) {
|
||||
func (auth *Auth) DeleteSessionCookie(c *gin.Context) error {
|
||||
log.Debug().Msg("Deleting session cookie")
|
||||
|
||||
// Get session
|
||||
sessions := sessions.Default(c)
|
||||
session, err := auth.GetSession(c)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get session")
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear session
|
||||
sessions.Clear()
|
||||
// Delete all values in the session
|
||||
for key := range session.Values {
|
||||
delete(session.Values, key)
|
||||
}
|
||||
|
||||
// Save session
|
||||
sessions.Save()
|
||||
err = session.Save(c.Request, c.Writer)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to save session")
|
||||
return err
|
||||
}
|
||||
|
||||
// Return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *Auth) GetSessionCookie(c *gin.Context) types.SessionCookie {
|
||||
func (auth *Auth) GetSessionCookie(c *gin.Context) (types.SessionCookie, error) {
|
||||
log.Debug().Msg("Getting session cookie")
|
||||
|
||||
// Get session
|
||||
sessions := sessions.Default(c)
|
||||
session, err := auth.GetSession(c)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get session")
|
||||
return types.SessionCookie{}, err
|
||||
}
|
||||
|
||||
// Get data
|
||||
cookieUsername := sessions.Get("username")
|
||||
cookieProvider := sessions.Get("provider")
|
||||
cookieExpiry := sessions.Get("expiry")
|
||||
cookieTotpPending := sessions.Get("totpPending")
|
||||
// Get data from session
|
||||
username, usernameOk := session.Values["username"].(string)
|
||||
provider, providerOK := session.Values["provider"].(string)
|
||||
redirectURI, redirectOK := session.Values["redirectURI"].(string)
|
||||
expiry, expiryOk := session.Values["expiry"].(int64)
|
||||
totpPending, totpPendingOk := session.Values["totpPending"].(bool)
|
||||
|
||||
// Convert interfaces to correct types
|
||||
username, usernameOk := cookieUsername.(string)
|
||||
provider, providerOk := cookieProvider.(string)
|
||||
expiry, expiryOk := cookieExpiry.(int64)
|
||||
totpPending, totpPendingOk := cookieTotpPending.(bool)
|
||||
|
||||
// Check if the cookie is invalid
|
||||
if !usernameOk || !providerOk || !expiryOk || !totpPendingOk {
|
||||
log.Warn().Msg("Session cookie invalid")
|
||||
return types.SessionCookie{}
|
||||
if !usernameOk || !providerOK || !expiryOk || !redirectOK || !totpPendingOk {
|
||||
log.Warn().Msg("Session cookie is missing data")
|
||||
return types.SessionCookie{}, nil
|
||||
}
|
||||
|
||||
// Check if the cookie has expired
|
||||
@@ -134,7 +247,7 @@ func (auth *Auth) GetSessionCookie(c *gin.Context) types.SessionCookie {
|
||||
auth.DeleteSessionCookie(c)
|
||||
|
||||
// Return empty cookie
|
||||
return types.SessionCookie{}
|
||||
return types.SessionCookie{}, nil
|
||||
}
|
||||
|
||||
log.Debug().Str("username", username).Str("provider", provider).Int64("expiry", expiry).Bool("totpPending", totpPending).Msg("Parsed cookie")
|
||||
@@ -144,12 +257,13 @@ func (auth *Auth) GetSessionCookie(c *gin.Context) types.SessionCookie {
|
||||
Username: username,
|
||||
Provider: provider,
|
||||
TotpPending: totpPending,
|
||||
}
|
||||
RedirectURI: redirectURI,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (auth *Auth) UserAuthConfigured() bool {
|
||||
// If there are users, return true
|
||||
return len(auth.Users) > 0
|
||||
return len(auth.Config.Users) > 0
|
||||
}
|
||||
|
||||
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext) (bool, error) {
|
||||
@@ -159,41 +273,35 @@ func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext) (bo
|
||||
// Get app id
|
||||
appId := strings.Split(host, ".")[0]
|
||||
|
||||
// Check if resource is allowed
|
||||
allowed, err := auth.Docker.ContainerAction(appId, func(labels types.TinyauthLabels) (bool, error) {
|
||||
// If the container has an oauth whitelist, check if the user is in it
|
||||
if context.OAuth {
|
||||
if len(labels.OAuthWhitelist) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
log.Debug().Msg("Checking OAuth whitelist")
|
||||
if slices.Contains(labels.OAuthWhitelist, context.Username) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// If the container has users, check if the user is in it
|
||||
if len(labels.Users) != 0 {
|
||||
log.Debug().Msg("Checking users")
|
||||
if slices.Contains(labels.Users, context.Username) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Allowed
|
||||
return true, nil
|
||||
})
|
||||
// Get the container labels
|
||||
labels, err := auth.Docker.GetLabels(appId)
|
||||
|
||||
// If there is an error, return false
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error checking if resource is allowed")
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Return if the resource is allowed
|
||||
return allowed, nil
|
||||
// Check if oauth is allowed
|
||||
if context.OAuth {
|
||||
if len(labels.OAuthWhitelist) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
log.Debug().Msg("Checking OAuth whitelist")
|
||||
if slices.Contains(labels.OAuthWhitelist, context.Username) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user is allowed
|
||||
if len(labels.Users) != 0 {
|
||||
log.Debug().Msg("Checking users")
|
||||
if slices.Contains(labels.Users, context.Username) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Not allowed
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (auth *Auth) AuthEnabled(c *gin.Context) (bool, error) {
|
||||
@@ -204,40 +312,37 @@ func (auth *Auth) AuthEnabled(c *gin.Context) (bool, error) {
|
||||
// Get app id
|
||||
appId := strings.Split(host, ".")[0]
|
||||
|
||||
// Check if auth is enabled
|
||||
enabled, err := auth.Docker.ContainerAction(appId, func(labels types.TinyauthLabels) (bool, error) {
|
||||
// Check if the allowed label is empty
|
||||
if labels.Allowed == "" {
|
||||
// Auth enabled
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Compile regex
|
||||
regex, err := regexp.Compile(labels.Allowed)
|
||||
|
||||
// If there is an error, invalid regex, auth enabled
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Invalid regex")
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Check if the uri matches the regex
|
||||
if regex.MatchString(uri) {
|
||||
// Auth disabled
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Auth enabled
|
||||
return true, nil
|
||||
})
|
||||
// Get the container labels
|
||||
labels, err := auth.Docker.GetLabels(appId)
|
||||
|
||||
// If there is an error, auth enabled
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error checking if auth is enabled")
|
||||
return true, err
|
||||
}
|
||||
|
||||
return enabled, nil
|
||||
// Check if the allowed label is empty
|
||||
if labels.Allowed == "" {
|
||||
// Auth enabled
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Compile regex
|
||||
regex, err := regexp.Compile(labels.Allowed)
|
||||
|
||||
// If there is an error, invalid regex, auth enabled
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Invalid regex")
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Check if the uri matches the regex
|
||||
if regex.MatchString(uri) {
|
||||
// Auth disabled
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Auth enabled
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (auth *Auth) GetBasicAuth(c *gin.Context) *types.User {
|
||||
|
||||
147
internal/auth/auth_test.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package auth_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
"tinyauth/internal/auth"
|
||||
"tinyauth/internal/docker"
|
||||
"tinyauth/internal/types"
|
||||
)
|
||||
|
||||
var config = types.AuthConfig{
|
||||
Users: types.Users{},
|
||||
OauthWhitelist: []string{},
|
||||
SessionExpiry: 3600,
|
||||
}
|
||||
|
||||
func TestLoginRateLimiting(t *testing.T) {
|
||||
// Initialize a new auth service with 3 max retries and 5 seconds timeout
|
||||
config.LoginMaxRetries = 3
|
||||
config.LoginTimeout = 5
|
||||
authService := auth.NewAuth(config, &docker.Docker{})
|
||||
|
||||
// Test identifier
|
||||
identifier := "test_user"
|
||||
|
||||
// Test successful login - should not lock account
|
||||
t.Log("Testing successful login")
|
||||
|
||||
authService.RecordLoginAttempt(identifier, true)
|
||||
locked, _ := authService.IsAccountLocked(identifier)
|
||||
|
||||
if locked {
|
||||
t.Fatalf("Account should not be locked after successful login")
|
||||
}
|
||||
|
||||
// Test 2 failed attempts - should not lock account yet
|
||||
t.Log("Testing 2 failed login attempts")
|
||||
|
||||
authService.RecordLoginAttempt(identifier, false)
|
||||
authService.RecordLoginAttempt(identifier, false)
|
||||
locked, _ = authService.IsAccountLocked(identifier)
|
||||
|
||||
if locked {
|
||||
t.Fatalf("Account should not be locked after only 2 failed attempts")
|
||||
}
|
||||
|
||||
// Add one more failed attempt (total 3) - should lock account with maxRetries=3
|
||||
t.Log("Testing 3 failed login attempts")
|
||||
authService.RecordLoginAttempt(identifier, false)
|
||||
locked, remainingTime := authService.IsAccountLocked(identifier)
|
||||
|
||||
if !locked {
|
||||
t.Fatalf("Account should be locked after reaching max retries")
|
||||
}
|
||||
if remainingTime <= 0 || remainingTime > 5 {
|
||||
t.Fatalf("Expected remaining time between 1-5 seconds, got %d", remainingTime)
|
||||
}
|
||||
|
||||
// Test reset after waiting for timeout - use 1 second timeout for fast testing
|
||||
t.Log("Testing unlocking after timeout")
|
||||
|
||||
// Reinitialize auth service with a shorter timeout for testing
|
||||
config.LoginTimeout = 1
|
||||
config.LoginMaxRetries = 3
|
||||
authService = auth.NewAuth(config, &docker.Docker{})
|
||||
|
||||
// Add enough failed attempts to lock the account
|
||||
for i := 0; i < 3; i++ {
|
||||
authService.RecordLoginAttempt(identifier, false)
|
||||
}
|
||||
|
||||
// Verify it's locked
|
||||
locked, _ = authService.IsAccountLocked(identifier)
|
||||
if !locked {
|
||||
t.Fatalf("Account should be locked initially")
|
||||
}
|
||||
|
||||
// Wait a bit and verify it gets unlocked after timeout
|
||||
time.Sleep(1500 * time.Millisecond) // Wait longer than the timeout
|
||||
locked, _ = authService.IsAccountLocked(identifier)
|
||||
|
||||
if locked {
|
||||
t.Fatalf("Account should be unlocked after timeout period")
|
||||
}
|
||||
|
||||
// Test disabled rate limiting
|
||||
t.Log("Testing disabled rate limiting")
|
||||
config.LoginMaxRetries = 0
|
||||
config.LoginTimeout = 0
|
||||
authService = auth.NewAuth(config, &docker.Docker{})
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
authService.RecordLoginAttempt(identifier, false)
|
||||
}
|
||||
|
||||
locked, _ = authService.IsAccountLocked(identifier)
|
||||
if locked {
|
||||
t.Fatalf("Account should not be locked when rate limiting is disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentLoginAttempts(t *testing.T) {
|
||||
// Initialize a new auth service with 2 max retries and 5 seconds timeout
|
||||
config.LoginMaxRetries = 2
|
||||
config.LoginTimeout = 5
|
||||
authService := auth.NewAuth(config, &docker.Docker{})
|
||||
|
||||
// Test multiple identifiers
|
||||
identifiers := []string{"user1", "user2", "user3"}
|
||||
|
||||
// Test that locking one identifier doesn't affect others
|
||||
t.Log("Testing multiple identifiers")
|
||||
|
||||
// Add enough failed attempts to lock first user (2 attempts with maxRetries=2)
|
||||
authService.RecordLoginAttempt(identifiers[0], false)
|
||||
authService.RecordLoginAttempt(identifiers[0], false)
|
||||
|
||||
// Check if first user is locked
|
||||
locked, _ := authService.IsAccountLocked(identifiers[0])
|
||||
if !locked {
|
||||
t.Fatalf("User1 should be locked after reaching max retries")
|
||||
}
|
||||
|
||||
// Check that other users are not affected
|
||||
for i := 1; i < len(identifiers); i++ {
|
||||
locked, _ := authService.IsAccountLocked(identifiers[i])
|
||||
if locked {
|
||||
t.Fatalf("User%d should not be locked", i+1)
|
||||
}
|
||||
}
|
||||
|
||||
// Test successful login after failed attempts (but before lock)
|
||||
t.Log("Testing successful login after failed attempts but before lock")
|
||||
|
||||
// One failed attempt for user2
|
||||
authService.RecordLoginAttempt(identifiers[1], false)
|
||||
|
||||
// Successful login should reset the counter
|
||||
authService.RecordLoginAttempt(identifiers[1], true)
|
||||
|
||||
// Now try a failed login again - should not be locked as counter was reset
|
||||
authService.RecordLoginAttempt(identifiers[1], false)
|
||||
locked, _ = authService.IsAccountLocked(identifiers[1])
|
||||
if locked {
|
||||
t.Fatalf("User2 should not be locked after successful login reset")
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,5 @@ var TinyauthLabels = []string{
|
||||
"tinyauth.oauth.whitelist",
|
||||
"tinyauth.users",
|
||||
"tinyauth.allowed",
|
||||
"tinyauth.headers",
|
||||
}
|
||||
|
||||