mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 12:45:47 +00:00
Compare commits
5 Commits
v3.4.1-bet
...
feat/app-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e22d181de7 | ||
|
|
c9b609b69c | ||
|
|
4e6372ea97 | ||
|
|
3397e2aa8e | ||
|
|
ee83c177f4 |
@@ -1,5 +1,5 @@
|
||||
# Site builder
|
||||
FROM oven/bun:1.2.15-alpine AS frontend-builder
|
||||
FROM oven/bun:1.2.16-alpine AS frontend-builder
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please always use the latest available Tinyauth version which can be found [here](https://github.com/steveiliop56/tinyauth/releases/latest). Older versions (especially major) may contain security issues which I cannot go back and fix.
|
||||
It is recommended to use the [latest](https://github.com/steveiliop56/tinyauth/releases/latest) available version of tinyauth. This is because it includes security fixes, new features and dependency updates. Older versions, especially major ones, are not supported and won't receive security or patch updates.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Due to the nature of this app, it needs to be secure. If you find any security issues in the OAuth or login flow of the app please contact me at <steve@doesmycode.work> and include a concise description of the issue. Please do not use the issues section for reporting major security issues.
|
||||
Due to the nature of this app, it needs to be secure. If you discover any security issues or vulnerabilities in the app please contact me as soon as possible at <steve@doesmycode.work>. Please do not use the issues section to report security issues as I won't be able to patch them in time and they may get exploited by malicious actors.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM oven/bun:1.1.45-alpine
|
||||
FROM oven/bun:1.2.16-alpine
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export const Layout = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative flex flex-col justify-center items-center min-h-svh"
|
||||
className="relative flex flex-col justify-center items-center min-h-dvh"
|
||||
style={{
|
||||
backgroundImage: `url(${backgroundImage})`,
|
||||
backgroundSize: "cover",
|
||||
|
||||
1
go.mod
1
go.mod
@@ -24,6 +24,7 @@ require (
|
||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.2 // 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
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -238,6 +238,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ=
|
||||
github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
|
||||
@@ -264,11 +264,11 @@ func (auth *Auth) UserAuthConfigured() bool {
|
||||
return len(auth.Config.Users) > 0
|
||||
}
|
||||
|
||||
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.TinyauthLabels) bool {
|
||||
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.Labels) bool {
|
||||
// Check if oauth is allowed
|
||||
if context.OAuth {
|
||||
log.Debug().Msg("Checking OAuth whitelist")
|
||||
return utils.CheckWhitelist(labels.OAuthWhitelist, context.Email)
|
||||
return utils.CheckWhitelist(labels.OAuth.Whitelist, context.Email)
|
||||
}
|
||||
|
||||
// Check users
|
||||
@@ -277,9 +277,9 @@ func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, lab
|
||||
return utils.CheckWhitelist(labels.Users, context.Username)
|
||||
}
|
||||
|
||||
func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels types.TinyauthLabels) bool {
|
||||
func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels types.Labels) bool {
|
||||
// Check if groups are required
|
||||
if labels.OAuthGroups == "" {
|
||||
if labels.OAuth.Groups == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels t
|
||||
|
||||
// For every group check if it is in the required groups
|
||||
for _, group := range oauthGroups {
|
||||
if utils.CheckWhitelist(labels.OAuthGroups, group) {
|
||||
if utils.CheckWhitelist(labels.OAuth.Groups, group) {
|
||||
log.Debug().Str("group", group).Msg("Group is in required groups")
|
||||
return true
|
||||
}
|
||||
@@ -307,7 +307,7 @@ func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels t
|
||||
return false
|
||||
}
|
||||
|
||||
func (auth *Auth) AuthEnabled(c *gin.Context, labels types.TinyauthLabels) (bool, error) {
|
||||
func (auth *Auth) AuthEnabled(c *gin.Context, labels types.Labels) (bool, error) {
|
||||
// Get headers
|
||||
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
||||
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
package constants
|
||||
|
||||
// TinyauthLabels is a list of labels that can be used in a tinyauth protected container
|
||||
var TinyauthLabels = []string{
|
||||
"tinyauth.oauth.whitelist",
|
||||
"tinyauth.users",
|
||||
"tinyauth.allowed",
|
||||
"tinyauth.headers",
|
||||
"tinyauth.oauth.groups",
|
||||
}
|
||||
|
||||
// Claims are the OIDC supported claims (including preferd username for some reason)
|
||||
type Claims struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -74,56 +74,58 @@ func (docker *Docker) DockerConnected() bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) {
|
||||
func (docker *Docker) GetLabels(id string, domain string) (types.Labels, error) {
|
||||
// Check if we have access to the Docker API
|
||||
isConnected := docker.DockerConnected()
|
||||
|
||||
// If we don't have access, return an empty struct
|
||||
if !isConnected {
|
||||
log.Debug().Msg("Docker not connected, returning empty labels")
|
||||
return types.TinyauthLabels{}, nil
|
||||
return types.Labels{}, nil
|
||||
}
|
||||
|
||||
// Get the containers
|
||||
log.Debug().Msg("Getting containers")
|
||||
|
||||
containers, err := docker.GetContainers()
|
||||
|
||||
// If there is an error, return false
|
||||
if err != nil {
|
||||
return types.TinyauthLabels{}, err
|
||||
log.Error().Err(err).Msg("Error getting containers")
|
||||
return types.Labels{}, err
|
||||
}
|
||||
|
||||
log.Debug().Msg("Got containers")
|
||||
|
||||
// Loop through the containers
|
||||
for _, container := range containers {
|
||||
// Inspect the container
|
||||
inspect, err := docker.InspectContainer(container.ID)
|
||||
|
||||
// If there is an error, return false
|
||||
// Check if there was an error
|
||||
if err != nil {
|
||||
return types.TinyauthLabels{}, err
|
||||
log.Warn().Str("id", container.ID).Err(err).Msg("Error inspecting container, skipping")
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the container name (for some reason it is /name)
|
||||
containerName := strings.TrimPrefix(inspect.Name, "/")
|
||||
// Get the labels
|
||||
log.Debug().Str("id", inspect.ID).Msg("Getting labels for container")
|
||||
|
||||
// There is a container with the same name as the app ID
|
||||
if containerName == appId {
|
||||
log.Debug().Str("container", containerName).Msg("Found container")
|
||||
labels, err := utils.GetLabels(inspect.Config.Labels)
|
||||
|
||||
// Get only the tinyauth labels in a struct
|
||||
labels := utils.GetTinyauthLabels(inspect.Config.Labels)
|
||||
// Check if there was an error
|
||||
if err != nil {
|
||||
log.Warn().Str("id", container.ID).Err(err).Msg("Error getting container labels, skipping")
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debug().Msg("Got labels")
|
||||
|
||||
// Return labels
|
||||
// Check if the labels match the id or the domain
|
||||
if strings.TrimPrefix(inspect.Name, "/") == id || labels.Domain == domain {
|
||||
log.Debug().Str("id", inspect.ID).Msg("Found matching container")
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.Debug().Msg("No matching container found, returning empty labels")
|
||||
|
||||
// If no matching container is found, return empty labels
|
||||
return types.TinyauthLabels{}, nil
|
||||
return types.Labels{}, nil
|
||||
}
|
||||
|
||||
@@ -69,11 +69,14 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
||||
proto := c.Request.Header.Get("X-Forwarded-Proto")
|
||||
host := c.Request.Header.Get("X-Forwarded-Host")
|
||||
|
||||
// Get the app id
|
||||
appId := strings.Split(host, ".")[0]
|
||||
// Remove the port from the host if it exists
|
||||
hostPortless := strings.Split(host, ":")[0] // *lol*
|
||||
|
||||
// Get the id
|
||||
id := strings.Split(hostPortless, ".")[0]
|
||||
|
||||
// Get the container labels
|
||||
labels, err := h.Docker.GetLabels(appId)
|
||||
labels, err := h.Docker.GetLabels(id, hostPortless)
|
||||
|
||||
log.Debug().Interface("labels", labels).Msg("Got labels")
|
||||
|
||||
@@ -114,7 +117,8 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
||||
|
||||
// If auth is not enabled, return 200
|
||||
if !authEnabled {
|
||||
for key, value := range labels.Headers {
|
||||
headersParsed := utils.ParseHeaders(labels.Headers)
|
||||
for key, value := range headersParsed {
|
||||
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
||||
c.Header(key, utils.SanitizeHeader(value))
|
||||
}
|
||||
@@ -236,7 +240,8 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
||||
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
|
||||
|
||||
// Set the rest of the headers
|
||||
for key, value := range labels.Headers {
|
||||
parsedHeaders := utils.ParseHeaders(labels.Headers)
|
||||
for key, value := range parsedHeaders {
|
||||
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
||||
c.Header(key, utils.SanitizeHeader(value))
|
||||
}
|
||||
|
||||
@@ -92,3 +92,18 @@ type AuthConfig struct {
|
||||
type HooksConfig struct {
|
||||
Domain string
|
||||
}
|
||||
|
||||
// OAuthLabels is a list of labels that can be used in a tinyauth protected container
|
||||
type OAuthLabels struct {
|
||||
Whitelist string
|
||||
Groups string
|
||||
}
|
||||
|
||||
// Labels is a struct that contains the labels for a tinyauth protected container
|
||||
type Labels struct {
|
||||
Users string
|
||||
Allowed string
|
||||
Headers []string
|
||||
Domain string
|
||||
OAuth OAuthLabels
|
||||
}
|
||||
|
||||
@@ -32,15 +32,6 @@ type SessionCookie struct {
|
||||
OAuthGroups string
|
||||
}
|
||||
|
||||
// TinyauthLabels is the labels for the tinyauth container
|
||||
type TinyauthLabels struct {
|
||||
OAuthWhitelist string
|
||||
Users string
|
||||
Allowed string
|
||||
Headers map[string]string
|
||||
OAuthGroups string
|
||||
}
|
||||
|
||||
// UserContext is the context for the user
|
||||
type UserContext struct {
|
||||
Username string
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"tinyauth/internal/constants"
|
||||
"tinyauth/internal/types"
|
||||
|
||||
"github.com/traefik/paerser/parser"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -174,45 +174,43 @@ func GetUsers(conf string, file string) (types.Users, error) {
|
||||
return ParseUsers(users)
|
||||
}
|
||||
|
||||
// Parse the docker labels to the tinyauth labels struct
|
||||
func GetTinyauthLabels(labels map[string]string) types.TinyauthLabels {
|
||||
// Create a new tinyauth labels struct
|
||||
var tinyauthLabels types.TinyauthLabels
|
||||
// Parse the headers in a map[string]string format
|
||||
func ParseHeaders(headers []string) map[string]string {
|
||||
// Create a map to store the headers
|
||||
headerMap := make(map[string]string)
|
||||
|
||||
// Loop through the labels
|
||||
for label, value := range labels {
|
||||
|
||||
// Check if the label is in the tinyauth labels
|
||||
if slices.Contains(constants.TinyauthLabels, label) {
|
||||
|
||||
log.Debug().Str("label", label).Msg("Found label")
|
||||
|
||||
// Add the label value to the tinyauth labels struct
|
||||
switch label {
|
||||
case "tinyauth.oauth.whitelist":
|
||||
tinyauthLabels.OAuthWhitelist = value
|
||||
case "tinyauth.users":
|
||||
tinyauthLabels.Users = value
|
||||
case "tinyauth.allowed":
|
||||
tinyauthLabels.Allowed = value
|
||||
case "tinyauth.headers":
|
||||
tinyauthLabels.Headers = make(map[string]string)
|
||||
headers := strings.Split(value, ",")
|
||||
for _, header := range headers {
|
||||
headerSplit := strings.Split(header, "=")
|
||||
if len(headerSplit) != 2 {
|
||||
continue
|
||||
}
|
||||
tinyauthLabels.Headers[headerSplit[0]] = headerSplit[1]
|
||||
}
|
||||
case "tinyauth.oauth.groups":
|
||||
tinyauthLabels.OAuthGroups = value
|
||||
}
|
||||
// Loop through the headers
|
||||
for _, header := range headers {
|
||||
split := strings.SplitN(header, "=", 2)
|
||||
if len(split) != 2 {
|
||||
log.Warn().Str("header", header).Msg("Invalid header format, skipping")
|
||||
continue
|
||||
}
|
||||
key := SanitizeHeader(strings.TrimSpace(split[0]))
|
||||
value := SanitizeHeader(strings.TrimSpace(split[1]))
|
||||
headerMap[key] = value
|
||||
}
|
||||
|
||||
// Return the tinyauth labels
|
||||
return tinyauthLabels
|
||||
// Return the header map
|
||||
return headerMap
|
||||
}
|
||||
|
||||
// Get labels parses a map of labels into a struct with only the needed labels
|
||||
func GetLabels(labels map[string]string) (types.Labels, error) {
|
||||
// Create a new labels struct
|
||||
var labelsParsed types.Labels
|
||||
|
||||
// Decode the labels into the labels struct
|
||||
err := parser.Decode(labels, &labelsParsed, "tinyauth", "tinyauth.users", "tinyauth.allowed", "tinyauth.headers", "tinyauth.domain", "tinyauth.oauth")
|
||||
|
||||
// Check if there was an error
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error parsing labels")
|
||||
return types.Labels{}, err
|
||||
}
|
||||
|
||||
// Return the labels struct
|
||||
return labelsParsed, nil
|
||||
}
|
||||
|
||||
// Check if any of the OAuth providers are configured based on the client id and secret
|
||||
|
||||
@@ -279,29 +279,35 @@ func TestGetUsers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test the tinyauth labels function
|
||||
func TestGetTinyauthLabels(t *testing.T) {
|
||||
t.Log("Testing get tinyauth labels with a valid map")
|
||||
// Test the get labels function
|
||||
func TestGetLabels(t *testing.T) {
|
||||
t.Log("Testing get labels with a valid map")
|
||||
|
||||
// Test the get tinyauth labels function with a valid map
|
||||
labels := map[string]string{
|
||||
"tinyauth.users": "user1,user2",
|
||||
"tinyauth.oauth.whitelist": "/regex/",
|
||||
"tinyauth.allowed": "random",
|
||||
"random": "random",
|
||||
"tinyauth.headers": "X-Header=value",
|
||||
"tinyauth.oauth.groups": "group1,group2",
|
||||
}
|
||||
|
||||
expected := types.TinyauthLabels{
|
||||
Users: "user1,user2",
|
||||
OAuthWhitelist: "/regex/",
|
||||
Allowed: "random",
|
||||
Headers: map[string]string{
|
||||
"X-Header": "value",
|
||||
expected := types.Labels{
|
||||
Users: "user1,user2",
|
||||
Allowed: "random",
|
||||
Headers: []string{"X-Header=value"},
|
||||
OAuth: types.OAuthLabels{
|
||||
Whitelist: "/regex/",
|
||||
Groups: "group1,group2",
|
||||
},
|
||||
}
|
||||
|
||||
result := utils.GetTinyauthLabels(labels)
|
||||
result, err := utils.GetLabels(labels)
|
||||
|
||||
// Check if there was an error
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting labels: %v", err)
|
||||
}
|
||||
|
||||
// Check if the result is equal to the expected
|
||||
if !reflect.DeepEqual(expected, result) {
|
||||
|
||||
Reference in New Issue
Block a user