mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-29 05:05:42 +00:00
Compare commits
24 Commits
v3.4.1-bet
...
feat/ldap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc3b2bc83e | ||
|
|
c671ef13b8 | ||
|
|
01042a3003 | ||
|
|
0e43c50ac0 | ||
|
|
3fe17cb4ec | ||
|
|
e55f29ccf9 | ||
|
|
1e413e671f | ||
|
|
4524e3322c | ||
|
|
1941de1125 | ||
|
|
49c4c7a455 | ||
|
|
c10bff55de | ||
|
|
7640e956c2 | ||
|
|
4c18a4c44d | ||
|
|
87a5c0f3f1 | ||
|
|
84d4c84ed2 | ||
|
|
9008b67f7d | ||
|
|
47980a3dd0 | ||
|
|
6cbb9e93c0 | ||
|
|
f3ec4baf3c | ||
|
|
b5799da703 | ||
|
|
80b1820333 | ||
|
|
aed29d2923 | ||
|
|
3397e2aa8e | ||
|
|
ee83c177f4 |
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -39,4 +39,9 @@ jobs:
|
|||||||
cp -r frontend/dist internal/assets/dist
|
cp -r frontend/dist internal/assets/dist
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: go test -v ./...
|
run: go test -coverprofile=coverage.txt -v ./...
|
||||||
|
|
||||||
|
- name: Upload coverage reports to Codecov
|
||||||
|
uses: codecov/codecov-action@v5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|||||||
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@@ -1,6 +1,8 @@
|
|||||||
name: Nightly Release
|
name: Nightly Release
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-release:
|
create-release:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Site builder
|
# Site builder
|
||||||
FROM oven/bun:1.2.15-alpine AS frontend-builder
|
FROM oven/bun:1.2.18-alpine AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /frontend
|
WORKDIR /frontend
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
Please always use the latest available Tinyauth version which can be found [here](https://github.com/steveiliop56/tinyauth/releases/latest). Older versions (especially major) may contain security issues which I cannot go back and fix.
|
It is recommended to use the [latest](https://github.com/steveiliop56/tinyauth/releases/latest) available version of tinyauth. This is because it includes security fixes, new features and dependency updates. Older versions, especially major ones, are not supported and won't receive security or patch updates.
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
Due to the nature of this app, it needs to be secure. If you find any security issues in the OAuth or login flow of the app please contact me at <steve@doesmycode.work> and include a concise description of the issue. Please do not use the issues section for reporting major security issues.
|
Due to the nature of this app, it needs to be secure. If you discover any security issues or vulnerabilities in the app please contact me as soon as possible at <steve@doesmycode.work>. Please do not use the issues section to report security issues as I won't be able to patch them in time and they may get exploited by malicious actors.
|
||||||
|
|||||||
86
cmd/root.go
86
cmd/root.go
@@ -8,13 +8,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
totpCmd "tinyauth/cmd/totp"
|
totpCmd "tinyauth/cmd/totp"
|
||||||
userCmd "tinyauth/cmd/user"
|
userCmd "tinyauth/cmd/user"
|
||||||
"tinyauth/internal/api"
|
|
||||||
"tinyauth/internal/auth"
|
"tinyauth/internal/auth"
|
||||||
"tinyauth/internal/constants"
|
"tinyauth/internal/constants"
|
||||||
"tinyauth/internal/docker"
|
"tinyauth/internal/docker"
|
||||||
"tinyauth/internal/handlers"
|
"tinyauth/internal/handlers"
|
||||||
"tinyauth/internal/hooks"
|
"tinyauth/internal/hooks"
|
||||||
|
"tinyauth/internal/ldap"
|
||||||
"tinyauth/internal/providers"
|
"tinyauth/internal/providers"
|
||||||
|
"tinyauth/internal/server"
|
||||||
"tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
"tinyauth/internal/utils"
|
"tinyauth/internal/utils"
|
||||||
|
|
||||||
@@ -58,10 +59,6 @@ var rootCmd = &cobra.Command{
|
|||||||
users, err := utils.GetUsers(config.Users, config.UsersFile)
|
users, err := utils.GetUsers(config.Users, config.UsersFile)
|
||||||
HandleError(err, "Failed to parse users")
|
HandleError(err, "Failed to parse users")
|
||||||
|
|
||||||
if len(users) == 0 && !utils.OAuthConfigured(config) {
|
|
||||||
HandleError(errors.New("no users or OAuth configured"), "No users or OAuth configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get domain
|
// Get domain
|
||||||
log.Debug().Msg("Getting domain")
|
log.Debug().Msg("Getting domain")
|
||||||
domain, err := utils.GetUpperDomain(config.AppURL)
|
domain, err := utils.GetUpperDomain(config.AppURL)
|
||||||
@@ -74,6 +71,15 @@ var rootCmd = &cobra.Command{
|
|||||||
csrfCookieName := fmt.Sprintf("%s-%s", constants.CsrfCookieName, cookieId)
|
csrfCookieName := fmt.Sprintf("%s-%s", constants.CsrfCookieName, cookieId)
|
||||||
redirectCookieName := fmt.Sprintf("%s-%s", constants.RedirectCookieName, cookieId)
|
redirectCookieName := fmt.Sprintf("%s-%s", constants.RedirectCookieName, cookieId)
|
||||||
|
|
||||||
|
// Generate HMAC and encryption secrets
|
||||||
|
log.Debug().Msg("Deriving HMAC and encryption secrets")
|
||||||
|
|
||||||
|
hmacSecret, err := utils.DeriveKey(config.Secret, "hmac")
|
||||||
|
HandleError(err, "Failed to derive HMAC secret")
|
||||||
|
|
||||||
|
encryptionSecret, err := utils.DeriveKey(config.Secret, "encryption")
|
||||||
|
HandleError(err, "Failed to derive encryption secret")
|
||||||
|
|
||||||
// Create OAuth config
|
// Create OAuth config
|
||||||
oauthConfig := types.OAuthConfig{
|
oauthConfig := types.OAuthConfig{
|
||||||
GithubClientId: config.GithubClientId,
|
GithubClientId: config.GithubClientId,
|
||||||
@@ -105,8 +111,8 @@ var rootCmd = &cobra.Command{
|
|||||||
RedirectCookieName: redirectCookieName,
|
RedirectCookieName: redirectCookieName,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create api config
|
// Create server config
|
||||||
apiConfig := types.APIConfig{
|
serverConfig := types.ServerConfig{
|
||||||
Port: config.Port,
|
Port: config.Port,
|
||||||
Address: config.Address,
|
Address: config.Address,
|
||||||
}
|
}
|
||||||
@@ -115,13 +121,14 @@ var rootCmd = &cobra.Command{
|
|||||||
authConfig := types.AuthConfig{
|
authConfig := types.AuthConfig{
|
||||||
Users: users,
|
Users: users,
|
||||||
OauthWhitelist: config.OAuthWhitelist,
|
OauthWhitelist: config.OAuthWhitelist,
|
||||||
Secret: config.Secret,
|
|
||||||
CookieSecure: config.CookieSecure,
|
CookieSecure: config.CookieSecure,
|
||||||
SessionExpiry: config.SessionExpiry,
|
SessionExpiry: config.SessionExpiry,
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
LoginTimeout: config.LoginTimeout,
|
LoginTimeout: config.LoginTimeout,
|
||||||
LoginMaxRetries: config.LoginMaxRetries,
|
LoginMaxRetries: config.LoginMaxRetries,
|
||||||
SessionCookieName: sessionCookieName,
|
SessionCookieName: sessionCookieName,
|
||||||
|
HMACSecret: hmacSecret,
|
||||||
|
EncryptionSecret: encryptionSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create hooks config
|
// Create hooks config
|
||||||
@@ -130,36 +137,55 @@ var rootCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create docker service
|
// Create docker service
|
||||||
docker := docker.NewDocker()
|
docker, err := docker.NewDocker()
|
||||||
|
|
||||||
// Initialize docker
|
|
||||||
err = docker.Init()
|
|
||||||
HandleError(err, "Failed to initialize docker")
|
HandleError(err, "Failed to initialize docker")
|
||||||
|
|
||||||
|
// Create LDAP service if configured
|
||||||
|
var ldapService *ldap.LDAP
|
||||||
|
|
||||||
|
if config.LdapAddress != "" {
|
||||||
|
log.Info().Msg("Using LDAP for authentication")
|
||||||
|
|
||||||
|
ldapConfig := types.LdapConfig{
|
||||||
|
Address: config.LdapAddress,
|
||||||
|
BindDN: config.LdapBindDN,
|
||||||
|
BindPassword: config.LdapBindPassword,
|
||||||
|
BaseDN: config.LdapBaseDN,
|
||||||
|
Insecure: config.LdapInsecure,
|
||||||
|
SearchFilter: config.LdapSearchFilter,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create LDAP service
|
||||||
|
ldapService, err = ldap.NewLDAP(ldapConfig)
|
||||||
|
HandleError(err, "Failed to create LDAP service")
|
||||||
|
} else {
|
||||||
|
log.Info().Msg("LDAP not configured, using local users or OAuth")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have any users configured
|
||||||
|
if len(users) == 0 && !utils.OAuthConfigured(config) && ldapService == nil {
|
||||||
|
HandleError(errors.New("err no users"), "Unable to find a source of users")
|
||||||
|
}
|
||||||
|
|
||||||
// Create auth service
|
// Create auth service
|
||||||
auth := auth.NewAuth(authConfig, docker)
|
auth := auth.NewAuth(authConfig, docker, ldapService)
|
||||||
|
|
||||||
// Create OAuth providers service
|
// Create OAuth providers service
|
||||||
providers := providers.NewProviders(oauthConfig)
|
providers := providers.NewProviders(oauthConfig)
|
||||||
|
|
||||||
// Initialize providers
|
|
||||||
providers.Init()
|
|
||||||
|
|
||||||
// Create hooks service
|
// Create hooks service
|
||||||
hooks := hooks.NewHooks(hooksConfig, auth, providers)
|
hooks := hooks.NewHooks(hooksConfig, auth, providers)
|
||||||
|
|
||||||
// Create handlers
|
// Create handlers
|
||||||
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
||||||
|
|
||||||
// Create API
|
// Create server
|
||||||
api := api.NewAPI(apiConfig, handlers)
|
srv, err := server.NewServer(serverConfig, handlers)
|
||||||
|
HandleError(err, "Failed to create server")
|
||||||
|
|
||||||
// Setup routes
|
// Start server
|
||||||
api.Init()
|
err = srv.Start()
|
||||||
api.SetupRoutes()
|
HandleError(err, "Failed to start server")
|
||||||
|
|
||||||
// Start
|
|
||||||
api.Run()
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +245,12 @@ func init() {
|
|||||||
rootCmd.Flags().String("app-title", "Tinyauth", "Title of the app.")
|
rootCmd.Flags().String("app-title", "Tinyauth", "Title of the app.")
|
||||||
rootCmd.Flags().String("forgot-password-message", "You can reset your password by changing the `USERS` environment variable.", "Message to show on the forgot password page.")
|
rootCmd.Flags().String("forgot-password-message", "You can reset your password by changing the `USERS` environment variable.", "Message to show on the forgot password page.")
|
||||||
rootCmd.Flags().String("background-image", "/background.jpg", "Background image URL for the login page.")
|
rootCmd.Flags().String("background-image", "/background.jpg", "Background image URL for the login page.")
|
||||||
|
rootCmd.Flags().String("ldap-address", "", "LDAP server address (e.g. ldap://localhost:389).")
|
||||||
|
rootCmd.Flags().String("ldap-bind-dn", "", "LDAP bind DN (e.g. uid=user,dc=example,dc=com).")
|
||||||
|
rootCmd.Flags().String("ldap-bind-password", "", "LDAP bind password.")
|
||||||
|
rootCmd.Flags().String("ldap-base-dn", "", "LDAP base DN (e.g. dc=example,dc=com).")
|
||||||
|
rootCmd.Flags().Bool("ldap-insecure", false, "Skip certificate verification for the LDAP server.")
|
||||||
|
rootCmd.Flags().String("ldap-search-filter", "(uid=%s)", "LDAP search filter for user lookup.")
|
||||||
|
|
||||||
// Bind flags to environment
|
// Bind flags to environment
|
||||||
viper.BindEnv("port", "PORT")
|
viper.BindEnv("port", "PORT")
|
||||||
@@ -254,6 +286,12 @@ func init() {
|
|||||||
viper.BindEnv("login-max-retries", "LOGIN_MAX_RETRIES")
|
viper.BindEnv("login-max-retries", "LOGIN_MAX_RETRIES")
|
||||||
viper.BindEnv("forgot-password-message", "FORGOT_PASSWORD_MESSAGE")
|
viper.BindEnv("forgot-password-message", "FORGOT_PASSWORD_MESSAGE")
|
||||||
viper.BindEnv("background-image", "BACKGROUND_IMAGE")
|
viper.BindEnv("background-image", "BACKGROUND_IMAGE")
|
||||||
|
viper.BindEnv("ldap-address", "LDAP_ADDRESS")
|
||||||
|
viper.BindEnv("ldap-bind-dn", "LDAP_BIND_DN")
|
||||||
|
viper.BindEnv("ldap-bind-password", "LDAP_BIND_PASSWORD")
|
||||||
|
viper.BindEnv("ldap-base-dn", "LDAP_BASE_DN")
|
||||||
|
viper.BindEnv("ldap-insecure", "LDAP_INSECURE")
|
||||||
|
viper.BindEnv("ldap-search-filter", "LDAP_SEARCH_FILTER")
|
||||||
|
|
||||||
// Bind flags to viper
|
// Bind flags to viper
|
||||||
viper.BindPFlags(rootCmd.Flags())
|
viper.BindPFlags(rootCmd.Flags())
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM oven/bun:1.1.45-alpine
|
FROM oven/bun:1.2.16-alpine
|
||||||
|
|
||||||
WORKDIR /frontend
|
WORKDIR /frontend
|
||||||
|
|
||||||
|
|||||||
@@ -9,44 +9,44 @@
|
|||||||
"@radix-ui/react-select": "^2.2.5",
|
"@radix-ui/react-select": "^2.2.5",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@tailwindcss/vite": "^4.1.8",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"@tanstack/react-query": "^5.80.6",
|
"@tanstack/react-query": "^5.81.5",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.10.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dompurify": "^3.2.6",
|
"dompurify": "^3.2.6",
|
||||||
"i18next": "^25.2.1",
|
"i18next": "^25.3.0",
|
||||||
"i18next-browser-languagedetector": "^8.0.5",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"i18next-resources-to-backend": "^1.2.1",
|
"i18next-resources-to-backend": "^1.2.1",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.513.0",
|
"lucide-react": "^0.525.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.57.0",
|
"react-hook-form": "^7.59.0",
|
||||||
"react-i18next": "^15.5.2",
|
"react-i18next": "^15.5.3",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-router": "^7.6.2",
|
"react-router": "^7.6.3",
|
||||||
"sonner": "^2.0.5",
|
"sonner": "^2.0.5",
|
||||||
"tailwind-merge": "^3.3.0",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss": "^4.1.8",
|
"tailwindcss": "^4.1.11",
|
||||||
"zod": "^3.25.57",
|
"zod": "^3.25.67",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.28.0",
|
"@eslint/js": "^9.30.0",
|
||||||
"@tanstack/eslint-plugin-query": "^5.78.0",
|
"@tanstack/eslint-plugin-query": "^5.81.2",
|
||||||
"@types/node": "^22.15.29",
|
"@types/node": "^24.0.8",
|
||||||
"@types/react": "^19.1.7",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@vitejs/plugin-react": "^4.5.2",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
"eslint": "^9.28.0",
|
"eslint": "^9.30.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^16.2.0",
|
"globals": "^16.2.0",
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.6.2",
|
||||||
"tw-animate-css": "^1.3.4",
|
"tw-animate-css": "^1.3.4",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"typescript-eslint": "^8.34.0",
|
"typescript-eslint": "^8.35.1",
|
||||||
"vite": "^6.3.1",
|
"vite": "^6.3.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
|
|
||||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
||||||
|
|
||||||
"@babel/runtime": ["@babel/runtime@7.27.1", "", {}, "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog=="],
|
"@babel/runtime": ["@babel/runtime@7.27.6", "", {}, "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q=="],
|
||||||
|
|
||||||
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||||
|
|
||||||
@@ -146,15 +146,15 @@
|
|||||||
|
|
||||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
|
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
|
||||||
|
|
||||||
"@eslint/config-array": ["@eslint/config-array@0.20.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ=="],
|
"@eslint/config-array": ["@eslint/config-array@0.21.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ=="],
|
||||||
|
|
||||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.2.2", "", {}, "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg=="],
|
"@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.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="],
|
||||||
|
|
||||||
"@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/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.28.0", "", {}, "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg=="],
|
"@eslint/js": ["@eslint/js@9.30.0", "", {}, "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww=="],
|
||||||
|
|
||||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
|
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
|
|
||||||
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
|
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
|
||||||
|
|
||||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.11", "", {}, "sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag=="],
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.19", "", {}, "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA=="],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.2", "", { "os": "android", "cpu": "arm" }, "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg=="],
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.2", "", { "os": "android", "cpu": "arm" }, "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg=="],
|
||||||
|
|
||||||
@@ -296,41 +296,41 @@
|
|||||||
|
|
||||||
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
|
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
|
||||||
|
|
||||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.8", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.8" } }, "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q=="],
|
"@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.8", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.8", "@tailwindcss/oxide-darwin-arm64": "4.1.8", "@tailwindcss/oxide-darwin-x64": "4.1.8", "@tailwindcss/oxide-freebsd-x64": "4.1.8", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8", "@tailwindcss/oxide-linux-arm64-musl": "4.1.8", "@tailwindcss/oxide-linux-x64-gnu": "4.1.8", "@tailwindcss/oxide-linux-x64-musl": "4.1.8", "@tailwindcss/oxide-wasm32-wasi": "4.1.8", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8", "@tailwindcss/oxide-win32-x64-msvc": "4.1.8" } }, "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A=="],
|
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.11", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.11", "@tailwindcss/oxide-darwin-arm64": "4.1.11", "@tailwindcss/oxide-darwin-x64": "4.1.11", "@tailwindcss/oxide-freebsd-x64": "4.1.11", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", "@tailwindcss/oxide-linux-x64-musl": "4.1.11", "@tailwindcss/oxide-wasm32-wasi": "4.1.11", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.8", "", { "os": "android", "cpu": "arm64" }, "sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg=="],
|
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.11", "", { "os": "android", "cpu": "arm64" }, "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A=="],
|
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw=="],
|
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg=="],
|
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8", "", { "os": "linux", "cpu": "arm" }, "sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ=="],
|
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11", "", { "os": "linux", "cpu": "arm" }, "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q=="],
|
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ=="],
|
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.8", "", { "os": "linux", "cpu": "x64" }, "sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g=="],
|
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.8", "", { "os": "linux", "cpu": "x64" }, "sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg=="],
|
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.8", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.10", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg=="],
|
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.11", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.11", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA=="],
|
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.8", "", { "os": "win32", "cpu": "x64" }, "sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ=="],
|
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.11", "", { "os": "win32", "cpu": "x64" }, "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg=="],
|
||||||
|
|
||||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.8", "", { "dependencies": { "@tailwindcss/node": "4.1.8", "@tailwindcss/oxide": "4.1.8", "tailwindcss": "4.1.8" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-CQ+I8yxNV5/6uGaJjiuymgw0kEQiNKRinYbZXPdx1fk5WgiyReG0VaUx/Xq6aVNSUNJFzxm6o8FNKS5aMaim5A=="],
|
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="],
|
||||||
|
|
||||||
"@tanstack/eslint-plugin-query": ["@tanstack/eslint-plugin-query@5.78.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.18.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-hYkhWr3UP0CkAsn/phBVR98UQawbw8CmTSgWtdgEBUjI60/GBaEIkpgi/Bp/2I8eIDK4+vdY7ac6jZx+GR+hEQ=="],
|
"@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.80.6", "", {}, "sha512-nl7YxT/TAU+VTf+e2zTkObGTyY8YZBMnbgeA1ee66lIVqzKlYursAII6z5t0e6rXgwUMJSV4dshBTNacNpZHbQ=="],
|
"@tanstack/query-core": ["@tanstack/query-core@5.81.5", "", {}, "sha512-ZJOgCy/z2qpZXWaj/oxvodDx07XcQa9BF92c0oINjHkoqUPsmm3uG08HpTaviviZ/N9eP1f9CM7mKSEkIo7O1Q=="],
|
||||||
|
|
||||||
"@tanstack/react-query": ["@tanstack/react-query@5.80.6", "", { "dependencies": { "@tanstack/query-core": "5.80.6" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-izX+5CnkpON3NQGcEm3/d7LfFQNo9ZpFtX2QsINgCYK9LT2VCIdi8D3bMaMSNhrAJCznRoAkFic76uvLroALBw=="],
|
"@tanstack/react-query": ["@tanstack/react-query@5.81.5", "", { "dependencies": { "@tanstack/query-core": "5.81.5" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-lOf2KqRRiYWpQT86eeeftAGnjuTR35myTP8MXyvHa81VlomoAWNEd8x5vkcAfQefu0qtYCvyqLropFZqgI2EQw=="],
|
||||||
|
|
||||||
"@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=="],
|
"@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,9 +354,9 @@
|
|||||||
|
|
||||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@22.15.29", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ=="],
|
"@types/node": ["@types/node@24.0.8", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-WytNrFSgWO/esSH9NbpWUfTMGQwCGIKfCmNlmFDNiI5gGhgMmEA+V1AEvKLeBNvvtBnailJtkrEa2OIISwrVAA=="],
|
||||||
|
|
||||||
"@types/react": ["@types/react@19.1.7", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-BnsPLV43ddr05N71gaGzyZ5hzkCmGwhMvYc8zmvI8Ci1bRkkDSzDDVfAXfN2tk748OwI7ediiPX6PfT9p0QGVg=="],
|
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
||||||
|
|
||||||
"@types/react-dom": ["@types/react-dom@19.1.6", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw=="],
|
"@types/react-dom": ["@types/react-dom@19.1.6", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw=="],
|
||||||
|
|
||||||
@@ -364,31 +364,31 @@
|
|||||||
|
|
||||||
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.34.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/type-utils": "8.34.0", "@typescript-eslint/utils": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.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.34.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w=="],
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.35.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.35.1", "@typescript-eslint/type-utils": "8.35.1", "@typescript-eslint/utils": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.35.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg=="],
|
||||||
|
|
||||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.34.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/typescript-estree": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA=="],
|
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.35.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.35.1", "@typescript-eslint/types": "8.35.1", "@typescript-eslint/typescript-estree": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w=="],
|
||||||
|
|
||||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.34.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.34.0", "@typescript-eslint/types": "^8.34.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw=="],
|
"@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/scope-manager": ["@typescript-eslint/scope-manager@8.32.0", "", { "dependencies": { "@typescript-eslint/types": "8.32.0", "@typescript-eslint/visitor-keys": "8.32.0" } }, "sha512-jc/4IxGNedXkmG4mx4nJTILb6TMjL66D41vyeaPWvDUmeYQzF3lKtN15WsAeTr65ce4mPxwopPSo1yUUAWw0hQ=="],
|
"@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.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA=="],
|
"@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/type-utils": ["@typescript-eslint/type-utils@8.34.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.34.0", "@typescript-eslint/utils": "8.34.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-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg=="],
|
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.35.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.35.1", "@typescript-eslint/utils": "8.35.1", "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-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.32.0", "", {}, "sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA=="],
|
"@typescript-eslint/types": ["@typescript-eslint/types@8.34.1", "", {}, "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.32.0", "", { "dependencies": { "@typescript-eslint/types": "8.32.0", "@typescript-eslint/visitor-keys": "8.32.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-pU9VD7anSCOIoBFnhTGfOzlVFQIA1XXiQpH/CezqOBaDppRwTglJzCC6fUQGpfwey4T183NKhF1/mfatYmjRqQ=="],
|
"@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/utils": ["@typescript-eslint/utils@8.32.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.32.0", "@typescript-eslint/types": "8.32.0", "@typescript-eslint/typescript-estree": "8.32.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-8S9hXau6nQ/sYVtC3D6ISIDoJzS1NsCK+gluVhLN2YkBPX+/1wkwyUiDKnxRh15579WoOIyVWnoyIf3yGI9REw=="],
|
"@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.34.0", "", { "dependencies": { "@typescript-eslint/types": "8.34.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA=="],
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.35.1", "", { "dependencies": { "@typescript-eslint/types": "8.35.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw=="],
|
||||||
|
|
||||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||||
|
|
||||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.5.2", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.11", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "sha512-QNVT3/Lxx99nMQWJWF7K4N6apUEuT0KlZA3mx/mVaoGj3smm/8rc8ezz15J1pcbcjDK0V15rpHetVfya08r76Q=="],
|
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.6.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.19", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ=="],
|
||||||
|
|
||||||
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
|
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||||
|
|
||||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||||
|
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
|
|
||||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||||
|
|
||||||
"axios": ["axios@1.9.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg=="],
|
"axios": ["axios@1.10.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw=="],
|
||||||
|
|
||||||
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||||
|
|
||||||
@@ -494,17 +494,17 @@
|
|||||||
|
|
||||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||||
|
|
||||||
"eslint": ["eslint@9.28.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.28.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.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.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-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ=="],
|
"eslint": ["eslint@9.30.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.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.30.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-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g=="],
|
||||||
|
|
||||||
"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=="],
|
"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=="],
|
||||||
|
|
||||||
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.20", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA=="],
|
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.20", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA=="],
|
||||||
|
|
||||||
"eslint-scope": ["eslint-scope@8.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="],
|
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||||
|
|
||||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
|
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||||
|
|
||||||
"espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="],
|
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||||
|
|
||||||
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
|
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
|
||||||
|
|
||||||
@@ -582,9 +582,9 @@
|
|||||||
|
|
||||||
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
|
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
|
||||||
|
|
||||||
"i18next": ["i18next@25.2.1", "", { "dependencies": { "@babel/runtime": "^7.27.1" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw=="],
|
"i18next": ["i18next@25.3.0", "", { "dependencies": { "@babel/runtime": "^7.27.6" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-ZSQIiNGfqSG6yoLHaCvrkPp16UejHI8PCDxFYaNG/1qxtmqNmqEg4JlWKlxkrUmrin2sEjsy+Mjy1TRozBhOgw=="],
|
||||||
|
|
||||||
"i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.1.0", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-mHZxNx1Lq09xt5kCauZ/4bsXOEA2pfpwSoU11/QTJB+pD94iONFwp+ohqi///PwiFvjFOxe1akYCdHyFo1ng5Q=="],
|
"i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.0", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g=="],
|
||||||
|
|
||||||
"i18next-resources-to-backend": ["i18next-resources-to-backend@1.2.1", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw=="],
|
"i18next-resources-to-backend": ["i18next-resources-to-backend@1.2.1", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw=="],
|
||||||
|
|
||||||
@@ -666,7 +666,7 @@
|
|||||||
|
|
||||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||||
|
|
||||||
"lucide-react": ["lucide-react@0.513.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-CJZKq2g8Y8yN4Aq002GahSXbG2JpFv9kXwyiOAMvUBv7pxeOFHUWKB0mO7MiY4ZVFCV4aNjv2BJFq/z3DgKPQg=="],
|
"lucide-react": ["lucide-react@0.525.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ=="],
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
||||||
|
|
||||||
@@ -778,7 +778,7 @@
|
|||||||
|
|
||||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||||
|
|
||||||
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
|
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
||||||
|
|
||||||
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
||||||
|
|
||||||
@@ -792,9 +792,9 @@
|
|||||||
|
|
||||||
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
|
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
|
||||||
|
|
||||||
"react-hook-form": ["react-hook-form@7.57.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-RbEks3+cbvTP84l/VXGUZ+JMrKOS8ykQCRYdm5aYsxnDquL0vspsyNhGRO7pcH6hsZqWlPOjLye7rJqdtdAmlg=="],
|
"react-hook-form": ["react-hook-form@7.59.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-kmkek2/8grqarTJExFNjy+RXDIP8yM+QTl3QL6m6Q8b2bih4ltmiXxH7T9n+yXNK477xPh5yZT/6vD8sYGzJTA=="],
|
||||||
|
|
||||||
"react-i18next": ["react-i18next@15.5.2", "", { "dependencies": { "@babel/runtime": "^7.25.0", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { "i18next": ">= 23.2.3", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A=="],
|
"react-i18next": ["react-i18next@15.5.3", "", { "dependencies": { "@babel/runtime": "^7.27.6", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { "i18next": ">= 23.2.3", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-ypYmOKOnjqPEJZO4m1BI0kS8kWqkBNsKYyhVUfij0gvjy9xJNoG/VcGkxq5dRlVwzmrmY1BQMAmpbbUBLwC4Kw=="],
|
||||||
|
|
||||||
"react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="],
|
"react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="],
|
||||||
|
|
||||||
@@ -804,7 +804,7 @@
|
|||||||
|
|
||||||
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
||||||
|
|
||||||
"react-router": ["react-router@7.6.2", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-U7Nv3y+bMimgWjhlT5CRdzHPu2/KVmqPwKUCChW8en5P3znxUqwlYFlbmyj8Rgp1SF6zs5X4+77kBVknkg6a0w=="],
|
"react-router": ["react-router@7.6.3", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA=="],
|
||||||
|
|
||||||
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
||||||
|
|
||||||
@@ -846,9 +846,9 @@
|
|||||||
|
|
||||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||||
|
|
||||||
"tailwind-merge": ["tailwind-merge@3.3.0", "", {}, "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ=="],
|
"tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="],
|
||||||
|
|
||||||
"tailwindcss": ["tailwindcss@4.1.8", "", {}, "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og=="],
|
"tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="],
|
||||||
|
|
||||||
"tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="],
|
"tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="],
|
||||||
|
|
||||||
@@ -872,9 +872,9 @@
|
|||||||
|
|
||||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||||
|
|
||||||
"typescript-eslint": ["typescript-eslint@8.34.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.34.0", "@typescript-eslint/parser": "8.34.0", "@typescript-eslint/utils": "8.34.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ=="],
|
"typescript-eslint": ["typescript-eslint@8.35.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.35.1", "@typescript-eslint/parser": "8.35.1", "@typescript-eslint/utils": "8.35.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||||
|
|
||||||
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||||
|
|
||||||
@@ -912,7 +912,7 @@
|
|||||||
|
|
||||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
"zod": ["zod@3.25.57", "", {}, "sha512-6tgzLuwVST5oLUxXTmBqoinKMd3JeesgbgseXeFasKKj8Q1FCZrHnbqJOyiEvr4cVAlbug+CgIsmJ8cl/pU5FA=="],
|
"zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="],
|
||||||
|
|
||||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||||
|
|
||||||
@@ -928,6 +928,8 @@
|
|||||||
|
|
||||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
|
|
||||||
|
"@eslint/eslintrc/espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="],
|
||||||
|
|
||||||
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||||
|
|
||||||
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
|
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
|
||||||
@@ -940,7 +942,7 @@
|
|||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.10", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.9.0" }, "bundled": true }, "sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ=="],
|
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.11", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.9.0" }, "bundled": true }, "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
|
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
|
||||||
|
|
||||||
@@ -958,43 +960,45 @@
|
|||||||
|
|
||||||
"@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=="],
|
"@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.34.0", "", { "dependencies": { "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0" } }, "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw=="],
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.35.1", "", { "dependencies": { "@typescript-eslint/types": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.1" } }, "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.34.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/typescript-estree": "8.34.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ=="],
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.35.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.35.1", "@typescript-eslint/types": "8.35.1", "@typescript-eslint/typescript-estree": "8.35.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.4", "", {}, "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A=="],
|
"@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.34.0", "", { "dependencies": { "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0" } }, "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw=="],
|
"@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.35.1", "", { "dependencies": { "@typescript-eslint/types": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.1" } }, "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg=="],
|
||||||
|
|
||||||
"@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
"@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.35.1", "", {}, "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.34.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.34.0", "@typescript-eslint/tsconfig-utils": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.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-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg=="],
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.35.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.35.1", "@typescript-eslint/tsconfig-utils": "8.35.1", "@typescript-eslint/types": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.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-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g=="],
|
||||||
|
|
||||||
"@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
"@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/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.32.0", "", { "dependencies": { "@typescript-eslint/types": "8.32.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w=="],
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.35.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.35.1", "@typescript-eslint/tsconfig-utils": "8.35.1", "@typescript-eslint/types": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.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-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g=="],
|
||||||
|
|
||||||
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.34.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.34.0", "@typescript-eslint/tsconfig-utils": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.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-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg=="],
|
"@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.35.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.35.1", "@typescript-eslint/types": "8.35.1", "@typescript-eslint/typescript-estree": "8.35.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.34.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/typescript-estree": "8.34.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ=="],
|
"@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/visitor-keys": ["@typescript-eslint/visitor-keys@8.32.0", "", { "dependencies": { "@typescript-eslint/types": "8.32.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"@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/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.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
"@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.35.1", "", {}, "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ=="],
|
||||||
|
|
||||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
|
"i18next-browser-languagedetector/@babel/runtime": ["@babel/runtime@7.27.1", "", {}, "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog=="],
|
||||||
|
|
||||||
|
"i18next-resources-to-backend/@babel/runtime": ["@babel/runtime@7.27.1", "", {}, "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog=="],
|
||||||
|
|
||||||
"lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
"lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|
||||||
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
||||||
|
|
||||||
"typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.34.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/typescript-estree": "8.34.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ=="],
|
"typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.35.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.35.1", "@typescript-eslint/types": "8.35.1", "@typescript-eslint/typescript-estree": "8.35.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ=="],
|
||||||
|
|
||||||
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
@@ -1002,6 +1006,10 @@
|
|||||||
|
|
||||||
"@babel/helper-module-imports/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
"@babel/helper-module-imports/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||||
|
|
||||||
|
"@eslint/eslintrc/espree/acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
|
||||||
|
|
||||||
|
"@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
|
"@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
|
"@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
|
||||||
@@ -1032,36 +1040,48 @@
|
|||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@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.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.35.1", "", {}, "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.35.1", "", {}, "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.34.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.34.0", "@typescript-eslint/tsconfig-utils": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.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-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg=="],
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.35.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.35.1", "@typescript-eslint/tsconfig-utils": "8.35.1", "@typescript-eslint/types": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.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-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.35.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.35.1", "@typescript-eslint/types": "^8.35.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.35.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ=="],
|
||||||
|
|
||||||
"@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/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/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/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.35.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.35.1", "@typescript-eslint/types": "^8.35.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.35.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.35.1", "", {}, "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ=="],
|
||||||
|
|
||||||
"@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/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/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.34.0", "", { "dependencies": { "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0" } }, "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw=="],
|
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.35.1", "", { "dependencies": { "@typescript-eslint/types": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.1" } }, "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg=="],
|
||||||
|
|
||||||
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.35.1", "", {}, "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ=="],
|
||||||
|
|
||||||
"@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-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.34.0", "", { "dependencies": { "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0" } }, "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw=="],
|
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.35.1", "", { "dependencies": { "@typescript-eslint/types": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.1" } }, "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg=="],
|
||||||
|
|
||||||
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.35.1", "", {}, "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ=="],
|
||||||
|
|
||||||
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.34.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.34.0", "@typescript-eslint/tsconfig-utils": "8.34.0", "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.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-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg=="],
|
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.35.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.35.1", "@typescript-eslint/tsconfig-utils": "8.35.1", "@typescript-eslint/types": "8.35.1", "@typescript-eslint/visitor-keys": "8.35.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-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@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.35.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.35.1", "@typescript-eslint/types": "^8.35.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.35.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ=="],
|
||||||
|
|
||||||
"@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/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/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
||||||
@@ -1070,6 +1090,10 @@
|
|||||||
|
|
||||||
"@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/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.35.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.35.1", "@typescript-eslint/types": "^8.35.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.35.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ=="],
|
||||||
|
|
||||||
"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/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/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
||||||
|
|||||||
@@ -15,44 +15,44 @@
|
|||||||
"@radix-ui/react-select": "^2.2.5",
|
"@radix-ui/react-select": "^2.2.5",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@tailwindcss/vite": "^4.1.8",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"@tanstack/react-query": "^5.80.6",
|
"@tanstack/react-query": "^5.81.5",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.10.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dompurify": "^3.2.6",
|
"dompurify": "^3.2.6",
|
||||||
"i18next": "^25.2.1",
|
"i18next": "^25.3.0",
|
||||||
"i18next-browser-languagedetector": "^8.0.5",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"i18next-resources-to-backend": "^1.2.1",
|
"i18next-resources-to-backend": "^1.2.1",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.513.0",
|
"lucide-react": "^0.525.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.57.0",
|
"react-hook-form": "^7.59.0",
|
||||||
"react-i18next": "^15.5.2",
|
"react-i18next": "^15.5.3",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-router": "^7.6.2",
|
"react-router": "^7.6.3",
|
||||||
"sonner": "^2.0.5",
|
"sonner": "^2.0.5",
|
||||||
"tailwind-merge": "^3.3.0",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss": "^4.1.8",
|
"tailwindcss": "^4.1.11",
|
||||||
"zod": "^3.25.57"
|
"zod": "^3.25.67"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.28.0",
|
"@eslint/js": "^9.30.0",
|
||||||
"@tanstack/eslint-plugin-query": "^5.78.0",
|
"@tanstack/eslint-plugin-query": "^5.81.2",
|
||||||
"@types/node": "^22.15.29",
|
"@types/node": "^24.0.8",
|
||||||
"@types/react": "^19.1.7",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@vitejs/plugin-react": "^4.5.2",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
"eslint": "^9.28.0",
|
"eslint": "^9.30.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^16.2.0",
|
"globals": "^16.2.0",
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.6.2",
|
||||||
"tw-animate-css": "^1.3.4",
|
"tw-animate-css": "^1.3.4",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"typescript-eslint": "^8.34.0",
|
"typescript-eslint": "^8.35.1",
|
||||||
"vite": "^6.3.1"
|
"vite": "^6.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
"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>.",
|
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
|
||||||
|
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{domain}}</code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{domain}}</code>). Are you sure you want to continue?",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
"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>.",
|
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
|
||||||
|
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{domain}}</code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{domain}}</code>). Are you sure you want to continue?",
|
||||||
|
|||||||
@@ -162,9 +162,9 @@ export const LoginPage = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{configuredProviders.length == 0 && (
|
{configuredProviders.length == 0 && (
|
||||||
<h3 className="text-center text-xl text-red-600">
|
<p className="text-center text-red-600 max-w-sm">
|
||||||
{t("failedToFetchProvidersTitle")}
|
{t("failedToFetchProvidersTitle")}
|
||||||
</h3>
|
</p>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ export const UnauthorizedPage = () => {
|
|||||||
const username = searchParams.get("username");
|
const username = searchParams.get("username");
|
||||||
const resource = searchParams.get("resource");
|
const resource = searchParams.get("resource");
|
||||||
const groupErr = searchParams.get("groupErr");
|
const groupErr = searchParams.get("groupErr");
|
||||||
|
const ip = searchParams.get("ip");
|
||||||
|
|
||||||
if (!username) {
|
if (!username && !ip) {
|
||||||
return <Navigate to="/" />;
|
return <Navigate to="/" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +42,10 @@ export const UnauthorizedPage = () => {
|
|||||||
i18nKey = "unauthorizedGroupsSubtitle";
|
i18nKey = "unauthorizedGroupsSubtitle";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ip) {
|
||||||
|
i18nKey = "unauthorizedIpSubtitle";
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="min-w-xs sm:min-w-sm">
|
<Card className="min-w-xs sm:min-w-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -55,6 +60,7 @@ export const UnauthorizedPage = () => {
|
|||||||
values={{
|
values={{
|
||||||
username,
|
username,
|
||||||
resource,
|
resource,
|
||||||
|
ip,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -11,16 +11,19 @@ require (
|
|||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
|
github.com/traefik/paerser v0.2.2
|
||||||
golang.org/x/crypto v0.39.0
|
golang.org/x/crypto v0.39.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||||
github.com/containerd/errdefs v1.0.0 // indirect
|
github.com/containerd/errdefs v1.0.0 // indirect
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
|
||||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||||
github.com/moby/term v0.5.2 // indirect
|
github.com/moby/term v0.5.2 // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
@@ -50,7 +53,7 @@ require (
|
|||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
github.com/docker/docker v28.2.2+incompatible
|
github.com/docker/docker v28.3.0+incompatible
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
@@ -59,6 +62,7 @@ require (
|
|||||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||||
|
github.com/go-ldap/ldap/v3 v3.4.11
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
|||||||
32
go.sum
32
go.sum
@@ -1,9 +1,13 @@
|
|||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||||
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
||||||
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
@@ -68,8 +72,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
|
github.com/docker/docker v28.3.0+incompatible h1:ffS62aKWupCWdvcee7nBU9fhnmknOqDPaJAMtfK0ImQ=
|
||||||
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v28.3.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
@@ -90,6 +94,10 @@ github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E
|
|||||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
|
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
|
||||||
|
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
@@ -103,8 +111,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
|||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
@@ -126,8 +134,22 @@ github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzq
|
|||||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||||
|
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||||
|
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||||
|
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
||||||
|
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
|
||||||
|
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
|
||||||
|
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
|
||||||
|
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
|
||||||
|
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
|
||||||
|
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||||
|
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||||
|
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
@@ -238,6 +260,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
|||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ=
|
||||||
|
github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"tinyauth/internal/docker"
|
"tinyauth/internal/docker"
|
||||||
|
"tinyauth/internal/ldap"
|
||||||
"tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
"tinyauth/internal/utils"
|
"tinyauth/internal/utils"
|
||||||
|
|
||||||
@@ -16,52 +17,151 @@ import (
|
|||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAuth(config types.AuthConfig, docker *docker.Docker) *Auth {
|
|
||||||
return &Auth{
|
|
||||||
Config: config,
|
|
||||||
Docker: docker,
|
|
||||||
LoginAttempts: make(map[string]*types.LoginAttempt),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
Config types.AuthConfig
|
Config types.AuthConfig
|
||||||
Docker *docker.Docker
|
Docker *docker.Docker
|
||||||
LoginAttempts map[string]*types.LoginAttempt
|
LoginAttempts map[string]*types.LoginAttempt
|
||||||
LoginMutex sync.RWMutex
|
LoginMutex sync.RWMutex
|
||||||
|
Store *sessions.CookieStore
|
||||||
|
LDAP *ldap.LDAP
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) {
|
func NewAuth(config types.AuthConfig, docker *docker.Docker, ldap *ldap.LDAP) *Auth {
|
||||||
// Create cookie store
|
// Create cookie store
|
||||||
store := sessions.NewCookieStore([]byte(auth.Config.Secret))
|
store := sessions.NewCookieStore([]byte(config.HMACSecret), []byte(config.EncryptionSecret))
|
||||||
|
|
||||||
// Configure cookie store
|
// Configure cookie store
|
||||||
store.Options = &sessions.Options{
|
store.Options = &sessions.Options{
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: auth.Config.SessionExpiry,
|
MaxAge: config.SessionExpiry,
|
||||||
Secure: auth.Config.CookieSecure,
|
Secure: config.CookieSecure,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Domain: fmt.Sprintf(".%s", auth.Config.Domain),
|
Domain: fmt.Sprintf(".%s", config.Domain),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return &Auth{
|
||||||
|
Config: config,
|
||||||
|
Docker: docker,
|
||||||
|
LoginAttempts: make(map[string]*types.LoginAttempt),
|
||||||
|
Store: store,
|
||||||
|
LDAP: ldap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) {
|
||||||
// Get session
|
// Get session
|
||||||
session, err := store.Get(c.Request, auth.Config.SessionCookieName)
|
session, err := auth.Store.Get(c.Request, auth.Config.SessionCookieName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to get session")
|
log.Warn().Err(err).Msg("Invalid session, clearing cookie and retrying")
|
||||||
return nil, err
|
|
||||||
|
// Delete the session cookie if there is an error
|
||||||
|
c.SetCookie(auth.Config.SessionCookieName, "", -1, "/", fmt.Sprintf(".%s", auth.Config.Domain), auth.Config.CookieSecure, true)
|
||||||
|
|
||||||
|
// Try to get the session again
|
||||||
|
session, err = auth.Store.Get(c.Request, auth.Config.SessionCookieName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// If we still can't get the session, log the error and return nil
|
||||||
|
log.Error().Err(err).Msg("Failed to get session")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) GetUser(username string) *types.User {
|
func (auth *Auth) SearchUser(username string) types.UserSearch {
|
||||||
// Loop through users and return the user if the username matches
|
// Loop through users and return the user if the username matches
|
||||||
for _, user := range auth.Config.Users {
|
log.Debug().Str("username", username).Msg("Searching for user")
|
||||||
if user.Username == username {
|
|
||||||
return &user
|
if auth.GetLocalUser(username).Username != "" {
|
||||||
|
log.Debug().Str("username", username).Msg("Found local user")
|
||||||
|
|
||||||
|
// If user found, return a user with the username and type "local"
|
||||||
|
return types.UserSearch{
|
||||||
|
Username: username,
|
||||||
|
Type: "local",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
// If no user found, check LDAP
|
||||||
|
if auth.LDAP != nil {
|
||||||
|
log.Debug().Str("username", username).Msg("Checking LDAP for user")
|
||||||
|
|
||||||
|
userDN, err := auth.LDAP.Search(username)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Str("username", username).Msg("Failed to find user in LDAP")
|
||||||
|
return types.UserSearch{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user found in LDAP, return a user with the DN as username
|
||||||
|
return types.UserSearch{
|
||||||
|
Username: userDN,
|
||||||
|
Type: "ldap",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.UserSearch{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *Auth) VerifyUser(search types.UserSearch, password string) bool {
|
||||||
|
// Authenticate the user based on the type
|
||||||
|
switch search.Type {
|
||||||
|
case "local":
|
||||||
|
// Get local user
|
||||||
|
user := auth.GetLocalUser(search.Username)
|
||||||
|
|
||||||
|
// Check if password is correct
|
||||||
|
return auth.CheckPassword(user, password)
|
||||||
|
case "ldap":
|
||||||
|
// If LDAP is configured, bind to the LDAP server with the user DN and password
|
||||||
|
if auth.LDAP != nil {
|
||||||
|
log.Debug().Str("username", search.Username).Msg("Binding to LDAP for user authentication")
|
||||||
|
|
||||||
|
// Bind to the LDAP server
|
||||||
|
err := auth.LDAP.Bind(search.Username, password)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Str("username", search.Username).Msg("Failed to bind to LDAP")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If bind is successful, rebind with the LDAP bind user
|
||||||
|
err = auth.LDAP.Bind(auth.LDAP.Config.BindDN, auth.LDAP.Config.BindPassword)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to rebind with service account after user authentication")
|
||||||
|
// Consider closing the connection or creating a new one
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Str("username", search.Username).Msg("LDAP authentication successful")
|
||||||
|
|
||||||
|
// Return true if the bind was successful
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Warn().Str("type", search.Type).Msg("Unknown user type for authentication")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no user found or authentication failed, return false
|
||||||
|
log.Warn().Str("username", search.Username).Msg("User authentication failed")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *Auth) GetLocalUser(username string) types.User {
|
||||||
|
// Loop through users and return the user if the username matches
|
||||||
|
log.Debug().Str("username", username).Msg("Searching for local user")
|
||||||
|
|
||||||
|
for _, user := range auth.Config.Users {
|
||||||
|
if user.Username == username {
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no user found, return an empty user
|
||||||
|
log.Warn().Str("username", username).Msg("Local user not found")
|
||||||
|
return types.User{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) CheckPassword(user types.User, password string) bool {
|
func (auth *Auth) CheckPassword(user types.User, password string) bool {
|
||||||
@@ -261,14 +361,14 @@ func (auth *Auth) GetSessionCookie(c *gin.Context) (types.SessionCookie, error)
|
|||||||
|
|
||||||
func (auth *Auth) UserAuthConfigured() bool {
|
func (auth *Auth) UserAuthConfigured() bool {
|
||||||
// If there are users, return true
|
// If there are users, return true
|
||||||
return len(auth.Config.Users) > 0
|
return len(auth.Config.Users) > 0 || auth.LDAP != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.TinyauthLabels) bool {
|
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.Labels) bool {
|
||||||
// Check if oauth is allowed
|
// Check if oauth is allowed
|
||||||
if context.OAuth {
|
if context.OAuth {
|
||||||
log.Debug().Msg("Checking OAuth whitelist")
|
log.Debug().Msg("Checking OAuth whitelist")
|
||||||
return utils.CheckWhitelist(labels.OAuthWhitelist, context.Email)
|
return utils.CheckWhitelist(labels.OAuth.Whitelist, context.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check users
|
// Check users
|
||||||
@@ -277,9 +377,9 @@ func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, lab
|
|||||||
return utils.CheckWhitelist(labels.Users, context.Username)
|
return utils.CheckWhitelist(labels.Users, context.Username)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels types.TinyauthLabels) bool {
|
func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels types.Labels) bool {
|
||||||
// Check if groups are required
|
// Check if groups are required
|
||||||
if labels.OAuthGroups == "" {
|
if labels.OAuth.Groups == "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +394,7 @@ func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels t
|
|||||||
|
|
||||||
// For every group check if it is in the required groups
|
// For every group check if it is in the required groups
|
||||||
for _, group := range oauthGroups {
|
for _, group := range oauthGroups {
|
||||||
if utils.CheckWhitelist(labels.OAuthGroups, group) {
|
if utils.CheckWhitelist(labels.OAuth.Groups, group) {
|
||||||
log.Debug().Str("group", group).Msg("Group is in required groups")
|
log.Debug().Str("group", group).Msg("Group is in required groups")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -307,7 +407,7 @@ func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels t
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) AuthEnabled(c *gin.Context, labels types.TinyauthLabels) (bool, error) {
|
func (auth *Auth) AuthEnabled(c *gin.Context, labels types.Labels) (bool, error) {
|
||||||
// Get headers
|
// Get headers
|
||||||
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
||||||
|
|
||||||
@@ -351,3 +451,44 @@ func (auth *Auth) GetBasicAuth(c *gin.Context) *types.User {
|
|||||||
Password: password,
|
Password: password,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (auth *Auth) CheckIP(c *gin.Context, labels types.Labels) bool {
|
||||||
|
// Get the IP address from the request
|
||||||
|
ip := c.ClientIP()
|
||||||
|
|
||||||
|
// Check if the IP is in block list
|
||||||
|
for _, blocked := range labels.IP.Block {
|
||||||
|
res, err := utils.FilterIP(blocked, ip)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Str("item", blocked).Msg("Invalid IP/CIDR in block list")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if res {
|
||||||
|
log.Warn().Str("ip", ip).Str("item", blocked).Msg("IP is in blocked list, denying access")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For every IP in the allow list, check if the IP matches
|
||||||
|
for _, allowed := range labels.IP.Allow {
|
||||||
|
res, err := utils.FilterIP(allowed, ip)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Str("item", allowed).Msg("Invalid IP/CIDR in allow list")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if res {
|
||||||
|
log.Debug().Str("ip", ip).Str("item", allowed).Msg("IP is in allowed list, allowing access")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not in allowed range and allowed range is not empty, deny access
|
||||||
|
if len(labels.IP.Allow) > 0 {
|
||||||
|
log.Warn().Str("ip", ip).Msg("IP not in allow list, denying access")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Str("ip", ip).Msg("IP not in allow or block list, allowing by default")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func TestLoginRateLimiting(t *testing.T) {
|
|||||||
// Initialize a new auth service with 3 max retries and 5 seconds timeout
|
// Initialize a new auth service with 3 max retries and 5 seconds timeout
|
||||||
config.LoginMaxRetries = 3
|
config.LoginMaxRetries = 3
|
||||||
config.LoginTimeout = 5
|
config.LoginTimeout = 5
|
||||||
authService := auth.NewAuth(config, &docker.Docker{})
|
authService := auth.NewAuth(config, &docker.Docker{}, nil)
|
||||||
|
|
||||||
// Test identifier
|
// Test identifier
|
||||||
identifier := "test_user"
|
identifier := "test_user"
|
||||||
@@ -62,7 +62,7 @@ func TestLoginRateLimiting(t *testing.T) {
|
|||||||
// Reinitialize auth service with a shorter timeout for testing
|
// Reinitialize auth service with a shorter timeout for testing
|
||||||
config.LoginTimeout = 1
|
config.LoginTimeout = 1
|
||||||
config.LoginMaxRetries = 3
|
config.LoginMaxRetries = 3
|
||||||
authService = auth.NewAuth(config, &docker.Docker{})
|
authService = auth.NewAuth(config, &docker.Docker{}, nil)
|
||||||
|
|
||||||
// Add enough failed attempts to lock the account
|
// Add enough failed attempts to lock the account
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
@@ -87,7 +87,7 @@ func TestLoginRateLimiting(t *testing.T) {
|
|||||||
t.Log("Testing disabled rate limiting")
|
t.Log("Testing disabled rate limiting")
|
||||||
config.LoginMaxRetries = 0
|
config.LoginMaxRetries = 0
|
||||||
config.LoginTimeout = 0
|
config.LoginTimeout = 0
|
||||||
authService = auth.NewAuth(config, &docker.Docker{})
|
authService = auth.NewAuth(config, &docker.Docker{}, nil)
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
authService.RecordLoginAttempt(identifier, false)
|
authService.RecordLoginAttempt(identifier, false)
|
||||||
@@ -103,7 +103,7 @@ func TestConcurrentLoginAttempts(t *testing.T) {
|
|||||||
// Initialize a new auth service with 2 max retries and 5 seconds timeout
|
// Initialize a new auth service with 2 max retries and 5 seconds timeout
|
||||||
config.LoginMaxRetries = 2
|
config.LoginMaxRetries = 2
|
||||||
config.LoginTimeout = 5
|
config.LoginTimeout = 5
|
||||||
authService := auth.NewAuth(config, &docker.Docker{})
|
authService := auth.NewAuth(config, &docker.Docker{}, nil)
|
||||||
|
|
||||||
// Test multiple identifiers
|
// Test multiple identifiers
|
||||||
identifiers := []string{"user1", "user2", "user3"}
|
identifiers := []string{"user1", "user2", "user3"}
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
package constants
|
package constants
|
||||||
|
|
||||||
// TinyauthLabels is a list of labels that can be used in a tinyauth protected container
|
|
||||||
var TinyauthLabels = []string{
|
|
||||||
"tinyauth.oauth.whitelist",
|
|
||||||
"tinyauth.users",
|
|
||||||
"tinyauth.allowed",
|
|
||||||
"tinyauth.headers",
|
|
||||||
"tinyauth.oauth.groups",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Claims are the OIDC supported claims (including preferd username for some reason)
|
// Claims are the OIDC supported claims (including preferd username for some reason)
|
||||||
type Claims struct {
|
type Claims struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|||||||
@@ -11,35 +11,30 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDocker() *Docker {
|
|
||||||
return &Docker{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Docker struct {
|
type Docker struct {
|
||||||
Client *client.Client
|
Client *client.Client
|
||||||
Context context.Context
|
Context context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (docker *Docker) Init() error {
|
func NewDocker() (*Docker, error) {
|
||||||
// Create a new docker client
|
// Create a new docker client
|
||||||
client, err := client.NewClientWithOpts(client.FromEnv)
|
client, err := client.NewClientWithOpts(client.FromEnv)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the context
|
// Create the context
|
||||||
docker.Context = context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Negotiate API version
|
// Negotiate API version
|
||||||
client.NegotiateAPIVersion(docker.Context)
|
client.NegotiateAPIVersion(ctx)
|
||||||
|
|
||||||
// Set client
|
return &Docker{
|
||||||
docker.Client = client
|
Client: client,
|
||||||
|
Context: ctx,
|
||||||
// Done
|
}, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (docker *Docker) GetContainers() ([]container.Summary, error) {
|
func (docker *Docker) GetContainers() ([]container.Summary, error) {
|
||||||
@@ -74,56 +69,58 @@ func (docker *Docker) DockerConnected() bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) {
|
func (docker *Docker) GetLabels(id string, domain string) (types.Labels, error) {
|
||||||
// Check if we have access to the Docker API
|
// Check if we have access to the Docker API
|
||||||
isConnected := docker.DockerConnected()
|
isConnected := docker.DockerConnected()
|
||||||
|
|
||||||
// If we don't have access, return an empty struct
|
// If we don't have access, return an empty struct
|
||||||
if !isConnected {
|
if !isConnected {
|
||||||
log.Debug().Msg("Docker not connected, returning empty labels")
|
log.Debug().Msg("Docker not connected, returning empty labels")
|
||||||
return types.TinyauthLabels{}, nil
|
return types.Labels{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the containers
|
// Get the containers
|
||||||
|
log.Debug().Msg("Getting containers")
|
||||||
|
|
||||||
containers, err := docker.GetContainers()
|
containers, err := docker.GetContainers()
|
||||||
|
|
||||||
// If there is an error, return false
|
// If there is an error, return false
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.TinyauthLabels{}, err
|
log.Error().Err(err).Msg("Error getting containers")
|
||||||
|
return types.Labels{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Got containers")
|
|
||||||
|
|
||||||
// Loop through the containers
|
// Loop through the containers
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
// Inspect the container
|
// Inspect the container
|
||||||
inspect, err := docker.InspectContainer(container.ID)
|
inspect, err := docker.InspectContainer(container.ID)
|
||||||
|
|
||||||
// If there is an error, return false
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.TinyauthLabels{}, err
|
log.Warn().Str("id", container.ID).Err(err).Msg("Error inspecting container, skipping")
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the container name (for some reason it is /name)
|
// Get the labels
|
||||||
containerName := strings.TrimPrefix(inspect.Name, "/")
|
log.Debug().Str("id", inspect.ID).Msg("Getting labels for container")
|
||||||
|
|
||||||
// There is a container with the same name as the app ID
|
labels, err := utils.GetLabels(inspect.Config.Labels)
|
||||||
if containerName == appId {
|
|
||||||
log.Debug().Str("container", containerName).Msg("Found container")
|
|
||||||
|
|
||||||
// Get only the tinyauth labels in a struct
|
// Check if there was an error
|
||||||
labels := utils.GetTinyauthLabels(inspect.Config.Labels)
|
if err != nil {
|
||||||
|
log.Warn().Str("id", container.ID).Err(err).Msg("Error getting container labels, skipping")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Got labels")
|
// Check if the labels match the id or the domain
|
||||||
|
if strings.TrimPrefix(inspect.Name, "/") == id || labels.Domain == domain {
|
||||||
// Return labels
|
log.Debug().Str("id", inspect.ID).Msg("Found matching container")
|
||||||
return labels, nil
|
return labels, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("No matching container found, returning empty labels")
|
log.Debug().Msg("No matching container found, returning empty labels")
|
||||||
|
|
||||||
// If no matching container is found, return empty labels
|
// If no matching container is found, return empty labels
|
||||||
return types.TinyauthLabels{}, nil
|
return types.Labels{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Handlers struct {
|
||||||
|
Config types.HandlersConfig
|
||||||
|
Auth *auth.Auth
|
||||||
|
Hooks *hooks.Hooks
|
||||||
|
Providers *providers.Providers
|
||||||
|
Docker *docker.Docker
|
||||||
|
}
|
||||||
|
|
||||||
func NewHandlers(config types.HandlersConfig, auth *auth.Auth, hooks *hooks.Hooks, providers *providers.Providers, docker *docker.Docker) *Handlers {
|
func NewHandlers(config types.HandlersConfig, auth *auth.Auth, hooks *hooks.Hooks, providers *providers.Providers, docker *docker.Docker) *Handlers {
|
||||||
return &Handlers{
|
return &Handlers{
|
||||||
Config: config,
|
Config: config,
|
||||||
@@ -28,14 +36,6 @@ func NewHandlers(config types.HandlersConfig, auth *auth.Auth, hooks *hooks.Hook
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Handlers struct {
|
|
||||||
Config types.HandlersConfig
|
|
||||||
Auth *auth.Auth
|
|
||||||
Hooks *hooks.Hooks
|
|
||||||
Providers *providers.Providers
|
|
||||||
Docker *docker.Docker
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handlers) AuthHandler(c *gin.Context) {
|
func (h *Handlers) AuthHandler(c *gin.Context) {
|
||||||
// Create struct for proxy
|
// Create struct for proxy
|
||||||
var proxy types.Proxy
|
var proxy types.Proxy
|
||||||
@@ -69,11 +69,14 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
proto := c.Request.Header.Get("X-Forwarded-Proto")
|
proto := c.Request.Header.Get("X-Forwarded-Proto")
|
||||||
host := c.Request.Header.Get("X-Forwarded-Host")
|
host := c.Request.Header.Get("X-Forwarded-Host")
|
||||||
|
|
||||||
// Get the app id
|
// Remove the port from the host if it exists
|
||||||
appId := strings.Split(host, ".")[0]
|
hostPortless := strings.Split(host, ":")[0] // *lol*
|
||||||
|
|
||||||
|
// Get the id
|
||||||
|
id := strings.Split(hostPortless, ".")[0]
|
||||||
|
|
||||||
// Get the container labels
|
// Get the container labels
|
||||||
labels, err := h.Docker.GetLabels(appId)
|
labels, err := h.Docker.GetLabels(id, hostPortless)
|
||||||
|
|
||||||
log.Debug().Interface("labels", labels).Msg("Got labels")
|
log.Debug().Interface("labels", labels).Msg("Got labels")
|
||||||
|
|
||||||
@@ -93,6 +96,38 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the IP is allowed/blocked
|
||||||
|
ip := c.ClientIP()
|
||||||
|
if !h.Auth.CheckIP(c, labels) {
|
||||||
|
log.Warn().Str("ip", ip).Msg("IP not allowed")
|
||||||
|
|
||||||
|
if proxy.Proxy == "nginx" || !isBrowser {
|
||||||
|
c.JSON(403, gin.H{
|
||||||
|
"status": 403,
|
||||||
|
"message": "Forbidden",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
values := types.UnauthorizedQuery{
|
||||||
|
Resource: strings.Split(host, ".")[0],
|
||||||
|
IP: ip,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build query
|
||||||
|
queries, err := query.Values(values)
|
||||||
|
|
||||||
|
// Handle error
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to build queries")
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Check if auth is enabled
|
// Check if auth is enabled
|
||||||
authEnabled, err := h.Auth.AuthEnabled(c, labels)
|
authEnabled, err := h.Auth.AuthEnabled(c, labels)
|
||||||
|
|
||||||
@@ -114,9 +149,14 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
|
|
||||||
// If auth is not enabled, return 200
|
// If auth is not enabled, return 200
|
||||||
if !authEnabled {
|
if !authEnabled {
|
||||||
for key, value := range labels.Headers {
|
headersParsed := utils.ParseHeaders(labels.Headers)
|
||||||
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
for key, value := range headersParsed {
|
||||||
c.Header(key, utils.SanitizeHeader(value))
|
log.Debug().Str("key", key).Msg("Setting header")
|
||||||
|
c.Header(key, value)
|
||||||
|
}
|
||||||
|
if labels.Basic.User != "" && labels.Basic.Password != "" {
|
||||||
|
log.Debug().Str("username", labels.Basic.User).Msg("Setting basic auth headers")
|
||||||
|
c.Header("Authorization", fmt.Sprintf("Basic %s", utils.GetBasicAuth(labels.Basic.User, labels.Basic.Password)))
|
||||||
}
|
}
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
@@ -236,9 +276,16 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
|
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
|
||||||
|
|
||||||
// Set the rest of the headers
|
// Set the rest of the headers
|
||||||
for key, value := range labels.Headers {
|
parsedHeaders := utils.ParseHeaders(labels.Headers)
|
||||||
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
for key, value := range parsedHeaders {
|
||||||
c.Header(key, utils.SanitizeHeader(value))
|
log.Debug().Str("key", key).Msg("Setting header")
|
||||||
|
c.Header(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set basic auth headers if configured
|
||||||
|
if labels.Basic.User != "" && labels.Basic.Password != "" {
|
||||||
|
log.Debug().Str("username", labels.Basic.User).Msg("Setting basic auth headers")
|
||||||
|
c.Header("Authorization", fmt.Sprintf("Basic %s", utils.GetBasicAuth(labels.Basic.User, labels.Basic.Password)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// The user is allowed to access the app
|
// The user is allowed to access the app
|
||||||
@@ -315,11 +362,13 @@ func (h *Handlers) LoginHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user based on username
|
// Search for a user based on username
|
||||||
user := h.Auth.GetUser(login.Username)
|
userSearch := h.Auth.SearchUser(login.Username)
|
||||||
|
|
||||||
|
log.Debug().Interface("userSearch", userSearch).Msg("Searching for user")
|
||||||
|
|
||||||
// User does not exist
|
// User does not exist
|
||||||
if user == nil {
|
if userSearch.Type == "" {
|
||||||
log.Debug().Str("username", login.Username).Msg("User not found")
|
log.Debug().Str("username", login.Username).Msg("User not found")
|
||||||
// Record failed login attempt
|
// Record failed login attempt
|
||||||
h.Auth.RecordLoginAttempt(rateIdentifier, false)
|
h.Auth.RecordLoginAttempt(rateIdentifier, false)
|
||||||
@@ -333,7 +382,7 @@ func (h *Handlers) LoginHandler(c *gin.Context) {
|
|||||||
log.Debug().Msg("Got user")
|
log.Debug().Msg("Got user")
|
||||||
|
|
||||||
// Check if password is correct
|
// Check if password is correct
|
||||||
if !h.Auth.CheckPassword(*user, login.Password) {
|
if !h.Auth.VerifyUser(userSearch, login.Password) {
|
||||||
log.Debug().Str("username", login.Username).Msg("Password incorrect")
|
log.Debug().Str("username", login.Username).Msg("Password incorrect")
|
||||||
// Record failed login attempt
|
// Record failed login attempt
|
||||||
h.Auth.RecordLoginAttempt(rateIdentifier, false)
|
h.Auth.RecordLoginAttempt(rateIdentifier, false)
|
||||||
@@ -349,28 +398,34 @@ func (h *Handlers) LoginHandler(c *gin.Context) {
|
|||||||
// Record successful login attempt (will reset failed attempt counter)
|
// Record successful login attempt (will reset failed attempt counter)
|
||||||
h.Auth.RecordLoginAttempt(rateIdentifier, true)
|
h.Auth.RecordLoginAttempt(rateIdentifier, true)
|
||||||
|
|
||||||
// Check if user has totp enabled
|
// Check if user is using TOTP
|
||||||
if user.TotpSecret != "" {
|
if userSearch.Type == "local" {
|
||||||
log.Debug().Msg("Totp enabled")
|
// Get local user
|
||||||
|
localUser := h.Auth.GetLocalUser(login.Username)
|
||||||
|
|
||||||
// Set totp pending cookie
|
// Check if TOTP is enabled
|
||||||
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
|
if localUser.TotpSecret != "" {
|
||||||
Username: login.Username,
|
log.Debug().Msg("Totp enabled")
|
||||||
Name: utils.Capitalize(login.Username),
|
|
||||||
Email: fmt.Sprintf("%s@%s", strings.ToLower(login.Username), h.Config.Domain),
|
|
||||||
Provider: "username",
|
|
||||||
TotpPending: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Return totp required
|
// Set totp pending cookie
|
||||||
c.JSON(200, gin.H{
|
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
|
||||||
"status": 200,
|
Username: login.Username,
|
||||||
"message": "Waiting for totp",
|
Name: utils.Capitalize(login.Username),
|
||||||
"totpPending": true,
|
Email: fmt.Sprintf("%s@%s", strings.ToLower(login.Username), h.Config.Domain),
|
||||||
})
|
Provider: "username",
|
||||||
|
TotpPending: true,
|
||||||
|
})
|
||||||
|
|
||||||
// Stop further processing
|
// Return totp required
|
||||||
return
|
c.JSON(200, gin.H{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Waiting for totp",
|
||||||
|
"totpPending": true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Stop further processing
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create session cookie with username as provider
|
// Create session cookie with username as provider
|
||||||
@@ -422,17 +477,7 @@ func (h *Handlers) TotpHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get user
|
// Get user
|
||||||
user := h.Auth.GetUser(userContext.Username)
|
user := h.Auth.GetLocalUser(userContext.Username)
|
||||||
|
|
||||||
// Check if user exists
|
|
||||||
if user == nil {
|
|
||||||
log.Debug().Msg("User not found")
|
|
||||||
c.JSON(401, gin.H{
|
|
||||||
"status": 401,
|
|
||||||
"message": "Unauthorized",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if totp is correct
|
// Check if totp is correct
|
||||||
ok := totp.Validate(totpReq.Code, user.TotpSecret)
|
ok := totp.Validate(totpReq.Code, user.TotpSecret)
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Hooks struct {
|
||||||
|
Config types.HooksConfig
|
||||||
|
Auth *auth.Auth
|
||||||
|
Providers *providers.Providers
|
||||||
|
}
|
||||||
|
|
||||||
func NewHooks(config types.HooksConfig, auth *auth.Auth, providers *providers.Providers) *Hooks {
|
func NewHooks(config types.HooksConfig, auth *auth.Auth, providers *providers.Providers) *Hooks {
|
||||||
return &Hooks{
|
return &Hooks{
|
||||||
Config: config,
|
Config: config,
|
||||||
@@ -20,12 +26,6 @@ func NewHooks(config types.HooksConfig, auth *auth.Auth, providers *providers.Pr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Hooks struct {
|
|
||||||
Config types.HooksConfig
|
|
||||||
Auth *auth.Auth
|
|
||||||
Providers *providers.Providers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
|
func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
|
||||||
// Get session cookie and basic auth
|
// Get session cookie and basic auth
|
||||||
cookie, err := hooks.Auth.GetSessionCookie(c)
|
cookie, err := hooks.Auth.GetSessionCookie(c)
|
||||||
@@ -35,30 +35,49 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
|
|||||||
if basic != nil {
|
if basic != nil {
|
||||||
log.Debug().Msg("Got basic auth")
|
log.Debug().Msg("Got basic auth")
|
||||||
|
|
||||||
// Get user
|
// Search for a user based on username
|
||||||
user := hooks.Auth.GetUser(basic.Username)
|
userSearch := hooks.Auth.SearchUser(basic.Username)
|
||||||
|
|
||||||
// Check we have a user
|
if userSearch.Type == "" {
|
||||||
if user == nil {
|
|
||||||
log.Error().Str("username", basic.Username).Msg("User does not exist")
|
log.Error().Str("username", basic.Username).Msg("User does not exist")
|
||||||
|
|
||||||
// Return empty context
|
// Return empty context
|
||||||
return types.UserContext{}
|
return types.UserContext{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user has a correct password
|
// Verify the user
|
||||||
if hooks.Auth.CheckPassword(*user, basic.Password) {
|
if !hooks.Auth.VerifyUser(userSearch, basic.Password) {
|
||||||
// Return user context since we are logged in with basic auth
|
log.Error().Str("username", basic.Username).Msg("Password incorrect")
|
||||||
|
|
||||||
|
// Return empty context
|
||||||
|
return types.UserContext{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the user type
|
||||||
|
if userSearch.Type == "ldap" {
|
||||||
|
log.Debug().Msg("User is LDAP")
|
||||||
|
|
||||||
return types.UserContext{
|
return types.UserContext{
|
||||||
Username: basic.Username,
|
Username: basic.Username,
|
||||||
Name: utils.Capitalize(basic.Username),
|
Name: utils.Capitalize(basic.Username),
|
||||||
Email: fmt.Sprintf("%s@%s", strings.ToLower(basic.Username), hooks.Config.Domain),
|
Email: fmt.Sprintf("%s@%s", strings.ToLower(basic.Username), hooks.Config.Domain),
|
||||||
IsLoggedIn: true,
|
IsLoggedIn: true,
|
||||||
Provider: "basic",
|
Provider: "basic",
|
||||||
TotpEnabled: user.TotpSecret != "",
|
TotpEnabled: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user := hooks.Auth.GetLocalUser(basic.Username)
|
||||||
|
|
||||||
|
return types.UserContext{
|
||||||
|
Username: basic.Username,
|
||||||
|
Name: utils.Capitalize(basic.Username),
|
||||||
|
Email: fmt.Sprintf("%s@%s", strings.ToLower(basic.Username), hooks.Config.Domain),
|
||||||
|
IsLoggedIn: true,
|
||||||
|
Provider: "basic",
|
||||||
|
TotpEnabled: user.TotpSecret != "",
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check cookie error after basic auth
|
// Check cookie error after basic auth
|
||||||
@@ -85,18 +104,25 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
|
|||||||
if cookie.Provider == "username" {
|
if cookie.Provider == "username" {
|
||||||
log.Debug().Msg("Provider is username")
|
log.Debug().Msg("Provider is username")
|
||||||
|
|
||||||
// Check if user exists
|
// Search for the user with the username
|
||||||
if hooks.Auth.GetUser(cookie.Username) != nil {
|
userSearch := hooks.Auth.SearchUser(cookie.Username)
|
||||||
log.Debug().Msg("User exists")
|
|
||||||
|
|
||||||
// It exists so we are logged in
|
if userSearch.Type == "" {
|
||||||
return types.UserContext{
|
log.Error().Str("username", cookie.Username).Msg("User does not exist")
|
||||||
Username: cookie.Username,
|
|
||||||
Name: cookie.Name,
|
// Return empty context
|
||||||
Email: cookie.Email,
|
return types.UserContext{}
|
||||||
IsLoggedIn: true,
|
}
|
||||||
Provider: "username",
|
|
||||||
}
|
log.Debug().Str("type", userSearch.Type).Msg("User exists")
|
||||||
|
|
||||||
|
// It exists so we are logged in
|
||||||
|
return types.UserContext{
|
||||||
|
Username: cookie.Username,
|
||||||
|
Name: cookie.Name,
|
||||||
|
Email: cookie.Email,
|
||||||
|
IsLoggedIn: true,
|
||||||
|
Provider: "username",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
77
internal/ldap/ldap.go
Normal file
77
internal/ldap/ldap.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package ldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"tinyauth/internal/types"
|
||||||
|
|
||||||
|
ldapgo "github.com/go-ldap/ldap/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LDAP struct {
|
||||||
|
Config types.LdapConfig
|
||||||
|
Conn *ldapgo.Conn
|
||||||
|
BaseDN string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLDAP(config types.LdapConfig) (*LDAP, error) {
|
||||||
|
// Connect to the LDAP server
|
||||||
|
conn, err := ldapgo.DialURL(config.Address, ldapgo.DialWithTLSConfig(&tls.Config{
|
||||||
|
InsecureSkipVerify: config.Insecure,
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind to the LDAP server with the provided credentials
|
||||||
|
err = conn.Bind(config.BindDN, config.BindPassword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LDAP{
|
||||||
|
Config: config,
|
||||||
|
Conn: conn,
|
||||||
|
BaseDN: config.BaseDN,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LDAP) Search(username string) (string, error) {
|
||||||
|
// Escape the username to prevent LDAP injection
|
||||||
|
escapedUsername := ldapgo.EscapeFilter(username)
|
||||||
|
filter := fmt.Sprintf(l.Config.SearchFilter, escapedUsername)
|
||||||
|
|
||||||
|
// Create a search request to find the user by username
|
||||||
|
searchRequest := ldapgo.NewSearchRequest(
|
||||||
|
l.BaseDN,
|
||||||
|
ldapgo.ScopeWholeSubtree, ldapgo.NeverDerefAliases, 0, 0, false,
|
||||||
|
filter,
|
||||||
|
[]string{"dn"},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Perform the search
|
||||||
|
searchResult, err := l.Conn.Search(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(searchResult.Entries) != 1 {
|
||||||
|
return "", fmt.Errorf("err multiple or no entries found for user %s", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
// User found, return the distinguished name (DN)
|
||||||
|
userDN := searchResult.Entries[0].DN
|
||||||
|
|
||||||
|
return userDN, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LDAP) Bind(userDN string, password string) error {
|
||||||
|
// Bind to the LDAP server with the user's DN and password
|
||||||
|
err := l.Conn.Bind(userDN, password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -10,32 +10,24 @@ import (
|
|||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewOAuth(config oauth2.Config, insecureSkipVerify bool) *OAuth {
|
|
||||||
return &OAuth{
|
|
||||||
Config: config,
|
|
||||||
InsecureSkipVerify: insecureSkipVerify,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type OAuth struct {
|
type OAuth struct {
|
||||||
Config oauth2.Config
|
Config oauth2.Config
|
||||||
Context context.Context
|
Context context.Context
|
||||||
Token *oauth2.Token
|
Token *oauth2.Token
|
||||||
Verifier string
|
Verifier string
|
||||||
InsecureSkipVerify bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (oauth *OAuth) Init() {
|
func NewOAuth(config oauth2.Config, insecureSkipVerify bool) *OAuth {
|
||||||
// Create transport with TLS
|
// Create transport with TLS
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
InsecureSkipVerify: oauth.InsecureSkipVerify,
|
InsecureSkipVerify: insecureSkipVerify,
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new context
|
// Create a new context
|
||||||
oauth.Context = context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Create the HTTP client with the transport
|
// Create the HTTP client with the transport
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
@@ -43,9 +35,16 @@ func (oauth *OAuth) Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the HTTP client in the context
|
// Set the HTTP client in the context
|
||||||
oauth.Context = context.WithValue(oauth.Context, oauth2.HTTPClient, httpClient)
|
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
|
||||||
|
|
||||||
// Create the verifier
|
// Create the verifier
|
||||||
oauth.Verifier = oauth2.GenerateVerifier()
|
verifier := oauth2.GenerateVerifier()
|
||||||
|
|
||||||
|
return &OAuth{
|
||||||
|
Config: config,
|
||||||
|
Context: ctx,
|
||||||
|
Verifier: verifier,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (oauth *OAuth) GetAuthURL(state string) string {
|
func (oauth *OAuth) GetAuthURL(state string) string {
|
||||||
|
|||||||
@@ -11,12 +11,6 @@ import (
|
|||||||
"golang.org/x/oauth2/endpoints"
|
"golang.org/x/oauth2/endpoints"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewProviders(config types.OAuthConfig) *Providers {
|
|
||||||
return &Providers{
|
|
||||||
Config: config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Providers struct {
|
type Providers struct {
|
||||||
Config types.OAuthConfig
|
Config types.OAuthConfig
|
||||||
Github *oauth.OAuth
|
Github *oauth.OAuth
|
||||||
@@ -24,60 +18,57 @@ type Providers struct {
|
|||||||
Generic *oauth.OAuth
|
Generic *oauth.OAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func (providers *Providers) Init() {
|
func NewProviders(config types.OAuthConfig) *Providers {
|
||||||
|
providers := &Providers{
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
|
||||||
// If we have a client id and secret for github, initialize the oauth provider
|
// If we have a client id and secret for github, initialize the oauth provider
|
||||||
if providers.Config.GithubClientId != "" && providers.Config.GithubClientSecret != "" {
|
if config.GithubClientId != "" && config.GithubClientSecret != "" {
|
||||||
log.Info().Msg("Initializing Github OAuth")
|
log.Info().Msg("Initializing Github OAuth")
|
||||||
|
|
||||||
// Create a new oauth provider with the github config
|
// Create a new oauth provider with the github config
|
||||||
providers.Github = oauth.NewOAuth(oauth2.Config{
|
providers.Github = oauth.NewOAuth(oauth2.Config{
|
||||||
ClientID: providers.Config.GithubClientId,
|
ClientID: config.GithubClientId,
|
||||||
ClientSecret: providers.Config.GithubClientSecret,
|
ClientSecret: config.GithubClientSecret,
|
||||||
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/github", providers.Config.AppURL),
|
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/github", config.AppURL),
|
||||||
Scopes: GithubScopes(),
|
Scopes: GithubScopes(),
|
||||||
Endpoint: endpoints.GitHub,
|
Endpoint: endpoints.GitHub,
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
// Initialize the oauth provider
|
|
||||||
providers.Github.Init()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a client id and secret for google, initialize the oauth provider
|
// If we have a client id and secret for google, initialize the oauth provider
|
||||||
if providers.Config.GoogleClientId != "" && providers.Config.GoogleClientSecret != "" {
|
if config.GoogleClientId != "" && config.GoogleClientSecret != "" {
|
||||||
log.Info().Msg("Initializing Google OAuth")
|
log.Info().Msg("Initializing Google OAuth")
|
||||||
|
|
||||||
// Create a new oauth provider with the google config
|
// Create a new oauth provider with the google config
|
||||||
providers.Google = oauth.NewOAuth(oauth2.Config{
|
providers.Google = oauth.NewOAuth(oauth2.Config{
|
||||||
ClientID: providers.Config.GoogleClientId,
|
ClientID: config.GoogleClientId,
|
||||||
ClientSecret: providers.Config.GoogleClientSecret,
|
ClientSecret: config.GoogleClientSecret,
|
||||||
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/google", providers.Config.AppURL),
|
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/google", config.AppURL),
|
||||||
Scopes: GoogleScopes(),
|
Scopes: GoogleScopes(),
|
||||||
Endpoint: endpoints.Google,
|
Endpoint: endpoints.Google,
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
// Initialize the oauth provider
|
|
||||||
providers.Google.Init()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a client id and secret for generic oauth, initialize the oauth provider
|
// If we have a client id and secret for generic oauth, initialize the oauth provider
|
||||||
if providers.Config.GenericClientId != "" && providers.Config.GenericClientSecret != "" {
|
if config.GenericClientId != "" && config.GenericClientSecret != "" {
|
||||||
log.Info().Msg("Initializing Generic OAuth")
|
log.Info().Msg("Initializing Generic OAuth")
|
||||||
|
|
||||||
// Create a new oauth provider with the generic config
|
// Create a new oauth provider with the generic config
|
||||||
providers.Generic = oauth.NewOAuth(oauth2.Config{
|
providers.Generic = oauth.NewOAuth(oauth2.Config{
|
||||||
ClientID: providers.Config.GenericClientId,
|
ClientID: config.GenericClientId,
|
||||||
ClientSecret: providers.Config.GenericClientSecret,
|
ClientSecret: config.GenericClientSecret,
|
||||||
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/generic", providers.Config.AppURL),
|
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/generic", config.AppURL),
|
||||||
Scopes: providers.Config.GenericScopes,
|
Scopes: config.GenericScopes,
|
||||||
Endpoint: oauth2.Endpoint{
|
Endpoint: oauth2.Endpoint{
|
||||||
AuthURL: providers.Config.GenericAuthURL,
|
AuthURL: config.GenericAuthURL,
|
||||||
TokenURL: providers.Config.GenericTokenURL,
|
TokenURL: config.GenericTokenURL,
|
||||||
},
|
},
|
||||||
}, providers.Config.GenericSkipSSL)
|
}, config.GenericSkipSSL)
|
||||||
|
|
||||||
// Initialize the oauth provider
|
|
||||||
providers.Generic.Init()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return providers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (providers *Providers) GetProvider(provider string) *oauth.OAuth {
|
func (providers *Providers) GetProvider(provider string) *oauth.OAuth {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package api
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -15,20 +15,13 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAPI(config types.APIConfig, handlers *handlers.Handlers) *API {
|
type Server struct {
|
||||||
return &API{
|
Config types.ServerConfig
|
||||||
Config: config,
|
|
||||||
Handlers: handlers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type API struct {
|
|
||||||
Config types.APIConfig
|
|
||||||
Router *gin.Engine
|
|
||||||
Handlers *handlers.Handlers
|
Handlers *handlers.Handlers
|
||||||
|
Router *gin.Engine
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) Init() {
|
func NewServer(config types.ServerConfig, handlers *handlers.Handlers) (*Server, error) {
|
||||||
// Disable gin logs
|
// Disable gin logs
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
@@ -42,7 +35,7 @@ func (api *API) Init() {
|
|||||||
dist, err := fs.Sub(assets.Assets, "dist")
|
dist, err := fs.Sub(assets.Assets, "dist")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Failed to get UI assets")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create file server
|
// Create file server
|
||||||
@@ -69,41 +62,38 @@ func (api *API) Init() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set router
|
// Proxy routes
|
||||||
api.Router = router
|
router.GET("/api/auth/:proxy", handlers.AuthHandler)
|
||||||
|
|
||||||
|
// Auth routes
|
||||||
|
router.POST("/api/login", handlers.LoginHandler)
|
||||||
|
router.POST("/api/totp", handlers.TotpHandler)
|
||||||
|
router.POST("/api/logout", handlers.LogoutHandler)
|
||||||
|
|
||||||
|
// Context routes
|
||||||
|
router.GET("/api/app", handlers.AppHandler)
|
||||||
|
router.GET("/api/user", handlers.UserHandler)
|
||||||
|
|
||||||
|
// OAuth routes
|
||||||
|
router.GET("/api/oauth/url/:provider", handlers.OauthUrlHandler)
|
||||||
|
router.GET("/api/oauth/callback/:provider", handlers.OauthCallbackHandler)
|
||||||
|
|
||||||
|
// App routes
|
||||||
|
router.GET("/api/healthcheck", handlers.HealthcheckHandler)
|
||||||
|
|
||||||
|
// Return the server
|
||||||
|
return &Server{
|
||||||
|
Config: config,
|
||||||
|
Handlers: handlers,
|
||||||
|
Router: router,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) SetupRoutes() {
|
func (s *Server) Start() error {
|
||||||
// Proxy
|
|
||||||
api.Router.GET("/api/auth/:proxy", api.Handlers.AuthHandler)
|
|
||||||
|
|
||||||
// Auth
|
|
||||||
api.Router.POST("/api/login", api.Handlers.LoginHandler)
|
|
||||||
api.Router.POST("/api/totp", api.Handlers.TotpHandler)
|
|
||||||
api.Router.POST("/api/logout", api.Handlers.LogoutHandler)
|
|
||||||
|
|
||||||
// Context
|
|
||||||
api.Router.GET("/api/app", api.Handlers.AppHandler)
|
|
||||||
api.Router.GET("/api/user", api.Handlers.UserHandler)
|
|
||||||
|
|
||||||
// OAuth
|
|
||||||
api.Router.GET("/api/oauth/url/:provider", api.Handlers.OauthUrlHandler)
|
|
||||||
api.Router.GET("/api/oauth/callback/:provider", api.Handlers.OauthCallbackHandler)
|
|
||||||
|
|
||||||
// App
|
|
||||||
api.Router.GET("/api/healthcheck", api.Handlers.HealthcheckHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) Run() {
|
|
||||||
log.Info().Str("address", api.Config.Address).Int("port", api.Config.Port).Msg("Starting server")
|
|
||||||
|
|
||||||
// Run server
|
// Run server
|
||||||
err := api.Router.Run(fmt.Sprintf("%s:%d", api.Config.Address, api.Config.Port))
|
log.Info().Str("address", s.Config.Address).Int("port", s.Config.Port).Msg("Starting server")
|
||||||
|
|
||||||
// Check for errors
|
return s.Router.Run(fmt.Sprintf("%s:%d", s.Config.Address, s.Config.Port))
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to start server")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// zerolog is a middleware for gin that logs requests using zerolog
|
// zerolog is a middleware for gin that logs requests using zerolog
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package api_test
|
package server_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -8,19 +8,19 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"tinyauth/internal/api"
|
|
||||||
"tinyauth/internal/auth"
|
"tinyauth/internal/auth"
|
||||||
"tinyauth/internal/docker"
|
"tinyauth/internal/docker"
|
||||||
"tinyauth/internal/handlers"
|
"tinyauth/internal/handlers"
|
||||||
"tinyauth/internal/hooks"
|
"tinyauth/internal/hooks"
|
||||||
"tinyauth/internal/providers"
|
"tinyauth/internal/providers"
|
||||||
|
"tinyauth/internal/server"
|
||||||
"tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
|
|
||||||
"github.com/magiconair/properties/assert"
|
"github.com/magiconair/properties/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Simple API config for tests
|
// Simple server config for tests
|
||||||
var apiConfig = types.APIConfig{
|
var serverConfig = types.ServerConfig{
|
||||||
Port: 8080,
|
Port: 8080,
|
||||||
Address: "0.0.0.0",
|
Address: "0.0.0.0",
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,8 @@ var handlersConfig = types.HandlersConfig{
|
|||||||
var authConfig = types.AuthConfig{
|
var authConfig = types.AuthConfig{
|
||||||
Users: types.Users{},
|
Users: types.Users{},
|
||||||
OauthWhitelist: "",
|
OauthWhitelist: "",
|
||||||
Secret: "super-secret-api-thing-for-tests", // It is 32 chars long
|
HMACSecret: "super-secret-api-thing-for-test1",
|
||||||
|
EncryptionSecret: "super-secret-api-thing-for-test2",
|
||||||
CookieSecure: false,
|
CookieSecure: false,
|
||||||
SessionExpiry: 3600,
|
SessionExpiry: 3600,
|
||||||
LoginTimeout: 0,
|
LoginTimeout: 0,
|
||||||
@@ -67,15 +68,11 @@ var user = types.User{
|
|||||||
Password: "$2a$10$AvGHLTYv3xiRJ0xV9xs3XeVIlkGTygI9nqIamFYB5Xu.5.0UWF7B6", // pass
|
Password: "$2a$10$AvGHLTYv3xiRJ0xV9xs3XeVIlkGTygI9nqIamFYB5Xu.5.0UWF7B6", // pass
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need all this to be able to test the API
|
// We need all this to be able to test the server
|
||||||
func getAPI(t *testing.T) *api.API {
|
func getServer(t *testing.T) *server.Server {
|
||||||
// Create docker service
|
// Create docker service
|
||||||
docker := docker.NewDocker()
|
docker, err := docker.NewDocker()
|
||||||
|
|
||||||
// Initialize docker
|
|
||||||
err := docker.Init()
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to initialize docker: %v", err)
|
t.Fatalf("Failed to initialize docker: %v", err)
|
||||||
}
|
}
|
||||||
@@ -87,36 +84,34 @@ func getAPI(t *testing.T) *api.API {
|
|||||||
Password: user.Password,
|
Password: user.Password,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
auth := auth.NewAuth(authConfig, docker)
|
auth := auth.NewAuth(authConfig, docker, nil)
|
||||||
|
|
||||||
// Create providers service
|
// Create providers service
|
||||||
providers := providers.NewProviders(types.OAuthConfig{})
|
providers := providers.NewProviders(types.OAuthConfig{})
|
||||||
|
|
||||||
// Initialize providers
|
|
||||||
providers.Init()
|
|
||||||
|
|
||||||
// Create hooks service
|
// Create hooks service
|
||||||
hooks := hooks.NewHooks(hooksConfig, auth, providers)
|
hooks := hooks.NewHooks(hooksConfig, auth, providers)
|
||||||
|
|
||||||
// Create handlers service
|
// Create handlers service
|
||||||
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
||||||
|
|
||||||
// Create API
|
// Create server
|
||||||
api := api.NewAPI(apiConfig, handlers)
|
srv, err := server.NewServer(serverConfig, handlers)
|
||||||
|
|
||||||
// Setup routes
|
if err != nil {
|
||||||
api.Init()
|
t.Fatalf("Failed to create server: %v", err)
|
||||||
api.SetupRoutes()
|
}
|
||||||
|
|
||||||
return api
|
// Return the server
|
||||||
|
return srv
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test login (we will need this for the other tests)
|
// Test login
|
||||||
func TestLogin(t *testing.T) {
|
func TestLogin(t *testing.T) {
|
||||||
t.Log("Testing login")
|
t.Log("Testing login")
|
||||||
|
|
||||||
// Get API
|
// Get server
|
||||||
api := getAPI(t)
|
api := getServer(t)
|
||||||
|
|
||||||
// Create recorder
|
// Create recorder
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
@@ -161,8 +156,8 @@ func TestLogin(t *testing.T) {
|
|||||||
func TestAppContext(t *testing.T) {
|
func TestAppContext(t *testing.T) {
|
||||||
t.Log("Testing app context")
|
t.Log("Testing app context")
|
||||||
|
|
||||||
// Get API
|
// Get server
|
||||||
api := getAPI(t)
|
api := getServer(t)
|
||||||
|
|
||||||
// Create recorder
|
// Create recorder
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
@@ -229,8 +224,8 @@ func TestAppContext(t *testing.T) {
|
|||||||
func TestUserContext(t *testing.T) {
|
func TestUserContext(t *testing.T) {
|
||||||
t.Log("Testing user context")
|
t.Log("Testing user context")
|
||||||
|
|
||||||
// Get API
|
// Get server
|
||||||
api := getAPI(t)
|
api := getServer(t)
|
||||||
|
|
||||||
// Create recorder
|
// Create recorder
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
@@ -287,8 +282,8 @@ func TestUserContext(t *testing.T) {
|
|||||||
func TestLogout(t *testing.T) {
|
func TestLogout(t *testing.T) {
|
||||||
t.Log("Testing logout")
|
t.Log("Testing logout")
|
||||||
|
|
||||||
// Get API
|
// Get server
|
||||||
api := getAPI(t)
|
api := getServer(t)
|
||||||
|
|
||||||
// Create recorder
|
// Create recorder
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
@@ -318,5 +313,3 @@ func TestLogout(t *testing.T) {
|
|||||||
t.Fatalf("Cookie not flushed")
|
t.Fatalf("Cookie not flushed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Testing for the oauth stuff
|
|
||||||
@@ -21,6 +21,7 @@ type UnauthorizedQuery struct {
|
|||||||
Username string `url:"username"`
|
Username string `url:"username"`
|
||||||
Resource string `url:"resource"`
|
Resource string `url:"resource"`
|
||||||
GroupErr bool `url:"groupErr"`
|
GroupErr bool `url:"groupErr"`
|
||||||
|
IP string `url:"ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy is the uri parameters for the proxy endpoint
|
// Proxy is the uri parameters for the proxy endpoint
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ type Config struct {
|
|||||||
LoginMaxRetries int `mapstructure:"login-max-retries"`
|
LoginMaxRetries int `mapstructure:"login-max-retries"`
|
||||||
FogotPasswordMessage string `mapstructure:"forgot-password-message" validate:"required"`
|
FogotPasswordMessage string `mapstructure:"forgot-password-message" validate:"required"`
|
||||||
BackgroundImage string `mapstructure:"background-image" validate:"required"`
|
BackgroundImage string `mapstructure:"background-image" validate:"required"`
|
||||||
|
LdapAddress string `mapstructure:"ldap-address"`
|
||||||
|
LdapBindDN string `mapstructure:"ldap-bind-dn"`
|
||||||
|
LdapBindPassword string `mapstructure:"ldap-bind-password"`
|
||||||
|
LdapBaseDN string `mapstructure:"ldap-base-dn"`
|
||||||
|
LdapInsecure bool `mapstructure:"ldap-insecure"`
|
||||||
|
LdapSearchFilter string `mapstructure:"ldap-search-filter"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server configuration
|
// Server configuration
|
||||||
@@ -69,8 +75,8 @@ type OAuthConfig struct {
|
|||||||
AppURL string
|
AppURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIConfig is the configuration for the API
|
// ServerConfig is the configuration for the server
|
||||||
type APIConfig struct {
|
type ServerConfig struct {
|
||||||
Port int
|
Port int
|
||||||
Address string
|
Address string
|
||||||
}
|
}
|
||||||
@@ -80,15 +86,55 @@ type AuthConfig struct {
|
|||||||
Users Users
|
Users Users
|
||||||
OauthWhitelist string
|
OauthWhitelist string
|
||||||
SessionExpiry int
|
SessionExpiry int
|
||||||
Secret string
|
|
||||||
CookieSecure bool
|
CookieSecure bool
|
||||||
Domain string
|
Domain string
|
||||||
LoginTimeout int
|
LoginTimeout int
|
||||||
LoginMaxRetries int
|
LoginMaxRetries int
|
||||||
SessionCookieName string
|
SessionCookieName string
|
||||||
|
HMACSecret string
|
||||||
|
EncryptionSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
// HooksConfig is the configuration for the hooks service
|
// HooksConfig is the configuration for the hooks service
|
||||||
type HooksConfig struct {
|
type HooksConfig struct {
|
||||||
Domain string
|
Domain string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OAuthLabels is a list of labels that can be used in a tinyauth protected container
|
||||||
|
type OAuthLabels struct {
|
||||||
|
Whitelist string
|
||||||
|
Groups string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic auth labels for a tinyauth protected container
|
||||||
|
type BasicLabels struct {
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP labels for a tinyauth protected container
|
||||||
|
type IPLabels struct {
|
||||||
|
Allow []string
|
||||||
|
Block []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Labels is a struct that contains the labels for a tinyauth protected container
|
||||||
|
type Labels struct {
|
||||||
|
Users string
|
||||||
|
Allowed string
|
||||||
|
Headers []string
|
||||||
|
Domain string
|
||||||
|
Basic BasicLabels
|
||||||
|
OAuth OAuthLabels
|
||||||
|
IP IPLabels
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ldap config is a struct that contains the configuration for the LDAP service
|
||||||
|
type LdapConfig struct {
|
||||||
|
Address string
|
||||||
|
BindDN string
|
||||||
|
BindPassword string
|
||||||
|
BaseDN string
|
||||||
|
Insecure bool
|
||||||
|
SearchFilter string
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ type User struct {
|
|||||||
TotpSecret string
|
TotpSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserSearch is the response of the get user
|
||||||
|
type UserSearch struct {
|
||||||
|
Username string
|
||||||
|
Type string // "local", "ldap" or empty
|
||||||
|
}
|
||||||
|
|
||||||
// Users is a list of users
|
// Users is a list of users
|
||||||
type Users []User
|
type Users []User
|
||||||
|
|
||||||
@@ -32,15 +38,6 @@ type SessionCookie struct {
|
|||||||
OAuthGroups string
|
OAuthGroups string
|
||||||
}
|
}
|
||||||
|
|
||||||
// TinyauthLabels is the labels for the tinyauth container
|
|
||||||
type TinyauthLabels struct {
|
|
||||||
OAuthWhitelist string
|
|
||||||
Users string
|
|
||||||
Allowed string
|
|
||||||
Headers map[string]string
|
|
||||||
OAuthGroups string
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserContext is the context for the user
|
// UserContext is the context for the user
|
||||||
type UserContext struct {
|
type UserContext struct {
|
||||||
Username string
|
Username string
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"tinyauth/internal/constants"
|
|
||||||
"tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
|
|
||||||
|
"github.com/traefik/paerser/parser"
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
@@ -174,45 +180,43 @@ func GetUsers(conf string, file string) (types.Users, error) {
|
|||||||
return ParseUsers(users)
|
return ParseUsers(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the docker labels to the tinyauth labels struct
|
// Parse the headers in a map[string]string format
|
||||||
func GetTinyauthLabels(labels map[string]string) types.TinyauthLabels {
|
func ParseHeaders(headers []string) map[string]string {
|
||||||
// Create a new tinyauth labels struct
|
// Create a map to store the headers
|
||||||
var tinyauthLabels types.TinyauthLabels
|
headerMap := make(map[string]string)
|
||||||
|
|
||||||
// Loop through the labels
|
// Loop through the headers
|
||||||
for label, value := range labels {
|
for _, header := range headers {
|
||||||
|
split := strings.SplitN(header, "=", 2)
|
||||||
// Check if the label is in the tinyauth labels
|
if len(split) != 2 {
|
||||||
if slices.Contains(constants.TinyauthLabels, label) {
|
log.Warn().Str("header", header).Msg("Invalid header format, skipping")
|
||||||
|
continue
|
||||||
log.Debug().Str("label", label).Msg("Found label")
|
|
||||||
|
|
||||||
// Add the label value to the tinyauth labels struct
|
|
||||||
switch label {
|
|
||||||
case "tinyauth.oauth.whitelist":
|
|
||||||
tinyauthLabels.OAuthWhitelist = value
|
|
||||||
case "tinyauth.users":
|
|
||||||
tinyauthLabels.Users = value
|
|
||||||
case "tinyauth.allowed":
|
|
||||||
tinyauthLabels.Allowed = value
|
|
||||||
case "tinyauth.headers":
|
|
||||||
tinyauthLabels.Headers = make(map[string]string)
|
|
||||||
headers := strings.Split(value, ",")
|
|
||||||
for _, header := range headers {
|
|
||||||
headerSplit := strings.Split(header, "=")
|
|
||||||
if len(headerSplit) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tinyauthLabels.Headers[headerSplit[0]] = headerSplit[1]
|
|
||||||
}
|
|
||||||
case "tinyauth.oauth.groups":
|
|
||||||
tinyauthLabels.OAuthGroups = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
key := SanitizeHeader(strings.TrimSpace(split[0]))
|
||||||
|
value := SanitizeHeader(strings.TrimSpace(split[1]))
|
||||||
|
headerMap[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the tinyauth labels
|
// Return the header map
|
||||||
return tinyauthLabels
|
return headerMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get labels parses a map of labels into a struct with only the needed labels
|
||||||
|
func GetLabels(labels map[string]string) (types.Labels, error) {
|
||||||
|
// Create a new labels struct
|
||||||
|
var labelsParsed types.Labels
|
||||||
|
|
||||||
|
// Decode the labels into the labels struct
|
||||||
|
err := parser.Decode(labels, &labelsParsed, "tinyauth", "tinyauth.users", "tinyauth.allowed", "tinyauth.headers", "tinyauth.domain", "tinyauth.basic", "tinyauth.oauth", "tinyauth.ip")
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error parsing labels")
|
||||||
|
return types.Labels{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the labels struct
|
||||||
|
return labelsParsed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any of the OAuth providers are configured based on the client id and secret
|
// Check if any of the OAuth providers are configured based on the client id and secret
|
||||||
@@ -360,3 +364,77 @@ func GenerateIdentifier(str string) string {
|
|||||||
// Convert the UUID to a string
|
// Convert the UUID to a string
|
||||||
return strings.Split(uuidString, "-")[0]
|
return strings.Split(uuidString, "-")[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get a basic auth header from a username and password
|
||||||
|
func GetBasicAuth(username string, password string) string {
|
||||||
|
// Create the auth string
|
||||||
|
auth := username + ":" + password
|
||||||
|
|
||||||
|
// Encode the auth string to base64
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if an IP is contained in a CIDR range/matches a single IP
|
||||||
|
func FilterIP(filter string, ip string) (bool, error) {
|
||||||
|
// Convert the check IP to an IP instance
|
||||||
|
ipAddr := net.ParseIP(ip)
|
||||||
|
|
||||||
|
// Check if the filter is a CIDR range
|
||||||
|
if strings.Contains(filter, "/") {
|
||||||
|
// Parse the CIDR range
|
||||||
|
_, cidr, err := net.ParseCIDR(filter)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the IP is in the CIDR range
|
||||||
|
return cidr.Contains(ipAddr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the filter as a single IP
|
||||||
|
ipFilter := net.ParseIP(filter)
|
||||||
|
|
||||||
|
// Check if the IP is valid
|
||||||
|
if ipFilter == nil {
|
||||||
|
return false, errors.New("invalid IP address in filter")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the IP matches the filter
|
||||||
|
if ipFilter.Equal(ipAddr) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the filter is not a CIDR range or a single IP, return false
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeriveKey(secret string, info string) (string, error) {
|
||||||
|
// Create hashing function
|
||||||
|
hash := sha256.New
|
||||||
|
|
||||||
|
// Create a new key using the secret and info
|
||||||
|
hkdf := hkdf.New(hash, []byte(secret), nil, []byte(info)) // I am not using a salt because I just want two different keys from one secret, maybe bad practice
|
||||||
|
|
||||||
|
// Create a new key
|
||||||
|
key := make([]byte, 24)
|
||||||
|
|
||||||
|
// Read the key from the HKDF
|
||||||
|
_, err := io.ReadFull(hkdf, key)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the key is not empty
|
||||||
|
if bytes.Equal(key, make([]byte, 24)) {
|
||||||
|
return "", errors.New("derived key is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the key to base64
|
||||||
|
encodedKey := base64.StdEncoding.EncodeToString(key)
|
||||||
|
|
||||||
|
// Return the key as a base64 encoded string
|
||||||
|
return encodedKey, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -279,29 +279,35 @@ func TestGetUsers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the tinyauth labels function
|
// Test the get labels function
|
||||||
func TestGetTinyauthLabels(t *testing.T) {
|
func TestGetLabels(t *testing.T) {
|
||||||
t.Log("Testing get tinyauth labels with a valid map")
|
t.Log("Testing get labels with a valid map")
|
||||||
|
|
||||||
// Test the get tinyauth labels function with a valid map
|
// Test the get tinyauth labels function with a valid map
|
||||||
labels := map[string]string{
|
labels := map[string]string{
|
||||||
"tinyauth.users": "user1,user2",
|
"tinyauth.users": "user1,user2",
|
||||||
"tinyauth.oauth.whitelist": "/regex/",
|
"tinyauth.oauth.whitelist": "/regex/",
|
||||||
"tinyauth.allowed": "random",
|
"tinyauth.allowed": "random",
|
||||||
"random": "random",
|
|
||||||
"tinyauth.headers": "X-Header=value",
|
"tinyauth.headers": "X-Header=value",
|
||||||
|
"tinyauth.oauth.groups": "group1,group2",
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := types.TinyauthLabels{
|
expected := types.Labels{
|
||||||
Users: "user1,user2",
|
Users: "user1,user2",
|
||||||
OAuthWhitelist: "/regex/",
|
Allowed: "random",
|
||||||
Allowed: "random",
|
Headers: []string{"X-Header=value"},
|
||||||
Headers: map[string]string{
|
OAuth: types.OAuthLabels{
|
||||||
"X-Header": "value",
|
Whitelist: "/regex/",
|
||||||
|
Groups: "group1,group2",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
result := utils.GetTinyauthLabels(labels)
|
result, err := utils.GetLabels(labels)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error getting labels: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the result is equal to the expected
|
// Check if the result is equal to the expected
|
||||||
if !reflect.DeepEqual(expected, result) {
|
if !reflect.DeepEqual(expected, result) {
|
||||||
|
|||||||
Reference in New Issue
Block a user