Compare commits

...

7 Commits

Author SHA1 Message Date
Stavros
d348d97003 chore: update contributing instructions 2025-12-30 16:53:41 +02:00
Stavros
3a93800158 refactor: update release workflows to include submodule and patches 2025-12-30 16:31:50 +02:00
Stavros
b37614f458 feat: add paerser as submodule and apply patch for nested maps 2025-12-30 16:08:40 +02:00
Stavros
333b854533 wip 2025-12-29 22:44:23 +02:00
Stavros
986ac88e14 Merge branch 'main' of https://github.com/steveiliop56/tinyauth 2025-12-29 19:46:38 +02:00
Stavros
b159f44729 fix: add missing ldap search filter 2025-12-29 19:46:33 +02:00
Stavros
43487d44f7 feat: forward sub from oidc providers (#543)
* feat: forward sub from oidc providers

* fix: review comments
2025-12-26 19:02:51 +02:00
29 changed files with 339 additions and 161 deletions

View File

@@ -18,7 +18,16 @@ jobs:
- name: Setup go - name: Setup go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: "^1.23.2" go-version: "^1.24.0"
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Install frontend dependencies - name: Install frontend dependencies
run: | run: |

View File

@@ -61,7 +61,16 @@ jobs:
- name: Install go - name: Install go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: "^1.23.2" go-version: "^1.24.0"
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Install frontend dependencies - name: Install frontend dependencies
run: | run: |
@@ -107,7 +116,16 @@ jobs:
- name: Install go - name: Install go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: "^1.23.2" go-version: "^1.24.0"
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Install frontend dependencies - name: Install frontend dependencies
run: | run: |
@@ -147,6 +165,15 @@ jobs:
with: with:
ref: nightly ref: nightly
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -205,6 +232,15 @@ jobs:
with: with:
ref: nightly ref: nightly
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -263,6 +299,15 @@ jobs:
with: with:
ref: nightly ref: nightly
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -321,6 +366,15 @@ jobs:
with: with:
ref: nightly ref: nightly
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5

View File

@@ -39,7 +39,16 @@ jobs:
- name: Install go - name: Install go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: "^1.23.2" go-version: "^1.24.0"
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Install frontend dependencies - name: Install frontend dependencies
run: | run: |
@@ -82,7 +91,16 @@ jobs:
- name: Install go - name: Install go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: "^1.23.2" go-version: "^1.24.0"
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Install frontend dependencies - name: Install frontend dependencies
run: | run: |
@@ -119,6 +137,15 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -174,6 +201,15 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -229,6 +265,15 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -284,6 +329,15 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Initialize submodules
run: |
git submodule init
git submodule update
- name: Apply patches
run: |
git apply --directory paerser/ patches/nested_maps.diff
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "paerser"]
path = paerser
url = https://github.com/traefik/paerser
ignore = all

View File

