Compare commits

..

2 Commits

Author SHA1 Message Date
Stavros
df10eb65ce chore: tidy go mod 2025-04-03 15:45:03 +03:00
Stavros
8bf5a6067e wip 2025-04-03 15:44:47 +03:00
145 changed files with 4260 additions and 5450 deletions

View File

@@ -12,6 +12,9 @@ GITHUB_CLIENT_SECRET_FILE=github_client_secret_file
GOOGLE_CLIENT_ID=google_client_id GOOGLE_CLIENT_ID=google_client_id
GOOGLE_CLIENT_SECRET=google_client_secret GOOGLE_CLIENT_SECRET=google_client_secret
GOOGLE_CLIENT_SECRET_FILE=google_client_secret_file GOOGLE_CLIENT_SECRET_FILE=google_client_secret_file
TAILSCALE_CLIENT_ID=tailscale_client_id
TAILSCALE_CLIENT_SECRET=tailscale_client_secret
TAILSCALE_CLIENT_SECRET_FILE=tailscale__client_secret_file
GENERIC_CLIENT_ID=generic_client_id GENERIC_CLIENT_ID=generic_client_id
GENERIC_CLIENT_SECRET=generic_client_secret GENERIC_CLIENT_SECRET=generic_client_secret
GENERIC_CLIENT_SECRET_FILE=generic_client_secret_file GENERIC_CLIENT_SECRET_FILE=generic_client_secret_file
@@ -23,10 +26,5 @@ DISABLE_CONTINUE=false
OAUTH_WHITELIST= OAUTH_WHITELIST=
GENERIC_NAME=My OAuth GENERIC_NAME=My OAuth
SESSION_EXPIRY=7200 SESSION_EXPIRY=7200
LOGIN_TIMEOUT=300
LOGIN_MAX_RETRIES=5
LOG_LEVEL=0 LOG_LEVEL=0
APP_TITLE=Tinyauth SSO APP_TITLE=Tinyauth SSO
FORGOT_PASSWORD_MESSAGE=Some message about resetting the password
OAUTH_AUTO_REDIRECT=none
BACKGROUND_IMAGE=some_image_url

View File

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

View File

@@ -4,31 +4,31 @@ on:
branches: branches:
- main - main
pull_request: pull_request:
branches:
- main
jobs: jobs:
ci: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup bun - name: Setup Go
uses: oven-sh/setup-bun@v2
- name: Setup go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: "^1.23.2" go-version: "^1.23.2"
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install frontend dependencies - name: Install frontend dependencies
run: | run: |
cd frontend cd frontend
bun install bun install
- name: Set version
run: |
echo testing > internal/assets/version
- name: Build frontend - name: Build frontend
run: | run: |
cd frontend cd frontend

View File

