mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-11-04 08:05:42 +00:00 
			
		
		
		
	Compare commits
	
		
			15 Commits
		
	
	
		
			v3.6.1-bet
			...
			v3.6.2-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					1a13936693 | ||
| 
						 | 
					af26d705cd | ||
| 
						 | 
					2d4ceda12f | ||
| 
						 | 
					4a87af4463 | ||
| 
						 | 
					88d918d608 | ||
| 
						 | 
					5854d973ea | ||
| 
						 | 
					f25ab72747 | ||
| 
						 | 
					2233557990 | ||
| 
						 | 
					d3bec635f8 | ||
| 
						 | 
					6519644fc1 | ||
| 
						 | 
					736f65b7b2 | ||
| 
						 | 
					63d39b5500 | ||
| 
						 | 
					b735ab6f39 | ||
| 
						 | 
					232c50eaef | ||
| 
						 | 
					52b12abeb2 | 
@@ -133,7 +133,10 @@ var rootCmd = &cobra.Command{
 | 
			
		||||
				SearchFilter: config.LdapSearchFilter,
 | 
			
		||||
			}
 | 
			
		||||
			ldapService, err = ldap.NewLDAP(ldapConfig)
 | 
			
		||||
			HandleError(err, "Failed to create LDAP service")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error().Err(err).Msg("Failed to initialize LDAP service, disabling LDAP authentication")
 | 
			
		||||
				ldapService = nil
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			log.Info().Msg("LDAP not configured, using local users or OAuth")
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								codeconv.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								codeconv.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
coverage:
 | 
			
		||||
  status:
 | 
			
		||||
    project:
 | 
			
		||||
      default:
 | 
			
		||||
        informational: true
 | 
			
		||||
    patch:
 | 
			
		||||
      default:
 | 
			
		||||
        informational: true
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
        "@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",
 | 
			
		||||
        "@tanstack/react-query": "^5.83.0",
 | 
			
		||||
        "axios": "^1.10.0",
 | 
			
		||||
        "class-variance-authority": "^0.7.1",
 | 
			
		||||
        "clsx": "^2.1.1",
 | 
			
		||||
@@ -30,23 +30,23 @@
 | 
			
		||||
        "sonner": "^2.0.6",
 | 
			
		||||
        "tailwind-merge": "^3.3.1",
 | 
			
		||||
        "tailwindcss": "^4.1.11",
 | 
			
		||||
        "zod": "^4.0.2",
 | 
			
		||||
        "zod": "^4.0.5",
 | 
			
		||||
      },
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
        "@eslint/js": "^9.30.1",
 | 
			
		||||
        "@eslint/js": "^9.31.0",
 | 
			
		||||
        "@tanstack/eslint-plugin-query": "^5.81.2",
 | 
			
		||||
        "@types/node": "^24.0.13",
 | 
			
		||||
        "@types/node": "^24.0.14",
 | 
			
		||||
        "@types/react": "^19.1.8",
 | 
			
		||||
        "@types/react-dom": "^19.1.6",
 | 
			
		||||
        "@vitejs/plugin-react": "^4.6.0",
 | 
			
		||||
        "eslint": "^9.30.1",
 | 
			
		||||
        "eslint": "^9.31.0",
 | 
			
		||||
        "eslint-plugin-react-hooks": "^5.2.0",
 | 
			
		||||
        "eslint-plugin-react-refresh": "^0.4.19",
 | 
			
		||||
        "globals": "^16.3.0",
 | 
			
		||||
        "prettier": "3.6.2",
 | 
			
		||||
        "tw-animate-css": "^1.3.5",
 | 
			
		||||
        "typescript": "~5.8.3",
 | 
			
		||||
        "typescript-eslint": "^8.36.0",
 | 
			
		||||
        "typescript-eslint": "^8.37.0",
 | 
			
		||||
        "vite": "^7.0.4",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
@@ -150,11 +150,11 @@
 | 
			
		||||
 | 
			
		||||
    "@eslint/config-helpers": ["@eslint/config-helpers@0.3.0", "", {}, "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw=="],
 | 
			
		||||
 | 
			
		||||
    "@eslint/core": ["@eslint/core@0.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="],
 | 
			
		||||
    "@eslint/core": ["@eslint/core@0.15.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA=="],
 | 
			
		||||
 | 
			
		||||
    "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
 | 
			
		||||
 | 
			
		||||
    "@eslint/js": ["@eslint/js@9.30.1", "", {}, "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg=="],
 | 
			
		||||
    "@eslint/js": ["@eslint/js@9.31.0", "", {}, "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw=="],
 | 
			
		||||
 | 
			
		||||
    "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
 | 
			
		||||
 | 
			
		||||
