Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
4b607d4ee6 chore(deps): bump the minor-patch group across 1 directory with 12 updates
Bumps the minor-patch group with 12 updates in the /frontend directory:

| Package | From | To |
| --- | --- | --- |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.1.14` | `4.1.15` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.90.3` | `5.90.5` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `0.545.0` | `0.546.0` |
| [react-i18next](https://github.com/i18next/react-i18next) | `16.0.1` | `16.1.3` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.1.14` | `4.1.15` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.37.0` | `9.38.0` |
| [@tanstack/eslint-plugin-query](https://github.com/TanStack/query/tree/HEAD/packages/eslint-plugin-query) | `5.91.0` | `5.91.2` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.7.2` | `24.9.1` |
| [eslint](https://github.com/eslint/eslint) | `9.37.0` | `9.38.0` |
| [eslint-plugin-react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh) | `0.4.23` | `0.4.24` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.46.1` | `8.46.2` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `7.1.10` | `7.1.11` |



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

Updates `@tanstack/react-query` from 5.90.3 to 5.90.5
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.90.5/packages/react-query)

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

Updates `react-i18next` from 16.0.1 to 16.1.3
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v16.0.1...v16.1.3)

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

Updates `@eslint/js` from 9.37.0 to 9.38.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.38.0/packages/js)

Updates `@tanstack/eslint-plugin-query` from 5.91.0 to 5.91.2
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/eslint-plugin-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/eslint-plugin-query@5.91.2/packages/eslint-plugin-query)

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

Updates `eslint` from 9.37.0 to 9.38.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.37.0...v9.38.0)

Updates `eslint-plugin-react-refresh` from 0.4.23 to 0.4.24
- [Release notes](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/releases)
- [Changelog](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/compare/v0.4.23...v0.4.24)

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

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

---
updated-dependencies:
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.1.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.90.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: lucide-react
  dependency-version: 0.546.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-i18next
  dependency-version: 16.1.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: tailwindcss
  dependency-version: 4.1.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@eslint/js"
  dependency-version: 9.38.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@tanstack/eslint-plugin-query"
  dependency-version: 5.91.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@types/node"
  dependency-version: 24.9.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: eslint
  dependency-version: 9.38.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: eslint-plugin-react-refresh
  dependency-version: 0.4.24
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: typescript-eslint
  dependency-version: 8.46.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: vite
  dependency-version: 7.1.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-21 08:25:20 +00:00
7 changed files with 37 additions and 148 deletions

View File

@@ -23,7 +23,7 @@
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-hook-form": "^7.65.0",
"react-i18next": "^16.1.4",
"react-i18next": "^16.1.3",
"react-markdown": "^10.1.0",
"react-router": "^7.9.4",
"sonner": "^2.0.7",
@@ -787,7 +787,7 @@
"react-hook-form": ["react-hook-form@7.65.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw=="],
"react-i18next": ["react-i18next@16.1.4", "", { "dependencies": { "@babel/runtime": "^7.27.6", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { "i18next": ">= 25.5.2", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-0UUKZDHjKnLk6dfbYXEZ9CVqLMpNiul+dHbPVQo2z2t1GkdirkeHXb/TtdsNuv+nyNOTDl1Jp6F6uwf9M3DMcg=="],
"react-i18next": ["react-i18next@16.1.3", "", { "dependencies": { "@babel/runtime": "^7.27.6", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { "i18next": ">= 25.5.2", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-LnHvU73n/GDMWSlK8WJQHEYtKSm5g3lpsrFoxyxp+sgJiI2SZg54U3lnt8zFfo9DOohaPbe5SsjquA0MuuvBqg=="],
"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=="],

View File

@@ -29,7 +29,7 @@
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-hook-form": "^7.65.0",
"react-i18next": "^16.1.4",
"react-i18next": "^16.1.3",
"react-markdown": "^10.1.0",
"react-router": "^7.9.4",
"sonner": "^2.0.7",

View File

@@ -139,14 +139,12 @@ func (app *BootstrapApp) Setup() error {
// Create services
dockerService := service.NewDockerService()
aclsService := service.NewAccessControlsService(dockerService)
authService := service.NewAuthService(authConfig, dockerService, ldapService, database)
oauthBrokerService := service.NewOAuthBrokerService(oauthProviders)
// Initialize services (order matters)
// Initialize services
services := []Service{
dockerService,
aclsService,
authService,
oauthBrokerService,
}
@@ -248,7 +246,7 @@ func (app *BootstrapApp) Setup() error {
proxyController := controller.NewProxyController(controller.ProxyControllerConfig{
AppURL: app.config.AppURL,
}, apiRouter, aclsService, authService)
}, apiRouter, dockerService, authService)
userController := controller.NewUserController(controller.UserControllerConfig{
CookieDomain: cookieDomain,

View File

@@ -24,15 +24,15 @@ type ProxyControllerConfig struct {
type ProxyController struct {
config ProxyControllerConfig
router *gin.RouterGroup
acls *service.AccessControlsService
docker *service.DockerService
auth *service.AuthService
}
func NewProxyController(config ProxyControllerConfig, router *gin.RouterGroup, acls *service.AccessControlsService, auth *service.AuthService) *ProxyController {
func NewProxyController(config ProxyControllerConfig, router *gin.RouterGroup, docker *service.DockerService, auth *service.AuthService) *ProxyController {
return &ProxyController{
config: config,
router: router,
acls: acls,
docker: docker,
auth: auth,
}
}
@@ -76,21 +76,20 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
proto := c.Request.Header.Get("X-Forwarded-Proto")
host := c.Request.Header.Get("X-Forwarded-Host")
// Get acls
acls, err := controller.acls.GetAccessControls(host)
labels, err := controller.docker.GetLabels(host)
if err != nil {
log.Error().Err(err).Msg("Failed to get access controls for resource")
log.Error().Err(err).Msg("Failed to get labels from Docker")
controller.handleError(c, req, isBrowser)
return
}
log.Trace().Interface("acls", acls).Msg("ACLs for resource")
log.Trace().Interface("labels", labels).Msg("Labels for resource")
clientIP := c.ClientIP()
if controller.auth.IsBypassedIP(acls.IP, clientIP) {
controller.setHeaders(c, acls)
if controller.auth.IsBypassedIP(labels.IP, clientIP) {
controller.setHeaders(c, labels)
c.JSON(200, gin.H{
"status": 200,
"message": "Authenticated",
@@ -98,7 +97,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}
authEnabled, err := controller.auth.IsAuthEnabled(uri, acls.Path)
authEnabled, err := controller.auth.IsAuthEnabled(uri, labels.Path)
if err != nil {
log.Error().Err(err).Msg("Failed to check if auth is enabled for resource")
@@ -108,7 +107,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
if !authEnabled {
log.Debug().Msg("Authentication disabled for resource, allowing access")
controller.setHeaders(c, acls)
controller.setHeaders(c, labels)
c.JSON(200, gin.H{
"status": 200,
"message": "Authenticated",
@@ -116,7 +115,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}
if !controller.auth.CheckIP(acls.IP, clientIP) {
if !controller.auth.CheckIP(labels.IP, clientIP) {
if req.Proxy == "nginx" || !isBrowser {
c.JSON(401, gin.H{
"status": 401,
@@ -161,7 +160,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
}
if userContext.IsLoggedIn {
appAllowed := controller.auth.IsResourceAllowed(c, userContext, acls)
appAllowed := controller.auth.IsResourceAllowed(c, userContext, labels)
if !appAllowed {
log.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User not allowed to access resource")
@@ -195,7 +194,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
}
if userContext.OAuth {
groupOK := controller.auth.IsInOAuthGroup(c, userContext, acls.OAuth.Groups)
groupOK := controller.auth.IsInOAuthGroup(c, userContext, labels.OAuth.Groups)
if !groupOK {
log.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User OAuth groups do not match resource requirements")
@@ -235,7 +234,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email))
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
controller.setHeaders(c, acls)
controller.setHeaders(c, labels)
c.JSON(200, gin.H{
"status": 200,
@@ -265,21 +264,21 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/login?%s", controller.config.AppURL, queries.Encode()))
}
func (controller *ProxyController) setHeaders(c *gin.Context, acls config.App) {
func (controller *ProxyController) setHeaders(c *gin.Context, labels config.App) {
c.Header("Authorization", c.Request.Header.Get("Authorization"))
headers := utils.ParseHeaders(acls.Response.Headers)
headers := utils.ParseHeaders(labels.Response.Headers)
for key, value := range headers {
log.Debug().Str("header", key).Msg("Setting header")
c.Header(key, value)
}
basicPassword := utils.GetSecret(acls.Response.BasicAuth.Password, acls.Response.BasicAuth.PasswordFile)
basicPassword := utils.GetSecret(labels.Response.BasicAuth.Password, labels.Response.BasicAuth.PasswordFile)
if acls.Response.BasicAuth.Username != "" && basicPassword != "" {
log.Debug().Str("username", acls.Response.BasicAuth.Username).Msg("Setting basic auth header")
c.Header("Authorization", fmt.Sprintf("Basic %s", utils.GetBasicAuth(acls.Response.BasicAuth.Username, basicPassword)))
if labels.Response.BasicAuth.Username != "" && basicPassword != "" {
log.Debug().Str("username", labels.Response.BasicAuth.Username).Msg("Setting basic auth header")
c.Header("Authorization", fmt.Sprintf("Basic %s", utils.GetBasicAuth(labels.Response.BasicAuth.Username, basicPassword)))
}
}

View File

@@ -39,11 +39,6 @@ func setupProxyController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.En
assert.NilError(t, dockerService.Init())
// Access controls
accessControlsService := service.NewAccessControlsService(dockerService)
assert.NilError(t, accessControlsService.Init())
// Auth service
authService := service.NewAuthService(service.AuthServiceConfig{
Users: []config.User{
@@ -64,7 +59,7 @@ func setupProxyController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.En
// Controller
ctrl := controller.NewProxyController(controller.ProxyControllerConfig{
AppURL: "http://localhost:8080",
}, group, accessControlsService, authService)
}, group, dockerService, authService)
ctrl.SetupRoutes()
return router, recorder, authService

View File

@@ -1,103 +0,0 @@
package service
import (
"os"
"strings"
"tinyauth/internal/config"
"tinyauth/internal/utils/decoders"
"github.com/rs/zerolog/log"
)
type AccessControlsService struct {
docker *DockerService
envACLs config.Apps
}
func NewAccessControlsService(docker *DockerService) *AccessControlsService {
return &AccessControlsService{
docker: docker,
}
}
func (acls *AccessControlsService) Init() error {
acls.envACLs = config.Apps{}
env := os.Environ()
appEnvVars := []string{}
for _, e := range env {
if strings.HasPrefix(e, "TINYAUTH_APPS_") {
appEnvVars = append(appEnvVars, e)
}
}
err := acls.loadEnvACLs(appEnvVars)
if err != nil {
return err
}
return nil
}
func (acls *AccessControlsService) loadEnvACLs(appEnvVars []string) error {
if len(appEnvVars) == 0 {
return nil
}
envAcls := map[string]string{}
for _, e := range appEnvVars {
parts := strings.SplitN(e, "=", 2)
if len(parts) != 2 {
continue
}
// Normalize key, this should use the same normalization logic as in utils/decoders/decoders.go
key := parts[0]
key = strings.ToLower(key)
key = strings.ReplaceAll(key, "_", ".")
value := parts[1]
envAcls[key] = value
}
apps, err := decoders.DecodeLabels(envAcls)
if err != nil {
return err
}
acls.envACLs = apps
return nil
}
func (acls *AccessControlsService) lookupEnvACLs(appDomain string) *config.App {
if len(acls.envACLs.Apps) == 0 {
return nil
}
for appName, appACLs := range acls.envACLs.Apps {
if appACLs.Config.Domain == appDomain {
return &appACLs
}
if strings.SplitN(appDomain, ".", 2)[0] == appName {
return &appACLs
}
}
return nil
}
func (acls *AccessControlsService) GetAccessControls(appDomain string) (config.App, error) {
// First check environment variables
envACLs := acls.lookupEnvACLs(appDomain)
if envACLs != nil {
log.Debug().Str("domain", appDomain).Msg("Found matching access controls in environment variables")
return *envACLs, nil
}
// Fallback to Docker labels
return acls.docker.GetLabels(appDomain)
}

View File

@@ -289,21 +289,21 @@ func (auth *AuthService) UserAuthConfigured() bool {
return len(auth.config.Users) > 0 || auth.ldap != nil
}
func (auth *AuthService) IsResourceAllowed(c *gin.Context, context config.UserContext, acls config.App) bool {
func (auth *AuthService) IsResourceAllowed(c *gin.Context, context config.UserContext, labels config.App) bool {
if context.OAuth {
log.Debug().Msg("Checking OAuth whitelist")
return utils.CheckFilter(acls.OAuth.Whitelist, context.Email)
return utils.CheckFilter(labels.OAuth.Whitelist, context.Email)
}
if acls.Users.Block != "" {
if labels.Users.Block != "" {
log.Debug().Msg("Checking blocked users")
if utils.CheckFilter(acls.Users.Block, context.Username) {
if utils.CheckFilter(labels.Users.Block, context.Username) {
return false
}
}
log.Debug().Msg("Checking users")
return utils.CheckFilter(acls.Users.Allow, context.Username)
return utils.CheckFilter(labels.Users.Allow, context.Username)
}
func (auth *AuthService) IsInOAuthGroup(c *gin.Context, context config.UserContext, requiredGroups string) bool {
@@ -371,8 +371,8 @@ func (auth *AuthService) GetBasicAuth(c *gin.Context) *config.User {
}
}
func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool {
for _, blocked := range acls.Block {
func (auth *AuthService) CheckIP(labels config.AppIP, ip string) bool {
for _, blocked := range labels.Block {
res, err := utils.FilterIP(blocked, ip)
if err != nil {
log.Warn().Err(err).Str("item", blocked).Msg("Invalid IP/CIDR in block list")
@@ -384,7 +384,7 @@ func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool {
}
}
for _, allowed := range acls.Allow {
for _, allowed := range labels.Allow {
res, err := utils.FilterIP(allowed, ip)
if err != nil {
log.Warn().Err(err).Str("item", allowed).Msg("Invalid IP/CIDR in allow list")
@@ -396,7 +396,7 @@ func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool {
}
}
if len(acls.Allow) > 0 {
if len(labels.Allow) > 0 {
log.Debug().Str("ip", ip).Msg("IP not in allow list, denying access")
return false
}
@@ -405,8 +405,8 @@ func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool {
return true
}
func (auth *AuthService) IsBypassedIP(acls config.AppIP, ip string) bool {
for _, bypassed := range acls.Bypass {
func (auth *AuthService) IsBypassedIP(labels config.AppIP, ip string) bool {
for _, bypassed := range labels.Bypass {
res, err := utils.FilterIP(bypassed, ip)
if err != nil {
log.Warn().Err(err).Str("item", bypassed).Msg("Invalid IP/CIDR in bypass list")