Compare commits

..

1 Commits

Author SHA1 Message Date
Stavros 748768d071 feat: make config file a stable feature 2026-05-24 20:27:55 +03:00
10 changed files with 103 additions and 101 deletions
+66 -5
View File
@@ -7,7 +7,9 @@ TINYAUTH_APPURL=
# database config
# The path to the database, including file name.
# The database driver to use. Valid values: sqlite, memory.
TINYAUTH_DATABASE_DRIVER="sqlite"
# The path to the SQLite database, including file name. Only used when driver is sqlite.
TINYAUTH_DATABASE_PATH="./tinyauth.db"
# analytics config
@@ -30,6 +32,8 @@ TINYAUTH_SERVER_PORT=3000
TINYAUTH_SERVER_ADDRESS="0.0.0.0"
# The path to the Unix socket.
TINYAUTH_SERVER_SOCKETPATH=
# Enable listening on both TCP and Unix socket at the same time.
TINYAUTH_SERVER_CONCURRENTLISTENERSENABLED=false
# auth config
@@ -37,8 +41,52 @@ TINYAUTH_SERVER_SOCKETPATH=
TINYAUTH_AUTH_IP_ALLOW=
# List of blocked IPs or CIDR ranges.
TINYAUTH_AUTH_IP_BLOCK=
# List of IPs or CIDR ranges that bypass authentication entirely.
TINYAUTH_AUTH_IP_BYPASS=
# Comma-separated list of users (username:hashed_password).
TINYAUTH_AUTH_USERS=
# Enable subdomains support.
TINYAUTH_AUTH_SUBDOMAINSENABLED=true
# Full name of the user.
TINYAUTH_AUTH_USERATTRIBUTES_name_NAME=
# Given (first) name of the user.
TINYAUTH_AUTH_USERATTRIBUTES_name_GIVENNAME=
# Family (last) name of the user.
TINYAUTH_AUTH_USERATTRIBUTES_name_FAMILYNAME=
# Middle name of the user.
TINYAUTH_AUTH_USERATTRIBUTES_name_MIDDLENAME=
# Nickname of the user.
TINYAUTH_AUTH_USERATTRIBUTES_name_NICKNAME=
# URL of the user's profile page.
TINYAUTH_AUTH_USERATTRIBUTES_name_PROFILE=
# URL of the user's profile picture.
TINYAUTH_AUTH_USERATTRIBUTES_name_PICTURE=
# URL of the user's website.
TINYAUTH_AUTH_USERATTRIBUTES_name_WEBSITE=
# Email address of the user.
TINYAUTH_AUTH_USERATTRIBUTES_name_EMAIL=
# Gender of the user.
TINYAUTH_AUTH_USERATTRIBUTES_name_GENDER=
# Birthdate of the user (YYYY-MM-DD).
TINYAUTH_AUTH_USERATTRIBUTES_name_BIRTHDATE=
# Time zone of the user (e.g. Europe/Athens).
TINYAUTH_AUTH_USERATTRIBUTES_name_ZONEINFO=
# Locale of the user (e.g. en-US).
TINYAUTH_AUTH_USERATTRIBUTES_name_LOCALE=
# Phone number of the user.
TINYAUTH_AUTH_USERATTRIBUTES_name_PHONENUMBER=
# Full mailing address, formatted for display.
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_FORMATTED=
# Street address.
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_STREETADDRESS=
# City or locality.
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_LOCALITY=
# State, province, or region.
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_REGION=
# Zip or postal code.
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_POSTALCODE=
# Country.
TINYAUTH_AUTH_USERATTRIBUTES_name_ADDRESS_COUNTRY=
# Path to the users file.
TINYAUTH_AUTH_USERSFILE=
# Enable secure cookies.
@@ -53,6 +101,8 @@ TINYAUTH_AUTH_LOGINTIMEOUT=300
TINYAUTH_AUTH_LOGINMAXRETRIES=3
# Comma-separated list of trusted proxy addresses.
TINYAUTH_AUTH_TRUSTEDPROXIES=
# ACL policy for allow-by-default or deny-by-default, available options are allow and deny, default is allow.
TINYAUTH_AUTH_ACLS_POLICY="allow"
# apps config
@@ -101,10 +151,6 @@ TINYAUTH_OAUTH_PROVIDERS_name_CLIENTID=
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRET=
# Path to the file containing the OAuth client secret.
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRETFILE=
# Comma-separated list of allowed OAuth domains for this provider.
TINYAUTH_OAUTH_PROVIDERS_name_WHITELIST=
# Path to the OAuth whitelist file for this provider.
TINYAUTH_OAUTH_PROVIDERS_name_WHITELISTFILE=
# OAuth scopes.
TINYAUTH_OAUTH_PROVIDERS_name_SCOPES=
# OAuth redirect URL.
@@ -168,6 +214,8 @@ TINYAUTH_LDAP_AUTHCERT=
TINYAUTH_LDAP_AUTHKEY=
# Cache duration for LDAP group membership in seconds.
TINYAUTH_LDAP_GROUPCACHETTL=900
# Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment.
TINYAUTH_LABELPROVIDER="auto"
# log config
@@ -187,3 +235,16 @@ TINYAUTH_LOG_STREAMS_APP_LEVEL=
TINYAUTH_LOG_STREAMS_AUDIT_ENABLED=false
# Log level for this stream. Use global if empty.
TINYAUTH_LOG_STREAMS_AUDIT_LEVEL=
# tailscale config
# Enable Tailscale integration.
TINYAUTH_TAILSCALE_ENABLED=false
# Tailscale state directory.
TINYAUTH_TAILSCALE_DIR="./tailscale_state"
# Tailscale hostname.
TINYAUTH_TAILSCALE_HOSTNAME=
# Tailscale auth key.
TINYAUTH_TAILSCALE_AUTHKEY=
# Use ephemeral Tailscale node.
TINYAUTH_TAILSCALE_EPHEMERAL=false
+6 -6
View File
@@ -156,7 +156,7 @@ jobs:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -214,7 +214,7 @@ jobs:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -272,7 +272,7 @@ jobs:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -330,7 +330,7 @@ jobs:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -384,7 +384,7 @@ jobs:
merge-multiple: true
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -423,7 +423,7 @@ jobs:
merge-multiple: true
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
+6 -6
View File
@@ -126,7 +126,7 @@ jobs:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -181,7 +181,7 @@ jobs:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -236,7 +236,7 @@ jobs:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -291,7 +291,7 @@ jobs:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -345,7 +345,7 @@ jobs:
merge-multiple: true
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -386,7 +386,7 @@ jobs:
merge-multiple: true
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
-7
View File
@@ -117,13 +117,6 @@ func (app *BootstrapApp) Setup() error {
app.runtime.OAuthProviders = app.config.OAuth.Providers
for id, provider := range app.runtime.OAuthProviders {
providerWhitelist, err := utils.GetStringList(provider.Whitelist, provider.WhitelistFile)
if err != nil {
return fmt.Errorf("failed to load oauth whitelist for provider %s: %w", id, err)
}
provider.Whitelist = providerWhitelist
secret := utils.GetSecret(provider.ClientSecret, provider.ClientSecretFile)
provider.ClientSecret = secret
provider.ClientSecretFile = ""
+16 -16
View File
@@ -183,23 +183,9 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
return
}
svc, err := controller.auth.GetOAuthService(sessionIdCookie)
if err != nil {
controller.log.App.Error().Err(err).Msg("Failed to get OAuth service for session")
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
return
}
if svc.ID() != req.Provider {
controller.log.App.Warn().Msgf("OAuth provider mismatch: expected %s, got %s", req.Provider, svc.ID())
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
return
}
if !controller.auth.IsEmailWhitelisted(svc.ID(), user.Email) {
if !controller.auth.IsEmailWhitelisted(user.Email) {
controller.log.App.Warn().Str("email", user.Email).Msg("Email not whitelisted, denying access")
controller.log.AuditLoginFailure(user.Email, svc.ID(), c.ClientIP(), "email not whitelisted")
controller.log.AuditLoginFailure(user.Email, req.Provider, c.ClientIP(), "email not whitelisted")
queries, err := query.Values(UnauthorizedQuery{
Username: user.Email,
@@ -240,6 +226,20 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
username = strings.Replace(user.Email, "@", "_", 1)
}
svc, err := controller.auth.GetOAuthService(sessionIdCookie)
if err != nil {
controller.log.App.Error().Err(err).Msg("Failed to get OAuth service for session")
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
return
}
if svc.ID() != req.Provider {
controller.log.App.Warn().Msgf("OAuth provider mismatch: expected %s, got %s", req.Provider, svc.ID())
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
return
}
sessionCookie := repository.Session{
Username: username,
Name: name,
+1 -1
View File
@@ -205,7 +205,7 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string, ip stri
return nil, nil, fmt.Errorf("oauth provider from session cookie not found: %s", userContext.OAuth.ID)
}
if !m.auth.IsEmailWhitelisted(userContext.OAuth.ID, userContext.OAuth.Email) {
if !m.auth.IsEmailWhitelisted(userContext.OAuth.Email) {
m.auth.DeleteSession(ctx, uuid)
return nil, nil, fmt.Errorf("email from session cookie not whitelisted: %s", userContext.OAuth.Email)
}
+3 -8
View File
@@ -62,9 +62,6 @@ func NewDefaultConfiguration() *Config {
PrivateKeyPath: "./tinyauth_oidc_key",
PublicKeyPath: "./tinyauth_oidc_key.pub",
},
Experimental: ExperimentalConfig{
ConfigFile: "",
},
Tailscale: TailscaleConfig{
Dir: "./tailscale_state",
},
@@ -88,6 +85,7 @@ type Config struct {
LabelProvider string `description:"Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment." yaml:"labelProvider"`
Log LogConfig `description:"Logging configuration." yaml:"log"`
Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale"`
ConfigFile string `description:"Path to config file." yaml:"-"`
}
type DatabaseConfig struct {
@@ -208,9 +206,8 @@ type LogStreamConfig struct {
Level string `description:"Log level for this stream. Use global if empty." yaml:"level"`
}
type ExperimentalConfig struct {
ConfigFile string `description:"Path to config file." yaml:"-"`
}
// no experimental features
type ExperimentalConfig struct{}
type TailscaleConfig struct {
Enabled bool `description:"Enable Tailscale integration." yaml:"enabled"`
@@ -226,8 +223,6 @@ type OAuthServiceConfig struct {
ClientID string `description:"OAuth client ID." yaml:"clientId"`
ClientSecret string `description:"OAuth client secret." yaml:"clientSecret"`
ClientSecretFile string `description:"Path to the file containing the OAuth client secret." yaml:"clientSecretFile"`
Whitelist []string `description:"Comma-separated list of allowed OAuth domains for this provider." yaml:"whitelist"`
WhitelistFile string `description:"Path to the OAuth whitelist file for this provider." yaml:"whitelistFile"`
Scopes []string `description:"OAuth scopes." yaml:"scopes"`
RedirectURL string `description:"OAuth redirect URL." yaml:"redirectUrl"`
AuthURL string `description:"OAuth authorization URL." yaml:"authUrl"`
+3 -8
View File
@@ -285,15 +285,10 @@ func (auth *AuthService) RecordLoginAttempt(identifier string, success bool) {
}
}
func (auth *AuthService) IsEmailWhitelisted(provider string, email string) bool {
whitelist := auth.runtime.OAuthWhitelist
if providerConfig, ok := auth.runtime.OAuthProviders[provider]; ok && len(providerConfig.Whitelist) > 0 {
whitelist = providerConfig.Whitelist
}
match, err := utils.CheckFilter(strings.Join(whitelist, ","), email)
func (auth *AuthService) IsEmailWhitelisted(email string) bool {
match, err := utils.CheckFilter(strings.Join(auth.runtime.OAuthWhitelist, ","), email)
if err != nil {
auth.log.App.Warn().Err(err).Str("provider", provider).Str("email", email).Msg("Invalid email filter pattern")
auth.log.App.Warn().Err(err).Str("email", email).Msg("Invalid email filter pattern")
return false
}
return match
-39
View File
@@ -1,39 +0,0 @@
package service
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
)
func TestIsEmailWhitelistedUsesProviderSpecificList(t *testing.T) {
log := logger.NewLogger().WithTestConfig()
log.Init()
auth := &AuthService{
log: log,
runtime: model.RuntimeConfig{
OAuthWhitelist: []string{"global@example.com"},
OAuthProviders: map[string]model.OAuthServiceConfig{
"github": {
Whitelist: []string{"github@example.com"},
},
"pocketid": {
Whitelist: []string{"pocket@example.com"},
},
"gitlab": {
Whitelist: []string{},
},
},
},
}
assert.True(t, auth.IsEmailWhitelisted("github", "github@example.com"))
assert.False(t, auth.IsEmailWhitelisted("github", "pocket@example.com"))
assert.True(t, auth.IsEmailWhitelisted("pocketid", "pocket@example.com"))
assert.True(t, auth.IsEmailWhitelisted("google", "global@example.com"))
assert.True(t, auth.IsEmailWhitelisted("gitlab", "global@example.com"))
assert.False(t, auth.IsEmailWhitelisted("gitlab", "unknown@example.com"))
}
+2 -5
View File
@@ -3,7 +3,6 @@ package loaders
import (
"os"
"github.com/rs/zerolog/log"
"github.com/tinyauthapp/paerser/cli"
"github.com/tinyauthapp/paerser/file"
"github.com/tinyauthapp/paerser/flag"
@@ -19,8 +18,8 @@ func (f *FileLoader) Load(args []string, cmd *cli.Command) (bool, error) {
}
// I guess we are using traefik as the root name (we can't change it)
configFileFlag := "traefik.experimental.configfile"
envVar := "TINYAUTH_EXPERIMENTAL_CONFIGFILE"
configFileFlag := "traefik.configfile"
envVar := "TINYAUTH_CONFIGFILE"
if _, ok := flags[configFileFlag]; !ok {
if value := os.Getenv(envVar); value != "" {
@@ -30,8 +29,6 @@ func (f *FileLoader) Load(args []string, cmd *cli.Command) (bool, error) {
}
}
log.Warn().Msg("Using experimental file config loader, this feature is experimental and may change or be removed in future releases")
err = file.Decode(flags[configFileFlag], cmd.Configuration)
if err != nil {