mirror of
https://github.com/tinyauthapp/tinyauth.git
synced 2026-08-14 05:23:31 +08:00
Compare commits
11
Commits
v5.1.3-rc.1
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61f65cfaa7 | ||
|
|
46c8c58f45 | ||
|
|
5f03ccbcc8 | ||
|
|
03841cf838 | ||
|
|
37272e4f4a | ||
|
|
e4bc5e9f42 | ||
|
|
20595b6793 | ||
|
|
01d316f27f | ||
|
|
e0394781f7 | ||
|
|
a0d24cf566 | ||
|
|
f9939538b9 |
@@ -16,7 +16,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
with:
|
||||
package_json_file: ./frontend/package.json
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
name: Run e2e tests
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # ratchet:pnpm/action-setup@v6
|
||||
with:
|
||||
package_json_file: ./e2e/package.json
|
||||
|
||||
- name: Set up Docker
|
||||
uses: docker/setup-docker-action@6d7cfa65f60a9dda7b46e5513fa982536f3c9877 # ratchet:docker/setup-docker-action@v5
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm ci
|
||||
working-directory: e2e
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm exec playwright install --with-deps
|
||||
working-directory: e2e
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: pnpm test
|
||||
working-directory: e2e
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # ratchet:actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-report
|
||||
path: e2e/playwright-report/
|
||||
retention-days: 5
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
ref: nightly
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
with:
|
||||
package_json_file: ./frontend/package.json
|
||||
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
ref: nightly
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
with:
|
||||
package_json_file: ./frontend/package.json
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
with:
|
||||
package_json_file: ./frontend/package.json
|
||||
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
with:
|
||||
package_json_file: ./frontend/package.json
|
||||
|
||||
|
||||
@@ -38,6 +38,6 @@ jobs:
|
||||
retention-days: 5
|
||||
|
||||
- name: Upload to code-scanning
|
||||
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4
|
||||
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Generate Sponsors
|
||||
uses: JamesIves/github-sponsors-readme-action@2fd9142e765f755780202122261dc85e78459405 # v1
|
||||
uses: JamesIves/github-sponsors-readme-action@02650b8cd445fc16dfef73195f9c406dce041623 # v1.6.1
|
||||
with:
|
||||
token: ${{ secrets.SPONSORS_GENERATOR_PAT }}
|
||||
active-only: false
|
||||
|
||||
+4
-1
@@ -50,7 +50,10 @@ __debug_*
|
||||
config.certify.yml
|
||||
|
||||
# deepsec
|
||||
/.deepsec
|
||||
/.deepsec/
|
||||
|
||||
# jetbrains
|
||||
/.idea/
|
||||
|
||||
# claude stuff
|
||||
/.claude/
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
# Agents
|
||||
|
||||
*This file is written by Humans for Agents.*
|
||||
|
||||
## Overview
|
||||
|
||||
Tinyauth is a lightweight and open-source authentication server written in Go and TypeScript (React). It acts as either an authentication middleware (forward_auth, ext_authz or auth_request) to protect applications using proxy authentication or as an OpenID Connect provider to offer SSO (Single-Sign-On) to your self-hosted apps. It supports local users with optional 2FA (via TOTP), LDAP, SSO users via OAuth, and access controls (ACLs). Tinyauth can be deployed with Docker, Kubernetes or bare-metal with a binary.
|
||||
|
||||
## Considerations
|
||||
|
||||
- The repository we are working at is `https://github.com/tinyauthapp/tinyauth`.
|
||||
- ALWAYS follow the instructions for committing and creating a pull request as mentioned below.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Tinyauth is designed to run with simplicity in mind. This is why we try to avoid adding unnecessary persistent storage and configuration options.
|
||||
|
||||
Tinyauth can run without persistent storage, and the SQLite database is only used for storing normal or OpenID Connect sessions. You MUST never store data in the database that is required for Tinyauth function.
|
||||
|
||||
As for the configuration, we support environment variables, CLI flags and a YAML configuration file. We try to keep the required configuration at a minimal with sane defaults so users can spend the least amount of time configuring Tinyauth.
|
||||
|
||||
We NEVER create a breaking change unless absolutely necessary and only if non-breaking changes have been discussed and deemed not ideal.
|
||||
|
||||
## Technical Overview
|
||||
|
||||
Tinyauth is designed to be as modular as possible. We use a repository-service-controller structure where each service/controller/middleware defines its dependencies in a Dig input struct, and then the main bootstrap entrypoint dynamically injects the dependencies to each method.
|
||||
|
||||
All methods share one global static config struct, which contains the user configuration as is, and a runtime config struct that contains dynamically generated values on startup. If a method needs a modified version, it MUST never modify the global configuration struct but rather create a local copy.
|
||||
|
||||
We write database migrations by hand. Migrations go in the respective database directory inside the `assets/migrations` directory and follow the `000001_migration_name_in_snake_case.sql` format where the 6-digit number is incremented on each new migration. The repository is automatically generated from SQL queries, SQLC and our own custom generator that unifies each SQLC package into one repository interface. Always ensure that migrations and queries exist for all available database drivers, else our store generation will fail. After adding your migrations and queries, run the SQLC code-gen with `make sql` and update the store code-gen with `make generate`. DO NOT EDIT the automatically generated files from SQLC or our store generator, they are marked.
|
||||
|
||||
When updating translations, you should only update the `frontend/src/lib/i18n/locales/en.json` and `frontend/src/lib/i18n/locales/en-US.json` files (they should be exactly the same). Crowdin will handle the generation of the keys for the rest of the available locales. NEVER hard-code plain English in the frontend, instead use the available `i18next` library and the respective translations.
|
||||
|
||||
For the REST framework we use Gin. However, functions or methods should avoid using the Gin Context (`gin.Context`) and default to stdlib arguments and outputs. The Gin Context exposes stdlib-compatible structs such as `http.Request` and `http.ResponseWriter` and is compatible with the `context` package.
|
||||
|
||||
When you need to log in the backend, use the injected logger, NOT the global zerolog struct.
|
||||
|
||||
In case you need toolchain versions, you can find the Node + Go version in the `Dockerfile` and the PNPM version in the `package.json` file inside the `frontend` directory.
|
||||
|
||||
Tinyauth uses Semantic Versioning (SemVer) for versions.
|
||||
|
||||
## File structure
|
||||
|
||||
Tinyauth is composed of two parts, the React frontend and the Go backend.
|
||||
|
||||
A high level of the backend is as follows:
|
||||
|
||||
```text
|
||||
internal
|
||||
├── assets # Contains the embedded assets
|
||||
│ ├── dist # Dist is the compiled frontend
|
||||
│ └── migrations # Migrations in SQL for all supported databases
|
||||
│ ├── postgres
|
||||
│ └── sqlite
|
||||
├── bootstrap # The main entrypoint that bootstraps and starts Tinyauth, called by the CLI
|
||||
├── controller # All of the HTTP controllers
|
||||
├── middleware # The HTTP middlewares
|
||||
├── model # Configuration schemas
|
||||
├── repository # Repository holds all of the queries used by the services, each child-repository implements the store interface
|
||||
│ ├── memory
|
||||
│ ├── postgres
|
||||
│ └── sqlite
|
||||
├── service # The services that handle the underlying logic for the controllers
|
||||
├── test # Creates any necessary package-wide configurations and helpers used by tests
|
||||
└── utils # Small helpers and utils used by the app
|
||||
├── decoders # Wrappers around paerser decoders such as the label decoder
|
||||
├── loaders # The env, cli and YAML wrappers around the paerser loaders
|
||||
└── logger # A wrapper around the zerolog logging library
|
||||
```
|
||||
|
||||
Same for the frontend:
|
||||
|
||||
```text
|
||||
frontend/src
|
||||
├── components # Different components used by the pages
|
||||
│ ├── auth # Forms used for authentication
|
||||
│ ├── domain-warning # Domain warning when configured domain and actual domain don't match
|
||||
│ ├── icons # Hardcoded SVG icons for OAuth providers
|
||||
│ ├── layout # Main frontend layout
|
||||
│ ├── providers # Different state providers such as theme
|
||||
│ ├── quick-actions # The top right quick settings menu
|
||||
│ └── ui # ShadCN based UI components
|
||||
├── context # Holds and provides the app and user context
|
||||
├── lib # Helpers used by the pages
|
||||
│ ├── hooks # Hooks around the query parameters
|
||||
│ └── i18n # Holds translation logic
|
||||
│ └── locales # The raw JSON locales provided by Crowdin
|
||||
├── pages # The actual app pages
|
||||
└── schemas # Different schemas, mostly used for fetching data from the backend
|
||||
```
|
||||
|
||||
## Make recipes
|
||||
|
||||
Tinyauth uses a Makefile for simplifying development. A reference of the available recipes can be found below:
|
||||
|
||||
- `deps` - Install the frontend and backend dependencies.
|
||||
- `clean-data` - Clean any data created by running Tinyauth.
|
||||
- `clean-webui` - Clean frontend build output.
|
||||
- `webui` - Compile the WebUI.
|
||||
- `binary` - Compile the binary for the current system.
|
||||
- `binary-linux-amd64` - Compile the binary for Linux amd64.
|
||||
- `binary-linux-arm64` - Compile the binary for Linux arm64.
|
||||
- `test` - Test the Go backend.
|
||||
- `vet` - Vet the Go backend.
|
||||
- `test-race` - Test the Go backend with the race detector enabled.
|
||||
- `dev` - Start the Docker-based development server.
|
||||
- `prod` - Start the Docker-based production deployment (used for testing pre-releases).
|
||||
- `sql` - Generate the SQLC repositories.
|
||||
- `generate` - Update Go code-gen.
|
||||
- `docker` - Build the Docker image for the current system.
|
||||
- `docker-distroless` - Build the distroless Docker image for the current system.
|
||||
- `lint-webui` - Lint the frontend with ESLint.
|
||||
- `fmt` - Format the Go code with the Go `fmt` tool.
|
||||
|
||||
## Development lifecycle
|
||||
|
||||
Development of Tinyauth happens inside two Docker containers. The backend is built automatically by air using a template build output for the frontend. The frontend is run with PNPM, and then backend requests are routed with the help of Vite's proxy.
|
||||
|
||||
When developing, you should default to the `make dev` command to start everything in Docker and avoid platform-specific issues. If you need to test the CLI, use the `make binary` command.
|
||||
|
||||
After finishing with the development, test and vet the backend with `make test` and `make vet` respectively. If you believe you need to test for race conditions, use `make test-race`. You can also test specific parts of the code using the normal `go test` command, for example to run the `TestHealthController` test, you can use `go test ./internal/controller/ -run TestHealthController -v`. Finally, format the Go code with `make fmt`.
|
||||
|
||||
If you made any changes to the frontend, make sure to lint with `make lint-webui`.
|
||||
|
||||
NEVER run any destructive commands like `make clean-data` or delete any configurations without the user's approval.
|
||||
|
||||
## Creating a pull request
|
||||
|
||||
When committing, you MUST use the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) standard for your commit messages. You can add a commit description if you like. You MUST also use your standard no-reply Co-Author trailer (`Co-Authored-By:`).
|
||||
|
||||
You should work in separate branches unless it's clearly specified to work in the main branch. When working in a separate branch, follow the naming convention below:
|
||||
|
||||
```text
|
||||
[feat/refactor/fix/tests/etc]/[small-change-description-in-kebab-case]
|
||||
```
|
||||
|
||||
For example, if your change was to add OAuth to Tinyauth, the branch would look as follows:
|
||||
|
||||
```text
|
||||
feat/oauth
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```text
|
||||
feat/add-oauth-support
|
||||
```
|
||||
|
||||
Shorter branch names that still describe the general change are preferred.
|
||||
|
||||
Finally, when creating the actual pull request and if you have access to the internet/a GitHub tool, you should look if it resolves any open issues and if it does, reference them.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Site builder
|
||||
FROM node:26.5-alpine3.23 AS frontend-builder
|
||||
FROM node:26.7-alpine3.23 AS frontend-builder
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Site builder
|
||||
FROM node:26.5-alpine3.23 AS frontend-builder
|
||||
FROM node:26.7-alpine3.23 AS frontend-builder
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
|
||||
@@ -107,3 +107,11 @@ docker-distroless:
|
||||
--build-arg=BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) \
|
||||
--build-arg=BUILD_TAGS=$(BUILD_TAGS) \
|
||||
-f Dockerfile.distroless .
|
||||
|
||||
# Lint the frontend
|
||||
lint-webui:
|
||||
cd frontend && pnpm lint
|
||||
|
||||
# Format the code
|
||||
fmt:
|
||||
go fmt ./...
|
||||
@@ -0,0 +1,7 @@
|
||||
# playwright files
|
||||
/node_modules/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
/playwright/.auth/
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
auto_https off
|
||||
}
|
||||
|
||||
http://whoami.127.0.0.1.sslip.io {
|
||||
forward_auth tinyauth:3000 {
|
||||
uri /api/auth/caddy
|
||||
copy_headers Remote-User Remote-Name Remote-Email Remote-Groups
|
||||
}
|
||||
reverse_proxy whoami:80
|
||||
}
|
||||
|
||||
http://tinyauth.127.0.0.1.sslip.io {
|
||||
reverse_proxy tinyauth:3000
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
appUrl: http://tinyauth.127.0.0.1.sslip.io
|
||||
|
||||
log:
|
||||
level: debug
|
||||
|
||||
auth:
|
||||
users:
|
||||
# user1:password,user2:password,user3:password:token
|
||||
- user1:$2a$10$h1laww4k5a4bJcG5KwE3nO45YKSC4mOKHxbcccgxr3Y7H9zHlQe8e
|
||||
- user2:$2a$10$h1laww4k5a4bJcG5KwE3nO45YKSC4mOKHxbcccgxr3Y7H9zHlQe8e
|
||||
- user3:$2a$10$h1laww4k5a4bJcG5KwE3nO45YKSC4mOKHxbcccgxr3Y7H9zHlQe8e:MVR4JQWNXYKNM6HHJEYEFP2O74QIIEJE
|
||||
# disable rate limits for multiple workers to work
|
||||
loginMaxRetries: 0
|
||||
|
||||
apps:
|
||||
whoami:
|
||||
config:
|
||||
domain: whoami.127.0.0.1.sslip.io
|
||||
path:
|
||||
allow: /foo
|
||||
users:
|
||||
allow: user1
|
||||
@@ -0,0 +1,24 @@
|
||||
services:
|
||||
caddy:
|
||||
image: caddy:2.11.4
|
||||
pull_policy: missing
|
||||
ports:
|
||||
- 80:80
|
||||
volumes:
|
||||
- ./conf:/etc/caddy
|
||||
|
||||
whoami:
|
||||
image: traefik/whoami:v1.11.0
|
||||
pull_policy: missing
|
||||
|
||||
tinyauth:
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- VERSION=e2e
|
||||
- BUILD_TAGS=nomsgpack
|
||||
- LDFLAGS=-s -w
|
||||
command: ["--configfile", "/app/config.yaml"]
|
||||
volumes:
|
||||
- ./config.e2e.yaml:/app/config.yaml:ro
|
||||
@@ -0,0 +1,47 @@
|
||||
import {expect, Page} from '@playwright/test';
|
||||
import { OTP } from 'otplib';
|
||||
|
||||
export class LoginFixture {
|
||||
constructor(public readonly page: Page) {}
|
||||
|
||||
async run(username: string, password: string) {
|
||||
await expect(this.page.getByText('Welcome back, please login')).toBeVisible();
|
||||
await this.page.getByLabel('Username').fill(username);
|
||||
await this.page.getByLabel('Password').fill(password);
|
||||
await this.page.getByRole('button', { name: 'Login' }).click();
|
||||
}
|
||||
|
||||
async expectSuccess(username: string) {
|
||||
await expect(this.page.getByText(`You are currently logged in as ${username}.`)).toBeVisible()
|
||||
}
|
||||
}
|
||||
|
||||
export class LogoutFixture {
|
||||
constructor(public readonly page: Page) {}
|
||||
|
||||
async run() {
|
||||
await expect(this.page.getByText('Click the button below to logout.')).toBeVisible();
|
||||
await this.page.getByRole('button', { name: 'Logout' }).click();
|
||||
}
|
||||
|
||||
async expectSuccess() {
|
||||
await expect(this.page.getByText('Welcome back, please login')).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export class TOTPFixture {
|
||||
constructor(public readonly page: Page) {}
|
||||
|
||||
async run(secret: string) {
|
||||
await expect(this.page.getByText('Enter your TOTP code')).toBeVisible();
|
||||
const otp = new OTP();
|
||||
const token = await otp.generate({ secret });
|
||||
await this.page.getByPlaceholder('XXXXXX').fill(token);
|
||||
// we shouldn't need to click continue, it will auto submit
|
||||
// await this.page.getByRole('button', { name: 'Continue' }).click();
|
||||
}
|
||||
|
||||
async expectSuccess(username: string) {
|
||||
await expect(this.page.getByText(`You are currently logged in as ${username}.`)).toBeVisible()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "e2e",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"down": "docker compose -f docker-compose.e2e.yml down",
|
||||
"up": "docker compose -f docker-compose.e2e.yml up --build --force-recreate --remove-orphans",
|
||||
"test": "playwright test",
|
||||
"report": "playwright show-report"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devEngines": {
|
||||
"packageManager": {
|
||||
"name": "pnpm",
|
||||
"version": "^11.1.2",
|
||||
"onFail": "download"
|
||||
}
|
||||
},
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.61.1",
|
||||
"@types/node": "^26.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"otplib": "^13.4.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './specs',
|
||||
fullyParallel: true,
|
||||
forbidOnly: false,
|
||||
retries: 0,
|
||||
workers: 4,
|
||||
reporter: 'html',
|
||||
use: {
|
||||
trace: 'on-first-retry',
|
||||
video: 'on',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
{
|
||||
name: 'Mobile Chrome',
|
||||
use: { ...devices['Pixel 5'] },
|
||||
},
|
||||
{
|
||||
name: 'Mobile Safari',
|
||||
use: { ...devices['iPhone 12'] },
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
command: 'pnpm run up',
|
||||
url: 'http://tinyauth.127.0.0.1.sslip.io/api/healthz',
|
||||
reuseExistingServer: true,
|
||||
timeout: 5 * 60 * 1000,
|
||||
gracefulShutdown: {
|
||||
signal: 'SIGINT',
|
||||
timeout: 1000,
|
||||
},
|
||||
stderr: 'pipe',
|
||||
stdout: 'pipe',
|
||||
},
|
||||
});
|
||||
Generated
+338
@@ -0,0 +1,338 @@
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
'@pnpm/exe':
|
||||
specifier: ^11.1.2
|
||||
version: 11.13.1
|
||||
pnpm:
|
||||
specifier: ^11.1.2
|
||||
version: 11.13.1
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe@11.13.1':
|
||||
resolution: {integrity: sha512-P4euEK6lOFnd5oTHEc5M/HhvyF4XUhTnVsklEcM6rmY0QJxPD6xbT+u1+gskEIBp4nSRorz20IJQtAU1Nerggg==}
|
||||
hasBin: true
|
||||
|
||||
'@pnpm/linux-arm64@11.13.1':
|
||||
resolution: {integrity: sha512-wB8zloqrYrudPyuA5qbuTCnJGe4eETPwqOjoPjoyyyvA4zFI5XfLpxgqOOcaY5UJBoqzckcGpRVDwhSRfsQ/6A==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@pnpm/linux-x64@11.13.1':
|
||||
resolution: {integrity: sha512-A+wnEvzfWEvanXiwww3tnOPmtjPSrrf5tOP6vk8+K0BRFEe/Df0oPytm2nWgGcn5iwPnqtr1Btkof913McnSPA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@pnpm/linuxstatic-arm64@11.13.1':
|
||||
resolution: {integrity: sha512-k4t65VeqRX4COMFe45TF58CVmCpmAsKZShaR1HobmUeleo98mWTctggKolrA2MHcVUeSS+12yB5Urb3uDazhmw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/linuxstatic-x64@11.13.1':
|
||||
resolution: {integrity: sha512-A65GqPzwCl0bAMk3kRWfbjSRBm5RRaqR2oMxV/9AYZrwO0X9yEfngbLBISCPHjt6/Qe4nH7DFemyhy6yODYwEw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/macos-arm64@11.13.1':
|
||||
resolution: {integrity: sha512-MJvOtyGOWSfBoqdVEfAH8ljmHs13mt82k/UxN4f+q7koDxJRR2n4Nie6Og6RwbnbaubCz0Fh2bTeL1+MxDSFpA==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/win-arm64@11.13.1':
|
||||
resolution: {integrity: sha512-kl/g1cCKOJPe4HntspyrAJW0LRco0UHnVfxHSspezo4Zj4AanJAZ8WzLqfa6/w3lBSKHTEN4x0pb3m4J7B7Vpw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/win-x64@11.13.1':
|
||||
resolution: {integrity: sha512-Bcb14NeBlbHS2Gq1qr8VnCiAz5eC1lYzXOls7zH0bnV0Taaj4/xyfm0HVO4dn9R2TQtVtz1qnBZHHL9PDFumqQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@reflink/reflink-darwin-arm64@0.1.19':
|
||||
resolution: {integrity: sha512-ruy44Lpepdk1FqDz38vExBY/PVUsjxZA+chd9wozjUH9JjuDT/HEaQYA6wYN9mf041l0yLVar6BCZuWABJvHSA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@reflink/reflink-darwin-x64@0.1.19':
|
||||
resolution: {integrity: sha512-By85MSWrMZa+c26TcnAy8SDk0sTUkYlNnwknSchkhHpGXOtjNDUOxJE9oByBnGbeuIE1PiQsxDG3Ud+IVV9yuA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@reflink/reflink-linux-arm64-gnu@0.1.19':
|
||||
resolution: {integrity: sha512-7P+er8+rP9iNeN+bfmccM4hTAaLP6PQJPKWSA4iSk2bNvo6KU6RyPgYeHxXmzNKzPVRcypZQTpFgstHam6maVg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@reflink/reflink-linux-arm64-musl@0.1.19':
|
||||
resolution: {integrity: sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@reflink/reflink-linux-x64-gnu@0.1.19':
|
||||
resolution: {integrity: sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@reflink/reflink-linux-x64-musl@0.1.19':
|
||||
resolution: {integrity: sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@reflink/reflink-win32-arm64-msvc@0.1.19':
|
||||
resolution: {integrity: sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@reflink/reflink-win32-x64-msvc@0.1.19':
|
||||
resolution: {integrity: sha512-E//yT4ni2SyhwP8JRjVGWr3cbnhWDiPLgnQ66qqaanjjnMiu3O/2tjCPQXlcGc/DEYofpDc9fvhv6tALQsMV9w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@reflink/reflink@0.1.19':
|
||||
resolution: {integrity: sha512-DmCG8GzysnCZ15bres3N5AHCmwBwYgp0As6xjhQ47rAUTUXxJiK+lLUxaGsX3hd/30qUpVElh05PbGuxRPgJwA==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
pnpm@11.13.1:
|
||||
resolution: {integrity: sha512-svx2g7imUlQU59E+G6KMqt3elr9m7FQL+ut+cCuB8+C+TR8pXt9/n+A5Z0Co3ORQnFgt33mJH0VD/qMtN2RfJQ==}
|
||||
engines: {node: '>=22.13'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe@11.13.1':
|
||||
dependencies:
|
||||
'@reflink/reflink': 0.1.19
|
||||
detect-libc: 2.1.2
|
||||
optionalDependencies:
|
||||
'@pnpm/linux-arm64': 11.13.1
|
||||
'@pnpm/linux-x64': 11.13.1
|
||||
'@pnpm/linuxstatic-arm64': 11.13.1
|
||||
'@pnpm/linuxstatic-x64': 11.13.1
|
||||
'@pnpm/macos-arm64': 11.13.1
|
||||
'@pnpm/win-arm64': 11.13.1
|
||||
'@pnpm/win-x64': 11.13.1
|
||||
|
||||
'@pnpm/linux-arm64@11.13.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/linux-x64@11.13.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/linuxstatic-arm64@11.13.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/linuxstatic-x64@11.13.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/macos-arm64@11.13.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/win-arm64@11.13.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/win-x64@11.13.1':
|
||||
optional: true
|
||||
|
||||
'@reflink/reflink-darwin-arm64@0.1.19':
|
||||
optional: true
|
||||
|
||||
'@reflink/reflink-darwin-x64@0.1.19':
|
||||
optional: true
|
||||
|
||||
'@reflink/reflink-linux-arm64-gnu@0.1.19':
|
||||
optional: true
|
||||
|
||||
'@reflink/reflink-linux-arm64-musl@0.1.19':
|
||||
optional: true
|
||||
|
||||
'@reflink/reflink-linux-x64-gnu@0.1.19':
|
||||
optional: true
|
||||
|
||||
'@reflink/reflink-linux-x64-musl@0.1.19':
|
||||
optional: true
|
||||
|
||||
'@reflink/reflink-win32-arm64-msvc@0.1.19':
|
||||
optional: true
|
||||
|
||||
'@reflink/reflink-win32-x64-msvc@0.1.19':
|
||||
optional: true
|
||||
|
||||
'@reflink/reflink@0.1.19':
|
||||
optionalDependencies:
|
||||
'@reflink/reflink-darwin-arm64': 0.1.19
|
||||
'@reflink/reflink-darwin-x64': 0.1.19
|
||||
'@reflink/reflink-linux-arm64-gnu': 0.1.19
|
||||
'@reflink/reflink-linux-arm64-musl': 0.1.19
|
||||
'@reflink/reflink-linux-x64-gnu': 0.1.19
|
||||
'@reflink/reflink-linux-x64-musl': 0.1.19
|
||||
'@reflink/reflink-win32-arm64-msvc': 0.1.19
|
||||
'@reflink/reflink-win32-x64-msvc': 0.1.19
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
pnpm@11.13.1: {}
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
otplib:
|
||||
specifier: ^13.4.1
|
||||
version: 13.4.1
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
specifier: ^1.61.1
|
||||
version: 1.61.1
|
||||
'@types/node':
|
||||
specifier: ^26.1.1
|
||||
version: 26.1.1
|
||||
|
||||
packages:
|
||||
|
||||
'@noble/hashes@2.2.0':
|
||||
resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
'@otplib/core@13.4.1':
|
||||
resolution: {integrity: sha512-KIXgK1hNtWJEBMTastbe1bpmuais+3f+ATeO8TkMs2rNkfGO1FbQy8+/UWVEu3TR/iTJerU0idkPudaPmLP2BA==}
|
||||
|
||||
'@otplib/hotp@13.4.1':
|
||||
resolution: {integrity: sha512-g9q04SwpG5ZtMnVkUcgcoAlwCH4YLROZN1qhyBwgkBzqYYVSYhpP6gSGaxGHwePLt1c+e6NqDlgIZN+e1/XPuA==}
|
||||
|
||||
'@otplib/plugin-base32-scure@13.4.1':
|
||||
resolution: {integrity: sha512-Fs/r5qisC05SRhT6xWXaypB6PVC0vgWf6zztmi0J5RnQ09OJiPDWCJFH6cDm6ANsrdvB9di7X+Jb7L13BoEbUA==}
|
||||
|
||||
'@otplib/plugin-crypto-noble@13.4.1':
|
||||
resolution: {integrity: sha512-PJfVW8/1hdS6CfxLheKPZSLTwDq4TijZbN4yRjxlv0ODdzmxpM+wGwWr1JXMdy0xJPxLziydQD5gdVqrR4/gAg==}
|
||||
|
||||
'@otplib/totp@13.4.1':
|
||||
resolution: {integrity: sha512-QOkBVPrf6AM4qZaReZPSk9/I8ATVdZpIISJz115MqeVtcrbcr5llPZ0J7804tpnjnp1vCRkI5Qjd47HhgVteBQ==}
|
||||
|
||||
'@otplib/uri@13.4.1':
|
||||
resolution: {integrity: sha512-xaIm7bvICMhoB2rZIR5luiaMdssWR5nY5nXnR1fdezUgZuEO58D6zrGzLp7pQuBmlpmL0HagnscDQFoskp9yiA==}
|
||||
|
||||
'@playwright/test@1.61.1':
|
||||
resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
'@scure/base@2.2.0':
|
||||
resolution: {integrity: sha512-b8XEupJibegiXV+tDUseI8oLQc8ei3d/4Jkb2RpbHh3MfE054ov3uIz2dhFkB3FI8iwYkEh0gGCApkrYggkPNg==}
|
||||
|
||||
'@types/node@26.1.1':
|
||||
resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
otplib@13.4.1:
|
||||
resolution: {integrity: sha512-o5CxfDw6bh7hoDv0NUUIcc0RqzJ9ipfUrzeKheKJ+vs4rXZnDlA9n4a/7R1cDjpmLjKLix4BgNVRmoDkm5rLSQ==}
|
||||
|
||||
playwright-core@1.61.1:
|
||||
resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.61.1:
|
||||
resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@8.3.0:
|
||||
resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@noble/hashes@2.2.0': {}
|
||||
|
||||
'@otplib/core@13.4.1': {}
|
||||
|
||||
'@otplib/hotp@13.4.1':
|
||||
dependencies:
|
||||
'@otplib/core': 13.4.1
|
||||
'@otplib/uri': 13.4.1
|
||||
|
||||
'@otplib/plugin-base32-scure@13.4.1':
|
||||
dependencies:
|
||||
'@otplib/core': 13.4.1
|
||||
'@scure/base': 2.2.0
|
||||
|
||||
'@otplib/plugin-crypto-noble@13.4.1':
|
||||
dependencies:
|
||||
'@noble/hashes': 2.2.0
|
||||
'@otplib/core': 13.4.1
|
||||
|
||||
'@otplib/totp@13.4.1':
|
||||
dependencies:
|
||||
'@otplib/core': 13.4.1
|
||||
'@otplib/hotp': 13.4.1
|
||||
'@otplib/uri': 13.4.1
|
||||
|
||||
'@otplib/uri@13.4.1':
|
||||
dependencies:
|
||||
'@otplib/core': 13.4.1
|
||||
|
||||
'@playwright/test@1.61.1':
|
||||
dependencies:
|
||||
playwright: 1.61.1
|
||||
|
||||
'@scure/base@2.2.0': {}
|
||||
|
||||
'@types/node@26.1.1':
|
||||
dependencies:
|
||||
undici-types: 8.3.0
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
otplib@13.4.1:
|
||||
dependencies:
|
||||
'@otplib/core': 13.4.1
|
||||
'@otplib/hotp': 13.4.1
|
||||
'@otplib/plugin-base32-scure': 13.4.1
|
||||
'@otplib/plugin-crypto-noble': 13.4.1
|
||||
'@otplib/totp': 13.4.1
|
||||
'@otplib/uri': 13.4.1
|
||||
|
||||
playwright-core@1.61.1: {}
|
||||
|
||||
playwright@1.61.1:
|
||||
dependencies:
|
||||
playwright-core: 1.61.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
undici-types@8.3.0: {}
|
||||
@@ -0,0 +1,28 @@
|
||||
import {expect, test} from '@playwright/test';
|
||||
import {LoginFixture} from "../fixtures/auth.fixtures";
|
||||
|
||||
test('should be able to login to app with forward-auth', async ({ page }) => {
|
||||
const loginFixture = new LoginFixture(page);
|
||||
await page.goto('http://whoami.127.0.0.1.sslip.io');
|
||||
// redirect to tinyauth
|
||||
await expect(page.getByText('Welcome back, please login')).toBeVisible()
|
||||
await loginFixture.run('user1', 'password')
|
||||
// redirect to app
|
||||
await expect(page.getByText('whoami.127.0.0.1.sslip.io')).toBeVisible()
|
||||
});
|
||||
|
||||
test('non authorized user should not be able to access app', async ({ page }) => {
|
||||
const loginFixture = new LoginFixture(page);
|
||||
await page.goto('http://whoami.127.0.0.1.sslip.io');
|
||||
// redirect to tinyauth
|
||||
await expect(page.getByText('Welcome back, please login')).toBeVisible()
|
||||
// user2 is not authorized to access app
|
||||
await loginFixture.run('user2', 'password')
|
||||
// redirect to app
|
||||
await expect(page.getByText('The user with username user2 is not authorized to access the resource whoami.')).toBeVisible()
|
||||
})
|
||||
|
||||
test('allowed path should skip authentication', async ({ page }) => {
|
||||
await page.goto('http://whoami.127.0.0.1.sslip.io/foo');
|
||||
await expect(page.getByText('whoami.127.0.0.1.sslip.io')).toBeVisible()
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {LoginFixture, LogoutFixture, TOTPFixture} from "../fixtures/auth.fixtures";
|
||||
|
||||
test('should be able to login', async ({ page }) => {
|
||||
const loginFixture = new LoginFixture(page);
|
||||
await page.goto('http://tinyauth.127.0.0.1.sslip.io');
|
||||
await loginFixture.run('user1', 'password')
|
||||
await loginFixture.expectSuccess('user1')
|
||||
});
|
||||
|
||||
test('should fail to login with wrong credentials', async ({ page }) => {
|
||||
const loginFixture = new LoginFixture(page);
|
||||
await page.goto('http://tinyauth.127.0.0.1.sslip.io');
|
||||
await loginFixture.run('user27267', 'password')
|
||||
const toast = page.locator('[data-sonner-toast]').first();
|
||||
await expect(toast).toBeVisible();
|
||||
await expect(toast).toContainText('Failed to log in');
|
||||
});
|
||||
|
||||
|
||||
test('should be able to logout', async ({ page }) => {
|
||||
const loginFixture = new LoginFixture(page);
|
||||
await page.goto('http://tinyauth.127.0.0.1.sslip.io');
|
||||
await loginFixture.run('user1', 'password')
|
||||
await loginFixture.expectSuccess('user1')
|
||||
const logoutFixture = new LogoutFixture(page);
|
||||
await logoutFixture.run()
|
||||
})
|
||||
|
||||
test('should be able to login with totp', async ({ page }) => {
|
||||
const loginFixture = new LoginFixture(page);
|
||||
const totpFixture = new TOTPFixture(page);
|
||||
await page.goto('http://tinyauth.127.0.0.1.sslip.io');
|
||||
await loginFixture.run('user3', 'password')
|
||||
await totpFixture.run('MVR4JQWNXYKNM6HHJEYEFP2O74QIIEJE')
|
||||
await loginFixture.expectSuccess('user3');
|
||||
});
|
||||
|
||||
test('should fail to login with wrong totp', async ({ page }) => {
|
||||
const loginFixture = new LoginFixture(page);
|
||||
const totpFixture = new TOTPFixture(page);
|
||||
await page.goto('http://tinyauth.127.0.0.1.sslip.io');
|
||||
await loginFixture.run('user3', 'password')
|
||||
await totpFixture.run('VZVMOMQCBN24DJ5VRFAL5TJAZGBHXMN3')
|
||||
const toast = page.locator('[data-sonner-toast]').first();
|
||||
await expect(toast).toBeVisible();
|
||||
await expect(toast).toContainText('Failed to verify code');
|
||||
});
|
||||
+14
-14
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"packageManager": "pnpm@11.1.2",
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.5.7",
|
||||
"@hookform/resolvers": "^5.7.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.24",
|
||||
"@radix-ui/react-label": "^2.1.15",
|
||||
"@radix-ui/react-select": "^2.3.7",
|
||||
@@ -20,22 +20,22 @@
|
||||
"@radix-ui/react-slot": "^1.3.3",
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"@tanstack/react-query": "^5.101.4",
|
||||
"axios": "^1.18.1",
|
||||
"axios": "^1.19.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^26.3.6",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"i18next-resources-to-backend": "^1.2.2",
|
||||
"lucide-react": "^1.27.0",
|
||||
"i18next-resources-to-backend": "^1.2.3",
|
||||
"lucide-react": "^1.31.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"radix-ui": "^1.6.7",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-hook-form": "^7.83.0",
|
||||
"react-hook-form": "^7.85.0",
|
||||
"react-i18next": "^17.0.11",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router": "^8.3.0",
|
||||
"sonner": "^2.0.7",
|
||||
"sonner": "^2.0.8",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"zod": "^4.3.6"
|
||||
@@ -43,18 +43,18 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@tanstack/eslint-plugin-query": "^5.101.4",
|
||||
"@types/node": "^26.1.2",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.4",
|
||||
"eslint": "^10.8.0",
|
||||
"@types/node": "^26.2.0",
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.4",
|
||||
"@vitejs/plugin-react": "^6.0.5",
|
||||
"eslint": "^10.8.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.3",
|
||||
"globals": "^17.8.0",
|
||||
"globals": "^17.9.0",
|
||||
"rollup-plugin-visualizer": "^7.0.1",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.65.0",
|
||||
"vite": "^8.1.5"
|
||||
"typescript-eslint": "^8.66.0",
|
||||
"vite": "^8.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+941
-783
File diff suppressed because it is too large
Load Diff
@@ -15,16 +15,16 @@
|
||||
"loginOauthSuccessTitle": "Přesměrování",
|
||||
"loginOauthSuccessSubtitle": "Přesměrování k poskytovateli OAuth",
|
||||
"loginOauthAutoRedirectTitle": "Automatické přesměrování OAuth",
|
||||
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||
"loginOauthAutoRedirectButton": "Redirect now",
|
||||
"loginOauthAutoRedirectSubtitle": "Pro ověření budete automaticky přesměrování na svého poskytovatele OAuth.",
|
||||
"loginOauthAutoRedirectButton": "Přesměrovat nyní",
|
||||
"continueTitle": "Pokračovat",
|
||||
"continueRedirectingTitle": "Přesměrování...",
|
||||
"continueRedirectingSubtitle": "Brzy budete přesměrováni do aplikace",
|
||||
"continueRedirectManually": "Redirect me manually",
|
||||
"continueRedirectManually": "Přesměrovat ručně",
|
||||
"continueInsecureRedirectTitle": "Nezabezpečené přesměrování",
|
||||
"continueInsecureRedirectSubtitle": "Pokoušíte se přesměrovat z <code>https</code> na <code>http</code>, které není bezpečné. Opravdu chcete pokračovat?",
|
||||
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||
"continueUntrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{cookieDomain}}</code>). Are you sure you want to continue?",
|
||||
"continueUntrustedRedirectTitle": "Nedůvěryhodné přesměrování",
|
||||
"continueUntrustedRedirectSubtitle": "Pokoušíte se přesměrovat na doménu, která neodpovídá vaší nakonfigurované doméně (<code>{{cookieDomain}}</code>). Opravdu chcete pokračovat?",
|
||||
"logoutFailTitle": "Odhlášení se nezdařilo",
|
||||
"logoutFailSubtitle": "Zkuste to prosím znovu",
|
||||
"logoutSuccessTitle": "Odhlášen",
|
||||
@@ -51,53 +51,53 @@
|
||||
"forgotPasswordTitle": "Zapomněli jste heslo?",
|
||||
"failedToFetchProvidersTitle": "Nepodařilo se načíst poskytovatele ověřování. Zkontrolujte prosím konfiguraci.",
|
||||
"errorTitle": "Došlo k chybě",
|
||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
||||
"errorSubtitleInfo": "Při zpracování požadavku došlo k následující chybě:",
|
||||
"errorSubtitle": "Nastala chyba při pokusu o provedení této akce. Pro více informací prosím zkontrolujte konzolu.",
|
||||
"forgotPasswordMessage": "Heslo můžete obnovit změnou proměnné `USERS`.",
|
||||
"fieldRequired": "Toto pole je povinné",
|
||||
"invalidInput": "Neplatný údaj",
|
||||
"domainWarningTitle": "Invalid Domain",
|
||||
"domainWarningSubtitle": "You are accessing this instance from an incorrect domain. If you proceed, you may encounter issues with authentication.",
|
||||
"domainWarningCurrent": "Current:",
|
||||
"domainWarningExpected": "Expected:",
|
||||
"ignoreTitle": "Ignore",
|
||||
"goToCorrectDomainTitle": "Go to correct domain",
|
||||
"authorizeTitle": "Authorize",
|
||||
"authorizeCardTitle": "Continue to {{app}}?",
|
||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
||||
"authorizeLoadingTitle": "Loading...",
|
||||
"domainWarningTitle": "Neplatná doména",
|
||||
"domainWarningSubtitle": "Pro přístup k této instanci používáte špatnou doménu. Při pokračování mohou nastat problémy s autentizací.",
|
||||
"domainWarningCurrent": "Aktuální:",
|
||||
"domainWarningExpected": "Vyžadovaná:",
|
||||
"ignoreTitle": "Ignorovat",
|
||||
"goToCorrectDomainTitle": "Přejít na správnou doménu",
|
||||
"authorizeTitle": "Autorizovat",
|
||||
"authorizeCardTitle": "Pokračovat do {{app}}?",
|
||||
"authorizeSubtitle": "Chcete pokračovat do této aplikace? Pečlivě si zkontrolujte jí vyžadovaná oprávnění, prosím.",
|
||||
"authorizeSubtitleOAuth": "Chcete pokračovat do této aplikace?",
|
||||
"authorizeLoadingTitle": "Načítání...",
|
||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
||||
"authorizeSuccessTitle": "Authorized",
|
||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
||||
"authorizeSuccessTitle": "Autorizováno",
|
||||
"authorizeSuccessSubtitle": "Během několika vteřin budete přesměrováni do aplikace.",
|
||||
"authorizeErrorClientInfo": "Při načítání klientských informací došlo k chybě. Zkuste to prosím později.",
|
||||
"authorizeErrorInvalidParams": "Parametry požadavku jsou chybné/neplatné. Prosím, zkontrolujte URL a zkuste znovu.",
|
||||
"openidScopeName": "OpenID Connect",
|
||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||
"emailScopeName": "Email",
|
||||
"emailScopeDescription": "Allows the app to access your email address.",
|
||||
"profileScopeName": "Profile",
|
||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||
"groupsScopeName": "Groups",
|
||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
||||
"backToLoginButton": "Back to login",
|
||||
"phoneScopeName": "Phone",
|
||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
||||
"addressScopeName": "Address",
|
||||
"addressScopeDescription": "Allows the app to access your address.",
|
||||
"loginTailscaleTitle": "Continue with Tailscale",
|
||||
"openidScopeDescription": "Povolí aplikaci přístup k Vaším informacím OpenID Connect.",
|
||||
"emailScopeName": "E-mail",
|
||||
"emailScopeDescription": "Povolí aplikaci přístup k Vaší e-mailové adrese.",
|
||||
"profileScopeName": "Profil",
|
||||
"profileScopeDescription": "Povolí aplikaci přístup k informacím o Vašem profilu.",
|
||||
"groupsScopeName": "Skupiny",
|
||||
"groupsScopeDescription": "Povolí aplikaci přístup k informacím o Vaší skupině.",
|
||||
"backToLoginButton": "Zpět na přihlášení",
|
||||
"phoneScopeName": "Telefon",
|
||||
"phoneScopeDescription": "Povolí aplikaci přístup k Vašemu telefonnímu číslu.",
|
||||
"addressScopeName": "Adresa",
|
||||
"addressScopeDescription": "Povolí aplikaci přístup k Vaší adrese.",
|
||||
"loginTailscaleTitle": "Pokračovat přes Tailscale",
|
||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
||||
"loginTailscaleDeviceName": "Device name:",
|
||||
"loginTailscaleOtherMethod": "Login with another method",
|
||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
||||
"loginTailscaleDeviceName": "Název zařízení:",
|
||||
"loginTailscaleOtherMethod": "Přihlásit jinou metodou",
|
||||
"loginTailscaleSuccess": "Úspěšně přihlášeno přes Tailscale.",
|
||||
"loginTailscaleFail": "Chyba při přihlášení přes Tailscale. Zkuste znovu, případně se přihlašte jiným způsobem, prosím.",
|
||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
||||
"quickActionsLanguage": "Language",
|
||||
"quickActionsTheme": "Theme",
|
||||
"quickActionsThemeLight": "Light",
|
||||
"quickActionsThemeDark": "Dark",
|
||||
"quickActionsThemeSystem": "System",
|
||||
"quickActionsLogout": "Logout",
|
||||
"quickActionsLanguage": "Jazyk",
|
||||
"quickActionsTheme": "Motiv",
|
||||
"quickActionsThemeLight": "Světlý",
|
||||
"quickActionsThemeDark": "Tmavý",
|
||||
"quickActionsThemeSystem": "Systémový",
|
||||
"quickActionsLogout": "Odhlásit se",
|
||||
"quickActionsTitle": "Quick Actions",
|
||||
"quickActionsProviderLocal": "Local",
|
||||
"quickActionsProviderLDAP": "LDAP",
|
||||
|
||||
@@ -30,7 +30,7 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/apimachinery v0.36.3
|
||||
k8s.io/client-go v0.36.3
|
||||
modernc.org/sqlite v1.55.0
|
||||
modernc.org/sqlite v1.56.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -92,7 +92,7 @@ require (
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.23 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
@@ -147,7 +147,7 @@ require (
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
|
||||
modernc.org/libc v1.74.1 // indirect
|
||||
modernc.org/libc v1.74.4 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
|
||||
@@ -154,8 +154,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
||||
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
||||
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
|
||||
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
@@ -208,8 +208,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
|
||||
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
@@ -348,7 +348,6 @@ golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
@@ -392,8 +391,8 @@ k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hk
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc=
|
||||
modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
|
||||
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
|
||||
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
@@ -404,8 +403,8 @@ modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
|
||||
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ=
|
||||
modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os=
|
||||
modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
|
||||
modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -414,8 +413,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.55.0 h1:hIFh0MCH0rGinQ/4KYb5/UbCkRkb+UP+OkLCVWa5MTM=
|
||||
modernc.org/sqlite v1.55.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw=
|
||||
modernc.org/sqlite v1.56.0 h1:/D8e2RfFqoy/Zc6PuC76U28zFwmI/sYx1Kjm4yEn9e0=
|
||||
modernc.org/sqlite v1.56.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -47,6 +47,7 @@ type ProxyContext struct {
|
||||
Host string
|
||||
Proto string
|
||||
Path string
|
||||
PathRaw string
|
||||
Method string
|
||||
Type AuthModuleType
|
||||
IsBrowser bool
|
||||
@@ -281,7 +282,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
queries, err := query.Values(RedirectQuery{
|
||||
RedirectURI: fmt.Sprintf("%s://%s%s", proxyCtx.Proto, proxyCtx.Host, proxyCtx.Path),
|
||||
RedirectURI: fmt.Sprintf("%s://%s%s", proxyCtx.Proto, proxyCtx.Host, proxyCtx.PathRaw),
|
||||
LoginFor: FrontendLoginForApp,
|
||||
})
|
||||
|
||||
@@ -402,11 +403,11 @@ func (controller *ProxyController) getForwardAuthContext(c *gin.Context) (ProxyC
|
||||
method := c.Request.Method
|
||||
|
||||
return ProxyContext{
|
||||
Host: host,
|
||||
Proto: proto,
|
||||
Path: uri,
|
||||
Method: method,
|
||||
Type: ForwardAuth,
|
||||
Host: host,
|
||||
Proto: proto,
|
||||
PathRaw: uri,
|
||||
Method: method,
|
||||
Type: ForwardAuth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -435,15 +436,14 @@ func (controller *ProxyController) getAuthRequestContext(c *gin.Context) (ProxyC
|
||||
return ProxyContext{}, errors.New("proto not found")
|
||||
}
|
||||
|
||||
path := url.Path
|
||||
method := c.Request.Method
|
||||
|
||||
return ProxyContext{
|
||||
Host: host,
|
||||
Proto: proto,
|
||||
Path: path,
|
||||
Method: method,
|
||||
Type: AuthRequest,
|
||||
Host: host,
|
||||
Proto: proto,
|
||||
PathRaw: url.RequestURI(),
|
||||
Method: method,
|
||||
Type: AuthRequest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -469,11 +469,11 @@ func (controller *ProxyController) getExtAuthzContext(c *gin.Context) (ProxyCont
|
||||
method := c.Request.Method
|
||||
|
||||
return ProxyContext{
|
||||
Host: host,
|
||||
Proto: proto,
|
||||
Path: path,
|
||||
Method: method,
|
||||
Type: ExtAuthz,
|
||||
Host: host,
|
||||
Proto: proto,
|
||||
PathRaw: path,
|
||||
Method: method,
|
||||
Type: ExtAuthz,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -552,8 +552,8 @@ func (controller *ProxyController) getProxyContext(c *gin.Context) (ProxyContext
|
||||
return ProxyContext{}, err
|
||||
}
|
||||
|
||||
// remove any query params from the request path
|
||||
upath, err := url.Parse(ctx.Path)
|
||||
// Parse the raw path to populate the cleaned path used for ACLs
|
||||
upath, err := url.Parse(ctx.PathRaw)
|
||||
|
||||
if err != nil {
|
||||
return ProxyContext{}, fmt.Errorf("failed to parse request path: %v", err)
|
||||
|
||||
@@ -95,6 +95,38 @@ func TestProxyController(t *testing.T) {
|
||||
assert.Contains(t, location, "https://tinyauth.example.com/login")
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Forward auth login redirect should preserve query parameters",
|
||||
middlewares: []gin.HandlerFunc{},
|
||||
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||
req := httptest.NewRequest("GET", "/api/auth/traefik", nil)
|
||||
req.Header.Set("x-forwarded-host", "test.example.com")
|
||||
req.Header.Set("x-forwarded-proto", "https")
|
||||
req.Header.Set("x-forwarded-uri", "/search?foo=bar")
|
||||
req.Header.Set("user-agent", browserUserAgent)
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, recorder.Code)
|
||||
location := recorder.Header().Get("Location")
|
||||
assert.Contains(t, location, url.QueryEscape("https://test.example.com/search?foo=bar"))
|
||||
assert.Contains(t, location, "login_for=app")
|
||||
assert.Contains(t, location, "https://tinyauth.example.com/login")
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Auth request (nginx) login redirect should preserve query parameters",
|
||||
middlewares: []gin.HandlerFunc{},
|
||||
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||
req := httptest.NewRequest("GET", "/api/auth/nginx", nil)
|
||||
req.Header.Set("x-original-url", "https://test.example.com/search?foo=bar")
|
||||
router.ServeHTTP(recorder, req)
|
||||
assert.Equal(t, http.StatusUnauthorized, recorder.Code)
|
||||
location := recorder.Header().Get("x-tinyauth-location")
|
||||
assert.Contains(t, location, url.QueryEscape("https://test.example.com/search?foo=bar"))
|
||||
assert.Contains(t, location, "login_for=app")
|
||||
assert.Contains(t, location, "https://tinyauth.example.com/login")
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Auth request (nginx) should be detected and used",
|
||||
middlewares: []gin.HandlerFunc{},
|
||||
@@ -126,6 +158,22 @@ func TestProxyController(t *testing.T) {
|
||||
assert.Contains(t, location, "https://tinyauth.example.com/login")
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Ext authz (envoy) login redirect should preserve query parameters",
|
||||
middlewares: []gin.HandlerFunc{},
|
||||
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||
req := httptest.NewRequest("HEAD", "/api/auth/envoy?path=%2Fhello%3Ffoo%3Dbar", nil)
|
||||
req.Host = "test.example.com"
|
||||
req.Header.Set("x-forwarded-proto", "https")
|
||||
req.Header.Set("user-agent", browserUserAgent)
|
||||
router.ServeHTTP(recorder, req)
|
||||
assert.Equal(t, http.StatusFound, recorder.Code)
|
||||
location := recorder.Header().Get("Location")
|
||||
assert.Contains(t, location, url.QueryEscape("https://test.example.com/hello?foo=bar"))
|
||||
assert.Contains(t, location, "login_for=app")
|
||||
assert.Contains(t, location, "https://tinyauth.example.com/login")
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Forward auth with caddy should be detected and used",
|
||||
middlewares: []gin.HandlerFunc{},
|
||||
|
||||
@@ -194,6 +194,9 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
if search.Email != "" {
|
||||
sessionCookie.Email = search.Email
|
||||
}
|
||||
if search.Name != "" {
|
||||
sessionCookie.Name = search.Name
|
||||
}
|
||||
}
|
||||
|
||||
cookie, err := controller.auth.CreateSession(c, sessionCookie)
|
||||
|
||||
@@ -204,7 +204,11 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string, ip stri
|
||||
}
|
||||
|
||||
userContext.LDAP.Groups = user.Groups
|
||||
userContext.LDAP.Name = utils.Capitalize(userContext.LDAP.Username)
|
||||
if search.Name != "" {
|
||||
userContext.LDAP.Name = search.Name
|
||||
} else {
|
||||
userContext.LDAP.Name = utils.Capitalize(userContext.LDAP.Username)
|
||||
}
|
||||
|
||||
userContext.LDAP.Email = utils.CompileUserEmail(userContext.LDAP.Username, m.runtime.CookieDomain)
|
||||
if search.Email != "" {
|
||||
@@ -291,10 +295,14 @@ func (m *ContextMiddleware) basicAuth(username string, password string) (*model.
|
||||
return nil, nil, fmt.Errorf("error retrieving ldap user details: %w", err)
|
||||
}
|
||||
|
||||
name := search.Name
|
||||
if name == "" {
|
||||
name = utils.Capitalize(username)
|
||||
}
|
||||
userContext.LDAP = &model.LDAPContext{
|
||||
BaseContext: model.BaseContext{
|
||||
Username: username,
|
||||
Name: utils.Capitalize(username),
|
||||
Name: name,
|
||||
},
|
||||
Groups: user.Groups,
|
||||
}
|
||||
|
||||
@@ -32,5 +32,6 @@ type LocalUser struct {
|
||||
type UserSearch struct {
|
||||
Username string
|
||||
Email string // used for LDAP, we can't throw it to LDAPUser because it would need another cache or an LDAP lookup every time
|
||||
Name string // used for LDAP cn attribute
|
||||
Type UserSearchType
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ func (auth *AuthService) SearchUser(username string) (*model.UserSearch, error)
|
||||
}
|
||||
|
||||
if auth.ldap != nil {
|
||||
userDN, email, err := auth.ldap.GetUserInfo(username)
|
||||
userDN, email, cn, err := auth.ldap.GetUserInfo(username)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get ldap user: %w", err)
|
||||
@@ -182,6 +182,7 @@ func (auth *AuthService) SearchUser(username string) (*model.UserSearch, error)
|
||||
return &model.UserSearch{
|
||||
Username: userDN,
|
||||
Email: email,
|
||||
Name: cn,
|
||||
Type: model.UserLDAP,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ func (ldap *LdapService) connect() (*ldapgo.Conn, error) {
|
||||
return ldap.conn, nil
|
||||
}
|
||||
|
||||
func (ldap *LdapService) GetUserInfo(username string) (dn string, email string, err error) {
|
||||
func (ldap *LdapService) GetUserInfo(username string) (dn string, email string, cn string, err error) {
|
||||
escapedUsername := ldapgo.EscapeFilter(username)
|
||||
filter := fmt.Sprintf(ldap.config.LDAP.SearchFilter, escapedUsername)
|
||||
|
||||
@@ -154,7 +154,7 @@ func (ldap *LdapService) GetUserInfo(username string) (dn string, email string,
|
||||
ldap.config.LDAP.BaseDN,
|
||||
ldapgo.ScopeWholeSubtree, ldapgo.NeverDerefAliases, 0, 0, false,
|
||||
filter,
|
||||
[]string{"dn", "mail"},
|
||||
[]string{"dn", "mail", "cn"},
|
||||
nil,
|
||||
)
|
||||
|
||||
@@ -163,15 +163,15 @@ func (ldap *LdapService) GetUserInfo(username string) (dn string, email string,
|
||||
|
||||
searchResult, err := ldap.conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) != 1 {
|
||||
return "", "", fmt.Errorf("multiple or no entries found for user %s", username)
|
||||
return "", "", "", fmt.Errorf("multiple or no entries found for user %s", username)
|
||||
}
|
||||
|
||||
entry := searchResult.Entries[0]
|
||||
return entry.DN, entry.GetAttributeValue("mail"), nil
|
||||
return entry.DN, entry.GetAttributeValue("mail"), entry.GetAttributeValue("cn"), nil
|
||||
}
|
||||
|
||||
func (ldap *LdapService) GetUserCount() (int, error) {
|
||||
|
||||
Reference in New Issue
Block a user