Compare commits

...

1 Commits

Author SHA1 Message Date
Stavros
6112f977ea feat: auto generate example env file (#647)
* feat: auto generate example env file

* refactor: simplify build paths func and better slice handling

* chore: forgot to stage everything

* chore: review comments

* refactor: remove square brackets because they mess up the syntax
highlighting

* refactor: use lowercase name to mark dynamic values
2026-02-16 23:39:05 +02:00
6 changed files with 414 additions and 139 deletions

View File

@@ -1,99 +1,221 @@
# Base Configuration
# Tinyauth example configuration
# The base URL where Tinyauth is accessible
TINYAUTH_APPURL="https://auth.example.com"
# Directory for static resources
TINYAUTH_RESOURCESDIR="/data/resources"
# Path to SQLite database file
TINYAUTH_DATABASEPATH="/data/tinyauth.db"
# Disable version heartbeat
TINYAUTH_DISABLEANALYTICS="false"
# Disable static resource serving
TINYAUTH_DISABLERESOURCES="false"
# The base URL where the app is hosted.
TINYAUTH_APPURL=
# Logging Configuration
# The directory where resources are stored.
TINYAUTH_RESOURCESDIR="./resources"
# Log level: trace, debug, info, warn, error
TINYAUTH_LOG_LEVEL="info"
# Enable JSON formatted logs
TINYAUTH_LOG_JSON="false"
# Specific Log stream configurations
# APP and HTTP log streams are enabled by default, and use the global log level unless overridden
TINYAUTH_LOG_STREAMS_APP_ENABLED="true"
TINYAUTH_LOG_STREAMS_APP_LEVEL="info"
TINYAUTH_LOG_STREAMS_HTTP_ENABLED="true"
TINYAUTH_LOG_STREAMS_HTTP_LEVEL="info"
TINYAUTH_LOG_STREAMS_AUDIT_ENABLED="false"
TINYAUTH_LOG_STREAMS_AUDIT_LEVEL="info"
# The path to the database file.
TINYAUTH_DATABASEPATH="./tinyauth.db"
# Server Configuration
# Disable analytics.
TINYAUTH_DISABLEANALYTICS=false
# Port to listen on
TINYAUTH_SERVER_PORT="3000"
# Interface to bind to (0.0.0.0 for all interfaces)
# Disable resources server.
TINYAUTH_DISABLERESOURCES=false
# The port on which the server listens.
TINYAUTH_SERVER_PORT=3000
# The address on which the server listens.
TINYAUTH_SERVER_ADDRESS="0.0.0.0"
# Unix socket path (optional, overrides port/address if set)
TINYAUTH_SERVER_SOCKETPATH=""
# Authentication Configuration
# The path to the Unix socket.
TINYAUTH_SERVER_SOCKETPATH=
# Format: username:bcrypt_hash (use bcrypt to generate hash)
TINYAUTH_AUTH_USERS="admin:$2a$10$example_bcrypt_hash_here"
# Path to external users file (optional)
TINYAUTH_AUTH_USERSFILE=""
# Enable secure cookies (requires HTTPS)
TINYAUTH_AUTH_SECURECOOKIE="true"
# Session expiry in seconds (7200 = 2 hours)
TINYAUTH_AUTH_SESSIONEXPIRY="7200"
# Session maximum lifetime in seconds (0 = unlimited)
TINYAUTH_AUTH_SESSIONMAXLIFETIME="0"
# Login timeout in seconds (300 = 5 minutes)
TINYAUTH_AUTH_LOGINTIMEOUT="300"
# Maximum login retries before lockout
TINYAUTH_AUTH_LOGINMAXRETRIES="5"
# Comma-separated list of trusted proxy IPs/CIDRs
TINYAUTH_AUTH_TRUSTEDPROXIES=""
# List of allowed IPs or CIDR ranges.
TINYAUTH_AUTH_IP_ALLOW=
# OAuth Configuration
# List of blocked IPs or CIDR ranges.
TINYAUTH_AUTH_IP_BLOCK=
# Regex pattern for allowed email addresses (e.g., /@example\.com$/)
TINYAUTH_OAUTH_WHITELIST=""
# Provider ID to auto-redirect to (skips login page)
TINYAUTH_OAUTH_AUTOREDIRECT=""
# OAuth Provider Configuration (replace MYPROVIDER with your provider name)
TINYAUTH_OAUTH_PROVIDERS_MYPROVIDER_CLIENTID="your_client_id_here"
TINYAUTH_OAUTH_PROVIDERS_MYPROVIDER_CLIENTSECRET="your_client_secret_here"
TINYAUTH_OAUTH_PROVIDERS_MYPROVIDER_AUTHURL="https://provider.example.com/oauth/authorize"
TINYAUTH_OAUTH_PROVIDERS_MYPROVIDER_TOKENURL="https://provider.example.com/oauth/token"
TINYAUTH_OAUTH_PROVIDERS_MYPROVIDER_USERINFOURL="https://provider.example.com/oauth/userinfo"
TINYAUTH_OAUTH_PROVIDERS_MYPROVIDER_REDIRECTURL="https://auth.example.com/oauth/callback/myprovider"
TINYAUTH_OAUTH_PROVIDERS_MYPROVIDER_SCOPES="openid email profile"
TINYAUTH_OAUTH_PROVIDERS_MYPROVIDER_NAME="My OAuth Provider"
# Allow self-signed certificates
TINYAUTH_OAUTH_PROVIDERS_MYPROVIDER_INSECURE="false"
# Comma-separated list of users (username:hashed_password).
TINYAUTH_AUTH_USERS=
# UI Customization
# Path to the users file.
TINYAUTH_AUTH_USERSFILE=
# Custom title for login page
# Enable secure cookies.
TINYAUTH_AUTH_SECURECOOKIE=false
# Session expiry time in seconds.
TINYAUTH_AUTH_SESSIONEXPIRY=86400
# Maximum session lifetime in seconds.
TINYAUTH_AUTH_SESSIONMAXLIFETIME=0
# Login timeout in seconds.
TINYAUTH_AUTH_LOGINTIMEOUT=300
# Maximum login retries.
TINYAUTH_AUTH_LOGINMAXRETRIES=3
# Comma-separated list of trusted proxy addresses.
TINYAUTH_AUTH_TRUSTEDPROXIES=
# The domain of the app.
TINYAUTH_APPS_name_CONFIG_DOMAIN=
# Comma-separated list of allowed users.
TINYAUTH_APPS_name_USERS_ALLOW=
# Comma-separated list of blocked users.
TINYAUTH_APPS_name_USERS_BLOCK=
# Comma-separated list of allowed OAuth groups.
TINYAUTH_APPS_name_OAUTH_WHITELIST=
# Comma-separated list of required OAuth groups.
TINYAUTH_APPS_name_OAUTH_GROUPS=
# List of allowed IPs or CIDR ranges.
TINYAUTH_APPS_name_IP_ALLOW=
# List of blocked IPs or CIDR ranges.
TINYAUTH_APPS_name_IP_BLOCK=
# List of IPs or CIDR ranges that bypass authentication.
TINYAUTH_APPS_name_IP_BYPASS=
# Custom headers to add to the response.
TINYAUTH_APPS_name_RESPONSE_HEADERS=
# Basic auth username.
TINYAUTH_APPS_name_RESPONSE_BASICAUTH_USERNAME=
# Basic auth password.
TINYAUTH_APPS_name_RESPONSE_BASICAUTH_PASSWORD=
# Path to the file containing the basic auth password.
TINYAUTH_APPS_name_RESPONSE_BASICAUTH_PASSWORDFILE=
# Comma-separated list of allowed paths.
TINYAUTH_APPS_name_PATH_ALLOW=
# Comma-separated list of blocked paths.
TINYAUTH_APPS_name_PATH_BLOCK=
# Comma-separated list of required LDAP groups.
TINYAUTH_APPS_name_LDAP_GROUPS=
# Comma-separated list of allowed OAuth domains.
TINYAUTH_OAUTH_WHITELIST=
# The OAuth provider to use for automatic redirection.
TINYAUTH_OAUTH_AUTOREDIRECT=
# OAuth client ID.
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTID=
# OAuth client secret.
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRET=
# Path to the file containing the OAuth client secret.
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRETFILE=
# OAuth scopes.
TINYAUTH_OAUTH_PROVIDERS_name_SCOPES=
# OAuth redirect URL.
TINYAUTH_OAUTH_PROVIDERS_name_REDIRECTURL=
# OAuth authorization URL.
TINYAUTH_OAUTH_PROVIDERS_name_AUTHURL=
# OAuth token URL.
TINYAUTH_OAUTH_PROVIDERS_name_TOKENURL=
# OAuth userinfo URL.
TINYAUTH_OAUTH_PROVIDERS_name_USERINFOURL=
# Allow insecure OAuth connections.
TINYAUTH_OAUTH_PROVIDERS_name_INSECURE=false
# Provider name in UI.
TINYAUTH_OAUTH_PROVIDERS_name_NAME=
# Path to the private key file.
TINYAUTH_OIDC_PRIVATEKEYPATH="./tinyauth_oidc_key"
# Path to the public key file.
TINYAUTH_OIDC_PUBLICKEYPATH="./tinyauth_oidc_key.pub"
# OIDC client ID.
TINYAUTH_OIDC_CLIENTS_name_CLIENTID=
# OIDC client secret.
TINYAUTH_OIDC_CLIENTS_name_CLIENTSECRET=
# Path to the file containing the OIDC client secret.
TINYAUTH_OIDC_CLIENTS_name_CLIENTSECRETFILE=
# List of trusted redirect URIs.
TINYAUTH_OIDC_CLIENTS_name_TRUSTEDREDIRECTURIS=
# Client name in UI.
TINYAUTH_OIDC_CLIENTS_name_NAME=
# The title of the UI.
TINYAUTH_UI_TITLE="Tinyauth"
# Message shown on forgot password page
TINYAUTH_UI_FORGOTPASSWORDMESSAGE="Contact your administrator to reset your password"
# Background image URL for login page
TINYAUTH_UI_BACKGROUNDIMAGE=""
# Disable UI warning messages
TINYAUTH_UI_DISABLEWARNINGS="false"
# LDAP Configuration
# Message displayed on the forgot password page.
TINYAUTH_UI_FORGOTPASSWORDMESSAGE="You can change your password by changing the configuration."
# Path to the background image.
TINYAUTH_UI_BACKGROUNDIMAGE="/background.jpg"
# Disable UI warnings.
TINYAUTH_UI_DISABLEWARNINGS=false
# LDAP server address.
TINYAUTH_LDAP_ADDRESS=
# Bind DN for LDAP authentication.
TINYAUTH_LDAP_BINDDN=
# Bind password for LDAP authentication.
TINYAUTH_LDAP_BINDPASSWORD=
# Base DN for LDAP searches.
TINYAUTH_LDAP_BASEDN=
# Allow insecure LDAP connections.
TINYAUTH_LDAP_INSECURE=false
# LDAP search filter.
TINYAUTH_LDAP_SEARCHFILTER="(uid=%s)"
# Certificate for mTLS authentication.
TINYAUTH_LDAP_AUTHCERT=
# Certificate key for mTLS authentication.
TINYAUTH_LDAP_AUTHKEY=
# Cache duration for LDAP group membership in seconds.
TINYAUTH_LDAP_GROUPCACHETTL=900
# Log level (trace, debug, info, warn, error).
TINYAUTH_LOG_LEVEL="info"
# Enable JSON formatted logs.
TINYAUTH_LOG_JSON=false
# Enable this log stream.
TINYAUTH_LOG_STREAMS_HTTP_ENABLED=true
# Log level for this stream. Use global if empty.
TINYAUTH_LOG_STREAMS_HTTP_LEVEL=
# Enable this log stream.
TINYAUTH_LOG_STREAMS_APP_ENABLED=true
# Log level for this stream. Use global if empty.
TINYAUTH_LOG_STREAMS_APP_LEVEL=
# Enable this log stream.
TINYAUTH_LOG_STREAMS_AUDIT_ENABLED=false
# Log level for this stream. Use global if empty.
TINYAUTH_LOG_STREAMS_AUDIT_LEVEL=
# LDAP server address
TINYAUTH_LDAP_ADDRESS="ldap://ldap.example.com:389"
# DN for binding to LDAP server
TINYAUTH_LDAP_BINDDN="cn=readonly,dc=example,dc=com"
# Password for bind DN
TINYAUTH_LDAP_BINDPASSWORD="your_bind_password"
# Base DN for user searches
TINYAUTH_LDAP_BASEDN="dc=example,dc=com"
# Search filter (%s will be replaced with username)
TINYAUTH_LDAP_SEARCHFILTER="(&(uid=%s)(memberOf=cn=users,ou=groups,dc=example,dc=com))"
# Allow insecure LDAP connections
TINYAUTH_LDAP_INSECURE="false"

View File

@@ -60,11 +60,11 @@ test:
go test -v ./...
# Development
develop:
dev:
docker compose -f $(DEV_COMPOSE) up --force-recreate --pull=always --remove-orphans --build
# Development - Infisical
develop-infisical:
dev-infisical:
infisical run --env=dev -- docker compose -f $(DEV_COMPOSE) up --force-recreate --pull=always --remove-orphans --build
# Production
@@ -79,3 +79,7 @@ prod-infisical:
.PHONY: sql
sql:
sqlc generate
# Go gen
generate:
go run ./gen

View File

@@ -12,60 +12,8 @@ import (
"github.com/traefik/paerser/cli"
)
func NewTinyauthCmdConfiguration() *config.Config {
return &config.Config{
ResourcesDir: "./resources",
DatabasePath: "./tinyauth.db",
Server: config.ServerConfig{
Port: 3000,
Address: "0.0.0.0",
},
Auth: config.AuthConfig{
SessionExpiry: 86400, // 1 day
SessionMaxLifetime: 0, // disabled
LoginTimeout: 300, // 5 minutes
LoginMaxRetries: 3,
},
UI: config.UIConfig{
Title: "Tinyauth",
ForgotPasswordMessage: "You can change your password by changing the configuration.",
BackgroundImage: "/background.jpg",
},
Ldap: config.LdapConfig{
Insecure: false,
SearchFilter: "(uid=%s)",
GroupCacheTTL: 900, // 15 minutes
},
Log: config.LogConfig{
Level: "info",
Json: false,
Streams: config.LogStreams{
HTTP: config.LogStreamConfig{
Enabled: true,
Level: "",
},
App: config.LogStreamConfig{
Enabled: true,
Level: "",
},
Audit: config.LogStreamConfig{
Enabled: false,
Level: "",
},
},
},
OIDC: config.OIDCConfig{
PrivateKeyPath: "./tinyauth_oidc_key",
PublicKeyPath: "./tinyauth_oidc_key.pub",
},
Experimental: config.ExperimentalConfig{
ConfigFile: "",
},
}
}
func main() {
tConfig := NewTinyauthCmdConfiguration()
tConfig := config.NewDefaultConfiguration()
loaders := []cli.ResourceLoader{
&loaders.FileLoader{},

11
gen/gen.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import (
"log/slog"
)
func main() {
slog.Info("generating example env file")
generateExampleEnv()
}

137
gen/gen_env.go Normal file
View File

@@ -0,0 +1,137 @@
package main
import (
"bytes"
"fmt"
"log/slog"
"os"
"reflect"
"strings"
"github.com/steveiliop56/tinyauth/internal/config"
)
type Path struct {
Name string
Description string
Value any
}
func generateExampleEnv() {
cfg := config.NewDefaultConfiguration()
paths := make([]Path, 0)
root := reflect.TypeOf(cfg).Elem()
rootValue := reflect.ValueOf(cfg).Elem()
rootPath := "TINYAUTH_"
buildPaths(root, rootValue, rootPath, &paths)
compiled := compileEnv(paths)
err := os.Remove(".env.example")
if err != nil {
slog.Error("failed to remove example env file", "error", err)
os.Exit(1)
}
err = os.WriteFile(".env.example", compiled, 0644)
if err != nil {
slog.Error("failed to write example env file", "error", err)
os.Exit(1)
}
}
func buildPaths(parent reflect.Type, parentValue reflect.Value, parentPath string, paths *[]Path) {
for i := 0; i < parent.NumField(); i++ {
field := parent.Field(i)
fieldType := field.Type
fieldValue := parentValue.Field(i)
switch fieldType.Kind() {
case reflect.Struct:
childPath := parentPath + strings.ToUpper(field.Name) + "_"
buildPaths(fieldType, fieldValue, childPath, paths)
case reflect.Map:
buildMapPaths(field, parentPath, paths)
case reflect.Bool, reflect.String, reflect.Slice, reflect.Int:
buildPath(field, fieldValue, parentPath, paths)
default:
slog.Info("unknown type", "type", fieldType.Kind())
}
}
}
func buildPath(field reflect.StructField, fieldValue reflect.Value, parent string, paths *[]Path) {
desc := field.Tag.Get("description")
yamlTag := field.Tag.Get("yaml")
// probably internal logic, should be skipped
if yamlTag == "-" {
return
}
defaultValue := fieldValue.Interface()
path := Path{
Name: parent + strings.ToUpper(field.Name),
Description: desc,
}
switch fieldValue.Kind() {
case reflect.Slice:
sl, ok := defaultValue.([]string)
if !ok {
slog.Error("invalid default value", "value", defaultValue)
return
}
path.Value = strings.Join(sl, ",")
case reflect.String:
st, ok := defaultValue.(string)
if !ok {
slog.Error("invalid default value", "value", defaultValue)
return
}
// good idea to escape strings probably
if st != "" {
path.Value = fmt.Sprintf(`"%s"`, st)
} else {
path.Value = ""
}
default:
path.Value = defaultValue
}
*paths = append(*paths, path)
}
func buildMapPaths(field reflect.StructField, parentPath string, paths *[]Path) {
fieldType := field.Type
if fieldType.Key().Kind() != reflect.String {
slog.Info("unsupported map key type", "type", fieldType.Key().Kind())
return
}
mapPath := parentPath + strings.ToUpper(field.Name) + "_name_"
valueType := fieldType.Elem()
if valueType.Kind() == reflect.Struct {
zeroValue := reflect.New(valueType).Elem()
buildPaths(valueType, zeroValue, mapPath, paths)
}
}
func compileEnv(paths []Path) []byte {
buffer := bytes.Buffer{}
buffer.WriteString("# Tinyauth example configuration\n\n")
for _, path := range paths {
buffer.WriteString("# ")
buffer.WriteString(path.Description)
buffer.WriteString("\n")
buffer.WriteString(path.Name)
buffer.WriteString("=")
fmt.Fprintf(&buffer, "%v", path.Value)
buffer.WriteString("\n\n")
}
return buffer.Bytes()
}

View File

@@ -1,5 +1,58 @@
package config
// Default configuration
func NewDefaultConfiguration() *Config {
return &Config{
ResourcesDir: "./resources",
DatabasePath: "./tinyauth.db",
Server: ServerConfig{
Port: 3000,
Address: "0.0.0.0",
},
Auth: AuthConfig{
SessionExpiry: 86400, // 1 day
SessionMaxLifetime: 0, // disabled
LoginTimeout: 300, // 5 minutes
LoginMaxRetries: 3,
},
UI: UIConfig{
Title: "Tinyauth",
ForgotPasswordMessage: "You can change your password by changing the configuration.",
BackgroundImage: "/background.jpg",
},
Ldap: LdapConfig{
Insecure: false,
SearchFilter: "(uid=%s)",
GroupCacheTTL: 900, // 15 minutes
},
Log: LogConfig{
Level: "info",
Json: false,
Streams: LogStreams{
HTTP: LogStreamConfig{
Enabled: true,
Level: "",
},
App: LogStreamConfig{
Enabled: true,
Level: "",
},
Audit: LogStreamConfig{
Enabled: false,
Level: "",
},
},
},
OIDC: OIDCConfig{
PrivateKeyPath: "./tinyauth_oidc_key",
PublicKeyPath: "./tinyauth_oidc_key.pub",
},
Experimental: ExperimentalConfig{
ConfigFile: "",
},
}
}
// Version information, set at build time
var Version = "development"