@@ -328,9 +328,9 @@
 | 
			
		||||
 | 
			
		||||
    "@tanstack/eslint-plugin-query": ["@tanstack/eslint-plugin-query@5.81.2", "", { "dependencies": { "@typescript-eslint/utils": "^8.18.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-h4k6P6fm5VhKP5NkK+0TTVpGGyKQdx6tk7NYYG7J7PkSu7ClpLgBihw7yzK8N3n5zPaF3IMyErxfoNiXWH/3/A=="],
 | 
			
		||||
 | 
			
		||||
    "@tanstack/query-core": ["@tanstack/query-core@5.82.0", "", {}, "sha512-JrjoVuaajBQtnoWSg8iaPHaT4mW73lK2t+exxHNOSMqy0+13eKLqJgTKXKImLejQIfdAHQ6Un0njEhOvUtOd5w=="],
 | 
			
		||||
    "@tanstack/query-core": ["@tanstack/query-core@5.83.0", "", {}, "sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA=="],
 | 
			
		||||
 | 
			
		||||
    "@tanstack/react-query": ["@tanstack/react-query@5.82.0", "", { "dependencies": { "@tanstack/query-core": "5.82.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-mnk8/ofKEthFeMdhV1dV8YXRf+9HqvXAcciXkoo755d/ocfWq7N/Y9jGOzS3h7ZW9dDGwSIhs3/HANWUBsyqYg=="],
 | 
			
		||||
    "@tanstack/react-query": ["@tanstack/react-query@5.83.0", "", { "dependencies": { "@tanstack/query-core": "5.83.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ=="],
 | 
			
		||||
 | 
			
		||||
    "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
 | 
			
		||||
 | 
			
		||||
@@ -354,7 +354,7 @@
 | 
			
		||||
 | 
			
		||||
    "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
 | 
			
		||||
 | 
			
		||||
    "@types/node": ["@types/node@24.0.13", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ=="],
 | 
			
		||||
    "@types/node": ["@types/node@24.0.14", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw=="],
 | 
			
		||||
 | 
			
		||||
    "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
 | 
			
		||||
 | 
			
		||||
@@ -364,25 +364,25 @@
 | 
			
		||||
 | 
			
		||||
    "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.36.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/type-utils": "8.36.0", "@typescript-eslint/utils": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.36.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg=="],
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.37.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/type-utils": "8.37.0", "@typescript-eslint/utils": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.37.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/parser": ["@typescript-eslint/parser@8.36.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q=="],
 | 
			
		||||
    "@typescript-eslint/parser": ["@typescript-eslint/parser@8.37.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.34.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.34.1", "@typescript-eslint/types": "^8.34.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA=="],
 | 
			
		||||
    "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.37.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.37.0", "@typescript-eslint/types": "^8.37.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.34.1", "", { "dependencies": { "@typescript-eslint/types": "8.34.1", "@typescript-eslint/visitor-keys": "8.34.1" } }, "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.34.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg=="],
 | 
			
		||||
    "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.37.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.36.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/utils": "8.36.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg=="],
 | 
			
		||||
    "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0", "@typescript-eslint/utils": "8.37.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/types": ["@typescript-eslint/types@8.34.1", "", {}, "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.34.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.34.1", "@typescript-eslint/tsconfig-utils": "8.34.1", "@typescript-eslint/types": "8.34.1", "@typescript-eslint/visitor-keys": "8.34.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA=="],
 | 
			
		||||
    "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.37.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.37.0", "@typescript-eslint/tsconfig-utils": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/utils": ["@typescript-eslint/utils@8.34.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/types": "8.34.1", "@typescript-eslint/typescript-estree": "8.34.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA=="],
 | 
			
		||||
    "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w=="],
 | 
			
		||||
 | 
			
		||||
    "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
 | 
			
		||||
 | 
			
		||||
@@ -494,7 +494,7 @@
 | 
			
		||||
 | 
			
		||||
    "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
 | 
			
		||||
 | 
			
		||||
    "eslint": ["eslint@9.30.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.30.1", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ=="],
 | 
			
		||||
    "eslint": ["eslint@9.31.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.31.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ=="],
 | 
			
		||||
 | 
			
		||||
    "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="],
 | 
			
		||||
 | 
			
		||||
@@ -872,7 +872,7 @@
 | 
			
		||||
 | 
			
		||||
    "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
 | 
			
		||||
 | 
			
		||||
    "typescript-eslint": ["typescript-eslint@8.36.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.36.0", "@typescript-eslint/parser": "8.36.0", "@typescript-eslint/utils": "8.36.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA=="],
 | 
			
		||||
    "typescript-eslint": ["typescript-eslint@8.37.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.37.0", "@typescript-eslint/parser": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0", "@typescript-eslint/utils": "8.37.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA=="],
 | 
			
		||||
 | 
			
		||||
    "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
 | 
			
		||||
 | 
			
		||||
@@ -912,7 +912,7 @@
 | 
			
		||||
 | 
			
		||||
    "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
 | 
			
		||||
 | 
			
		||||
    "zod": ["zod@4.0.2", "", {}, "sha512-X2niJNY54MGam4L6Kj0AxeedeDIi/E5QFW0On2faSX5J4/pfLk1tW+cRMIMoojnCavn/u5W/kX17e1CSGnKMxA=="],
 | 
			
		||||
    "zod": ["zod@4.0.5", "", {}, "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA=="],
 | 
			
		||||
 | 
			
		||||
    "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
 | 
			
		||||
 | 
			
		||||
@@ -932,6 +932,8 @@
 | 
			
		||||
 | 
			
		||||
    "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
 | 
			
		||||
 | 
			
		||||
    "@eslint/plugin-kit/@eslint/core": ["@eslint/core@0.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="],
 | 
			
		||||
 | 
			
		||||
    "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
 | 
			
		||||
 | 
			
		||||
    "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
 | 
			
		||||
@@ -958,31 +960,33 @@
 | 
			
		||||
 | 
			
		||||
    "@types/babel__traverse/@babel/types": ["@babel/types@7.27.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0" } }, "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA=="],
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0" } }, "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.36.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g=="],
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.37.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.4", "", {}, "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0" } }, "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA=="],
 | 
			
		||||
    "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0" } }, "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
 | 
			
		||||
    "@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.37.0", "", {}, "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.36.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg=="],
 | 
			
		||||
    "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.37.0", "", {}, "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.34.1", "", { "dependencies": { "@typescript-eslint/types": "8.34.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.36.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg=="],
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.37.0", "", {}, "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.36.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g=="],
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.37.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.34.1", "", { "dependencies": { "@typescript-eslint/types": "8.34.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw=="],
 | 
			
		||||
    "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.37.0", "", {}, "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
 | 
			
		||||
    "@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.34.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.34.1", "@typescript-eslint/tsconfig-utils": "8.34.1", "@typescript-eslint/types": "8.34.1", "@typescript-eslint/visitor-keys": "8.34.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.37.0", "", {}, "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ=="],
 | 
			
		||||
 | 
			
		||||
    "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
 | 
			
		||||
 | 
			
		||||
@@ -996,7 +1000,7 @@
 | 
			
		||||
 | 
			
		||||
    "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
 | 
			
		||||
 | 
			
		||||
    "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.36.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g=="],
 | 
			
		||||
    "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.37.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A=="],
 | 
			
		||||
 | 
			
		||||
    "@babel/helper-module-imports/@babel/traverse/@babel/generator": ["@babel/generator@7.27.1", "", { "dependencies": { "@babel/parser": "^7.27.1", "@babel/types": "^7.27.1", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w=="],
 | 
			
		||||
 | 
			
		||||
@@ -1018,66 +1022,30 @@
 | 
			
		||||
 | 
			
		||||
    "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.37.0", "", {}, "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.37.0", "", {}, "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.36.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/parser/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.36.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/parser/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.36.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/parser/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.36.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.36.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0" } }, "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0" } }, "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
 | 
			
		||||
 | 
			
		||||
    "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0" } }, "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA=="],
 | 
			
		||||
    "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.34.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.34.1", "@typescript-eslint/types": "^8.34.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA=="],
 | 
			
		||||
 | 
			
		||||
    "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
 | 
			
		||||
    "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.34.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg=="],
 | 
			
		||||
 | 
			
		||||
    "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.36.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg=="],
 | 
			
		||||
    "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.34.1", "", { "dependencies": { "@typescript-eslint/types": "8.34.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
 | 
			
		||||
 | 
			
		||||
    "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0" } }, "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA=="],
 | 
			
		||||
 | 
			
		||||
    "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.37.0", "", {}, "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ=="],
 | 
			
		||||
 | 
			
		||||
    "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.36.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.36.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
 | 
			
		||||
 | 
			
		||||
    "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.36.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g=="],
 | 
			
		||||
 | 
			
		||||
    "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.36.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA=="],
 | 
			
		||||
 | 
			
		||||
    "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
 | 
			
		||||
 | 
			
		||||
    "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
 | 
			
		||||
 | 
			
		||||
    "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
 | 
			
		||||
 | 
			
		||||
    "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
 | 
			
		||||
    "@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
    "@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",
 | 
			
		||||
    "@tanstack/react-query": "^5.83.0",
 | 
			
		||||
    "axios": "^1.10.0",
 | 
			
		||||
    "class-variance-authority": "^0.7.1",
 | 
			
		||||
    "clsx": "^2.1.1",
 | 
			
		||||
