mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 04:35:40 +00:00
feat: add ability to set custom headers
This commit is contained in:
@@ -133,7 +133,7 @@ var rootCmd = &cobra.Command{
|
|||||||
hooks := hooks.NewHooks(auth, providers)
|
hooks := hooks.NewHooks(auth, providers)
|
||||||
|
|
||||||
// Create handlers
|
// Create handlers
|
||||||
handlers := handlers.NewHandlers(serverConfig, auth, hooks, providers)
|
handlers := handlers.NewHandlers(serverConfig, auth, hooks, providers, docker)
|
||||||
|
|
||||||
// Create API
|
// Create API
|
||||||
api := api.NewAPI(apiConfig, handlers)
|
api := api.NewAPI(apiConfig, handlers)
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
traefik.http.routers.nginx.rule: Host(`whoami.example.com`)
|
traefik.http.routers.nginx.rule: Host(`whoami.example.com`)
|
||||||
traefik.http.services.nginx.loadbalancer.server.port: 80
|
|
||||||
traefik.http.routers.nginx.middlewares: tinyauth
|
traefik.http.routers.nginx.middlewares: tinyauth
|
||||||
|
|
||||||
tinyauth-frontend:
|
tinyauth-frontend:
|
||||||
@@ -29,7 +28,6 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
|
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
|
||||||
traefik.http.services.tinyauth.loadbalancer.server.port: 5173
|
|
||||||
|
|
||||||
tinyauth-backend:
|
tinyauth-backend:
|
||||||
container_name: tinyauth-backend
|
container_name: tinyauth-backend
|
||||||
@@ -41,6 +39,7 @@ services:
|
|||||||
- ./internal:/tinyauth/internal
|
- ./internal:/tinyauth/internal
|
||||||
- ./cmd:/tinyauth/cmd
|
- ./cmd:/tinyauth/cmd
|
||||||
- ./main.go:/tinyauth/main.go
|
- ./main.go:/tinyauth/main.go
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
traefik.http.routers.nginx.rule: Host(`whoami.example.com`)
|
traefik.http.routers.nginx.rule: Host(`whoami.example.com`)
|
||||||
traefik.http.services.nginx.loadbalancer.server.port: 80
|
|
||||||
traefik.http.routers.nginx.middlewares: tinyauth
|
traefik.http.routers.nginx.middlewares: tinyauth
|
||||||
|
|
||||||
tinyauth:
|
tinyauth:
|
||||||
@@ -27,5 +26,4 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
|
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
|
||||||
traefik.http.services.tinyauth.loadbalancer.server.port: 3000
|
|
||||||
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik
|
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ func getAPI(t *testing.T) *api.API {
|
|||||||
hooks := hooks.NewHooks(auth, providers)
|
hooks := hooks.NewHooks(auth, providers)
|
||||||
|
|
||||||
// Create handlers service
|
// Create handlers service
|
||||||
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers)
|
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
||||||
|
|
||||||
// Create API
|
// Create API
|
||||||
api := api.NewAPI(apiConfig, handlers)
|
api := api.NewAPI(apiConfig, handlers)
|
||||||
|
|||||||
@@ -159,41 +159,35 @@ func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext) (bo
|
|||||||
// Get app id
|
// Get app id
|
||||||
appId := strings.Split(host, ".")[0]
|
appId := strings.Split(host, ".")[0]
|
||||||
|
|
||||||
// Check if resource is allowed
|
// Get the container labels
|
||||||
allowed, err := auth.Docker.ContainerAction(appId, func(labels types.TinyauthLabels) (bool, error) {
|
labels, err := auth.Docker.GetLabels(appId)
|
||||||
// If the container has an oauth whitelist, check if the user is in it
|
|
||||||
if context.OAuth {
|
|
||||||
if len(labels.OAuthWhitelist) == 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
log.Debug().Msg("Checking OAuth whitelist")
|
|
||||||
if slices.Contains(labels.OAuthWhitelist, context.Username) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the container has users, check if the user is in it
|
|
||||||
if len(labels.Users) != 0 {
|
|
||||||
log.Debug().Msg("Checking users")
|
|
||||||
if slices.Contains(labels.Users, context.Username) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allowed
|
|
||||||
return true, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// 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 checking if resource is allowed")
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return if the resource is allowed
|
// Check if oauth is allowed
|
||||||
return allowed, nil
|
if context.OAuth {
|
||||||
|
if len(labels.OAuthWhitelist) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
log.Debug().Msg("Checking OAuth whitelist")
|
||||||
|
if slices.Contains(labels.OAuthWhitelist, context.Username) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is allowed
|
||||||
|
if len(labels.Users) != 0 {
|
||||||
|
log.Debug().Msg("Checking users")
|
||||||
|
if slices.Contains(labels.Users, context.Username) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not allowed
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) AuthEnabled(c *gin.Context) (bool, error) {
|
func (auth *Auth) AuthEnabled(c *gin.Context) (bool, error) {
|
||||||
@@ -204,40 +198,37 @@ func (auth *Auth) AuthEnabled(c *gin.Context) (bool, error) {
|
|||||||
// Get app id
|
// Get app id
|
||||||
appId := strings.Split(host, ".")[0]
|
appId := strings.Split(host, ".")[0]
|
||||||
|
|
||||||
// Check if auth is enabled
|
// Get the container labels
|
||||||
enabled, err := auth.Docker.ContainerAction(appId, func(labels types.TinyauthLabels) (bool, error) {
|
labels, err := auth.Docker.GetLabels(appId)
|
||||||
// Check if the allowed label is empty
|
|
||||||
if labels.Allowed == "" {
|
|
||||||
// Auth enabled
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile regex
|
|
||||||
regex, err := regexp.Compile(labels.Allowed)
|
|
||||||
|
|
||||||
// If there is an error, invalid regex, auth enabled
|
|
||||||
if err != nil {
|
|
||||||
log.Warn().Err(err).Msg("Invalid regex")
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the uri matches the regex
|
|
||||||
if regex.MatchString(uri) {
|
|
||||||
// Auth disabled
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auth enabled
|
|
||||||
return true, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// If there is an error, auth enabled
|
// If there is an error, auth enabled
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Error checking if auth is enabled")
|
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return enabled, nil
|
// Check if the allowed label is empty
|
||||||
|
if labels.Allowed == "" {
|
||||||
|
// Auth enabled
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile regex
|
||||||
|
regex, err := regexp.Compile(labels.Allowed)
|
||||||
|
|
||||||
|
// If there is an error, invalid regex, auth enabled
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Msg("Invalid regex")
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the uri matches the regex
|
||||||
|
if regex.MatchString(uri) {
|
||||||
|
// Auth disabled
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth enabled
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) GetBasicAuth(c *gin.Context) *types.User {
|
func (auth *Auth) GetBasicAuth(c *gin.Context) *types.User {
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ var TinyauthLabels = []string{
|
|||||||
"tinyauth.oauth.whitelist",
|
"tinyauth.oauth.whitelist",
|
||||||
"tinyauth.users",
|
"tinyauth.users",
|
||||||
"tinyauth.allowed",
|
"tinyauth.allowed",
|
||||||
|
"tinyauth.headers",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package docker
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
appTypes "tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
"tinyauth/internal/utils"
|
"tinyauth/internal/utils"
|
||||||
|
|
||||||
apiTypes "github.com/docker/docker/api/types"
|
apiTypes "github.com/docker/docker/api/types"
|
||||||
@@ -70,14 +70,14 @@ func (docker *Docker) DockerConnected() bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (docker *Docker) ContainerAction(appId string, runCheck func(labels appTypes.TinyauthLabels) (bool, error)) (bool, error) {
|
func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) {
|
||||||
// Check if we have access to the Docker API
|
// Check if we have access to the Docker API
|
||||||
isConnected := docker.DockerConnected()
|
isConnected := docker.DockerConnected()
|
||||||
|
|
||||||
// If we don't have access, it is assumed that the check passed
|
// If we don't have access, return an empty struct
|
||||||
if !isConnected {
|
if !isConnected {
|
||||||
log.Debug().Msg("Docker not connected, passing check")
|
log.Debug().Msg("Docker not connected, returning empty labels")
|
||||||
return true, nil
|
return types.TinyauthLabels{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the containers
|
// Get the containers
|
||||||
@@ -85,7 +85,7 @@ func (docker *Docker) ContainerAction(appId string, runCheck func(labels appType
|
|||||||
|
|
||||||
// If there is an error, return false
|
// If there is an error, return false
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return types.TinyauthLabels{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Got containers")
|
log.Debug().Msg("Got containers")
|
||||||
@@ -97,11 +97,11 @@ func (docker *Docker) ContainerAction(appId string, runCheck func(labels appType
|
|||||||
|
|
||||||
// If there is an error, return false
|
// If there is an error, return false
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return types.TinyauthLabels{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the container name (for some reason it is /name)
|
// Get the container name (for some reason it is /name)
|
||||||
containerName := strings.Split(inspect.Name, "/")[1]
|
containerName := strings.TrimPrefix(inspect.Name, "/")
|
||||||
|
|
||||||
// There is a container with the same name as the app ID
|
// There is a container with the same name as the app ID
|
||||||
if containerName == appId {
|
if containerName == appId {
|
||||||
@@ -112,14 +112,14 @@ func (docker *Docker) ContainerAction(appId string, runCheck func(labels appType
|
|||||||
|
|
||||||
log.Debug().Msg("Got labels")
|
log.Debug().Msg("Got labels")
|
||||||
|
|
||||||
// Run the check
|
// Return labels
|
||||||
return runCheck(labels)
|
return labels, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("No matching container found, passing check")
|
log.Debug().Msg("No matching container found, returning empty labels")
|
||||||
|
|
||||||
// If no matching container is found, pass check
|
// If no matching container is found, return empty labels
|
||||||
return true, nil
|
return types.TinyauthLabels{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"tinyauth/internal/auth"
|
"tinyauth/internal/auth"
|
||||||
|
"tinyauth/internal/docker"
|
||||||
"tinyauth/internal/hooks"
|
"tinyauth/internal/hooks"
|
||||||
"tinyauth/internal/providers"
|
"tinyauth/internal/providers"
|
||||||
"tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
@@ -16,12 +17,13 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewHandlers(config types.HandlersConfig, auth *auth.Auth, hooks *hooks.Hooks, providers *providers.Providers) *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,
|
||||||
Auth: auth,
|
Auth: auth,
|
||||||
Hooks: hooks,
|
Hooks: hooks,
|
||||||
Providers: providers,
|
Providers: providers,
|
||||||
|
Docker: docker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ type Handlers struct {
|
|||||||
Auth *auth.Auth
|
Auth *auth.Auth
|
||||||
Hooks *hooks.Hooks
|
Hooks *hooks.Hooks
|
||||||
Providers *providers.Providers
|
Providers *providers.Providers
|
||||||
|
Docker *docker.Docker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) AuthHandler(c *gin.Context) {
|
func (h *Handlers) AuthHandler(c *gin.Context) {
|
||||||
@@ -60,12 +63,39 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
|
|
||||||
log.Debug().Interface("proxy", proxy.Proxy).Msg("Got proxy")
|
log.Debug().Interface("proxy", proxy.Proxy).Msg("Got proxy")
|
||||||
|
|
||||||
|
// Get headers
|
||||||
|
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
||||||
|
proto := c.Request.Header.Get("X-Forwarded-Proto")
|
||||||
|
host := c.Request.Header.Get("X-Forwarded-Host")
|
||||||
|
|
||||||
// Check if auth is enabled
|
// Check if auth is enabled
|
||||||
authEnabled, err := h.Auth.AuthEnabled(c)
|
authEnabled, err := h.Auth.AuthEnabled(c)
|
||||||
|
|
||||||
// Handle error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to check if auth is enabled")
|
log.Error().Err(err).Msg("Failed to check if app is allowed")
|
||||||
|
|
||||||
|
if proxy.Proxy == "nginx" || !isBrowser {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"status": 500,
|
||||||
|
"message": "Internal Server Error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the app id
|
||||||
|
appId := strings.Split(host, ".")[0]
|
||||||
|
|
||||||
|
// Get the container labels
|
||||||
|
labels, err := h.Docker.GetLabels(appId)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to check if app is allowed")
|
||||||
|
|
||||||
if proxy.Proxy == "nginx" || !isBrowser {
|
if proxy.Proxy == "nginx" || !isBrowser {
|
||||||
c.JSON(500, gin.H{
|
c.JSON(500, gin.H{
|
||||||
@@ -81,6 +111,10 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
|
|
||||||
// If auth is not enabled, return 200
|
// If auth is not enabled, return 200
|
||||||
if !authEnabled {
|
if !authEnabled {
|
||||||
|
for key, value := range labels.Headers {
|
||||||
|
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
||||||
|
c.Header(key, value)
|
||||||
|
}
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"message": "Authenticated",
|
"message": "Authenticated",
|
||||||
@@ -91,11 +125,6 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
// Get user context
|
// Get user context
|
||||||
userContext := h.Hooks.UseUserContext(c)
|
userContext := h.Hooks.UseUserContext(c)
|
||||||
|
|
||||||
// Get headers
|
|
||||||
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
|
||||||
proto := c.Request.Header.Get("X-Forwarded-Proto")
|
|
||||||
host := c.Request.Header.Get("X-Forwarded-Host")
|
|
||||||
|
|
||||||
// Check if user is logged in
|
// Check if user is logged in
|
||||||
if userContext.IsLoggedIn {
|
if userContext.IsLoggedIn {
|
||||||
log.Debug().Msg("Authenticated")
|
log.Debug().Msg("Authenticated")
|
||||||
@@ -157,6 +186,12 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
// Set the user header
|
// Set the user header
|
||||||
c.Header("Remote-User", userContext.Username)
|
c.Header("Remote-User", userContext.Username)
|
||||||
|
|
||||||
|
// Set the rest of the headers
|
||||||
|
for key, value := range labels.Headers {
|
||||||
|
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
||||||
|
c.Header(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
// The user is allowed to access the app
|
// The user is allowed to access the app
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ type TinyauthLabels struct {
|
|||||||
OAuthWhitelist []string
|
OAuthWhitelist []string
|
||||||
Users []string
|
Users []string
|
||||||
Allowed string
|
Allowed string
|
||||||
|
Headers map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// TailscaleQuery is the query parameters for the tailscale endpoint
|
// TailscaleQuery is the query parameters for the tailscale endpoint
|
||||||
|
|||||||
@@ -193,6 +193,16 @@ func GetTinyauthLabels(labels map[string]string) types.TinyauthLabels {
|
|||||||
tinyauthLabels.Users = strings.Split(value, ",")
|
tinyauthLabels.Users = strings.Split(value, ",")
|
||||||
case "tinyauth.allowed":
|
case "tinyauth.allowed":
|
||||||
tinyauthLabels.Allowed = value
|
tinyauthLabels.Allowed = value
|
||||||
|
case "tinyauth.headers":
|
||||||
|
tinyauthLabels.Headers = make(map[string]string)
|
||||||
|
headers := strings.Split(value, ",")
|
||||||
|
for _, header := range headers {
|
||||||
|
headerSplit := strings.Split(header, "=")
|
||||||
|
if len(headerSplit) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tinyauthLabels.Headers[headerSplit[0]] = headerSplit[1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user