@@ -5,7 +5,7 @@ Contributing is relatively easy, you just need to follow the steps below and you
## Requirements ## Requirements
- Bun - Bun
- Golang v1.23.2 and above - Golang 1.24.0+
- Git - Git
- Docker - Docker
@@ -18,12 +18,21 @@ git clone https://github.com/steveiliop56/tinyauth
cd tinyauth cd tinyauth
``` ```
## Install requirements ## Initialize submodules
Although you will not need the requirements in your machine since the development will happen in docker, I still recommend to install them because this way you will not have import errors. To install the go requirements run: The project uses Git submodules for some dependencies, so you need to initialize them with:
```sh ```sh
go mod tidy git submodule init
git submodule update
```
## Install requirements
Although you will not need the requirements in your machine since the development will happen in Docker, I still recommend to install them because this way you will not have import errors. To install the Go requirements run:
```sh
go mod download
``` ```
You also need to download the frontend dependencies, this can be done like so: You also need to download the frontend dependencies, this can be done like so:
@@ -33,13 +42,21 @@ cd frontend/
bun install bun install
``` ```
## Apply patches
Some of the dependencies need to be patched in order to work correctly with the project, you can apply the patches by running:
```sh
git apply --directory paerser/ patches/nested_maps.diff
```
## Create your `.env` file ## Create your `.env` file
In order to configure the app you need to create an environment file, this can be done by copying the `.env.example` file to `.env` and modifying the environment variables to suit your needs. In order to configure the app you need to create an environment file, this can be done by copying the `.env.example` file to `.env` and modifying the environment variables to suit your needs.
## Developing ## Developing
I have designed the development workflow to be entirely in docker, this is because it will directly work with traefik and you will not need to do any building in your host machine. The recommended development setup is to have a subdomain pointing to your machine like this: I have designed the development workflow to be entirely in Docker, this is because it will directly work with Traefik and you will not need to do any building in your host machine. The recommended development setup is to have a subdomain pointing to your machine like this:
``` ```
*.dev.example.com -> 127.0.0.1 *.dev.example.com -> 127.0.0.1
@@ -49,7 +66,7 @@ dev.example.com -> 127.0.0.1
> [!TIP] > [!TIP]
> You can use [sslip.io](https://sslip.io) as a domain if you don't have one to develop with. > You can use [sslip.io](https://sslip.io) as a domain if you don't have one to develop with.
Then you can just make sure the domains are correct in the development docker compose file and run: Then you can just make sure the domains are correct in the development Docker compose file and run:
```sh ```sh
docker compose -f docker-compose.dev.yml up --build docker compose -f docker-compose.dev.yml up --build

View File

@@ -28,6 +28,8 @@ ARG BUILD_TIMESTAMP
WORKDIR /tinyauth WORKDIR /tinyauth
COPY ./paerser ./paerser
COPY go.mod ./ COPY go.mod ./
COPY go.sum ./ COPY go.sum ./

View File

@@ -2,6 +2,8 @@ FROM golang:1.25-alpine3.21
WORKDIR /tinyauth WORKDIR /tinyauth
COPY ./paerser ./paerser
COPY go.mod ./ COPY go.mod ./
COPY go.sum ./ COPY go.sum ./

View File

@@ -28,6 +28,8 @@ ARG BUILD_TIMESTAMP
WORKDIR /tinyauth WORKDIR /tinyauth
COPY ./paerser ./paerser
COPY go.mod ./ COPY go.mod ./
COPY go.sum ./ COPY go.sum ./

View File

@@ -34,6 +34,10 @@ func NewTinyauthCmdConfiguration() *config.Config {
ForgotPasswordMessage: "You can change your password by changing the configuration.", ForgotPasswordMessage: "You can change your password by changing the configuration.",
BackgroundImage: "/background.jpg", BackgroundImage: "/background.jpg",
}, },
Ldap: config.LdapConfig{
Insecure: false,
SearchFilter: "(uid=%s)",
},
Experimental: config.ExperimentalConfig{ Experimental: config.ExperimentalConfig{
ConfigFile: "", ConfigFile: "",
}, },

View File

@@ -42,7 +42,6 @@ services:
volumes: volumes:
- ./internal:/tinyauth/internal - ./internal:/tinyauth/internal
- ./cmd:/tinyauth/cmd - ./cmd:/tinyauth/cmd
- ./main.go:/tinyauth/main.go
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ./data:/data - ./data:/data
ports: ports:

2
go.mod
View File