@@ -36,23 +36,23 @@
 | 
			
		||||
    "sonner": "^2.0.6",
 | 
			
		||||
    "tailwind-merge": "^3.3.1",
 | 
			
		||||
    "tailwindcss": "^4.1.11",
 | 
			
		||||
    "zod": "^4.0.2"
 | 
			
		||||
    "zod": "^4.0.5"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@eslint/js": "^9.30.1",
 | 
			
		||||
    "@eslint/js": "^9.31.0",
 | 
			
		||||
    "@tanstack/eslint-plugin-query": "^5.81.2",
 | 
			
		||||
    "@types/node": "^24.0.13",
 | 
			
		||||
    "@types/node": "^24.0.14",
 | 
			
		||||
    "@types/react": "^19.1.8",
 | 
			
		||||
    "@types/react-dom": "^19.1.6",
 | 
			
		||||
    "@vitejs/plugin-react": "^4.6.0",
 | 
			
		||||
    "eslint": "^9.30.1",
 | 
			
		||||
    "eslint": "^9.31.0",
 | 
			
		||||
    "eslint-plugin-react-hooks": "^5.2.0",
 | 
			
		||||
    "eslint-plugin-react-refresh": "^0.4.19",
 | 
			
		||||
    "globals": "^16.3.0",
 | 
			
		||||
    "prettier": "3.6.2",
 | 
			
		||||
    "tw-animate-css": "^1.3.5",
 | 
			
		||||
    "typescript": "~5.8.3",
 | 
			
		||||
    "typescript-eslint": "^8.36.0",
 | 
			
		||||
    "typescript-eslint": "^8.37.0",
 | 
			
		||||
    "vite": "^7.0.4"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,6 +12,7 @@ import {
 | 
			
		||||
} from "../ui/form";
 | 
			
		||||
import { Button } from "../ui/button";
 | 
			
		||||
import { loginSchema, LoginSchema } from "@/schemas/login-schema";
 | 
			
		||||
import z from "zod";
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  onSubmit: (data: LoginSchema) => void;
 | 
			
		||||