@@ -1,298 +0,0 @@
name: Nightly Release
on:
workflow_dispatch:
jobs:
create-release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Delete old release
run: gh release delete --cleanup-tag --yes nightly || echo release not found
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OWNER: ${{ github.repository_owner }}
REPO: ${{ github.event.repository.name }}
- name: Create release
uses: softprops/action-gh-release@v2
with:
prerelease: true
tag_name: nightly
generate-metadata:
runs-on: ubuntu-latest
needs: create-release
outputs:
VERSION: ${{ steps.metadata.outputs.VERSION }}
COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: nightly
- name: Generate metadata
id: metadata
run: |
echo "VERSION=nightly" >> "$GITHUB_OUTPUT"
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT"
binary-build:
runs-on: ubuntu-latest
needs:
- create-release
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: nightly
- name: Install bun
uses: oven-sh/setup-bun@v2
- name: Install go
uses: actions/setup-go@v5
with:
go-version: "^1.23.2"
- name: Install frontend dependencies
run: |
cd frontend
bun install
- name: Install backend dependencies
run: |
go mod download
- name: Build frontend
run: |
cd frontend
bun run build
- name: Build
run: |
cp -r frontend/dist internal/assets/dist
go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tinyauth-amd64
path: tinyauth-amd64
binary-build-arm:
runs-on: ubuntu-24.04-arm
needs:
- create-release
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: nightly
- name: Install bun
uses: oven-sh/setup-bun@v2
- name: Install go
uses: actions/setup-go@v5
with:
go-version: "^1.23.2"
- name: Install frontend dependencies
run: |
cd frontend
bun install
- name: Install backend dependencies
run: |
go mod download
- name: Build frontend
run: |
cd frontend
bun run build
- name: Build
run: |
cp -r frontend/dist internal/assets/dist
go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tinyauth-arm64
path: tinyauth-arm64
image-build:
runs-on: ubuntu-latest
needs:
- create-release
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: nightly
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
id: build
with:
platforms: linux/amd64
labels: ${{ steps.meta.outputs.labels }}
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
build-args: |
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-linux-amd64
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
image-build-arm:
runs-on: ubuntu-24.04-arm
needs:
- create-release
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: nightly
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set version
run: |
echo nightly > internal/assets/version
- name: Build and push
uses: docker/build-push-action@v6
id: build
with:
platforms: linux/arm64
labels: ${{ steps.meta.outputs.labels }}
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
build-args: |
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-linux-arm64
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
image-merge:
runs-on: ubuntu-latest
needs:
- image-build
- image-build-arm
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
tags: |
type=raw,nightly
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/${{ github.repository_owner }}/tinyauth@sha256:%s ' *)
update-release:
runs-on: ubuntu-latest
needs:
- binary-build
- binary-build-arm
steps:
- uses: actions/download-artifact@v4
with:
pattern: tinyauth-*
path: binaries
merge-multiple: true
- name: Release
uses: softprops/action-gh-release@v2
with:
files: binaries/*
tag_name: nightly

View File

@@ -6,111 +6,8 @@ on:
- "v*" - "v*"
jobs: jobs:
generate-metadata: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
VERSION: ${{ steps.metadata.outputs.VERSION }}
COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: nightly
- name: Generate metadata
id: metadata
run: |
echo "VERSION=nightly" >> "$GITHUB_OUTPUT"
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT"
binary-build:
runs-on: ubuntu-latest
needs:
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install bun
uses: oven-sh/setup-bun@v2
- name: Install go
uses: actions/setup-go@v5
with:
go-version: "^1.23.2"
- name: Install frontend dependencies
run: |
cd frontend
bun install
- name: Install backend dependencies
run: |
go mod download
- name: Build frontend
run: |
cd frontend
bun run build
- name: Build
run: |
cp -r frontend/dist internal/assets/dist
go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tinyauth-amd64
path: tinyauth-amd64
binary-build-arm:
runs-on: ubuntu-24.04-arm
needs:
- generate-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install bun
uses: oven-sh/setup-bun@v2
- name: Install go
uses: actions/setup-go@v5
with:
go-version: "^1.23.2"
- name: Install frontend dependencies
run: |
cd frontend
bun install
- name: Install backend dependencies
run: |
go mod download
- name: Build frontend
run: |
cd frontend
bun run build
- name: Build
run: |
cp -r frontend/dist internal/assets/dist
go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${{ needs.generate-metadata.outputs.VERSION }} -X tinyauth/internal/constants.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X tinyauth/internal/constants.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tinyauth-arm64
path: tinyauth-arm64
image-build:
runs-on: ubuntu-latest
needs:
- generate-metadata
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -139,10 +36,6 @@ jobs:
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
tags: ghcr.io/${{ github.repository_owner }}/tinyauth tags: ghcr.io/${{ github.repository_owner }}/tinyauth
outputs: type=image,push-by-digest=true,name-canonical=true,push=true outputs: type=image,push-by-digest=true,name-canonical=true,push=true
build-args: |
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
- name: Export digest - name: Export digest
run: | run: |
@@ -158,10 +51,8 @@ jobs:
if-no-files-found: error if-no-files-found: error
retention-days: 1 retention-days: 1
image-build-arm: build-arm:
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
needs:
- generate-metadata
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -190,10 +81,6 @@ jobs:
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
tags: ghcr.io/${{ github.repository_owner }}/tinyauth tags: ghcr.io/${{ github.repository_owner }}/tinyauth
outputs: type=image,push-by-digest=true,name-canonical=true,push=true outputs: type=image,push-by-digest=true,name-canonical=true,push=true
build-args: |
VERSION=${{ needs.generate-metadata.outputs.VERSION }}
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
- name: Export digest - name: Export digest
run: | run: |
@@ -209,11 +96,11 @@ jobs:
if-no-files-found: error if-no-files-found: error
retention-days: 1 retention-days: 1
image-merge: merge:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- image-build - build
- image-build-arm - build-arm
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@@ -247,20 +134,3 @@ jobs:
run: | run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/${{ github.repository_owner }}/tinyauth@sha256:%s ' *) $(printf 'ghcr.io/${{ github.repository_owner }}/tinyauth@sha256:%s ' *)
update-release:
runs-on: ubuntu-latest
needs:
- binary-build
- binary-build-arm
steps:
- uses: actions/download-artifact@v4
with:
pattern: tinyauth-*
path: binaries
merge-multiple: true
- name: Release
uses: softprops/action-gh-release@v2
with:
files: binaries/*

View File

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

View File

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

48
.github/workflows/translations.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Publish translations
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Move translations
run: |
mkdir -p dist
mv frontend/src/lib/i18n/locales 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

5
.gitignore vendored
View File

@@ -24,7 +24,4 @@ secret_oauth.txt
.env .env
# tmp directory # tmp directory
tmp tmp
# version files
internal/assets/version

View File

@@ -1,6 +1,6 @@
# Contributing # Contributing
Contributing is relatively easy, you just need to follow the steps below and you will be up and running with a development server in less than five minutes. Contributing is relatively easy, you just need to follow the steps carefully and you will be up and running with a development server in less than 5 minutes.
## Requirements ## Requirements
@@ -20,7 +20,7 @@ cd tinyauth
## Install requirements ## Install requirements
Although you will not need the requirements in your machine since the development will happen in docker, I still recommend to install them because this way you will not have import errors. To install the go requirements run: Although you will not need the requirements in your machine since the development will happen in docker, I still recommend to install them because this way you will not have import errors, to install the go requirements, run:
```sh ```sh
go mod tidy go mod tidy
@@ -35,7 +35,7 @@ bun install
## Create your `.env` file ## Create your `.env` file
In order to configure the app you need to create an environment file, this can be done by copying the `.env.example` file to `.env` and modifying the environment variables to suit your needs. In order to configure the app you need to create an environment file, this can be done by copying the `.env.example` file to `.env` and modifying the environment variables inside to suit your needs.
## Developing ## Developing
@@ -46,14 +46,11 @@ I have designed the development workflow to be entirely in docker, this is becau
dev.example.com -> 127.0.0.1 dev.example.com -> 127.0.0.1
``` ```
> [!TIP] Then you can just make sure the domains are correct in the example docker compose file and run:
> You can use [sslip.io](https://sslip.io) as a domain if you don't have one to develop with.
Then you can just make sure the domains are correct in the development docker compose file and run:
```sh ```sh
docker compose -f docker-compose.dev.yml up --build docker compose -f docker-compose.dev.yml up --build
``` ```
> [!NOTE] > [!NOTE]
> I recommend copying the example `docker-compose.dev.yml` into a `docker-compose.test.yml` file, so as you don't accidentally commit any sensitive information. > I would recommend copying the example `docker-compose.dev.yml` into a `docker-compose.test.yml` file, so as you don't accidentally commit any sensitive information.

View File

@@ -1,15 +1,10 @@
# Arguments
ARG VERSION
ARG COMMIT_HASH
ARG BUILD_TIMESTAMP
# Site builder # Site builder
FROM oven/bun:1.2.12-alpine AS frontend-builder FROM oven/bun:1.1.45-alpine AS frontend-builder
WORKDIR /frontend WORKDIR /frontend
COPY ./frontend/package.json ./ COPY ./frontend/package.json ./
COPY ./frontend/bun.lock ./ COPY ./frontend/bun.lockb ./
RUN bun install RUN bun install
@@ -21,11 +16,12 @@ COPY ./frontend/tsconfig.json ./
COPY ./frontend/tsconfig.app.json ./ COPY ./frontend/tsconfig.app.json ./
COPY ./frontend/tsconfig.node.json ./ COPY ./frontend/tsconfig.node.json ./
COPY ./frontend/vite.config.ts ./ COPY ./frontend/vite.config.ts ./
COPY ./frontend/postcss.config.cjs ./
RUN bun run build RUN bun run build
# Builder # Builder
FROM golang:1.24-alpine3.21 AS builder FROM golang:1.23-alpine3.21 AS builder
WORKDIR /tinyauth WORKDIR /tinyauth
@@ -39,8 +35,8 @@ COPY ./cmd ./cmd
COPY ./internal ./internal COPY ./internal ./internal
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
RUN go build -ldflags "-s -w -X tinyauth/internal/constants.Version=${VERSION} -X tinyauth/internal/constants.CommitHash=${COMMIT_HASH} -X tinyauth/internal/constants.BuildTimestamp=${BUILD_TIMESTAMP}" RUN CGO_ENABLED=0 go build -ldflags "-s -w"
# Runner # Runner
FROM alpine:3.21 AS runner FROM alpine:3.21 AS runner

View File

@@ -1,4 +1,4 @@
FROM golang:1.24-alpine3.21 FROM golang:1.23-alpine3.21
WORKDIR /tinyauth WORKDIR /tinyauth
@@ -12,6 +12,9 @@ COPY ./internal ./internal
COPY ./main.go ./ COPY ./main.go ./
COPY ./air.toml ./ COPY ./air.toml ./
RUN mkdir -p ./internal/assets/dist && \
echo "app running" > ./internal/assets/dist/index.html
RUN go install github.com/air-verse/air@v1.61.7 RUN go install github.com/air-verse/air@v1.61.7
EXPOSE 3000 EXPOSE 3000

View File

@@ -1,5 +1,5 @@
<div align="center"> <div align="center">
<img alt="Tinyauth" title="Tinyauth" width="96" src="assets/logo-rounded.png"> <img alt="Tinyauth" title="Tinyauth" width="256" src="frontend/public/logo.png">
<h1>Tinyauth</h1> <h1>Tinyauth</h1>
<p>The easiest way to secure your apps with a login screen.</p> <p>The easiest way to secure your apps with a login screen.</p>
</div> </div>
@@ -7,6 +7,7 @@
<div align="center"> <div align="center">
<img alt="License" src="https://img.shields.io/github/license/steveiliop56/tinyauth"> <img alt="License" src="https://img.shields.io/github/license/steveiliop56/tinyauth">
<img alt="Release" src="https://img.shields.io/github/v/release/steveiliop56/tinyauth"> <img alt="Release" src="https://img.shields.io/github/v/release/steveiliop56/tinyauth">
<img alt="Commit activity" src="https://img.shields.io/github/commit-activity/w/steveiliop56/tinyauth">
<img alt="Issues" src="https://img.shields.io/github/issues/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"> <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> <a title="Crowdin" target="_blank" href="https://crowdin.com/project/tinyauth"><img src="https://badges.crowdin.net/tinyauth/localized.svg"></a>
@@ -14,27 +15,25 @@
<br /> <br />
Tinyauth is a simple authentication middleware that adds simple username/password login or OAuth with Google, Github and any generic provider to all of your docker apps. It is designed for traefik but it can be extended to work with other reverse proxies like caddy and nginx. Tinyauth is a simple authentication middleware that adds simple username/password login or OAuth with Google, Github and any generic OAuth provider to all of your docker apps. It is made for traefik but it can be extended to work with all reverse proxies like caddy and nginx.
![Screenshot](assets/screenshot.png)
> [!WARNING] > [!WARNING]
> Tinyauth is in active development and configuration may change often. Please make sure to carefully read the release notes before updating. > Tinyauth is in active development and configuration may change often. Please make sure to carefully read the release notes before updating.
> [!NOTE] > [!NOTE]
> Tinyauth is intended for homelab use only and it is not made for production use cases. If you are looking for something production ready please use [authentik](https://goauthentik.io) instead. > Tinyauth is intended for homelab use and it is not made for production use cases. If you are looking for something production ready please use [authentik](https://goauthentik.io).
## Discord ## Discord
I just made a Discord server for tinyauth! It is not only for tinyauth but general self-hosting and homelabbing. [See you there!](https://discord.gg/eHzVaCzRRd). I just made a Discord server for Tinyauth! It is not only for Tinyauth but general self-hosting because I just like chatting with people! The link is [here](https://discord.gg/eHzVaCzRRd), see you there!
## Getting Started ## Getting Started
You can easily get started with tinyauth by following the guide in the [documentation](https://tinyauth.app/docs/getting-started.html). There is also an available [docker compose file](./docker-compose.example.yml) that has traefik, whoami and tinyauth to demonstrate its capabilities. You can easily get started with tinyauth by following the guide on the [documentation](https://tinyauth.app/docs/getting-started.html). There is also an available [docker compose file](./docker-compose.example.yml) that has traefik, nginx and tinyauth to demonstrate its capabilities.
## Documentation ## Documentation
You can find documentation and guides on all of the available configuration of tinyauth in the [website](https://tinyauth.app). You can find documentation and guides on all available configuration of tinyauth [here](https://tinyauth.app).
## Contributing ## Contributing
@@ -52,7 +51,9 @@ Tinyauth is licensed under the GNU General Public License v3.0. TL;DR — You ma
Thanks a lot to the following people for providing me with more coffee: Thanks a lot to the following people for providing me with more coffee:
<!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https:&#x2F;&#x2F;github.com&#x2F;erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a>&nbsp;&nbsp;<a href="https://github.com/nicotsx"><img src="https:&#x2F;&#x2F;github.com&#x2F;nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a>&nbsp;&nbsp;<a href="https://github.com/SimpleHomelab"><img src="https:&#x2F;&#x2F;github.com&#x2F;SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a>&nbsp;&nbsp;<a href="https://github.com/jmadden91"><img src="https:&#x2F;&#x2F;github.com&#x2F;jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a>&nbsp;&nbsp;<a href="https://github.com/tribor"><img src="https:&#x2F;&#x2F;github.com&#x2F;tribor.png" width="64px" alt="User avatar: tribor" /></a>&nbsp;&nbsp;<a href="https://github.com/eliasbenb"><img src="https:&#x2F;&#x2F;github.com&#x2F;eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a>&nbsp;&nbsp;<!-- sponsors --> | <img height="64" src="https://avatars.githubusercontent.com/u/47644445?v=4" alt="Nicolas"> | <img height="64" src="https://avatars.githubusercontent.com/u/4255748?v=4" alt="Erwin"> |
| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
| <div align="center"><a href="https://github.com/nicotsx">Nicolas</a></div> | <div align="center"><a href="https://github.com/erwinkramer">Erwin</a></div> |
## Acknowledgements ## Acknowledgements
@@ -60,9 +61,3 @@ Credits for the logo of this app go to:
- **Freepik** for providing the police hat and badge. - **Freepik** for providing the police hat and badge.
- **Renee French** for the original gopher logo. - **Renee French** for the original gopher logo.
- **Coderabbit AI** for providing free AI code reviews.
- **Kurt Cotoaga** for providing the bacgkround image of the app.
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=steveiliop56/tinyauth&type=Date)](https://www.star-history.com/#steveiliop56/tinyauth&Date)

View File

@@ -2,7 +2,6 @@ root = "/tinyauth"
tmp_dir = "tmp" tmp_dir = "tmp"
[build] [build]
pre_cmd = ["mkdir -p internal/assets/dist", "echo 'backend running' > internal/assets/dist/index.html"]
cmd = "go build -o ./tmp/tinyauth ." cmd = "go build -o ./tmp/tinyauth ."
bin = "tmp/tinyauth" bin = "tmp/tinyauth"
include_ext = ["go"] include_ext = ["go"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

View File

@@ -2,14 +2,15 @@ package cmd
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"strings" "strings"
"time" "time"
totpCmd "tinyauth/cmd/totp" totpCmd "tinyauth/cmd/totp"
userCmd "tinyauth/cmd/user" userCmd "tinyauth/cmd/user"
"tinyauth/internal/api" "tinyauth/internal/api"
"tinyauth/internal/assets"
"tinyauth/internal/auth" "tinyauth/internal/auth"
"tinyauth/internal/constants"
"tinyauth/internal/docker" "tinyauth/internal/docker"
"tinyauth/internal/handlers" "tinyauth/internal/handlers"
"tinyauth/internal/hooks" "tinyauth/internal/hooks"
@@ -42,6 +43,7 @@ var rootCmd = &cobra.Command{
config.GithubClientSecret = utils.GetSecret(config.GithubClientSecret, config.GithubClientSecretFile) config.GithubClientSecret = utils.GetSecret(config.GithubClientSecret, config.GithubClientSecretFile)
config.GoogleClientSecret = utils.GetSecret(config.GoogleClientSecret, config.GoogleClientSecretFile) config.GoogleClientSecret = utils.GetSecret(config.GoogleClientSecret, config.GoogleClientSecretFile)
config.GenericClientSecret = utils.GetSecret(config.GenericClientSecret, config.GenericClientSecretFile) config.GenericClientSecret = utils.GetSecret(config.GenericClientSecret, config.GenericClientSecretFile)
config.TailscaleClientSecret = utils.GetSecret(config.TailscaleClientSecret, config.TailscaleClientSecretFile)
// Validate config // Validate config
validator := validator.New() validator := validator.New()
@@ -50,7 +52,7 @@ var rootCmd = &cobra.Command{
// Logger // Logger
log.Logger = log.Level(zerolog.Level(config.LogLevel)) log.Logger = log.Level(zerolog.Level(config.LogLevel))
log.Info().Str("version", strings.TrimSpace(constants.Version)).Msg("Starting tinyauth") log.Info().Str("version", assets.Version).Msg("Starting tinyauth")
// Users // Users
log.Info().Msg("Parsing users") log.Info().Msg("Parsing users")
@@ -61,6 +63,13 @@ var rootCmd = &cobra.Command{
HandleError(errors.New("no users or OAuth configured"), "No users or OAuth configured") HandleError(errors.New("no users or OAuth configured"), "No users or OAuth configured")
} }
// Create oauth whitelist
oauthWhitelist := utils.Filter(strings.Split(config.OAuthWhitelist, ","), func(val string) bool {
return val != ""
})
log.Debug().Msg("Parsed OAuth whitelist")
// Get domain // Get domain
log.Debug().Msg("Getting domain") log.Debug().Msg("Getting domain")
domain, err := utils.GetUpperDomain(config.AppURL) domain, err := utils.GetUpperDomain(config.AppURL)
@@ -69,30 +78,29 @@ var rootCmd = &cobra.Command{
// Create OAuth config // Create OAuth config
oauthConfig := types.OAuthConfig{ oauthConfig := types.OAuthConfig{
GithubClientId: config.GithubClientId, GithubClientId: config.GithubClientId,
GithubClientSecret: config.GithubClientSecret, GithubClientSecret: config.GithubClientSecret,
GoogleClientId: config.GoogleClientId, GoogleClientId: config.GoogleClientId,
GoogleClientSecret: config.GoogleClientSecret, GoogleClientSecret: config.GoogleClientSecret,
GenericClientId: config.GenericClientId, TailscaleClientId: config.TailscaleClientId,
GenericClientSecret: config.GenericClientSecret, TailscaleClientSecret: config.TailscaleClientSecret,
GenericScopes: strings.Split(config.GenericScopes, ","), GenericClientId: config.GenericClientId,
GenericAuthURL: config.GenericAuthURL, GenericClientSecret: config.GenericClientSecret,
GenericTokenURL: config.GenericTokenURL, GenericScopes: strings.Split(config.GenericScopes, ","),
GenericUserURL: config.GenericUserURL, GenericAuthURL: config.GenericAuthURL,
AppURL: config.AppURL, GenericTokenURL: config.GenericTokenURL,
GenericUserURL: config.GenericUserURL,
AppURL: config.AppURL,
} }
// Create handlers config // Create handlers config
handlersConfig := types.HandlersConfig{ serverConfig := types.HandlersConfig{
AppURL: config.AppURL, AppURL: config.AppURL,
DisableContinue: config.DisableContinue, Domain: fmt.Sprintf(".%s", domain),
Title: config.Title, CookieSecure: config.CookieSecure,
GenericName: config.GenericName, DisableContinue: config.DisableContinue,
CookieSecure: config.CookieSecure, Title: config.Title,
Domain: domain, GenericName: config.GenericName,
ForgotPasswordMessage: config.FogotPasswordMessage,
BackgroundImage: config.BackgroundImage,
OAuthAutoRedirect: config.OAuthAutoRedirect,
} }
// Create api config // Create api config
@@ -101,23 +109,6 @@ var rootCmd = &cobra.Command{
Address: config.Address, Address: config.Address,
} }
// Create auth config
authConfig := types.AuthConfig{
Users: users,
OauthWhitelist: config.OAuthWhitelist,
Secret: config.Secret,
CookieSecure: config.CookieSecure,
SessionExpiry: config.SessionExpiry,
Domain: domain,
LoginTimeout: config.LoginTimeout,
LoginMaxRetries: config.LoginMaxRetries,
}
// Create hooks config
hooksConfig := types.HooksConfig{
Domain: domain,
}
// Create docker service // Create docker service
docker := docker.NewDocker() docker := docker.NewDocker()
@@ -126,7 +117,14 @@ var rootCmd = &cobra.Command{
HandleError(err, "Failed to initialize docker") HandleError(err, "Failed to initialize docker")
// Create auth service // Create auth service
auth := auth.NewAuth(authConfig, docker) auth := auth.NewAuth(types.AuthConfig{
Domain: domain,
Secret: config.Secret,
SessionExpiry: config.SessionExpiry,
CookieSecure: config.CookieSecure,
Users: users,
OAuthWhitelist: oauthWhitelist,
}, docker)
// Create OAuth providers service // Create OAuth providers service
providers := providers.NewProviders(oauthConfig) providers := providers.NewProviders(oauthConfig)
@@ -135,10 +133,10 @@ var rootCmd = &cobra.Command{
providers.Init() providers.Init()
// Create hooks service // Create hooks service
hooks := hooks.NewHooks(hooksConfig, auth, providers) hooks := hooks.NewHooks(auth, providers)
// Create handlers // Create handlers
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker) handlers := handlers.NewHandlers(serverConfig, auth, hooks, providers, docker)
// Create API // Create API
api := api.NewAPI(apiConfig, handlers) api := api.NewAPI(apiConfig, handlers)
@@ -189,6 +187,9 @@ func init() {
rootCmd.Flags().String("google-client-id", "", "Google OAuth client ID.") rootCmd.Flags().String("google-client-id", "", "Google OAuth client ID.")
rootCmd.Flags().String("google-client-secret", "", "Google OAuth client secret.") rootCmd.Flags().String("google-client-secret", "", "Google OAuth client secret.")
rootCmd.Flags().String("google-client-secret-file", "", "Google OAuth client secret file.") rootCmd.Flags().String("google-client-secret-file", "", "Google OAuth client secret file.")
rootCmd.Flags().String("tailscale-client-id", "", "Tailscale OAuth client ID.")
rootCmd.Flags().String("tailscale-client-secret", "", "Tailscale OAuth client secret.")
rootCmd.Flags().String("tailscale-client-secret-file", "", "Tailscale OAuth client secret file.")
rootCmd.Flags().String("generic-client-id", "", "Generic OAuth client ID.") rootCmd.Flags().String("generic-client-id", "", "Generic OAuth client ID.")
rootCmd.Flags().String("generic-client-secret", "", "Generic OAuth client secret.") rootCmd.Flags().String("generic-client-secret", "", "Generic OAuth client secret.")
rootCmd.Flags().String("generic-client-secret-file", "", "Generic OAuth client secret file.") rootCmd.Flags().String("generic-client-secret-file", "", "Generic OAuth client secret file.")
@@ -199,14 +200,9 @@ func init() {
rootCmd.Flags().String("generic-name", "Generic", "Generic OAuth provider name.") rootCmd.Flags().String("generic-name", "Generic", "Generic OAuth provider name.")
rootCmd.Flags().Bool("disable-continue", false, "Disable continue screen and redirect to app directly.") rootCmd.Flags().Bool("disable-continue", false, "Disable continue screen and redirect to app directly.")
rootCmd.Flags().String("oauth-whitelist", "", "Comma separated list of email addresses to whitelist when using OAuth.") rootCmd.Flags().String("oauth-whitelist", "", "Comma separated list of email addresses to whitelist when using OAuth.")
rootCmd.Flags().String("oauth-auto-redirect", "none", "Auto redirect to the specified OAuth provider if configured. (available providers: github, google, generic)")
rootCmd.Flags().Int("session-expiry", 86400, "Session (cookie) expiration time in seconds.") rootCmd.Flags().Int("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().Int("log-level", 1, "Log level.")
rootCmd.Flags().String("app-title", "Tinyauth", "Title of the app.") rootCmd.Flags().String("app-title", "Tinyauth", "Title of the app.")
rootCmd.Flags().String("forgot-password-message", "You can reset your password by changing the `USERS` environment variable.", "Message to show on the forgot password page.")
rootCmd.Flags().String("background-image", "/background.jpg", "Background image URL for the login page.")
// Bind flags to environment // Bind flags to environment
viper.BindEnv("port", "PORT") viper.BindEnv("port", "PORT")
@@ -223,6 +219,9 @@ func init() {
viper.BindEnv("google-client-id", "GOOGLE_CLIENT_ID") viper.BindEnv("google-client-id", "GOOGLE_CLIENT_ID")
viper.BindEnv("google-client-secret", "GOOGLE_CLIENT_SECRET") viper.BindEnv("google-client-secret", "GOOGLE_CLIENT_SECRET")
viper.BindEnv("google-client-secret-file", "GOOGLE_CLIENT_SECRET_FILE") viper.BindEnv("google-client-secret-file", "GOOGLE_CLIENT_SECRET_FILE")
viper.BindEnv("tailscale-client-id", "TAILSCALE_CLIENT_ID")
viper.BindEnv("tailscale-client-secret", "TAILSCALE_CLIENT_SECRET")
viper.BindEnv("tailscale-client-secret-file", "TAILSCALE_CLIENT_SECRET_FILE")
viper.BindEnv("generic-client-id", "GENERIC_CLIENT_ID") viper.BindEnv("generic-client-id", "GENERIC_CLIENT_ID")
viper.BindEnv("generic-client-secret", "GENERIC_CLIENT_SECRET") viper.BindEnv("generic-client-secret", "GENERIC_CLIENT_SECRET")
viper.BindEnv("generic-client-secret-file", "GENERIC_CLIENT_SECRET_FILE") viper.BindEnv("generic-client-secret-file", "GENERIC_CLIENT_SECRET_FILE")
@@ -233,14 +232,9 @@ func init() {
viper.BindEnv("generic-name", "GENERIC_NAME") viper.BindEnv("generic-name", "GENERIC_NAME")
viper.BindEnv("disable-continue", "DISABLE_CONTINUE") viper.BindEnv("disable-continue", "DISABLE_CONTINUE")
viper.BindEnv("oauth-whitelist", "OAUTH_WHITELIST") viper.BindEnv("oauth-whitelist", "OAUTH_WHITELIST")
viper.BindEnv("oauth-auto-redirect", "OAUTH_AUTO_REDIRECT")
viper.BindEnv("session-expiry", "SESSION_EXPIRY") viper.BindEnv("session-expiry", "SESSION_EXPIRY")
viper.BindEnv("log-level", "LOG_LEVEL") viper.BindEnv("log-level", "LOG_LEVEL")
viper.BindEnv("app-title", "APP_TITLE") viper.BindEnv("app-title", "APP_TITLE")
viper.BindEnv("login-timeout", "LOGIN_TIMEOUT")
viper.BindEnv("login-max-retries", "LOGIN_MAX_RETRIES")
viper.BindEnv("forgot-password-message", "FORGOT_PASSWORD_MESSAGE")
viper.BindEnv("background-image", "BACKGROUND_IMAGE")
// Bind flags to viper // Bind flags to viper
viper.BindPFlags(rootCmd.Flags()) viper.BindPFlags(rootCmd.Flags())

View File

@@ -1,24 +0,0 @@
package cmd
import (
"fmt"
"tinyauth/internal/constants"
"github.com/spf13/cobra"
)
// Create the version command
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of Tinyauth",
Long: `All software has versions. This is Tinyauth's`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Version: %s\n", constants.Version)
fmt.Printf("Commit Hash: %s\n", constants.CommitHash)
fmt.Printf("Build Timestamp: %s\n", constants.BuildTimestamp)
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}

View File

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

View File

@@ -3,7 +3,7 @@ FROM oven/bun:1.1.45-alpine
WORKDIR /frontend WORKDIR /frontend
COPY ./frontend/package.json ./ COPY ./frontend/package.json ./
COPY ./frontend/bun.lock ./ COPY ./frontend/bun.lockb ./
RUN bun install RUN bun install
@@ -16,6 +16,7 @@ COPY ./frontend/tsconfig.json ./
COPY ./frontend/tsconfig.app.json ./ COPY ./frontend/tsconfig.app.json ./
COPY ./frontend/tsconfig.node.json ./ COPY ./frontend/tsconfig.node.json ./
COPY ./frontend/vite.config.ts ./ COPY ./frontend/vite.config.ts ./
COPY ./frontend/postcss.config.cjs ./
EXPOSE 5173 EXPOSE 5173

File diff suppressed because it is too large Load Diff

BIN
frontend/bun.lockb Executable file

Binary file not shown.

View File

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

View File

@@ -3,7 +3,6 @@ import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks"; import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh"; import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint"; import tseslint from "typescript-eslint";
import pluginQuery from "@tanstack/eslint-plugin-query";
export default tseslint.config( export default tseslint.config(
{ ignores: ["dist"] }, { ignores: ["dist"] },
@@ -17,7 +16,6 @@ export default tseslint.config(
plugins: { plugins: {
"react-hooks": reactHooks, "react-hooks": reactHooks,
"react-refresh": reactRefresh, "react-refresh": reactRefresh,
"@tanstack/query": pluginQuery,
}, },
rules: { rules: {
...reactHooks.configs.recommended.rules, ...reactHooks.configs.recommended.rules,
@@ -25,7 +23,6 @@ export default tseslint.config(
"warn", "warn",
{ allowConstantExport: true }, { allowConstantExport: true },
], ],
"@tanstack/query/exhaustive-deps": "error",
}, },
}, },
); );

View File

@@ -3,15 +3,13 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="Tinyauth" /> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="manifest" href="/site.webmanifest" /> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/frontend.webmanifest" />
<title>Tinyauth</title> <title>Tinyauth</title>
</head> </head>
<body class="dark"> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>
</body> </body>

2249
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{ {
"name": "tinyauth-shadcn", "name": "frontend",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
@@ -10,49 +10,38 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.0.1", "@mantine/core": "^7.16.0",
"@radix-ui/react-label": "^2.1.6", "@mantine/form": "^7.16.0",
"@radix-ui/react-select": "^2.2.2", "@mantine/hooks": "^7.16.0",
"@radix-ui/react-separator": "^1.1.4", "@mantine/notifications": "^7.16.0",
"@radix-ui/react-slot": "^1.2.2", "@tanstack/react-query": "4",
"@tailwindcss/vite": "^4.1.4", "axios": "^1.7.9",
"@tanstack/react-query": "^5.75.6", "i18next": "^24.2.3",
"axios": "^1.9.0", "i18next-browser-languagedetector": "^8.0.4",
"class-variance-authority": "^0.7.1", "i18next-chained-backend": "^4.6.2",
"clsx": "^2.1.1", "i18next-http-backend": "^3.0.2",
"dompurify": "^3.2.5",
"i18next": "^25.0.2",
"i18next-browser-languagedetector": "^8.0.5",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"input-otp": "^1.4.2", "react": "^18.3.1",
"lucide-react": "^0.503.0", "react-dom": "^18.3.1",
"next-themes": "^0.4.6", "react-i18next": "^15.4.1",
"react": "^19.0.0", "react-router": "^7.1.3",
"react-dom": "^19.0.0", "zod": "^3.24.1"
"react-hook-form": "^7.56.3",
"react-i18next": "^15.5.1",
"react-markdown": "^10.1.0",
"react-router": "^7.5.3",
"sonner": "^2.0.3",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.4",
"zod": "^3.24.4"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.22.0", "@eslint/js": "^9.17.0",
"@tanstack/eslint-plugin-query": "^5.74.7", "@types/react": "^18.3.18",
"@types/node": "^22.15.3", "@types/react-dom": "^18.3.5",
"@types/react": "^19.0.10", "@vitejs/plugin-react-swc": "^3.5.0",
"@types/react-dom": "^19.0.4", "eslint": "^9.17.0",
"@vitejs/plugin-react": "^4.3.4", "eslint-plugin-react-hooks": "^5.0.0",
"eslint": "^9.22.0", "eslint-plugin-react-refresh": "^0.4.16",
"eslint-plugin-react-hooks": "^5.2.0", "globals": "^15.14.0",
"eslint-plugin-react-refresh": "^0.4.19", "postcss": "^8.5.1",
"globals": "^16.0.0", "postcss-preset-mantine": "^1.17.0",
"prettier": "3.5.3", "postcss-simple-vars": "^7.0.1",
"tw-animate-css": "^1.2.8", "prettier": "3.4.2",
"typescript": "~5.7.2", "typescript": "~5.6.2",
"typescript-eslint": "^8.26.1", "typescript-eslint": "^8.18.2",
"vite": "^6.3.1" "vite": "^6.0.5"
} }
} }

View File

@@ -0,0 +1,14 @@
module.exports = {
plugins: {
"postcss-preset-mantine": {},
"postcss-simple-vars": {
variables: {
"mantine-breakpoint-xs": "36em",
"mantine-breakpoint-sm": "48em",
"mantine-breakpoint-md": "62em",
"mantine-breakpoint-lg": "75em",
"mantine-breakpoint-xl": "88em",
},
},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 588 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -1,12 +1,17 @@
import { Navigate } from "react-router"; import { Navigate } from "react-router";
import { useUserContext } from "./context/user-context"; import { useUserContext } from "./context/user-context";
import { LogoutPage } from "./pages/logout-page";
export const App = () => { export const App = () => {
const queryString = window.location.search;
const params = new URLSearchParams(queryString);
const redirectUri = params.get("redirect_uri");
const { isLoggedIn } = useUserContext(); const { isLoggedIn } = useUserContext();
if (isLoggedIn) { if (!isLoggedIn) {
return <Navigate to="/logout" />; return <Navigate to={`/login?redirect_uri=${redirectUri}`} />;
} }
return <Navigate to="/login" />; return <LogoutPage />;
}; };

View File

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

View File

@@ -0,0 +1,48 @@
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;
onSubmit: (values: LoginFormValues) => void;
}
export const LoginForm = (props: LoginFormProps) => {
const { isLoading, onSubmit } = props;
const { t } = useTranslation();
const form = useForm({
mode: "uncontrolled",
initialValues: {
username: "",
password: "",
},
validate: zodResolver(loginSchema),
});
return (
<form onSubmit={form.onSubmit(onSubmit)}>
<TextInput
label={t("loginUsername")}
placeholder="user@example.com"
required
disabled={isLoading}
key={form.key("username")}
{...form.getInputProps("username")}
/>
<PasswordInput
label={t("loginPassword")}
placeholder="password"
required
mt="md"
disabled={isLoading}
key={form.key("password")}
{...form.getInputProps("password")}
/>
<Button fullWidth mt="xl" type="submit" loading={isLoading}>
{t("loginSubmit")}
</Button>
</form>
);
};

View File

@@ -0,0 +1,72 @@
import { Grid, Button } from "@mantine/core";
import { GithubIcon } from "../../icons/github";
import { GoogleIcon } from "../../icons/google";
import { OAuthIcon } from "../../icons/oauth";
import { TailscaleIcon } from "../../icons/tailscale";
interface OAuthButtonsProps {
oauthProviders: string[];
isLoading: boolean;
mutate: (provider: string) => void;
genericName: string;
}
export const OAuthButtons = (props: OAuthButtonsProps) => {
const { oauthProviders, isLoading, genericName, mutate } = props;
return (
<Grid mb="md" mt="md" align="center" justify="center">
{oauthProviders.includes("google") && (
<Grid.Col span="content">
<Button
radius="xl"
leftSection={<GoogleIcon style={{ width: 14, height: 14 }} />}
variant="default"
onClick={() => mutate("google")}
loading={isLoading}
>
Google
</Button>
</Grid.Col>
)}
{oauthProviders.includes("github") && (
<Grid.Col span="content">
<Button
radius="xl"
leftSection={<GithubIcon style={{ width: 14, height: 14 }} />}
variant="default"
onClick={() => mutate("github")}
loading={isLoading}
>
Github
</Button>
</Grid.Col>
)}
{oauthProviders.includes("tailscale") && (
<Grid.Col span="content">
<Button
radius="xl"
leftSection={<TailscaleIcon style={{ width: 14, height: 14 }} />}
variant="default"
onClick={() => mutate("tailscale")}
loading={isLoading}
>
Tailscale
</Button>
</Grid.Col>
)}
{oauthProviders.includes("generic") && (
<Grid.Col span="content">
<Button
radius="xl"
leftSection={<OAuthIcon style={{ width: 14, height: 14 }} />}
variant="default"
onClick={() => mutate("generic")}
loading={isLoading}
>
{genericName}
</Button>
</Grid.Col>
)}
</Grid>
);
};

View File

@@ -1,54 +1,40 @@
import { Form, FormControl, FormField, FormItem } from "../ui/form"; import { Button, PinInput } from "@mantine/core";
import { import { useForm, zodResolver } from "@mantine/form";
InputOTP, import { z } from "zod";
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "../ui/input-otp";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { totpSchema, TotpSchema } from "@/schemas/totp-schema";
interface Props { const schema = z.object({
formId: string; code: z.string(),
onSubmit: (code: TotpSchema) => void; });
loading?: boolean;
type FormValues = z.infer<typeof schema>;
interface TotpFormProps {
onSubmit: (values: FormValues) => void;
isLoading: boolean;
} }
export const TotpForm = (props: Props) => { export const TotpForm = (props: TotpFormProps) => {
const { formId, onSubmit, loading } = props; const { onSubmit, isLoading } = props;
const form = useForm<TotpSchema>({ const form = useForm({
resolver: zodResolver(totpSchema), mode: "uncontrolled",
initialValues: {
code: "",
},
validate: zodResolver(schema),
}); });
return ( return (
<Form {...form}> <form onSubmit={form.onSubmit(onSubmit)}>
<form id={formId} onSubmit={form.handleSubmit(onSubmit)}> <PinInput
<FormField length={6}
control={form.control} type={"number"}
name="code" placeholder=""
render={({ field }) => ( {...form.getInputProps("code")}
<FormItem> />
<FormControl> <Button type="submit" mt="xl" loading={isLoading} fullWidth>
<InputOTP maxLength={6} disabled={loading} {...field}> Verify
<InputOTPGroup> </Button>
<InputOTPSlot index={0} /> </form>
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</FormControl>
</FormItem>
)}
/>
</form>
</Form>
); );
}; };

View File

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

View File

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

View File

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

View File

@@ -1,20 +0,0 @@
import { useAppContext } from "@/context/app-context";
import { LanguageSelector } from "../language/language";
export const Layout = ({ children }: { children: React.ReactNode }) => {
const { backgroundImage } = useAppContext();
return (
<div
className="relative flex flex-col justify-center items-center min-h-svh"
style={{
backgroundImage: `url(${backgroundImage})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
>
<LanguageSelector />
{children}
</div>
);
};

View 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>
</>
);
};

View File

@@ -1,77 +0,0 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { Loader2 } from "lucide-react";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
warning:
"bg-amber-500 text-white shadow-xs hover:bg-amber-400 focus-visible:ring-amber-200/20 dark:focus-visible:ring-amber-400/40 dark:bg-amber-600",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
function Button({
className,
variant,
size,
asChild = false,
loading = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
loading?: boolean;
}) {
const Comp = asChild ? Slot : "button";
if (loading) {
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
disabled
{...props}
>
<Loader2 className="animate-spin" />
</Comp>
);
}
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
export { Button, buttonVariants };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,183 +0,0 @@
import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />;
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default";
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-card dark:hover:bg-card/90 flex w-fit items-center justify-between gap-2 rounded-md border bg-card hover:bg-card/90 px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
}
function SelectContent({
className,
children,
position = "popper",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
);
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
{...props}
/>
);
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
);
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props}
/>
);
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
);
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
);
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
import type { SVGProps } from "react";
export function GoogleIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={48}
height={48}
viewBox="0 0 48 48"
{...props}
>
<path
fill="#ffc107"
d="M43.611 20.083H42V20H24v8h11.303c-1.649 4.657-6.08 8-11.303 8c-6.627 0-12-5.373-12-12s5.373-12 12-12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4C12.955 4 4 12.955 4 24s8.955 20 20 20s20-8.955 20-20c0-1.341-.138-2.65-.389-3.917"
></path>
<path
fill="#ff3d00"
d="m6.306 14.691l6.571 4.819C14.655 15.108 18.961 12 24 12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4C16.318 4 9.656 8.337 6.306 14.691"
></path>
<path
fill="#4caf50"
d="M24 44c5.166 0 9.86-1.977 13.409-5.192l-6.19-5.238A11.9 11.9 0 0 1 24 36c-5.202 0-9.619-3.317-11.283-7.946l-6.522 5.025C9.505 39.556 16.227 44 24 44"
></path>
<path
fill="#1976d2"
d="M43.611 20.083H42V20H24v8h11.303a12.04 12.04 0 0 1-4.087 5.571l.003-.002l6.19 5.238C36.971 39.205 44 34 44 24c0-1.341-.138-2.65-.389-3.917"
></path>
</svg>
);
}

View File

@@ -1,6 +1,6 @@
import type { SVGProps } from "react"; import type { SVGProps } from "react";
export function GenericIcon(props: SVGProps<SVGSVGElement>) { export function OAuthIcon(props: SVGProps<SVGSVGElement>) {
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@@ -0,0 +1,55 @@
import type { SVGProps } from "react";
export function TailscaleIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
width={24}
height={24}
{...props}
>
<style>{".st0{opacity:0.2;fill:#CCCAC9;}.st1{fill:#FFFFFF;}"}</style>
<g>
<g>
<path
className="st0"
d="M65.6,127.7c35.3,0,63.9-28.6,63.9-63.9S100.9,0,65.6,0S1.8,28.6,1.8,63.9S30.4,127.7,65.6,127.7z"
/>
<path
className="st1"
d="M65.6,318.1c35.3,0,63.9-28.6,63.9-63.9s-28.6-63.9-63.9-63.9S1.8,219,1.8,254.2S30.4,318.1,65.6,318.1z"
/>
<path
className="st0"
d="M65.6,512c35.3,0,63.9-28.6,63.9-63.9s-28.6-63.9-63.9-63.9S1.8,412.9,1.8,448.1S30.4,512,65.6,512z"
/>
<path
className="st1"
d="M257.2,318.1c35.3,0,63.9-28.6,63.9-63.9s-28.6-63.9-63.9-63.9s-63.9,28.6-63.9,63.9S221.9,318.1,257.2,318.1z"
/>
<path
className="st1"
d="M257.2,512c35.3,0,63.9-28.6,63.9-63.9s-28.6-63.9-63.9-63.9s-63.9,28.6-63.9,63.9S221.9,512,257.2,512z"
/>
<path
className="st0"
d="M257.2,127.7c35.3,0,63.9-28.6,63.9-63.9S292.5,0,257.2,0s-63.9,28.6-63.9,63.9S221.9,127.7,257.2,127.7z"
/>
<path
className="st0"
d="M446.4,127.7c35.3,0,63.9-28.6,63.9-63.9S481.6,0,446.4,0c-35.3,0-63.9,28.6-63.9,63.9S411.1,127.7,446.4,127.7z"
/>
<path
className="st1"
d="M446.4,318.1c35.3,0,63.9-28.6,63.9-63.9s-28.6-63.9-63.9-63.9s-63.9,28.6-63.9,63.9S411.1,318.1,446.4,318.1z"
/>
<path
className="st0"
d="M446.4,512c35.3,0,63.9-28.6,63.9-63.9s-28.6-63.9-63.9-63.9s-63.9,28.6-63.9,63.9S411.1,512,446.4,512z"
/>
</g>
</g>
</svg>
);
}

View File

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

View File

@@ -1,15 +0,0 @@
import { useCallback, useEffect, useRef } from "react";
export function useIsMounted(): () => boolean {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return useCallback(() => isMounted.current, []);
}

View File

@@ -1,16 +1,14 @@
import i18n from "i18next"; import i18n from "i18next";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector"; import LanguageDetector from "i18next-browser-languagedetector";
import ChainedBackend from "i18next-chained-backend";
import resourcesToBackend from "i18next-resources-to-backend"; import resourcesToBackend from "i18next-resources-to-backend";
import HttpBackend from "i18next-http-backend";
i18n i18n
.use(ChainedBackend)
.use(LanguageDetector) .use(LanguageDetector)
.use(initReactI18next) .use(initReactI18next)
.use(
resourcesToBackend(
(language: string) => import(`./locales/${language}.json`),
),
)
.init({ .init({
fallbackLng: "en", fallbackLng: "en",
debug: import.meta.env.MODE === "development", debug: import.meta.env.MODE === "development",
@@ -20,6 +18,20 @@ i18n
}, },
load: "currentOnly", load: "currentOnly",
backend: {
backends: [
HttpBackend,
resourcesToBackend(
(language: string) => import(`./locales/${language}.json`),
),
],
backendOptions: [
{
loadPath: "https://cdn.tinyauth.app/i18n/{{lng}}.json",
},
],
},
}); });
export default i18n; export default i18n;

View File

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

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -1,51 +1,45 @@
{ {
"loginTitle": "مرحبا بعودتك، قم بتسجيل الدخول باستخدام", "loginTitle": "Welcome back, login with",
"loginDivider": "أو المتابعة بكلمة المرور", "loginDivider": "Or continue with password",
"loginUsername": "اسم المستخدم", "loginUsername": "Username",
"loginPassword": "كلمة المرور", "loginPassword": "Password",
"loginSubmit": "تسجيل الدخول", "loginSubmit": "Login",
"loginFailTitle": "فشل تسجيل الدخول", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "الرجاء التحقق من اسم المستخدم وكلمة المرور", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "فشلت في تسجيل الدخول عدة مرات، الرجاء المحاولة مرة أخرى لاحقا", "loginSuccessTitle": "Logged in",
"loginSuccessTitle": "تم تسجيل الدخول", "loginSuccessSubtitle": "Welcome back!",
"loginSuccessSubtitle": "مرحبا بعودتك!", "loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "خطأ داخلي", "loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthFailSubtitle": "فشل في الحصول على رابط OAuth", "loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessTitle": "إعادة توجيه", "loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
"loginOauthSuccessSubtitle": "إعادة توجيه إلى مزود OAuth الخاص بك", "continueRedirectingTitle": "Redirecting...",
"continueRedirectingTitle": "إعادة توجيه...", "continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectingSubtitle": "يجب إعادة توجيهك إلى التطبيق قريبا", "continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectTitle": "إعادة توجيه غير صالحة", "continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInvalidRedirectSubtitle": "رابط إعادة التوجيه غير صالح", "continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectTitle": "إعادة توجيه غير آمنة", "continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"continueInsecureRedirectSubtitle": "أنت تحاول إعادة التوجيه من <Code>https</Code> إلى <Code>http</Code>، هل أنت متأكد أنك تريد المتابعة؟", "continueTitle": "Continue",
"continueTitle": "متابعة", "continueSubtitle": "Click the button to continue to your app.",
"continueSubtitle": "انقر الزر للمتابعة إلى التطبيق الخاص بك.", "internalErrorTitle": "Internal Server Error",
"internalErrorTitle": "خطأ داخلي في الخادم", "internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorSubtitle": "حدث خطأ على الخادم ولا يمكن حاليا تلبية طلبك.", "internalErrorButton": "Try again",
"internalErrorButton": "حاول مجددا", "logoutFailTitle": "Failed to log out",
"logoutFailTitle": "فشل تسجيل الخروج", "logoutFailSubtitle": "Please try again",
"logoutFailSubtitle": "يرجى إعادة المحاولة", "logoutSuccessTitle": "Logged out",
"logoutSuccessTitle": "تم تسجيل الخروج", "logoutSuccessSubtitle": "You have been logged out",
"logoutSuccessSubtitle": "تم تسجيل خروجك", "logoutTitle": "Logout",
"logoutTitle": "تسجيل الخروج", "logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutUsernameSubtitle": "أنت حاليا مسجل الدخول ك <Code>{{username}}</Code>، انقر الزر أدناه لتسجيل الخروج.", "logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutOauthSubtitle": "أنت حاليا مسجل الدخول ك <Code>{{username}}</Code> باستخدام مزود OAuth {{provider}} ، انقر الزر أدناه لتسجيل الخروج.", "notFoundTitle": "Page not found",
"notFoundTitle": "الصفحة غير موجودة", "notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundSubtitle": "الصفحة التي تبحث عنها غير موجودة.", "notFoundButton": "Go home",
"notFoundButton": "انتقل إلى الرئيسية", "totpFailTitle": "Failed to verify code",
"totpFailTitle": "فشل في التحقق من الرمز", "totpFailSubtitle": "Please check your code and try again",
"totpFailSubtitle": "الرجاء التحقق من الرمز الخاص بك وحاول مرة أخرى", "totpSuccessTitle": "Verified",
"totpSuccessTitle": "تم التحقق", "totpSuccessSubtitle": "Redirecting to your app",
"totpSuccessSubtitle": "إعادة توجيه إلى تطبيقك", "totpTitle": "Enter your TOTP code",
"totpTitle": "أدخل رمز TOTP الخاص بك", "unauthorizedTitle": "Unauthorized",
"unauthorizedTitle": "غير مرخص", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "المستخدم الذي يحمل اسم المستخدم <Code>{{username}}</Code> غير مصرح له بالوصول إلى المورد <Code>{{resource}}</Code>.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unauthorizedButton": "Try again"
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedButton": "حاول مجددا",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "إلغاء",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -1,51 +1,45 @@
{ {
"loginTitle": "Willkommen zurück, logge dich ein mit", "loginTitle": "Welcome back, login with",
"loginDivider": "Oder mit Passwort fortfahren", "loginDivider": "Or continue with password",
"loginUsername": "Benutzername", "loginUsername": "Username",
"loginPassword": "Passwort", "loginPassword": "Password",
"loginSubmit": "Anmelden", "loginSubmit": "Login",
"loginFailTitle": "Login fehlgeschlagen", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Bitte überprüfe deinen Benutzernamen und Passwort", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "Sie konnten sich zu oft nicht einloggen, bitte versuchen Sie es später erneut", "loginSuccessTitle": "Logged in",
"loginSuccessTitle": "Angemeldet", "loginSuccessSubtitle": "Welcome back!",
"loginSuccessSubtitle": "Willkommen zurück!", "loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "Interner Fehler", "loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthFailSubtitle": "Fehler beim Abrufen der OAuth-URL", "loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessTitle": "Leite weiter", "loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
"loginOauthSuccessSubtitle": "Weiterleitung zu Ihrem OAuth-Provider", "continueRedirectingTitle": "Redirecting...",
"continueRedirectingTitle": "Leite weiter...", "continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectingSubtitle": "Sie sollten in Kürze zur App weitergeleitet werden", "continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectTitle": "Ungültige Weiterleitung", "continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInvalidRedirectSubtitle": "Die Weiterleitungs-URL ist ungültig", "continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectTitle": "Unsichere Weiterleitung", "continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"continueInsecureRedirectSubtitle": "Sie versuchen von <Code>https</Code> auf <Code>http</Code>weiterzuleiten. Sind Sie sicher, dass Sie fortfahren möchten?", "continueTitle": "Continue",
"continueTitle": "Weiter", "continueSubtitle": "Click the button to continue to your app.",
"continueSubtitle": "Klicken Sie auf den Button, um zur App zu gelangen.", "internalErrorTitle": "Internal Server Error",
"internalErrorTitle": "Interner Serverfehler", "internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorSubtitle": "Ein Error ist auf dem Server aufgetreten, weshalb ihre Anfrage derzeit nicht bearbeitet werden kann.", "internalErrorButton": "Try again",
"internalErrorButton": "Erneut versuchen", "logoutFailTitle": "Failed to log out",
"logoutFailTitle": "Abmelden fehlgeschlagen", "logoutFailSubtitle": "Please try again",
"logoutFailSubtitle": "Bitte versuchen Sie es erneut", "logoutSuccessTitle": "Logged out",
"logoutSuccessTitle": "Abgemeldet", "logoutSuccessSubtitle": "You have been logged out",
"logoutSuccessSubtitle": "Sie wurden abgemeldet", "logoutTitle": "Logout",
"logoutTitle": "Abmelden", "logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutUsernameSubtitle": "Sie sind derzeit als <Code>{{username}}</Code>angemeldet. Klicken Sie auf den Button unten, um sich abzumelden.", "logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutOauthSubtitle": "Sie sind derzeit als <Code>{{username}}</Code> mit dem {{provider}} OAuth-Anbieter angemeldet. Klicken Sie auf den Button unten, um sich abzumelden.", "notFoundTitle": "Page not found",
"notFoundTitle": "Seite nicht gefunden", "notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundSubtitle": "Die gesuchte Seite existiert nicht.", "notFoundButton": "Go home",
"notFoundButton": "Nach Hause", "totpFailTitle": "Failed to verify code",
"totpFailTitle": "Fehler beim Verifizieren des Codes", "totpFailSubtitle": "Please check your code and try again",
"totpFailSubtitle": "Bitte überprüfen Sie Ihren Code und versuchen Sie es erneut", "totpSuccessTitle": "Verified",
"totpSuccessTitle": "Verifiziert", "totpSuccessSubtitle": "Redirecting to your app",
"totpSuccessSubtitle": "Leite zur App weiter", "totpTitle": "Enter your TOTP code",
"totpTitle": "Geben Sie Ihren TOTP Code ein", "unauthorizedTitle": "Unauthorized",
"unauthorizedTitle": "Unautorisiert", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "Der Benutzer mit Benutzername <Code>{{username}}</Code> ist nicht berechtigt auf die Ressource <Code>{{resource}}</Code> zuzugreifen.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unauthorizedButton": "Try again"
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedButton": "Erneut versuchen",
"untrustedRedirectTitle": "Nicht vertrauenswürdige Weiterleitung",
"untrustedRedirectSubtitle": "Sie versuchen auf eine Domain umzuleiten, die nicht mit Ihrer konfigurierten Domain übereinstimmt (<Code>{{domain}}</Code>). Sind Sie sicher, dass Sie fortfahren möchten?",
"cancelTitle": "Abbrechen",
"forgotPasswordTitle": "Passwort vergessen?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Είσοδος", "loginSubmit": "Είσοδος",
"loginFailTitle": "Αποτυχία σύνδεσης", "loginFailTitle": "Αποτυχία σύνδεσης",
"loginFailSubtitle": "Παρακαλώ ελέγξτε το όνομα χρήστη και τον κωδικό πρόσβασης", "loginFailSubtitle": "Παρακαλώ ελέγξτε το όνομα χρήστη και τον κωδικό πρόσβασης",
"loginFailRateLimit": "Αποτύχατε να συνδεθείτε πάρα πολλές φορές, παρακαλώ προσπαθήστε ξανά αργότερα",
"loginSuccessTitle": "Συνδεδεμένος", "loginSuccessTitle": "Συνδεδεμένος",
"loginSuccessSubtitle": "Καλώς ήρθατε!", "loginSuccessSubtitle": "Καλώς ήρθατε!",
"loginOauthFailTitle": "Εσωτερικό σφάλμα", "loginOauthFailTitle": "Εσωτερικό σφάλμα",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Ανακατεύθυνση στην εφαρμογή σας", "totpSuccessSubtitle": "Ανακατεύθυνση στην εφαρμογή σας",
"totpTitle": "Εισάγετε τον κωδικό TOTP", "totpTitle": "Εισάγετε τον κωδικό TOTP",
"unauthorizedTitle": "Μη εξουσιοδοτημένο", "unauthorizedTitle": "Μη εξουσιοδοτημένο",
"unauthorizedResourceSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν έχει άδεια πρόσβασης στον πόρο <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "Ο χρήστης με όνομα χρήστη {{username}} δεν είναι εξουσιοδοτημένος να έχει πρόσβαση στον πόρο <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν είναι εξουσιοδοτημένος να συνδεθεί.", "unaothorizedLoginSubtitle": "Ο χρήστης με όνομα χρήστη {{username}} δεν είναι εξουσιοδοτημένος να συνδεθεί.",
"unauthorizedGroupsSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν είναι στις ομάδες που απαιτούνται από τον πόρο <Code>{{resource}}</Code>.", "unauthorizedButton": "Προσπαθήστε ξανά"
"unauthorizedButton": "Προσπαθήστε ξανά",
"untrustedRedirectTitle": "Μη έμπιστη ανακατεύθυνση",
"untrustedRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε σε έναν τομέα που δεν ταιριάζει με τον ρυθμισμένο τομέα σας (<Code>{{domain}}</Code>). Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
"cancelTitle": "Ακύρωση",
"forgotPasswordTitle": "Ξεχάσατε το συνθηματικό σας;"
} }

View File

@@ -1,16 +1,14 @@
{ {
"loginTitle": "Welcome back, login with", "loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login", "loginDivider": "Or continue with password",
"loginDivider": "Or",
"loginUsername": "Username", "loginUsername": "Username",
"loginPassword": "Password", "loginPassword": "Password",
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred", "loginOauthFailTitle": "Internal error",
"loginOauthFailSubtitle": "Failed to get OAuth URL", "loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting", "loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider", "loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -19,16 +17,19 @@
"continueInvalidRedirectTitle": "Invalid redirect", "continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid", "continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect", "continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <code>https</code> to <code>http</code> which is not secure. Are you sure you want to continue?", "continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"continueTitle": "Continue", "continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.", "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", "logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again", "logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out", "logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out", "logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout", "logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.", "logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.", "logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"notFoundTitle": "Page not found", "notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.", "notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home", "notFoundButton": "Go home",
@@ -37,17 +38,8 @@
"totpSuccessTitle": "Verified", "totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{domain}}</code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information."
} }

View File

@@ -1,16 +1,14 @@
{ {
"loginTitle": "Welcome back, login with", "loginTitle": "Welcome back, login with",
"loginTitleSimple": "Welcome back, please login", "loginDivider": "Or continue with password",
"loginDivider": "Or",
"loginUsername": "Username", "loginUsername": "Username",
"loginPassword": "Password", "loginPassword": "Password",
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "An error occurred", "loginOauthFailTitle": "Internal error",
"loginOauthFailSubtitle": "Failed to get OAuth URL", "loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting", "loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider", "loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -19,16 +17,19 @@
"continueInvalidRedirectTitle": "Invalid redirect", "continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid", "continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect", "continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <code>https</code> to <code>http</code> which is not secure. Are you sure you want to continue?", "continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"continueTitle": "Continue", "continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.", "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", "logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again", "logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out", "logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out", "logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout", "logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.", "logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.", "logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"notFoundTitle": "Page not found", "notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.", "notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home", "notFoundButton": "Go home",
@@ -37,17 +38,8 @@
"totpSuccessTitle": "Verified", "totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{domain}}</code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "An error occurred",
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information."
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Se connecter", "loginSubmit": "Se connecter",
"loginFailTitle": "Échec de la connexion", "loginFailTitle": "Échec de la connexion",
"loginFailSubtitle": "Veuillez vérifier votre nom d'utilisateur et votre mot de passe", "loginFailSubtitle": "Veuillez vérifier votre nom d'utilisateur et votre mot de passe",
"loginFailRateLimit": "Vous n'avez pas pu vous connecter trop de fois, veuillez réessayer plus tard",
"loginSuccessTitle": "Connecté", "loginSuccessTitle": "Connecté",
"loginSuccessSubtitle": "Bienvenue!", "loginSuccessSubtitle": "Bienvenue!",
"loginOauthFailTitle": "Erreur interne", "loginOauthFailTitle": "Erreur interne",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirection vers votre application", "totpSuccessSubtitle": "Redirection vers votre application",
"totpTitle": "Saisissez votre code TOTP", "totpTitle": "Saisissez votre code TOTP",
"unauthorizedTitle": "Non autorisé", "unauthorizedTitle": "Non autorisé",
"unauthorizedResourceSubtitle": "L'utilisateur avec le nom d'utilisateur <Code>{{username}}</Code> n'est pas autorisé à accéder à la ressource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "L'utilisateur avec le nom d'utilisateur {{username}} n'est pas autorisé à accéder à la ressource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "L'utilisateur avec le nom d'utilisateur {{username}} n'est pas autorisé à se connecter.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Réessayer"
"unauthorizedButton": "Réessayer",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -1,51 +1,45 @@
{ {
"loginTitle": "Welkom terug, log in met", "loginTitle": "Welcome back, login with",
"loginDivider": "Of ga door met wachtwoord", "loginDivider": "Or continue with password",
"loginUsername": "Gebruikersnaam", "loginUsername": "Username",
"loginPassword": "Wachtwoord", "loginPassword": "Password",
"loginSubmit": "Log in", "loginSubmit": "Login",
"loginFailTitle": "Mislukt om in te loggen", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Controleer je gebruikersnaam en wachtwoord", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "Inloggen te vaak mislukt, probeer het later opnieuw", "loginSuccessTitle": "Logged in",
"loginSuccessTitle": "Ingelogd", "loginSuccessSubtitle": "Welcome back!",
"loginSuccessSubtitle": "Welkom terug!", "loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "Interne fout", "loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthFailSubtitle": "Fout bij het ophalen van OAuth URL", "loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessTitle": "Omleiden", "loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
"loginOauthSuccessSubtitle": "Omleiden naar je OAuth provider", "continueRedirectingTitle": "Redirecting...",
"continueRedirectingTitle": "Omleiden...", "continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectingSubtitle": "Je wordt naar de app doorgestuurd", "continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectTitle": "Ongeldige omleiding", "continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInvalidRedirectSubtitle": "De omleidings-URL is ongeldig", "continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectTitle": "Onveilige doorverwijzing", "continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"continueInsecureRedirectSubtitle": "Je probeert door te verwijzen van <Code>https</Code> naar <Code>http</Code>, weet je zeker dat je wilt doorgaan?", "continueTitle": "Continue",
"continueTitle": "Ga verder", "continueSubtitle": "Click the button to continue to your app.",
"continueSubtitle": "Klik op de knop om door te gaan naar de app.", "internalErrorTitle": "Internal Server Error",
"internalErrorTitle": "Interne server fout", "internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorSubtitle": "Er is een fout opgetreden op de server en het kan momenteel niet voldoen aan je verzoek.", "internalErrorButton": "Try again",
"internalErrorButton": "Opnieuw proberen", "logoutFailTitle": "Failed to log out",
"logoutFailTitle": "Afmelden mislukt", "logoutFailSubtitle": "Please try again",
"logoutFailSubtitle": "Probeer het opnieuw", "logoutSuccessTitle": "Logged out",
"logoutSuccessTitle": "Afgemeld", "logoutSuccessSubtitle": "You have been logged out",
"logoutSuccessSubtitle": "Je bent afgemeld", "logoutTitle": "Logout",
"logoutTitle": "Afmelden", "logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutUsernameSubtitle": "Je bent momenteel ingelogd als <Code>{{username}}</Code>, klik op de knop hieronder om uit te loggen.", "logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutOauthSubtitle": "Je bent momenteel ingelogd als <Code>{{username}}</Code> met behulp van de {{provider}} OAuth provider, klik op de knop hieronder om uit te loggen.", "notFoundTitle": "Page not found",
"notFoundTitle": "Pagina niet gevonden", "notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundSubtitle": "De pagina die je zoekt bestaat niet.", "notFoundButton": "Go home",
"notFoundButton": "Naar startpagina", "totpFailTitle": "Failed to verify code",
"totpFailTitle": "Verifiëren van code mislukt", "totpFailSubtitle": "Please check your code and try again",
"totpFailSubtitle": "Controleer je code en probeer het opnieuw", "totpSuccessTitle": "Verified",
"totpSuccessTitle": "Geverifiëerd", "totpSuccessSubtitle": "Redirecting to your app",
"totpSuccessSubtitle": "Omleiden naar je app", "totpTitle": "Enter your TOTP code",
"totpTitle": "Voer je TOTP-code in", "unauthorizedTitle": "Unauthorized",
"unauthorizedTitle": "Ongeautoriseerd", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "De gebruiker met gebruikersnaam <Code>{{username}}</Code> heeft geen toegang tot de bron <Code>{{resource}}</Code>.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unauthorizedButton": "Try again"
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedButton": "Opnieuw proberen",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -3,10 +3,9 @@
"loginDivider": "Lub kontynuuj z hasłem", "loginDivider": "Lub kontynuuj z hasłem",
"loginUsername": "Nazwa użytkownika", "loginUsername": "Nazwa użytkownika",
"loginPassword": "Hasło", "loginPassword": "Hasło",
"loginSubmit": "Zaloguj się", "loginSubmit": "Login",
"loginFailTitle": "Nie udało się zalogować", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Sprawdź swoją nazwę użytkownika i hasło", "loginFailSubtitle": "Sprawdź swoją nazwę użytkownika i hasło",
"loginFailRateLimit": "Nie udało się zalogować zbyt wiele razy, spróbuj ponownie później",
"loginSuccessTitle": "Zalogowano", "loginSuccessTitle": "Zalogowano",
"loginSuccessSubtitle": "Witaj ponownie!", "loginSuccessSubtitle": "Witaj ponownie!",
"loginOauthFailTitle": "Wewnętrzny błąd", "loginOauthFailTitle": "Wewnętrzny błąd",
@@ -22,9 +21,9 @@
"continueTitle": "Kontynuuj", "continueTitle": "Kontynuuj",
"continueSubtitle": "Kliknij przycisk, aby przejść do aplikacji.", "continueSubtitle": "Kliknij przycisk, aby przejść do aplikacji.",
"internalErrorTitle": "Wewnętrzny błąd serwera", "internalErrorTitle": "Wewnętrzny błąd serwera",
"internalErrorSubtitle": "Wystąpił błąd na serwerze i obecnie nie można obsłużyć tego żądania.", "internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Spróbuj ponownie", "internalErrorButton": "Spróbuj ponownie",
"logoutFailTitle": "Nie udało się wylogować", "logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Spróbuj ponownie", "logoutFailSubtitle": "Spróbuj ponownie",
"logoutSuccessTitle": "Wylogowano", "logoutSuccessTitle": "Wylogowano",
"logoutSuccessSubtitle": "Zostałeś wylogowany", "logoutSuccessSubtitle": "Zostałeś wylogowany",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Przekierowywanie do aplikacji", "totpSuccessSubtitle": "Przekierowywanie do aplikacji",
"totpTitle": "Wprowadź kod TOTP", "totpTitle": "Wprowadź kod TOTP",
"unauthorizedTitle": "Nieautoryzowany", "unauthorizedTitle": "Nieautoryzowany",
"unauthorizedResourceSubtitle": "Użytkownik o nazwie <Code>{{username}}</Code> nie jest upoważniony do uzyskania dostępu do zasobu <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "Użytkownik o nazwie {{username}} nie jest upoważniony do uzyskania dostępu do zasobu <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "Użytkownik o nazwie {{username}} nie jest upoważniony do logowania.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Spróbuj ponownie"
"unauthorizedButton": "Spróbuj ponownie",
"untrustedRedirectTitle": "Niezaufane przekierowanie",
"untrustedRedirectSubtitle": "Próbujesz przekierować do domeny, która nie pasuje do skonfigurowanej przez Ciebie domeny (<Code>{{domain}}</Code>). Czy na pewno chcesz kontynuować?",
"cancelTitle": "Anuluj",
"forgotPasswordTitle": "Nie pamiętasz hasła?"
} }

View File

@@ -1,51 +1,45 @@
{ {
"loginTitle": "Bem-vindo de volta, acesse com", "loginTitle": "Welcome back, login with",
"loginDivider": "Ou continuar com uma senha", "loginDivider": "Or continue with password",
"loginUsername": "Nome de usuário", "loginUsername": "Username",
"loginPassword": "Senha", "loginPassword": "Password",
"loginSubmit": "Entrar", "loginSubmit": "Login",
"loginFailTitle": "Falha ao iniciar sessão", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Por favor, verifique seu usuário e senha", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "Você falhou em iniciar sessão muitas vezes, por favor tente novamente mais tarde", "loginSuccessTitle": "Logged in",
"loginSuccessTitle": "Sessão Iniciada", "loginSuccessSubtitle": "Welcome back!",
"loginSuccessSubtitle": "Bem-vindo de volta!", "loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "Erro interno", "loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthFailSubtitle": "Falha ao obter URL de OAuth", "loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessTitle": "Redirecionando", "loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
"loginOauthSuccessSubtitle": "Redirecionando para seu provedor OAuth", "continueRedirectingTitle": "Redirecting...",
"continueRedirectingTitle": "Redirecionando...", "continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueRedirectingSubtitle": "Você deve ser redirecionado para o aplicativo em breve", "continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectTitle": "Redirecionamento inválido", "continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInvalidRedirectSubtitle": "O endereço de redirecionamento é inválido", "continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectTitle": "Redirecionamento inseguro", "continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"continueInsecureRedirectSubtitle": "Você está tentando redirecionar de <Code>https</Code> para <Code>http</Code>, você tem certeza que deseja continuar?", "continueTitle": "Continue",
"continueTitle": "Continuar", "continueSubtitle": "Click the button to continue to your app.",
"continueSubtitle": "Clique no botão para continuar para o seu aplicativo.", "internalErrorTitle": "Internal Server Error",
"internalErrorTitle": "Erro interno do servidor", "internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorSubtitle": "Ocorreu um erro no servidor e atualmente não pode servir sua solicitação.", "internalErrorButton": "Try again",
"internalErrorButton": "Tentar novamente", "logoutFailTitle": "Failed to log out",
"logoutFailTitle": "Falha ao encerrar sessão", "logoutFailSubtitle": "Please try again",
"logoutFailSubtitle": "Por favor, tente novamente", "logoutSuccessTitle": "Logged out",
"logoutSuccessTitle": "Sessão encerrada", "logoutSuccessSubtitle": "You have been logged out",
"logoutSuccessSubtitle": "Você foi desconectado", "logoutTitle": "Logout",
"logoutTitle": "Sair", "logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutUsernameSubtitle": "Você está atualmente logado como <Code>{{username}}</Code>, clique no botão abaixo para sair.", "logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutOauthSubtitle": "Você está atualmente logado como <Code>{{username}}</Code> usando o provedor {{provider}} OAuth, clique no botão abaixo para sair.", "notFoundTitle": "Page not found",
"notFoundTitle": "Página não encontrada", "notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundSubtitle": "A página que você está procurando não existe.", "notFoundButton": "Go home",
"notFoundButton": "Voltar para a tela inicial", "totpFailTitle": "Failed to verify code",
"totpFailTitle": "Falha ao verificar código", "totpFailSubtitle": "Please check your code and try again",
"totpFailSubtitle": "Por favor, verifique seu código e tente novamente", "totpSuccessTitle": "Verified",
"totpSuccessTitle": "Verificado", "totpSuccessSubtitle": "Redirecting to your app",
"totpSuccessSubtitle": "Redirecionando para o seu aplicativo", "totpTitle": "Enter your TOTP code",
"totpTitle": "Insira o seu código TOTP", "unauthorizedTitle": "Unauthorized",
"unauthorizedTitle": "Não autorizado", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "O usuário com nome de usuário <Code>{{username}}</Code> não está autorizado a acessar o recurso <Code>{{resource}}</Code>.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unauthorizedButton": "Try again"
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedButton": "Tentar novamente",
"untrustedRedirectTitle": "Redirecionamento não confiável",
"untrustedRedirectSubtitle": "Você está tentando redirecionar para um domínio que não corresponde ao seu domínio configurado (<Code>{{domain}}</Code>). Tem certeza que deseja continuar?",
"cancelTitle": "Cancelar",
"forgotPasswordTitle": "Esqueceu sua senha?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -1,51 +1,45 @@
{ {
"loginTitle": "Welcome back, login with", "loginTitle": "Welcome back, login with",
"loginDivider": "Ya da şifre ile devam edin", "loginDivider": "Or continue with password",
"loginUsername": "Kullanıcı Adı", "loginUsername": "Username",
"loginPassword": "Şifre", "loginPassword": "Password",
"loginSubmit": "Giriş Yap", "loginSubmit": "Login",
"loginFailTitle": "Giriş yapılamadı", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later", "loginSuccessTitle": "Logged in",
"loginSuccessTitle": "Giriş yapıldı", "loginSuccessSubtitle": "Welcome back!",
"loginSuccessSubtitle": "Tekrar hoş geldiniz!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
"loginOauthFailSubtitle": "Failed to get OAuth URL", "loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Yönlendiriliyor", "loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider", "loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
"continueRedirectingTitle": "Yönlendiriliyor...", "continueRedirectingTitle": "Redirecting...",
"continueRedirectingSubtitle": "You should be redirected to the app soon", "continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueInvalidRedirectTitle": "Invalid redirect", "continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid", "continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect", "continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?", "continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"continueTitle": "Devam et", "continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.", "continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "İç Sunucu Hatası", "internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.", "internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Tekrar deneyin", "internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out", "logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Lütfen tekrar deneyin", "logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Çıkış yapıldı", "logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out", "logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout", "logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.", "logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.", "logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"notFoundTitle": "Sayfa bulunamadı", "notFoundTitle": "Page not found",
"notFoundSubtitle": "Aradığınız sayfa mevcut değil.", "notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Ana sayfaya git", "notFoundButton": "Go home",
"totpFailTitle": "Kod doğrulanamadı", "totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again", "totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Doğrulandı", "totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "İptal",
"forgotPasswordTitle": "Forgot your password?"
} }

View File

@@ -6,7 +6,6 @@
"loginSubmit": "Login", "loginSubmit": "Login",
"loginFailTitle": "Failed to log in", "loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password", "loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in", "loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!", "loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error", "loginOauthFailTitle": "Internal error",
@@ -40,12 +39,7 @@
"totpSuccessSubtitle": "Redirecting to your app", "totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code", "totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized", "unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.", "unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.", "unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.", "unauthorizedButton": "Try again"
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
} }

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