mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-30 13:45:47 +00:00
Compare commits
4 Commits
v3.5.0-alp
...
refactor/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6157f75659 | ||
|
|
a621135ac0 | ||
|
|
aeb93da378 | ||
|
|
282cabf4dd |
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -39,9 +39,4 @@ jobs:
|
|||||||
cp -r frontend/dist internal/assets/dist
|
cp -r frontend/dist internal/assets/dist
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: go test -coverprofile=coverage.txt -v ./...
|
run: go test -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,8 +1,6 @@
|
|||||||
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.18-alpine AS frontend-builder
|
FROM oven/bun:1.2.16-alpine AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /frontend
|
WORKDIR /frontend
|
||||||
|
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -23,27 +23,27 @@ Tinyauth is a simple authentication middleware that adds a simple login screen o
|
|||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
You can easily get started with Tinyauth by following the guide in the [documentation](https://tinyauth.app/docs/getting-started.html). There is also an available [docker compose](./docker-compose.example.yml) file that has Traefik, Whoami and Tinyauth to demonstrate its capabilities.
|
You can easily get started with tinyauth by following the guide in the [documentation](https://tinyauth.app/docs/getting-started.html). There is also an available [docker compose](./docker-compose.example.yml) file that has traefik, whoami and tinyauth to demonstrate its capabilities.
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
If you are still not sure if Tinyauth suits your needs you can try out the [demo](https://demo.tinyauth.app). The default username is `user` and the default password is `password`.
|
If you are still not sure if tinyauth suits your needs you can try out the [demo](https://demo.tinyauth.app). The default username is `user` and the default password is `password`.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
You can find documentation and guides on all of the available configuration of Tinyauth in the [website](https://tinyauth.app).
|
You can find documentation and guides on all of the available configuration of tinyauth in the [website](https://tinyauth.app).
|
||||||
|
|
||||||
## Discord
|
## Discord
|
||||||
|
|
||||||
Tinyauth has a [discord](https://discord.gg/eHzVaCzRRd) server. Feel free to hop in to chat about self-hosting, homelabs and of course Tinyauth. See you there!
|
Tinyauth has a [discord](https://discord.gg/eHzVaCzRRd) server. Feel free to hop in to chat about self-hosting, homelabs and of course tinyauth. See you there!
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
All contributions to the codebase are welcome! If you have any free time feel free to pick up an [issue](https://github.com/steveiliop56/tinyauth/issues) or add your own missing features. Make sure to check out the [contributing guide](./CONTRIBUTING.md) for instructions on how to get the development server up and running.
|
All contributions to the codebase are welcome! If you have any free time feel free to pick up an [Issue](https://github.com/steveiliop56/tinyauth/issues) or add your own missing features. Make sure to check out the [contributing guide](./CONTRIBUTING.md) for instructions on how to get the development server up and running.
|
||||||
|
|
||||||
## Localization
|
## Localization
|
||||||
|
|
||||||
If you would like to help translate Tinyauth into more languages, visit the [Crowdin](https://crowdin.com/project/tinyauth) page.
|
If you would like to help translate tinyauth into more languages, visit the [Crowdin](https://crowdin.com/project/tinyauth) page.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ Tinyauth is licensed under the GNU General Public License v3.0. TL;DR — You ma
|
|||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
A big thank you to the following people for providing me with more coffee:
|
Thanks a lot to the following people for providing me with more coffee:
|
||||||
|
|
||||||
<!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https://github.com/erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a> <a href="https://github.com/nicotsx"><img src="https://github.com/nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a> <a href="https://github.com/SimpleHomelab"><img src="https://github.com/SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a> <a href="https://github.com/jmadden91"><img src="https://github.com/jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a> <a href="https://github.com/tribor"><img src="https://github.com/tribor.png" width="64px" alt="User avatar: tribor" /></a> <a href="https://github.com/eliasbenb"><img src="https://github.com/eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a> <!-- sponsors -->
|
<!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https://github.com/erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a> <a href="https://github.com/nicotsx"><img src="https://github.com/nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a> <a href="https://github.com/SimpleHomelab"><img src="https://github.com/SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a> <a href="https://github.com/jmadden91"><img src="https://github.com/jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a> <a href="https://github.com/tribor"><img src="https://github.com/tribor.png" width="64px" alt="User avatar: tribor" /></a> <a href="https://github.com/eliasbenb"><img src="https://github.com/eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a> <!-- sponsors -->
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|||||||
86
cmd/root.go
86
cmd/root.go
@@ -8,14 +8,13 @@ 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"
|
||||||
|
|
||||||
@@ -59,6 +58,10 @@ 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)
|
||||||
@@ -71,15 +74,6 @@ 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,
|
||||||
@@ -111,8 +105,8 @@ var rootCmd = &cobra.Command{
|
|||||||
RedirectCookieName: redirectCookieName,
|
RedirectCookieName: redirectCookieName,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create server config
|
// Create api config
|
||||||
serverConfig := types.ServerConfig{
|
apiConfig := types.APIConfig{
|
||||||
Port: config.Port,
|
Port: config.Port,
|
||||||
Address: config.Address,
|
Address: config.Address,
|
||||||
}
|
}
|
||||||
@@ -121,14 +115,13 @@ 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
|
||||||
@@ -137,55 +130,36 @@ var rootCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create docker service
|
// Create docker service
|
||||||
docker, err := docker.NewDocker()
|
docker := 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, ldapService)
|
auth := auth.NewAuth(authConfig, docker)
|
||||||
|
|
||||||
// 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 server
|
// Create API
|
||||||
srv, err := server.NewServer(serverConfig, handlers)
|
api := api.NewAPI(apiConfig, handlers)
|
||||||
HandleError(err, "Failed to create server")
|
|
||||||
|
|
||||||
// Start server
|
// Setup routes
|
||||||
err = srv.Start()
|
api.Init()
|
||||||
HandleError(err, "Failed to start server")
|
api.SetupRoutes()
|
||||||
|
|
||||||
|
// Start
|
||||||
|
api.Run()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,12 +219,6 @@ 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")
|
||||||
@@ -286,12 +254,6 @@ 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())
|
||||||
|
|||||||
@@ -9,45 +9,45 @@
|
|||||||
"@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.11",
|
"@tailwindcss/vite": "^4.1.8",
|
||||||
"@tanstack/react-query": "^5.81.5",
|
"@tanstack/react-query": "^5.80.6",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.9.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.3.1",
|
"i18next": "^25.2.1",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.0.5",
|
||||||
"i18next-resources-to-backend": "^1.2.1",
|
"i18next-resources-to-backend": "^1.2.1",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.513.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.60.0",
|
"react-hook-form": "^7.57.0",
|
||||||
"react-i18next": "^15.6.0",
|
"react-i18next": "^15.5.2",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-router": "^7.6.3",
|
"react-router": "^7.6.2",
|
||||||
"sonner": "^2.0.6",
|
"sonner": "^2.0.5",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.0",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.8",
|
||||||
"zod": "^3.25.75",
|
"zod": "^3.25.57",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.30.1",
|
"@eslint/js": "^9.28.0",
|
||||||
"@tanstack/eslint-plugin-query": "^5.81.2",
|
"@tanstack/eslint-plugin-query": "^5.78.0",
|
||||||
"@types/node": "^24.0.10",
|
"@types/node": "^22.15.29",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.7",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@vitejs/plugin-react": "^4.6.0",
|
"@vitejs/plugin-react": "^4.5.2",
|
||||||
"eslint": "^9.30.1",
|
"eslint": "^9.28.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.3.0",
|
"globals": "^16.2.0",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.5.3",
|
||||||
"tw-animate-css": "^1.3.5",
|
"tw-animate-css": "^1.3.4",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"typescript-eslint": "^8.36.0",
|
"typescript-eslint": "^8.34.0",
|
||||||
"vite": "^7.0.3",
|
"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.6", "", {}, "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q=="],
|
"@babel/runtime": ["@babel/runtime@7.27.1", "", {}, "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog=="],
|
||||||
|
|
||||||
"@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.21.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ=="],
|
"@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-helpers": ["@eslint/config-helpers@0.3.0", "", {}, "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw=="],
|
"@eslint/config-helpers": ["@eslint/config-helpers@0.2.2", "", {}, "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg=="],
|
||||||
|
|
||||||
"@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.30.1", "", {}, "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg=="],
|
"@eslint/js": ["@eslint/js@9.28.0", "", {}, "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg=="],
|
||||||
|
|
||||||
"@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.19", "", {}, "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA=="],
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.11", "", {}, "sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag=="],
|
||||||
|
|
||||||
"@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.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/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/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": ["@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-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.11", "", { "os": "android", "cpu": "arm64" }, "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg=="],
|
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.8", "", { "os": "android", "cpu": "arm64" }, "sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ=="],
|
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw=="],
|
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA=="],
|
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11", "", { "os": "linux", "cpu": "arm" }, "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg=="],
|
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8", "", { "os": "linux", "cpu": "arm" }, "sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ=="],
|
"@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-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ=="],
|
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg=="],
|
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.8", "", { "os": "linux", "cpu": "x64" }, "sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g=="],
|
||||||
|
|
||||||
"@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-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.8", "", { "os": "linux", "cpu": "x64" }, "sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg=="],
|
||||||
|
|
||||||
"@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-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-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w=="],
|
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.11", "", { "os": "win32", "cpu": "x64" }, "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg=="],
|
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.8", "", { "os": "win32", "cpu": "x64" }, "sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ=="],
|
||||||
|
|
||||||
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
"@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/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/query-core": ["@tanstack/query-core@5.81.5", "", {}, "sha512-ZJOgCy/z2qpZXWaj/oxvodDx07XcQa9BF92c0oINjHkoqUPsmm3uG08HpTaviviZ/N9eP1f9CM7mKSEkIo7O1Q=="],
|
"@tanstack/query-core": ["@tanstack/query-core@5.80.6", "", {}, "sha512-nl7YxT/TAU+VTf+e2zTkObGTyY8YZBMnbgeA1ee66lIVqzKlYursAII6z5t0e6rXgwUMJSV4dshBTNacNpZHbQ=="],
|
||||||
|
|
||||||
"@tanstack/react-query": ["@tanstack/react-query@5.81.5", "", { "dependencies": { "@tanstack/query-core": "5.81.5" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-lOf2KqRRiYWpQT86eeeftAGnjuTR35myTP8MXyvHa81VlomoAWNEd8x5vkcAfQefu0qtYCvyqLropFZqgI2EQw=="],
|
"@tanstack/react-query": ["@tanstack/react-query@5.80.6", "", { "dependencies": { "@tanstack/query-core": "5.80.6" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-izX+5CnkpON3NQGcEm3/d7LfFQNo9ZpFtX2QsINgCYK9LT2VCIdi8D3bMaMSNhrAJCznRoAkFic76uvLroALBw=="],
|
||||||
|
|
||||||
"@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@24.0.10", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA=="],
|
"@types/node": ["@types/node@22.15.29", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ=="],
|
||||||
|
|
||||||
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
"@types/react": ["@types/react@19.1.7", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-BnsPLV43ddr05N71gaGzyZ5hzkCmGwhMvYc8zmvI8Ci1bRkkDSzDDVfAXfN2tk748OwI7ediiPX6PfT9p0QGVg=="],
|
||||||
|
|
||||||
"@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.36.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/type-utils": "8.36.0", "@typescript-eslint/utils": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.36.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg=="],
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.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/parser": ["@typescript-eslint/parser@8.36.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q=="],
|
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.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/project-service": ["@typescript-eslint/project-service@8.34.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.34.1", "@typescript-eslint/types": "^8.34.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA=="],
|
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.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/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/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/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.34.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg=="],
|
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.34.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA=="],
|
||||||
|
|
||||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.36.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/utils": "8.36.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg=="],
|
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.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/types": ["@typescript-eslint/types@8.34.1", "", {}, "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA=="],
|
"@typescript-eslint/types": ["@typescript-eslint/types@8.32.0", "", {}, "sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.34.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.34.1", "@typescript-eslint/tsconfig-utils": "8.34.1", "@typescript-eslint/types": "8.34.1", "@typescript-eslint/visitor-keys": "8.34.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA=="],
|
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.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/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/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/visitor-keys": ["@typescript-eslint/visitor-keys@8.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA=="],
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.34.0", "", { "dependencies": { "@typescript-eslint/types": "8.34.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA=="],
|
||||||
|
|
||||||
"@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.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=="],
|
"@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=="],
|
||||||
|
|
||||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
|
||||||
|
|
||||||
"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.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=="],
|
"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=="],
|
||||||
|
|
||||||
"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.30.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.30.1", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ=="],
|
"eslint": ["eslint@9.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-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.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
"eslint-scope": ["eslint-scope@8.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="],
|
||||||
|
|
||||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
|
||||||
|
|
||||||
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
"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=="],
|
||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
@@ -528,7 +528,7 @@
|
|||||||
|
|
||||||
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||||
|
|
||||||
"fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="],
|
"fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="],
|
||||||
|
|
||||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||||
|
|
||||||
@@ -558,7 +558,7 @@
|
|||||||
|
|
||||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||||
|
|
||||||
"globals": ["globals@16.3.0", "", {}, "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ=="],
|
"globals": ["globals@16.2.0", "", {}, "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg=="],
|
||||||
|
|
||||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||||
|
|
||||||
@@ -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.3.1", "", { "dependencies": { "@babel/runtime": "^7.27.6" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-S4CPAx8LfMOnURnnJa8jFWvur+UX/LWcl6+61p9VV7SK2m0445JeBJ6tLD0D5SR0H29G4PYfWkEhivKG5p4RDg=="],
|
"i18next": ["i18next@25.2.1", "", { "dependencies": { "@babel/runtime": "^7.27.1" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw=="],
|
||||||
|
|
||||||
"i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.0", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g=="],
|
"i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.1.0", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-mHZxNx1Lq09xt5kCauZ/4bsXOEA2pfpwSoU11/QTJB+pD94iONFwp+ohqi///PwiFvjFOxe1akYCdHyFo1ng5Q=="],
|
||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
@@ -636,27 +636,27 @@
|
|||||||
|
|
||||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||||
|
|
||||||
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
|
"lightningcss": ["lightningcss@1.29.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.2", "lightningcss-darwin-x64": "1.29.2", "lightningcss-freebsd-x64": "1.29.2", "lightningcss-linux-arm-gnueabihf": "1.29.2", "lightningcss-linux-arm64-gnu": "1.29.2", "lightningcss-linux-arm64-musl": "1.29.2", "lightningcss-linux-x64-gnu": "1.29.2", "lightningcss-linux-x64-musl": "1.29.2", "lightningcss-win32-arm64-msvc": "1.29.2", "lightningcss-win32-x64-msvc": "1.29.2" } }, "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA=="],
|
||||||
|
|
||||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
|
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.29.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA=="],
|
||||||
|
|
||||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
|
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.29.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w=="],
|
||||||
|
|
||||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="],
|
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.29.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg=="],
|
||||||
|
|
||||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="],
|
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.29.2", "", { "os": "linux", "cpu": "arm" }, "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg=="],
|
||||||
|
|
||||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="],
|
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ=="],
|
||||||
|
|
||||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="],
|
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ=="],
|
||||||
|
|
||||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="],
|
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg=="],
|
||||||
|
|
||||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="],
|
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w=="],
|
||||||
|
|
||||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="],
|
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.29.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw=="],
|
||||||
|
|
||||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
|
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="],
|
||||||
|
|
||||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||||
|
|
||||||
@@ -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.525.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ=="],
|
"lucide-react": ["lucide-react@0.513.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-CJZKq2g8Y8yN4Aq002GahSXbG2JpFv9kXwyiOAMvUBv7pxeOFHUWKB0mO7MiY4ZVFCV4aNjv2BJFq/z3DgKPQg=="],
|
||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
@@ -774,11 +774,11 @@
|
|||||||
|
|
||||||
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
|
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
|
||||||
|
|
||||||
"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.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
|
||||||
|
|
||||||
"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.60.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-SBrYOvMbDB7cV8ZfNpaiLcgjH/a1c7aK0lK+aNigpf4xWLO8q+o4tcvVurv3c4EOyzn/3dCsYt4GKD42VvJ/+A=="],
|
"react-hook-form": ["react-hook-form@7.57.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-RbEks3+cbvTP84l/VXGUZ+JMrKOS8ykQCRYdm5aYsxnDquL0vspsyNhGRO7pcH6hsZqWlPOjLye7rJqdtdAmlg=="],
|
||||||
|
|
||||||
"react-i18next": ["react-i18next@15.6.0", "", { "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-W135dB0rDfiFmbMipC17nOhGdttO5mzH8BivY+2ybsQBbXvxWIwl3cmeH3T9d+YPBSJu/ouyJKFJTtkK7rJofw=="],
|
"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-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.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-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-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=="],
|
||||||
|
|
||||||
@@ -830,7 +830,7 @@
|
|||||||
|
|
||||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||||
|
|
||||||
"sonner": ["sonner@2.0.6", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q=="],
|
"sonner": ["sonner@2.0.5", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ=="],
|
||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
@@ -846,15 +846,15 @@
|
|||||||
|
|
||||||
"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.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="],
|
"tailwind-merge": ["tailwind-merge@3.3.0", "", {}, "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ=="],
|
||||||
|
|
||||||
"tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="],
|
"tailwindcss": ["tailwindcss@4.1.8", "", {}, "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og=="],
|
||||||
|
|
||||||
"tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="],
|
"tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="],
|
||||||
|
|
||||||
"tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
|
"tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
|
||||||
|
|
||||||
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
"tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="],
|
||||||
|
|
||||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||||
|
|
||||||
@@ -866,15 +866,15 @@
|
|||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"tw-animate-css": ["tw-animate-css@1.3.5", "", {}, "sha512-t3u+0YNoloIhj1mMXs779P6MO9q3p3mvGn4k1n3nJPqJw/glZcuijG2qTSN4z4mgNRfW5ZC3aXJFLwDtiipZXA=="],
|
"tw-animate-css": ["tw-animate-css@1.3.4", "", {}, "sha512-dd1Ht6/YQHcNbq0znIT6dG8uhO7Ce+VIIhZUhjsryXsMPJQz3bZg7Q2eNzLwipb25bRZslGb2myio5mScd1TFg=="],
|
||||||
|
|
||||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||||
|
|
||||||
"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.36.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.36.0", "@typescript-eslint/parser": "8.36.0", "@typescript-eslint/utils": "8.36.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA=="],
|
"typescript-eslint": ["typescript-eslint@8.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=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
@@ -900,7 +900,7 @@
|
|||||||
|
|
||||||
"vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="],
|
"vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="],
|
||||||
|
|
||||||
"vite": ["vite@7.0.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-y2L5oJZF7bj4c0jgGYgBNSdIu+5HF+m68rn2cQXFbGoShdhV1phX9rbnxy9YXj82aS8MMsCLAAFkRxZeWdldrQ=="],
|
"vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
|
||||||
|
|
||||||
"void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="],
|
"void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="],
|
||||||
|
|
||||||
@@ -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.75", "", {}, "sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg=="],
|
"zod": ["zod@3.25.57", "", {}, "sha512-6tgzLuwVST5oLUxXTmBqoinKMd3JeesgbgseXeFasKKj8Q1FCZrHnbqJOyiEvr4cVAlbug+CgIsmJ8cl/pU5FA=="],
|
||||||
|
|
||||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||||
|
|
||||||
@@ -928,19 +928,19 @@
|
|||||||
|
|
||||||
"@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=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
||||||
|
|
||||||
"@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.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/@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/@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,45 +958,43 @@
|
|||||||
|
|
||||||
"@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.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0" } }, "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA=="],
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.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/utils": ["@typescript-eslint/utils@8.36.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g=="],
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.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/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.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0" } }, "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA=="],
|
"@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.34.0", "", { "dependencies": { "@typescript-eslint/types": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0" } }, "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw=="],
|
||||||
|
|
||||||
"@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
|
"@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
||||||
|
|
||||||
"@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.36.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg=="],
|
"@typescript-eslint/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/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/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
||||||
|
|
||||||
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.36.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg=="],
|
"@typescript-eslint/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/utils": ["@typescript-eslint/utils@8.36.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g=="],
|
"@typescript-eslint/type-utils/@typescript-eslint/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-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/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.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.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
|
"@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
||||||
|
|
||||||
"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.36.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g=="],
|
"typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.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=="],
|
||||||
|
|
||||||
"@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=="],
|
||||||
|
|
||||||
@@ -1004,9 +1002,25 @@
|
|||||||
|
|
||||||
"@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=="],
|
"@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
|
||||||
|
|
||||||
"@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
|
"@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
|
||||||
|
|
||||||
@@ -1018,48 +1032,36 @@
|
|||||||
|
|
||||||
"@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.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
|
"@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/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.36.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg=="],
|
"@typescript-eslint/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/parser/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.36.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.36.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/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/project-service": ["@typescript-eslint/project-service@8.36.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g=="],
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
||||||
|
|
||||||
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.36.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/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.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0" } }, "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA=="],
|
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/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/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
|
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.34.0", "", {}, "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA=="],
|
||||||
|
|
||||||
"@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.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0" } }, "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA=="],
|
"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/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
|
"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/typescript-estree": ["@typescript-eslint/typescript-estree@8.36.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg=="],
|
"typescript-eslint/@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=="],
|
||||||
|
|
||||||
"@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.36.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.36.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/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=="],
|
||||||
@@ -1068,10 +1070,6 @@
|
|||||||
|
|
||||||
"@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.36.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g=="],
|
|
||||||
|
|
||||||
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.36.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA=="],
|
|
||||||
|
|
||||||
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/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.11",
|
"@tailwindcss/vite": "^4.1.8",
|
||||||
"@tanstack/react-query": "^5.81.5",
|
"@tanstack/react-query": "^5.80.6",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.9.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.3.1",
|
"i18next": "^25.2.1",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.0.5",
|
||||||
"i18next-resources-to-backend": "^1.2.1",
|
"i18next-resources-to-backend": "^1.2.1",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.513.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.60.0",
|
"react-hook-form": "^7.57.0",
|
||||||
"react-i18next": "^15.6.0",
|
"react-i18next": "^15.5.2",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-router": "^7.6.3",
|
"react-router": "^7.6.2",
|
||||||
"sonner": "^2.0.6",
|
"sonner": "^2.0.5",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.0",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.8",
|
||||||
"zod": "^3.25.75"
|
"zod": "^3.25.57"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.30.1",
|
"@eslint/js": "^9.28.0",
|
||||||
"@tanstack/eslint-plugin-query": "^5.81.2",
|
"@tanstack/eslint-plugin-query": "^5.78.0",
|
||||||
"@types/node": "^24.0.10",
|
"@types/node": "^22.15.29",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.7",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@vitejs/plugin-react": "^4.6.0",
|
"@vitejs/plugin-react": "^4.5.2",
|
||||||
"eslint": "^9.30.1",
|
"eslint": "^9.28.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.3.0",
|
"globals": "^16.2.0",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.5.3",
|
||||||
"tw-animate-css": "^1.3.5",
|
"tw-animate-css": "^1.3.4",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"typescript-eslint": "^8.36.0",
|
"typescript-eslint": "^8.34.0",
|
||||||
"vite": "^7.0.3"
|
"vite": "^6.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,9 +33,9 @@ export const LoginForm = (props: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="username"
|
name="username"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="mb-4 gap-0">
|
<FormItem className="mb-4">
|
||||||
<FormLabel className="mb-2">{t("loginUsername")}</FormLabel>
|
<FormLabel>{t("loginUsername")}</FormLabel>
|
||||||
<FormControl className="mb-1">
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("loginUsername")}
|
placeholder={t("loginUsername")}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
@@ -50,8 +50,8 @@ export const LoginForm = (props: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="password"
|
name="password"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="mb-4 gap-0">
|
<FormItem className="mb-4">
|
||||||
<div className="relative mb-1">
|
<div className="relative">
|
||||||
<FormLabel className="mb-2">{t("loginPassword")}</FormLabel>
|
<FormLabel className="mb-2">{t("loginPassword")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
@@ -61,14 +61,14 @@ export const LoginForm = (props: Props) => {
|
|||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
<a
|
<a
|
||||||
href="/forgot-password"
|
href="/forgot-password"
|
||||||
className="text-muted-foreground text-sm absolute right-0 bottom-[2.565rem]" // 2.565 is *just* perfect
|
className="text-muted-foreground text-sm absolute right-0 bottom-10"
|
||||||
>
|
>
|
||||||
{t("forgotPasswordTitle")}
|
{t("forgotPasswordTitle")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ h4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@apply leading-6;
|
@apply leading-6 [&:not(:first-child)]:mt-6;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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": "حاول مجددا",
|
"unauthorizedButton": "حاول مجددا",
|
||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"unauthorizedResourceSubtitle": "Brugeren med brugernavnet <code>{{username}}</code> har ikke tilladelse til at tilgå ressourcen <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "Brugeren med brugernavnet <code>{{username}}</code> har ikke tilladelse til at tilgå ressourcen <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "Brugeren med brugernavnet <code>{{username}}</code> har ikke tilladelse til at logge ind.",
|
"unauthorizedLoginSubtitle": "Brugeren med brugernavnet <code>{{username}}</code> har ikke tilladelse til at logge ind.",
|
||||||
"unauthorizedGroupsSubtitle": "Brugeren med brugernavnet <code>{{username}}</code> er ikke i de grupper, som ressourcen <code>{{resource}}</code> kræver.",
|
"unauthorizedGroupsSubtitle": "Brugeren med brugernavnet <code>{{username}}</code> er ikke i de grupper, som ressourcen <code>{{resource}}</code> kræver.",
|
||||||
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
|
||||||
"unauthorizedButton": "Prøv igen",
|
"unauthorizedButton": "Prøv igen",
|
||||||
"untrustedRedirectTitle": "Usikker omdirigering",
|
"untrustedRedirectTitle": "Usikker omdirigering",
|
||||||
"untrustedRedirectSubtitle": "Du forsøger at omdirigere til et domæne, der ikke matcher dit konfigurerede domæne (<code>{{domain}}</code>). Er du sikker på, at du vil fortsætte?",
|
"untrustedRedirectSubtitle": "Du forsøger at omdirigere til et domæne, der ikke matcher dit konfigurerede domæne (<code>{{domain}}</code>). Er du sikker på, at du vil fortsætte?",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"loginSubmit": "Anmelden",
|
"loginSubmit": "Anmelden",
|
||||||
"loginFailTitle": "Login fehlgeschlagen",
|
"loginFailTitle": "Login fehlgeschlagen",
|
||||||
"loginFailSubtitle": "Bitte überprüfe deinen Benutzernamen und Passwort",
|
"loginFailSubtitle": "Bitte überprüfe deinen Benutzernamen und Passwort",
|
||||||
"loginFailRateLimit": "Zu viele fehlgeschlagene Loginversuche. Versuche es später erneut",
|
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||||
"loginSuccessTitle": "Angemeldet",
|
"loginSuccessTitle": "Angemeldet",
|
||||||
"loginSuccessSubtitle": "Willkommen zurück!",
|
"loginSuccessSubtitle": "Willkommen zurück!",
|
||||||
"loginOauthFailTitle": "Ein Fehler ist aufgetreten",
|
"loginOauthFailTitle": "Ein Fehler ist aufgetreten",
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"continueInvalidRedirectTitle": "Ungültige Weiterleitung",
|
"continueInvalidRedirectTitle": "Ungültige Weiterleitung",
|
||||||
"continueInvalidRedirectSubtitle": "Die Weiterleitungs-URL ist ungültig",
|
"continueInvalidRedirectSubtitle": "Die Weiterleitungs-URL ist ungültig",
|
||||||
"continueInsecureRedirectTitle": "Unsichere Weiterleitung",
|
"continueInsecureRedirectTitle": "Unsichere Weiterleitung",
|
||||||
"continueInsecureRedirectSubtitle": "Sie versuchen von <code>https</code> auf <code>http</code> weiterzuleiten, was unsicher ist. Sind Sie sicher, dass Sie fortfahren möchten?",
|
"continueInsecureRedirectSubtitle": "You are trying to redirect from <code>https</code> to <code>http</code> which is not secure. Are you sure you want to continue?",
|
||||||
"continueTitle": "Weiter",
|
"continueTitle": "Weiter",
|
||||||
"continueSubtitle": "Klicken Sie auf den Button, um zur App zu gelangen.",
|
"continueSubtitle": "Klicken Sie auf den Button, um zur App zu gelangen.",
|
||||||
"logoutFailTitle": "Abmelden fehlgeschlagen",
|
"logoutFailTitle": "Abmelden fehlgeschlagen",
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
"logoutSuccessTitle": "Abgemeldet",
|
"logoutSuccessTitle": "Abgemeldet",
|
||||||
"logoutSuccessSubtitle": "Sie wurden abgemeldet",
|
"logoutSuccessSubtitle": "Sie wurden abgemeldet",
|
||||||
"logoutTitle": "Abmelden",
|
"logoutTitle": "Abmelden",
|
||||||
"logoutUsernameSubtitle": "Sie sind derzeit als <code>{{username}}</code> angemeldet. Klicken Sie auf den Button unten, um sich abzumelden.",
|
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
|
||||||
"logoutOauthSubtitle": "Sie sind derzeit als <code>{{username}}</code> über den OAuth-Anbieter {{provider}} angemeldet. Klicken Sie auf den Button unten, um sich abzumelden.",
|
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
|
||||||
"notFoundTitle": "Seite nicht gefunden",
|
"notFoundTitle": "Seite nicht gefunden",
|
||||||
"notFoundSubtitle": "Die gesuchte Seite existiert nicht.",
|
"notFoundSubtitle": "Die gesuchte Seite existiert nicht.",
|
||||||
"notFoundButton": "Nach Hause",
|
"notFoundButton": "Nach Hause",
|
||||||
@@ -37,18 +37,17 @@
|
|||||||
"totpSuccessTitle": "Verifiziert",
|
"totpSuccessTitle": "Verifiziert",
|
||||||
"totpSuccessSubtitle": "Leite zur App weiter",
|
"totpSuccessSubtitle": "Leite zur App weiter",
|
||||||
"totpTitle": "Geben Sie Ihren TOTP Code ein",
|
"totpTitle": "Geben Sie Ihren TOTP Code ein",
|
||||||
"totpSubtitle": "Bitte geben Sie den Code aus Ihrer Authenticator-App ein.",
|
"totpSubtitle": "Please enter the code from your authenticator app.",
|
||||||
"unauthorizedTitle": "Unautorisiert",
|
"unauthorizedTitle": "Unautorisiert",
|
||||||
"unauthorizedResourceSubtitle": "Der Benutzer mit Benutzername <code>{{username}}</code> ist nicht berechtigt, auf die Ressource <code>{{resource}}</code> zuzugreifen.",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "Der Benutzer mit Benutzername <code>{{username}}</code> ist nicht berechtigt, sich anzumelden.",
|
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
||||||
"unauthorizedGroupsSubtitle": "Der Benutzer mit Benutzername <code>{{username}}</code> ist nicht in den Gruppen, die von der Ressource <code>{{resource}}</code> benötigt werden.",
|
"unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedIpSubtitle": "Ihre IP-Adresse <code>{{ip}}</code> ist nicht berechtigt, auf die Ressource <code>{{resource}}</code> zuzugreifen.",
|
|
||||||
"unauthorizedButton": "Erneut versuchen",
|
"unauthorizedButton": "Erneut versuchen",
|
||||||
"untrustedRedirectTitle": "Nicht vertrauenswürdige Weiterleitung",
|
"untrustedRedirectTitle": "Nicht vertrauenswürdige Weiterleitung",
|
||||||
"untrustedRedirectSubtitle": "Sie versuchen auf eine Domain umzuleiten, die nicht mit Ihrer konfigurierten Domain übereinstimmt (<code>{{domain}}</code>). Sind Sie sicher, dass Sie fortfahren möchten?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{domain}}</code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Abbrechen",
|
"cancelTitle": "Abbrechen",
|
||||||
"forgotPasswordTitle": "Passwort vergessen?",
|
"forgotPasswordTitle": "Passwort vergessen?",
|
||||||
"failedToFetchProvidersTitle": "Fehler beim Laden der Authentifizierungsanbieter. Bitte überprüfen Sie Ihre Konfiguration.",
|
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||||
"errorTitle": "Ein Fehler ist aufgetreten",
|
"errorTitle": "An error occurred",
|
||||||
"errorSubtitle": "Beim Versuch, diese Aktion auszuführen, ist ein Fehler aufgetreten. Bitte überprüfen Sie die Konsole für weitere Informationen."
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information."
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,6 @@
|
|||||||
"unauthorizedResourceSubtitle": "Ο χρήστης με όνομα χρήστη <code>{{username}}</code> δεν έχει άδεια πρόσβασης στον πόρο <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "Ο χρήστης με όνομα χρήστη <code>{{username}}</code> δεν έχει άδεια πρόσβασης στον πόρο <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "Ο χρήστης με όνομα χρήστη <code>{{username}}</code> δεν είναι εξουσιοδοτημένος να συνδεθεί.",
|
"unauthorizedLoginSubtitle": "Ο χρήστης με όνομα χρήστη <code>{{username}}</code> δεν είναι εξουσιοδοτημένος να συνδεθεί.",
|
||||||
"unauthorizedGroupsSubtitle": "Ο χρήστης με όνομα χρήστη <code>{{username}}</code> δεν είναι στις ομάδες που απαιτούνται από τον πόρο <code>{{resource}}</code>.",
|
"unauthorizedGroupsSubtitle": "Ο χρήστης με όνομα χρήστη <code>{{username}}</code> δεν είναι στις ομάδες που απαιτούνται από τον πόρο <code>{{resource}}</code>.",
|
||||||
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
|
||||||
"unauthorizedButton": "Προσπαθήστε ξανά",
|
"unauthorizedButton": "Προσπαθήστε ξανά",
|
||||||
"untrustedRedirectTitle": "Μη έμπιστη ανακατεύθυνση",
|
"untrustedRedirectTitle": "Μη έμπιστη ανακατεύθυνση",
|
||||||
"untrustedRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε σε ένα domain που δεν ταιριάζει με τον ρυθμισμένο domain σας (<code>{{domain}}</code>). Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
|
"untrustedRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε σε ένα domain που δεν ταιριάζει με τον ρυθμισμένο domain σας (<code>{{domain}}</code>). Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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?",
|
||||||
|
|||||||
@@ -1,54 +1,53 @@
|
|||||||
{
|
{
|
||||||
"loginTitle": "Bienvenido de vuelta, inicie sesión con",
|
"loginTitle": "Bienvenido de nuevo, inicie sesión con",
|
||||||
"loginTitleSimple": "Bienvenido de vuelta, por favor inicie sesión",
|
"loginTitleSimple": "Welcome back, please login",
|
||||||
"loginDivider": "O",
|
"loginDivider": "Or",
|
||||||
"loginUsername": "Usuario",
|
"loginUsername": "Username",
|
||||||
"loginPassword": "Contraseña",
|
"loginPassword": "Password",
|
||||||
"loginSubmit": "Iniciar sesión",
|
"loginSubmit": "Login",
|
||||||
"loginFailTitle": "Fallo al iniciar sesión",
|
"loginFailTitle": "Failed to log in",
|
||||||
"loginFailSubtitle": "Por favor revise su usuario y contraseña",
|
"loginFailSubtitle": "Please check your username and password",
|
||||||
"loginFailRateLimit": "Muchos inicios de sesión consecutivos fallidos. Por favor inténtelo más tarde",
|
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||||
"loginSuccessTitle": "Sesión iniciada",
|
"loginSuccessTitle": "Logged in",
|
||||||
"loginSuccessSubtitle": "¡Bienvenido de vuelta!",
|
"loginSuccessSubtitle": "Welcome back!",
|
||||||
"loginOauthFailTitle": "Ocurrió un error",
|
"loginOauthFailTitle": "An error occurred",
|
||||||
"loginOauthFailSubtitle": "Error al obtener la URL de OAuth",
|
"loginOauthFailSubtitle": "Error al obtener la URL de OAuth",
|
||||||
"loginOauthSuccessTitle": "Redireccionando",
|
"loginOauthSuccessTitle": "Redirecting",
|
||||||
"loginOauthSuccessSubtitle": "Redireccionando a tu proveedor de OAuth",
|
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||||
"continueRedirectingTitle": "Redireccionando...",
|
"continueRedirectingTitle": "Redirecting...",
|
||||||
"continueRedirectingSubtitle": "Pronto será redirigido a la aplicación",
|
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||||
"continueInvalidRedirectTitle": "Redirección inválida",
|
"continueInvalidRedirectTitle": "Invalid redirect",
|
||||||
"continueInvalidRedirectSubtitle": "La URL de redirección es inválida",
|
"continueInvalidRedirectSubtitle": "The redirect URL is invalid",
|
||||||
"continueInsecureRedirectTitle": "Redirección insegura",
|
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||||
"continueInsecureRedirectSubtitle": "Está intentando redirigir desde <code>https</code> a <code>http</code> lo cual no es seguro. ¿Está seguro que desea continuar?",
|
"continueInsecureRedirectSubtitle": "You are trying to redirect from <code>https</code> to <code>http</code> which is not secure. Are you sure you want to continue?",
|
||||||
"continueTitle": "Continuar",
|
"continueTitle": "Continue",
|
||||||
"continueSubtitle": "Haga clic en el botón para continuar hacia su aplicación.",
|
"continueSubtitle": "Click the button to continue to your app.",
|
||||||
"logoutFailTitle": "Fallo al cerrar sesión",
|
"logoutFailTitle": "Failed to log out",
|
||||||
"logoutFailSubtitle": "Por favor intente nuevamente",
|
"logoutFailSubtitle": "Please try again",
|
||||||
"logoutSuccessTitle": "Sesión cerrada",
|
"logoutSuccessTitle": "Logged out",
|
||||||
"logoutSuccessSubtitle": "Su sesión ha sido cerrada",
|
"logoutSuccessSubtitle": "You have been logged out",
|
||||||
"logoutTitle": "Cerrar sesión",
|
"logoutTitle": "Logout",
|
||||||
"logoutUsernameSubtitle": "Actualmente está conectado como <code>{{username}}</code>. Haga clic en el botón de abajo para cerrar sesión.",
|
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
|
||||||
"logoutOauthSubtitle": "Actualmente está conectado como <code>{{username}}</code> usando {{provider}} como su proveedor de OAuth. Haga clic en el botón de abajo para cerrar sesión.",
|
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
|
||||||
"notFoundTitle": "Página no encontrada",
|
"notFoundTitle": "Page not found",
|
||||||
"notFoundSubtitle": "La página que está buscando no existe.",
|
"notFoundSubtitle": "The page you are looking for does not exist.",
|
||||||
"notFoundButton": "Volver al inicio",
|
"notFoundButton": "Go home",
|
||||||
"totpFailTitle": "Error al verificar código",
|
"totpFailTitle": "Failed to verify code",
|
||||||
"totpFailSubtitle": "Por favor compruebe su código e inténtelo de nuevo",
|
"totpFailSubtitle": "Please check your code and try again",
|
||||||
"totpSuccessTitle": "Verificado",
|
"totpSuccessTitle": "Verified",
|
||||||
"totpSuccessSubtitle": "Redirigiendo a su aplicación",
|
"totpSuccessSubtitle": "Redirecting to your app",
|
||||||
"totpTitle": "Ingrese su código TOTP",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"totpSubtitle": "Por favor introduzca el código de su aplicación de autenticación.",
|
"totpSubtitle": "Please enter the code from your authenticator app.",
|
||||||
"unauthorizedTitle": "No autorizado",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "El usuario con nombre de usuario <code>{{username}}</code> no está autorizado para acceder al recurso <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "El usuario con nombre de usuario <code>{{username}}</code> no está autorizado a iniciar sesión.",
|
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
||||||
"unauthorizedGroupsSubtitle": "El usuario con nombre de usuario <code>{{username}}</code> no está en los grupos requeridos por el recurso <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": "Inténtelo de nuevo",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectTitle": "Redirección no confiable",
|
"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": "Está intentando redirigir a un dominio que no coincide con su dominio configurado (<code>{{domain}}</code>). ¿Está seguro que desea continuar?",
|
"cancelTitle": "Cancel",
|
||||||
"cancelTitle": "Cancelar",
|
"forgotPasswordTitle": "Forgot your password?",
|
||||||
"forgotPasswordTitle": "¿Olvidó su contraseña?",
|
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||||
"failedToFetchProvidersTitle": "Error al cargar los proveedores de autenticación. Por favor revise su configuración.",
|
"errorTitle": "An error occurred",
|
||||||
"errorTitle": "Ha ocurrido un error",
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information."
|
||||||
"errorSubtitle": "Ocurrió un error mientras se trataba de realizar esta acción. Por favor, revise la consola para más información."
|
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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": "Réessayer",
|
"unauthorizedButton": "Réessayer",
|
||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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": "Opnieuw proberen",
|
"unauthorizedButton": "Opnieuw proberen",
|
||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"unauthorizedResourceSubtitle": "Użytkownik o nazwie użytkownika <code>{{username}}</code> nie ma uprawnień dostępu do zasobu <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "Użytkownik o nazwie użytkownika <code>{{username}}</code> nie ma uprawnień dostępu do zasobu <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "Użytkownik o nazwie <code>{{username}}</code> nie jest upoważniony do zalogowania się.",
|
"unauthorizedLoginSubtitle": "Użytkownik o nazwie <code>{{username}}</code> nie jest upoważniony do zalogowania się.",
|
||||||
"unauthorizedGroupsSubtitle": "Użytkownik o nazwie <code>{{username}}</code> nie należy do grup wymaganych przez zasób <code>{{resource}}</code>.",
|
"unauthorizedGroupsSubtitle": "Użytkownik o nazwie <code>{{username}}</code> nie należy do grup wymaganych przez zasób <code>{{resource}}</code>.",
|
||||||
"unauthorizedIpSubtitle": "Twój adres IP <code>{{ip}}</code> nie ma autoryzacji do dostępu do zasobu <code>{{resource}}</code>.",
|
|
||||||
"unauthorizedButton": "Spróbuj ponownie",
|
"unauthorizedButton": "Spróbuj ponownie",
|
||||||
"untrustedRedirectTitle": "Niezaufane przekierowanie",
|
"untrustedRedirectTitle": "Niezaufane przekierowanie",
|
||||||
"untrustedRedirectSubtitle": "Próbujesz przekierować do domeny, która nie pasuje do Twojej skonfigurowanej domeny (<code>{{domain}}</code>). Czy na pewno chcesz kontynuować?",
|
"untrustedRedirectSubtitle": "Próbujesz przekierować do domeny, która nie pasuje do Twojej skonfigurowanej domeny (<code>{{domain}}</code>). Czy na pewno chcesz kontynuować?",
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
"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": "Tentar novamente",
|
"unauthorizedButton": "Tentar novamente",
|
||||||
"untrustedRedirectTitle": "Redirecionamento não confiável",
|
"untrustedRedirectTitle": "Redirecionamento não confiável",
|
||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"unauthorizedResourceSubtitle": "Пользователю <code>{{username}}</code> не разрешен доступ к <code>{{resource}}</code>.",
|
"unauthorizedResourceSubtitle": "Пользователю <code>{{username}}</code> не разрешен доступ к <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "Пользователю <code>{{username}}</code> не разрешен вход.",
|
"unauthorizedLoginSubtitle": "Пользователю <code>{{username}}</code> не разрешен вход.",
|
||||||
"unauthorizedGroupsSubtitle": "Пользователь <code>{{username}}</code> не состоит в группах, которым разрешен доступ к <code>{{resource}}</code>.",
|
"unauthorizedGroupsSubtitle": "Пользователь <code>{{username}}</code> не состоит в группах, которым разрешен доступ к <code>{{resource}}</code>.",
|
||||||
"unauthorizedIpSubtitle": "Ваш IP адрес <code>{{ip}}</code> не авторизован для доступа к ресурсу <code>{{resource}}</code>.",
|
|
||||||
"unauthorizedButton": "Повторить",
|
"unauthorizedButton": "Повторить",
|
||||||
"untrustedRedirectTitle": "Ненадежное перенаправление",
|
"untrustedRedirectTitle": "Ненадежное перенаправление",
|
||||||
"untrustedRedirectSubtitle": "Попытка перенаправить на домен, который не соответствует вашему заданному домену (<code>{{domain}}</code>). Уверены, что хотите продолжить?",
|
"untrustedRedirectSubtitle": "Попытка перенаправить на домен, который не соответствует вашему заданному домену (<code>{{domain}}</code>). Уверены, что хотите продолжить?",
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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,7 +42,6 @@
|
|||||||
"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?",
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"loginTitle": "欢迎回来,请使用以下方式登录",
|
"loginTitle": "欢迎回来,请登录",
|
||||||
"loginTitleSimple": "欢迎回来,请登录",
|
"loginTitleSimple": "Welcome back, please login",
|
||||||
"loginDivider": "或",
|
"loginDivider": "或",
|
||||||
"loginUsername": "用户名",
|
"loginUsername": "用户名",
|
||||||
"loginPassword": "密码",
|
"loginPassword": "密码",
|
||||||
"loginSubmit": "登录",
|
"loginSubmit": "登录",
|
||||||
"loginFailTitle": "登录失败",
|
"loginFailTitle": "登录失败",
|
||||||
"loginFailSubtitle": "请检查您的用户名和密码",
|
"loginFailSubtitle": "请检查您的用户名和密码",
|
||||||
"loginFailRateLimit": "您登录失败次数过多。请稍后再试",
|
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||||
"loginSuccessTitle": "已登录",
|
"loginSuccessTitle": "已登录",
|
||||||
"loginSuccessSubtitle": "欢迎回来!",
|
"loginSuccessSubtitle": "欢迎回来!",
|
||||||
"loginOauthFailTitle": "发生错误",
|
"loginOauthFailTitle": "An error occurred",
|
||||||
"loginOauthFailSubtitle": "获取 OAuth URL 失败",
|
"loginOauthFailSubtitle": "获取 OAuth URL 失败",
|
||||||
"loginOauthSuccessTitle": "重定向中",
|
"loginOauthSuccessTitle": "重定向中",
|
||||||
"loginOauthSuccessSubtitle": "重定向到您的 OAuth 提供商",
|
"loginOauthSuccessSubtitle": "重定向到您的 OAuth 提供商",
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"continueInvalidRedirectTitle": "无效的重定向",
|
"continueInvalidRedirectTitle": "无效的重定向",
|
||||||
"continueInvalidRedirectSubtitle": "重定向URL无效",
|
"continueInvalidRedirectSubtitle": "重定向URL无效",
|
||||||
"continueInsecureRedirectTitle": "不安全的重定向",
|
"continueInsecureRedirectTitle": "不安全的重定向",
|
||||||
"continueInsecureRedirectSubtitle": "您正在尝试从<code>https</code>重定向到<code>http</code>可能存在风险。您确定要继续吗?",
|
"continueInsecureRedirectSubtitle": "You are trying to redirect from <code>https</code> to <code>http</code> which is not secure. Are you sure you want to continue?",
|
||||||
"continueTitle": "继续",
|
"continueTitle": "继续",
|
||||||
"continueSubtitle": "点击按钮以继续您的应用。",
|
"continueSubtitle": "点击按钮以继续您的应用。",
|
||||||
"logoutFailTitle": "注销失败",
|
"logoutFailTitle": "注销失败",
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
"logoutSuccessTitle": "已登出",
|
"logoutSuccessTitle": "已登出",
|
||||||
"logoutSuccessSubtitle": "您已登出",
|
"logoutSuccessSubtitle": "您已登出",
|
||||||
"logoutTitle": "登出",
|
"logoutTitle": "登出",
|
||||||
"logoutUsernameSubtitle": "您当前登录用户为<code>{{username}}</code>。点击下方按钮注销。",
|
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
|
||||||
"logoutOauthSubtitle": "您当前以<code>{{username}}</code>登录,使用的是{{provider}} OAuth 提供商。点击下方按钮注销。",
|
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
|
||||||
"notFoundTitle": "无法找到页面",
|
"notFoundTitle": "无法找到页面",
|
||||||
"notFoundSubtitle": "您正在查找的页面不存在。",
|
"notFoundSubtitle": "您正在查找的页面不存在。",
|
||||||
"notFoundButton": "回到主页",
|
"notFoundButton": "回到主页",
|
||||||
@@ -37,18 +37,17 @@
|
|||||||
"totpSuccessTitle": "已验证",
|
"totpSuccessTitle": "已验证",
|
||||||
"totpSuccessSubtitle": "重定向到您的应用",
|
"totpSuccessSubtitle": "重定向到您的应用",
|
||||||
"totpTitle": "输入您的 TOTP 代码",
|
"totpTitle": "输入您的 TOTP 代码",
|
||||||
"totpSubtitle": "请输入您身份验证器应用中的代码。",
|
"totpSubtitle": "Please enter the code from your authenticator app.",
|
||||||
"unauthorizedTitle": "未授权",
|
"unauthorizedTitle": "未授权",
|
||||||
"unauthorizedResourceSubtitle": "用户名为<code>{{username}}</code>的用户无权访问资源<code>{{resource}}</code>。",
|
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||||
"unauthorizedLoginSubtitle": "用户名为<code>{{username}}</code>的用户无权登录。",
|
"unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
|
||||||
"unauthorizedGroupsSubtitle": "用户名为<code>{{username}}</code>的用户不在资源<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": "重试",
|
"unauthorizedButton": "重试",
|
||||||
"untrustedRedirectTitle": "不可信的重定向",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "您正在尝试重定向到一个与您已配置的域名 (<code>{{domain}}</code>) 不匹配的域名。您确定要继续吗?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{domain}}</code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "取消",
|
"cancelTitle": "Cancel",
|
||||||
"forgotPasswordTitle": "忘记密码?",
|
"forgotPasswordTitle": "Forgot your password?",
|
||||||
"failedToFetchProvidersTitle": "加载身份验证提供程序失败,请检查您的配置。",
|
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||||
"errorTitle": "发生了错误",
|
"errorTitle": "An error occurred",
|
||||||
"errorSubtitle": "执行此操作时发生错误,请检查控制台以获取更多信息。"
|
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information."
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,6 @@
|
|||||||
"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 && (
|
||||||
<p className="text-center text-red-600 max-w-sm">
|
<h3 className="text-center text-xl text-red-600">
|
||||||
{t("failedToFetchProvidersTitle")}
|
{t("failedToFetchProvidersTitle")}
|
||||||
</p>
|
</h3>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -17,9 +17,8 @@ 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 && !ip) {
|
if (!username) {
|
||||||
return <Navigate to="/" />;
|
return <Navigate to="/" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,10 +41,6 @@ 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>
|
||||||
@@ -60,7 +55,6 @@ export const UnauthorizedPage = () => {
|
|||||||
values={{
|
values={{
|
||||||
username,
|
username,
|
||||||
resource,
|
resource,
|
||||||
ip,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
|||||||
11
go.mod
11
go.mod
@@ -4,29 +4,27 @@ go 1.23.2
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.10.1
|
github.com/gin-gonic/gin v1.10.1
|
||||||
github.com/go-playground/validator/v10 v10.27.0
|
github.com/go-playground/validator/v10 v10.26.0
|
||||||
github.com/google/go-querystring v1.1.0
|
github.com/google/go-querystring v1.1.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/mdp/qrterminal/v3 v3.2.1
|
github.com/mdp/qrterminal/v3 v3.2.1
|
||||||
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-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
github.com/go-viper/mapstructure/v2 v2.2.1 // 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
|
||||||
|
github.com/traefik/paerser v0.2.2 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
|
||||||
@@ -53,7 +51,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.3.1+incompatible
|
github.com/docker/docker v28.2.2+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
|
||||||
@@ -62,7 +60,6 @@ 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
|
||||||
|
|||||||
34
go.sum
34
go.sum
@@ -1,13 +1,9 @@
|
|||||||
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=
|
||||||
@@ -72,8 +68,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.3.1+incompatible h1:20+BmuA9FXlCX4ByQ0vYJcUEnOmRM6XljDnFWR+jCyY=
|
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
|
||||||
github.com/docker/docker v28.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
github.com/docker/go-connections v0.5.0 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=
|
||||||
@@ -94,10 +90,6 @@ 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=
|
||||||
@@ -109,10 +101,10 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
|||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
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.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||||
github.com/go-playground/validator/v10 v10.27.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.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
|
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||||
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.2.1/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=
|
||||||
@@ -134,22 +126,8 @@ 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=
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package server
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -15,13 +15,20 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
func NewAPI(config types.APIConfig, handlers *handlers.Handlers) *API {
|
||||||
Config types.ServerConfig
|
return &API{
|
||||||
Handlers *handlers.Handlers
|
Config: config,
|
||||||
Router *gin.Engine
|
Handlers: handlers,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(config types.ServerConfig, handlers *handlers.Handlers) (*Server, error) {
|
type API struct {
|
||||||
|
Config types.APIConfig
|
||||||
|
Router *gin.Engine
|
||||||
|
Handlers *handlers.Handlers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) Init() {
|
||||||
// Disable gin logs
|
// Disable gin logs
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
@@ -35,7 +42,7 @@ func NewServer(config types.ServerConfig, handlers *handlers.Handlers) (*Server,
|
|||||||
dist, err := fs.Sub(assets.Assets, "dist")
|
dist, err := fs.Sub(assets.Assets, "dist")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.Fatal().Err(err).Msg("Failed to get UI assets")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create file server
|
// Create file server
|
||||||
@@ -62,38 +69,41 @@ func NewServer(config types.ServerConfig, handlers *handlers.Handlers) (*Server,
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Proxy routes
|
// Set router
|
||||||
router.GET("/api/auth/:proxy", handlers.AuthHandler)
|
api.Router = router
|
||||||
|
|
||||||
// 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 (s *Server) Start() error {
|
func (api *API) SetupRoutes() {
|
||||||
// Run server
|
// Proxy
|
||||||
log.Info().Str("address", s.Config.Address).Int("port", s.Config.Port).Msg("Starting server")
|
api.Router.GET("/api/auth/:proxy", api.Handlers.AuthHandler)
|
||||||
|
|
||||||
return s.Router.Run(fmt.Sprintf("%s:%d", s.Config.Address, s.Config.Port))
|
// 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
|
||||||
|
err := api.Router.Run(fmt.Sprintf("%s:%d", api.Config.Address, api.Config.Port))
|
||||||
|
|
||||||
|
// Check for errors
|
||||||
|
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
|
||||||
322
internal/api/api_test.go
Normal file
322
internal/api/api_test.go
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
package api_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"tinyauth/internal/api"
|
||||||
|
"tinyauth/internal/auth"
|
||||||
|
"tinyauth/internal/docker"
|
||||||
|
"tinyauth/internal/handlers"
|
||||||
|
"tinyauth/internal/hooks"
|
||||||
|
"tinyauth/internal/providers"
|
||||||
|
"tinyauth/internal/types"
|
||||||
|
|
||||||
|
"github.com/magiconair/properties/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Simple API config for tests
|
||||||
|
var apiConfig = types.APIConfig{
|
||||||
|
Port: 8080,
|
||||||
|
Address: "0.0.0.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple handlers config for tests
|
||||||
|
var handlersConfig = types.HandlersConfig{
|
||||||
|
AppURL: "http://localhost:8080",
|
||||||
|
Domain: "localhost",
|
||||||
|
DisableContinue: false,
|
||||||
|
CookieSecure: false,
|
||||||
|
Title: "Tinyauth",
|
||||||
|
GenericName: "Generic",
|
||||||
|
ForgotPasswordMessage: "Some message",
|
||||||
|
CsrfCookieName: "tinyauth-csrf",
|
||||||
|
RedirectCookieName: "tinyauth-redirect",
|
||||||
|
BackgroundImage: "https://example.com/image.png",
|
||||||
|
OAuthAutoRedirect: "none",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple auth config for tests
|
||||||
|
var authConfig = types.AuthConfig{
|
||||||
|
Users: types.Users{},
|
||||||
|
OauthWhitelist: "",
|
||||||
|
Secret: "super-secret-api-thing-for-tests", // It is 32 chars long
|
||||||
|
CookieSecure: false,
|
||||||
|
SessionExpiry: 3600,
|
||||||
|
LoginTimeout: 0,
|
||||||
|
LoginMaxRetries: 0,
|
||||||
|
SessionCookieName: "tinyauth-session",
|
||||||
|
Domain: "localhost",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple hooks config for tests
|
||||||
|
var hooksConfig = types.HooksConfig{
|
||||||
|
Domain: "localhost",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cookie
|
||||||
|
var cookie string
|
||||||
|
|
||||||
|
// User
|
||||||
|
var user = types.User{
|
||||||
|
Username: "user",
|
||||||
|
Password: "$2a$10$AvGHLTYv3xiRJ0xV9xs3XeVIlkGTygI9nqIamFYB5Xu.5.0UWF7B6", // pass
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need all this to be able to test the API
|
||||||
|
func getAPI(t *testing.T) *api.API {
|
||||||
|
// Create docker service
|
||||||
|
docker := docker.NewDocker()
|
||||||
|
|
||||||
|
// Initialize docker
|
||||||
|
err := docker.Init()
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to initialize docker: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create auth service
|
||||||
|
authConfig.Users = types.Users{
|
||||||
|
{
|
||||||
|
Username: user.Username,
|
||||||
|
Password: user.Password,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
auth := auth.NewAuth(authConfig, docker)
|
||||||
|
|
||||||
|
// Create providers service
|
||||||
|
providers := providers.NewProviders(types.OAuthConfig{})
|
||||||
|
|
||||||
|
// Initialize providers
|
||||||
|
providers.Init()
|
||||||
|
|
||||||
|
// Create hooks service
|
||||||
|
hooks := hooks.NewHooks(hooksConfig, auth, providers)
|
||||||
|
|
||||||
|
// Create handlers service
|
||||||
|
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
||||||
|
|
||||||
|
// Create API
|
||||||
|
api := api.NewAPI(apiConfig, handlers)
|
||||||
|
|
||||||
|
// Setup routes
|
||||||
|
api.Init()
|
||||||
|
api.SetupRoutes()
|
||||||
|
|
||||||
|
return api
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test login (we will need this for the other tests)
|
||||||
|
func TestLogin(t *testing.T) {
|
||||||
|
t.Log("Testing login")
|
||||||
|
|
||||||
|
// Get API
|
||||||
|
api := getAPI(t)
|
||||||
|
|
||||||
|
// Create recorder
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
user := types.LoginRequest{
|
||||||
|
Username: "user",
|
||||||
|
Password: "pass",
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := json.Marshal(user)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error marshalling json: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
req, err := http.NewRequest("POST", "/api/login", strings.NewReader(string(json)))
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve the request
|
||||||
|
api.Router.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.Equal(t, recorder.Code, http.StatusOK)
|
||||||
|
|
||||||
|
// Get the cookie
|
||||||
|
cookie = recorder.Result().Cookies()[0].Value
|
||||||
|
|
||||||
|
// Check if the cookie is set
|
||||||
|
if cookie == "" {
|
||||||
|
t.Fatalf("Cookie not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test app context
|
||||||
|
func TestAppContext(t *testing.T) {
|
||||||
|
t.Log("Testing app context")
|
||||||
|
|
||||||
|
// Get API
|
||||||
|
api := getAPI(t)
|
||||||
|
|
||||||
|
// Create recorder
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
req, err := http.NewRequest("GET", "/api/app", nil)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the cookie
|
||||||
|
req.AddCookie(&http.Cookie{
|
||||||
|
Name: "tinyauth",
|
||||||
|
Value: cookie,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Serve the request
|
||||||
|
api.Router.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.Equal(t, recorder.Code, http.StatusOK)
|
||||||
|
|
||||||
|
// Read the body of the response
|
||||||
|
body, err := io.ReadAll(recorder.Body)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error getting body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal the body into the user struct
|
||||||
|
var app types.AppContext
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &app)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error unmarshalling body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create tests values
|
||||||
|
expected := types.AppContext{
|
||||||
|
Status: 200,
|
||||||
|
Message: "OK",
|
||||||
|
ConfiguredProviders: []string{"username"},
|
||||||
|
DisableContinue: false,
|
||||||
|
Title: "Tinyauth",
|
||||||
|
GenericName: "Generic",
|
||||||
|
ForgotPasswordMessage: "Some message",
|
||||||
|
BackgroundImage: "https://example.com/image.png",
|
||||||
|
OAuthAutoRedirect: "none",
|
||||||
|
Domain: "localhost",
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should get the username back
|
||||||
|
if !reflect.DeepEqual(app, expected) {
|
||||||
|
t.Fatalf("Expected %v, got %v", expected, app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test user context
|
||||||
|
func TestUserContext(t *testing.T) {
|
||||||
|
t.Log("Testing user context")
|
||||||
|
|
||||||
|
// Get API
|
||||||
|
api := getAPI(t)
|
||||||
|
|
||||||
|
// Create recorder
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
req, err := http.NewRequest("GET", "/api/user", nil)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the cookie
|
||||||
|
req.AddCookie(&http.Cookie{
|
||||||
|
Name: "tinyauth-session",
|
||||||
|
Value: cookie,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Serve the request
|
||||||
|
api.Router.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.Equal(t, recorder.Code, http.StatusOK)
|
||||||
|
|
||||||
|
// Read the body of the response
|
||||||
|
body, err := io.ReadAll(recorder.Body)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error getting body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal the body into the user struct
|
||||||
|
type User struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var user User
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &user)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error unmarshalling body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should get the username back
|
||||||
|
if user.Username != "user" {
|
||||||
|
t.Fatalf("Expected user, got %s", user.Username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test logout
|
||||||
|
func TestLogout(t *testing.T) {
|
||||||
|
t.Log("Testing logout")
|
||||||
|
|
||||||
|
// Get API
|
||||||
|
api := getAPI(t)
|
||||||
|
|
||||||
|
// Create recorder
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
req, err := http.NewRequest("POST", "/api/logout", nil)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the cookie
|
||||||
|
req.AddCookie(&http.Cookie{
|
||||||
|
Name: "tinyauth",
|
||||||
|
Value: cookie,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Serve the request
|
||||||
|
api.Router.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.Equal(t, recorder.Code, http.StatusOK)
|
||||||
|
|
||||||
|
// Check if the cookie is different (means go sessions flushed it)
|
||||||
|
if recorder.Result().Cookies()[0].Value == cookie {
|
||||||
|
t.Fatalf("Cookie not flushed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Testing for the oauth stuff
|
||||||
@@ -7,7 +7,6 @@ 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"
|
||||||
|
|
||||||
@@ -17,151 +16,52 @@ 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 NewAuth(config types.AuthConfig, docker *docker.Docker, ldap *ldap.LDAP) *Auth {
|
func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) {
|
||||||
// Create cookie store
|
// Create cookie store
|
||||||
store := sessions.NewCookieStore([]byte(config.HMACSecret), []byte(config.EncryptionSecret))
|
store := sessions.NewCookieStore([]byte(auth.Config.Secret))
|
||||||
|
|
||||||
// Configure cookie store
|
// Configure cookie store
|
||||||
store.Options = &sessions.Options{
|
store.Options = &sessions.Options{
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: config.SessionExpiry,
|
MaxAge: auth.Config.SessionExpiry,
|
||||||
Secure: config.CookieSecure,
|
Secure: auth.Config.CookieSecure,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Domain: fmt.Sprintf(".%s", config.Domain),
|
Domain: fmt.Sprintf(".%s", auth.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 := auth.Store.Get(c.Request, auth.Config.SessionCookieName)
|
session, err := store.Get(c.Request, auth.Config.SessionCookieName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msg("Invalid session, clearing cookie and retrying")
|
|
||||||
|
|
||||||
// Delete the session cookie if there is an error
|
|
||||||
c.SetCookie(auth.Config.SessionCookieName, "", -1, "/", fmt.Sprintf(".%s", auth.Config.Domain), auth.Config.CookieSecure, true)
|
|
||||||
|
|
||||||
// Try to get the session again
|
|
||||||
session, err = auth.Store.Get(c.Request, auth.Config.SessionCookieName)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// If we still can't get the session, log the error and return nil
|
|
||||||
log.Error().Err(err).Msg("Failed to get session")
|
log.Error().Err(err).Msg("Failed to get session")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) SearchUser(username string) types.UserSearch {
|
func (auth *Auth) GetUser(username string) *types.User {
|
||||||
// Loop through users and return the user if the username matches
|
// Loop through users and return the user if the username matches
|
||||||
log.Debug().Str("username", username).Msg("Searching for user")
|
|
||||||
|
|
||||||
if auth.GetLocalUser(username).Username != "" {
|
|
||||||
log.Debug().Str("username", username).Msg("Found local user")
|
|
||||||
|
|
||||||
// If user found, return a user with the username and type "local"
|
|
||||||
return types.UserSearch{
|
|
||||||
Username: username,
|
|
||||||
Type: "local",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
for _, user := range auth.Config.Users {
|
||||||
if user.Username == username {
|
if user.Username == username {
|
||||||
return user
|
return &user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
// 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 {
|
||||||
@@ -361,7 +261,7 @@ 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 || auth.LDAP != nil
|
return len(auth.Config.Users) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.Labels) bool {
|
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.Labels) bool {
|
||||||
@@ -451,44 +351,3 @@ 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{}, nil)
|
authService := auth.NewAuth(config, &docker.Docker{})
|
||||||
|
|
||||||
// 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{}, nil)
|
authService = auth.NewAuth(config, &docker.Docker{})
|
||||||
|
|
||||||
// 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{}, nil)
|
authService = auth.NewAuth(config, &docker.Docker{})
|
||||||
|
|
||||||
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{}, nil)
|
authService := auth.NewAuth(config, &docker.Docker{})
|
||||||
|
|
||||||
// Test multiple identifiers
|
// Test multiple identifiers
|
||||||
identifiers := []string{"user1", "user2", "user3"}
|
identifiers := []string{"user1", "user2", "user3"}
|
||||||
|
|||||||
@@ -11,30 +11,35 @@ 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 NewDocker() (*Docker, error) {
|
func (docker *Docker) Init() 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 nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the context
|
// Create the context
|
||||||
ctx := context.Background()
|
docker.Context = context.Background()
|
||||||
|
|
||||||
// Negotiate API version
|
// Negotiate API version
|
||||||
client.NegotiateAPIVersion(ctx)
|
client.NegotiateAPIVersion(docker.Context)
|
||||||
|
|
||||||
return &Docker{
|
// Set client
|
||||||
Client: client,
|
docker.Client = client
|
||||||
Context: ctx,
|
|
||||||
}, nil
|
// Done
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (docker *Docker) GetContainers() ([]container.Summary, error) {
|
func (docker *Docker) GetContainers() ([]container.Summary, error) {
|
||||||
@@ -69,7 +74,7 @@ func (docker *Docker) DockerConnected() bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (docker *Docker) GetLabels(id string, domain string) (types.Labels, error) {
|
func (docker *Docker) GetLabels(appId 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()
|
||||||
|
|
||||||
@@ -80,16 +85,15 @@ func (docker *Docker) GetLabels(id string, domain string) (types.Labels, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
log.Error().Err(err).Msg("Error getting containers")
|
|
||||||
return types.Labels{}, err
|
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
|
||||||
@@ -101,22 +105,28 @@ func (docker *Docker) GetLabels(id string, domain string) (types.Labels, error)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the labels
|
// Get the container name (for some reason it is /name)
|
||||||
log.Debug().Str("id", inspect.ID).Msg("Getting labels for container")
|
containerName := strings.TrimPrefix(inspect.Name, "/")
|
||||||
|
|
||||||
|
// There is a container with the same name as the app ID
|
||||||
|
if containerName == appId {
|
||||||
|
log.Debug().Str("container", containerName).Msg("Found container")
|
||||||
|
|
||||||
|
// Get only the tinyauth labels in a struct
|
||||||
labels, err := utils.GetLabels(inspect.Config.Labels)
|
labels, err := utils.GetLabels(inspect.Config.Labels)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Str("id", container.ID).Err(err).Msg("Error getting container labels, skipping")
|
log.Error().Err(err).Msg("Error parsing labels")
|
||||||
continue
|
return types.Labels{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the labels match the id or the domain
|
log.Debug().Msg("Got labels")
|
||||||
if strings.TrimPrefix(inspect.Name, "/") == id || labels.Domain == domain {
|
|
||||||
log.Debug().Str("id", inspect.ID).Msg("Found matching container")
|
// Return labels
|
||||||
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")
|
||||||
|
|||||||
@@ -18,14 +18,6 @@ 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,
|
||||||
@@ -36,6 +28,14 @@ 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,14 +69,11 @@ 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")
|
||||||
|
|
||||||
// Remove the port from the host if it exists
|
// Get the app id
|
||||||
hostPortless := strings.Split(host, ":")[0] // *lol*
|
appId := strings.Split(host, ".")[0]
|
||||||
|
|
||||||
// Get the id
|
|
||||||
id := strings.Split(hostPortless, ".")[0]
|
|
||||||
|
|
||||||
// Get the container labels
|
// Get the container labels
|
||||||
labels, err := h.Docker.GetLabels(id, hostPortless)
|
labels, err := h.Docker.GetLabels(appId)
|
||||||
|
|
||||||
log.Debug().Interface("labels", labels).Msg("Got labels")
|
log.Debug().Interface("labels", labels).Msg("Got labels")
|
||||||
|
|
||||||
@@ -96,38 +93,6 @@ 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)
|
||||||
|
|
||||||
@@ -151,12 +116,8 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
if !authEnabled {
|
if !authEnabled {
|
||||||
headersParsed := utils.ParseHeaders(labels.Headers)
|
headersParsed := utils.ParseHeaders(labels.Headers)
|
||||||
for key, value := range headersParsed {
|
for key, value := range headersParsed {
|
||||||
log.Debug().Str("key", key).Msg("Setting header")
|
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
||||||
c.Header(key, value)
|
c.Header(key, utils.SanitizeHeader(value))
|
||||||
}
|
|
||||||
if labels.Basic.Username != "" && utils.GetSecret(labels.Basic.Password.Plain, labels.Basic.Password.File) != "" {
|
|
||||||
log.Debug().Str("username", labels.Basic.Username).Msg("Setting basic auth headers")
|
|
||||||
c.Header("Authorization", fmt.Sprintf("Basic %s", utils.GetBasicAuth(labels.Basic.Username, utils.GetSecret(labels.Basic.Password.Plain, labels.Basic.Password.File))))
|
|
||||||
}
|
}
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
@@ -278,14 +239,8 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
// Set the rest of the headers
|
// Set the rest of the headers
|
||||||
parsedHeaders := utils.ParseHeaders(labels.Headers)
|
parsedHeaders := utils.ParseHeaders(labels.Headers)
|
||||||
for key, value := range parsedHeaders {
|
for key, value := range parsedHeaders {
|
||||||
log.Debug().Str("key", key).Msg("Setting header")
|
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
||||||
c.Header(key, value)
|
c.Header(key, utils.SanitizeHeader(value))
|
||||||
}
|
|
||||||
|
|
||||||
// Set basic auth headers if configured
|
|
||||||
if labels.Basic.Username != "" && utils.GetSecret(labels.Basic.Password.Plain, labels.Basic.Password.File) != "" {
|
|
||||||
log.Debug().Str("username", labels.Basic.Username).Msg("Setting basic auth headers")
|
|
||||||
c.Header("Authorization", fmt.Sprintf("Basic %s", utils.GetBasicAuth(labels.Basic.Username, utils.GetSecret(labels.Basic.Password.Plain, labels.Basic.Password.File))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The user is allowed to access the app
|
// The user is allowed to access the app
|
||||||
@@ -362,13 +317,11 @@ func (h *Handlers) LoginHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for a user based on username
|
// Get user based on username
|
||||||
userSearch := h.Auth.SearchUser(login.Username)
|
user := h.Auth.GetUser(login.Username)
|
||||||
|
|
||||||
log.Debug().Interface("userSearch", userSearch).Msg("Searching for user")
|
|
||||||
|
|
||||||
// User does not exist
|
// User does not exist
|
||||||
if userSearch.Type == "" {
|
if user == nil {
|
||||||
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)
|
||||||
@@ -382,7 +335,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.VerifyUser(userSearch, login.Password) {
|
if !h.Auth.CheckPassword(*user, 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)
|
||||||
@@ -398,13 +351,8 @@ 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 is using TOTP
|
// Check if user has totp enabled
|
||||||
if userSearch.Type == "local" {
|
if user.TotpSecret != "" {
|
||||||
// Get local user
|
|
||||||
localUser := h.Auth.GetLocalUser(login.Username)
|
|
||||||
|
|
||||||
// Check if TOTP is enabled
|
|
||||||
if localUser.TotpSecret != "" {
|
|
||||||
log.Debug().Msg("Totp enabled")
|
log.Debug().Msg("Totp enabled")
|
||||||
|
|
||||||
// Set totp pending cookie
|
// Set totp pending cookie
|
||||||
@@ -426,7 +374,6 @@ func (h *Handlers) LoginHandler(c *gin.Context) {
|
|||||||
// Stop further processing
|
// Stop further processing
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Create session cookie with username as provider
|
// Create session cookie with username as provider
|
||||||
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
|
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
|
||||||
@@ -477,7 +424,17 @@ func (h *Handlers) TotpHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get user
|
// Get user
|
||||||
user := h.Auth.GetLocalUser(userContext.Username)
|
user := h.Auth.GetUser(userContext.Username)
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
if user == nil {
|
||||||
|
log.Debug().Msg("User not found")
|
||||||
|
c.JSON(401, gin.H{
|
||||||
|
"status": 401,
|
||||||
|
"message": "Unauthorized",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 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,12 +12,6 @@ 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,
|
||||||
@@ -26,6 +20,12 @@ 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,40 +35,20 @@ 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")
|
||||||
|
|
||||||
// Search for a user based on username
|
// Get user
|
||||||
userSearch := hooks.Auth.SearchUser(basic.Username)
|
user := hooks.Auth.GetUser(basic.Username)
|
||||||
|
|
||||||
if userSearch.Type == "" {
|
// Check we have a user
|
||||||
|
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{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the user
|
// Check if the user has a correct password
|
||||||
if !hooks.Auth.VerifyUser(userSearch, basic.Password) {
|
if hooks.Auth.CheckPassword(*user, basic.Password) {
|
||||||
log.Error().Str("username", basic.Username).Msg("Password incorrect")
|
// Return user context since we are logged in with basic auth
|
||||||
|
|
||||||
// Return empty context
|
|
||||||
return types.UserContext{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the user type
|
|
||||||
if userSearch.Type == "ldap" {
|
|
||||||
log.Debug().Msg("User is LDAP")
|
|
||||||
|
|
||||||
return types.UserContext{
|
|
||||||
Username: basic.Username,
|
|
||||||
Name: utils.Capitalize(basic.Username),
|
|
||||||
Email: fmt.Sprintf("%s@%s", strings.ToLower(basic.Username), hooks.Config.Domain),
|
|
||||||
IsLoggedIn: true,
|
|
||||||
Provider: "basic",
|
|
||||||
TotpEnabled: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user := hooks.Auth.GetLocalUser(basic.Username)
|
|
||||||
|
|
||||||
return types.UserContext{
|
return types.UserContext{
|
||||||
Username: basic.Username,
|
Username: basic.Username,
|
||||||
Name: utils.Capitalize(basic.Username),
|
Name: utils.Capitalize(basic.Username),
|
||||||
@@ -77,6 +57,7 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
|
|||||||
Provider: "basic",
|
Provider: "basic",
|
||||||
TotpEnabled: user.TotpSecret != "",
|
TotpEnabled: user.TotpSecret != "",
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,17 +85,9 @@ 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")
|
||||||
|
|
||||||
// Search for the user with the username
|
// Check if user exists
|
||||||
userSearch := hooks.Auth.SearchUser(cookie.Username)
|
if hooks.Auth.GetUser(cookie.Username) != nil {
|
||||||
|
log.Debug().Msg("User exists")
|
||||||
if userSearch.Type == "" {
|
|
||||||
log.Error().Str("username", cookie.Username).Msg("User does not exist")
|
|
||||||
|
|
||||||
// Return empty context
|
|
||||||
return types.UserContext{}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Str("type", userSearch.Type).Msg("User exists")
|
|
||||||
|
|
||||||
// It exists so we are logged in
|
// It exists so we are logged in
|
||||||
return types.UserContext{
|
return types.UserContext{
|
||||||
@@ -125,6 +98,7 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
|
|||||||
Provider: "username",
|
Provider: "username",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Provider is not username")
|
log.Debug().Msg("Provider is not username")
|
||||||
|
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
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,24 +10,32 @@ 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 NewOAuth(config oauth2.Config, insecureSkipVerify bool) *OAuth {
|
func (oauth *OAuth) Init() {
|
||||||
// Create transport with TLS
|
// Create transport with TLS
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
InsecureSkipVerify: insecureSkipVerify,
|
InsecureSkipVerify: oauth.InsecureSkipVerify,
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new context
|
// Create a new context
|
||||||
ctx := context.Background()
|
oauth.Context = context.Background()
|
||||||
|
|
||||||
// Create the HTTP client with the transport
|
// Create the HTTP client with the transport
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
@@ -35,16 +43,9 @@ func NewOAuth(config oauth2.Config, insecureSkipVerify bool) *OAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the HTTP client in the context
|
// Set the HTTP client in the context
|
||||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
|
oauth.Context = context.WithValue(oauth.Context, oauth2.HTTPClient, httpClient)
|
||||||
|
|
||||||
// Create the verifier
|
// Create the verifier
|
||||||
verifier := oauth2.GenerateVerifier()
|
oauth.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,6 +11,12 @@ 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
|
||||||
@@ -18,57 +24,60 @@ type Providers struct {
|
|||||||
Generic *oauth.OAuth
|
Generic *oauth.OAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProviders(config types.OAuthConfig) *Providers {
|
func (providers *Providers) Init() {
|
||||||
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 config.GithubClientId != "" && config.GithubClientSecret != "" {
|
if providers.Config.GithubClientId != "" && providers.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: config.GithubClientId,
|
ClientID: providers.Config.GithubClientId,
|
||||||
ClientSecret: config.GithubClientSecret,
|
ClientSecret: providers.Config.GithubClientSecret,
|
||||||
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/github", config.AppURL),
|
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/github", providers.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 config.GoogleClientId != "" && config.GoogleClientSecret != "" {
|
if providers.Config.GoogleClientId != "" && providers.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: config.GoogleClientId,
|
ClientID: providers.Config.GoogleClientId,
|
||||||
ClientSecret: config.GoogleClientSecret,
|
ClientSecret: providers.Config.GoogleClientSecret,
|
||||||
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/google", config.AppURL),
|
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/google", providers.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 config.GenericClientId != "" && config.GenericClientSecret != "" {
|
if providers.Config.GenericClientId != "" && providers.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: config.GenericClientId,
|
ClientID: providers.Config.GenericClientId,
|
||||||
ClientSecret: config.GenericClientSecret,
|
ClientSecret: providers.Config.GenericClientSecret,
|
||||||
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/generic", config.AppURL),
|
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/generic", providers.Config.AppURL),
|
||||||
Scopes: config.GenericScopes,
|
Scopes: providers.Config.GenericScopes,
|
||||||
Endpoint: oauth2.Endpoint{
|
Endpoint: oauth2.Endpoint{
|
||||||
AuthURL: config.GenericAuthURL,
|
AuthURL: providers.Config.GenericAuthURL,
|
||||||
TokenURL: config.GenericTokenURL,
|
TokenURL: providers.Config.GenericTokenURL,
|
||||||
},
|
},
|
||||||
}, config.GenericSkipSSL)
|
}, providers.Config.GenericSkipSSL)
|
||||||
}
|
|
||||||
|
|
||||||
return providers
|
// Initialize the oauth provider
|
||||||
|
providers.Generic.Init()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (providers *Providers) GetProvider(provider string) *oauth.OAuth {
|
func (providers *Providers) GetProvider(provider string) *oauth.OAuth {
|
||||||
|
|||||||
@@ -1,521 +0,0 @@
|
|||||||
package server_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
"tinyauth/internal/auth"
|
|
||||||
"tinyauth/internal/docker"
|
|
||||||
"tinyauth/internal/handlers"
|
|
||||||
"tinyauth/internal/hooks"
|
|
||||||
"tinyauth/internal/providers"
|
|
||||||
"tinyauth/internal/server"
|
|
||||||
"tinyauth/internal/types"
|
|
||||||
|
|
||||||
"github.com/magiconair/properties/assert"
|
|
||||||
"github.com/pquerna/otp/totp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Simple server config for tests
|
|
||||||
var serverConfig = types.ServerConfig{
|
|
||||||
Port: 8080,
|
|
||||||
Address: "0.0.0.0",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple handlers config for tests
|
|
||||||
var handlersConfig = types.HandlersConfig{
|
|
||||||
AppURL: "http://localhost:8080",
|
|
||||||
Domain: "localhost",
|
|
||||||
DisableContinue: false,
|
|
||||||
CookieSecure: false,
|
|
||||||
Title: "Tinyauth",
|
|
||||||
GenericName: "Generic",
|
|
||||||
ForgotPasswordMessage: "Message",
|
|
||||||
CsrfCookieName: "tinyauth-csrf",
|
|
||||||
RedirectCookieName: "tinyauth-redirect",
|
|
||||||
BackgroundImage: "https://example.com/image.png",
|
|
||||||
OAuthAutoRedirect: "none",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple auth config for tests
|
|
||||||
var authConfig = types.AuthConfig{
|
|
||||||
Users: types.Users{},
|
|
||||||
OauthWhitelist: "",
|
|
||||||
HMACSecret: "4bZ9K.*:;zH=,9zG!meUxu.B5-S[7.V.", // Complex on purpose
|
|
||||||
EncryptionSecret: "\\:!R(u[Sbv6ZLm.7es)H|OqH4y}0u\\rj",
|
|
||||||
CookieSecure: false,
|
|
||||||
SessionExpiry: 3600,
|
|
||||||
LoginTimeout: 0,
|
|
||||||
LoginMaxRetries: 0,
|
|
||||||
SessionCookieName: "tinyauth-session",
|
|
||||||
Domain: "localhost",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple hooks config for tests
|
|
||||||
var hooksConfig = types.HooksConfig{
|
|
||||||
Domain: "localhost",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cookie
|
|
||||||
var cookie = "MTc1MTkyMzM5MnxiME9aTzlGQjZMNEJMdDZMc0lHMk9zcXQyME9SR1ZnUmlaYWZNcWplek5vcVNpdkdHRTZqb09YWkVUYUN6NEt4MkEyOGEyX2hFQWZEUEYtbllDX0h5eDBCb3VyT2phQlRpZWFfRFdTMGw2WUg2VWw4RGdNbEhQclotOUJjblJGaWFQcmhyaWFna0dXRWNud2c1akg5eEpLZ3JzS0pfWktscVZyckZFR1VDX0R5QjFOT0hzMTNKb18ySEMxZlluSWNxa1ByM0VhSzNyMkRtdDNORWJXVGFYSnMzWjFGa0lrZlhSTWduRmttMHhQUXN4UFhNbHFXY0lBWjBnUWpKU0xXMHRubjlKbjV0LXBGdjk0MmpJX0xMX1ZYblVJVW9LWUJoWmpNanVXNkNjamhYWlR2V29rY0RNYWkxY2lMQnpqLUI2cHMyYTZkWWgtWnlFdGN0amh2WURUeUNGT3ZLS1FJVUFIb0NWR1RPMlRtY2c9PXwerwFtb9urOXnwA02qXbLeorMloaK_paQd0in4BAesmg=="
|
|
||||||
|
|
||||||
// User
|
|
||||||
var user = types.User{
|
|
||||||
Username: "user",
|
|
||||||
Password: "$2a$10$AvGHLTYv3xiRJ0xV9xs3XeVIlkGTygI9nqIamFYB5Xu.5.0UWF7B6", // pass
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the server for tests
|
|
||||||
func getServer(t *testing.T) *server.Server {
|
|
||||||
// Create docker service
|
|
||||||
docker, err := docker.NewDocker()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to initialize docker: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create auth service
|
|
||||||
authConfig.Users = types.Users{
|
|
||||||
{
|
|
||||||
Username: user.Username,
|
|
||||||
Password: user.Password,
|
|
||||||
TotpSecret: user.TotpSecret,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
auth := auth.NewAuth(authConfig, docker, nil)
|
|
||||||
|
|
||||||
// Create providers service
|
|
||||||
providers := providers.NewProviders(types.OAuthConfig{})
|
|
||||||
|
|
||||||
// Create hooks service
|
|
||||||
hooks := hooks.NewHooks(hooksConfig, auth, providers)
|
|
||||||
|
|
||||||
// Create handlers service
|
|
||||||
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
|
||||||
|
|
||||||
// Create server
|
|
||||||
srv, err := server.NewServer(serverConfig, handlers)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create server: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the server
|
|
||||||
return srv
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test login
|
|
||||||
func TestLogin(t *testing.T) {
|
|
||||||
t.Log("Testing login")
|
|
||||||
|
|
||||||
// Get server
|
|
||||||
srv := getServer(t)
|
|
||||||
|
|
||||||
// Create recorder
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
|
|
||||||
// Create request
|
|
||||||
user := types.LoginRequest{
|
|
||||||
Username: "user",
|
|
||||||
Password: "pass",
|
|
||||||
}
|
|
||||||
|
|
||||||
json, err := json.Marshal(user)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error marshalling json: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create request
|
|
||||||
req, err := http.NewRequest("POST", "/api/login", strings.NewReader(string(json)))
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error creating request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve the request
|
|
||||||
srv.Router.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assert.Equal(t, recorder.Code, http.StatusOK)
|
|
||||||
|
|
||||||
// Get the result cookie
|
|
||||||
cookies := recorder.Result().Cookies()
|
|
||||||
|
|
||||||
// Check if the cookie is set
|
|
||||||
if len(cookies) == 0 {
|
|
||||||
t.Fatalf("Cookie not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the cookie for further tests
|
|
||||||
cookie = cookies[0].Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test app context
|
|
||||||
func TestAppContext(t *testing.T) {
|
|
||||||
t.Log("Testing app context")
|
|
||||||
|
|
||||||
// Get server
|
|
||||||
srv := getServer(t)
|
|
||||||
|
|
||||||
// Create recorder
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
|
|
||||||
// Create request
|
|
||||||
req, err := http.NewRequest("GET", "/api/app", nil)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error creating request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the cookie
|
|
||||||
req.AddCookie(&http.Cookie{
|
|
||||||
Name: "tinyauth",
|
|
||||||
Value: cookie,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Serve the request
|
|
||||||
srv.Router.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assert.Equal(t, recorder.Code, http.StatusOK)
|
|
||||||
|
|
||||||
// Read the body of the response
|
|
||||||
body, err := io.ReadAll(recorder.Body)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error getting body: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal the body into the user struct
|
|
||||||
var app types.AppContext
|
|
||||||
|
|
||||||
err = json.Unmarshal(body, &app)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error unmarshalling body: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create tests values
|
|
||||||
expected := types.AppContext{
|
|
||||||
Status: 200,
|
|
||||||
Message: "OK",
|
|
||||||
ConfiguredProviders: []string{"username"},
|
|
||||||
DisableContinue: false,
|
|
||||||
Title: "Tinyauth",
|
|
||||||
GenericName: "Generic",
|
|
||||||
ForgotPasswordMessage: "Message",
|
|
||||||
BackgroundImage: "https://example.com/image.png",
|
|
||||||
OAuthAutoRedirect: "none",
|
|
||||||
Domain: "localhost",
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should get the username back
|
|
||||||
if !reflect.DeepEqual(app, expected) {
|
|
||||||
t.Fatalf("Expected %v, got %v", expected, app)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test user context
|
|
||||||
func TestUserContext(t *testing.T) {
|
|
||||||
// Refresh the cookie
|
|
||||||
TestLogin(t)
|
|
||||||
|
|
||||||
t.Log("Testing user context")
|
|
||||||
|
|
||||||
// Get server
|
|
||||||
srv := getServer(t)
|
|
||||||
|
|
||||||
// Create recorder
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
|
|
||||||
// Create request
|
|
||||||
req, err := http.NewRequest("GET", "/api/user", nil)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error creating request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the cookie
|
|
||||||
req.AddCookie(&http.Cookie{
|
|
||||||
Name: "tinyauth-session",
|
|
||||||
Value: cookie,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Serve the request
|
|
||||||
srv.Router.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assert.Equal(t, recorder.Code, http.StatusOK)
|
|
||||||
|
|
||||||
// Read the body of the response
|
|
||||||
body, err := io.ReadAll(recorder.Body)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error getting body: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal the body into the user struct
|
|
||||||
type User struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var user User
|
|
||||||
|
|
||||||
err = json.Unmarshal(body, &user)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error unmarshalling body: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should get the username back
|
|
||||||
if user.Username != "user" {
|
|
||||||
t.Fatalf("Expected user, got %s", user.Username)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test logout
|
|
||||||
func TestLogout(t *testing.T) {
|
|
||||||
// Refresh the cookie
|
|
||||||
TestLogin(t)
|
|
||||||
|
|
||||||
t.Log("Testing logout")
|
|
||||||
|
|
||||||
// Get server
|
|
||||||
srv := getServer(t)
|
|
||||||
|
|
||||||
// Create recorder
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
|
|
||||||
// Create request
|
|
||||||
req, err := http.NewRequest("POST", "/api/logout", nil)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error creating request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the cookie
|
|
||||||
req.AddCookie(&http.Cookie{
|
|
||||||
Name: "tinyauth-session",
|
|
||||||
Value: cookie,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Serve the request
|
|
||||||
srv.Router.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assert.Equal(t, recorder.Code, http.StatusOK)
|
|
||||||
|
|
||||||
// Check if the cookie is different (means the cookie is gone)
|
|
||||||
if recorder.Result().Cookies()[0].Value == cookie {
|
|
||||||
t.Fatalf("Cookie not flushed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test auth endpoint
|
|
||||||
func TestAuth(t *testing.T) {
|
|
||||||
// Refresh the cookie
|
|
||||||
TestLogin(t)
|
|
||||||
|
|
||||||
t.Log("Testing auth endpoint")
|
|
||||||
|
|
||||||
// Get server
|
|
||||||
srv := getServer(t)
|
|
||||||
|
|
||||||
// Create recorder
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
|
|
||||||
// Create request
|
|
||||||
req, err := http.NewRequest("GET", "/api/auth/traefik", nil)
|
|
||||||
|
|
||||||
// Set the accept header
|
|
||||||
req.Header.Set("Accept", "text/html")
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error creating request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve the request
|
|
||||||
srv.Router.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assert.Equal(t, recorder.Code, http.StatusTemporaryRedirect)
|
|
||||||
|
|
||||||
// Recreate recorder
|
|
||||||
recorder = httptest.NewRecorder()
|
|
||||||
|
|
||||||
// Recreate the request
|
|
||||||
req, err = http.NewRequest("GET", "/api/auth/traefik", nil)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error creating request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with the cookie
|
|
||||||
req.AddCookie(&http.Cookie{
|
|
||||||
Name: "tinyauth-session",
|
|
||||||
Value: cookie,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Serve the request again
|
|
||||||
srv.Router.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assert.Equal(t, recorder.Code, http.StatusOK)
|
|
||||||
|
|
||||||
// Recreate recorder
|
|
||||||
recorder = httptest.NewRecorder()
|
|
||||||
|
|
||||||
// Recreate the request
|
|
||||||
req, err = http.NewRequest("GET", "/api/auth/nginx", nil)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error creating request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve the request again
|
|
||||||
srv.Router.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assert.Equal(t, recorder.Code, http.StatusUnauthorized)
|
|
||||||
|
|
||||||
// Recreate recorder
|
|
||||||
recorder = httptest.NewRecorder()
|
|
||||||
|
|
||||||
// Recreate the request
|
|
||||||
req, err = http.NewRequest("GET", "/api/auth/nginx", nil)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error creating request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with the cookie
|
|
||||||
req.AddCookie(&http.Cookie{
|
|
||||||
Name: "tinyauth-session",
|
|
||||||
Value: cookie,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Serve the request again
|
|
||||||
srv.Router.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assert.Equal(t, recorder.Code, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTOTP(t *testing.T) {
|
|
||||||
t.Log("Testing TOTP")
|
|
||||||
|
|
||||||
// Generate totp secret
|
|
||||||
key, err := totp.Generate(totp.GenerateOpts{
|
|
||||||
Issuer: "Tinyauth",
|
|
||||||
AccountName: user.Username,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to generate TOTP secret: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create secret
|
|
||||||
secret := key.Secret()
|
|
||||||
|
|
||||||
// Set the user's TOTP secret
|
|
||||||
user.TotpSecret = secret
|
|
||||||
|
|
||||||
// Get server
|
|
||||||
srv := getServer(t)
|
|
||||||
|
|
||||||
// Create request
|
|
||||||
user := types.LoginRequest{
|
|
||||||
Username: "user",
|
|
||||||
Password: "pass",
|
|
||||||
}
|
|
||||||
|
|
||||||
loginJson, err := json.Marshal(user)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error marshalling json: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create recorder
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
|
|
||||||
// Create request
|
|
||||||
req, err := http.NewRequest("POST", "/api/login", strings.NewReader(string(loginJson)))
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error creating request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve the request
|
|
||||||
srv.Router.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assert.Equal(t, recorder.Code, http.StatusOK)
|
|
||||||
|
|
||||||
// Set the cookie for next test
|
|
||||||
cookie = recorder.Result().Cookies()[0].Value
|
|
||||||
|
|
||||||
// Create TOTP code
|
|
||||||
code, err := totp.GenerateCode(secret, time.Now())
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to generate TOTP code: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create TOTP request
|
|
||||||
totpRequest := types.TotpRequest{
|
|
||||||
Code: code,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal the TOTP request
|
|
||||||
totpJson, err := json.Marshal(totpRequest)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error marshalling TOTP request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create recorder
|
|
||||||
recorder = httptest.NewRecorder()
|
|
||||||
|
|
||||||
// Create request
|
|
||||||
req, err = http.NewRequest("POST", "/api/totp", strings.NewReader(string(totpJson)))
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error creating request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the cookie
|
|
||||||
req.AddCookie(&http.Cookie{
|
|
||||||
Name: "tinyauth-session",
|
|
||||||
Value: cookie,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Serve the request
|
|
||||||
srv.Router.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assert.Equal(t, recorder.Code, http.StatusOK)
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,6 @@ 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,12 +36,6 @@ 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
|
||||||
@@ -75,8 +69,8 @@ type OAuthConfig struct {
|
|||||||
AppURL string
|
AppURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerConfig is the configuration for the server
|
// APIConfig is the configuration for the API
|
||||||
type ServerConfig struct {
|
type APIConfig struct {
|
||||||
Port int
|
Port int
|
||||||
Address string
|
Address string
|
||||||
}
|
}
|
||||||
@@ -86,13 +80,12 @@ 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
|
||||||
@@ -106,41 +99,10 @@ type OAuthLabels struct {
|
|||||||
Groups string
|
Groups string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic auth labels for a tinyauth protected container
|
|
||||||
type BasicLabels struct {
|
|
||||||
Username string
|
|
||||||
Password PassowrdLabels
|
|
||||||
}
|
|
||||||
|
|
||||||
// PassowrdLabels is a struct that contains the password labels for a tinyauth protected container
|
|
||||||
type PassowrdLabels struct {
|
|
||||||
Plain string
|
|
||||||
File string
|
|
||||||
}
|
|
||||||
|
|
||||||
// IP labels for a tinyauth protected container
|
|
||||||
type IPLabels struct {
|
|
||||||
Allow []string
|
|
||||||
Block []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Labels is a struct that contains the labels for a tinyauth protected container
|
// Labels is a struct that contains the labels for a tinyauth protected container
|
||||||
type Labels struct {
|
type Labels struct {
|
||||||
Users string
|
Users string
|
||||||
Allowed string
|
Allowed string
|
||||||
Headers []string
|
Headers []string
|
||||||
Domain string
|
|
||||||
Basic BasicLabels
|
|
||||||
OAuth OAuthLabels
|
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,12 +12,6 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -14,7 +9,6 @@ import (
|
|||||||
"tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
|
|
||||||
"github.com/traefik/paerser/parser"
|
"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"
|
||||||
@@ -188,7 +182,7 @@ func ParseHeaders(headers []string) map[string]string {
|
|||||||
// Loop through the headers
|
// Loop through the headers
|
||||||
for _, header := range headers {
|
for _, header := range headers {
|
||||||
split := strings.SplitN(header, "=", 2)
|
split := strings.SplitN(header, "=", 2)
|
||||||
if len(split) != 2 || strings.TrimSpace(split[0]) == "" || strings.TrimSpace(split[1]) == "" {
|
if len(split) != 2 {
|
||||||
log.Warn().Str("header", header).Msg("Invalid header format, skipping")
|
log.Warn().Str("header", header).Msg("Invalid header format, skipping")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -207,7 +201,7 @@ func GetLabels(labels map[string]string) (types.Labels, error) {
|
|||||||
var labelsParsed types.Labels
|
var labelsParsed types.Labels
|
||||||
|
|
||||||
// Decode the labels into the labels struct
|
// 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")
|
err := parser.Decode(labels, &labelsParsed, "tinyauth", "tinyauth.users", "tinyauth.allowed", "tinyauth.headers", "tinyauth.oauth")
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -364,77 +358,3 @@ 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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -315,6 +315,25 @@ func TestGetLabels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test the filter function
|
||||||
|
func TestFilter(t *testing.T) {
|
||||||
|
t.Log("Testing filter helper")
|
||||||
|
|
||||||
|
// Create variables
|
||||||
|
data := []string{"", "val1", "", "val2", "", "val3", ""}
|
||||||
|
expected := []string{"val1", "val2", "val3"}
|
||||||
|
|
||||||
|
// Test the filter function
|
||||||
|
result := utils.Filter(data, func(val string) bool {
|
||||||
|
return val != ""
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if the result is equal to the expected
|
||||||
|
if !reflect.DeepEqual(expected, result) {
|
||||||
|
t.Fatalf("Expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test parse user
|
// Test parse user
|
||||||
func TestParseUser(t *testing.T) {
|
func TestParseUser(t *testing.T) {
|
||||||
t.Log("Testing parse user with a valid user")
|
t.Log("Testing parse user with a valid user")
|
||||||
@@ -455,6 +474,37 @@ func TestCheckWhitelist(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test capitalize
|
||||||
|
func TestCapitalize(t *testing.T) {
|
||||||
|
t.Log("Testing capitalize with a valid string")
|
||||||
|
|
||||||
|
// Create variables
|
||||||
|
str := "test"
|
||||||
|
expected := "Test"
|
||||||
|
|
||||||
|
// Test the capitalize function
|
||||||
|
result := utils.Capitalize(str)
|
||||||
|
|
||||||
|
// Check if the result is equal to the expected
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("Expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("Testing capitalize with an empty string")
|
||||||
|
|
||||||
|
// Create variables
|
||||||
|
str = ""
|
||||||
|
expected = ""
|
||||||
|
|
||||||
|
// Test the capitalize function
|
||||||
|
result = utils.Capitalize(str)
|
||||||
|
|
||||||
|
// Check if the result is equal to the expected
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("Expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test the header sanitizer
|
// Test the header sanitizer
|
||||||
func TestSanitizeHeader(t *testing.T) {
|
func TestSanitizeHeader(t *testing.T) {
|
||||||
t.Log("Testing sanitize header with a valid string")
|
t.Log("Testing sanitize header with a valid string")
|
||||||
@@ -485,170 +535,3 @@ func TestSanitizeHeader(t *testing.T) {
|
|||||||
t.Fatalf("Expected %v, got %v", expected, result)
|
t.Fatalf("Expected %v, got %v", expected, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the parse headers function
|
|
||||||
func TestParseHeaders(t *testing.T) {
|
|
||||||
t.Log("Testing parse headers with a valid string")
|
|
||||||
|
|
||||||
// Create variables
|
|
||||||
headers := []string{"X-Hea\x00der1=value1", "X-Header2=value\n2"}
|
|
||||||
expected := map[string]string{
|
|
||||||
"X-Header1": "value1",
|
|
||||||
"X-Header2": "value2",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the parse headers function
|
|
||||||
result := utils.ParseHeaders(headers)
|
|
||||||
|
|
||||||
// Check if the result is equal to the expected
|
|
||||||
if !reflect.DeepEqual(expected, result) {
|
|
||||||
t.Fatalf("Expected %v, got %v", expected, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Log("Testing parse headers with an invalid string")
|
|
||||||
|
|
||||||
// Create variables
|
|
||||||
headers = []string{"X-Header1=", "X-Header2", "=value", "X-Header3=value3"}
|
|
||||||
expected = map[string]string{"X-Header3": "value3"}
|
|
||||||
|
|
||||||
// Test the parse headers function
|
|
||||||
result = utils.ParseHeaders(headers)
|
|
||||||
|
|
||||||
// Check if the result is equal to the expected
|
|
||||||
if !reflect.DeepEqual(expected, result) {
|
|
||||||
t.Fatalf("Expected %v, got %v", expected, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the parse secret file function
|
|
||||||
func TestParseSecretFile(t *testing.T) {
|
|
||||||
t.Log("Testing parse secret file with a valid file")
|
|
||||||
|
|
||||||
// Create variables
|
|
||||||
content := "\n\n \n\n\n secret \n\n \n "
|
|
||||||
expected := "secret"
|
|
||||||
|
|
||||||
// Test the parse secret file function
|
|
||||||
result := utils.ParseSecretFile(content)
|
|
||||||
|
|
||||||
// Check if the result is equal to the expected
|
|
||||||
if result != expected {
|
|
||||||
t.Fatalf("Expected %v, got %v", expected, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the filter IP function
|
|
||||||
func TestFilterIP(t *testing.T) {
|
|
||||||
t.Log("Testing filter IP with an IP and a valid CIDR")
|
|
||||||
|
|
||||||
// Create variables
|
|
||||||
ip := "10.10.10.10"
|
|
||||||
filter := "10.10.10.0/24"
|
|
||||||
expected := true
|
|
||||||
|
|
||||||
// Test the filter IP function
|
|
||||||
result, err := utils.FilterIP(filter, ip)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error filtering IP: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the result is equal to the expected
|
|
||||||
if result != expected {
|
|
||||||
t.Fatalf("Expected %v, got %v", expected, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Log("Testing filter IP with an IP and a valid IP")
|
|
||||||
|
|
||||||
// Create variables
|
|
||||||
filter = "10.10.10.10"
|
|
||||||
expected = true
|
|
||||||
|
|
||||||
// Test the filter IP function
|
|
||||||
result, err = utils.FilterIP(filter, ip)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error filtering IP: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the result is equal to the expected
|
|
||||||
if result != expected {
|
|
||||||
t.Fatalf("Expected %v, got %v", expected, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Log("Testing filter IP with an IP and an non matching CIDR")
|
|
||||||
|
|
||||||
// Create variables
|
|
||||||
filter = "10.10.15.0/24"
|
|
||||||
expected = false
|
|
||||||
|
|
||||||
// Test the filter IP function
|
|
||||||
result, err = utils.FilterIP(filter, ip)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error filtering IP: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the result is equal to the expected
|
|
||||||
if result != expected {
|
|
||||||
t.Fatalf("Expected %v, got %v", expected, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Log("Testing filter IP with a non matching IP and a valid CIDR")
|
|
||||||
|
|
||||||
// Create variables
|
|
||||||
filter = "10.10.10.11"
|
|
||||||
expected = false
|
|
||||||
|
|
||||||
// Test the filter IP function
|
|
||||||
result, err = utils.FilterIP(filter, ip)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error filtering IP: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the result is equal to the expected
|
|
||||||
if result != expected {
|
|
||||||
t.Fatalf("Expected %v, got %v", expected, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Log("Testing filter IP with an IP and an invalid CIDR")
|
|
||||||
|
|
||||||
// Create variables
|
|
||||||
filter = "10.../83"
|
|
||||||
|
|
||||||
// Test the filter IP function
|
|
||||||
_, err = utils.FilterIP(filter, ip)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected error filtering IP")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the derive key function
|
|
||||||
func TestDeriveKey(t *testing.T) {
|
|
||||||
t.Log("Testing the derive key function")
|
|
||||||
|
|
||||||
// Create variables
|
|
||||||
master := "master"
|
|
||||||
info := "info"
|
|
||||||
expected := "gdrdU/fXzclYjiSXRexEatVgV13qQmKl"
|
|
||||||
|
|
||||||
// Test the derive key function
|
|
||||||
result, err := utils.DeriveKey(master, info)
|
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error deriving key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the result is equal to the expected
|
|
||||||
if result != expected {
|
|
||||||
t.Fatalf("Expected %v, got %v", expected, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user