@@ -4,6 +4,8 @@ go 1.24.0
toolchain go1.24.3 toolchain go1.24.3
replace github.com/traefik/paerser v0.2.2 => ./paerser
require ( require (
github.com/cenkalti/backoff/v5 v5.0.3 github.com/cenkalti/backoff/v5 v5.0.3
github.com/charmbracelet/huh v0.8.0 github.com/charmbracelet/huh v0.8.0

2
go.sum
View File

@@ -271,8 +271,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
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 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=

View File

@@ -0,0 +1 @@
ALTER TABLE "sessions" DROP COLUMN "oauth_sub";

View File

@@ -0,0 +1 @@
ALTER TABLE "sessions" ADD COLUMN "oauth_sub" TEXT;

View File

@@ -57,7 +57,7 @@ func (app *BootstrapApp) initServices() (Services, error) {
services.dockerService = dockerService services.dockerService = dockerService
accessControlsService := service.NewAccessControlsService(dockerService) accessControlsService := service.NewAccessControlsService(dockerService, app.config.Apps)
err = accessControlsService.Init() err = accessControlsService.Init()

View File

@@ -25,6 +25,7 @@ type Config struct {
LogJSON bool `description:"Enable JSON formatted logs." yaml:"logJSON"` LogJSON bool `description:"Enable JSON formatted logs." yaml:"logJSON"`
Server ServerConfig `description:"Server configuration." yaml:"server"` Server ServerConfig `description:"Server configuration." yaml:"server"`
Auth AuthConfig `description:"Authentication configuration." yaml:"auth"` Auth AuthConfig `description:"Authentication configuration." yaml:"auth"`
Apps map[string]App `description:"Application ACLs configuration." yaml:"apps"`
OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth"` OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth"`
UI UIConfig `description:"UI customization." yaml:"ui"` UI UIConfig `description:"UI customization." yaml:"ui"`
Ldap LdapConfig `description:"LDAP configuration." yaml:"ldap"` Ldap LdapConfig `description:"LDAP configuration." yaml:"ldap"`
@@ -79,6 +80,7 @@ const DefaultNamePrefix = "TINYAUTH_"
// OAuth/OIDC config // OAuth/OIDC config
type Claims struct { type Claims struct {
Sub string `json:"sub"`
Name string `json:"name"` Name string `json:"name"`
Email string `json:"email"` Email string `json:"email"`
PreferredUsername string `json:"preferred_username"` PreferredUsername string `json:"preferred_username"`
@@ -125,6 +127,7 @@ type SessionCookie struct {
TotpPending bool TotpPending bool
OAuthGroups string OAuthGroups string
OAuthName string OAuthName string
OAuthSub string
} }
type UserContext struct { type UserContext struct {
@@ -138,6 +141,7 @@ type UserContext struct {
OAuthGroups string OAuthGroups string
TotpEnabled bool TotpEnabled bool
OAuthName string OAuthName string
OAuthSub string
} }
// API responses and queries // API responses and queries
@@ -153,61 +157,55 @@ type RedirectQuery struct {
RedirectURI string `url:"redirect_uri"` RedirectURI string `url:"redirect_uri"`
} }
// Labels // ACLs
type Apps struct { type Apps struct {
Apps map[string]App Apps map[string]App `description:"App ACLs configuration." yaml:"apps"`
} }
type App struct { type App struct {
Config AppConfig Config AppConfig `description:"App configuration." yaml:"config"`
Users AppUsers Users AppUsers `description:"User access configuration." yaml:"users"`
OAuth AppOAuth OAuth AppOAuth `description:"OAuth access configuration." yaml:"oauth"`
IP AppIP IP AppIP `description:"IP access configuration." yaml:"ip"`
Response AppResponse Response AppResponse `description:"Response customization." yaml:"response"`
Path AppPath Path AppPath `description:"Path access configuration." yaml:"path"`
} }
type AppConfig struct { type AppConfig struct {
Domain string Domain string `description:"The domain of the app." yaml:"domain"`
} }
type AppUsers struct { type AppUsers struct {
Allow string Allow string `description:"Comma-separated list of allowed users." yaml:"allow"`
Block string Block string `description:"Comma-separated list of blocked users." yaml:"block"`
} }
type AppOAuth struct { type AppOAuth struct {
Whitelist string Whitelist string `description:"Comma-separated list of allowed OAuth groups." yaml:"whitelist"`
Groups string Groups string `description:"Comma-separated list of required OAuth groups." yaml:"groups"`
} }
type AppIP struct { type AppIP struct {
Allow []string Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"`
Block []string Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"`
Bypass []string Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication." yaml:"bypass"`
} }
type AppResponse struct { type AppResponse struct {
Headers []string Headers []string `description:"Custom headers to add to the response." yaml:"headers"`
BasicAuth AppBasicAuth BasicAuth AppBasicAuth `description:"Basic authentication for the app." yaml:"basicAuth"`
} }
type AppBasicAuth struct { type AppBasicAuth struct {
Username string Username string `description:"Basic auth username." yaml:"username"`
Password string Password string `description:"Basic auth password." yaml:"password"`
PasswordFile string PasswordFile string `description:"Path to the file containing the basic auth password." yaml:"passwordFile"`
} }
type AppPath struct { type AppPath struct {
Allow string Allow string `description:"Comma-separated list of allowed paths." yaml:"allow"`
Block string Block string `description:"Comma-separated list of blocked paths." yaml:"block"`
}
// Flags
type Providers struct {
Providers map[string]OAuthServiceConfig
} }
// API server // API server

View File

@@ -21,6 +21,7 @@ type UserContextResponse struct {
OAuth bool `json:"oauth"` OAuth bool `json:"oauth"`
TotpPending bool `json:"totpPending"` TotpPending bool `json:"totpPending"`
OAuthName string `json:"oauthName"` OAuthName string `json:"oauthName"`
OAuthSub string `json:"oauthSub"`
} }
type AppContextResponse struct { type AppContextResponse struct {
@@ -89,6 +90,7 @@ func (controller *ContextController) userContextHandler(c *gin.Context) {
OAuth: context.OAuth, OAuth: context.OAuth,
TotpPending: context.TotpPending, TotpPending: context.TotpPending,
OAuthName: context.OAuthName, OAuthName: context.OAuthName,
OAuthSub: context.OAuthSub,
} }
if err != nil { if err != nil {

View File

@@ -44,6 +44,7 @@ var userContext = config.UserContext{
TotpPending: false, TotpPending: false,
OAuthGroups: "", OAuthGroups: "",
TotpEnabled: false, TotpEnabled: false,
OAuthSub: "",
} }
func setupContextController(middlewares *[]gin.HandlerFunc) (*gin.Engine, *httptest.ResponseRecorder) { func setupContextController(middlewares *[]gin.HandlerFunc) (*gin.Engine, *httptest.ResponseRecorder) {

View File

@@ -197,6 +197,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
Provider: req.Provider, Provider: req.Provider,
OAuthGroups: utils.CoalesceToString(user.Groups), OAuthGroups: utils.CoalesceToString(user.Groups),
OAuthName: service.GetName(), OAuthName: service.GetName(),
OAuthSub: user.Sub,
} }
log.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie") log.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")

View File

@@ -239,6 +239,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
c.Header("Remote-Name", utils.SanitizeHeader(userContext.Name)) c.Header("Remote-Name", utils.SanitizeHeader(userContext.Name))
c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email)) c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email))
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups)) c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
c.Header("Remote-Sub", utils.SanitizeHeader(userContext.OAuthSub))
controller.setHeaders(c, acls) controller.setHeaders(c, acls)

