Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
f7bd2062c7 chore(deps-dev): bump @types/node in /frontend in the minor-patch group
Bumps the minor-patch group in /frontend with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


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

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-13 08:16:51 +00:00
3 changed files with 90 additions and 205 deletions

View File

@@ -37,7 +37,7 @@
"devDependencies": {
"@eslint/js": "^10.0.1",
"@tanstack/eslint-plugin-query": "^5.91.4",
"@types/node": "^25.4.0",
"@types/node": "^25.5.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4",
@@ -417,7 +417,7 @@
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/node@25.4.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw=="],
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],

View File

@@ -43,7 +43,7 @@
"devDependencies": {
"@eslint/js": "^10.0.1",
"@tanstack/eslint-plugin-query": "^5.91.4",
"@types/node": "^25.4.0",
"@types/node": "^25.5.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4",

View File

@@ -1,11 +1,9 @@
package controller
import (
"errors"
"fmt"
"net/http"
"net/url"
"regexp"
"slices"
"strings"
"github.com/steveiliop56/tinyauth/internal/config"
@@ -17,31 +15,12 @@ import (
"github.com/google/go-querystring/query"
)
type RequestType int
const (
AuthRequest RequestType = iota
ExtAuthz
ForwardAuth
)
var BrowserUserAgentRegex = regexp.MustCompile("Chrome|Gecko|AppleWebKit|Opera|Edge")
var SupportedProxies = []string{"nginx", "traefik", "caddy", "envoy"}
type Proxy struct {
Proxy string `uri:"proxy" binding:"required"`
}
type ProxyContext struct {
Host string
Proto string
Path string
Method string
Type RequestType
IsBrowser bool
}
type ProxyControllerConfig struct {
AppURL string
}
@@ -64,30 +43,82 @@ func NewProxyController(config ProxyControllerConfig, router *gin.RouterGroup, a
func (controller *ProxyController) SetupRoutes() {
proxyGroup := controller.router.Group("/auth")
// There is a later check to control allowed methods per proxy
proxyGroup.Any("/:proxy", controller.proxyHandler)
}
func (controller *ProxyController) proxyHandler(c *gin.Context) {
// Load proxy context based on the request type
proxyCtx, err := controller.getProxyContext(c)
var req Proxy
err := c.BindUri(&req)
if err != nil {
tlog.App.Warn().Err(err).Msg("Failed to get proxy context")
tlog.App.Error().Err(err).Msg("Failed to bind URI")
c.JSON(400, gin.H{
"status": 400,
"message": "Bad request",
"message": "Bad Request",
})
return
}
tlog.App.Trace().Interface("ctx", proxyCtx).Msg("Got proxy context")
if !slices.Contains(SupportedProxies, req.Proxy) {
tlog.App.Warn().Str("proxy", req.Proxy).Msg("Invalid proxy")
c.JSON(400, gin.H{
"status": 400,
"message": "Bad Request",
})
return
}
// Only allow GET for non-envoy proxies.
// Envoy uses the original client method for the external auth request
// so we allow Any standard HTTP method for /api/auth/envoy
if req.Proxy != "envoy" && c.Request.Method != http.MethodGet {
tlog.App.Warn().Str("method", c.Request.Method).Msg("Invalid method for proxy")
c.Header("Allow", "GET")
c.JSON(405, gin.H{
"status": 405,
"message": "Method Not Allowed",
})
return
}
isBrowser := strings.Contains(c.Request.Header.Get("Accept"), "text/html")
if isBrowser {
tlog.App.Debug().Msg("Request identified as (most likely) coming from a browser")
} else {
tlog.App.Debug().Msg("Request identified as (most likely) coming from a non-browser client")
}
// We are not marking the URI as a required header because it may be missing
// and we only use it for the auth enabled check which will simply not match
// if the header is missing. For deployments like Kubernetes, we use the
// x-original-uri header instead.
uri, ok := controller.getHeader(c, "x-forwarded-uri")
if !ok {
originalUri, ok := controller.getHeader(c, "x-original-uri")
if ok {
uri = originalUri
}
}
host, ok := controller.requireHeader(c, "x-forwarded-host")
if !ok {
return
}
proto, ok := controller.requireHeader(c, "x-forwarded-proto")
if !ok {
return
}
// Get acls
acls, err := controller.acls.GetAccessControls(proxyCtx.Host)
acls, err := controller.acls.GetAccessControls(host)
if err != nil {
tlog.App.Error().Err(err).Msg("Failed to get access controls for resource")
controller.handleError(c, proxyCtx)
controller.handleError(c, req, isBrowser)
return
}
@@ -104,11 +135,11 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}
authEnabled, err := controller.auth.IsAuthEnabled(proxyCtx.Path, acls.Path)
authEnabled, err := controller.auth.IsAuthEnabled(uri, acls.Path)
if err != nil {
tlog.App.Error().Err(err).Msg("Failed to check if auth is enabled for resource")
controller.handleError(c, proxyCtx)
controller.handleError(c, req, isBrowser)
return
}
@@ -123,7 +154,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
}
if !controller.auth.CheckIP(acls.IP, clientIP) {
if !controller.useFriendlyError(proxyCtx) {
if req.Proxy == "nginx" || !isBrowser {
c.JSON(401, gin.H{
"status": 401,
"message": "Unauthorized",
@@ -132,7 +163,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
}
queries, err := query.Values(config.UnauthorizedQuery{
Resource: strings.Split(proxyCtx.Host, ".")[0],
Resource: strings.Split(host, ".")[0],
IP: clientIP,
})
@@ -165,9 +196,9 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
userAllowed := controller.auth.IsUserAllowed(c, userContext, acls)
if !userAllowed {
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(proxyCtx.Host, ".")[0]).Msg("User not allowed to access resource")
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User not allowed to access resource")
if !controller.useFriendlyError(proxyCtx) {
if req.Proxy == "nginx" || !isBrowser {
c.JSON(403, gin.H{
"status": 403,
"message": "Forbidden",
@@ -176,7 +207,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
}
queries, err := query.Values(config.UnauthorizedQuery{
Resource: strings.Split(proxyCtx.Host, ".")[0],
Resource: strings.Split(host, ".")[0],
})
if err != nil {
@@ -205,9 +236,9 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
}
if !groupOK {
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(proxyCtx.Host, ".")[0]).Msg("User groups do not match resource requirements")
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User groups do not match resource requirements")
if !controller.useFriendlyError(proxyCtx) {
if req.Proxy == "nginx" || !isBrowser {
c.JSON(403, gin.H{
"status": 403,
"message": "Forbidden",
@@ -216,7 +247,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
}
queries, err := query.Values(config.UnauthorizedQuery{
Resource: strings.Split(proxyCtx.Host, ".")[0],
Resource: strings.Split(host, ".")[0],
GroupErr: true,
})
@@ -258,7 +289,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}
if !controller.useFriendlyError(proxyCtx) {
if req.Proxy == "nginx" || !isBrowser {
c.JSON(401, gin.H{
"status": 401,
"message": "Unauthorized",
@@ -267,7 +298,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
}
queries, err := query.Values(config.RedirectQuery{
RedirectURI: fmt.Sprintf("%s://%s%s", proxyCtx.Proto, proxyCtx.Host, proxyCtx.Path),
RedirectURI: fmt.Sprintf("%s://%s%s", proto, host, uri),
})
if err != nil {
@@ -297,8 +328,8 @@ func (controller *ProxyController) setHeaders(c *gin.Context, acls config.App) {
}
}
func (controller *ProxyController) handleError(c *gin.Context, proxyCtx ProxyContext) {
if !controller.useFriendlyError(proxyCtx) {
func (controller *ProxyController) handleError(c *gin.Context, req Proxy, isBrowser bool) {
if req.Proxy == "nginx" || !isBrowser {
c.JSON(500, gin.H{
"status": 500,
"message": "Internal Server Error",
@@ -309,166 +340,20 @@ func (controller *ProxyController) handleError(c *gin.Context, proxyCtx ProxyCon
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
}
func (controller *ProxyController) requireHeader(c *gin.Context, header string) (string, bool) {
val, ok := controller.getHeader(c, header)
if !ok {
tlog.App.Error().Str("header", header).Msg("Header not found")
c.JSON(400, gin.H{
"status": 400,
"message": "Bad Request",
})
return "", false
}
return val, true
}
func (controller *ProxyController) getHeader(c *gin.Context, header string) (string, bool) {
val := c.Request.Header.Get(header)
return val, strings.TrimSpace(val) != ""
}
func (controller *ProxyController) useFriendlyError(proxyCtx ProxyContext) bool {
return (proxyCtx.Type == ForwardAuth || proxyCtx.Type == ExtAuthz) && proxyCtx.IsBrowser
}
// Code below is inspired from https://github.com/authelia/authelia/blob/master/internal/handlers/handler_authz.go
// and thus it may be subject to Apache 2.0 License
func (controller *ProxyController) getForwardAuthContext(c *gin.Context) (ProxyContext, error) {
host, ok := controller.getHeader(c, "x-forwarded-host")
if !ok {
return ProxyContext{}, errors.New("x-forwarded-host not found")
}
uri, ok := controller.getHeader(c, "x-forwarded-uri")
if !ok {
return ProxyContext{}, errors.New("x-forwarded-uri not found")
}
proto, ok := controller.getHeader(c, "x-forwarded-proto")
if !ok {
return ProxyContext{}, errors.New("x-forwarded-proto not found")
}
method := c.Request.Method
if method != http.MethodGet {
return ProxyContext{}, errors.New("method not allowed")
}
return ProxyContext{
Host: host,
Proto: proto,
Path: uri,
Method: method,
Type: ForwardAuth,
}, nil
}
func (controller *ProxyController) getAuthRequestContext(c *gin.Context) (ProxyContext, error) {
xOriginalUrl, ok := controller.getHeader(c, "x-original-url")
if !ok {
return ProxyContext{}, errors.New("x-original-url not found")
}
url, err := url.Parse(xOriginalUrl)
if err != nil {
return ProxyContext{}, err
}
host := url.Host
proto := url.Scheme
path := url.Path
method := c.Request.Method
if method != http.MethodGet {
return ProxyContext{}, errors.New("method not allowed")
}
return ProxyContext{
Host: host,
Proto: proto,
Path: path,
Method: method,
Type: AuthRequest,
}, nil
}
func (controller *ProxyController) getExtAuthzContext(c *gin.Context) (ProxyContext, error) {
proto, ok := controller.getHeader(c, "x-forwarded-proto")
if !ok {
return ProxyContext{}, errors.New("x-forwarded-proto not found")
}
host, ok := controller.getHeader(c, "host")
if !ok {
return ProxyContext{}, errors.New("host not found")
}
// Seems like we can't get the path?
// For envoy we need to support every method
method := c.Request.Method
return ProxyContext{
Host: host,
Proto: proto,
Method: method,
Type: ExtAuthz,
}, nil
}
func (controller *ProxyController) getProxyContext(c *gin.Context) (ProxyContext, error) {
var req Proxy
err := c.BindUri(&req)
if err != nil {
return ProxyContext{}, err
}
var ctx ProxyContext
switch req.Proxy {
// For nginx we need to handle both forward_auth and auth_request extraction since it can be
// used either with something line nginx proxy manager with advanced config or with
// the kubernetes ingress controller
case "nginx":
tlog.App.Debug().Str("proxy", req.Proxy).Msg("Attempting forward_auth compatible extraction")
forwardAuthCtx, err := controller.getForwardAuthContext(c)
if err == nil {
tlog.App.Debug().Str("proxy", req.Proxy).Msg("Extractions success using forward_auth")
ctx = forwardAuthCtx
} else {
tlog.App.Debug().Str("proxy", req.Proxy).Msg("Extractions failed using forward_auth trying with auth_request")
authRequestCtx, err := controller.getAuthRequestContext(c)
if err != nil {
tlog.App.Warn().Str("proxy", req.Proxy).Msg("Failed to determine required module for header extraction")
return ProxyContext{}, err
}
ctx = authRequestCtx
}
case "envoy":
tlog.App.Debug().Str("proxy", req.Proxy).Msg("Attempting ext_authz compatible extraction")
extAuthzCtx, err := controller.getExtAuthzContext(c)
if err != nil {
tlog.App.Warn().Str("proxy", req.Proxy).Msg("Failed to determine required module for header extraction")
return ProxyContext{}, err
}
ctx = extAuthzCtx
// By default we fallback to the forward_auth module which supports most proxies like traefik or caddy
default:
tlog.App.Debug().Str("proxy", req.Proxy).Msg("Attempting forward_auth compatible extraction")
forwardAuthCtx, err := controller.getForwardAuthContext(c)
if err != nil {
tlog.App.Warn().Str("proxy", req.Proxy).Msg("Failed to determine required module for header extraction")
return ProxyContext{}, err
}
ctx = forwardAuthCtx
}
// We don't care if the header is empty, we will just assume it's not a browser
userAgent, _ := controller.getHeader(c, "user-agent")
isBrowser := BrowserUserAgentRegex.MatchString(userAgent)
if isBrowser {
tlog.App.Debug().Msg("Request identified as (most likely) coming from a browser")
} else {
tlog.App.Debug().Msg("Request identified as (most likely) coming from a non-browser client")
}
ctx.IsBrowser = isBrowser
return ctx, nil
}