Compare commits

...

72 Commits

Author SHA1 Message Date
Stavros
3215bb6baa refactor: simplify ldap heartbeat 2025-07-12 00:21:22 +03:00
Stavros
a11aba72d8 feat: add heartbeat to keep ldap connection alive 2025-07-11 23:16:09 +03:00
Stavros
10d1b48505 chore: add dlv for debugging in dev workflow 2025-07-11 17:15:32 +03:00
Stavros
f73eb9571f fix: fix password reset message translations 2025-07-11 16:16:49 +03:00
dependabot[bot]
da2877a682 chore(deps): bump the minor-patch group across 1 directory with 3 updates (#259)
Bumps the minor-patch group with 3 updates in the /frontend directory: [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `@tanstack/react-query` from 5.81.5 to 5.82.0
- [Release notes](https://github.com/TanStack/query/releases)
- [Commits](https://github.com/TanStack/query/commits/v5.82.0/packages/react-query)

Updates `@types/node` from 24.0.12 to 24.0.13
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `vite` from 7.0.3 to 7.0.4
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.0.4/packages/vite)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.82.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@types/node"
  dependency-version: 24.0.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: vite
  dependency-version: 7.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-11 16:06:17 +03:00
dependabot[bot]
33cbfef02a chore(deps): bump the minor-patch group across 1 directory with 2 updates (#258)
Bumps the minor-patch group with 2 updates in the / directory: [golang.org/x/crypto](https://github.com/golang/crypto) and [github.com/docker/docker](https://github.com/docker/docker).


Updates `golang.org/x/crypto` from 0.39.0 to 0.40.0
- [Commits](https://github.com/golang/crypto/compare/v0.39.0...v0.40.0)

Updates `github.com/docker/docker` from 28.3.1+incompatible to 28.3.2+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.3.1...v28.3.2)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: github.com/docker/docker
  dependency-version: 28.3.2+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-11 16:05:54 +03:00
dependabot[bot]
c1a6428ed3 chore(deps): bump zod from 3.25.76 to 4.0.2 in /frontend (#254)
Bumps [zod](https://github.com/colinhacks/zod) from 3.25.76 to 4.0.2.
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/commits)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-11 16:05:34 +03:00
github-actions[bot]
2ee7932cba docs: regenerate readme sponsors list (#249)
Co-authored-by: GitHub <noreply@github.com>
2025-07-10 02:28:58 +03:00
Stavros
fe440a6f2e New translations en.json (Arabic) (#245) 2025-07-10 01:00:34 +03:00
Stavros
0ace88a877 feat: add support for bypassing authentication for specific IPs 2025-07-10 00:53:22 +03:00
Stavros
476ed6964d fix: fix docker label matching logic 2025-07-10 00:34:04 +03:00
Stavros
b3dca0429f New translations en.json (French) (#243) 2025-07-09 23:36:55 +03:00
Vincent Young
9e4b68112c fix: i18n zh-TW locale updates (#242) 2025-07-09 23:30:37 +03:00
dependabot[bot]
364f0e221e chore(deps): bump the minor-patch group in /frontend with 3 updates (#239)
Bumps the minor-patch group in /frontend with 3 updates: [i18next](https://github.com/i18next/i18next), [zod](https://github.com/colinhacks/zod) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `i18next` from 25.3.1 to 25.3.2
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v25.3.1...v25.3.2)

Updates `zod` from 3.25.75 to 3.25.76
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v3.25.75...v3.25.76)

Updates `@types/node` from 24.0.10 to 24.0.12
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: i18next
  dependency-version: 25.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: zod
  dependency-version: 3.25.76
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@types/node"
  dependency-version: 24.0.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-09 18:05:55 +03:00
Stavros
09635666aa New Crowdin updates (#240)
* New translations en.json (Arabic)

* New translations en.json (Arabic)

* New translations en.json (Danish)

* New translations en.json (Greek)

* New translations en.json (Polish)
2025-07-09 18:05:28 +03:00
Stavros
9f02710114 feat: add support for comma list in label domain check 2025-07-09 17:49:13 +03:00
dependabot[bot]
64bdab5e5b chore(deps-dev): bump the minor-patch group in /frontend with 2 updates (#237)
Bumps the minor-patch group in /frontend with 2 updates: [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `typescript-eslint` from 8.35.1 to 8.36.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.36.0/packages/typescript-eslint)

Updates `vite` from 7.0.2 to 7.0.3
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.0.3/packages/vite)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: vite
  dependency-version: 7.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-08 23:50:31 +03:00
Stavros
0f4a6b5924 tests: fix parse header tests 2025-07-08 00:54:36 +03:00
Stavros
c662b9e222 tests: extend tests in utils and server 2025-07-08 00:47:07 +03:00
dependabot[bot]
a4722db7d7 chore(deps): bump zod in /frontend in the minor-patch group (#236)
Bumps the minor-patch group in /frontend with 1 update: [zod](https://github.com/colinhacks/zod).


Updates `zod` from 3.25.74 to 3.25.75
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v3.25.74...v3.25.75)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 3.25.75
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-07 23:37:16 +03:00
Stavros
f48bb65d7b feat: add support for using secret files for basic auth password 2025-07-07 23:31:51 +03:00
Stavros
7e604419ab New Crowdin updates (#192)
* New translations en.json (Spanish)

* New translations en.json (Spanish)

* New translations en.json (German)

* New translations en.json (Chinese Simplified)

* New translations en.json (Spanish)

* New translations en.json (German)

* New translations en.json (Chinese Simplified)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Afrikaans)

* New translations en.json (Arabic)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Dutch)

* New translations en.json (Norwegian)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Traditional)

* New translations en.json (English)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (German)
2025-07-06 01:14:09 +03:00
dependabot[bot]
60cd0a216f chore(deps): bump the minor-patch group across 1 directory with 11 updates (#233)
Bumps the minor-patch group with 11 updates in the /frontend directory:

| Package | From | To |
| --- | --- | --- |
| [i18next](https://github.com/i18next/i18next) | `25.3.0` | `25.3.1` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.59.0` | `7.60.0` |
| [react-i18next](https://github.com/i18next/react-i18next) | `15.5.3` | `15.6.0` |
| [sonner](https://github.com/emilkowalski/sonner) | `2.0.5` | `2.0.6` |
| [zod](https://github.com/colinhacks/zod) | `3.25.67` | `3.25.74` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.30.0` | `9.30.1` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.0.8` | `24.0.10` |
| [eslint](https://github.com/eslint/eslint) | `9.30.0` | `9.30.1` |
| [globals](https://github.com/sindresorhus/globals) | `16.2.0` | `16.3.0` |
| [tw-animate-css](https://github.com/Wombosvideo/tw-animate-css) | `1.3.4` | `1.3.5` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `7.0.0` | `7.0.2` |



Updates `i18next` from 25.3.0 to 25.3.1
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v25.3.0...v25.3.1)

Updates `react-hook-form` from 7.59.0 to 7.60.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.59.0...v7.60.0)

Updates `react-i18next` from 15.5.3 to 15.6.0
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v15.5.3...v15.6.0)

Updates `sonner` from 2.0.5 to 2.0.6
- [Release notes](https://github.com/emilkowalski/sonner/releases)
- [Commits](https://github.com/emilkowalski/sonner/compare/v2.0.5...v2.0.6)

Updates `zod` from 3.25.67 to 3.25.74
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v3.25.67...v3.25.74)

Updates `@eslint/js` from 9.30.0 to 9.30.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.30.1/packages/js)

Updates `@types/node` from 24.0.8 to 24.0.10
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `eslint` from 9.30.0 to 9.30.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.30.0...v9.30.1)

Updates `globals` from 16.2.0 to 16.3.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.2.0...v16.3.0)

Updates `tw-animate-css` from 1.3.4 to 1.3.5
- [Release notes](https://github.com/Wombosvideo/tw-animate-css/releases)
- [Commits](https://github.com/Wombosvideo/tw-animate-css/compare/v1.3.4...v1.3.5)

Updates `vite` from 7.0.0 to 7.0.2
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.0.2/packages/vite)

---
updated-dependencies:
- dependency-name: i18next
  dependency-version: 25.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: react-hook-form
  dependency-version: 7.60.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-i18next
  dependency-version: 15.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: sonner
  dependency-version: 2.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: zod
  dependency-version: 3.25.74
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@eslint/js"
  dependency-version: 9.30.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@types/node"
  dependency-version: 24.0.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: eslint
  dependency-version: 9.30.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: globals
  dependency-version: 16.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: tw-animate-css
  dependency-version: 1.3.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: vite
  dependency-version: 7.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-06 01:13:29 +03:00
Stavros
ec6e3aa718 chore: update readme 2025-07-06 01:09:09 +03:00
Stavros
6dc57ddf0f refactor: change basic auth label to username instead of user 2025-07-06 01:02:08 +03:00
Stavros
f780e81ec2 fix: fix error state look in login form 2025-07-06 00:46:59 +03:00
dependabot[bot]
8b70ab47a4 chore(deps): bump the minor-patch group across 1 directory with 2 updates (#223)
Bumps the minor-patch group with 2 updates in the / directory: [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) and [github.com/docker/docker](https://github.com/docker/docker).


Updates `github.com/go-playground/validator/v10` from 10.26.0 to 10.27.0
- [Release notes](https://github.com/go-playground/validator/releases)
- [Commits](https://github.com/go-playground/validator/compare/v10.26.0...v10.27.0)

Updates `github.com/docker/docker` from 28.3.0+incompatible to 28.3.1+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.3.0...v28.3.1)

---
updated-dependencies:
- dependency-name: github.com/go-playground/validator/v10
  dependency-version: 10.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: github.com/docker/docker
  dependency-version: 28.3.1+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-05 18:20:03 +03:00
dependabot[bot]
b800359bb2 chore(deps-dev): bump vite from 6.3.5 to 7.0.0 in /frontend (#213)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.3.5 to 7.0.0.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@7.0.0/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-05 18:19:31 +03:00
Stavros
6ec8c9766c feat: add ldap support (#232)
* feat: add ldap support

* feat: add insecure option for self-signed certificates

* fix: recognize ldap as a username provider

* test: fix tests

* feat: add configurable search filter

* fix: fix error message in ldap search result

* refactor: bot suggestions
2025-07-05 18:17:39 +03:00
dependabot[bot]
4524e3322c chore(deps): bump oven/bun from 1.2.17-alpine to 1.2.18-alpine (#229)
Bumps oven/bun from 1.2.17-alpine to 1.2.18-alpine.

---
updated-dependencies:
- dependency-name: oven/bun
  dependency-version: 1.2.18-alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-04 12:05:46 +03:00
Stavros
1941de1125 refactor: remove init functions from methods (#228) 2025-07-04 02:35:09 +03:00
Stavros
49c4c7a455 feat: add codeconv to ci 2025-07-04 01:48:38 +03:00
Stavros
c10bff55de fix: encrypt the cookie in sessions (#225)
* fix: encrypt the cookie in sessions

* tests: use new auth config in tests

* fix: coderabbit suggestions
2025-07-04 01:43:36 +03:00
Stavros
7640e956c2 chore: run nightly workflow every day 2025-07-03 12:54:26 +03:00
dependabot[bot]
4c18a4c44d chore(deps): bump github.com/go-viper/mapstructure/v2 (#215)
Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.2.1 to 2.3.0.
- [Release notes](https://github.com/go-viper/mapstructure/releases)
- [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-viper/mapstructure/compare/v2.2.1...v2.3.0)

---
updated-dependencies:
- dependency-name: github.com/go-viper/mapstructure/v2
  dependency-version: 2.3.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 15:55:23 +03:00
dependabot[bot]
87a5c0f3f1 chore(deps): bump the minor-patch group across 1 directory with 12 updates (#218)
Bumps the minor-patch group with 12 updates in the /frontend directory:

| Package | From | To |
| --- | --- | --- |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.1.10` | `4.1.11` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.81.2` | `5.81.5` |
| [i18next](https://github.com/i18next/i18next) | `25.2.1` | `25.3.0` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `0.523.0` | `0.525.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.58.1` | `7.59.0` |
| [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) | `7.6.2` | `7.6.3` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.1.10` | `4.1.11` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.29.0` | `9.30.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.0.4` | `24.0.8` |
| [eslint](https://github.com/eslint/eslint) | `9.29.0` | `9.30.0` |
| [prettier](https://github.com/prettier/prettier) | `3.6.1` | `3.6.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.35.0` | `8.35.1` |



Updates `@tailwindcss/vite` from 4.1.10 to 4.1.11
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.11/packages/@tailwindcss-vite)

Updates `@tanstack/react-query` from 5.81.2 to 5.81.5
- [Release notes](https://github.com/TanStack/query/releases)
- [Commits](https://github.com/TanStack/query/commits/v5.81.5/packages/react-query)

Updates `i18next` from 25.2.1 to 25.3.0
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v25.2.1...v25.3.0)

Updates `lucide-react` from 0.523.0 to 0.525.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.525.0/packages/lucide-react)

Updates `react-hook-form` from 7.58.1 to 7.59.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.58.1...v7.59.0)

Updates `react-router` from 7.6.2 to 7.6.3
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.6.3/packages/react-router)

Updates `tailwindcss` from 4.1.10 to 4.1.11
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.11/packages/tailwindcss)

Updates `@eslint/js` from 9.29.0 to 9.30.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.30.0/packages/js)

Updates `@types/node` from 24.0.4 to 24.0.8
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `eslint` from 9.29.0 to 9.30.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.29.0...v9.30.0)

Updates `prettier` from 3.6.1 to 3.6.2
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.6.1...3.6.2)

Updates `typescript-eslint` from 8.35.0 to 8.35.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.35.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.1.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.81.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: i18next
  dependency-version: 25.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: lucide-react
  dependency-version: 0.525.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-hook-form
  dependency-version: 7.59.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-router
  dependency-version: 7.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: tailwindcss
  dependency-version: 4.1.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@eslint/js"
  dependency-version: 9.30.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@types/node"
  dependency-version: 24.0.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: eslint
  dependency-version: 9.30.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: prettier
  dependency-version: 3.6.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: typescript-eslint
  dependency-version: 8.35.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 15:52:47 +03:00
Stavros
84d4c84ed2 feat: allow or block an ip/range of ips using labels (#211)
* feat: allow or block an ip/range of ips using labels

* refactor: redirect to root page when no username or ip is provided in the unauthorized page
2025-06-25 20:35:48 +03:00
dependabot[bot]
9008b67f7d chore(deps): bump the minor-patch group across 1 directory with 8 updates (#210)
Bumps the minor-patch group with 8 updates in the /frontend directory:

| Package | From | To |
| --- | --- | --- |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.80.7` | `5.81.2` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `0.516.0` | `0.523.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.58.0` | `7.58.1` |
| [@tanstack/eslint-plugin-query](https://github.com/TanStack/query/tree/HEAD/packages/eslint-plugin-query) | `5.78.0` | `5.81.2` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.0.3` | `24.0.4` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `4.5.2` | `4.6.0` |
| [prettier](https://github.com/prettier/prettier) | `3.5.3` | `3.6.1` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.34.1` | `8.35.0` |



Updates `@tanstack/react-query` from 5.80.7 to 5.81.2
- [Release notes](https://github.com/TanStack/query/releases)
- [Commits](https://github.com/TanStack/query/commits/v5.81.2/packages/react-query)

Updates `lucide-react` from 0.516.0 to 0.523.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.523.0/packages/lucide-react)

Updates `react-hook-form` from 7.58.0 to 7.58.1
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.58.0...v7.58.1)

Updates `@tanstack/eslint-plugin-query` from 5.78.0 to 5.81.2
- [Release notes](https://github.com/TanStack/query/releases)
- [Commits](https://github.com/TanStack/query/commits/v5.81.2/packages/eslint-plugin-query)

Updates `@types/node` from 24.0.3 to 24.0.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@vitejs/plugin-react` from 4.5.2 to 4.6.0
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@4.6.0/packages/plugin-react)

Updates `prettier` from 3.5.3 to 3.6.1
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.5.3...3.6.1)

Updates `typescript-eslint` from 8.34.1 to 8.35.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.35.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.81.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: lucide-react
  dependency-version: 0.523.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-hook-form
  dependency-version: 7.58.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@tanstack/eslint-plugin-query"
  dependency-version: 5.81.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@types/node"
  dependency-version: 24.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 4.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: prettier
  dependency-version: 3.6.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: typescript-eslint
  dependency-version: 8.35.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-25 17:20:27 +03:00
dependabot[bot]
47980a3dd0 chore(deps): bump github.com/docker/docker in the minor-patch group (#209)
Bumps the minor-patch group with 1 update: [github.com/docker/docker](https://github.com/docker/docker).


Updates `github.com/docker/docker` from 28.2.2+incompatible to 28.3.0+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.2.2...v28.3.0)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-version: 28.3.0+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-25 17:19:59 +03:00
dependabot[bot]
6cbb9e93c0 chore(deps): bump oven/bun from 1.2.16-alpine to 1.2.17-alpine (#205)
Bumps oven/bun from 1.2.16-alpine to 1.2.17-alpine.

---
updated-dependencies:
- dependency-name: oven/bun
  dependency-version: 1.2.17-alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-25 17:19:32 +03:00
Stavros
f3ec4baf3c feat: add support for logging in to a basic auth protected app (#203) 2025-06-20 11:33:06 +03:00
dependabot[bot]
b5799da703 chore(deps-dev): bump @types/node from 22.15.29 to 24.0.0 in /frontend (#191)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.15.29 to 24.0.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-19 22:17:14 +03:00
dependabot[bot]
80b1820333 chore(deps): bump the minor-patch group across 1 directory with 14 updates (#200)
Bumps the minor-patch group with 14 updates in the /frontend directory:

| Package | From | To |
| --- | --- | --- |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.1.8` | `4.1.10` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.80.6` | `5.80.7` |
| [axios](https://github.com/axios/axios) | `1.9.0` | `1.10.0` |
| [i18next-browser-languagedetector](https://github.com/i18next/i18next-browser-languageDetector) | `8.1.0` | `8.2.0` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `0.513.0` | `0.516.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.57.0` | `7.58.0` |
| [react-i18next](https://github.com/i18next/react-i18next) | `15.5.2` | `15.5.3` |
| [tailwind-merge](https://github.com/dcastil/tailwind-merge) | `3.3.0` | `3.3.1` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.1.8` | `4.1.10` |
| [zod](https://github.com/colinhacks/zod) | `3.25.57` | `3.25.67` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.28.0` | `9.29.0` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.1.7` | `19.1.8` |
| [eslint](https://github.com/eslint/eslint) | `9.28.0` | `9.29.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.34.0` | `8.34.1` |



Updates `@tailwindcss/vite` from 4.1.8 to 4.1.10
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.10/packages/@tailwindcss-vite)

Updates `@tanstack/react-query` from 5.80.6 to 5.80.7
- [Release notes](https://github.com/TanStack/query/releases)
- [Commits](https://github.com/TanStack/query/commits/v5.80.7/packages/react-query)

Updates `axios` from 1.9.0 to 1.10.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.9.0...v1.10.0)

Updates `i18next-browser-languagedetector` from 8.1.0 to 8.2.0
- [Changelog](https://github.com/i18next/i18next-browser-languageDetector/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next-browser-languageDetector/compare/v8.1.0...v8.2.0)

Updates `lucide-react` from 0.513.0 to 0.516.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.516.0/packages/lucide-react)

Updates `react-hook-form` from 7.57.0 to 7.58.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.57.0...v7.58.0)

Updates `react-i18next` from 15.5.2 to 15.5.3
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v15.5.2...v15.5.3)

Updates `tailwind-merge` from 3.3.0 to 3.3.1
- [Release notes](https://github.com/dcastil/tailwind-merge/releases)
- [Commits](https://github.com/dcastil/tailwind-merge/compare/v3.3.0...v3.3.1)

Updates `tailwindcss` from 4.1.8 to 4.1.10
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.10/packages/tailwindcss)

Updates `zod` from 3.25.57 to 3.25.67
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v3.25.57...v3.25.67)

Updates `@eslint/js` from 9.28.0 to 9.29.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.29.0/packages/js)

Updates `@types/react` from 19.1.7 to 19.1.8
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `eslint` from 9.28.0 to 9.29.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.28.0...v9.29.0)

Updates `typescript-eslint` from 8.34.0 to 8.34.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.34.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.1.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.80.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: axios
  dependency-version: 1.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: i18next-browser-languagedetector
  dependency-version: 8.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: lucide-react
  dependency-version: 0.516.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-hook-form
  dependency-version: 7.58.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-i18next
  dependency-version: 15.5.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: tailwind-merge
  dependency-version: 3.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: tailwindcss
  dependency-version: 4.1.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: zod
  dependency-version: 3.25.67
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@eslint/js"
  dependency-version: 9.29.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@types/react"
  dependency-version: 19.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: eslint
  dependency-version: 9.29.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: typescript-eslint
  dependency-version: 8.34.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-19 22:09:34 +03:00
Stavros
aed29d2923 feat: allow user to specify domain in container labels in order to identify it (#198)
* feat: allow user to specify domain in container labels in order to identify it

* refactor: remove port from domain before getting container
2025-06-15 20:30:52 +03:00
Stavros
3397e2aa8e refactor: move to traefik paerser for label parsing (#197)
* refactor: move to traefik paerser for label parsing

* fix: sanitize headers before adding to map

* refactor: use splitn in header parser

* refactor: ignore containers that failed to get inspected in docker
2025-06-15 19:58:23 +03:00
Stavros
ee83c177f4 chore: update security note 2025-06-15 19:48:11 +03:00
Stavros
9eb296f146 New Crowdin updates (#176)
* New translations en.json (Spanish)

* New translations en.json (German)

* New translations en.json (German)

* New translations en.json (Chinese Simplified)
2025-06-11 10:15:58 +03:00
dependabot[bot]
7ac25f0a2a chore(deps): bump golang.org/x/crypto in the minor-patch group (#187)
Bumps the minor-patch group with 1 update: [golang.org/x/crypto](https://github.com/golang/crypto).


Updates `golang.org/x/crypto` from 0.38.0 to 0.39.0
- [Commits](https://github.com/golang/crypto/compare/v0.38.0...v0.39.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-11 10:15:37 +03:00
dependabot[bot]
6af079d369 chore(deps): bump the minor-patch group across 1 directory with 6 updates (#189)
Bumps the minor-patch group with 6 updates in the /frontend directory:

| Package | From | To |
| --- | --- | --- |
| [@hookform/resolvers](https://github.com/react-hook-form/resolvers) | `5.0.1` | `5.1.1` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.80.5` | `5.80.6` |
| [zod](https://github.com/colinhacks/zod) | `3.25.51` | `3.25.57` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.1.6` | `19.1.7` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `4.5.1` | `4.5.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.33.1` | `8.34.0` |



Updates `@hookform/resolvers` from 5.0.1 to 5.1.1
- [Release notes](https://github.com/react-hook-form/resolvers/releases)
- [Commits](https://github.com/react-hook-form/resolvers/compare/v5.0.1...v5.1.1)

Updates `@tanstack/react-query` from 5.80.5 to 5.80.6
- [Release notes](https://github.com/TanStack/query/releases)
- [Commits](https://github.com/TanStack/query/commits/v5.80.6/packages/react-query)

Updates `zod` from 3.25.51 to 3.25.57
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v3.25.51...v3.25.57)

Updates `@types/react` from 19.1.6 to 19.1.7
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@vitejs/plugin-react` from 4.5.1 to 4.5.2
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@4.5.2/packages/plugin-react)

Updates `typescript-eslint` from 8.33.1 to 8.34.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.34.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@hookform/resolvers"
  dependency-version: 5.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.80.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: zod
  dependency-version: 3.25.57
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@types/react"
  dependency-version: 19.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 4.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: typescript-eslint
  dependency-version: 8.34.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-11 10:15:19 +03:00
Stavros
523267e55b fix: disable CGO in binary builds 2025-06-11 10:11:23 +03:00
Stavros
e96a2bcaec chore: update discohook config 2025-06-06 15:27:42 +03:00
dependabot[bot]
8494fbec8b chore(deps): bump the minor-patch group across 1 directory with 13 updates (#184)
Bumps the minor-patch group with 13 updates in the /frontend directory:

| Package | From | To |
| --- | --- | --- |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.79.0` | `5.80.5` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `0.511.0` | `0.513.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.56.4` | `7.57.0` |
| [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) | `7.6.1` | `7.6.2` |
| [sonner](https://github.com/emilkowalski/sonner) | `2.0.3` | `2.0.5` |
| [zod](https://github.com/colinhacks/zod) | `3.25.42` | `3.25.51` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.27.0` | `9.28.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.15.27` | `22.15.29` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.1.5` | `19.1.6` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `4.5.0` | `4.5.1` |
| [eslint](https://github.com/eslint/eslint) | `9.27.0` | `9.28.0` |
| [tw-animate-css](https://github.com/Wombosvideo/tw-animate-css) | `1.3.2` | `1.3.4` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.33.0` | `8.33.1` |



Updates `@tanstack/react-query` from 5.79.0 to 5.80.5
- [Release notes](https://github.com/TanStack/query/releases)
- [Commits](https://github.com/TanStack/query/commits/v5.80.5/packages/react-query)

Updates `lucide-react` from 0.511.0 to 0.513.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.513.0/packages/lucide-react)

Updates `react-hook-form` from 7.56.4 to 7.57.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.56.4...v7.57.0)

Updates `react-router` from 7.6.1 to 7.6.2
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/7.6.2/packages/react-router)

Updates `sonner` from 2.0.3 to 2.0.5
- [Release notes](https://github.com/emilkowalski/sonner/releases)
- [Commits](https://github.com/emilkowalski/sonner/compare/v2.0.3...v2.0.5)

Updates `zod` from 3.25.42 to 3.25.51
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v3.25.42...v3.25.51)

Updates `@eslint/js` from 9.27.0 to 9.28.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.28.0/packages/js)

Updates `@types/node` from 22.15.27 to 22.15.29
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@types/react-dom` from 19.1.5 to 19.1.6
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `@vitejs/plugin-react` from 4.5.0 to 4.5.1
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@4.5.1/packages/plugin-react)

Updates `eslint` from 9.27.0 to 9.28.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.27.0...v9.28.0)

Updates `tw-animate-css` from 1.3.2 to 1.3.4
- [Release notes](https://github.com/Wombosvideo/tw-animate-css/releases)
- [Commits](https://github.com/Wombosvideo/tw-animate-css/compare/v1.3.2...v1.3.4)

Updates `typescript-eslint` from 8.33.0 to 8.33.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.33.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.80.5
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: lucide-react
  dependency-version: 0.513.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-hook-form
  dependency-version: 7.57.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-router
  dependency-version: 7.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: sonner
  dependency-version: 2.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: zod
  dependency-version: 3.25.51
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@eslint/js"
  dependency-version: 9.28.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@types/node"
  dependency-version: 22.15.29
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@types/react-dom"
  dependency-version: 19.1.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 4.5.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: eslint
  dependency-version: 9.28.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: tw-animate-css
  dependency-version: 1.3.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: typescript-eslint
  dependency-version: 8.33.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-05 17:35:41 +03:00
dependabot[bot]
4a2aa6ef43 chore(deps): bump github.com/docker/docker in the minor-patch group (#180)
Bumps the minor-patch group with 1 update: [github.com/docker/docker](https://github.com/docker/docker).


Updates `github.com/docker/docker` from 28.2.1+incompatible to 28.2.2+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.2.1...v28.2.2)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-version: 28.2.2+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-05 17:35:15 +03:00
dependabot[bot]
07a5429b6f chore(deps): bump alpine from 3.21 to 3.22 (#179)
Bumps alpine from 3.21 to 3.22.

---
updated-dependencies:
- dependency-name: alpine
  dependency-version: '3.22'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-05 17:34:49 +03:00
Stavros
ad4275bee5 chore: fix typo in readme 2025-06-01 18:03:11 +03:00
Stavros
3665c3a283 chore: update readme 2025-06-01 17:58:27 +03:00
Stavros
38fdb38b92 New Crowdin updates (#139)
* New translations en.json (Spanish)

* New translations en.json (Polish)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Russian)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Afrikaans)

* New translations en.json (Arabic)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Dutch)

* New translations en.json (Norwegian)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (English)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Russian)

* New translations en.json (Danish)

* New translations en.json (Greek)

* New translations en.json (Greek)
2025-06-01 17:23:50 +03:00
Stavros
bc0a38a857 refactor: only use 302 redirects 2025-06-01 17:16:22 +03:00
Stavros
f2c81b6a5c refactor: switch to declarative mode instead of data mode in react router 2025-05-30 18:24:34 +03:00
Stavros
34c8d16c7d fix: fix loading states in forms 2025-05-30 18:14:33 +03:00
dependabot[bot]
75f07a9d7f chore(deps): bump the minor-patch group across 1 directory with 6 updates (#175)
Bumps the minor-patch group with 6 updates in the /frontend directory:

| Package | From | To |
| --- | --- | --- |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.1.7` | `4.1.8` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.77.2` | `5.79.0` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.1.7` | `4.1.8` |
| [zod](https://github.com/colinhacks/zod) | `3.25.32` | `3.25.42` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.15.23` | `22.15.27` |
| [tw-animate-css](https://github.com/Wombosvideo/tw-animate-css) | `1.3.0` | `1.3.2` |



Updates `@tailwindcss/vite` from 4.1.7 to 4.1.8
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.8/packages/@tailwindcss-vite)

Updates `@tanstack/react-query` from 5.77.2 to 5.79.0
- [Release notes](https://github.com/TanStack/query/releases)
- [Commits](https://github.com/TanStack/query/commits/v5.79.0/packages/react-query)

Updates `tailwindcss` from 4.1.7 to 4.1.8
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.8/packages/tailwindcss)

Updates `zod` from 3.25.32 to 3.25.42
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v3.25.32...v3.25.42)

Updates `@types/node` from 22.15.23 to 22.15.27
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `tw-animate-css` from 1.3.0 to 1.3.2
- [Release notes](https://github.com/Wombosvideo/tw-animate-css/releases)
- [Commits](https://github.com/Wombosvideo/tw-animate-css/compare/v1.3.0...v1.3.2)

---
updated-dependencies:
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.79.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: tailwindcss
  dependency-version: 4.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: zod
  dependency-version: 3.25.42
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@types/node"
  dependency-version: 22.15.27
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: tw-animate-css
  dependency-version: 1.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-30 16:07:15 +03:00
dependabot[bot]
74f1c10826 chore(deps): bump github.com/docker/docker in the minor-patch group (#173)
Bumps the minor-patch group with 1 update: [github.com/docker/docker](https://github.com/docker/docker).


Updates `github.com/docker/docker` from 28.1.1+incompatible to 28.2.1+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.1.1...v28.2.1)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-version: 28.2.1+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-30 16:06:53 +03:00
dependabot[bot]
168467d648 chore(deps): bump oven/bun from 1.2.14-alpine to 1.2.15-alpine (#171)
Bumps oven/bun from 1.2.14-alpine to 1.2.15-alpine.

---
updated-dependencies:
- dependency-name: oven/bun
  dependency-version: 1.2.15-alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-30 16:06:29 +03:00
dependabot[bot]
d527900991 chore(deps): bump the minor-patch group in /frontend with 3 updates (#170)
Bumps the minor-patch group in /frontend with 3 updates: [zod](https://github.com/colinhacks/zod), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `zod` from 3.25.30 to 3.25.32
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v3.25.30...v3.25.32)

Updates `@types/node` from 22.15.21 to 22.15.23
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `typescript-eslint` from 8.32.1 to 8.33.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.33.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 3.25.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@types/node"
  dependency-version: 22.15.23
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: typescript-eslint
  dependency-version: 8.33.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-28 17:45:11 +03:00
Stavros
f3acec7daf chore: move build arguments after the from in dockerfile 2025-05-28 17:26:04 +03:00
Stavros
bbaa036d85 chore: use ref name as version in release workflow 2025-05-28 14:17:02 +03:00
Stavros
fc73e25d51 feat: allow generic provider to use untrusted SSL certificates (#164)
* feat: allow generic provider to use untrusted SSL certificates

* chore: fix typo

* chore: bot suggestion

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-05-27 16:42:20 +03:00
dependabot[bot]
e50ffe7907 chore(deps): bump the minor-patch group across 1 directory with 26 updates (#168)
Bumps the minor-patch group with 26 updates in the /frontend directory:

| Package | From | To |
| --- | --- | --- |
| [@radix-ui/react-label](https://github.com/radix-ui/primitives) | `2.1.6` | `2.1.7` |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives) | `2.2.4` | `2.2.5` |
| [@radix-ui/react-separator](https://github.com/radix-ui/primitives) | `1.1.6` | `1.1.7` |
| [@radix-ui/react-slot](https://github.com/radix-ui/primitives) | `1.2.2` | `1.2.3` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.1.5` | `4.1.7` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.75.6` | `5.77.2` |
| [dompurify](https://github.com/cure53/DOMPurify) | `3.2.5` | `3.2.6` |
| [i18next](https://github.com/i18next/i18next) | `25.1.2` | `25.2.1` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `0.503.0` | `0.511.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.56.3` | `7.56.4` |
| [react-i18next](https://github.com/i18next/react-i18next) | `15.5.1` | `15.5.2` |
| [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) | `7.5.3` | `7.6.1` |
| [tailwind-merge](https://github.com/dcastil/tailwind-merge) | `3.2.0` | `3.3.0` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.1.5` | `4.1.7` |
| [zod](https://github.com/colinhacks/zod) | `3.24.4` | `3.25.30` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.26.0` | `9.27.0` |
| [@tanstack/eslint-plugin-query](https://github.com/TanStack/query/tree/HEAD/packages/eslint-plugin-query) | `5.74.7` | `5.78.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.15.16` | `22.15.21` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.1.3` | `19.1.6` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.1.3` | `19.1.5` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `4.4.1` | `4.5.0` |
| [eslint](https://github.com/eslint/eslint) | `9.26.0` | `9.27.0` |
| [globals](https://github.com/sindresorhus/globals) | `16.1.0` | `16.2.0` |
| [tw-animate-css](https://github.com/Wombosvideo/tw-animate-css) | `1.2.9` | `1.3.0` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.7.3` | `5.8.3` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.32.0` | `8.32.1` |



Updates `@radix-ui/react-label` from 2.1.6 to 2.1.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-select` from 2.2.4 to 2.2.5
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-separator` from 1.1.6 to 1.1.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-slot` from 1.2.2 to 1.2.3
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@tailwindcss/vite` from 4.1.5 to 4.1.7
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.7/packages/@tailwindcss-vite)

Updates `@tanstack/react-query` from 5.75.6 to 5.77.2
- [Release notes](https://github.com/TanStack/query/releases)
- [Commits](https://github.com/TanStack/query/commits/v5.77.2/packages/react-query)

Updates `dompurify` from 3.2.5 to 3.2.6
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.2.5...3.2.6)

Updates `i18next` from 25.1.2 to 25.2.1
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v25.1.2...v25.2.1)

Updates `lucide-react` from 0.503.0 to 0.511.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.511.0/packages/lucide-react)

Updates `react-hook-form` from 7.56.3 to 7.56.4
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.56.3...v7.56.4)

Updates `react-i18next` from 15.5.1 to 15.5.2
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v15.5.1...v15.5.2)

Updates `react-router` from 7.5.3 to 7.6.1
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.6.1/packages/react-router)

Updates `tailwind-merge` from 3.2.0 to 3.3.0
- [Release notes](https://github.com/dcastil/tailwind-merge/releases)
- [Commits](https://github.com/dcastil/tailwind-merge/compare/v3.2.0...v3.3.0)

Updates `tailwindcss` from 4.1.5 to 4.1.7
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.7/packages/tailwindcss)

Updates `zod` from 3.24.4 to 3.25.30
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v3.24.4...v3.25.30)

Updates `@eslint/js` from 9.26.0 to 9.27.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.27.0/packages/js)

Updates `@tanstack/eslint-plugin-query` from 5.74.7 to 5.78.0
- [Release notes](https://github.com/TanStack/query/releases)
- [Commits](https://github.com/TanStack/query/commits/v5.78.0/packages/eslint-plugin-query)

Updates `@types/node` from 22.15.16 to 22.15.21
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@types/react` from 19.1.3 to 19.1.6
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@types/react-dom` from 19.1.3 to 19.1.5
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `@vitejs/plugin-react` from 4.4.1 to 4.5.0
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@4.5.0/packages/plugin-react)

Updates `eslint` from 9.26.0 to 9.27.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.26.0...v9.27.0)

Updates `globals` from 16.1.0 to 16.2.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.1.0...v16.2.0)

Updates `tw-animate-css` from 1.2.9 to 1.3.0
- [Release notes](https://github.com/Wombosvideo/tw-animate-css/releases)
- [Commits](https://github.com/Wombosvideo/tw-animate-css/compare/v1.2.9...v1.3.0)

Updates `typescript` from 5.7.3 to 5.8.3
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.7.3...v5.8.3)

Updates `typescript-eslint` from 8.32.0 to 8.32.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.32.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-label"
  dependency-version: 2.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@radix-ui/react-select"
  dependency-version: 2.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@radix-ui/react-separator"
  dependency-version: 1.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@radix-ui/react-slot"
  dependency-version: 1.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.77.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: dompurify
  dependency-version: 3.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: i18next
  dependency-version: 25.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: lucide-react
  dependency-version: 0.511.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-hook-form
  dependency-version: 7.56.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: react-i18next
  dependency-version: 15.5.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: react-router
  dependency-version: 7.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: tailwind-merge
  dependency-version: 3.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: tailwindcss
  dependency-version: 4.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: zod
  dependency-version: 3.25.30
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@eslint/js"
  dependency-version: 9.27.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@tanstack/eslint-plugin-query"
  dependency-version: 5.78.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@types/node"
  dependency-version: 22.15.21
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@types/react"
  dependency-version: 19.1.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@types/react-dom"
  dependency-version: 19.1.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 4.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: eslint
  dependency-version: 9.27.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: globals
  dependency-version: 16.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: tw-animate-css
  dependency-version: 1.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: typescript
  dependency-version: 5.8.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: typescript-eslint
  dependency-version: 8.32.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 16:30:28 +03:00
dependabot[bot]
dda6a8c47f chore(deps): bump oven/bun from 1.2.12-alpine to 1.2.14-alpine (#159)
Bumps oven/bun from 1.2.12-alpine to 1.2.14-alpine.

---
updated-dependencies:
- dependency-name: oven/bun
  dependency-version: 1.2.14-alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 16:30:01 +03:00
dependabot[bot]
eba2df3dd9 chore(deps): bump github.com/gin-gonic/gin in the minor-patch group (#156)
Bumps the minor-patch group with 1 update: [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin).


Updates `github.com/gin-gonic/gin` from 1.10.0 to 1.10.1
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.10.0...v1.10.1)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-version: 1.10.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 16:29:42 +03:00
Stavros
11731f78d1 chore: update screenshot 2025-05-27 16:27:25 +03:00
Stavros
3a7b71ae3e feat: generate a unique id for the cookie names based on the domain (#161)
* feat: generate a unique id for the cookie names based on the domain

* tests: fix tests
2025-05-25 12:38:21 +03:00
79 changed files with 2818 additions and 1774 deletions

View File

@@ -30,3 +30,4 @@ APP_TITLE=Tinyauth SSO
FORGOT_PASSWORD_MESSAGE=Some message about resetting the password
OAUTH_AUTO_REDIRECT=none
BACKGROUND_IMAGE=some_image_url
GENERIC_SKIP_SSL=false

View File

@@ -39,4 +39,9 @@ jobs:
cp -r frontend/dist internal/assets/dist
- name: Run tests
run: go test -v ./...
run: go test -coverprofile=coverage.txt -v ./...
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -1,6 +1,8 @@
name: Nightly Release
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
create-release:
@@ -79,6 +81,8 @@ jobs:
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
env:
CGO_ENABLED: 0
- name: Upload artifact
uses: actions/upload-artifact@v4
@@ -123,6 +127,8 @@ jobs:
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
env:
CGO_ENABLED: 0
- name: Upload artifact
uses: actions/upload-artifact@v4

View File

@@ -21,7 +21,7 @@ jobs:
- name: Generate metadata
id: metadata
run: |
echo "VERSION=nightly" >> "$GITHUB_OUTPUT"
echo "VERSION=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT"
@@ -59,6 +59,8 @@ jobs:
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
env:
CGO_ENABLED: 0
- name: Upload artifact
uses: actions/upload-artifact@v4
@@ -100,6 +102,8 @@ jobs:
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
env:
CGO_ENABLED: 0
- name: Upload artifact
uses: actions/upload-artifact@v4

6
.gitignore vendored
View File

@@ -11,11 +11,7 @@ docker-compose.test*
users.txt
# secret test file
secret.txt
secret_oauth.txt
# vscode
.vscode
secret*
# apple stuff
.DS_Store

15
.vscode/launch.json vendored Normal file
View File

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

View File

@@ -1,10 +1,5 @@
# Arguments
ARG VERSION
ARG COMMIT_HASH
ARG BUILD_TIMESTAMP
# Site builder
FROM oven/bun:1.2.12-alpine AS frontend-builder
FROM oven/bun:1.2.18-alpine AS frontend-builder
WORKDIR /frontend
@@ -27,6 +22,10 @@ RUN bun run build
# Builder
FROM golang:1.24-alpine3.21 AS builder
ARG VERSION
ARG COMMIT_HASH
ARG BUILD_TIMESTAMP
WORKDIR /tinyauth
COPY go.mod ./
@@ -39,10 +38,10 @@ COPY ./cmd ./cmd
COPY ./internal ./internal
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 -X tinyauth/internal/constants.Version=${VERSION} -X tinyauth/internal/constants.CommitHash=${COMMIT_HASH} -X tinyauth/internal/constants.BuildTimestamp=${BUILD_TIMESTAMP}"
# Runner
FROM alpine:3.21 AS runner
FROM alpine:3.22 AS runner
WORKDIR /tinyauth

View File

@@ -14,39 +14,36 @@
<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 a simple login screen or OAuth with Google, Github and any provider to all of your docker apps. It supports all the popular proxies like Traefik, Nginx and Caddy.
![Screenshot](assets/screenshot.png)
> [!WARNING]
> Tinyauth is in active development and configuration may change often. Please make sure to carefully read the release notes before updating.
> [!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.
## 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 in the [documentation](https://tinyauth.app/docs/getting-started.html). There is also an available [docker compose](./docker-compose.example.yml) file that has Traefik, Whoami and Tinyauth to demonstrate its capabilities.
## Demo
If you are still not sure if tinyauth suits your needs you can try out the [demo](https://demo.tinyauth.app). The default username is `user` and the default password is `password`.
If you are still not sure if Tinyauth suits your needs you can try out the [demo](https://demo.tinyauth.app). The default username is `user` and the default password is `password`.
## Documentation
You can find documentation and guides on all of the available configuration of tinyauth in the [website](https://tinyauth.app).
You can find documentation and guides on all of the available configuration of Tinyauth in the [website](https://tinyauth.app).
## 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).
Tinyauth has a [discord](https://discord.gg/eHzVaCzRRd) server. Feel free to hop in to chat about self-hosting, homelabs and of course Tinyauth. See you there!
## Contributing
All contributions to the codebase are welcome! If you have any recommendations on how to improve security or find a security issue in tinyauth please open an issue or pull request so it can be fixed as soon as possible!
All contributions to the codebase are welcome! If you have any free time feel free to pick up an [issue](https://github.com/steveiliop56/tinyauth/issues) or add your own missing features. Make sure to check out the [contributing guide](./CONTRIBUTING.md) for instructions on how to get the development server up and running.
## Localization
If you would like to help translating the project in more languages you can do so by visiting the [Crowdin](https://crowdin.com/project/tinyauth) page.
If you would like to help translate Tinyauth into more languages, visit the [Crowdin](https://crowdin.com/project/tinyauth) page.
## License
@@ -54,18 +51,16 @@ Tinyauth is licensed under the GNU General Public License v3.0. TL;DR — You ma
## Sponsors
Thanks a lot to the following people for providing me with more coffee:
A big thank you to the following people for providing me with more coffee:
<!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https:&#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 -->
<!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https:&#x2F;&#x2F;github.com&#x2F;erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a>&nbsp;&nbsp;<a href="https://github.com/nicotsx"><img src="https:&#x2F;&#x2F;github.com&#x2F;nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a>&nbsp;&nbsp;<a href="https://github.com/SimpleHomelab"><img src="https:&#x2F;&#x2F;github.com&#x2F;SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a>&nbsp;&nbsp;<a href="https://github.com/jmadden91"><img src="https:&#x2F;&#x2F;github.com&#x2F;jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a>&nbsp;&nbsp;<a href="https://github.com/tribor"><img src="https:&#x2F;&#x2F;github.com&#x2F;tribor.png" width="64px" alt="User avatar: tribor" /></a>&nbsp;&nbsp;<a href="https://github.com/eliasbenb"><img src="https:&#x2F;&#x2F;github.com&#x2F;eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a>&nbsp;&nbsp;<a href="https://github.com/afunworm"><img src="https:&#x2F;&#x2F;github.com&#x2F;afunworm.png" width="64px" alt="User avatar: afunworm" /></a>&nbsp;&nbsp;<!-- sponsors -->
## Acknowledgements
Credits for the logo of this app go to:
- **Freepik** for providing the police hat and badge.
- **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.
- **Syrhu** for providing the background image of the app.
## Star History

View File

@@ -2,8 +2,8 @@
## Supported Versions
Please always use the latest available Tinyauth version which can be found [here](https://github.com/steveiliop56/tinyauth/releases/latest). Older versions (especially major) may contain security issues which I cannot go back and fix.
It is recommended to use the [latest](https://github.com/steveiliop56/tinyauth/releases/latest) available version of tinyauth. This is because it includes security fixes, new features and dependency updates. Older versions, especially major ones, are not supported and won't receive security or patch updates.
## Reporting a Vulnerability
Due to the nature of this app, it needs to be secure. If you find any security issues in the OAuth or login flow of the app please contact me at <steve@doesmycode.work> and include a concise description of the issue. Please do not use the issues section for reporting major security issues.
Due to the nature of this app, it needs to be secure. If you discover any security issues or vulnerabilities in the app please contact me as soon as possible at <steve@doesmycode.work>. Please do not use the issues section to report security issues as I won't be able to patch them in time and they may get exploited by malicious actors.

View File

@@ -2,9 +2,9 @@ root = "/tinyauth"
tmp_dir = "tmp"
[build]
pre_cmd = ["mkdir -p internal/assets/dist", "echo 'backend running' > internal/assets/dist/index.html"]
cmd = "go build -o ./tmp/tinyauth ."
bin = "tmp/tinyauth"
pre_cmd = ["mkdir -p internal/assets/dist", "echo 'backend running' > internal/assets/dist/index.html", "go install github.com/go-delve/delve/cmd/dlv@v1.25.0"]
cmd = "CGO_ENABLED=0 go build -gcflags=\"all=-N -l\" -o tmp/tinyauth ."
bin = "/go/bin/dlv --listen :4000 --headless=true --api-version=2 --accept-multiclient --log=true exec tmp/tinyauth --continue"
include_ext = ["go"]
exclude_dir = ["internal/assets/dist"]
exclude_regex = [".*_test\\.go"]

View File

@@ -3,7 +3,7 @@
"embeds": [
{
"title": "Welcome to Tinyauth Discord!",
"description": "Tinyauth is a simple authentication middleware that adds simple username/password login or OAuth with Google, Github and any generic OAuth provider to all of your docker apps.\n\n**Information**\n\n• Github: <https://github.com/steveiliop56/tinyauth>\n• Website: <https://tinyauth.app>",
"description": "Tinyauth is a simple authentication middleware that adds a simple login screen or OAuth with Google, Github and any provider to all of your docker apps. It supports all the popular proxies like Traefik, Nginx and Caddy.\n\n**Information**\n\n• Github: <https://github.com/steveiliop56/tinyauth>\n• Website: <https://tinyauth.app>",
"url": "https://tinyauth.app",
"color": 7002085,
"author": {
@@ -12,9 +12,9 @@
"footer": {
"text": "Updated at"
},
"timestamp": "2025-03-10T19:00:00.000Z",
"timestamp": "2025-06-06T12:25:27.629Z",
"thumbnail": {
"url": "https://github.com/steveiliop56/tinyauth/blob/main/frontend/public/logo.png?raw=true"
"url": "https://github.com/steveiliop56/tinyauth/blob/main/assets/logo.png?raw=true"
}
}
],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

After

Width:  |  Height:  |  Size: 4.5 MiB

View File

@@ -2,18 +2,20 @@ package cmd
import (
"errors"
"fmt"
"os"
"strings"
"time"
totpCmd "tinyauth/cmd/totp"
userCmd "tinyauth/cmd/user"
"tinyauth/internal/api"
"tinyauth/internal/auth"
"tinyauth/internal/constants"
"tinyauth/internal/docker"
"tinyauth/internal/handlers"
"tinyauth/internal/hooks"
"tinyauth/internal/ldap"
"tinyauth/internal/providers"
"tinyauth/internal/server"
"tinyauth/internal/types"
"tinyauth/internal/utils"
@@ -57,16 +59,27 @@ var rootCmd = &cobra.Command{
users, err := utils.GetUsers(config.Users, config.UsersFile)
HandleError(err, "Failed to parse users")
if len(users) == 0 && !utils.OAuthConfigured(config) {
HandleError(errors.New("no users or OAuth configured"), "No users or OAuth configured")
}
// Get domain
log.Debug().Msg("Getting domain")
domain, err := utils.GetUpperDomain(config.AppURL)
HandleError(err, "Failed to get upper domain")
log.Info().Str("domain", domain).Msg("Using domain for cookie store")
// Generate cookie name
cookieId := utils.GenerateIdentifier(strings.Split(domain, ".")[0])
sessionCookieName := fmt.Sprintf("%s-%s", constants.SessionCookieName, cookieId)
csrfCookieName := fmt.Sprintf("%s-%s", constants.CsrfCookieName, cookieId)
redirectCookieName := fmt.Sprintf("%s-%s", constants.RedirectCookieName, cookieId)
// Generate HMAC and encryption secrets
log.Debug().Msg("Deriving HMAC and encryption secrets")
hmacSecret, err := utils.DeriveKey(config.Secret, "hmac")
HandleError(err, "Failed to derive HMAC secret")
encryptionSecret, err := utils.DeriveKey(config.Secret, "encryption")
HandleError(err, "Failed to derive encryption secret")
// Create OAuth config
oauthConfig := types.OAuthConfig{
GithubClientId: config.GithubClientId,
@@ -79,6 +92,7 @@ var rootCmd = &cobra.Command{
GenericAuthURL: config.GenericAuthURL,
GenericTokenURL: config.GenericTokenURL,
GenericUserURL: config.GenericUserURL,
GenericSkipSSL: config.GenericSkipSSL,
AppURL: config.AppURL,
}
@@ -93,24 +107,28 @@ var rootCmd = &cobra.Command{
ForgotPasswordMessage: config.FogotPasswordMessage,
BackgroundImage: config.BackgroundImage,
OAuthAutoRedirect: config.OAuthAutoRedirect,
CsrfCookieName: csrfCookieName,
RedirectCookieName: redirectCookieName,
}
// Create api config
apiConfig := types.APIConfig{
// Create server config
serverConfig := types.ServerConfig{
Port: config.Port,
Address: config.Address,
}
// Create auth config
authConfig := types.AuthConfig{
Users: users,
OauthWhitelist: config.OAuthWhitelist,
Secret: config.Secret,
CookieSecure: config.CookieSecure,
SessionExpiry: config.SessionExpiry,
Domain: domain,
LoginTimeout: config.LoginTimeout,
LoginMaxRetries: config.LoginMaxRetries,
Users: users,
OauthWhitelist: config.OAuthWhitelist,
CookieSecure: config.CookieSecure,
SessionExpiry: config.SessionExpiry,
Domain: domain,
LoginTimeout: config.LoginTimeout,
LoginMaxRetries: config.LoginMaxRetries,
SessionCookieName: sessionCookieName,
HMACSecret: hmacSecret,
EncryptionSecret: encryptionSecret,
}
// Create hooks config
@@ -119,36 +137,55 @@ var rootCmd = &cobra.Command{
}
// Create docker service
docker := docker.NewDocker()
// Initialize docker
err = docker.Init()
docker, err := docker.NewDocker()
HandleError(err, "Failed to initialize docker")
// Create LDAP service if configured
var ldapService *ldap.LDAP
if config.LdapAddress != "" {
log.Info().Msg("Using LDAP for authentication")
ldapConfig := types.LdapConfig{
Address: config.LdapAddress,
BindDN: config.LdapBindDN,
BindPassword: config.LdapBindPassword,
BaseDN: config.LdapBaseDN,
Insecure: config.LdapInsecure,
SearchFilter: config.LdapSearchFilter,
}
// Create LDAP service
ldapService, err = ldap.NewLDAP(ldapConfig)
HandleError(err, "Failed to create LDAP service")
} else {
log.Info().Msg("LDAP not configured, using local users or OAuth")
}
// Check if we have any users configured
if len(users) == 0 && !utils.OAuthConfigured(config) && ldapService == nil {
HandleError(errors.New("err no users"), "Unable to find a source of users")
}
// Create auth service
auth := auth.NewAuth(authConfig, docker)
auth := auth.NewAuth(authConfig, docker, ldapService)
// Create OAuth providers service
providers := providers.NewProviders(oauthConfig)
// Initialize providers
providers.Init()
// Create hooks service
hooks := hooks.NewHooks(hooksConfig, auth, providers)
// Create handlers
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
// Create API
api := api.NewAPI(apiConfig, handlers)
// Create server
srv, err := server.NewServer(serverConfig, handlers)
HandleError(err, "Failed to create server")
// Setup routes
api.Init()
api.SetupRoutes()
// Start
api.Run()
// Start server
err = srv.Start()
HandleError(err, "Failed to start server")
},
}
@@ -197,6 +234,7 @@ func init() {
rootCmd.Flags().String("generic-token-url", "", "Generic OAuth token URL.")
rootCmd.Flags().String("generic-user-url", "", "Generic OAuth user info URL.")
rootCmd.Flags().String("generic-name", "Generic", "Generic OAuth provider name.")
rootCmd.Flags().Bool("generic-skip-ssl", false, "Skip SSL verification for the generic OAuth provider.")
rootCmd.Flags().Bool("disable-continue", false, "Disable continue screen and redirect to app directly.")
rootCmd.Flags().String("oauth-whitelist", "", "Comma separated list of email addresses to whitelist when using OAuth.")
rootCmd.Flags().String("oauth-auto-redirect", "none", "Auto redirect to the specified OAuth provider if configured. (available providers: github, google, generic)")
@@ -205,8 +243,14 @@ func init() {
rootCmd.Flags().Int("login-max-retries", 5, "Maximum login attempts before timeout (0 to disable).")
rootCmd.Flags().Int("log-level", 1, "Log level.")
rootCmd.Flags().String("app-title", "Tinyauth", "Title of the app.")
rootCmd.Flags().String("forgot-password-message", "You can reset your password by changing the `USERS` environment variable.", "Message to show on the forgot password page.")
rootCmd.Flags().String("forgot-password-message", "", "Message to show on the forgot password page.")
rootCmd.Flags().String("background-image", "/background.jpg", "Background image URL for the login page.")
rootCmd.Flags().String("ldap-address", "", "LDAP server address (e.g. ldap://localhost:389).")
rootCmd.Flags().String("ldap-bind-dn", "", "LDAP bind DN (e.g. uid=user,dc=example,dc=com).")
rootCmd.Flags().String("ldap-bind-password", "", "LDAP bind password.")
rootCmd.Flags().String("ldap-base-dn", "", "LDAP base DN (e.g. dc=example,dc=com).")
rootCmd.Flags().Bool("ldap-insecure", false, "Skip certificate verification for the LDAP server.")
rootCmd.Flags().String("ldap-search-filter", "(uid=%s)", "LDAP search filter for user lookup.")
// Bind flags to environment
viper.BindEnv("port", "PORT")
@@ -231,6 +275,7 @@ func init() {
viper.BindEnv("generic-token-url", "GENERIC_TOKEN_URL")
viper.BindEnv("generic-user-url", "GENERIC_USER_URL")
viper.BindEnv("generic-name", "GENERIC_NAME")
viper.BindEnv("generic-skip-ssl", "GENERIC_SKIP_SSL")
viper.BindEnv("disable-continue", "DISABLE_CONTINUE")
viper.BindEnv("oauth-whitelist", "OAUTH_WHITELIST")
viper.BindEnv("oauth-auto-redirect", "OAUTH_AUTO_REDIRECT")
@@ -241,6 +286,12 @@ func init() {
viper.BindEnv("login-max-retries", "LOGIN_MAX_RETRIES")
viper.BindEnv("forgot-password-message", "FORGOT_PASSWORD_MESSAGE")
viper.BindEnv("background-image", "BACKGROUND_IMAGE")
viper.BindEnv("ldap-address", "LDAP_ADDRESS")
viper.BindEnv("ldap-bind-dn", "LDAP_BIND_DN")
viper.BindEnv("ldap-bind-password", "LDAP_BIND_PASSWORD")
viper.BindEnv("ldap-base-dn", "LDAP_BASE_DN")
viper.BindEnv("ldap-insecure", "LDAP_INSECURE")
viper.BindEnv("ldap-search-filter", "LDAP_SEARCH_FILTER")
// Bind flags to viper
viper.BindPFlags(rootCmd.Flags())

View File

@@ -42,6 +42,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 3000:3000
- 4000:4000
labels:
traefik.enable: true
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth-backend:3000/api/auth/traefik

View File

@@ -1,4 +1,4 @@
FROM oven/bun:1.1.45-alpine
FROM oven/bun:1.2.16-alpine
WORKDIR /frontend

File diff suppressed because it is too large Load Diff

View File

@@ -10,49 +10,49 @@
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^5.0.1",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-select": "^2.2.2",
"@radix-ui/react-separator": "^1.1.4",
"@radix-ui/react-slot": "^1.2.2",
"@tailwindcss/vite": "^4.1.4",
"@tanstack/react-query": "^5.75.6",
"axios": "^1.9.0",
"@hookform/resolvers": "^5.1.1",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/vite": "^4.1.11",
"@tanstack/react-query": "^5.82.0",
"axios": "^1.10.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dompurify": "^3.2.5",
"i18next": "^25.0.2",
"i18next-browser-languagedetector": "^8.0.5",
"dompurify": "^3.2.6",
"i18next": "^25.3.2",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-resources-to-backend": "^1.2.1",
"input-otp": "^1.4.2",
"lucide-react": "^0.503.0",
"lucide-react": "^0.525.0",
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.56.3",
"react-i18next": "^15.5.1",
"react-hook-form": "^7.60.0",
"react-i18next": "^15.6.0",
"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"
"react-router": "^7.6.3",
"sonner": "^2.0.6",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.11",
"zod": "^4.0.2"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@tanstack/eslint-plugin-query": "^5.74.7",
"@types/node": "^22.15.3",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.22.0",
"@eslint/js": "^9.30.1",
"@tanstack/eslint-plugin-query": "^5.81.2",
"@types/node": "^24.0.13",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
"eslint": "^9.30.1",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
"prettier": "3.5.3",
"tw-animate-css": "^1.2.8",
"typescript": "~5.7.2",
"typescript-eslint": "^8.26.1",
"vite": "^6.3.1"
"globals": "^16.3.0",
"prettier": "3.6.2",
"tw-animate-css": "^1.3.5",
"typescript": "~5.8.3",
"typescript-eslint": "^8.36.0",
"vite": "^7.0.4"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 588 KiB

After

Width:  |  Height:  |  Size: 530 KiB

View File

@@ -33,9 +33,9 @@ export const LoginForm = (props: Props) => {
control={form.control}
name="username"
render={({ field }) => (
<FormItem className="mb-4">
<FormLabel>{t("loginUsername")}</FormLabel>
<FormControl>
<FormItem className="mb-4 gap-0">
<FormLabel className="mb-2">{t("loginUsername")}</FormLabel>
<FormControl className="mb-1">
<Input
placeholder={t("loginUsername")}
disabled={loading}
@@ -50,8 +50,8 @@ export const LoginForm = (props: Props) => {
control={form.control}
name="password"
render={({ field }) => (
<FormItem className="mb-4">
<div className="relative">
<FormItem className="mb-4 gap-0">
<div className="relative mb-1">
<FormLabel className="mb-2">{t("loginPassword")}</FormLabel>
<FormControl>
<Input
@@ -61,14 +61,14 @@ export const LoginForm = (props: Props) => {
{...field}
/>
</FormControl>
<FormMessage />
<a
href="/forgot-password"
className="text-muted-foreground text-sm absolute right-0 bottom-10"
className="text-muted-foreground text-sm absolute right-0 bottom-[2.565rem]" // 2.565 is *just* perfect
>
{t("forgotPasswordTitle")}
</a>
</div>
<FormMessage />
</FormItem>
)}
/>

View File

@@ -1,7 +1,8 @@
import { useAppContext } from "@/context/app-context";
import { LanguageSelector } from "../language/language";
import { Outlet } from "react-router";
export const Layout = ({ children }: { children: React.ReactNode }) => {
export const Layout = () => {
const { backgroundImage } = useAppContext();
return (
@@ -14,7 +15,7 @@ export const Layout = ({ children }: { children: React.ReactNode }) => {
}}
>
<LanguageSelector />
{children}
<Outlet />
</div>
);
};

View File

@@ -136,7 +136,7 @@ h4 {
}
p {
@apply leading-7 [&:not(:first-child)]:mt-6;
@apply leading-6;
}
blockquote {

View File

@@ -27,8 +27,8 @@ export const languages = {
"tr-TR": "Türkçe",
"uk-UA": "Українська",
"vi-VN": "Tiếng Việt",
"zh-CN": "中文",
"zh-TW": "中文",
"zh-CN": "简体中文",
"zh-TW": "繁體中文(台灣)",
};
export type SupportedLanguage = keyof typeof languages;

View File

@@ -1,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,17 @@
{
"loginTitle": "مرحبا بعودتك، قم بتسجيل الدخول باستخدام",
"loginDivider": "أو المتابعة بكلمة المرور",
"loginTitle": "مرحبا بعودتك، ادخل باستخدام",
"loginTitleSimple": "مرحبا بعودتك، سجل دخولك",
"loginDivider": "أو",
"loginUsername": "اسم المستخدم",
"loginPassword": "كلمة المرور",
"loginSubmit": "تسجيل الدخول",
"loginFailTitle": "فشل تسجيل الدخول",
"loginFailSubtitle": "الرجاء التحقق من اسم المستخدم وكلمة المرور",
"loginFailRateLimit": "فشلت في تسجيل الدخول عدة مرات، الرجاء المحاولة مرة أخرى لاحقا",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "تم تسجيل الدخول",
"loginSuccessSubtitle": "مرحبا بعودتك!",
"loginOauthFailTitle": "خطأ داخلي",
"loginOauthFailSubtitle": "فشل في الحصول على رابط OAuth",
"loginOauthFailTitle": "حدث خطأ",
"loginOauthFailSubtitle": "أخفق الحصول على رابط OAuth",
"loginOauthSuccessTitle": "إعادة توجيه",
"loginOauthSuccessSubtitle": "إعادة توجيه إلى مزود OAuth الخاص بك",
"continueRedirectingTitle": "إعادة توجيه...",
@@ -18,34 +19,36 @@
"continueInvalidRedirectTitle": "إعادة توجيه غير صالحة",
"continueInvalidRedirectSubtitle": "رابط إعادة التوجيه غير صالح",
"continueInsecureRedirectTitle": "إعادة توجيه غير آمنة",
"continueInsecureRedirectSubtitle": "أنت تحاول إعادة التوجيه من <Code>https</Code> إلى <Code>http</Code>، هل أنت متأكد أنك تريد المتابعة؟",
"continueInsecureRedirectSubtitle": "أنت تحاول إعادة التوجيه من <code>https</code> إلى <code>http</code>، هل أنت متأكد أنك تريد المتابعة؟",
"continueTitle": "متابعة",
"continueSubtitle": "انقر الزر للمتابعة إلى التطبيق الخاص بك.",
"internalErrorTitle": "خطأ داخلي في الخادم",
"internalErrorSubtitle": "حدث خطأ على الخادم ولا يمكن حاليا تلبية طلبك.",
"internalErrorButton": "حاول مجددا",
"logoutFailTitle": "فشل تسجيل الخروج",
"logoutFailSubtitle": "يرجى إعادة المحاولة",
"logoutSuccessTitle": "تم تسجيل الخروج",
"logoutSuccessSubtitle": "تم تسجيل خروجك",
"logoutTitle": "تسجيل الخروج",
"logoutUsernameSubtitle": "أنت حاليا مسجل الدخول ك <Code>{{username}}</Code>، انقر الزر أدناه لتسجيل الخروج.",
"logoutOauthSubtitle": "أنت حاليا مسجل الدخول ك <Code>{{username}}</Code> باستخدام مزود OAuth {{provider}} ، انقر الزر أدناه لتسجيل الخروج.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "الصفحة غير موجودة",
"notFoundSubtitle": "الصفحة التي تبحث عنها غير موجودة.",
"notFoundButton": "انتقل إلى الرئيسية",
"totpFailTitle": "فشل في التحقق من الرمز",
"totpFailTitle": "أخفق التحقق من الرمز",
"totpFailSubtitle": "الرجاء التحقق من الرمز الخاص بك وحاول مرة أخرى",
"totpSuccessTitle": "تم التحقق",
"totpSuccessSubtitle": "إعادة توجيه إلى تطبيقك",
"totpTitle": "أدخل رمز TOTP الخاص بك",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "غير مرخص",
"unauthorizedResourceSubtitle": "المستخدم الذي يحمل اسم المستخدم <Code>{{username}}</Code> غير مصرح له بالوصول إلى المورد <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "حاول مجددا",
"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?",
"untrustedRedirectTitle": "إعادة توجيه غير موثوقة",
"untrustedRedirectSubtitle": "أنت تحاول إعادة التوجيه إلى نطاق لا يتطابق مع النطاق المكون الخاص بك (<code>{{domain}}</code>). هل أنت متأكد من أنك تريد المتابعة؟",
"cancelTitle": "إلغاء",
"forgotPasswordTitle": "Forgot your password?"
"forgotPasswordTitle": "نسيت كلمة المرور؟",
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
"errorTitle": "حدث خطأ",
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information."
}

View File

@@ -1,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,51 +1,54 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
"continueRedirectingTitle": "Redirecting...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
"loginTitle": "Velkommen tilbage, log ind med",
"loginTitleSimple": "Velkommen tilbage, log venligst ind",
"loginDivider": "Eller",
"loginUsername": "Brugernavn",
"loginPassword": "Adgangskode",
"loginSubmit": "Log ind",
"loginFailTitle": "Login mislykkedes",
"loginFailSubtitle": "Tjek venligst dit brugernavn og adgangskode",
"loginFailRateLimit": "Du har forsøgt at logge ind for mange gange, prøv igen senere",
"loginSuccessTitle": "Logget ind",
"loginSuccessSubtitle": "Velkommen tilbage!",
"loginOauthFailTitle": "Der opstod en fejl",
"loginOauthFailSubtitle": "Kunne ikke hente OAuth-URL",
"loginOauthSuccessTitle": "Omdirigerer",
"loginOauthSuccessSubtitle": "Omdirigerer til din OAuth-udbyder",
"continueRedirectingTitle": "Omdirigerer...",
"continueRedirectingSubtitle": "Du bør blive omdirigeret til appen snart",
"continueInvalidRedirectTitle": "Ugyldig omdirigering",
"continueInvalidRedirectSubtitle": "Omdirigerings-URL'en er ugyldig",
"continueInsecureRedirectTitle": "Usikker omdirigering",
"continueInsecureRedirectSubtitle": "Du forsøger at omdirigere fra <code>https</code> til <code>http</code>, som ikke er sikker. Er du sikker på, at du vil fortsætte?",
"continueTitle": "Fortsæt",
"continueSubtitle": "Klik på knappen for at fortsætte til din app.",
"logoutFailTitle": "Log ud mislykkedes",
"logoutFailSubtitle": "Prøv venligst igen",
"logoutSuccessTitle": "Logget ud",
"logoutSuccessSubtitle": "Du er blevet logget ud",
"logoutTitle": "Log ud",
"logoutUsernameSubtitle": "Du er i øjeblikket logget ind som <code>{{username}}</code>. Klik på knappen nedenfor for at logge ud.",
"logoutOauthSubtitle": "Du er i øjeblikket logget ind som <code>{{username}}</code> via {{provider}} OAuth-udbyderen. Klik på knappen nedenfor for at logge ud.",
"notFoundTitle": "Siden blev ikke fundet",
"notFoundSubtitle": "Siden du leder efter, findes ikke.",
"notFoundButton": "Gå til forsiden",
"totpFailTitle": "Verificering af kode mislykkedes",
"totpFailSubtitle": "Tjek venligst din kode og prøv igen",
"totpSuccessTitle": "Verificeret",
"totpSuccessSubtitle": "Omdirigerer til din app",
"totpTitle": "Indtast din TOTP-kode",
"totpSubtitle": "Indtast venligst koden fra din to-faktor-godkendelsesapp.",
"unauthorizedTitle": "Uautoriseret",
"unauthorizedResourceSubtitle": "Brugeren med brugernavnet <code>{{username}}</code> har ikke tilladelse til at tilgå ressourcen <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "Brugeren med brugernavnet <code>{{username}}</code> har ikke tilladelse til at logge ind.",
"unauthorizedGroupsSubtitle": "Brugeren med brugernavnet <code>{{username}}</code> er ikke i de grupper, som ressourcen <code>{{resource}}</code> kræver.",
"unauthorizedIpSubtitle": "Din IP adresse <code>{{ip}}</code> er ikke autoriseret til at tilgå ressourcen <code>{{resource}}</code>.",
"unauthorizedButton": "Prøv igen",
"untrustedRedirectTitle": "Usikker omdirigering",
"untrustedRedirectSubtitle": "Du forsøger at omdirigere til et domæne, der ikke matcher dit konfigurerede domæne (<code>{{domain}}</code>). Er du sikker på, at du vil fortsætte?",
"cancelTitle": "Annuller",
"forgotPasswordTitle": "Glemt din adgangskode?",
"failedToFetchProvidersTitle": "Kunne ikke indlæse godkendelsesudbydere. Tjek venligst din konfiguration.",
"errorTitle": "Der opstod en fejl",
"errorSubtitle": "Der opstod en fejl under forsøget på at udføre denne handling. Tjek venligst konsollen for mere information."
}

View File

@@ -1,15 +1,16 @@
{
"loginTitle": "Willkommen zurück, logge dich ein mit",
"loginDivider": "Oder mit Passwort fortfahren",
"loginTitleSimple": "Willkommen zurück, bitte anmelden",
"loginDivider": "Oder",
"loginUsername": "Benutzername",
"loginPassword": "Passwort",
"loginSubmit": "Anmelden",
"loginFailTitle": "Login fehlgeschlagen",
"loginFailSubtitle": "Bitte überprüfe deinen Benutzernamen und Passwort",
"loginFailRateLimit": "Sie konnten sich zu oft nicht einloggen, bitte versuchen Sie es später erneut",
"loginFailRateLimit": "Zu viele fehlgeschlagene Loginversuche. Versuche es später erneut",
"loginSuccessTitle": "Angemeldet",
"loginSuccessSubtitle": "Willkommen zurück!",
"loginOauthFailTitle": "Interner Fehler",
"loginOauthFailTitle": "Ein Fehler ist aufgetreten",
"loginOauthFailSubtitle": "Fehler beim Abrufen der OAuth-URL",
"loginOauthSuccessTitle": "Leite weiter",
"loginOauthSuccessSubtitle": "Weiterleitung zu Ihrem OAuth-Provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Ungültige Weiterleitung",
"continueInvalidRedirectSubtitle": "Die Weiterleitungs-URL ist ungültig",
"continueInsecureRedirectTitle": "Unsichere Weiterleitung",
"continueInsecureRedirectSubtitle": "Sie versuchen von <Code>https</Code> auf <Code>http</Code>weiterzuleiten. Sind Sie sicher, dass Sie fortfahren möchten?",
"continueInsecureRedirectSubtitle": "Sie versuchen von <code>https</code> auf <code>http</code> weiterzuleiten, was unsicher ist. Sind Sie sicher, dass Sie fortfahren möchten?",
"continueTitle": "Weiter",
"continueSubtitle": "Klicken Sie auf den Button, um zur App zu gelangen.",
"internalErrorTitle": "Interner Serverfehler",
"internalErrorSubtitle": "Ein Error ist auf dem Server aufgetreten, weshalb ihre Anfrage derzeit nicht bearbeitet werden kann.",
"internalErrorButton": "Erneut versuchen",
"logoutFailTitle": "Abmelden fehlgeschlagen",
"logoutFailSubtitle": "Bitte versuchen Sie es erneut",
"logoutSuccessTitle": "Abgemeldet",
"logoutSuccessSubtitle": "Sie wurden abgemeldet",
"logoutTitle": "Abmelden",
"logoutUsernameSubtitle": "Sie sind derzeit als <Code>{{username}}</Code>angemeldet. Klicken Sie auf den Button unten, um sich abzumelden.",
"logoutOauthSubtitle": "Sie sind derzeit als <Code>{{username}}</Code> mit dem {{provider}} OAuth-Anbieter angemeldet. Klicken Sie auf den Button unten, um sich abzumelden.",
"logoutUsernameSubtitle": "Sie sind derzeit als <code>{{username}}</code> angemeldet. Klicken Sie auf den Button unten, um sich abzumelden.",
"logoutOauthSubtitle": "Sie sind derzeit als <code>{{username}}</code> über den OAuth-Anbieter {{provider}} angemeldet. Klicken Sie auf den Button unten, um sich abzumelden.",
"notFoundTitle": "Seite nicht gefunden",
"notFoundSubtitle": "Die gesuchte Seite existiert nicht.",
"notFoundButton": "Nach Hause",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verifiziert",
"totpSuccessSubtitle": "Leite zur App weiter",
"totpTitle": "Geben Sie Ihren TOTP Code ein",
"totpSubtitle": "Bitte geben Sie den Code aus Ihrer Authenticator-App ein.",
"unauthorizedTitle": "Unautorisiert",
"unauthorizedResourceSubtitle": "Der Benutzer mit Benutzername <Code>{{username}}</Code> ist nicht berechtigt auf die Ressource <Code>{{resource}}</Code> zuzugreifen.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "Der Benutzer mit Benutzername <code>{{username}}</code> ist nicht berechtigt, auf die Ressource <code>{{resource}}</code> zuzugreifen.",
"unauthorizedLoginSubtitle": "Der Benutzer mit Benutzername <code>{{username}}</code> ist nicht berechtigt, sich anzumelden.",
"unauthorizedGroupsSubtitle": "Der Benutzer mit Benutzername <code>{{username}}</code> ist nicht in den Gruppen, die von der Ressource <code>{{resource}}</code> benötigt werden.",
"unauthorizedIpSubtitle": "Ihre IP-Adresse <code>{{ip}}</code> ist nicht berechtigt, auf die Ressource <code>{{resource}}</code> zuzugreifen.",
"unauthorizedButton": "Erneut versuchen",
"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?",
"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?"
"forgotPasswordTitle": "Passwort vergessen?",
"failedToFetchProvidersTitle": "Fehler beim Laden der Authentifizierungsanbieter. Bitte überprüfen Sie Ihre Konfiguration.",
"errorTitle": "Ein Fehler ist aufgetreten",
"errorSubtitle": "Beim Versuch, diese Aktion auszuführen, ist ein Fehler aufgetreten. Bitte überprüfen Sie die Konsole für weitere Informationen."
}

View File

@@ -1,15 +1,16 @@
{
"loginTitle": "Καλώς ήρθατε, συνδεθείτε με",
"loginDivider": "Ή συνεχίστε με κωδικό πρόσβασης",
"loginTitleSimple": "Καλώς ορίσατε, παρακαλώ συνδεθείτε",
"loginDivider": "Ή",
"loginUsername": "Όνομα Χρήστη",
"loginPassword": "Κωδικός",
"loginSubmit": "Είσοδος",
"loginFailTitle": "Αποτυχία σύνδεσης",
"loginFailSubtitle": "Παρακαλώ ελέγξτε το όνομα χρήστη και τον κωδικό πρόσβασης",
"loginFailRateLimit": "Αποτύχατε να συνδεθείτε πάρα πολλές φορές, παρακαλώ προσπαθήστε ξανά αργότερα",
"loginFailRateLimit": "Αποτύχατε να συνδεθείτε πάρα πολλές φορές. Παρακαλώ προσπαθήστε ξανά αργότερα",
"loginSuccessTitle": "Συνδεδεμένος",
"loginSuccessSubtitle": "Καλώς ήρθατε!",
"loginOauthFailTitle": "Εσωτερικό σφάλμα",
"loginOauthFailTitle": "Παρουσιάστηκε ένα σφάλμα",
"loginOauthFailSubtitle": "Αποτυχία λήψης OAuth URL",
"loginOauthSuccessTitle": "Ανακατεύθυνση",
"loginOauthSuccessSubtitle": "Ανακατεύθυνση στον πάροχο OAuth σας",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Μη έγκυρη ανακατεύθυνση",
"continueInvalidRedirectSubtitle": "Το URL ανακατεύθυνσης δεν είναι έγκυρο",
"continueInsecureRedirectTitle": "Μη ασφαλής ανακατεύθυνση",
"continueInsecureRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε από <Code>https</Code> σε <Code>http</Code>, είστε σίγουροι ότι θέλετε να συνεχίσετε;",
"continueInsecureRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε από <code>https</code> σε <code>http</code> το οποίο δεν είναι ασφαλές. Είστε σίγουροι ότι θέλετε να συνεχίσετε;",
"continueTitle": "Συνέχεια",
"continueSubtitle": "Κάντε κλικ στο κουμπί για να συνεχίσετε στην εφαρμογή σας.",
"internalErrorTitle": "Εσωτερικό Σφάλμα Διακομιστή",
"internalErrorSubtitle": "Παρουσιάστηκε σφάλμα στο διακομιστή και δεν μπορεί να εξυπηρετήσει το αίτημά σας.",
"internalErrorButton": "Προσπαθήστε ξανά",
"logoutFailTitle": "Αποτυχία αποσύνδεσης",
"logoutFailSubtitle": "Παρακαλώ δοκιμάστε ξανά",
"logoutSuccessTitle": "Αποσυνδεδεμένος",
"logoutSuccessSubtitle": "Έχετε αποσυνδεθεί",
"logoutTitle": "Αποσύνδεση",
"logoutUsernameSubtitle": "Αυτή τη στιγμή είστε συνδεδεμένοι ως <Code>{{username}}</Code>, κάντε κλικ στο παρακάτω κουμπί για να αποσυνδεθείτε.",
"logoutOauthSubtitle": "Αυτή τη στιγμή είστε συνδεδεμένοι ως <Code>{{username}}</Code> χρησιμοποιώντας την υπηρεσία παροχής {{provider}} OAuth, κάντε κλικ στο παρακάτω κουμπί για να αποσυνδεθείτε.",
"logoutUsernameSubtitle": "Αυτή τη στιγμή είστε συνδεδεμένοι ως <code>{{username}}</code>. Κάντε κλικ στο παρακάτω κουμπί για να αποσυνδεθείτε.",
"logoutOauthSubtitle": "Αυτή τη στιγμή είστε συνδεδεμένοι ως <code>{{username}}</code> χρησιμοποιώντας την υπηρεσία παροχής {{provider}} OAuth. Κάντε κλικ στο παρακάτω κουμπί για να αποσυνδεθείτε.",
"notFoundTitle": "Η σελίδα δε βρέθηκε",
"notFoundSubtitle": "Η σελίδα που ψάχνετε δεν υπάρχει.",
"notFoundButton": "Μετάβαση στην αρχική",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Επαληθεύθηκε",
"totpSuccessSubtitle": "Ανακατεύθυνση στην εφαρμογή σας",
"totpTitle": "Εισάγετε τον κωδικό TOTP",
"totpSubtitle": "Παρακαλώ εισάγετε τον κωδικό από την εφαρμογή ελέγχου ταυτότητας.",
"unauthorizedTitle": "Μη εξουσιοδοτημένο",
"unauthorizedResourceSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν έχει άδεια πρόσβασης στον πόρο <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν είναι εξουσιοδοτημένος να συνδεθεί.",
"unauthorizedGroupsSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν είναι στις ομάδες που απαιτούνται από τον πόρο <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "Ο χρήστης με όνομα χρήστη <code>{{username}}</code> δεν έχει άδεια πρόσβασης στον πόρο <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "Ο χρήστης με όνομα χρήστη <code>{{username}}</code> δεν είναι εξουσιοδοτημένος να συνδεθεί.",
"unauthorizedGroupsSubtitle": "Ο χρήστης με όνομα χρήστη <code>{{username}}</code> δεν είναι στις ομάδες που απαιτούνται από τον πόρο <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Η διεύθυνση IP σας <code>{{ip}}</code> δεν είναι εξουσιοδοτημένη να έχει πρόσβαση στον πόρο <code>{{resource}}</code>.",
"unauthorizedButton": "Προσπαθήστε ξανά",
"untrustedRedirectTitle": "Μη έμπιστη ανακατεύθυνση",
"untrustedRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε σε έναν τομέα που δεν ταιριάζει με τον ρυθμισμένο τομέα σας (<Code>{{domain}}</Code>). Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
"untrustedRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε σε ένα domain που δεν ταιριάζει με τον ρυθμισμένο domain σας (<code>{{domain}}</code>). Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
"cancelTitle": "Ακύρωση",
"forgotPasswordTitle": "Ξεχάσατε το συνθηματικό σας;"
"forgotPasswordTitle": "Ξεχάσατε το συνθηματικό σας;",
"failedToFetchProvidersTitle": "Αποτυχία φόρτωσης παρόχων πιστοποίησης. Παρακαλώ ελέγξτε τις ρυθμίσεις σας.",
"errorTitle": "Παρουσιάστηκε ένα σφάλμα",
"errorSubtitle": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια εκτέλεσης αυτής της ενέργειας. Ελέγξτε την κονσόλα για περισσότερες πληροφορίες."
}

View File

@@ -42,6 +42,7 @@
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
@@ -49,5 +50,6 @@
"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."
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable."
}

View File

@@ -42,6 +42,7 @@
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
@@ -49,5 +50,6 @@
"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."
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable."
}

View File

@@ -1,51 +1,54 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
"continueRedirectingTitle": "Redirecting...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
"loginTitle": "Bienvenido de vuelta, inicie sesión con",
"loginTitleSimple": "Bienvenido de vuelta, por favor inicie sesión",
"loginDivider": "O",
"loginUsername": "Usuario",
"loginPassword": "Contraseña",
"loginSubmit": "Iniciar sesión",
"loginFailTitle": "Fallo al iniciar sesión",
"loginFailSubtitle": "Por favor revise su usuario y contraseña",
"loginFailRateLimit": "Muchos inicios de sesión consecutivos fallidos. Por favor inténtelo más tarde",
"loginSuccessTitle": "Sesión iniciada",
"loginSuccessSubtitle": "¡Bienvenido de vuelta!",
"loginOauthFailTitle": "Ocurrió un error",
"loginOauthFailSubtitle": "Error al obtener la URL de OAuth",
"loginOauthSuccessTitle": "Redireccionando",
"loginOauthSuccessSubtitle": "Redireccionando a tu proveedor de OAuth",
"continueRedirectingTitle": "Redireccionando...",
"continueRedirectingSubtitle": "Pronto será redirigido a la aplicación",
"continueInvalidRedirectTitle": "Redirección inválida",
"continueInvalidRedirectSubtitle": "La URL de redirección es inválida",
"continueInsecureRedirectTitle": "Redirección insegura",
"continueInsecureRedirectSubtitle": "Está intentando redirigir desde <code>https</code> a <code>http</code> lo cual no es seguro. ¿Está seguro que desea continuar?",
"continueTitle": "Continuar",
"continueSubtitle": "Haga clic en el botón para continuar hacia su aplicación.",
"logoutFailTitle": "Fallo al cerrar sesión",
"logoutFailSubtitle": "Por favor intente nuevamente",
"logoutSuccessTitle": "Sesión cerrada",
"logoutSuccessSubtitle": "Su sesión ha sido cerrada",
"logoutTitle": "Cerrar sesión",
"logoutUsernameSubtitle": "Actualmente está conectado como <code>{{username}}</code>. Haga clic en el botón de abajo para cerrar sesión.",
"logoutOauthSubtitle": "Actualmente está conectado como <code>{{username}}</code> usando {{provider}} como su proveedor de OAuth. Haga clic en el botón de abajo para cerrar sesión.",
"notFoundTitle": "Página no encontrada",
"notFoundSubtitle": "La página que está buscando no existe.",
"notFoundButton": "Volver al inicio",
"totpFailTitle": "Error al verificar código",
"totpFailSubtitle": "Por favor compruebe su código e inténtelo de nuevo",
"totpSuccessTitle": "Verificado",
"totpSuccessSubtitle": "Redirigiendo a su aplicación",
"totpTitle": "Ingrese su código TOTP",
"totpSubtitle": "Por favor introduzca el código de su aplicación de autenticación.",
"unauthorizedTitle": "No autorizado",
"unauthorizedResourceSubtitle": "El usuario con nombre de usuario <code>{{username}}</code> no está autorizado para acceder al recurso <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "El usuario con nombre de usuario <code>{{username}}</code> no está autorizado a iniciar sesión.",
"unauthorizedGroupsSubtitle": "El usuario con nombre de usuario <code>{{username}}</code> no está en los grupos requeridos por el recurso <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Inténtelo de nuevo",
"untrustedRedirectTitle": "Redirección no confiable",
"untrustedRedirectSubtitle": "Está intentando redirigir a un dominio que no coincide con su dominio configurado (<code>{{domain}}</code>). ¿Está seguro que desea continuar?",
"cancelTitle": "Cancelar",
"forgotPasswordTitle": "¿Olvidó su contraseña?",
"failedToFetchProvidersTitle": "Error al cargar los proveedores de autenticación. Por favor revise su configuración.",
"errorTitle": "Ha ocurrido un error",
"errorSubtitle": "Ocurrió un error mientras se trataba de realizar esta acción. Por favor, revise la consola para más información."
}

View File

@@ -1,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Bienvenue, connectez-vous avec",
"loginDivider": "Ou continuez avec le mot de passe",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Nom d'utilisateur",
"loginPassword": "Mot de passe",
"loginSubmit": "Se connecter",
"loginFailTitle": "Échec de la connexion",
"loginFailSubtitle": "Veuillez vérifier votre nom d'utilisateur et votre mot de passe",
"loginFailRateLimit": "Vous n'avez pas pu vous connecter trop de fois, veuillez réessayer plus tard",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Connecté",
"loginSuccessSubtitle": "Bienvenue!",
"loginOauthFailTitle": "Erreur interne",
"loginOauthFailTitle": "Une erreur s'est produite",
"loginOauthFailSubtitle": "Impossible d'obtenir l'URL OAuth",
"loginOauthSuccessTitle": "Redirection",
"loginOauthSuccessSubtitle": "Redirection vers votre fournisseur OAuth",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Redirection invalide",
"continueInvalidRedirectSubtitle": "L'URL de redirection est invalide",
"continueInsecureRedirectTitle": "Redirection non sécurisée",
"continueInsecureRedirectSubtitle": "Vous essayez de rediriger de <Code>https</Code> vers <Code>http</Code>, êtes-vous sûr de vouloir continuer ?",
"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?",
"continueTitle": "Continuer",
"continueSubtitle": "Cliquez sur le bouton pour continuer vers votre application.",
"internalErrorTitle": "Erreur interne du serveur",
"internalErrorSubtitle": "Une erreur s'est produite sur le serveur et il ne peut actuellement pas répondre à votre demande.",
"internalErrorButton": "Réessayer",
"logoutFailTitle": "Échec de la déconnexion",
"logoutFailSubtitle": "Veuillez réessayer",
"logoutSuccessTitle": "Déconnecté",
"logoutSuccessSubtitle": "Vous avez été déconnecté",
"logoutTitle": "Déconnexion",
"logoutUsernameSubtitle": "Vous êtes actuellement connecté en tant que <Code>{{username}}</Code>, cliquez sur le bouton ci-dessous pour vous déconnecter.",
"logoutOauthSubtitle": "Vous êtes actuellement connecté en tant que <Code>{{username}}</Code> en utilisant le fournisseur OAuth {{provider}} , cliquez sur le bouton ci-dessous pour vous déconnecter.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page introuvable",
"notFoundSubtitle": "La page recherchée n'existe pas.",
"notFoundButton": "Retour à la page d'accueil",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Vérifié",
"totpSuccessSubtitle": "Redirection vers votre application",
"totpTitle": "Saisissez votre code TOTP",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Non autorisé",
"unauthorizedResourceSubtitle": "L'utilisateur avec le nom d'utilisateur <Code>{{username}}</Code> n'est pas autorisé à accéder à la ressource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welkom terug, log in met",
"loginDivider": "Of ga door met wachtwoord",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Gebruikersnaam",
"loginPassword": "Wachtwoord",
"loginSubmit": "Log in",
"loginFailTitle": "Mislukt om in te loggen",
"loginFailSubtitle": "Controleer je gebruikersnaam en wachtwoord",
"loginFailRateLimit": "Inloggen te vaak mislukt, probeer het later opnieuw",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Ingelogd",
"loginSuccessSubtitle": "Welkom terug!",
"loginOauthFailTitle": "Interne fout",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Fout bij het ophalen van OAuth URL",
"loginOauthSuccessTitle": "Omleiden",
"loginOauthSuccessSubtitle": "Omleiden naar je OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Ongeldige omleiding",
"continueInvalidRedirectSubtitle": "De omleidings-URL is ongeldig",
"continueInsecureRedirectTitle": "Onveilige doorverwijzing",
"continueInsecureRedirectSubtitle": "Je probeert door te verwijzen van <Code>https</Code> naar <Code>http</Code>, weet je zeker dat je wilt doorgaan?",
"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?",
"continueTitle": "Ga verder",
"continueSubtitle": "Klik op de knop om door te gaan naar de app.",
"internalErrorTitle": "Interne server fout",
"internalErrorSubtitle": "Er is een fout opgetreden op de server en het kan momenteel niet voldoen aan je verzoek.",
"internalErrorButton": "Opnieuw proberen",
"logoutFailTitle": "Afmelden mislukt",
"logoutFailSubtitle": "Probeer het opnieuw",
"logoutSuccessTitle": "Afgemeld",
"logoutSuccessSubtitle": "Je bent afgemeld",
"logoutTitle": "Afmelden",
"logoutUsernameSubtitle": "Je bent momenteel ingelogd als <Code>{{username}}</Code>, klik op de knop hieronder om uit te loggen.",
"logoutOauthSubtitle": "Je bent momenteel ingelogd als <Code>{{username}}</Code> met behulp van de {{provider}} OAuth provider, klik op de knop hieronder om uit te loggen.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Pagina niet gevonden",
"notFoundSubtitle": "De pagina die je zoekt bestaat niet.",
"notFoundButton": "Naar startpagina",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Geverifiëerd",
"totpSuccessSubtitle": "Omleiden naar je app",
"totpTitle": "Voer je TOTP-code in",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Ongeautoriseerd",
"unauthorizedResourceSubtitle": "De gebruiker met gebruikersnaam <Code>{{username}}</Code> heeft geen toegang tot de bron <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Opnieuw proberen",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Witaj ponownie, zaloguj się przez",
"loginDivider": "Lub kontynuuj z hasłem",
"loginTitleSimple": "Witaj ponownie, zaloguj się",
"loginDivider": "Lub",
"loginUsername": "Nazwa użytkownika",
"loginPassword": "Hasło",
"loginSubmit": "Zaloguj się",
"loginFailTitle": "Nie udało się zalogować",
"loginFailSubtitle": "Sprawdź swoją nazwę użytkownika i hasło",
"loginFailRateLimit": "Nie udało się zalogować zbyt wiele razy, spróbuj ponownie później",
"loginFailSubtitle": "Sprawdź swoją nazwę użytkownika i hasło",
"loginFailRateLimit": "Zbyt wiele razy nie udało Ci się zalogować. Spróbuj ponownie później",
"loginSuccessTitle": "Zalogowano",
"loginSuccessSubtitle": "Witaj ponownie!",
"loginOauthFailTitle": "Wewnętrzny błąd",
"loginOauthFailTitle": "Wystąpił błąd",
"loginOauthFailSubtitle": "Nie udało się uzyskać adresu URL OAuth",
"loginOauthSuccessTitle": "Przekierowywanie",
"loginOauthSuccessSubtitle": "Przekierowywanie do Twojego dostawcy OAuth",
@@ -18,34 +19,36 @@
"continueInvalidRedirectTitle": "Nieprawidłowe przekierowanie",
"continueInvalidRedirectSubtitle": "Adres przekierowania jest nieprawidłowy",
"continueInsecureRedirectTitle": "Niezabezpieczone przekierowanie",
"continueInsecureRedirectSubtitle": "Próbujesz przekierować z <Code>https</Code> do <Code>http</Code>, czy na pewno chcesz kontynuować?",
"continueInsecureRedirectSubtitle": "Próbujesz przekierować z <code>https</code> do <code>http</code>, co nie jest bezpieczne. Czy na pewno chcesz kontynuować?",
"continueTitle": "Kontynuuj",
"continueSubtitle": "Kliknij przycisk, aby przejść do aplikacji.",
"internalErrorTitle": "Wewnętrzny błąd serwera",
"internalErrorSubtitle": "Wystąpił błąd na serwerze i obecnie nie można obsłużyć tego żądania.",
"internalErrorButton": "Spróbuj ponownie",
"logoutFailTitle": "Nie udało się wylogować",
"logoutFailSubtitle": "Spróbuj ponownie",
"logoutSuccessTitle": "Wylogowano",
"logoutSuccessSubtitle": "Zostałeś wylogowany",
"logoutTitle": "Wylogowanie",
"logoutUsernameSubtitle": "Jesteś aktualnie zalogowany jako <Code>{{username}}</Code>, kliknij przycisk poniżej, aby się wylogować.",
"logoutOauthSubtitle": "Jesteś obecnie zalogowany jako <Code>{{username}}</Code> przy użyciu providera OAuth {{provider}}, kliknij przycisk poniżej, aby się wylogować.",
"notFoundTitle": "Strona nie znaleziona",
"logoutUsernameSubtitle": "Jesteś obecnie zalogowany jako <code>{{username}}</code>. Kliknij poniższy przycisk, aby się wylogować.",
"logoutOauthSubtitle": "Obecnie jesteś zalogowany jako <code>{{username}}</code> przy użyciu dostawcy {{provider}} OAuth. Kliknij poniższy przycisk, aby się wylogować.",
"notFoundTitle": "Nie znaleziono strony",
"notFoundSubtitle": "Strona, której szukasz nie istnieje.",
"notFoundButton": "Wróć do strony głównej",
"totpFailTitle": "Nie udało się zweryfikować kodu",
"totpFailSubtitle": "Sprawdź swój kod i spróbuj ponownie",
"totpFailSubtitle": "Sprawdź swój kod i spróbuj ponownie",
"totpSuccessTitle": "Zweryfikowano",
"totpSuccessSubtitle": "Przekierowywanie do aplikacji",
"totpTitle": "Wprowadź kod TOTP",
"totpSubtitle": "Wpisz kod z aplikacji uwierzytelniającej.",
"unauthorizedTitle": "Nieautoryzowany",
"unauthorizedResourceSubtitle": "Użytkownik o nazwie <Code>{{username}}</Code> nie jest upoważniony do uzyskania dostępu do zasobu <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "Użytkownik o nazwie użytkownika <code>{{username}}</code> nie ma uprawnień dostępu do zasobu <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "Użytkownik o nazwie <code>{{username}}</code> nie jest upoważniony do zalogowania się.",
"unauthorizedGroupsSubtitle": "Użytkownik o nazwie <code>{{username}}</code> nie należy do grup wymaganych przez zasób <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Twój adres IP <code>{{ip}}</code> nie ma autoryzacji do dostępu do zasobu <code>{{resource}}</code>.",
"unauthorizedButton": "Spróbuj ponownie",
"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ć?",
"untrustedRedirectSubtitle": "Próbujesz przekierować do domeny, która nie pasuje do Twojej skonfigurowanej domeny (<code>{{domain}}</code>). Czy na pewno chcesz kontynuować?",
"cancelTitle": "Anuluj",
"forgotPasswordTitle": "Nie pamiętasz hasła?"
"forgotPasswordTitle": "Nie pamiętasz hasła?",
"failedToFetchProvidersTitle": "Nie udało się załadować dostawców uwierzytelniania. Sprawdź swoją konfigurację.",
"errorTitle": "Wystąpił błąd",
"errorSubtitle": "Wystąpił błąd podczas próby wykonania tej czynności. Sprawdź konsolę, aby uzyskać więcej informacji."
}

View File

@@ -1,15 +1,16 @@
{
"loginTitle": "Bem-vindo de volta, acesse com",
"loginDivider": "Ou continuar com uma senha",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Nome de usuário",
"loginPassword": "Senha",
"loginSubmit": "Entrar",
"loginFailTitle": "Falha ao iniciar sessão",
"loginFailSubtitle": "Por favor, verifique seu usuário e senha",
"loginFailRateLimit": "Você falhou em iniciar sessão muitas vezes, por favor tente novamente mais tarde",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Sessão Iniciada",
"loginSuccessSubtitle": "Bem-vindo de volta!",
"loginOauthFailTitle": "Erro interno",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Falha ao obter URL de OAuth",
"loginOauthSuccessTitle": "Redirecionando",
"loginOauthSuccessSubtitle": "Redirecionando para seu provedor OAuth",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Redirecionamento inválido",
"continueInvalidRedirectSubtitle": "O endereço de redirecionamento é inválido",
"continueInsecureRedirectTitle": "Redirecionamento inseguro",
"continueInsecureRedirectSubtitle": "Você está tentando redirecionar de <Code>https</Code> para <Code>http</Code>, você tem certeza que deseja continuar?",
"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?",
"continueTitle": "Continuar",
"continueSubtitle": "Clique no botão para continuar para o seu aplicativo.",
"internalErrorTitle": "Erro interno do servidor",
"internalErrorSubtitle": "Ocorreu um erro no servidor e atualmente não pode servir sua solicitação.",
"internalErrorButton": "Tentar novamente",
"logoutFailTitle": "Falha ao encerrar sessão",
"logoutFailSubtitle": "Por favor, tente novamente",
"logoutSuccessTitle": "Sessão encerrada",
"logoutSuccessSubtitle": "Você foi desconectado",
"logoutTitle": "Sair",
"logoutUsernameSubtitle": "Você está atualmente logado como <Code>{{username}}</Code>, clique no botão abaixo para sair.",
"logoutOauthSubtitle": "Você está atualmente logado como <Code>{{username}}</Code> usando o provedor {{provider}} OAuth, clique no botão abaixo para sair.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Página não encontrada",
"notFoundSubtitle": "A página que você está procurando não existe.",
"notFoundButton": "Voltar para a tela inicial",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verificado",
"totpSuccessSubtitle": "Redirecionando para o seu aplicativo",
"totpTitle": "Insira o seu código TOTP",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Não autorizado",
"unauthorizedResourceSubtitle": "O usuário com nome de usuário <Code>{{username}}</Code> não está autorizado a acessar o recurso <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "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?",
"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": "Cancelar",
"forgotPasswordTitle": "Esqueceu sua senha?"
"forgotPasswordTitle": "Esqueceu sua senha?",
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,51 +1,54 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
"continueRedirectingTitle": "Redirecting...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
"loginTitle": "С возвращением, войти с",
"loginTitleSimple": "Вход",
"loginDivider": "Или",
"loginUsername": "Имя пользователя",
"loginPassword": "Пароль",
"loginSubmit": "Войти",
"loginFailTitle": "Вход не удался",
"loginFailSubtitle": "Проверьте имя пользователя и пароль",
"loginFailRateLimit": "Слишком много ошибок входа. Попробуйте позже",
"loginSuccessTitle": "Вы вошли",
"loginSuccessSubtitle": "С возвращением!",
"loginOauthFailTitle": "Произошла ошибка",
"loginOauthFailSubtitle": "Не удалось получить OAuth URL",
"loginOauthSuccessTitle": "Перенаправление",
"loginOauthSuccessSubtitle": "Перенаправление к поставщику OAuth",
"continueRedirectingTitle": "Перенаправление...",
"continueRedirectingSubtitle": "Скоро вы будете перенаправлены в приложение",
"continueInvalidRedirectTitle": "Неверное перенаправление",
"continueInvalidRedirectSubtitle": "URL перенаправления недействителен",
"continueInsecureRedirectTitle": "Небезопасное перенаправление",
"continueInsecureRedirectSubtitle": "Попытка перенаправления с <code>https</code> на <code>http</code>, уверены, что хотите продолжить?",
"continueTitle": "Продолжить",
"continueSubtitle": "Нажмите на кнопку, чтобы перейти к приложению.",
"logoutFailTitle": "Не удалось выйти",
"logoutFailSubtitle": "Попробуйте ещё раз",
"logoutSuccessTitle": "Выход",
"logoutSuccessSubtitle": "Вы вышли из системы",
"logoutTitle": "Выйти",
"logoutUsernameSubtitle": "Вход выполнен как <code>{{username}}</code>, нажмите на кнопку ниже, чтобы выйти.",
"logoutOauthSubtitle": "Вход выполнен как <code>{{username}}</code> с использованием {{provider}} OAuth, нажмите кнопку ниже, чтобы выйти.",
"notFoundTitle": "Страница не найдена",
"notFoundSubtitle": "Эта страница не существует.",
"notFoundButton": "На главную",
"totpFailTitle": "Не удалось проверить код",
"totpFailSubtitle": "Пожалуйста, проверьте свой код и повторите попытку",
"totpSuccessTitle": "Подтверждён",
"totpSuccessSubtitle": "Перенаправление в приложение",
"totpTitle": "Введите код TOTP",
"totpSubtitle": "Пожалуйста, введите код из вашего приложения — аутентификатора.",
"unauthorizedTitle": "Доступ запрещен",
"unauthorizedResourceSubtitle": "Пользователю <code>{{username}}</code> не разрешен доступ к <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "Пользователю <code>{{username}}</code> не разрешен вход.",
"unauthorizedGroupsSubtitle": "Пользователь <code>{{username}}</code> не состоит в группах, которым разрешен доступ к <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Ваш IP адрес <code>{{ip}}</code> не авторизован для доступа к ресурсу <code>{{resource}}</code>.",
"unauthorizedButton": "Повторить",
"untrustedRedirectTitle": "Ненадежное перенаправление",
"untrustedRedirectSubtitle": "Попытка перенаправить на домен, который не соответствует вашему заданному домену (<code>{{domain}}</code>). Уверены, что хотите продолжить?",
"cancelTitle": "Отмена",
"forgotPasswordTitle": "Забыли пароль?",
"failedToFetchProvidersTitle": "Не удалось загрузить провайдеров аутентификации. Пожалуйста, проверьте конфигурацию.",
"errorTitle": "Произошла ошибка",
"errorSubtitle": "Произошла ошибка при попытке выполнить это действие. Проверьте консоль для дополнительной информации."
}

View File

@@ -1,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Ya da şifre ile devam edin",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Kullanıcı Adı",
"loginPassword": "Şifre",
"loginSubmit": "Giriş Yap",
"loginFailTitle": "Giriş yapılamadı",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Giriş yapıldı",
"loginSuccessSubtitle": "Tekrar hoş geldiniz!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Yönlendiriliyor",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Devam et",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "İç Sunucu Hatası",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Tekrar deneyin",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Lütfen tekrar deneyin",
"logoutSuccessTitle": ıkış yapıldı",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Sayfa bulunamadı",
"notFoundSubtitle": "Aradığınız sayfa mevcut değil.",
"notFoundButton": "Ana sayfaya git",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Doğrulandı",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginTitleSimple": "Welcome back, please login",
"loginDivider": "Or",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginFailRateLimit": "You failed to login too many times. Please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailTitle": "An error occurred",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"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?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"totpSubtitle": "Please enter the code from your authenticator app.",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "Try again",
"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?",
"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?"
"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,15 +1,16 @@
{
"loginTitle": "欢迎回来,请登录",
"loginDivider": "或者继续使用密码",
"loginTitle": "欢迎回来,请使用以下方式登录",
"loginTitleSimple": "欢迎回来,请登录",
"loginDivider": "或",
"loginUsername": "用户名",
"loginPassword": "密码",
"loginSubmit": "登录",
"loginFailTitle": "登录失败",
"loginFailSubtitle": "请检查您的用户名和密码",
"loginFailRateLimit": "您登录次数过多请稍后再试",
"loginFailRateLimit": "您登录失败次数过多请稍后再试",
"loginSuccessTitle": "已登录",
"loginSuccessSubtitle": "欢迎回来!",
"loginOauthFailTitle": "内部错误",
"loginOauthFailTitle": "发生错误",
"loginOauthFailSubtitle": "获取 OAuth URL 失败",
"loginOauthSuccessTitle": "重定向中",
"loginOauthSuccessSubtitle": "重定向到您的 OAuth 提供商",
@@ -18,19 +19,16 @@
"continueInvalidRedirectTitle": "无效的重定向",
"continueInvalidRedirectSubtitle": "重定向URL无效",
"continueInsecureRedirectTitle": "不安全的重定向",
"continueInsecureRedirectSubtitle": "您正在尝试将 <Code>https</Code> 重定向到 <Code>http</Code>您确定要继续吗?",
"continueInsecureRedirectSubtitle": "您正在尝试从<code>https</code>重定向到<code>http</code>可能存在风险。您确定要继续吗?",
"continueTitle": "继续",
"continueSubtitle": "点击按钮以继续您的应用。",
"internalErrorTitle": "服务器内部错误",
"internalErrorSubtitle": "服务器上发生错误,当前无法满足您的请求。",
"internalErrorButton": "重试",
"logoutFailTitle": "注销失败",
"logoutFailSubtitle": "请重试",
"logoutSuccessTitle": "已登出",
"logoutSuccessSubtitle": "您已登出",
"logoutTitle": "登出",
"logoutUsernameSubtitle": "您当前以 <Code>{{username}}</Code> 的身份登录,点击下方按钮退出登录。",
"logoutOauthSubtitle": "您当前以 <Code>{{username}}</Code> 的身份登录,使用的是 {{provider}} OAuth 提供商点击下方按钮退出登录。",
"logoutUsernameSubtitle": "您当前登录用户为<code>{{username}}</code>点击下方按钮注销。",
"logoutOauthSubtitle": "您当前以<code>{{username}}</code>登录,使用的是{{provider}} OAuth 提供商点击下方按钮注销。",
"notFoundTitle": "无法找到页面",
"notFoundSubtitle": "您正在查找的页面不存在。",
"notFoundButton": "回到主页",
@@ -39,13 +37,18 @@
"totpSuccessTitle": "已验证",
"totpSuccessSubtitle": "重定向到您的应用",
"totpTitle": "输入您的 TOTP 代码",
"totpSubtitle": "请输入您身份验证器应用中的代码。",
"unauthorizedTitle": "未授权",
"unauthorizedResourceSubtitle": "用户 <Code>{{username}}</Code> 无权访问资源 <Code>{{resource}}</Code>。",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedResourceSubtitle": "用户名为<code>{{username}}</code>的用户无权访问资源<code>{{resource}}</code>。",
"unauthorizedLoginSubtitle": "用户名为<code>{{username}}</code>的用户无权登录。",
"unauthorizedGroupsSubtitle": "用户名为<code>{{username}}</code>的用户不在资源<code>{{resource}}</code>所需的组中。",
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
"unauthorizedButton": "重试",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
"untrustedRedirectTitle": "不可信的重定向",
"untrustedRedirectSubtitle": "您正在尝试重定向到一个与您已配置的域名 (<code>{{domain}}</code>) 不匹配的域名。您确定要继续吗?",
"cancelTitle": "取消",
"forgotPasswordTitle": "忘记密码?",
"failedToFetchProvidersTitle": "加载身份验证提供程序失败,请检查您的配置。",
"errorTitle": "发生了错误",
"errorSubtitle": "执行此操作时发生错误,请检查控制台以获取更多信息。"
}

View File

@@ -1,51 +1,54 @@
{
"loginTitle": "Welcome back, login with",
"loginDivider": "Or continue with password",
"loginUsername": "Username",
"loginPassword": "Password",
"loginSubmit": "Login",
"loginFailTitle": "Failed to log in",
"loginFailSubtitle": "Please check your username and password",
"loginFailRateLimit": "You failed to login too many times, please try again later",
"loginSuccessTitle": "Logged in",
"loginSuccessSubtitle": "Welcome back!",
"loginOauthFailTitle": "Internal error",
"loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
"continueRedirectingTitle": "Redirecting...",
"continueRedirectingSubtitle": "You should be redirected to the app soon",
"continueInvalidRedirectTitle": "Invalid redirect",
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
"continueInsecureRedirectTitle": "Insecure redirect",
"continueInsecureRedirectSubtitle": "You are trying to redirect from <Code>https</Code> to <Code>http</Code>, are you sure you want to continue?",
"continueTitle": "Continue",
"continueSubtitle": "Click the button to continue to your app.",
"internalErrorTitle": "Internal Server Error",
"internalErrorSubtitle": "An error occurred on the server and it currently cannot serve your request.",
"internalErrorButton": "Try again",
"logoutFailTitle": "Failed to log out",
"logoutFailSubtitle": "Please try again",
"logoutSuccessTitle": "Logged out",
"logoutSuccessSubtitle": "You have been logged out",
"logoutTitle": "Logout",
"logoutUsernameSubtitle": "You are currently logged in as <Code>{{username}}</Code>, click the button below to logout.",
"logoutOauthSubtitle": "You are currently logged in as <Code>{{username}}</Code> using the {{provider}} OAuth provider, click the button below to logout.",
"notFoundTitle": "Page not found",
"notFoundSubtitle": "The page you are looking for does not exist.",
"notFoundButton": "Go home",
"totpFailTitle": "Failed to verify code",
"totpFailSubtitle": "Please check your code and try again",
"totpSuccessTitle": "Verified",
"totpSuccessSubtitle": "Redirecting to your app",
"totpTitle": "Enter your TOTP code",
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
"unauthorizedButton": "Try again",
"untrustedRedirectTitle": "Untrusted redirect",
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
"cancelTitle": "Cancel",
"forgotPasswordTitle": "Forgot your password?"
"loginTitle": "歡迎回來,請用以下方式登入",
"loginTitleSimple": "歡迎回來,請登入",
"loginDivider": "或",
"loginUsername": "帳號",
"loginPassword": "密碼",
"loginSubmit": "登入",
"loginFailTitle": "登入失敗",
"loginFailSubtitle": "請檢查您的帳號與密碼",
"loginFailRateLimit": "登入失敗次數過多,請稍後再試",
"loginSuccessTitle": "登入成功",
"loginSuccessSubtitle": "歡迎回來!",
"loginOauthFailTitle": "發生錯誤",
"loginOauthFailSubtitle": "無法取得 OAuth 網址",
"loginOauthSuccessTitle": "重新導向中",
"loginOauthSuccessSubtitle": "正在將您重新導向至 OAuth 供應商",
"continueRedirectingTitle": "重新導向中...",
"continueRedirectingSubtitle": "您即將被重新導向至應用程式",
"continueInvalidRedirectTitle": "無效的重新導向",
"continueInvalidRedirectSubtitle": "重新導向的網址無效",
"continueInsecureRedirectTitle": "不安全的重新導向",
"continueInsecureRedirectSubtitle": "您正嘗試從安全的 <code>https</code> 重新導向至不安全的 <code>http</code>。您確定要繼續嗎?",
"continueTitle": "繼續",
"continueSubtitle": "點擊按鈕以繼續前往您的應用程式。",
"logoutFailTitle": "登出失敗",
"logoutFailSubtitle": "請再試一次",
"logoutSuccessTitle": "登出成功",
"logoutSuccessSubtitle": "您已成功登出",
"logoutTitle": "登出",
"logoutUsernameSubtitle": "您目前以 <code>{{username}}</code> 的身分登入。點擊下方按鈕以登出。",
"logoutOauthSubtitle": "您目前使用 {{provider}} OAuth 供應商並以 <code>{{username}}</code> 的身分登入。點擊下方按鈕以登出。",
"notFoundTitle": "找不到頁面",
"notFoundSubtitle": "您要尋找的頁面不存在。",
"notFoundButton": "回到首頁",
"totpFailTitle": "驗證失敗",
"totpFailSubtitle": "請檢查您的驗證碼並再試一次",
"totpSuccessTitle": "驗證成功",
"totpSuccessSubtitle": "正在重新導向至您的應用程式",
"totpTitle": "輸入您的 TOTP 驗證碼",
"totpSubtitle": "請輸入您驗證器應用程式中的代碼。",
"unauthorizedTitle": "未經授權",
"unauthorizedResourceSubtitle": "使用者 <code>{{username}}</code> 未被授權存取資源 <code>{{resource}}</code>。",
"unauthorizedLoginSubtitle": "使用者 <code>{{username}}</code> 未被授權登入。",
"unauthorizedGroupsSubtitle": "使用者 <code>{{username}}</code> 不在存取資源 <code>{{resource}}</code> 所需的群組中。",
"unauthorizedIpSubtitle": "您的 IP 位址 <code>{{ip}}</code> 未被授權存取資源 <code>{{resource}}</code>",
"unauthorizedButton": "再試一次",
"untrustedRedirectTitle": "不受信任的重新導向",
"untrustedRedirectSubtitle": "您正嘗試重新導向至的網域與您設定的網域 (<code>{{domain}}</code>) 不符。您確定要繼續嗎?",
"cancelTitle": "取消",
"forgotPasswordTitle": "忘記密碼?",
"failedToFetchProvidersTitle": "載入驗證供應商失敗。請檢查您的設定。",
"errorTitle": "發生錯誤",
"errorSubtitle": "執行此操作時發生錯誤。請檢查主控台以獲取更多資訊。"
}

View File

@@ -2,7 +2,7 @@ import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import { Layout } from "./components/layout/layout.tsx";
import { createBrowserRouter, RouterProvider } from "react-router";
import { BrowserRouter, Route, Routes } from "react-router";
import { LoginPage } from "./pages/login-page.tsx";
import { App } from "./App.tsx";
import { ErrorPage } from "./pages/error-page.tsx";
@@ -17,54 +17,6 @@ import { AppContextProvider } from "./context/app-context.tsx";
import { UserContextProvider } from "./context/user-context.tsx";
import { Toaster } from "@/components/ui/sonner";
const router = createBrowserRouter([
{
path: "/",
element: <App />,
errorElement: <ErrorPage />,
},
{
path: "/login",
element: <LoginPage />,
errorElement: <ErrorPage />,
},
{
path: "/logout",
element: <LogoutPage />,
errorElement: <ErrorPage />,
},
{
path: "/continue",
element: <ContinuePage />,
errorElement: <ErrorPage />,
},
{
path: "/totp",
element: <TotpPage />,
errorElement: <ErrorPage />,
},
{
path: "/forgot-password",
element: <ForgotPasswordPage />,
errorElement: <ErrorPage />,
},
{
path: "/unauthorized",
element: <UnauthorizedPage />,
errorElement: <ErrorPage />,
},
{
path: "/error",
element: <ErrorPage />,
errorElement: <ErrorPage />,
},
{
path: "*",
element: <NotFoundPage />,
errorElement: <ErrorPage />,
},
]);
const queryClient = new QueryClient();
createRoot(document.getElementById("root")!).render(
@@ -72,10 +24,25 @@ createRoot(document.getElementById("root")!).render(
<QueryClientProvider client={queryClient}>
<AppContextProvider>
<UserContextProvider>
<Layout>
<RouterProvider router={router} />
<Toaster />
</Layout>
<BrowserRouter>
<Routes>
<Route element={<Layout />} errorElement={<ErrorPage />}>
<Route path="/" element={<App />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/logout" element={<LogoutPage />} />
<Route path="/continue" element={<ContinuePage />} />
<Route path="/totp" element={<TotpPage />} />
<Route
path="/forgot-password"
element={<ForgotPasswordPage />}
/>
<Route path="/unauthorized" element={<UnauthorizedPage />} />
<Route path="/error" element={<ErrorPage />} />
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
</BrowserRouter>
<Toaster />
</UserContextProvider>
</AppContextProvider>
</QueryClientProvider>

View File

@@ -12,6 +12,7 @@ import { isValidUrl } from "@/lib/utils";
import { Trans, useTranslation } from "react-i18next";
import { Navigate, useLocation, useNavigate } from "react-router";
import DOMPurify from "dompurify";
import { useState } from "react";
export const ContinuePage = () => {
const { isLoggedIn } = useUserContext();
@@ -22,6 +23,7 @@ export const ContinuePage = () => {
const { domain, disableContinue } = useAppContext();
const { search } = useLocation();
const [loading, setLoading] = useState(false);
const searchParams = new URLSearchParams(search);
const redirectURI = searchParams.get("redirect_uri");
@@ -34,10 +36,15 @@ export const ContinuePage = () => {
return <Navigate to="/logout" />;
}
if (disableContinue) {
const handleRedirect = () => {
setLoading(true);
window.location.href = DOMPurify.sanitize(redirectURI);
}
if (disableContinue) {
handleRedirect();
}
const { t } = useTranslation();
const navigate = useNavigate();
@@ -63,14 +70,13 @@ export const ContinuePage = () => {
</CardHeader>
<CardFooter className="flex flex-col items-stretch gap-2">
<Button
onClick={() =>
(window.location.href = DOMPurify.sanitize(redirectURI))
}
onClick={handleRedirect}
loading={loading}
variant="destructive"
>
{t("continueTitle")}
</Button>
<Button onClick={() => navigate("/logout")} variant="outline">
<Button onClick={() => navigate("/logout")} variant="outline" disabled={loading}>
{t("cancelTitle")}
</Button>
</CardFooter>
@@ -97,14 +103,13 @@ export const ContinuePage = () => {
</CardHeader>
<CardFooter className="flex flex-col items-stretch gap-2">
<Button
onClick={() =>
(window.location.href = DOMPurify.sanitize(redirectURI))
}
onClick={handleRedirect}
loading={loading}
variant="warning"
>
{t("continueTitle")}
</Button>
<Button onClick={() => navigate("/logout")} variant="outline">
<Button onClick={() => navigate("/logout")} variant="outline" disabled={loading}>
{t("cancelTitle")}
</Button>
</CardFooter>
@@ -120,9 +125,8 @@ export const ContinuePage = () => {
</CardHeader>
<CardFooter className="flex flex-col items-stretch">
<Button
onClick={() =>
(window.location.href = DOMPurify.sanitize(redirectURI))
}
onClick={handleRedirect}
loading={loading}
>
{t("continueTitle")}
</Button>

View File

@@ -17,7 +17,7 @@ export const ForgotPasswordPage = () => {
<CardHeader>
<CardTitle className="text-3xl">{t("forgotPasswordTitle")}</CardTitle>
<CardDescription>
<Markdown>{forgotPasswordMessage}</Markdown>
<Markdown>{forgotPasswordMessage !== "" ? forgotPasswordMessage : t('forgotPasswordMessage')}</Markdown>
</CardDescription>
</CardHeader>
</Card>

View File

@@ -126,6 +126,8 @@ export const LoginPage = () => {
icon={<GoogleIcon />}
className="w-full"
onClick={() => oauthMutation.mutate("google")}
loading={oauthMutation.isPending && oauthMutation.variables === "google"}
disabled={oauthMutation.isPending || loginMutation.isPending}
/>
)}
{configuredProviders.includes("github") && (
@@ -134,6 +136,8 @@ export const LoginPage = () => {
icon={<GithubIcon />}
className="w-full"
onClick={() => oauthMutation.mutate("github")}
loading={oauthMutation.isPending && oauthMutation.variables === "github"}
disabled={oauthMutation.isPending || loginMutation.isPending}
/>
)}
{configuredProviders.includes("generic") && (
@@ -142,6 +146,8 @@ export const LoginPage = () => {
icon={<GenericIcon />}
className="w-full"
onClick={() => oauthMutation.mutate("generic")}
loading={oauthMutation.isPending && oauthMutation.variables === "generic"}
disabled={oauthMutation.isPending || loginMutation.isPending}
/>
)}
</div>
@@ -152,13 +158,13 @@ export const LoginPage = () => {
{userAuthConfigured && (
<LoginForm
onSubmit={(values) => loginMutation.mutate(values)}
loading={loginMutation.isPending}
loading={loginMutation.isPending || oauthMutation.isPending}
/>
)}
{configuredProviders.length == 0 && (
<h3 className="text-center text-xl text-red-600">
<p className="text-center text-red-600 max-w-sm">
{t("failedToFetchProvidersTitle")}
</h3>
</p>
)}
</CardContent>
</Card>

View File

@@ -6,12 +6,19 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
export const NotFoundPage = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const handleRedirect = () => {
setLoading(true);
navigate("/");
};
return (
<Card className="min-w-xs sm:min-w-sm">
@@ -20,7 +27,7 @@ export const NotFoundPage = () => {
<CardDescription>{t("notFoundSubtitle")}</CardDescription>
</CardHeader>
<CardFooter className="flex flex-col items-stretch">
<Button onClick={() => navigate("/")}>{t("notFoundButton")}</Button>
<Button onClick={handleRedirect} loading={loading}>{t("notFoundButton")}</Button>
</CardFooter>
</Card>
);

View File

@@ -8,18 +8,24 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { useUserContext } from "@/context/user-context";
import { TotpSchema } from "@/schemas/totp-schema";
import { useMutation } from "@tanstack/react-query";
import axios from "axios";
import { useId } from "react";
import { useTranslation } from "react-i18next";
import { useLocation, useNavigate } from "react-router";
import { Navigate, useLocation } from "react-router";
import { toast } from "sonner";
export const TotpPage = () => {
const { totpPending } = useUserContext();
if (!totpPending) {
return <Navigate to="/" />;
}
const { t } = useTranslation();
const { search } = useLocation();
const navigate = useNavigate();
const formId = useId();
const searchParams = new URLSearchParams(search);
@@ -34,7 +40,7 @@ export const TotpPage = () => {
});
setTimeout(() => {
navigate(
window.location.replace(
`/continue?redirect_uri=${encodeURIComponent(redirectUri ?? "")}`,
);
}, 500);

View File

@@ -6,6 +6,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import { Navigate, useLocation, useNavigate } from "react-router";
@@ -16,13 +17,20 @@ export const UnauthorizedPage = () => {
const username = searchParams.get("username");
const resource = searchParams.get("resource");
const groupErr = searchParams.get("groupErr");
const ip = searchParams.get("ip");
if (!username) {
if (!username && !ip) {
return <Navigate to="/" />;
}
const { t } = useTranslation();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const handleRedirect = () => {
setLoading(true);
navigate("/login");
};
let i18nKey = "unauthorizedLoginSubtitle";
@@ -34,6 +42,10 @@ export const UnauthorizedPage = () => {
i18nKey = "unauthorizedGroupsSubtitle";
}
if (ip) {
i18nKey = "unauthorizedIpSubtitle";
}
return (
<Card className="min-w-xs sm:min-w-sm">
<CardHeader>
@@ -48,12 +60,13 @@ export const UnauthorizedPage = () => {
values={{
username,
resource,
ip,
}}
/>
</CardDescription>
</CardHeader>
<CardFooter className="flex flex-col items-stretch">
<Button onClick={() => navigate("/login")}>
<Button onClick={handleRedirect} loading={loading}>
{t("unauthorizedButton")}
</Button>
</CardFooter>

27
go.mod
View File

@@ -3,21 +3,27 @@ module tinyauth
go 1.23.2
require (
github.com/gin-gonic/gin v1.10.0
github.com/go-playground/validator/v10 v10.26.0
github.com/gin-gonic/gin v1.10.1
github.com/go-playground/validator/v10 v10.27.0
github.com/google/go-querystring v1.1.0
github.com/google/uuid v1.6.0
github.com/mdp/qrterminal/v3 v3.2.1
github.com/rs/zerolog v1.34.0
github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.20.1
golang.org/x/crypto v0.38.0
github.com/traefik/paerser v0.2.2
golang.org/x/crypto v0.40.0
)
require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect
@@ -25,7 +31,7 @@ require (
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/term v0.33.0 // indirect
gotest.tools/v3 v3.5.2 // indirect
rsc.io/qr v0.2.0 // indirect
)
@@ -47,7 +53,7 @@ require (
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v28.1.1+incompatible
github.com/docker/docker v28.3.2+incompatible
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
@@ -56,6 +62,7 @@ require (
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
@@ -102,11 +109,11 @@ require (
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/arch v0.13.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
google.golang.org/protobuf v1.36.3 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

68
go.sum
View File

@@ -1,9 +1,13 @@
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
@@ -53,6 +57,10 @@ github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJn
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
@@ -64,8 +72,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.3.2+incompatible h1:wn66NJ6pWB1vBZIilP8G3qQPqHy5XymfYn5vsqeA5oA=
github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -84,8 +92,12 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -97,10 +109,10 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -122,8 +134,22 @@ github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzq
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -234,6 +260,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ=
github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
@@ -269,8 +297,8 @@ golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -279,15 +307,15 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -297,14 +325,14 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -1,311 +0,0 @@
package api_test
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"tinyauth/internal/api"
"tinyauth/internal/auth"
"tinyauth/internal/docker"
"tinyauth/internal/handlers"
"tinyauth/internal/hooks"
"tinyauth/internal/providers"
"tinyauth/internal/types"
"github.com/magiconair/properties/assert"
)
// Simple API config for tests
var apiConfig = types.APIConfig{
Port: 8080,
Address: "0.0.0.0",
}
// Simple handlers config for tests
var handlersConfig = types.HandlersConfig{
AppURL: "http://localhost:8080",
DisableContinue: false,
Title: "Tinyauth",
GenericName: "Generic",
ForgotPasswordMessage: "Some message",
}
// Simple auth config for tests
var authConfig = types.AuthConfig{
Users: types.Users{},
OauthWhitelist: "",
Secret: "super-secret-api-thing-for-tests", // It is 32 chars long
CookieSecure: false,
SessionExpiry: 3600,
LoginTimeout: 0,
LoginMaxRetries: 0,
}
// Simple hooks config for tests
var hooksConfig = types.HooksConfig{
Domain: "localhost",
}
// Cookie
var cookie string
// User
var user = types.User{
Username: "user",
Password: "$2a$10$AvGHLTYv3xiRJ0xV9xs3XeVIlkGTygI9nqIamFYB5Xu.5.0UWF7B6", // pass
}
// We need all this to be able to test the API
func getAPI(t *testing.T) *api.API {
// Create docker service
docker := docker.NewDocker()
// Initialize docker
err := docker.Init()
// Check if there was an error
if err != nil {
t.Fatalf("Failed to initialize docker: %v", err)
}
// Create auth service
authConfig.Users = types.Users{
{
Username: user.Username,
Password: user.Password,
},
}
auth := auth.NewAuth(authConfig, docker)
// Create providers service
providers := providers.NewProviders(types.OAuthConfig{})
// Initialize providers
providers.Init()
// Create hooks service
hooks := hooks.NewHooks(hooksConfig, auth, providers)
// Create handlers service
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
// Create API
api := api.NewAPI(apiConfig, handlers)
// Setup routes
api.Init()
api.SetupRoutes()
return api
}
// Test login (we will need this for the other tests)
func TestLogin(t *testing.T) {
t.Log("Testing login")
// Get API
api := getAPI(t)
// Create recorder
recorder := httptest.NewRecorder()
// Create request
user := types.LoginRequest{
Username: "user",
Password: "pass",
}
json, err := json.Marshal(user)
// Check if there was an error
if err != nil {
t.Fatalf("Error marshalling json: %v", err)
}
// Create request
req, err := http.NewRequest("POST", "/api/login", strings.NewReader(string(json)))
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Serve the request
api.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
// Get the cookie
cookie = recorder.Result().Cookies()[0].Value
// Check if the cookie is set
if cookie == "" {
t.Fatalf("Cookie not set")
}
}
// Test app context
func TestAppContext(t *testing.T) {
t.Log("Testing app context")
// Get API
api := getAPI(t)
// Create recorder
recorder := httptest.NewRecorder()
// Create request
req, err := http.NewRequest("GET", "/api/app", nil)
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Set the cookie
req.AddCookie(&http.Cookie{
Name: "tinyauth",
Value: cookie,
})
// Serve the request
api.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
// Read the body of the response
body, err := io.ReadAll(recorder.Body)
// Check if there was an error
if err != nil {
t.Fatalf("Error getting body: %v", err)
}
// Unmarshal the body into the user struct
var app types.AppContext
err = json.Unmarshal(body, &app)
// Check if there was an error
if err != nil {
t.Fatalf("Error unmarshalling body: %v", err)
}
// Create tests values
expected := types.AppContext{
Status: 200,
Message: "OK",
ConfiguredProviders: []string{"username"},
DisableContinue: false,
Title: "Tinyauth",
GenericName: "Generic",
ForgotPasswordMessage: "Some message",
}
// We should get the username back
if !reflect.DeepEqual(app, expected) {
t.Fatalf("Expected %v, got %v", expected, app)
}
}
// Test user context
func TestUserContext(t *testing.T) {
t.Log("Testing user context")
// Get API
api := getAPI(t)
// Create recorder
recorder := httptest.NewRecorder()
// Create request
req, err := http.NewRequest("GET", "/api/user", nil)
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Set the cookie
req.AddCookie(&http.Cookie{
Name: "tinyauth",
Value: cookie,
})
// Serve the request
api.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
// Read the body of the response
body, err := io.ReadAll(recorder.Body)
// Check if there was an error
if err != nil {
t.Fatalf("Error getting body: %v", err)
}
// Unmarshal the body into the user struct
type User struct {
Username string `json:"username"`
}
var user User
err = json.Unmarshal(body, &user)
// Check if there was an error
if err != nil {
t.Fatalf("Error unmarshalling body: %v", err)
}
// We should get the username back
if user.Username != "user" {
t.Fatalf("Expected user, got %s", user.Username)
}
}
// Test logout
func TestLogout(t *testing.T) {
t.Log("Testing logout")
// Get API
api := getAPI(t)
// Create recorder
recorder := httptest.NewRecorder()
// Create request
req, err := http.NewRequest("POST", "/api/logout", nil)
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Set the cookie
req.AddCookie(&http.Cookie{
Name: "tinyauth",
Value: cookie,
})
// Serve the request
api.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
// Check if the cookie is different (means go sessions flushed it)
if recorder.Result().Cookies()[0].Value == cookie {
t.Fatalf("Cookie not flushed")
}
}
// TODO: Testing for the oauth stuff

View File

@@ -7,6 +7,7 @@ import (
"sync"
"time"
"tinyauth/internal/docker"
"tinyauth/internal/ldap"
"tinyauth/internal/types"
"tinyauth/internal/utils"
@@ -16,52 +17,151 @@ import (
"golang.org/x/crypto/bcrypt"
)
func NewAuth(config types.AuthConfig, docker *docker.Docker) *Auth {
return &Auth{
Config: config,
Docker: docker,
LoginAttempts: make(map[string]*types.LoginAttempt),
}
}
type Auth struct {
Config types.AuthConfig
Docker *docker.Docker
LoginAttempts map[string]*types.LoginAttempt
LoginMutex sync.RWMutex
Store *sessions.CookieStore
LDAP *ldap.LDAP
}
func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) {
func NewAuth(config types.AuthConfig, docker *docker.Docker, ldap *ldap.LDAP) *Auth {
// Create cookie store
store := sessions.NewCookieStore([]byte(auth.Config.Secret))
store := sessions.NewCookieStore([]byte(config.HMACSecret), []byte(config.EncryptionSecret))
// Configure cookie store
store.Options = &sessions.Options{
Path: "/",
MaxAge: auth.Config.SessionExpiry,
Secure: auth.Config.CookieSecure,
MaxAge: config.SessionExpiry,
Secure: config.CookieSecure,
HttpOnly: true,
Domain: fmt.Sprintf(".%s", auth.Config.Domain),
Domain: fmt.Sprintf(".%s", config.Domain),
}
return &Auth{
Config: config,
Docker: docker,
LoginAttempts: make(map[string]*types.LoginAttempt),
Store: store,
LDAP: ldap,
}
}
func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) {
// Get session
session, err := store.Get(c.Request, "tinyauth")
session, err := auth.Store.Get(c.Request, auth.Config.SessionCookieName)
if err != nil {
log.Error().Err(err).Msg("Failed to get session")
return nil, err
log.Warn().Err(err).Msg("Invalid session, clearing cookie and retrying")
// Delete the session cookie if there is an error
c.SetCookie(auth.Config.SessionCookieName, "", -1, "/", fmt.Sprintf(".%s", auth.Config.Domain), auth.Config.CookieSecure, true)
// Try to get the session again
session, err = auth.Store.Get(c.Request, auth.Config.SessionCookieName)
if err != nil {
// If we still can't get the session, log the error and return nil
log.Error().Err(err).Msg("Failed to get session")
return nil, err
}
}
return session, nil
}
func (auth *Auth) GetUser(username string) *types.User {
func (auth *Auth) SearchUser(username string) types.UserSearch {
// Loop through users and return the user if the username matches
for _, user := range auth.Config.Users {
if user.Username == username {
return &user
log.Debug().Str("username", username).Msg("Searching for user")
if auth.GetLocalUser(username).Username != "" {
log.Debug().Str("username", username).Msg("Found local user")
// If user found, return a user with the username and type "local"
return types.UserSearch{
Username: username,
Type: "local",
}
}
return nil
// If no user found, check LDAP
if auth.LDAP != nil {
log.Debug().Str("username", username).Msg("Checking LDAP for user")
userDN, err := auth.LDAP.Search(username)
if err != nil {
log.Warn().Err(err).Str("username", username).Msg("Failed to find user in LDAP")
return types.UserSearch{}
}
// If user found in LDAP, return a user with the DN as username
return types.UserSearch{
Username: userDN,
Type: "ldap",
}
}
return types.UserSearch{}
}
func (auth *Auth) VerifyUser(search types.UserSearch, password string) bool {
// Authenticate the user based on the type
switch search.Type {
case "local":
// Get local user
user := auth.GetLocalUser(search.Username)
// Check if password is correct
return auth.CheckPassword(user, password)
case "ldap":
// If LDAP is configured, bind to the LDAP server with the user DN and password
if auth.LDAP != nil {
log.Debug().Str("username", search.Username).Msg("Binding to LDAP for user authentication")
// Bind to the LDAP server
err := auth.LDAP.Bind(search.Username, password)
if err != nil {
log.Warn().Err(err).Str("username", search.Username).Msg("Failed to bind to LDAP")
return false
}
// If bind is successful, rebind with the LDAP bind user
err = auth.LDAP.Bind(auth.LDAP.Config.BindDN, auth.LDAP.Config.BindPassword)
if err != nil {
log.Error().Err(err).Msg("Failed to rebind with service account after user authentication")
// Consider closing the connection or creating a new one
return false
}
log.Debug().Str("username", search.Username).Msg("LDAP authentication successful")
// Return true if the bind was successful
return true
}
default:
log.Warn().Str("type", search.Type).Msg("Unknown user type for authentication")
return false
}
// If no user found or authentication failed, return false
log.Warn().Str("username", search.Username).Msg("User authentication failed")
return false
}
func (auth *Auth) GetLocalUser(username string) types.User {
// Loop through users and return the user if the username matches
log.Debug().Str("username", username).Msg("Searching for local user")
for _, user := range auth.Config.Users {
if user.Username == username {
return user
}
}
// If no user found, return an empty user
log.Warn().Str("username", username).Msg("Local user not found")
return types.User{}
}
func (auth *Auth) CheckPassword(user types.User, password string) bool {
@@ -133,8 +233,8 @@ func (auth *Auth) RecordLoginAttempt(identifier string, success bool) {
}
}
func (auth *Auth) EmailWhitelisted(emailSrc string) bool {
return utils.CheckWhitelist(auth.Config.OauthWhitelist, emailSrc)
func (auth *Auth) EmailWhitelisted(email string) bool {
return utils.CheckFilter(auth.Config.OauthWhitelist, email)
}
func (auth *Auth) CreateSessionCookie(c *gin.Context, data *types.SessionCookie) error {
@@ -261,25 +361,25 @@ func (auth *Auth) GetSessionCookie(c *gin.Context) (types.SessionCookie, error)
func (auth *Auth) UserAuthConfigured() bool {
// If there are users, return true
return len(auth.Config.Users) > 0
return len(auth.Config.Users) > 0 || auth.LDAP != nil
}
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.TinyauthLabels) bool {
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.Labels) bool {
// Check if oauth is allowed
if context.OAuth {
log.Debug().Msg("Checking OAuth whitelist")
return utils.CheckWhitelist(labels.OAuthWhitelist, context.Email)
return utils.CheckFilter(labels.OAuth.Whitelist, context.Email)
}
// Check users
log.Debug().Msg("Checking users")
return utils.CheckWhitelist(labels.Users, context.Username)
return utils.CheckFilter(labels.Users, context.Username)
}
func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels types.TinyauthLabels) bool {
func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels types.Labels) bool {
// Check if groups are required
if labels.OAuthGroups == "" {
if labels.OAuth.Groups == "" {
return true
}
@@ -294,7 +394,7 @@ func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels t
// For every group check if it is in the required groups
for _, group := range oauthGroups {
if utils.CheckWhitelist(labels.OAuthGroups, group) {
if utils.CheckFilter(labels.OAuth.Groups, group) {
log.Debug().Str("group", group).Msg("Group is in required groups")
return true
}
@@ -307,7 +407,7 @@ func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels t
return false
}
func (auth *Auth) AuthEnabled(c *gin.Context, labels types.TinyauthLabels) (bool, error) {
func (auth *Auth) AuthEnabled(c *gin.Context, labels types.Labels) (bool, error) {
// Get headers
uri := c.Request.Header.Get("X-Forwarded-Uri")
@@ -351,3 +451,60 @@ func (auth *Auth) GetBasicAuth(c *gin.Context) *types.User {
Password: password,
}
}
func (auth *Auth) CheckIP(labels types.Labels, ip string) bool {
// Check if the IP is in block list
for _, blocked := range labels.IP.Block {
res, err := utils.FilterIP(blocked, ip)
if err != nil {
log.Warn().Err(err).Str("item", blocked).Msg("Invalid IP/CIDR in block list")
continue
}
if res {
log.Warn().Str("ip", ip).Str("item", blocked).Msg("IP is in blocked list, denying access")
return false
}
}
// For every IP in the allow list, check if the IP matches
for _, allowed := range labels.IP.Allow {
res, err := utils.FilterIP(allowed, ip)
if err != nil {
log.Warn().Err(err).Str("item", allowed).Msg("Invalid IP/CIDR in allow list")
continue
}
if res {
log.Debug().Str("ip", ip).Str("item", allowed).Msg("IP is in allowed list, allowing access")
return true
}
}
// If not in allowed range and allowed range is not empty, deny access
if len(labels.IP.Allow) > 0 {
log.Warn().Str("ip", ip).Msg("IP not in allow list, denying access")
return false
}
log.Debug().Str("ip", ip).Msg("IP not in allow or block list, allowing by default")
return true
}
func (auth *Auth) BypassedIP(labels types.Labels, ip string) bool {
// For every IP in the bypass list, check if the IP matches
for _, bypassed := range labels.IP.Bypass {
res, err := utils.FilterIP(bypassed, ip)
if err != nil {
log.Warn().Err(err).Str("item", bypassed).Msg("Invalid IP/CIDR in bypass list")
continue
}
if res {
log.Debug().Str("ip", ip).Str("item", bypassed).Msg("IP is in bypass list, allowing access")
return true
}
}
log.Debug().Str("ip", ip).Msg("IP not in bypass list, continuing with authentication")
return false
}

View File

@@ -18,7 +18,7 @@ func TestLoginRateLimiting(t *testing.T) {
// Initialize a new auth service with 3 max retries and 5 seconds timeout
config.LoginMaxRetries = 3
config.LoginTimeout = 5
authService := auth.NewAuth(config, &docker.Docker{})
authService := auth.NewAuth(config, &docker.Docker{}, nil)
// Test identifier
identifier := "test_user"
@@ -62,7 +62,7 @@ func TestLoginRateLimiting(t *testing.T) {
// Reinitialize auth service with a shorter timeout for testing
config.LoginTimeout = 1
config.LoginMaxRetries = 3
authService = auth.NewAuth(config, &docker.Docker{})
authService = auth.NewAuth(config, &docker.Docker{}, nil)
// Add enough failed attempts to lock the account
for i := 0; i < 3; i++ {
@@ -87,7 +87,7 @@ func TestLoginRateLimiting(t *testing.T) {
t.Log("Testing disabled rate limiting")
config.LoginMaxRetries = 0
config.LoginTimeout = 0
authService = auth.NewAuth(config, &docker.Docker{})
authService = auth.NewAuth(config, &docker.Docker{}, nil)
for i := 0; i < 10; i++ {
authService.RecordLoginAttempt(identifier, false)
@@ -103,7 +103,7 @@ func TestConcurrentLoginAttempts(t *testing.T) {
// Initialize a new auth service with 2 max retries and 5 seconds timeout
config.LoginMaxRetries = 2
config.LoginTimeout = 5
authService := auth.NewAuth(config, &docker.Docker{})
authService := auth.NewAuth(config, &docker.Docker{}, nil)
// Test multiple identifiers
identifiers := []string{"user1", "user2", "user3"}

View File

@@ -1,14 +1,5 @@
package constants
// TinyauthLabels is a list of labels that can be used in a tinyauth protected container
var TinyauthLabels = []string{
"tinyauth.oauth.whitelist",
"tinyauth.users",
"tinyauth.allowed",
"tinyauth.headers",
"tinyauth.oauth.groups",
}
// Claims are the OIDC supported claims (including preferd username for some reason)
type Claims struct {
Name string `json:"name"`
@@ -21,3 +12,8 @@ type Claims struct {
var Version = "development"
var CommitHash = "n/a"
var BuildTimestamp = "n/a"
// Cookie names
var SessionCookieName = "tinyauth-session"
var CsrfCookieName = "tinyauth-csrf"
var RedirectCookieName = "tinyauth-redirect"

View File

@@ -11,35 +11,30 @@ import (
"github.com/rs/zerolog/log"
)
func NewDocker() *Docker {
return &Docker{}
}
type Docker struct {
Client *client.Client
Context context.Context
}
func (docker *Docker) Init() error {
func NewDocker() (*Docker, error) {
// Create a new docker client
client, err := client.NewClientWithOpts(client.FromEnv)
// Check if there was an error
if err != nil {
return err
return nil, err
}
// Create the context
docker.Context = context.Background()
ctx := context.Background()
// Negotiate API version
client.NegotiateAPIVersion(docker.Context)
client.NegotiateAPIVersion(ctx)
// Set client
docker.Client = client
// Done
return nil
return &Docker{
Client: client,
Context: ctx,
}, nil
}
func (docker *Docker) GetContainers() ([]container.Summary, error) {
@@ -74,56 +69,65 @@ func (docker *Docker) DockerConnected() bool {
return err == nil
}
func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) {
func (docker *Docker) GetLabels(app string, domain string) (types.Labels, error) {
// Check if we have access to the Docker API
isConnected := docker.DockerConnected()
// If we don't have access, return an empty struct
if !isConnected {
log.Debug().Msg("Docker not connected, returning empty labels")
return types.TinyauthLabels{}, nil
return types.Labels{}, nil
}
// Get the containers
log.Debug().Msg("Getting containers")
containers, err := docker.GetContainers()
// If there is an error, return false
if err != nil {
return types.TinyauthLabels{}, err
log.Error().Err(err).Msg("Error getting containers")
return types.Labels{}, err
}
log.Debug().Msg("Got containers")
// Loop through the containers
for _, container := range containers {
// Inspect the container
inspect, err := docker.InspectContainer(container.ID)
// If there is an error, return false
// Check if there was an error
if err != nil {
return types.TinyauthLabels{}, err
log.Warn().Str("id", container.ID).Err(err).Msg("Error inspecting container, skipping")
continue
}
// Get the container name (for some reason it is /name)
containerName := strings.TrimPrefix(inspect.Name, "/")
// Get the labels
log.Debug().Str("id", inspect.ID).Msg("Getting labels for container")
// There is a container with the same name as the app ID
if containerName == appId {
log.Debug().Str("container", containerName).Msg("Found container")
labels, err := utils.GetLabels(inspect.Config.Labels)
// Get only the tinyauth labels in a struct
labels := utils.GetTinyauthLabels(inspect.Config.Labels)
// Check if there was an error
if err != nil {
log.Warn().Str("id", container.ID).Err(err).Msg("Error getting container labels, skipping")
continue
}
log.Debug().Msg("Got labels")
// Check if the container matches the ID or domain
for _, lDomain := range labels.Domain {
if lDomain == domain {
log.Debug().Str("id", inspect.ID).Msg("Found matching container by domain")
return labels, nil
}
}
// Return labels
if strings.TrimPrefix(inspect.Name, "/") == app {
log.Debug().Str("id", inspect.ID).Msg("Found matching container by name")
return labels, nil
}
}
log.Debug().Msg("No matching container found, returning empty labels")
// If no matching container is found, return empty labels
return types.TinyauthLabels{}, nil
return types.Labels{}, nil
}

View File

@@ -18,6 +18,14 @@ import (
"github.com/rs/zerolog/log"
)
type Handlers struct {
Config types.HandlersConfig
Auth *auth.Auth
Hooks *hooks.Hooks
Providers *providers.Providers
Docker *docker.Docker
}
func NewHandlers(config types.HandlersConfig, auth *auth.Auth, hooks *hooks.Hooks, providers *providers.Providers, docker *docker.Docker) *Handlers {
return &Handlers{
Config: config,
@@ -28,14 +36,6 @@ func NewHandlers(config types.HandlersConfig, auth *auth.Auth, hooks *hooks.Hook
}
}
type Handlers struct {
Config types.HandlersConfig
Auth *auth.Auth
Hooks *hooks.Hooks
Providers *providers.Providers
Docker *docker.Docker
}
func (h *Handlers) AuthHandler(c *gin.Context) {
// Create struct for proxy
var proxy types.Proxy
@@ -69,11 +69,14 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
proto := c.Request.Header.Get("X-Forwarded-Proto")
host := c.Request.Header.Get("X-Forwarded-Host")
// Get the app id
appId := strings.Split(host, ".")[0]
// Remove the port from the host if it exists
hostPortless := strings.Split(host, ":")[0] // *lol*
// Get the id
id := strings.Split(hostPortless, ".")[0]
// Get the container labels
labels, err := h.Docker.GetLabels(appId)
labels, err := h.Docker.GetLabels(id, hostPortless)
log.Debug().Interface("labels", labels).Msg("Got labels")
@@ -89,7 +92,57 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
return
}
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
// Get client IP
ip := c.ClientIP()
// Check if the IP is in bypass list
if h.Auth.BypassedIP(labels, ip) {
headersParsed := utils.ParseHeaders(labels.Headers)
for key, value := range headersParsed {
log.Debug().Str("key", key).Msg("Setting header")
c.Header(key, value)
}
if labels.Basic.Username != "" && utils.GetSecret(labels.Basic.Password.Plain, labels.Basic.Password.File) != "" {
log.Debug().Str("username", labels.Basic.Username).Msg("Setting basic auth headers")
c.Header("Authorization", fmt.Sprintf("Basic %s", utils.GetBasicAuth(labels.Basic.Username, utils.GetSecret(labels.Basic.Password.Plain, labels.Basic.Password.File))))
}
c.JSON(200, gin.H{
"status": 200,
"message": "Authenticated",
})
return
}
// Check if the IP is allowed/blocked
if !h.Auth.CheckIP(labels, ip) {
if proxy.Proxy == "nginx" || !isBrowser {
c.JSON(403, gin.H{
"status": 403,
"message": "Forbidden",
})
return
}
values := types.UnauthorizedQuery{
Resource: strings.Split(host, ".")[0],
IP: ip,
}
// Build query
queries, err := query.Values(values)
// Handle error
if err != nil {
log.Error().Err(err).Msg("Failed to build queries")
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
return
}
@@ -108,15 +161,20 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
return
}
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
// If auth is not enabled, return 200
if !authEnabled {
for key, value := range labels.Headers {
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
c.Header(key, utils.SanitizeHeader(value))
headersParsed := utils.ParseHeaders(labels.Headers)
for key, value := range headersParsed {
log.Debug().Str("key", key).Msg("Setting header")
c.Header(key, value)
}
if labels.Basic.Username != "" && utils.GetSecret(labels.Basic.Password.Plain, labels.Basic.Password.File) != "" {
log.Debug().Str("username", labels.Basic.Username).Msg("Setting basic auth headers")
c.Header("Authorization", fmt.Sprintf("Basic %s", utils.GetBasicAuth(labels.Basic.Username, utils.GetSecret(labels.Basic.Password.Plain, labels.Basic.Password.File))))
}
c.JSON(200, gin.H{
"status": 200,
@@ -173,12 +231,12 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
// Handle error (no need to check for nginx/headers since we are sure we are using caddy/traefik)
if err != nil {
log.Error().Err(err).Msg("Failed to build queries")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
// We are using caddy/traefik so redirect
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
return
}
@@ -220,12 +278,12 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
// Handle error (no need to check for nginx/headers since we are sure we are using caddy/traefik)
if err != nil {
log.Error().Err(err).Msg("Failed to build queries")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
// We are using caddy/traefik so redirect
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
return
}
}
@@ -236,9 +294,16 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
// Set the rest of the headers
for key, value := range labels.Headers {
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
c.Header(key, utils.SanitizeHeader(value))
parsedHeaders := utils.ParseHeaders(labels.Headers)
for key, value := range parsedHeaders {
log.Debug().Str("key", key).Msg("Setting header")
c.Header(key, value)
}
// Set basic auth headers if configured
if labels.Basic.Username != "" && utils.GetSecret(labels.Basic.Password.Plain, labels.Basic.Password.File) != "" {
log.Debug().Str("username", labels.Basic.Username).Msg("Setting basic auth headers")
c.Header("Authorization", fmt.Sprintf("Basic %s", utils.GetBasicAuth(labels.Basic.Username, utils.GetSecret(labels.Basic.Password.Plain, labels.Basic.Password.File))))
}
// The user is allowed to access the app
@@ -266,7 +331,7 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
if err != nil {
log.Error().Err(err).Msg("Failed to build queries")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
@@ -315,11 +380,13 @@ func (h *Handlers) LoginHandler(c *gin.Context) {
return
}
// Get user based on username
user := h.Auth.GetUser(login.Username)
// Search for a user based on username
userSearch := h.Auth.SearchUser(login.Username)
log.Debug().Interface("userSearch", userSearch).Msg("Searching for user")
// User does not exist
if user == nil {
if userSearch.Type == "" {
log.Debug().Str("username", login.Username).Msg("User not found")
// Record failed login attempt
h.Auth.RecordLoginAttempt(rateIdentifier, false)
@@ -333,7 +400,7 @@ func (h *Handlers) LoginHandler(c *gin.Context) {
log.Debug().Msg("Got user")
// Check if password is correct
if !h.Auth.CheckPassword(*user, login.Password) {
if !h.Auth.VerifyUser(userSearch, login.Password) {
log.Debug().Str("username", login.Username).Msg("Password incorrect")
// Record failed login attempt
h.Auth.RecordLoginAttempt(rateIdentifier, false)
@@ -349,28 +416,34 @@ func (h *Handlers) LoginHandler(c *gin.Context) {
// Record successful login attempt (will reset failed attempt counter)
h.Auth.RecordLoginAttempt(rateIdentifier, true)
// Check if user has totp enabled
if user.TotpSecret != "" {
log.Debug().Msg("Totp enabled")
// Check if user is using TOTP
if userSearch.Type == "local" {
// Get local user
localUser := h.Auth.GetLocalUser(login.Username)
// Set totp pending cookie
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
Username: login.Username,
Name: utils.Capitalize(login.Username),
Email: fmt.Sprintf("%s@%s", strings.ToLower(login.Username), h.Config.Domain),
Provider: "username",
TotpPending: true,
})
// Check if TOTP is enabled
if localUser.TotpSecret != "" {
log.Debug().Msg("Totp enabled")
// Return totp required
c.JSON(200, gin.H{
"status": 200,
"message": "Waiting for totp",
"totpPending": true,
})
// Set totp pending cookie
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
Username: login.Username,
Name: utils.Capitalize(login.Username),
Email: fmt.Sprintf("%s@%s", strings.ToLower(login.Username), h.Config.Domain),
Provider: "username",
TotpPending: true,
})
// Stop further processing
return
// Return totp required
c.JSON(200, gin.H{
"status": 200,
"message": "Waiting for totp",
"totpPending": true,
})
// Stop further processing
return
}
}
// Create session cookie with username as provider
@@ -422,17 +495,7 @@ func (h *Handlers) TotpHandler(c *gin.Context) {
}
// Get user
user := h.Auth.GetUser(userContext.Username)
// Check if user exists
if user == nil {
log.Debug().Msg("User not found")
c.JSON(401, gin.H{
"status": 401,
"message": "Unauthorized",
})
return
}
user := h.Auth.GetLocalUser(userContext.Username)
// Check if totp is correct
ok := totp.Validate(totpReq.Code, user.TotpSecret)
@@ -581,7 +644,7 @@ func (h *Handlers) OauthUrlHandler(c *gin.Context) {
log.Debug().Msg("Got auth URL")
// Set CSRF cookie
c.SetCookie("tinyauth-csrf", state, int(time.Hour.Seconds()), "/", "", h.Config.CookieSecure, true)
c.SetCookie(h.Config.CsrfCookieName, state, int(time.Hour.Seconds()), "/", "", h.Config.CookieSecure, true)
// Get redirect URI
redirectURI := c.Query("redirect_uri")
@@ -589,7 +652,7 @@ func (h *Handlers) OauthUrlHandler(c *gin.Context) {
// Set redirect cookie if redirect URI is provided
if redirectURI != "" {
log.Debug().Str("redirectURI", redirectURI).Msg("Setting redirect cookie")
c.SetCookie("tinyauth-redirect", redirectURI, int(time.Hour.Seconds()), "/", "", h.Config.CookieSecure, true)
c.SetCookie(h.Config.RedirectCookieName, redirectURI, int(time.Hour.Seconds()), "/", "", h.Config.CookieSecure, true)
}
// Return auth URL
@@ -610,7 +673,7 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
// Handle error
if err != nil {
log.Error().Err(err).Msg("Failed to bind URI")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
@@ -620,11 +683,11 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
state := c.Query("state")
// Get CSRF cookie
csrfCookie, err := c.Cookie("tinyauth-csrf")
csrfCookie, err := c.Cookie(h.Config.CsrfCookieName)
if err != nil {
log.Debug().Msg("No CSRF cookie")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
@@ -633,12 +696,12 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
// Check if CSRF cookie is valid
if csrfCookie != state {
log.Warn().Msg("Invalid CSRF cookie or CSRF cookie does not match with the state")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
// Clean up CSRF cookie
c.SetCookie("tinyauth-csrf", "", -1, "/", "", h.Config.CookieSecure, true)
c.SetCookie(h.Config.CsrfCookieName, "", -1, "/", "", h.Config.CookieSecure, true)
// Get code
code := c.Query("code")
@@ -652,7 +715,7 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
// Provider does not exist
if provider == nil {
c.Redirect(http.StatusPermanentRedirect, "/not-found")
c.Redirect(http.StatusTemporaryRedirect, "/not-found")
return
}
@@ -664,7 +727,7 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
// Handle error
if err != nil {
log.Error().Err(err).Msg("Failed to exchange token")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
@@ -674,7 +737,7 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
// Handle error
if err != nil {
log.Error().Msg("Failed to get user")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
@@ -683,7 +746,7 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
// Check that email is not empty
if user.Email == "" {
log.Error().Msg("Email is empty")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
@@ -699,12 +762,12 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
// Handle error
if err != nil {
log.Error().Err(err).Msg("Failed to build queries")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
// Redirect to unauthorized
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
}
log.Debug().Msg("Email whitelisted")
@@ -737,11 +800,11 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
})
// Check if we have a redirect URI
redirectCookie, err := c.Cookie("tinyauth-redirect")
redirectCookie, err := c.Cookie(h.Config.RedirectCookieName)
if err != nil {
log.Debug().Msg("No redirect cookie")
c.Redirect(http.StatusPermanentRedirect, h.Config.AppURL)
c.Redirect(http.StatusTemporaryRedirect, h.Config.AppURL)
return
}
@@ -757,15 +820,15 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
// Handle error
if err != nil {
log.Error().Err(err).Msg("Failed to build queries")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
// Clean up redirect cookie
c.SetCookie("tinyauth-redirect", "", -1, "/", "", h.Config.CookieSecure, true)
c.SetCookie(h.Config.RedirectCookieName, "", -1, "/", "", h.Config.CookieSecure, true)
// Redirect to continue with the redirect URI
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/continue?%s", h.Config.AppURL, queries.Encode()))
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/continue?%s", h.Config.AppURL, queries.Encode()))
}
func (h *Handlers) HealthcheckHandler(c *gin.Context) {

View File

@@ -12,6 +12,12 @@ import (
"github.com/rs/zerolog/log"
)
type Hooks struct {
Config types.HooksConfig
Auth *auth.Auth
Providers *providers.Providers
}
func NewHooks(config types.HooksConfig, auth *auth.Auth, providers *providers.Providers) *Hooks {
return &Hooks{
Config: config,
@@ -20,12 +26,6 @@ func NewHooks(config types.HooksConfig, auth *auth.Auth, providers *providers.Pr
}
}
type Hooks struct {
Config types.HooksConfig
Auth *auth.Auth
Providers *providers.Providers
}
func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
// Get session cookie and basic auth
cookie, err := hooks.Auth.GetSessionCookie(c)
@@ -35,30 +35,49 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
if basic != nil {
log.Debug().Msg("Got basic auth")
// Get user
user := hooks.Auth.GetUser(basic.Username)
// Search for a user based on username
userSearch := hooks.Auth.SearchUser(basic.Username)
// Check we have a user
if user == nil {
if userSearch.Type == "" {
log.Error().Str("username", basic.Username).Msg("User does not exist")
// Return empty context
return types.UserContext{}
}
// Check if the user has a correct password
if hooks.Auth.CheckPassword(*user, basic.Password) {
// Return user context since we are logged in with basic auth
// Verify the user
if !hooks.Auth.VerifyUser(userSearch, basic.Password) {
log.Error().Str("username", basic.Username).Msg("Password incorrect")
// Return empty context
return types.UserContext{}
}
// Get the user type
if userSearch.Type == "ldap" {
log.Debug().Msg("User is LDAP")
return types.UserContext{
Username: basic.Username,
Name: utils.Capitalize(basic.Username),
Email: fmt.Sprintf("%s@%s", strings.ToLower(basic.Username), hooks.Config.Domain),
IsLoggedIn: true,
Provider: "basic",
TotpEnabled: user.TotpSecret != "",
TotpEnabled: false,
}
}
user := hooks.Auth.GetLocalUser(basic.Username)
return types.UserContext{
Username: basic.Username,
Name: utils.Capitalize(basic.Username),
Email: fmt.Sprintf("%s@%s", strings.ToLower(basic.Username), hooks.Config.Domain),
IsLoggedIn: true,
Provider: "basic",
TotpEnabled: user.TotpSecret != "",
}
}
// Check cookie error after basic auth
@@ -85,18 +104,25 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
if cookie.Provider == "username" {
log.Debug().Msg("Provider is username")
// Check if user exists
if hooks.Auth.GetUser(cookie.Username) != nil {
log.Debug().Msg("User exists")
// Search for the user with the username
userSearch := hooks.Auth.SearchUser(cookie.Username)
// It exists so we are logged in
return types.UserContext{
Username: cookie.Username,
Name: cookie.Name,
Email: cookie.Email,
IsLoggedIn: true,
Provider: "username",
}
if userSearch.Type == "" {
log.Error().Str("username", cookie.Username).Msg("User does not exist")
// Return empty context
return types.UserContext{}
}
log.Debug().Str("type", userSearch.Type).Msg("User exists")
// It exists so we are logged in
return types.UserContext{
Username: cookie.Username,
Name: cookie.Name,
Email: cookie.Email,
IsLoggedIn: true,
Provider: "username",
}
}

122
internal/ldap/ldap.go Normal file
View File

@@ -0,0 +1,122 @@
package ldap
import (
"crypto/tls"
"fmt"
"time"
"tinyauth/internal/types"
ldapgo "github.com/go-ldap/ldap/v3"
"github.com/rs/zerolog/log"
)
type LDAP struct {
Config types.LdapConfig
Conn *ldapgo.Conn
}
func NewLDAP(config types.LdapConfig) (*LDAP, error) {
// Create a new LDAP instance with the provided configuration
ldap := &LDAP{
Config: config,
}
// Connect to the LDAP server
if err := ldap.Connect(); err != nil {
return nil, fmt.Errorf("failed to connect to LDAP server: %w", err)
}
// Start heartbeat goroutine
go func() {
for range time.Tick(time.Duration(5) * time.Minute) {
err := ldap.heartbeat()
if err != nil {
log.Error().Err(err).Msg("LDAP connection heartbeat failed")
}
}
}()
return ldap, nil
}
func (l *LDAP) Connect() error {
// Connect to the LDAP server
conn, err := ldapgo.DialURL(l.Config.Address, ldapgo.DialWithTLSConfig(&tls.Config{
InsecureSkipVerify: l.Config.Insecure,
MinVersion: tls.VersionTLS12,
}))
if err != nil {
return err
}
// Bind to the LDAP server with the provided credentials
err = conn.Bind(l.Config.BindDN, l.Config.BindPassword)
if err != nil {
return err
}
// Store the connection in the LDAP struct
l.Conn = conn
return nil
}
func (l *LDAP) Search(username string) (string, error) {
// Escape the username to prevent LDAP injection
escapedUsername := ldapgo.EscapeFilter(username)
filter := fmt.Sprintf(l.Config.SearchFilter, escapedUsername)
// Create a search request to find the user by username
searchRequest := ldapgo.NewSearchRequest(
l.Config.BaseDN,
ldapgo.ScopeWholeSubtree, ldapgo.NeverDerefAliases, 0, 0, false,
filter,
[]string{"dn"},
nil,
)
// Perform the search
searchResult, err := l.Conn.Search(searchRequest)
if err != nil {
return "", err
}
if len(searchResult.Entries) != 1 {
return "", fmt.Errorf("err multiple or no entries found for user %s", username)
}
// User found, return the distinguished name (DN)
userDN := searchResult.Entries[0].DN
return userDN, nil
}
func (l *LDAP) Bind(userDN string, password string) error {
// Bind to the LDAP server with the user's DN and password
err := l.Conn.Bind(userDN, password)
if err != nil {
return err
}
return nil
}
func (l *LDAP) heartbeat() error {
// Perform a simple search to check if the connection is alive
log.Info().Msg("Performing LDAP connection heartbeat")
// Create a search request to find the user by username
searchRequest := ldapgo.NewSearchRequest(
"",
ldapgo.ScopeBaseObject, ldapgo.NeverDerefAliases, 0, 0, false,
"(objectClass=*)",
[]string{},
nil,
)
// Perform the search
_, err := l.Conn.Search(searchRequest)
if err != nil {
return err
}
// No error means the connection is alive
return nil
}

View File

@@ -3,18 +3,13 @@ package oauth
import (
"context"
"crypto/rand"
"crypto/tls"
"encoding/base64"
"net/http"
"golang.org/x/oauth2"
)
func NewOAuth(config oauth2.Config) *OAuth {
return &OAuth{
Config: config,
}
}
type OAuth struct {
Config oauth2.Config
Context context.Context
@@ -22,10 +17,34 @@ type OAuth struct {
Verifier string
}
func (oauth *OAuth) Init() {
// Create a new context and verifier
oauth.Context = context.Background()
oauth.Verifier = oauth2.GenerateVerifier()
func NewOAuth(config oauth2.Config, insecureSkipVerify bool) *OAuth {
// Create transport with TLS
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecureSkipVerify,
MinVersion: tls.VersionTLS12,
},
}
// Create a new context
ctx := context.Background()
// Create the HTTP client with the transport
httpClient := &http.Client{
Transport: transport,
}
// Set the HTTP client in the context
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
// Create the verifier
verifier := oauth2.GenerateVerifier()
return &OAuth{
Config: config,
Context: ctx,
Verifier: verifier,
}
}
func (oauth *OAuth) GetAuthURL(state string) string {

View File

@@ -11,12 +11,6 @@ import (
"golang.org/x/oauth2/endpoints"
)
func NewProviders(config types.OAuthConfig) *Providers {
return &Providers{
Config: config,
}
}
type Providers struct {
Config types.OAuthConfig
Github *oauth.OAuth
@@ -24,60 +18,57 @@ type Providers struct {
Generic *oauth.OAuth
}
func (providers *Providers) Init() {
func NewProviders(config types.OAuthConfig) *Providers {
providers := &Providers{
Config: config,
}
// If we have a client id and secret for github, initialize the oauth provider
if providers.Config.GithubClientId != "" && providers.Config.GithubClientSecret != "" {
if config.GithubClientId != "" && config.GithubClientSecret != "" {
log.Info().Msg("Initializing Github OAuth")
// Create a new oauth provider with the github config
providers.Github = oauth.NewOAuth(oauth2.Config{
ClientID: providers.Config.GithubClientId,
ClientSecret: providers.Config.GithubClientSecret,
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/github", providers.Config.AppURL),
ClientID: config.GithubClientId,
ClientSecret: config.GithubClientSecret,
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/github", config.AppURL),
Scopes: GithubScopes(),
Endpoint: endpoints.GitHub,
})
// Initialize the oauth provider
providers.Github.Init()
}, false)
}
// If we have a client id and secret for google, initialize the oauth provider
if providers.Config.GoogleClientId != "" && providers.Config.GoogleClientSecret != "" {
if config.GoogleClientId != "" && config.GoogleClientSecret != "" {
log.Info().Msg("Initializing Google OAuth")
// Create a new oauth provider with the google config
providers.Google = oauth.NewOAuth(oauth2.Config{
ClientID: providers.Config.GoogleClientId,
ClientSecret: providers.Config.GoogleClientSecret,
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/google", providers.Config.AppURL),
ClientID: config.GoogleClientId,
ClientSecret: config.GoogleClientSecret,
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/google", config.AppURL),
Scopes: GoogleScopes(),
Endpoint: endpoints.Google,
})
// Initialize the oauth provider
providers.Google.Init()
}, false)
}
// If we have a client id and secret for generic oauth, initialize the oauth provider
if providers.Config.GenericClientId != "" && providers.Config.GenericClientSecret != "" {
if config.GenericClientId != "" && config.GenericClientSecret != "" {
log.Info().Msg("Initializing Generic OAuth")
// Create a new oauth provider with the generic config
providers.Generic = oauth.NewOAuth(oauth2.Config{
ClientID: providers.Config.GenericClientId,
ClientSecret: providers.Config.GenericClientSecret,
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/generic", providers.Config.AppURL),
Scopes: providers.Config.GenericScopes,
ClientID: config.GenericClientId,
ClientSecret: config.GenericClientSecret,
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/generic", config.AppURL),
Scopes: config.GenericScopes,
Endpoint: oauth2.Endpoint{
AuthURL: providers.Config.GenericAuthURL,
TokenURL: providers.Config.GenericTokenURL,
AuthURL: config.GenericAuthURL,
TokenURL: config.GenericTokenURL,
},
})
// Initialize the oauth provider
providers.Generic.Init()
}, config.GenericSkipSSL)
}
return providers
}
func (providers *Providers) GetProvider(provider string) *oauth.OAuth {

View File

@@ -1,4 +1,4 @@
package api
package server
import (
"fmt"
@@ -15,20 +15,13 @@ import (
"github.com/rs/zerolog/log"
)
func NewAPI(config types.APIConfig, handlers *handlers.Handlers) *API {
return &API{
Config: config,
Handlers: handlers,
}
}
type API struct {
Config types.APIConfig
Router *gin.Engine
type Server struct {
Config types.ServerConfig
Handlers *handlers.Handlers
Router *gin.Engine
}
func (api *API) Init() {
func NewServer(config types.ServerConfig, handlers *handlers.Handlers) (*Server, error) {
// Disable gin logs
gin.SetMode(gin.ReleaseMode)
@@ -42,7 +35,7 @@ func (api *API) Init() {
dist, err := fs.Sub(assets.Assets, "dist")
if err != nil {
log.Fatal().Err(err).Msg("Failed to get UI assets")
return nil, err
}
// Create file server
@@ -69,41 +62,38 @@ func (api *API) Init() {
}
})
// Set router
api.Router = router
// Proxy routes
router.GET("/api/auth/:proxy", handlers.AuthHandler)
// Auth routes
router.POST("/api/login", handlers.LoginHandler)
router.POST("/api/totp", handlers.TotpHandler)
router.POST("/api/logout", handlers.LogoutHandler)
// Context routes
router.GET("/api/app", handlers.AppHandler)
router.GET("/api/user", handlers.UserHandler)
// OAuth routes
router.GET("/api/oauth/url/:provider", handlers.OauthUrlHandler)
router.GET("/api/oauth/callback/:provider", handlers.OauthCallbackHandler)
// App routes
router.GET("/api/healthcheck", handlers.HealthcheckHandler)
// Return the server
return &Server{
Config: config,
Handlers: handlers,
Router: router,
}, nil
}
func (api *API) SetupRoutes() {
// Proxy
api.Router.GET("/api/auth/:proxy", api.Handlers.AuthHandler)
// Auth
api.Router.POST("/api/login", api.Handlers.LoginHandler)
api.Router.POST("/api/totp", api.Handlers.TotpHandler)
api.Router.POST("/api/logout", api.Handlers.LogoutHandler)
// Context
api.Router.GET("/api/app", api.Handlers.AppHandler)
api.Router.GET("/api/user", api.Handlers.UserHandler)
// OAuth
api.Router.GET("/api/oauth/url/:provider", api.Handlers.OauthUrlHandler)
api.Router.GET("/api/oauth/callback/:provider", api.Handlers.OauthCallbackHandler)
// App
api.Router.GET("/api/healthcheck", api.Handlers.HealthcheckHandler)
}
func (api *API) Run() {
log.Info().Str("address", api.Config.Address).Int("port", api.Config.Port).Msg("Starting server")
func (s *Server) Start() error {
// Run server
err := api.Router.Run(fmt.Sprintf("%s:%d", api.Config.Address, api.Config.Port))
log.Info().Str("address", s.Config.Address).Int("port", s.Config.Port).Msg("Starting server")
// Check for errors
if err != nil {
log.Fatal().Err(err).Msg("Failed to start server")
}
return s.Router.Run(fmt.Sprintf("%s:%d", s.Config.Address, s.Config.Port))
}
// zerolog is a middleware for gin that logs requests using zerolog

View File

@@ -0,0 +1,521 @@
package server_test
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"time"
"tinyauth/internal/auth"
"tinyauth/internal/docker"
"tinyauth/internal/handlers"
"tinyauth/internal/hooks"
"tinyauth/internal/providers"
"tinyauth/internal/server"
"tinyauth/internal/types"
"github.com/magiconair/properties/assert"
"github.com/pquerna/otp/totp"
)
// Simple server config for tests
var serverConfig = types.ServerConfig{
Port: 8080,
Address: "0.0.0.0",
}
// Simple handlers config for tests
var handlersConfig = types.HandlersConfig{
AppURL: "http://localhost:8080",
Domain: "localhost",
DisableContinue: false,
CookieSecure: false,
Title: "Tinyauth",
GenericName: "Generic",
ForgotPasswordMessage: "Message",
CsrfCookieName: "tinyauth-csrf",
RedirectCookieName: "tinyauth-redirect",
BackgroundImage: "https://example.com/image.png",
OAuthAutoRedirect: "none",
}
// Simple auth config for tests
var authConfig = types.AuthConfig{
Users: types.Users{},
OauthWhitelist: "",
HMACSecret: "4bZ9K.*:;zH=,9zG!meUxu.B5-S[7.V.", // Complex on purpose
EncryptionSecret: "\\:!R(u[Sbv6ZLm.7es)H|OqH4y}0u\\rj",
CookieSecure: false,
SessionExpiry: 3600,
LoginTimeout: 0,
LoginMaxRetries: 0,
SessionCookieName: "tinyauth-session",
Domain: "localhost",
}
// Simple hooks config for tests
var hooksConfig = types.HooksConfig{
Domain: "localhost",
}
// Cookie
var cookie = "MTc1MTkyMzM5MnxiME9aTzlGQjZMNEJMdDZMc0lHMk9zcXQyME9SR1ZnUmlaYWZNcWplek5vcVNpdkdHRTZqb09YWkVUYUN6NEt4MkEyOGEyX2hFQWZEUEYtbllDX0h5eDBCb3VyT2phQlRpZWFfRFdTMGw2WUg2VWw4RGdNbEhQclotOUJjblJGaWFQcmhyaWFna0dXRWNud2c1akg5eEpLZ3JzS0pfWktscVZyckZFR1VDX0R5QjFOT0hzMTNKb18ySEMxZlluSWNxa1ByM0VhSzNyMkRtdDNORWJXVGFYSnMzWjFGa0lrZlhSTWduRmttMHhQUXN4UFhNbHFXY0lBWjBnUWpKU0xXMHRubjlKbjV0LXBGdjk0MmpJX0xMX1ZYblVJVW9LWUJoWmpNanVXNkNjamhYWlR2V29rY0RNYWkxY2lMQnpqLUI2cHMyYTZkWWgtWnlFdGN0amh2WURUeUNGT3ZLS1FJVUFIb0NWR1RPMlRtY2c9PXwerwFtb9urOXnwA02qXbLeorMloaK_paQd0in4BAesmg=="
// User
var user = types.User{
Username: "user",
Password: "$2a$10$AvGHLTYv3xiRJ0xV9xs3XeVIlkGTygI9nqIamFYB5Xu.5.0UWF7B6", // pass
}
// Initialize the server for tests
func getServer(t *testing.T) *server.Server {
// Create docker service
docker, err := docker.NewDocker()
if err != nil {
t.Fatalf("Failed to initialize docker: %v", err)
}
// Create auth service
authConfig.Users = types.Users{
{
Username: user.Username,
Password: user.Password,
TotpSecret: user.TotpSecret,
},
}
auth := auth.NewAuth(authConfig, docker, nil)
// Create providers service
providers := providers.NewProviders(types.OAuthConfig{})
// Create hooks service
hooks := hooks.NewHooks(hooksConfig, auth, providers)
// Create handlers service
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
// Create server
srv, err := server.NewServer(serverConfig, handlers)
if err != nil {
t.Fatalf("Failed to create server: %v", err)
}
// Return the server
return srv
}
// Test login
func TestLogin(t *testing.T) {
t.Log("Testing login")
// Get server
srv := getServer(t)
// Create recorder
recorder := httptest.NewRecorder()
// Create request
user := types.LoginRequest{
Username: "user",
Password: "pass",
}
json, err := json.Marshal(user)
// Check if there was an error
if err != nil {
t.Fatalf("Error marshalling json: %v", err)
}
// Create request
req, err := http.NewRequest("POST", "/api/login", strings.NewReader(string(json)))
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Serve the request
srv.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
// Get the result cookie
cookies := recorder.Result().Cookies()
// Check if the cookie is set
if len(cookies) == 0 {
t.Fatalf("Cookie not set")
}
// Set the cookie for further tests
cookie = cookies[0].Value
}
// Test app context
func TestAppContext(t *testing.T) {
t.Log("Testing app context")
// Get server
srv := getServer(t)
// Create recorder
recorder := httptest.NewRecorder()
// Create request
req, err := http.NewRequest("GET", "/api/app", nil)
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Set the cookie
req.AddCookie(&http.Cookie{
Name: "tinyauth",
Value: cookie,
})
// Serve the request
srv.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
// Read the body of the response
body, err := io.ReadAll(recorder.Body)
// Check if there was an error
if err != nil {
t.Fatalf("Error getting body: %v", err)
}
// Unmarshal the body into the user struct
var app types.AppContext
err = json.Unmarshal(body, &app)
// Check if there was an error
if err != nil {
t.Fatalf("Error unmarshalling body: %v", err)
}
// Create tests values
expected := types.AppContext{
Status: 200,
Message: "OK",
ConfiguredProviders: []string{"username"},
DisableContinue: false,
Title: "Tinyauth",
GenericName: "Generic",
ForgotPasswordMessage: "Message",
BackgroundImage: "https://example.com/image.png",
OAuthAutoRedirect: "none",
Domain: "localhost",
}
// We should get the username back
if !reflect.DeepEqual(app, expected) {
t.Fatalf("Expected %v, got %v", expected, app)
}
}
// Test user context
func TestUserContext(t *testing.T) {
// Refresh the cookie
TestLogin(t)
t.Log("Testing user context")
// Get server
srv := getServer(t)
// Create recorder
recorder := httptest.NewRecorder()
// Create request
req, err := http.NewRequest("GET", "/api/user", nil)
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Set the cookie
req.AddCookie(&http.Cookie{
Name: "tinyauth-session",
Value: cookie,
})
// Serve the request
srv.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
// Read the body of the response
body, err := io.ReadAll(recorder.Body)
// Check if there was an error
if err != nil {
t.Fatalf("Error getting body: %v", err)
}
// Unmarshal the body into the user struct
type User struct {
Username string `json:"username"`
}
var user User
err = json.Unmarshal(body, &user)
// Check if there was an error
if err != nil {
t.Fatalf("Error unmarshalling body: %v", err)
}
// We should get the username back
if user.Username != "user" {
t.Fatalf("Expected user, got %s", user.Username)
}
}
// Test logout
func TestLogout(t *testing.T) {
// Refresh the cookie
TestLogin(t)
t.Log("Testing logout")
// Get server
srv := getServer(t)
// Create recorder
recorder := httptest.NewRecorder()
// Create request
req, err := http.NewRequest("POST", "/api/logout", nil)
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Set the cookie
req.AddCookie(&http.Cookie{
Name: "tinyauth-session",
Value: cookie,
})
// Serve the request
srv.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
// Check if the cookie is different (means the cookie is gone)
if recorder.Result().Cookies()[0].Value == cookie {
t.Fatalf("Cookie not flushed")
}
}
// Test auth endpoint
func TestAuth(t *testing.T) {
// Refresh the cookie
TestLogin(t)
t.Log("Testing auth endpoint")
// Get server
srv := getServer(t)
// Create recorder
recorder := httptest.NewRecorder()
// Create request
req, err := http.NewRequest("GET", "/api/auth/traefik", nil)
// Set the accept header
req.Header.Set("Accept", "text/html")
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Serve the request
srv.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusTemporaryRedirect)
// Recreate recorder
recorder = httptest.NewRecorder()
// Recreate the request
req, err = http.NewRequest("GET", "/api/auth/traefik", nil)
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Test with the cookie
req.AddCookie(&http.Cookie{
Name: "tinyauth-session",
Value: cookie,
})
// Serve the request again
srv.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
// Recreate recorder
recorder = httptest.NewRecorder()
// Recreate the request
req, err = http.NewRequest("GET", "/api/auth/nginx", nil)
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Serve the request again
srv.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusUnauthorized)
// Recreate recorder
recorder = httptest.NewRecorder()
// Recreate the request
req, err = http.NewRequest("GET", "/api/auth/nginx", nil)
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Test with the cookie
req.AddCookie(&http.Cookie{
Name: "tinyauth-session",
Value: cookie,
})
// Serve the request again
srv.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
}
func TestTOTP(t *testing.T) {
t.Log("Testing TOTP")
// Generate totp secret
key, err := totp.Generate(totp.GenerateOpts{
Issuer: "Tinyauth",
AccountName: user.Username,
})
if err != nil {
t.Fatalf("Failed to generate TOTP secret: %v", err)
}
// Create secret
secret := key.Secret()
// Set the user's TOTP secret
user.TotpSecret = secret
// Get server
srv := getServer(t)
// Create request
user := types.LoginRequest{
Username: "user",
Password: "pass",
}
loginJson, err := json.Marshal(user)
// Check if there was an error
if err != nil {
t.Fatalf("Error marshalling json: %v", err)
}
// Create recorder
recorder := httptest.NewRecorder()
// Create request
req, err := http.NewRequest("POST", "/api/login", strings.NewReader(string(loginJson)))
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Serve the request
srv.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
// Set the cookie for next test
cookie = recorder.Result().Cookies()[0].Value
// Create TOTP code
code, err := totp.GenerateCode(secret, time.Now())
// Check if there was an error
if err != nil {
t.Fatalf("Failed to generate TOTP code: %v", err)
}
// Create TOTP request
totpRequest := types.TotpRequest{
Code: code,
}
// Marshal the TOTP request
totpJson, err := json.Marshal(totpRequest)
// Check if there was an error
if err != nil {
t.Fatalf("Error marshalling TOTP request: %v", err)
}
// Create recorder
recorder = httptest.NewRecorder()
// Create request
req, err = http.NewRequest("POST", "/api/totp", strings.NewReader(string(totpJson)))
// Check if there was an error
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Set the cookie
req.AddCookie(&http.Cookie{
Name: "tinyauth-session",
Value: cookie,
})
// Serve the request
srv.Router.ServeHTTP(recorder, req)
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
}

View File

@@ -21,6 +21,7 @@ type UnauthorizedQuery struct {
Username string `url:"username"`
Resource string `url:"resource"`
GroupErr bool `url:"groupErr"`
IP string `url:"ip"`
}
// Proxy is the uri parameters for the proxy endpoint

View File

@@ -24,6 +24,7 @@ type Config struct {
GenericTokenURL string `mapstructure:"generic-token-url"`
GenericUserURL string `mapstructure:"generic-user-url"`
GenericName string `mapstructure:"generic-name"`
GenericSkipSSL bool `mapstructure:"generic-skip-ssl"`
DisableContinue bool `mapstructure:"disable-continue"`
OAuthWhitelist string `mapstructure:"oauth-whitelist"`
OAuthAutoRedirect string `mapstructure:"oauth-auto-redirect" validate:"oneof=none github google generic"`
@@ -33,8 +34,14 @@ type Config struct {
EnvFile string `mapstructure:"env-file"`
LoginTimeout int `mapstructure:"login-timeout"`
LoginMaxRetries int `mapstructure:"login-max-retries"`
FogotPasswordMessage string `mapstructure:"forgot-password-message" validate:"required"`
FogotPasswordMessage string `mapstructure:"forgot-password-message"`
BackgroundImage string `mapstructure:"background-image" validate:"required"`
LdapAddress string `mapstructure:"ldap-address"`
LdapBindDN string `mapstructure:"ldap-bind-dn"`
LdapBindPassword string `mapstructure:"ldap-bind-password"`
LdapBaseDN string `mapstructure:"ldap-base-dn"`
LdapInsecure bool `mapstructure:"ldap-insecure"`
LdapSearchFilter string `mapstructure:"ldap-search-filter"`
}
// Server configuration
@@ -48,6 +55,8 @@ type HandlersConfig struct {
ForgotPasswordMessage string
BackgroundImage string
OAuthAutoRedirect string
CsrfCookieName string
RedirectCookieName string
}
// OAuthConfig is the configuration for the providers
@@ -62,28 +71,77 @@ type OAuthConfig struct {
GenericAuthURL string
GenericTokenURL string
GenericUserURL string
GenericSkipSSL bool
AppURL string
}
// APIConfig is the configuration for the API
type APIConfig struct {
// ServerConfig is the configuration for the server
type ServerConfig struct {
Port int
Address string
}
// AuthConfig is the configuration for the auth service
type AuthConfig struct {
Users Users
OauthWhitelist string
SessionExpiry int
Secret string
CookieSecure bool
Domain string
LoginTimeout int
LoginMaxRetries int
Users Users
OauthWhitelist string
SessionExpiry int
CookieSecure bool
Domain string
LoginTimeout int
LoginMaxRetries int
SessionCookieName string
HMACSecret string
EncryptionSecret string
}
// HooksConfig is the configuration for the hooks service
type HooksConfig struct {
Domain string
}
// OAuthLabels is a list of labels that can be used in a tinyauth protected container
type OAuthLabels struct {
Whitelist string
Groups string
}
// Basic auth labels for a tinyauth protected container
type BasicLabels struct {
Username string
Password PassowrdLabels
}
// PassowrdLabels is a struct that contains the password labels for a tinyauth protected container
type PassowrdLabels struct {
Plain string
File string
}
// IP labels for a tinyauth protected container
type IPLabels struct {
Allow []string
Block []string
Bypass []string
}
// Labels is a struct that contains the labels for a tinyauth protected container
type Labels struct {
Users string
Allowed string
Headers []string
Domain []string
Basic BasicLabels
OAuth OAuthLabels
IP IPLabels
}
// Ldap config is a struct that contains the configuration for the LDAP service
type LdapConfig struct {
Address string
BindDN string
BindPassword string
BaseDN string
Insecure bool
SearchFilter string
}

View File

@@ -12,6 +12,12 @@ type User struct {
TotpSecret string
}
// UserSearch is the response of the get user
type UserSearch struct {
Username string
Type string // "local", "ldap" or empty
}
// Users is a list of users
type Users []User
@@ -32,15 +38,6 @@ type SessionCookie struct {
OAuthGroups string
}
// TinyauthLabels is the labels for the tinyauth container
type TinyauthLabels struct {
OAuthWhitelist string
Users string
Allowed string
Headers map[string]string
OAuthGroups string
}
// UserContext is the context for the user
type UserContext struct {
Username string

View File

@@ -1,15 +1,22 @@
package utils
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"errors"
"io"
"net"
"net/url"
"os"
"regexp"
"slices"
"strings"
"tinyauth/internal/constants"
"tinyauth/internal/types"
"github.com/traefik/paerser/parser"
"golang.org/x/crypto/hkdf"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
)
@@ -173,45 +180,43 @@ func GetUsers(conf string, file string) (types.Users, error) {
return ParseUsers(users)
}
// Parse the docker labels to the tinyauth labels struct
func GetTinyauthLabels(labels map[string]string) types.TinyauthLabels {
// Create a new tinyauth labels struct
var tinyauthLabels types.TinyauthLabels
// Parse the headers in a map[string]string format
func ParseHeaders(headers []string) map[string]string {
// Create a map to store the headers
headerMap := make(map[string]string)
// Loop through the labels
for label, value := range labels {
// Check if the label is in the tinyauth labels
if slices.Contains(constants.TinyauthLabels, label) {
log.Debug().Str("label", label).Msg("Found label")
// Add the label value to the tinyauth labels struct
switch label {
case "tinyauth.oauth.whitelist":
tinyauthLabels.OAuthWhitelist = value
case "tinyauth.users":
tinyauthLabels.Users = value
case "tinyauth.allowed":
tinyauthLabels.Allowed = value
case "tinyauth.headers":
tinyauthLabels.Headers = make(map[string]string)
headers := strings.Split(value, ",")
for _, header := range headers {
headerSplit := strings.Split(header, "=")
if len(headerSplit) != 2 {
continue
}
tinyauthLabels.Headers[headerSplit[0]] = headerSplit[1]
}
case "tinyauth.oauth.groups":
tinyauthLabels.OAuthGroups = value
}
// Loop through the headers
for _, header := range headers {
split := strings.SplitN(header, "=", 2)
if len(split) != 2 || strings.TrimSpace(split[0]) == "" || strings.TrimSpace(split[1]) == "" {
log.Warn().Str("header", header).Msg("Invalid header format, skipping")
continue
}
key := SanitizeHeader(strings.TrimSpace(split[0]))
value := SanitizeHeader(strings.TrimSpace(split[1]))
headerMap[key] = value
}
// Return the tinyauth labels
return tinyauthLabels
// Return the header map
return headerMap
}
// Get labels parses a map of labels into a struct with only the needed labels
func GetLabels(labels map[string]string) (types.Labels, error) {
// Create a new labels struct
var labelsParsed types.Labels
// Decode the labels into the labels struct
err := parser.Decode(labels, &labelsParsed, "tinyauth", "tinyauth.users", "tinyauth.allowed", "tinyauth.headers", "tinyauth.domain", "tinyauth.basic", "tinyauth.oauth", "tinyauth.ip")
// Check if there was an error
if err != nil {
log.Error().Err(err).Msg("Error parsing labels")
return types.Labels{}, err
}
// Return the labels struct
return labelsParsed, nil
}
// Check if any of the OAuth providers are configured based on the client id and secret
@@ -254,16 +259,16 @@ func ParseUser(user string) (types.User, error) {
// Check if the user has a totp secret
if len(userSplit) == 2 {
return types.User{
Username: userSplit[0],
Password: userSplit[1],
Username: strings.TrimSpace(userSplit[0]),
Password: strings.TrimSpace(userSplit[1]),
}, nil
}
// Return the user struct
return types.User{
Username: userSplit[0],
Password: userSplit[1],
TotpSecret: userSplit[2],
Username: strings.TrimSpace(userSplit[0]),
Password: strings.TrimSpace(userSplit[1]),
TotpSecret: strings.TrimSpace(userSplit[2]),
}, nil
}
@@ -287,17 +292,17 @@ func ParseSecretFile(contents string) string {
return ""
}
// Check if a string matches a regex or a whitelist
func CheckWhitelist(whitelist string, str string) bool {
// Check if the whitelist is empty
if len(strings.TrimSpace(whitelist)) == 0 {
// Check if a string matches a regex or if it is included in a comma separated list
func CheckFilter(filter string, str string) bool {
// Check if the filter is empty
if len(strings.TrimSpace(filter)) == 0 {
return true
}
// Check if the whitelist is a regex
if strings.HasPrefix(whitelist, "/") && strings.HasSuffix(whitelist, "/") {
// Check if the filter is a regex
if strings.HasPrefix(filter, "/") && strings.HasSuffix(filter, "/") {
// Create regex
re, err := regexp.Compile(whitelist[1 : len(whitelist)-1])
re, err := regexp.Compile(filter[1 : len(filter)-1])
// Check if there was an error
if err != nil {
@@ -311,11 +316,11 @@ func CheckWhitelist(whitelist string, str string) bool {
}
}
// Split the whitelist by comma
whitelistSplit := strings.Split(whitelist, ",")
// Split the filter by comma
filterSplit := strings.Split(filter, ",")
// Loop through the whitelist
for _, item := range whitelistSplit {
// Loop through the filter items
for _, item := range filterSplit {
// Check if the item matches with the string
if strings.TrimSpace(item) == str {
return true
@@ -344,3 +349,92 @@ func SanitizeHeader(header string) string {
return -1
}, header)
}
// Generate a static identifier from a string
func GenerateIdentifier(str string) string {
// Create a new UUID
uuid := uuid.NewSHA1(uuid.NameSpaceURL, []byte(str))
// Convert the UUID to a string
uuidString := uuid.String()
// Show the UUID
log.Debug().Str("uuid", uuidString).Msg("Generated UUID")
// Convert the UUID to a string
return strings.Split(uuidString, "-")[0]
}
// Get a basic auth header from a username and password
func GetBasicAuth(username string, password string) string {
// Create the auth string
auth := username + ":" + password
// Encode the auth string to base64
return base64.StdEncoding.EncodeToString([]byte(auth))
}
// Check if an IP is contained in a CIDR range/matches a single IP
func FilterIP(filter string, ip string) (bool, error) {
// Convert the check IP to an IP instance
ipAddr := net.ParseIP(ip)
// Check if the filter is a CIDR range
if strings.Contains(filter, "/") {
// Parse the CIDR range
_, cidr, err := net.ParseCIDR(filter)
// Check if there was an error
if err != nil {
return false, err
}
// Check if the IP is in the CIDR range
return cidr.Contains(ipAddr), nil
}
// Parse the filter as a single IP
ipFilter := net.ParseIP(filter)
// Check if the IP is valid
if ipFilter == nil {
return false, errors.New("invalid IP address in filter")
}
// Check if the IP matches the filter
if ipFilter.Equal(ipAddr) {
return true, nil
}
// If the filter is not a CIDR range or a single IP, return false
return false, nil
}
func DeriveKey(secret string, info string) (string, error) {
// Create hashing function
hash := sha256.New
// Create a new key using the secret and info
hkdf := hkdf.New(hash, []byte(secret), nil, []byte(info)) // I am not using a salt because I just want two different keys from one secret, maybe bad practice
// Create a new key
key := make([]byte, 24)
// Read the key from the HKDF
_, err := io.ReadFull(hkdf, key)
if err != nil {
return "", err
}
// Verify the key is not empty
if bytes.Equal(key, make([]byte, 24)) {
return "", errors.New("derived key is empty")
}
// Encode the key to base64
encodedKey := base64.StdEncoding.EncodeToString(key)
// Return the key as a base64 encoded string
return encodedKey, nil
}

View File

@@ -279,48 +279,35 @@ func TestGetUsers(t *testing.T) {
}
}
// Test the tinyauth labels function
func TestGetTinyauthLabels(t *testing.T) {
t.Log("Testing get tinyauth labels with a valid map")
// Test the get labels function
func TestGetLabels(t *testing.T) {
t.Log("Testing get labels with a valid map")
// Test the get tinyauth labels function with a valid map
labels := map[string]string{
"tinyauth.users": "user1,user2",
"tinyauth.oauth.whitelist": "/regex/",
"tinyauth.allowed": "random",
"random": "random",
"tinyauth.headers": "X-Header=value",
"tinyauth.oauth.groups": "group1,group2",
}
expected := types.TinyauthLabels{
Users: "user1,user2",
OAuthWhitelist: "/regex/",
Allowed: "random",
Headers: map[string]string{
"X-Header": "value",
expected := types.Labels{
Users: "user1,user2",
Allowed: "random",
Headers: []string{"X-Header=value"},
OAuth: types.OAuthLabels{
Whitelist: "/regex/",
Groups: "group1,group2",
},
}
result := utils.GetTinyauthLabels(labels)
result, err := utils.GetLabels(labels)
// Check if the result is equal to the expected
if !reflect.DeepEqual(expected, result) {
t.Fatalf("Expected %v, got %v", expected, result)
// Check if there was an error
if err != nil {
t.Fatalf("Error getting labels: %v", err)
}
}
// Test the filter function
func TestFilter(t *testing.T) {
t.Log("Testing filter helper")
// Create variables
data := []string{"", "val1", "", "val2", "", "val3", ""}
expected := []string{"val1", "val2", "val3"}
// Test the filter function
result := utils.Filter(data, func(val string) bool {
return val != ""
})
// Check if the result is equal to the expected
if !reflect.DeepEqual(expected, result) {
@@ -390,108 +377,77 @@ func TestParseUser(t *testing.T) {
}
}
// Test the whitelist function
func TestCheckWhitelist(t *testing.T) {
t.Log("Testing check whitelist with a comma whitelist")
// Test the check filter function
func TestCheckFilter(t *testing.T) {
t.Log("Testing check filter with a comma separated list")
// Create variables
whitelist := "user1,user2,user3"
filter := "user1,user2,user3"
str := "user1"
expected := true
// Test the check whitelist function
result := utils.CheckWhitelist(whitelist, str)
// Test the check filter function
result := utils.CheckFilter(filter, str)
// Check if the result is equal to the expected
if result != expected {
t.Fatalf("Expected %v, got %v", expected, result)
}
t.Log("Testing check whitelist with a regex whitelist")
t.Log("Testing check filter with a regex filter")
// Create variables
whitelist = "/^user[0-9]+$/"
filter = "/^user[0-9]+$/"
str = "user1"
expected = true
// Test the check whitelist function
result = utils.CheckWhitelist(whitelist, str)
// Test the check filter function
result = utils.CheckFilter(filter, str)
// Check if the result is equal to the expected
if result != expected {
t.Fatalf("Expected %v, got %v", expected, result)
}
t.Log("Testing check whitelist with an empty whitelist")
t.Log("Testing check filter with an empty filter")
// Create variables
whitelist = ""
filter = ""
str = "user1"
expected = true
// Test the check whitelist function
result = utils.CheckWhitelist(whitelist, str)
// Test the check filter function
result = utils.CheckFilter(filter, str)
// Check if the result is equal to the expected
if result != expected {
t.Fatalf("Expected %v, got %v", expected, result)
}
t.Log("Testing check whitelist with an invalid regex whitelist")
t.Log("Testing check filter with an invalid regex filter")
// Create variables
whitelist = "/^user[0-9+$/"
filter = "/^user[0-9+$/"
str = "user1"
expected = false
// Test the check whitelist function
result = utils.CheckWhitelist(whitelist, str)
// Test the check filter function
result = utils.CheckFilter(filter, str)
// Check if the result is equal to the expected
if result != expected {
t.Fatalf("Expected %v, got %v", expected, result)
}
t.Log("Testing check whitelist with a non matching whitelist")
t.Log("Testing check filter with a non matching list")
// Create variables
whitelist = "user1,user2,user3"
filter = "user1,user2,user3"
str = "user4"
expected = false
// Test the check whitelist function
result = utils.CheckWhitelist(whitelist, str)
// Check if the result is equal to the expected
if result != expected {
t.Fatalf("Expected %v, got %v", expected, result)
}
}
// Test capitalize
func TestCapitalize(t *testing.T) {
t.Log("Testing capitalize with a valid string")
// Create variables
str := "test"
expected := "Test"
// Test the capitalize function
result := utils.Capitalize(str)
// Check if the result is equal to the expected
if result != expected {
t.Fatalf("Expected %v, got %v", expected, result)
}
t.Log("Testing capitalize with an empty string")
// Create variables
str = ""
expected = ""
// Test the capitalize function
result = utils.Capitalize(str)
// Test the check filter function
result = utils.CheckFilter(filter, str)
// Check if the result is equal to the expected
if result != expected {
@@ -529,3 +485,170 @@ func TestSanitizeHeader(t *testing.T) {
t.Fatalf("Expected %v, got %v", expected, result)
}
}
// Test the parse headers function
func TestParseHeaders(t *testing.T) {
t.Log("Testing parse headers with a valid string")
// Create variables
headers := []string{"X-Hea\x00der1=value1", "X-Header2=value\n2"}
expected := map[string]string{
"X-Header1": "value1",
"X-Header2": "value2",
}
// Test the parse headers function
result := utils.ParseHeaders(headers)
// Check if the result is equal to the expected
if !reflect.DeepEqual(expected, result) {
t.Fatalf("Expected %v, got %v", expected, result)
}
t.Log("Testing parse headers with an invalid string")
// Create variables
headers = []string{"X-Header1=", "X-Header2", "=value", "X-Header3=value3"}
expected = map[string]string{"X-Header3": "value3"}
// Test the parse headers function
result = utils.ParseHeaders(headers)
// Check if the result is equal to the expected
if !reflect.DeepEqual(expected, result) {
t.Fatalf("Expected %v, got %v", expected, result)
}
}
// Test the parse secret file function
func TestParseSecretFile(t *testing.T) {
t.Log("Testing parse secret file with a valid file")
// Create variables
content := "\n\n \n\n\n secret \n\n \n "
expected := "secret"
// Test the parse secret file function
result := utils.ParseSecretFile(content)
// Check if the result is equal to the expected
if result != expected {
t.Fatalf("Expected %v, got %v", expected, result)
}
}
// Test the filter IP function
func TestFilterIP(t *testing.T) {
t.Log("Testing filter IP with an IP and a valid CIDR")
// Create variables
ip := "10.10.10.10"
filter := "10.10.10.0/24"
expected := true
// Test the filter IP function
result, err := utils.FilterIP(filter, ip)
// Check if there was an error
if err != nil {
t.Fatalf("Error filtering IP: %v", err)
}
// Check if the result is equal to the expected
if result != expected {
t.Fatalf("Expected %v, got %v", expected, result)
}
t.Log("Testing filter IP with an IP and a valid IP")
// Create variables
filter = "10.10.10.10"
expected = true
// Test the filter IP function
result, err = utils.FilterIP(filter, ip)
// Check if there was an error
if err != nil {
t.Fatalf("Error filtering IP: %v", err)
}
// Check if the result is equal to the expected
if result != expected {
t.Fatalf("Expected %v, got %v", expected, result)
}
t.Log("Testing filter IP with an IP and an non matching CIDR")
// Create variables
filter = "10.10.15.0/24"
expected = false
// Test the filter IP function
result, err = utils.FilterIP(filter, ip)
// Check if there was an error
if err != nil {
t.Fatalf("Error filtering IP: %v", err)
}
// Check if the result is equal to the expected
if result != expected {
t.Fatalf("Expected %v, got %v", expected, result)
}
t.Log("Testing filter IP with a non matching IP and a valid CIDR")
// Create variables
filter = "10.10.10.11"
expected = false
// Test the filter IP function
result, err = utils.FilterIP(filter, ip)
// Check if there was an error
if err != nil {
t.Fatalf("Error filtering IP: %v", err)
}
// Check if the result is equal to the expected
if result != expected {
t.Fatalf("Expected %v, got %v", expected, result)
}
t.Log("Testing filter IP with an IP and an invalid CIDR")
// Create variables
filter = "10.../83"
// Test the filter IP function
_, err = utils.FilterIP(filter, ip)
// Check if there was an error
if err == nil {
t.Fatalf("Expected error filtering IP")
}
}
// Test the derive key function
func TestDeriveKey(t *testing.T) {
t.Log("Testing the derive key function")
// Create variables
master := "master"
info := "info"
expected := "gdrdU/fXzclYjiSXRexEatVgV13qQmKl"
// Test the derive key function
result, err := utils.DeriveKey(master, info)
// Check if there was an error
if err != nil {
t.Fatalf("Error deriving key: %v", err)
}
// Check if the result is equal to the expected
if result != expected {
t.Fatalf("Expected %v, got %v", expected, result)
}
}