View File

@@ -41,7 +41,7 @@ func setupProxyController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.En
assert.NilError(t, dockerService.Init()) assert.NilError(t, dockerService.Init())
// Access controls // Access controls
accessControlsService := service.NewAccessControlsService(dockerService) accessControlsService := service.NewAccessControlsService(dockerService, map[string]config.App{})
assert.NilError(t, accessControlsService.Init()) assert.NilError(t, accessControlsService.Init())

View File

@@ -99,6 +99,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
Provider: cookie.Provider, Provider: cookie.Provider,
OAuthGroups: cookie.OAuthGroups, OAuthGroups: cookie.OAuthGroups,
OAuthName: cookie.OAuthName, OAuthName: cookie.OAuthName,
OAuthSub: cookie.OAuthSub,
IsLoggedIn: true, IsLoggedIn: true,
OAuth: true, OAuth: true,
}) })

View File

@@ -10,4 +10,5 @@ type Session struct {
OAuthGroups string `gorm:"column:oauth_groups"` OAuthGroups string `gorm:"column:oauth_groups"`
Expiry int64 `gorm:"column:expiry"` Expiry int64 `gorm:"column:expiry"`
OAuthName string `gorm:"column:oauth_name"` OAuthName string `gorm:"column:oauth_name"`
OAuthSub string `gorm:"column:oauth_sub"`
} }

View File