@@ -22,6 +23,11 @@ export const LoginForm = (props: Props) => {
 | 
			
		||||
  const { onSubmit, loading } = props;
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  z.config({
 | 
			
		||||
    customError: (iss) =>
 | 
			
		||||
      iss.input === undefined ? t("fieldRequired") : t("invalidInput"),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const form = useForm<LoginSchema>({
 | 
			
		||||
    resolver: zodResolver(loginSchema),
 | 
			
		||||
  });
 | 
			
		||||
@@ -39,6 +45,7 @@ export const LoginForm = (props: Props) => {
 | 
			
		||||
                <Input
 | 
			
		||||
                  placeholder={t("loginUsername")}
 | 
			
		||||
                  disabled={loading}
 | 
			
		||||
                  autoComplete="username"
 | 
			
		||||
                  {...field}
 | 
			
		||||
                />
 | 
			
		||||
              </FormControl>
 | 
			
		||||
@@ -58,6 +65,7 @@ export const LoginForm = (props: Props) => {
 | 
			
		||||
                    placeholder={t("loginPassword")}
 | 
			
		||||
                    type="password"
 | 
			
		||||
                    disabled={loading}
 | 
			
		||||
                    autoComplete="current-password"
 | 
			
		||||
                    {...field}
 | 
			
		||||
                  />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,8 @@ import {
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { useForm } from "react-hook-form";
 | 
			
		||||
import { totpSchema, TotpSchema } from "@/schemas/totp-schema";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import z from "zod";
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  formId: string;
 | 
			
		||||
@@ -17,6 +19,12 @@ interface Props {
 | 
			
		||||
 | 
			
		||||
export const TotpForm = (props: Props) => {
 | 
			
		||||
  const { formId, onSubmit, loading } = props;
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  z.config({
 | 
			
		||||
    customError: (iss) =>
 | 
			
		||||
      iss.input === undefined ? t("fieldRequired") : t("invalidInput"),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const form = useForm<TotpSchema>({
 | 
			
		||||
    resolver: zodResolver(totpSchema),
 | 
			
		||||
@@ -31,7 +39,12 @@ export const TotpForm = (props: Props) => {
 | 
			
		||||
          render={({ field }) => (
 | 
			
		||||
            <FormItem>
 | 
			
		||||
              <FormControl>
 | 
			
		||||
                <InputOTP maxLength={6} disabled={loading} {...field}>
 | 
			
		||||
                <InputOTP
 | 
			
		||||
                  maxLength={6}
 | 
			
		||||
                  disabled={loading}
 | 
			
		||||
                  {...field}
 | 
			
		||||
                  autoComplete="one-time-code"
 | 
			
		||||
                >
 | 
			
		||||
                  <InputOTPGroup>
 | 
			
		||||
                    <InputOTPSlot index={0} />
 | 
			
		||||
                    <InputOTPSlot index={1} />
 | 
			
		||||
 
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,5 +50,6 @@
 | 
			
		||||
    "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."
 | 
			
		||||
    "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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,5 +50,6 @@
 | 
			
		||||
    "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."
 | 
			
		||||
    "errorSubtitle": "Der opstod en fejl under forsøget på at udføre denne handling. Tjek venligst konsollen for mere information.",
 | 
			
		||||
    "forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,5 +50,6 @@
 | 
			
		||||
    "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."
 | 
			
		||||
    "errorSubtitle": "Beim Versuch, diese Aktion auszuführen, ist ein Fehler aufgetreten. Bitte überprüfen Sie die Konsole für weitere Informationen.",
 | 
			
		||||
    "forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,5 +50,6 @@
 | 
			
		||||
    "forgotPasswordTitle": "Ξεχάσατε το συνθηματικό σας;",
 | 
			
		||||
    "failedToFetchProvidersTitle": "Αποτυχία φόρτωσης παρόχων πιστοποίησης. Παρακαλώ ελέγξτε τις ρυθμίσεις σας.",
 | 
			
		||||
    "errorTitle": "Παρουσιάστηκε ένα σφάλμα",
 | 
			
		||||
    "errorSubtitle": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια εκτέλεσης αυτής της ενέργειας. Ελέγξτε την κονσόλα για περισσότερες πληροφορίες."
 | 
			
		||||
    "errorSubtitle": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια εκτέλεσης αυτής της ενέργειας. Ελέγξτε την κονσόλα για περισσότερες πληροφορίες.",
 | 
			
		||||
    "forgotPasswordMessage": "Μπορείτε να επαναφέρετε τον κωδικό πρόσβασής σας αλλάζοντας τη μεταβλητή περιβάλλοντος `USERS`."
 | 
			
		||||
}
 | 
			
		||||
@@ -51,5 +51,7 @@
 | 
			
		||||
    "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.",
 | 
			
		||||
    "forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable."
 | 
			
		||||
    "forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
 | 
			
		||||
    "fieldRequired": "This field is required",
 | 
			
		||||
    "invalidInput": "Invalid input"
 | 
			
		||||
}
 | 
			
		||||
@@ -51,5 +51,7 @@
 | 
			
		||||
    "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.",
 | 
			
		||||
    "forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable."
 | 
			
		||||
    "forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
 | 
			
		||||
    "fieldRequired": "This field is required",
 | 
			
		||||
    "invalidInput": "Invalid input"
 | 
			
		||||
}
 | 
			
		||||
@@ -50,5 +50,6 @@
 | 
			
		||||
    "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."
 | 
			
		||||
    "errorSubtitle": "Ocurrió un error mientras se trataba de realizar esta acción. Por favor, revise la consola para más información.",
 | 
			
		||||
    "forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +1,15 @@
 | 
			
		||||
{
 | 
			
		||||
    "loginTitle": "Bienvenue, connectez-vous avec",
 | 
			
		||||
    "loginTitleSimple": "Welcome back, please login",
 | 
			
		||||
    "loginDivider": "Or",
 | 
			
		||||
    "loginTitleSimple": "De retour parmi nous, veuillez vous connecter",
 | 
			
		||||
    "loginDivider": "Ou",
 | 
			
		||||
    "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": "You failed to login too many times. Please try again later",
 | 
			
		||||
    "loginFailRateLimit": "Vous avez échoué trop de fois à vous connecter. Veuillez réessayer ultérieurement",
 | 
			
		||||
    "loginSuccessTitle": "Connecté",
 | 
			
		||||
    "loginSuccessSubtitle": "Bienvenue!",
 | 
			
		||||
    "loginSuccessSubtitle": "Bienvenue !",
 | 
			
		||||
    "loginOauthFailTitle": "Une erreur s'est produite",
 | 
			
		||||
    "loginOauthFailSubtitle": "Impossible d'obtenir l'URL OAuth",
 | 
			
		||||
    "loginOauthSuccessTitle": "Redirection",
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
    "continueInvalidRedirectTitle": "Redirection invalide",
 | 
			
		||||
    "continueInvalidRedirectSubtitle": "L'URL de redirection est invalide",
 | 
			
		||||
    "continueInsecureRedirectTitle": "Redirection non sécurisée",
 | 
			
		||||
    "continueInsecureRedirectSubtitle": "You are trying to redirect from <code>https</code> to <code>http</code> which is not secure. Are you sure you want to continue?",
 | 
			
		||||
    "continueInsecureRedirectSubtitle": "Vous tentez de rediriger de <code>https</code> vers <code>http</code>, ce qui n'est pas sécurisé. Êtes-vous sûr de vouloir continuer ?",
 | 
			
		||||
    "continueTitle": "Continuer",
 | 
			
		||||
    "continueSubtitle": "Cliquez sur le bouton pour continuer vers votre application.",
 | 
			
		||||
    "logoutFailTitle": "Échec de la déconnexion",
 | 
			
		||||
@@ -27,8 +27,8 @@
 | 
			
		||||
    "logoutSuccessTitle": "Déconnecté",
 | 
			
		||||
    "logoutSuccessSubtitle": "Vous avez été déconnecté",
 | 
			
		||||
    "logoutTitle": "Déconnexion",
 | 
			
		||||
    "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": "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> via le fournisseur OAuth {{provider}}. Cliquez sur le bouton ci-dessous pour vous déconnecter.",
 | 
			
		||||
    "notFoundTitle": "Page introuvable",
 | 
			
		||||
    "notFoundSubtitle": "La page recherchée n'existe pas.",
 | 
			
		||||
    "notFoundButton": "Retour à la page d'accueil",
 | 
			
		||||
@@ -37,18 +37,19 @@
 | 
			
		||||
    "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": "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>.",
 | 
			
		||||
    "totpSubtitle": "Veuillez saisir le code de votre application d'authentification.",
 | 
			
		||||
    "unauthorizedTitle": "Unauthorized",
 | 
			
		||||
    "unauthorizedResourceSubtitle": "L'utilisateur avec le nom d'utilisateur <code>{{username}}</code> n'est pas autorisé à accéder à la ressource <code>{{resource}}</code>.",
 | 
			
		||||
    "unauthorizedLoginSubtitle": "L'utilisateur avec le nom d'utilisateur <code>{{username}}</code> n'est pas autorisé à se connecter.",
 | 
			
		||||
    "unauthorizedGroupsSubtitle": "L'utilisateur avec le nom d'utilisateur <code>{{username}}</code> n'appartient pas aux groupes requis par la ressource <code>{{resource}}</code>.",
 | 
			
		||||
    "unauthorizedIpSubtitle": "Votre adresse IP <code>{{ip}}</code> n'est pas autorisée à accéder à la ressource <code>{{resource}}</code>.",
 | 
			
		||||
    "unauthorizedButton": "Réessayer",
 | 
			
		||||
    "untrustedRedirectTitle": "Untrusted redirect",
 | 
			
		||||
    "untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{domain}}</code>). Are you sure you want to continue?",
 | 
			
		||||
    "cancelTitle": "Cancel",
 | 
			
		||||
    "forgotPasswordTitle": "Forgot your password?",
 | 
			
		||||
    "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."
 | 
			
		||||
    "untrustedRedirectTitle": "Redirection non fiable",
 | 
			
		||||
    "untrustedRedirectSubtitle": "Vous tentez de rediriger vers un domaine qui ne correspond pas à votre domaine configuré (<code>{{domain}}</code>). Êtes-vous sûr de vouloir continuer ?",
 | 
			
		||||
    "cancelTitle": "Annuler",
 | 
			
		||||
    "forgotPasswordTitle": "Mot de passe oublié ?",
 | 
			
		||||
    "failedToFetchProvidersTitle": "Échec du chargement des fournisseurs d'authentification. Veuillez vérifier votre configuration.",
 | 
			
		||||
    "errorTitle": "Une erreur est survenue",
 | 
			
		||||
    "errorSubtitle": "Une erreur est survenue lors de l'exécution de cette action. Veuillez consulter la console pour plus d'informations.",
 | 
			
		||||
    "forgotPasswordMessage": "Vous pouvez réinitialiser votre mot de passe en modifiant la variable d'environnement `USERS`."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -26,7 +26,7 @@
 | 
			
		||||
    "logoutFailSubtitle": "Spróbuj ponownie",
 | 
			
		||||
    "logoutSuccessTitle": "Wylogowano",
 | 
			
		||||
    "logoutSuccessSubtitle": "Zostałeś wylogowany",
 | 
			
		||||
    "logoutTitle": "Wylogowanie",
 | 
			
		||||
    "logoutTitle": "Wyloguj się",
 | 
			
		||||
    "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",
 | 
			
		||||
@@ -50,5 +50,6 @@
 | 
			
		||||
    "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."
 | 
			
		||||
    "errorSubtitle": "Wystąpił błąd podczas próby wykonania tej czynności. Sprawdź konsolę, aby uzyskać więcej informacji.",
 | 
			
		||||
    "forgotPasswordMessage": "Możesz zresetować hasło, zmieniając zmienną środowiskową `USERS`."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,5 +50,6 @@
 | 
			
		||||
    "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."
 | 
			
		||||
    "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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,5 +50,6 @@
 | 
			
		||||
    "forgotPasswordTitle": "Забыли пароль?",
 | 
			
		||||
    "failedToFetchProvidersTitle": "Не удалось загрузить провайдеров аутентификации. Пожалуйста, проверьте конфигурацию.",
 | 
			
		||||
    "errorTitle": "Произошла ошибка",
 | 
			
		||||
    "errorSubtitle": "Произошла ошибка при попытке выполнить это действие. Проверьте консоль для дополнительной информации."
 | 
			
		||||
    "errorSubtitle": "Произошла ошибка при попытке выполнить это действие. Проверьте консоль для дополнительной информации.",
 | 
			
		||||
    "forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,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."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,5 +50,6 @@
 | 
			
		||||
    "forgotPasswordTitle": "忘记密码?",
 | 
			
		||||
    "failedToFetchProvidersTitle": "加载身份验证提供程序失败,请检查您的配置。",
 | 
			
		||||
    "errorTitle": "发生了错误",
 | 
			
		||||
    "errorSubtitle": "执行此操作时发生错误,请检查控制台以获取更多信息。"
 | 
			
		||||
    "errorSubtitle": "执行此操作时发生错误,请检查控制台以获取更多信息。",
 | 
			
		||||
    "forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable."
 | 
			
		||||
}
 | 
			
		||||
@@ -50,5 +50,6 @@
 | 
			
		||||
    "forgotPasswordTitle": "忘記密碼?",
 | 
			
		||||
    "failedToFetchProvidersTitle": "載入驗證供應商失敗。請檢查您的設定。",
 | 
			
		||||
    "errorTitle": "發生錯誤",
 | 
			
		||||
    "errorSubtitle": "執行此操作時發生錯誤。請檢查主控台以獲取更多資訊。"
 | 
			
		||||
    "errorSubtitle": "執行此操作時發生錯誤。請檢查主控台以獲取更多資訊。",
 | 
			
		||||
    "forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable."
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							@@ -17,6 +17,7 @@ require (
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
 | 
			
		||||
	github.com/cenkalti/backoff/v5 v5.0.2 // 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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							@@ -26,6 +26,8 @@ github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
 | 
			
		||||
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
 | 
			
		||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
 | 
			
		||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 | 
			
		||||
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
 | 
			
		||||
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
 | 
			
		||||
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
 | 
			
		||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
 | 
			
		||||
github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) {
 | 
			
		||||
 | 
			
		||||
	// If there was an error getting the session, it might be invalid so let's clear it and retry
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Warn().Err(err).Msg("Invalid session, clearing cookie and retrying")
 | 
			
		||||
		log.Error().Err(err).Msg("Invalid session, clearing cookie and retrying")
 | 
			
		||||
		c.SetCookie(auth.Config.SessionCookieName, "", -1, "/", fmt.Sprintf(".%s", auth.Config.Domain), auth.Config.CookieSecure, true)
 | 
			
		||||
		session, err = auth.Store.Get(c.Request, auth.Config.SessionCookieName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -88,7 +88,9 @@ func (auth *Auth) SearchUser(username string) types.UserSearch {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return types.UserSearch{}
 | 
			
		||||
	return types.UserSearch{
 | 
			
		||||
		Type: "unknown",
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (auth *Auth) VerifyUser(search types.UserSearch, password string) bool {
 | 
			
		||||
@@ -370,7 +372,7 @@ func (auth *Auth) AuthEnabled(uri string, labels types.Labels) (bool, error) {
 | 
			
		||||
 | 
			
		||||
	// If there is an error, invalid regex, auth enabled
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Warn().Err(err).Msg("Invalid regex")
 | 
			
		||||
		log.Error().Err(err).Msg("Invalid regex")
 | 
			
		||||
		return true, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -399,7 +401,7 @@ func (auth *Auth) CheckIP(labels types.Labels, ip string) bool {
 | 
			
		||||
	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")
 | 
			
		||||
			log.Error().Err(err).Str("item", blocked).Msg("Invalid IP/CIDR in block list")
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if res {
 | 
			
		||||
@@ -412,7 +414,7 @@ func (auth *Auth) CheckIP(labels types.Labels, ip string) bool {
 | 
			
		||||
	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")
 | 
			
		||||
			log.Error().Err(err).Str("item", allowed).Msg("Invalid IP/CIDR in allow list")
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if res {
 | 
			
		||||
@@ -436,7 +438,7 @@ func (auth *Auth) BypassedIP(labels types.Labels, ip string) bool {
 | 
			
		||||
	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")
 | 
			
		||||
			log.Error().Err(err).Str("item", bypassed).Msg("Invalid IP/CIDR in bypass list")
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if res {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ type Claims struct {
 | 
			
		||||
	Name              string `json:"name"`
 | 
			
		||||
	Email             string `json:"email"`
 | 
			
		||||
	PreferredUsername string `json:"preferred_username"`
 | 
			
		||||
	Groups            []string `json:"groups"`
 | 
			
		||||
	Groups            any    `json:"groups"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Version information
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package server_test
 | 
			
		||||
package handlers_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
@@ -189,7 +189,7 @@ func (h *Handlers) OAuthCallbackHandler(c *gin.Context) {
 | 
			
		||||
		Name:        name,
 | 
			
		||||
		Email:       user.Email,
 | 
			
		||||
		Provider:    providerName.Provider,
 | 
			
		||||
		OAuthGroups: strings.Join(user.Groups, ","),
 | 
			
		||||
		OAuthGroups: utils.CoalesceToString(user.Groups),
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Check if we have a redirect URI
 | 
			
		||||
 
 | 
			
		||||
@@ -40,10 +40,7 @@ func (h *Handlers) ProxyHandler(c *gin.Context) {
 | 
			
		||||
	proto := c.Request.Header.Get("X-Forwarded-Proto")
 | 
			
		||||
	host := c.Request.Header.Get("X-Forwarded-Host")
 | 
			
		||||
 | 
			
		||||
	// Remove the port from the host if it exists
 | 
			
		||||
	hostPortless := strings.Split(host, ":")[0] // *lol*
 | 
			
		||||
 | 
			
		||||
	// Get the id
 | 
			
		||||
	id := strings.Split(hostPortless, ".")[0]
 | 
			
		||||
 | 
			
		||||
	labels, err := h.Docker.GetLabels(id, hostPortless)
 | 
			
		||||
@@ -66,10 +63,10 @@ func (h *Handlers) ProxyHandler(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
	ip := c.ClientIP()
 | 
			
		||||
 | 
			
		||||
	// Check if the IP is in bypass list
 | 
			
		||||
	if h.Auth.BypassedIP(labels, ip) {
 | 
			
		||||
		headersParsed := utils.ParseHeaders(labels.Headers)
 | 
			
		||||
		c.Header("Authorization", c.Request.Header.Get("Authorization"))
 | 
			
		||||
 | 
			
		||||
		headersParsed := utils.ParseHeaders(labels.Headers)
 | 
			
		||||
		for key, value := range headersParsed {
 | 
			
		||||
			log.Debug().Str("key", key).Msg("Setting header")
 | 
			
		||||
			c.Header(key, value)
 | 
			
		||||
@@ -87,7 +84,6 @@ func (h *Handlers) ProxyHandler(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if the IP is allowed/blocked
 | 
			
		||||
	if !h.Auth.CheckIP(labels, ip) {
 | 
			
		||||
		if proxy.Proxy == "nginx" || !isBrowser {
 | 
			
		||||
			c.JSON(403, gin.H{
 | 
			
		||||
@@ -113,7 +109,6 @@ func (h *Handlers) ProxyHandler(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if auth is enabled
 | 
			
		||||
	authEnabled, err := h.Auth.AuthEnabled(uri, labels)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error().Err(err).Msg("Failed to check if app is allowed")
 | 
			
		||||
@@ -129,8 +124,9 @@ func (h *Handlers) ProxyHandler(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If auth is not enabled, return 200
 | 
			
		||||
	if !authEnabled {
 | 
			
		||||
		c.Header("Authorization", c.Request.Header.Get("Authorization"))
 | 
			
		||||
 | 
			
		||||
		headersParsed := utils.ParseHeaders(labels.Headers)
 | 
			
		||||
		for key, value := range headersParsed {
 | 
			
		||||
			log.Debug().Str("key", key).Msg("Setting header")
 | 
			
		||||
@@ -150,7 +146,6 @@ func (h *Handlers) ProxyHandler(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get user context
 | 
			
		||||
	userContext := h.Hooks.UseUserContext(c)
 | 
			
		||||
 | 
			
		||||
	// If we are using basic auth, we need to check if the user has totp and if it does then disable basic auth
 | 
			
		||||
@@ -159,7 +154,6 @@ func (h *Handlers) ProxyHandler(c *gin.Context) {
 | 
			
		||||
		userContext.IsLoggedIn = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if user is logged in
 | 
			
		||||
	if userContext.IsLoggedIn {
 | 
			
		||||
		log.Debug().Msg("Authenticated")
 | 
			
		||||
 | 
			
		||||
@@ -200,7 +194,6 @@ func (h *Handlers) ProxyHandler(c *gin.Context) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check groups if using OAuth
 | 
			
		||||
		if userContext.OAuth {
 | 
			
		||||
			groupOk := h.Auth.OAuthGroup(c, userContext, labels)
 | 
			
		||||
 | 
			
		||||
@@ -239,19 +232,18 @@ func (h *Handlers) ProxyHandler(c *gin.Context) {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Header("Authorization", c.Request.Header.Get("Authorization"))
 | 
			
		||||
		c.Header("Remote-User", utils.SanitizeHeader(userContext.Username))
 | 
			
		||||
		c.Header("Remote-Name", utils.SanitizeHeader(userContext.Name))
 | 
			
		||||
		c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email))
 | 
			
		||||
		c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
 | 
			
		||||
 | 
			
		||||
		// Set the rest of the headers
 | 
			
		||||
		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))))
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"tinyauth/internal/auth"
 | 
			
		||||
	"tinyauth/internal/oauth"
 | 
			
		||||
	"tinyauth/internal/providers"
 | 
			
		||||
	"tinyauth/internal/types"
 | 
			
		||||
	"tinyauth/internal/utils"
 | 
			
		||||
@@ -27,28 +28,92 @@ func NewHooks(config types.HooksConfig, auth *auth.Auth, providers *providers.Pr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
 | 
			
		||||
	// Get session cookie and basic auth
 | 
			
		||||
	cookie, err := hooks.Auth.GetSessionCookie(c)
 | 
			
		||||
	var provider *oauth.OAuth
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error().Err(err).Msg("Failed to get session cookie")
 | 
			
		||||
		goto basic
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cookie.TotpPending {
 | 
			
		||||
		log.Debug().Msg("Totp pending")
 | 
			
		||||
		return types.UserContext{
 | 
			
		||||
			Username:    cookie.Username,
 | 
			
		||||
			Name:        cookie.Name,
 | 
			
		||||
			Email:       cookie.Email,
 | 
			
		||||
			Provider:    cookie.Provider,
 | 
			
		||||
			TotpPending: true,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cookie.Provider == "username" {
 | 
			
		||||
		log.Debug().Msg("Provider is username")
 | 
			
		||||
 | 
			
		||||
		userSearch := hooks.Auth.SearchUser(cookie.Username)
 | 
			
		||||
 | 
			
		||||
		if userSearch.Type == "unknown" {
 | 
			
		||||
			log.Warn().Str("username", cookie.Username).Msg("User does not exist")
 | 
			
		||||
			goto basic
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Debug().Str("type", userSearch.Type).Msg("User exists")
 | 
			
		||||
 | 
			
		||||
		return types.UserContext{
 | 
			
		||||
			Username:   cookie.Username,
 | 
			
		||||
			Name:       cookie.Name,
 | 
			
		||||
			Email:      cookie.Email,
 | 
			
		||||
			IsLoggedIn: true,
 | 
			
		||||
			Provider:   "username",
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Debug().Msg("Provider is not username")
 | 
			
		||||
 | 
			
		||||
	provider = hooks.Providers.GetProvider(cookie.Provider)
 | 
			
		||||
 | 
			
		||||
	if provider != nil {
 | 
			
		||||
		log.Debug().Msg("Provider exists")
 | 
			
		||||
 | 
			
		||||
		if !hooks.Auth.EmailWhitelisted(cookie.Email) {
 | 
			
		||||
			log.Warn().Str("email", cookie.Email).Msg("Email is not whitelisted")
 | 
			
		||||
			hooks.Auth.DeleteSessionCookie(c)
 | 
			
		||||
			goto basic
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Debug().Msg("Email is whitelisted")
 | 
			
		||||
 | 
			
		||||
		return types.UserContext{
 | 
			
		||||
			Username:    cookie.Username,
 | 
			
		||||
			Name:        cookie.Name,
 | 
			
		||||
			Email:       cookie.Email,
 | 
			
		||||
			IsLoggedIn:  true,
 | 
			
		||||
			OAuth:       true,
 | 
			
		||||
			Provider:    cookie.Provider,
 | 
			
		||||
			OAuthGroups: cookie.OAuthGroups,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
basic:
 | 
			
		||||
	log.Debug().Msg("Trying basic auth")
 | 
			
		||||
 | 
			
		||||
	basic := hooks.Auth.GetBasicAuth(c)
 | 
			
		||||
 | 
			
		||||
	// Check if basic auth is set
 | 
			
		||||
	if basic != nil {
 | 
			
		||||
		log.Debug().Msg("Got basic auth")
 | 
			
		||||
 | 
			
		||||
		userSearch := hooks.Auth.SearchUser(basic.Username)
 | 
			
		||||
 | 
			
		||||
		if userSearch.Type == "" {
 | 
			
		||||
			log.Error().Str("username", basic.Username).Msg("User does not exist")
 | 
			
		||||
		if userSearch.Type == "unkown" {
 | 
			
		||||
			log.Error().Str("username", basic.Username).Msg("Basic auth user does not exist")
 | 
			
		||||
			return types.UserContext{}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Verify the user
 | 
			
		||||
		if !hooks.Auth.VerifyUser(userSearch, basic.Password) {
 | 
			
		||||
			log.Error().Str("username", basic.Username).Msg("Password incorrect")
 | 
			
		||||
			log.Error().Str("username", basic.Username).Msg("Basic auth user password incorrect")
 | 
			
		||||
			return types.UserContext{}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Get the user type
 | 
			
		||||
		if userSearch.Type == "ldap" {
 | 
			
		||||
			log.Debug().Msg("User is LDAP")
 | 
			
		||||
 | 
			
		||||
@@ -75,73 +140,5 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check cookie error after basic auth
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error().Err(err).Msg("Failed to get session cookie")
 | 
			
		||||
		return types.UserContext{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cookie.TotpPending {
 | 
			
		||||
		log.Debug().Msg("Totp pending")
 | 
			
		||||
		return types.UserContext{
 | 
			
		||||
			Username:    cookie.Username,
 | 
			
		||||
			Name:        cookie.Name,
 | 
			
		||||
			Email:       cookie.Email,
 | 
			
		||||
			Provider:    cookie.Provider,
 | 
			
		||||
			TotpPending: true,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if session cookie is username/password auth
 | 
			
		||||
	if cookie.Provider == "username" {
 | 
			
		||||
		log.Debug().Msg("Provider is username")
 | 
			
		||||
 | 
			
		||||
		userSearch := hooks.Auth.SearchUser(cookie.Username)
 | 
			
		||||
 | 
			
		||||
		if userSearch.Type == "" {
 | 
			
		||||
			log.Error().Str("username", cookie.Username).Msg("User does not exist")
 | 
			
		||||
			return types.UserContext{}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Debug().Str("type", userSearch.Type).Msg("User exists")
 | 
			
		||||
 | 
			
		||||
		return types.UserContext{
 | 
			
		||||
			Username:   cookie.Username,
 | 
			
		||||
			Name:       cookie.Name,
 | 
			
		||||
			Email:      cookie.Email,
 | 
			
		||||
			IsLoggedIn: true,
 | 
			
		||||
			Provider:   "username",
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Debug().Msg("Provider is not username")
 | 
			
		||||
 | 
			
		||||
	// The provider is not username so we need to check if it is an oauth provider
 | 
			
		||||
	provider := hooks.Providers.GetProvider(cookie.Provider)
 | 
			
		||||
 | 
			
		||||
	// If we have a provider with this name
 | 
			
		||||
	if provider != nil {
 | 
			
		||||
		log.Debug().Msg("Provider exists")
 | 
			
		||||
 | 
			
		||||
		// If the email is not whitelisted we delete the cookie and return an empty context
 | 
			
		||||
		if !hooks.Auth.EmailWhitelisted(cookie.Email) {
 | 
			
		||||
			log.Error().Str("email", cookie.Email).Msg("Email is not whitelisted")
 | 
			
		||||
			hooks.Auth.DeleteSessionCookie(c)
 | 
			
		||||
			return types.UserContext{}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Debug().Msg("Email is whitelisted")
 | 
			
		||||
 | 
			
		||||
		return types.UserContext{
 | 
			
		||||
			Username:    cookie.Username,
 | 
			
		||||
			Name:        cookie.Name,
 | 
			
		||||
			Email:       cookie.Email,
 | 
			
		||||
			IsLoggedIn:  true,
 | 
			
		||||
			OAuth:       true,
 | 
			
		||||
			Provider:    cookie.Provider,
 | 
			
		||||
			OAuthGroups: cookie.OAuthGroups,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return types.UserContext{}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,13 @@
 | 
			
		||||
package ldap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
	"tinyauth/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff/v5"
 | 
			
		||||
	ldapgo "github.com/go-ldap/ldap/v3"
 | 
			
		||||
	"github.com/rs/zerolog/log"
 | 
			
		||||
)
 | 
			
		||||
@@ -30,6 +32,11 @@ func NewLDAP(config types.LdapConfig) (*LDAP, error) {
 | 
			
		||||
			err := ldap.heartbeat()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error().Err(err).Msg("LDAP connection heartbeat failed")
 | 
			
		||||
				if reconnectErr := ldap.reconnect(); reconnectErr != nil {
 | 
			
		||||
					log.Error().Err(reconnectErr).Msg("Failed to reconnect to LDAP server")
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				log.Info().Msg("Successfully reconnected to LDAP server")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
@@ -38,6 +45,7 @@ func NewLDAP(config types.LdapConfig) (*LDAP, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LDAP) connect() (*ldapgo.Conn, error) {
 | 
			
		||||
	log.Debug().Msg("Connecting to LDAP server")
 | 
			
		||||
	conn, err := ldapgo.DialURL(l.Config.Address, ldapgo.DialWithTLSConfig(&tls.Config{
 | 
			
		||||
		InsecureSkipVerify: l.Config.Insecure,
 | 
			
		||||
		MinVersion:         tls.VersionTLS12,
 | 
			
		||||
@@ -46,6 +54,7 @@ func (l *LDAP) connect() (*ldapgo.Conn, error) {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Debug().Msg("Binding to LDAP server")
 | 
			
		||||
	err = conn.Bind(l.Config.BindDN, l.Config.BindPassword)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
@@ -91,7 +100,7 @@ func (l *LDAP) Bind(userDN string, password string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LDAP) heartbeat() error {
 | 
			
		||||
	log.Info().Msg("Performing LDAP connection heartbeat")
 | 
			
		||||
	log.Debug().Msg("Performing LDAP connection heartbeat")
 | 
			
		||||
 | 
			
		||||
	searchRequest := ldapgo.NewSearchRequest(
 | 
			
		||||
		"",
 | 
			
		||||
@@ -109,3 +118,30 @@ func (l *LDAP) heartbeat() error {
 | 
			
		||||
	// No error means the connection is alive
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LDAP) reconnect() error {
 | 
			
		||||
	log.Info().Msg("Reconnecting to LDAP server")
 | 
			
		||||
 | 
			
		||||
	exp := backoff.NewExponentialBackOff()
 | 
			
		||||
	exp.InitialInterval = 500 * time.Millisecond
 | 
			
		||||
	exp.RandomizationFactor = 0.1
 | 
			
		||||
	exp.Multiplier = 1.5
 | 
			
		||||
	exp.Reset()
 | 
			
		||||
 | 
			
		||||
	operation := func() (*ldapgo.Conn, error) {
 | 
			
		||||
		l.Conn.Close()
 | 
			
		||||
		_, err := l.connect()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil
 | 
			
		||||
		}
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := backoff.Retry(context.TODO(), operation, backoff.WithBackOff(exp), backoff.WithMaxTries(3))
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,6 @@ func GetGenericUser(client *http.Client, url string) (constants.Claims, error) {
 | 
			
		||||
		return user, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Debug().Msg("Parsed user from generic provider")
 | 
			
		||||
	log.Debug().Interface("user", user).Msg("Parsed user from generic provider")
 | 
			
		||||
	return user, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -327,3 +327,15 @@ func DeriveKey(secret string, info string) (string, error) {
 | 
			
		||||
	encodedKey := base64.StdEncoding.EncodeToString(key)
 | 
			
		||||
	return encodedKey, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CoalesceToString(value any) string {
 | 
			
		||||
	switch v := value.(type) {
 | 
			
		||||
	case []string:
 | 
			
		||||
		return strings.Join(v, ",")
 | 
			
		||||
	case string:
 | 
			
		||||
		return v
 | 
			
		||||
	default:
 | 
			
		||||
		log.Warn().Interface("value", value).Msg("Unsupported type, returning empty string")
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -511,3 +511,38 @@ func TestDeriveKey(t *testing.T) {
 | 
			
		||||
		t.Fatalf("Expected %v, got %v", expected, result)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCoalesceToString(t *testing.T) {
 | 
			
		||||
	t.Log("Testing coalesce to string with a string")
 | 
			
		||||
 | 
			
		||||
	value := "test"
 | 
			
		||||
	expected := "test"
 | 
			
		||||
 | 
			
		||||
	result := utils.CoalesceToString(value)
 | 
			
		||||
 | 
			
		||||
	if result != expected {
 | 
			
		||||
		t.Fatalf("Expected %v, got %v", expected, result)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Log("Testing coalesce to string with a slice of strings")
 | 
			
		||||
 | 
			
		||||
	valueSlice := []string{"test1", "test2"}
 | 
			
		||||
	expected = "test1,test2"
 | 
			
		||||
 | 
			
		||||
	result = utils.CoalesceToString(valueSlice)
 | 
			
		||||
 | 
			
		||||
	if result != expected {
 | 
			
		||||
		t.Fatalf("Expected %v, got %v", expected, result)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Log("Testing coalesce to string with an unsupported type")
 | 
			
		||||
 | 
			
		||||
	valueUnsupported := 12345
 | 
			
		||||
	expected = ""
 | 
			
		||||
 | 
			
		||||
	result = utils.CoalesceToString(valueUnsupported)
 | 
			
		||||
 | 
			
		||||
	if result != expected {
 | 
			
		||||
		t.Fatalf("Expected %v, got %v", expected, result)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user