@@ -1,122 +1,54 @@
package service package service
import ( import (
"errors"
"strings"
"github.com/rs/zerolog/log"
"github.com/steveiliop56/tinyauth/internal/config" "github.com/steveiliop56/tinyauth/internal/config"
) )
/*
Environment variable/flag based ACLs are disabled until v5 due to a technical challenge
with the current parsing logic.
The current parser works for simple OAuth provider configs like:
- PROVIDERS_MY_AMAZING_PROVIDER_CLIENT_ID
However, it breaks down when handling nested structs required for ACLs. The custom parsing
solution that worked for v4 OAuth providers is incompatible with the ACL parsing logic,
making the codebase unmaintainable and fragile.
A solution is being considered for v5 that would standardize the format to something like:
- TINYAUTH_PROVIDERS_GOOGLE_CLIENTSECRET
- TINYAUTH_APPS_MYAPP_CONFIG_DOMAIN
This would allow the Traefik parser to handle everything consistently, but requires a
config migration. Until this is resolved, environment-based ACLs are disabled and only
Docker label-based ACLs are supported.
See: https://discord.com/channels/1337450123600465984/1337459086270271538/1434986689935179838 for more information
*/
type AccessControlsService struct { type AccessControlsService struct {
docker *DockerService docker *DockerService
// envACLs config.Apps static map[string]config.App
} }
func NewAccessControlsService(docker *DockerService) *AccessControlsService { func NewAccessControlsService(docker *DockerService, static map[string]config.App) *AccessControlsService {
return &AccessControlsService{ return &AccessControlsService{
docker: docker, docker: docker,
static: static,
} }
} }
func (acls *AccessControlsService) Init() error { func (acls *AccessControlsService) Init() error {
// acls.envACLs = config.Apps{} return nil // No initialization needed
// 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
return nil
} }
// func (acls *AccessControlsService) loadEnvACLs(appEnvVars []string) error { func (acls *AccessControlsService) lookupStaticACLs(domain string) (config.App, error) {
// if len(appEnvVars) == 0 { for app, config := range acls.static {
// return nil if config.Config.Domain == domain {
// } log.Debug().Str("name", app).Msg("Found matching container by domain")
return config, nil
}
// envAcls := map[string]string{} if strings.SplitN(domain, ".", 2)[0] == app {
log.Debug().Str("name", app).Msg("Found matching container by app name")
return config, nil
}
}
return config.App{}, errors.New("no results")
}
// for _, e := range appEnvVars { func (acls *AccessControlsService) GetAccessControls(domain string) (config.App, error) {
// parts := strings.SplitN(e, "=", 2) // First check in the static config
// if len(parts) != 2 { app, err := acls.lookupStaticACLs(domain)
// continue
// }
// key := parts[0] if err == nil {
// key = strings.ToLower(key) log.Debug().Msg("Using ACls from static configuration")
// key = strings.ReplaceAll(key, "_", ".") return app, nil
// 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 // Fallback to Docker labels
return acls.docker.GetLabels(appDomain) log.Debug().Msg("Falling back to Docker labels for ACLs")
return acls.docker.GetLabels(domain)
} }

View File

@@ -213,6 +213,7 @@ func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.Sessio
OAuthGroups: data.OAuthGroups, OAuthGroups: data.OAuthGroups,
Expiry: time.Now().Add(time.Duration(expiry) * time.Second).Unix(), Expiry: time.Now().Add(time.Duration(expiry) * time.Second).Unix(),
OAuthName: data.OAuthName, OAuthName: data.OAuthName,
OAuthSub: data.OAuthSub,
} }
err = gorm.G[model.Session](auth.database).Create(c, &session) err = gorm.G[model.Session](auth.database).Create(c, &session)
@@ -314,6 +315,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie,
TotpPending: session.TOTPPending, TotpPending: session.TOTPPending,
OAuthGroups: session.OAuthGroups, OAuthGroups: session.OAuthGroups,
OAuthName: session.OAuthName, OAuthName: session.OAuthName,
OAuthSub: session.OAuthSub,
}, nil }, nil
} }

View File

@@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strconv"
"time" "time"
"github.com/steveiliop56/tinyauth/internal/config" "github.com/steveiliop56/tinyauth/internal/config"
@@ -27,6 +28,7 @@ type GithubEmailResponse []struct {
type GithubUserInfoResponse struct { type GithubUserInfoResponse struct {
Login string `json:"login"` Login string `json:"login"`
Name string `json:"name"` Name string `json:"name"`
ID int `json:"id"`
} }
type GithubOAuthService struct { type GithubOAuthService struct {
@@ -172,6 +174,7 @@ func (github *GithubOAuthService) Userinfo() (config.Claims, error) {
user.PreferredUsername = userInfo.Login user.PreferredUsername = userInfo.Login
user.Name = userInfo.Name user.Name = userInfo.Name
user.Sub = strconv.Itoa(userInfo.ID)
return user, nil return user, nil
} }

View File

@@ -17,12 +17,7 @@ import (
"golang.org/x/oauth2/endpoints" "golang.org/x/oauth2/endpoints"
) )
var GoogleOAuthScopes = []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"} var GoogleOAuthScopes = []string{"openid", "email", "profile"}
type GoogleUserInfoResponse struct {
Email string `json:"email"`
Name string `json:"name"`
}
type GoogleOAuthService struct { type GoogleOAuthService struct {
config oauth2.Config config oauth2.Config
@@ -91,7 +86,7 @@ func (google *GoogleOAuthService) Userinfo() (config.Claims, error) {
client := google.config.Client(google.context, google.token) client := google.config.Client(google.context, google.token)
res, err := client.Get("https://www.googleapis.com/userinfo/v2/me") res, err := client.Get("https://openidconnect.googleapis.com/v1/userinfo")
if err != nil { if err != nil {
return config.Claims{}, err return config.Claims{}, err
} }
@@ -106,16 +101,12 @@ func (google *GoogleOAuthService) Userinfo() (config.Claims, error) {
return config.Claims{}, err return config.Claims{}, err
} }
var userInfo GoogleUserInfoResponse err = json.Unmarshal(body, &user)
err = json.Unmarshal(body, &userInfo)
if err != nil { if err != nil {
return config.Claims{}, err return config.Claims{}, err
} }
user.PreferredUsername = strings.Split(userInfo.Email, "@")[0] user.PreferredUsername = strings.SplitN(user.Email, "@", 2)[0]
user.Name = userInfo.Name
user.Email = userInfo.Email
return user, nil return user, nil
} }

1
paerser Submodule

Submodule paerser added at 7e1b633ba9

95
patches/nested_maps.diff Normal file
View File

@@ -0,0 +1,95 @@
diff --git a/env/env_test.go b/env/env_test.go
index 7045569..365dc00 100644
--- a/env/env_test.go
+++ b/env/env_test.go
@@ -166,6 +166,38 @@ func TestDecode(t *testing.T) {
Foo: &struct{ Field string }{},
},
},
+ {
+ desc: "map under the root key",
+ environ: []string{"TRAEFIK_FOO_BAR_FOOBAR_BARFOO=foo"},
+ element: &struct {
+ Foo map[string]struct {
+ Foobar struct {
+ Barfoo string
+ }
+ }
+ }{},
+ expected: &struct {
+ Foo map[string]struct {
+ Foobar struct {
+ Barfoo string
+ }
+ }
+ }{
+ Foo: map[string]struct {
+ Foobar struct {
+ Barfoo string
+ }
+ }{
+ "bar": {
+ Foobar: struct {
+ Barfoo string
+ }{
+ Barfoo: "foo",
+ },
+ },
+ },
+ },
+ },
}
for _, test := range testCases {
diff --git a/parser/nodes_metadata.go b/parser/nodes_metadata.go
index 36946c1..0279705 100644
--- a/parser/nodes_metadata.go
+++ b/parser/nodes_metadata.go
@@ -75,8 +75,13 @@ func (m metadata) add(rootType reflect.Type, node *Node) error {
node.Kind = fType.Kind()
node.Tag = field.Tag
- if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Pointer && fType.Elem().Kind() == reflect.Struct ||
- fType.Kind() == reflect.Map {
+ if node.Kind == reflect.String && len(node.Children) > 0 {
+ fType = reflect.TypeOf(struct{}{})
+ node.Kind = reflect.Struct
+ }
+
+ if node.Kind == reflect.Struct || node.Kind == reflect.Pointer && fType.Elem().Kind() == reflect.Struct ||
+ node.Kind == reflect.Map {
if len(node.Children) == 0 && !(field.Tag.Get(m.TagName) == TagLabelAllowEmpty || field.Tag.Get(m.TagName) == "-") {
return fmt.Errorf("%s cannot be a standalone element (type %s)", node.Name, fType)
}
@@ -90,11 +95,11 @@ func (m metadata) add(rootType reflect.Type, node *Node) error {
return nil
}
- if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Pointer && fType.Elem().Kind() == reflect.Struct {
+ if node.Kind == reflect.Struct || node.Kind == reflect.Pointer && fType.Elem().Kind() == reflect.Struct {
return m.browseChildren(fType, node)
}
- if fType.Kind() == reflect.Map {
+ if node.Kind == reflect.Map {
if fType.Elem().Kind() == reflect.Interface {
addRawValue(node)
return nil
@@ -115,7 +120,7 @@ func (m metadata) add(rootType reflect.Type, node *Node) error {
return nil
}
- if fType.Kind() == reflect.Slice {
+ if node.Kind == reflect.Slice {
if m.AllowSliceAsStruct && field.Tag.Get(TagLabelSliceAsStruct) != "" {
return m.browseChildren(fType.Elem(), node)
}
@@ -129,7 +134,7 @@ func (m metadata) add(rootType reflect.Type, node *Node) error {
return nil
}
- return fmt.Errorf("invalid node %s: %v", node.Name, fType.Kind())
+ return fmt.Errorf("invalid node %s: %v", node.Name, node.Kind)
}
func (m metadata) findTypedField(rType reflect.Type, node *Node) (reflect.StructField, error) {